feat: 移植商户端订单管理到平台端 & 新增订单打印功能

1. 将商户端订单管理功能完整移植到平台端管理后台,包括:
   - 商户订单列表、退款单、预约管理(服务看板+工单管理)
   - 菜单名称加"商户"前缀,区别于平台端原有订单管理
   - 不影响平台端原有订单管理功能

2. 新增订单打印功能:
   - 前端:独立打印页面(无layout),支持浏览器打印
   - 后端:新增打印专用API,使用eb_sync_order_detail_staging表的product_name和info字段

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
AriadenCaseblg
2026-04-10 11:16:27 +08:00
parent de02c8a3e1
commit 61c5d964a3
30 changed files with 7911 additions and 0 deletions

View File

@@ -0,0 +1,383 @@
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
import request from '@/utils/request';
/**
* 商户订单 列表
* @param params
*/
export function merchantOrderListApi(params) {
return request({
url: '/admin/merchant/order/list',
method: 'get',
params,
});
}
/**
* 商户订单 列表 获取各状态数量
* @param params
*/
export function merchantOrderStatusNumApi(params) {
return request({
url: '/admin/merchant/order/status/num',
method: 'get',
params,
});
}
/**
* 商户订单 删除
* @param orderNo
*/
export function merchantOrderDeleteApi(orderNo) {
return request({
url: `/admin/merchant/order/delete/${orderNo}`,
method: 'post',
});
}
/**
* 商户订单 记录
* @param params
*/
export function merchantOrderLogApi(params) {
return request({
url: '/admin/store/order/status/list',
method: 'get',
params,
});
}
/**
* 商户订单 详情
* @param orderNo
*/
export function merchantOrderDetailApi(orderNo) {
return request({
url: `/admin/merchant/order/info/${orderNo}`,
method: 'get',
});
}
/**
* 商户订单 备注
* @param data
*/
export function merchantOrderMarkApi(data) {
return request({
url: '/admin/merchant/order/mark',
method: 'post',
data,
});
}
/**
* 商户订单 发货
* @param data
*/
export function merchantOrderSendApi(data) {
return request({
url: '/admin/merchant/order/send',
method: 'post',
data,
});
}
/**
* 商户订单 打印小票
* @param orderno
*/
export function merchantOrderPrintReceiptApi(orderno) {
return request({
url: '/admin/merchant/order/printreceipt/' + orderno,
method: 'get',
});
}
/**
* 商户订单 拒绝退款
* @param params
*/
export function merchantOrderRefuseApi(params) {
return request({
url: '/admin/merchant/refund/order/refund/refuse',
method: 'get',
params,
});
}
/**
* 商户订单 立即退款
* @param params
*/
export function merchantOrderRefundApi(params) {
return request({
url: '/admin/merchant/refund/order/refund',
method: 'get',
params,
});
}
/**
* 商户订单 统计 头部数据
*/
export function merchantOrderStatisticsApi() {
return request({
url: `/admin/store/order/statistics`,
method: 'get',
});
}
/**
* 商户 核销订单 月列表数据
*/
export function merchantStatisticsDataApi(params) {
return request({
url: `/admin/store/order/statisticsData`,
method: 'get',
params,
});
}
/**
* 商户 一键改价
*/
export function merchantUpdatePriceApi(data) {
return request({
url: `admin/store/order/update/price`,
method: 'post',
data,
});
}
/**
* 商户 订单统计详情
*/
export function merchantOrderTimeApi(params) {
return request({
url: `/admin/store/order/time`,
method: 'get',
params,
});
}
/**
* 商户 面单默认配置信息
*/
export function merchantSheetInfoApi() {
return request({
url: `/admin/store/order/sheet/info`,
method: 'get',
});
}
/**
* 商户订单 物流详情
* @param invoiceId
*/
export function merchantGetLogisticsInfoApi(invoiceId) {
return request({
url: `/admin/merchant/order/get/${invoiceId}/logistics/info`,
method: 'get',
});
}
/**
* 视频号物流公司
*/
export function merchantCompanyGetListApi() {
return request({
url: `/admin/pay/component/delivery/company/get/list`,
method: 'get',
});
}
/**
* 视频号发货
*/
export function merchantVideoSendApi(data) {
return request({
url: `/admin/store/order/video/send`,
method: 'post',
data,
});
}
/**
* 商户 打印小票
*/
export function merchantOrderPrint(id) {
return request({
url: `/admin/yly/print/${id}`,
method: 'get',
});
}
/**
* 商户 退款列表
*/
export function merchantRefundListApi(params) {
return request({
url: `/admin/merchant/refund/order/list`,
method: 'get',
params,
});
}
/**
* 商户 备注退款订单
*/
export function merchantRefundMarkApi(data) {
return request({
url: `/admin/merchant/refund/order/mark`,
method: 'post',
data,
});
}
/**
* 商户 获取退款订单各状态数量
*/
export function merchantRefundStatusNumApi(params) {
return request({
url: `/admin/merchant/refund/order/status/num`,
method: 'GET',
params,
});
}
/**
* 商户 核销订单
* @param data
*/
export function merchantWriteUpdateApi(data) {
return request({
url: `/admin/merchant/order/verification`,
method: 'post',
data,
});
}
/**
* 商户 订单细节详情列表(发货使用)
* @param orderNo 订单号
*/
export function merchantOrderProDetailApi(orderNo) {
return request({
url: `/admin/merchant/order/${orderNo}/detail/list`,
method: 'get',
});
}
/**
* 商户 获取订单发货单列表
* @param orderNo 订单号
*/
export function merchantOrderInvoiceListApi(orderNo) {
return request({
url: `/admin/merchant/order/${orderNo}/invoice/list`,
method: 'get',
});
}
/**
* 商户 退款订单详情
* @param refundOrderNo 订单号
*/
export function merchantRefundOrderDetailApi(refundOrderNo) {
return request({
url: `/admin/merchant/refund/order/detail/${refundOrderNo}`,
method: 'get',
});
}
/**
* 商户 退款订单导出
* @param params 对象
*/
export function merchantOrderExcelApi(params) {
return request({
url: `/admin/merchant/export/order/excel`,
method: 'get',
params,
});
}
/**
* 商户 退款单审核
* @param data 对象
*/
export function merchantOrderAuditApi(data) {
return request({
url: `/admin/merchant/refund/order/audit`,
method: 'post',
data,
});
}
/**
* 商户 退款单收到退货
* @param refundOrderNo 退款单号
*/
export function merchantRefundOrderReceivingApi(refundOrderNo) {
return request({
url: `/admin/merchant/refund/order/receiving/${refundOrderNo}`,
method: 'post',
});
}
/**
* 商户 退款单拒绝收货
* @param data
*/
export function merchantRefundOrderReceivingRejectApi(data) {
return request({
url: `/admin/merchant/refund/order/receiving/reject`,
method: 'post',
data,
});
}
/**
* 商户 直接退款
* @param data
*/
export function merchantOrderDirectRefundApi(data) {
return request({
url: `/admin/merchant/order/direct/refund`,
method: 'post',
data,
});
}
/**
* 商户 修改发货单配送信息
* @param data
*/
export function merchantOrderInvoiceUpdateApi(data) {
return request({
url: `/admin/merchant/order/invoice/update`,
method: 'post',
data,
});
}
/**
* 订单打印详情使用staging表数据
* @param orderNo 订单号
*/
export function merchantOrderPrintDetailApi(orderNo) {
return request({
url: `/admin/merchant/order/print/detail/${orderNo}`,
method: 'get',
});
}

View File

@@ -0,0 +1,174 @@
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
import request from '@/utils/request';
/**
* 编辑到店服务预约信息
* @param data
*/
export function merchantReservationStoreEditApi(data) {
return request({
url: '/admin/merchant/reservation/config/store/edit',
method: 'post',
data,
});
}
/**
* 到店服务详情
*/
export function merchantReservationStoreInfoApi() {
return request({
url: '/admin/merchant/reservation/config/store/info',
method: 'get',
});
}
/**
* 上门服务详情
*/
export function merchantReservationHomeInfoApi() {
return request({
url: '/admin/merchant/reservation/config/home/info',
method: 'get',
});
}
/**
* 编辑上门服务详情
* @param data
*/
export function merchantReservationHomeEditApi(data) {
return request({
url: '/admin/merchant/reservation/config/home/edit',
method: 'post',
data,
});
}
/**
* 预约工单甘特图
* @param params
*/
export function merchantWorkOrderOverViewApi(params) {
return request({
url: '/admin/merchant/work/order/overView',
method: 'get',
params,
});
}
/**
* 工单甘特图各状态数量
* @param params
*/
export function merchantWorkOrderOverViewNumApi(params) {
return request({
url: '/admin/merchant/work/order/overView/num',
method: 'get',
params,
});
}
/**
* 预约工单分页列表
* @param params
*/
export function merchantWorkOrderListApi(params) {
return request({
url: '/admin/merchant/work/order/list',
method: 'get',
params,
});
}
/**
* 获取工单各状态数量
* @param params
*/
export function merchantWorkOrderStatusNumApi(params) {
return request({
url: '/admin/merchant/work/order/status/num',
method: 'get',
params,
});
}
/**
* 工单详情
* @param workOrderNo
*/
export function merchantWorkOrderDetailApi(workOrderNo) {
return request({
url: `/admin/merchant/work/order/detail/${workOrderNo}`,
method: 'get',
});
}
/**
* 商户派单
* @param data
*/
export function merchantWorkOrderAssignApi(data) {
return request({
url: `/admin/merchant/work/order/assign`,
method: 'post',
data,
});
}
/**
* 商户改派
* @param data
*/
export function merchantWorkOrderReassignApi(data) {
return request({
url: `/admin/merchant/work/order/reassign`,
method: 'post',
data,
});
}
/**
* 商户改约
* @param data
*/
export function merchantWorkOrderUpdateAgreementApi(data) {
return request({
url: `/admin/merchant/work/order/updateAgreement`,
method: 'post',
data,
});
}
/**
* 商户备注工单
* @param data
*/
export function merchantWorkOrderMarkApi(data) {
return request({
url: `/admin/merchant/work/order/mark`,
method: 'post',
data,
});
}
/**
* 商户强制完成工单
* @param data
*/
export function merchantWorkOrderForceFinishApi(data) {
return request({
url: `/admin/merchant/work/order/forceFinish`,
method: 'post',
data,
});
}

View File

@@ -28,6 +28,7 @@ import operationRouter from './modules/operation';
import merchant from './modules/merchant';
import pagediy from '@/router/modules/pagediy';
import areaRouter from './modules/area';
import merchantOrderRouter from './modules/merchantOrder';
/**
* Note: sub-menu only appear when route children.length >= 1
* Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
@@ -77,6 +78,8 @@ export const constantRoutes = [
pagediy,
// 圈层管理
areaRouter,
// 商户订单管理(从商户端移植)
merchantOrderRouter,
{
path: '/404',
component: () => import('@/views/error-page/404'),
@@ -176,6 +179,13 @@ export const constantRoutes = [
},
],
},
{
path: '/merchantOrder/print/:orderNo',
component: () => import('@/views/merchantOrder/orderPrint'),
name: 'MerchantOrderPrint',
hidden: true,
meta: { title: '订单打印' },
},
{
path: '/setting/uploadPicture',
component: () => import('@/components/base/uploadPicture.vue'),

View File

@@ -0,0 +1,59 @@
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
import Layout from '@/layout';
const merchantOrderRouter = {
path: '/merchantOrder',
component: Layout,
redirect: '/merchantOrder/list',
name: 'MerchantOrder',
alwaysShow: true,
meta: {
title: '商户订单',
icon: 'clipboard',
},
children: [
{
path: 'list',
component: () => import('@/views/merchantOrder/index'),
name: 'MerchantOrderIndex',
meta: { title: '商户订单' },
},
{
path: 'refund',
component: () => import('@/views/merchantOrder/refund/index'),
name: 'MerchantRefund',
meta: { title: '商户退款订单' },
},
{
path: 'reservation',
component: () => import('@/views/merchantOrder/reservation/index'),
name: 'MerchantOrderReservation',
meta: { title: '商户预约', icon: '' },
children: [
{
path: 'service',
component: () => import('@/views/merchantOrder/reservation/service'),
name: 'MerchantService',
meta: { title: '商户预约看板', icon: '' },
},
{
path: 'workOrder',
component: () => import('@/views/merchantOrder/reservation/workOrder'),
name: 'MerchantWorkOrder',
meta: { title: '商户工单管理', icon: '' },
},
],
},
],
};
export default merchantOrderRouter;

View File

@@ -0,0 +1,142 @@
<template>
<div>
<el-alert title="同意退款后,用户会根据下方地址将商品退回!" type="warning" show-icon class="mb20"> </el-alert>
<div>
<div class="detail-term mb20">
<span class="detail-infoTitle">退货方式</span
><span class="detail-info">{{ refundInfo.afterSalesType === 1 ? '仅退款' : '退货退款' }}</span>
</div>
<div class="detail-term acea-row">
<span class="detail-infoTitle">退回地址</span>
<div v-if="!addressList.length">请先去设置-商家地址管理中添加售后地址</div>
<div v-else v-loading="listLoading" class="h-82%">
<div v-for="item in addressList" :key="item.id">
<div v-if="item.isShow" class="">
<el-card class="box-card" shadow="never" :bordered="false">
<div class="acea-row row-between">
<div class="text-14 text-666 address">
<div class="mb10">
{{ item.province }}{{ item.city }}{{ item.district }}{{ item.street }}{{ item.detail }}
<span v-show="item.isDefault" style="color: var(--prev-color-primary)" class="ml10">[默认退货]</span>
</div>
<div class="">
<span class="w-70px inline-block mr15">{{ item.receiverName }}</span
>{{ item.receiverPhone }}
</div>
</div>
<div>
<el-radio-group v-model="defaultId" @change="handleChecked">
<el-radio :label="item.id" size="large">
<span class="text-14px text-#666">选择地址</span>
</el-radio>
</el-radio-group>
</div>
</div>
</el-card>
</div>
</div>
</div>
</div>
</div>
<div class="dialog-footer-inner">
<el-button @click="handleCancel"> </el-button>
<el-button type="primary" @click="handleSubmit" :loading="loading"> </el-button>
</div>
</div>
</template>
<script>
import { merchantAddressListApi } from '@/api/systemSetting';
import { merchantOrderAuditApi as orderAuditApi, merchantOrderRefuseApi as orderRefuseApi } from '@/api/merchantOrder';
export default {
name: 'agreeToReturn',
props: {
refundInfo: {
type: Object,
default: null,
},
},
data() {
return {
listLoading: false,
addressList: [],
defaultId: 0,
loading: false,
};
},
mounted() {
if (localStorage.getItem('merchantAddressList')) {
this.addressList = JSON.parse(localStorage.getItem('merchantAddressList'));
let data = this.addressList.find((item) => item.isDefault);
if (data) {
this.defaultId = data.id;
}
} else {
this.getList();
}
},
methods: {
//确定提交
handleSubmit() {
this.submit();
},
// 提交方法
submit() {
this.loading = true;
orderAuditApi({
auditType: 'success',
refundOrderNo: this.refundInfo.refundOrderNo,
merAddressId: this.defaultId,
})
.then((res) => {
this.loading = false;
this.$message.success('审核成功');
this.$emit('onHandleSuccess');
})
.catch((res) => {
this.loading = false;
});
},
handleCancel() {
this.$emit('onHandleCancel');
},
// 列表
getList() {
this.listLoading = true;
merchantAddressListApi()
.then((res) => {
this.addressList = res;
if (this.addressList.length) {
localStorage.setItem('merchantAddressList', JSON.stringify(res));
let data = this.addressList.find((item) => item.isDefault);
this.defaultId = data.id;
}
this.listLoading = false;
})
.catch((res) => {
this.listLoading = false;
});
},
handleClose() {
this.dialogVisible = false;
},
//选中地址
handleChecked() {},
},
};
</script>
<style scoped lang="scss">
.dialog-footer {
text-align: right;
padding-top: 20px;
}
.box-card {
width: 675px;
}
.address {
width: 450px;
}
::v-deep .el-card__body {
padding: 0 15px 15px 0 !important;
}
</style>

View File

@@ -0,0 +1,346 @@
<template>
<el-dialog
:visible.sync="dialogVisible"
title="直接退款"
destroy-on-close
:close-on-click-modal="false"
width="1100px"
@close="handlerClose"
class="dialog-bottom"
>
<el-form ref="pram" size="small" :model="pram" label-width="95px" @submit.native.prevent>
<el-form-item label="退款类型:" prop="returnType">
<el-radio-group v-model="pram.returnType" :disabled="type == 2 || secondType == OrderSecondTypeEnum.PunchCard">
<el-radio :label="1">整单退款</el-radio>
<el-radio :label="2">分单退款</el-radio>
</el-radio-group>
<div v-show="pram.returnType === 2" class="from-tips">可选择下方表格中的商品进行退款退款后不能撤回</div>
</el-form-item>
<div v-if="pram.returnType === 2 && workOrderList.length" class="tableLeft mb20 font12">
<div class="mb10">预约类型{{ workOrderList[0].serviceType === 1 ? '上门服务' : '到店服务' }}</div>
<div>
预约信息<span class="priceBox">{{ workOrderList[0].userName }} {{ workOrderList[0].userPhone }}</span>
<span class="ml10">{{ workOrderList[0].userAddress }}</span>
</div>
</div>
<el-table
v-if="pram.returnType === 2"
v-loading="loading"
class="mb15 tableSelection tableLeft"
style="width: 91%"
ref="multipleSelection"
:data="productList"
border
tooltip-effect="dark"
size="mini"
:row-key="
(row) => {
return row.id;
}
"
@selection-change="handleSelectionChange"
>
<el-table-column
v-if="shouldShowColumn('workOrderList')"
type="selection"
:reserve-selection="true"
min-width="50"
/>
<el-table-column label="商品信息" width="200">
<template slot-scope="scope">
<div class="acea-row" style="align-items: center">
<div class="demo-image__preview mr5 line-heightOne refundImg">
<el-image :src="scope.row.image" :preview-src-list="[scope.row.image]" />
<span class="priceBox product-info-text" style="width: 120px">{{ scope.row.productName }}</span>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="规格" min-width="60">
<template slot-scope="scope">
<span class="priceBox">{{ scope.row.sku }}</span>
</template>
</el-table-column>
<el-table-column label="总数" min-width="80">
<template slot-scope="scope">
<span class="priceBox">购买数量:{{ scope.row.payNum }}</span>
(
<span class="priceBox">已发货{{ scope.row.deliveryNum }}</span
>)
<div class="priceBox textE93323 mt3">
可退款{{ scope.row.payNum - scope.row.refundNum - scope.row.applyRefundNum }}
</div>
</template>
</el-table-column>
<el-table-column label="可退总额" min-width="60">
<template slot-scope="scope">
<span class="priceBox">{{ scope.row.payPrice - scope.row.refundPrice }}</span>
</template>
</el-table-column>
<el-table-column v-if="shouldShowColumn('workOrderList')" label="退款数量" min-width="100">
<template slot-scope="scope">
<el-input-number
v-model.trim="scope.row['num']"
:min="1"
:max="scope.row.payNum - scope.row.refundNum - scope.row.applyRefundNum"
class="priceBox"
:step="1"
@change="limitCount(scope.row, scope.$index)"
/>
</template>
</el-table-column>
</el-table>
<!-- 预约商品选择工单-->
<el-table
border
v-if="secondType === OrderSecondTypeEnum.Reservation && pram.returnType === 2 && workOrderList.length"
v-loading="loading"
class="mb15 tableSelection tableLeft"
style="width: 91%"
ref="multipleSelection"
:data="workOrderList"
tooltip-effect="dark"
size="mini"
:row-key="
(row) => {
return row.id;
}
"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" :reserve-selection="true" min-width="50" />
<el-table-column label="工单号" prop="workOrderNo" min-width="200" />
<el-table-column label="表单信息" min-width="250">
<template slot-scope="scope">
<ul v-if="scope.row.reservationFormData">
<li class="item" v-for="(item, index) in JSON.parse(scope.row.reservationFormData)" :key="index">
<system-from-info :item="item"></system-from-info>
</li>
</ul>
<div v-else>--</div>
</template>
</el-table-column>
</el-table>
<!-- 次卡商品预计退款金额 -->
<template v-if="secondType === OrderSecondTypeEnum.PunchCard && productList[0]">
<el-form-item label="剩余核销次数">
<div>{{ productList[0].verifyRemainingTimes }}/{{ productList[0].verifyTotalTimes }}</div>
</el-form-item>
<el-form-item label="预计退款金额">
<div>{{ productList[0].verifyRefundPrice }} 元</div>
<div class="from-tips">具体退款金额以实际为准</div>
</el-form-item>
</template>
<!-- 非次卡商品预计退款金额 -->
<el-form-item v-else label="预计退款金额" prop="resource">
<div>{{ refundPrice.toFixed(2) }} 元</div>
<div class="from-tips">具体退款金额以实际为准</div>
</el-form-item>
</el-form>
<div class="acea-row row-right dialog-footer-inner dialog-btn-top">
<el-button size="small" @click="handlerClose">取 消</el-button>
<el-button size="small" type="primary" @click="handlerSubmit">确 定</el-button>
</div>
</el-dialog>
</template>
<script>
import { merchantOrderDirectRefundApi as orderDirectRefundApi, merchantOrderProDetailApi as orderProDetailApi, merchantWriteUpdateApi as writeUpdateApi } from '@/api/merchantOrder';
import { OrderSecondTypeEnum } from '@/enums/productEnums';
import systemFromInfo from '@/views/merchantOrder/components/systemFromInfo';
export default {
name: 'directRefund',
components: {
systemFromInfo,
},
props: {
dialogVisibleDirectRefund: {
type: Boolean,
default: function () {
return false;
},
},
orderNo: {
type: String,
default: function () {
return '';
},
},
type: {
type: Number,
default: 0,
},
secondType: {
type: Number,
default: 0,
},
},
data() {
return {
OrderSecondTypeEnum,
pram: {
returnType: 1,
orderNo: '',
detailList: [],
},
dialogVisible: this.dialogVisibleDirectRefund,
productList: [],
multipleSelection: [],
refundPrice: 0, //预计退款金额
loading: false,
workOrderList: [], // 工单列表
};
},
watch: {
dialogVisibleDirectRefund: {
handler: function (val) {
this.dialogVisible = val;
},
deep: true,
},
'pram.returnType': {
handler: function (val) {
if (this.multipleSelection.length === 0 && val === 2) {
this.refundPrice = 0;
} else {
this.multipleSelection = [];
this.getRefundPrice(this.productList);
}
},
deep: true,
},
},
mounted() {
this.orderProDetail();
},
methods: {
shouldShowColumn(columnName) {
// 检查至少有一行的该列值存在非null、非undefined、非空字符串等
return this.productList.some((row) => {
const value = row[columnName];
return !value;
});
},
//决定这一行的 CheckBox 是否可以勾选
selectable(row, index) {
if (row.deliveryNum === row.payNum) {
return false;
} else {
return true;
}
},
limitCount(row, i) {
if (row.num > row.payNum) row.num = row.payNum;
this.getRefundPrice(this.multipleSelection);
},
// 商品信息
orderProDetail() {
this.loading = true;
orderProDetailApi(this.orderNo)
.then(async (res) => {
this.productList = res;
this.productList.map((item) => this.$set(item, 'num', 1));
this.workOrderList = res[0].workOrderList || [];
this.getRefundPrice(this.productList);
this.loading = false;
})
.catch(() => {
this.loading = false;
});
},
// 获取一件的价格
getOnePrice(currentValue) {
return this.$selfUtil.Division(
currentValue.payPrice - currentValue.refundPrice,
currentValue.payNum - currentValue.refundNum - currentValue.applyRefundNum,
);
},
//预计退款金额
getRefundPrice(multipleSelection) {
// 预计退款金额 = (可退总额/可退数量 * 退款数量)表格选中之和
// 可退总额 = 购买金额-已退款金额payPrice - refundPrice
// 可退数量 = 购买数量-已退款数量-申请退款数量payNum-refundNum-applyRefundNum
// 可退总额/可退数量 onePrice = this.$selfUtil.Division(payPrice , refundPrice); //除法
this.refundPrice = multipleSelection.reduce((accumulator, currentValue) => {
let onePrice = this.getOnePrice(currentValue);
//可退数量
let refundNum =
this.pram.returnType === 1
? currentValue.payNum - currentValue.refundNum - currentValue.applyRefundNum
: currentValue.num;
return accumulator + onePrice * refundNum;
}, 0);
},
// 选择商品
handleSelectionChange(val) {
this.multipleSelection = val;
let data = [];
this.multipleSelection.map((item) => {
data.push({
id: item.id,
num: this.secondType === OrderSecondTypeEnum.Reservation ? 1 : item.num,
});
});
if (this.secondType === OrderSecondTypeEnum.Reservation) {
this.getReservationRefundPrice(this.multipleSelection);
} else {
this.getRefundPrice(this.multipleSelection);
}
},
// 计算预约退款价格
getReservationRefundPrice(multipleSelection) {
let onePrice = this.getOnePrice(this.productList[0]);
this.refundPrice = onePrice * multipleSelection.length;
},
//提交
handlerSubmit() {
if (this.pram.returnType === 2 && this.multipleSelection.length === 0)
return this.$message.warning('分单退款请选择商品!');
this.pram.orderNo = this.orderNo;
if (this.secondType !== OrderSecondTypeEnum.Reservation) {
this.multipleSelection.map((item, index) => {
this.pram.detailList.push({ num: item.num, orderDetailId: item.id });
});
} else {
const workOrderIdList = this.multipleSelection.map((item) => item.id);
this.pram.detailList.push({
num: this.multipleSelection.length,
orderDetailId: this.productList[0].id,
workOrderIdList: workOrderIdList,
});
}
orderDirectRefundApi(this.pram).then(() => {
this.$message.success('操作成功');
this.$emit('handlerSuccessSubmit');
});
},
//取消
handlerClose() {
this.$emit('handlerSuccessClose');
},
},
};
</script>
<style scoped lang="scss">
::v-deep .el-input-number--medium {
width: 170px !important;
}
.from-tips {
margin-top: 5px !important;
}
.tableLeft {
margin-left: 95px;
}
.refundImg {
display: flex;
align-items: center;
}
.product-info-text {
display: block;
white-space: nowrap; /* 确保文本在一行内显示 */
overflow: hidden; /* 超出容器部分隐藏 */
text-overflow: ellipsis; /* 使用省略号表示被截断的文本 */
margin-left: 5px;
}
</style>

View File

@@ -0,0 +1,152 @@
<template>
<el-dialog
title="修改配送信息"
:visible.sync="visible"
width="540px"
:before-close="handleClose"
class="dialog-bottom"
>
<el-form ref="formItem" :model="formItem" label-width="95px" @submit.native.prevent :rules="rules">
<SendFrom :formItem="formItem" :isShowBtn="false"></SendFrom>
</el-form>
<div slot="footer">
<el-button size="mini" @click="handleClose">取消</el-button>
<el-button
:loading="loadingBtn"
size="smalll"
type="primary"
@click="handleSubmit('formItem')"
v-hasPermi="['merchant:order:invoice:update']"
>提交</el-button
>
</div>
</el-dialog>
</template>
<script>
import { useLogistics } from '@/hooks/use-order';
import { Debounce } from '@/utils/validate';
import { merchantOrderInvoiceUpdateApi as orderInvoiceUpdateApi } from '@/api/merchantOrder';
import { postRules } from '@/views/merchantOrder/default';
import SendFrom from './sendFrom';
export default {
name: 'editDelivery',
props: {
visible: {
type: Boolean,
required: false,
},
editData: {
type: Object,
required: null,
},
},
components: { SendFrom },
data() {
return {
formItem: {
carrierPhone: '',
deliveryCarrier: '',
deliveryMark: '',
expressCode: '',
expressNumber: '',
id: 0,
},
rules: postRules,
logistics: [],
loadingBtn: false,
};
},
watch: {
visible: {
handler: function (val) {
if (val) this.getLogistics();
},
deep: true,
},
editData: {
handler: function (val) {
this.formItem = {
...this.editData,
expressNumber: this.editData.expressNumber ? this.editData.expressNumber : this.editData.trackingNumber,
};
},
deep: true,
},
},
methods: {
handleClose() {
this.$emit('onCloseVisible');
this.formItem = {
...this.editData,
expressNumber: this.editData.expressNumber ? this.editData.expressNumber : this.editData.trackingNumber,
};
this.$refs.formItem.resetFields();
},
handleSubmitSuccess() {
this.$emit('onSubmitSuccess');
this.loadingBtn = false;
this.$refs.formItem.resetFields();
},
//物流公司
async getLogistics() {
const params = {
keywords: '',
page: 1,
limit: 50,
openStatus: true,
};
this.logistics = await useLogistics(params);
},
//提交
handleSubmit: Debounce(function (name) {
this.$refs[name].validate((valid) => {
if (valid) {
const { carrierPhone, deliveryCarrier, deliveryMark, expressCode, expressNumber, id } = this.formItem;
let data = {
carrierPhone: carrierPhone,
deliveryCarrier: deliveryCarrier,
deliveryMark: deliveryMark,
expressCode: expressCode,
expressNumber: expressNumber,
id: id,
toAddr: this.formItem.toAddr,
toTel: this.formItem.toTel,
toName: this.formItem.toName,
expressRecordType: this.formItem.expressRecordType,
};
if (this.formItem.expressRecordType == '2') {
if (!this.formItem.toAddr) {
this.$message.warning('请填写寄件人地址');
return;
}
if (!this.formItem.toTel) {
this.$message.warning('请填写寄件人电话');
return;
}
if (!this.formItem.toName) {
this.$message.warning('请填写寄件人姓名');
return;
}
if (!this.formItem.expressTempId) {
this.$message.warning('请选择电子面单');
return;
}
}
orderInvoiceUpdateApi(data)
.then((res) => {
this.$message.success('修改发货单配送信息成功');
this.handleSubmitSuccess();
})
.catch((res) => {
this.loadingBtn = false;
});
}
});
}),
},
};
</script>
<style scoped></style>

View File

@@ -0,0 +1,741 @@
<template>
<div>
<el-drawer :visible.sync="dialogVisible" :direction="direction" size="1000px" :before-close="handleClose">
<div slot="title">
<div class="detailHead">
<div class="full">
<div class="order_icon"><span class="iconfont icon-dingdan"></span></div>
<div class="text">
<div class="acea-row">
<div class="title">{{ orderDatalist.type | orderTypeFilter }}</div>
<div class="ml5"></div>
<div class="title ml5">
<div v-if="orderDatalist.secondType === 5 || orderDatalist.secondType === 6" class="value">
自动发货
</div>
<div v-else-if="orderDatalist.secondType === 2" class="value">虚拟发货</div>
<div v-else class="value">{{ orderDatalist.shippingType | shippingTypeFilter }}</div>
</div>
</div>
<div>
<span class="mr20">订单号{{ orderDatalist.orderNo }}</span>
</div>
</div>
</div>
<ul class="list">
<li class="item">
<div class="title">订单状态</div>
<div class="color-warning">
<span v-if="orderDatalist.refundStatus === 3">已退款</span>
<!-- 次卡商品部分使用状态 -->
<span
v-else-if="
orderDatalist.secondType == OrderSecondTypeEnum.PunchCard &&
orderDatalist.status == 3 &&
orderDatalist.orderDetailList[0].verifyRemainingTimes !=
orderDatalist.orderDetailList[0].verifyTotalTimes
"
>部分使用</span
>
<span v-else>{{ orderDatalist.status | orderStatusFilter }}</span>
</div>
</li>
<li class="item">
<div class="title">实际支付</div>
<div>¥ {{ orderDatalist.payPrice || '0.0' }}</div>
</li>
<li class="item">
<div class="title">支付方式</div>
<div>{{ orderDatalist.payType | payTypeFilter }}</div>
</li>
<li class="item">
<div class="title">支付时间</div>
<div>{{ orderDatalist.payTime | filterEmpty }}</div>
</li>
</ul>
</div>
</div>
<div v-if="orderDatalist" v-loading="loading">
<el-tabs type="border-card" v-model="activeName">
<el-tab-pane label="订单信息" name="detail">
<div class="detailSection" style="border: none">
<div class="title">用户信息</div>
<ul class="list">
<li class="item">
<div class="lang">用户名称:</div>
<div class="value">{{ orderDatalist.nickname }} | {{ orderDatalist.uid }}</div>
</li>
<li class="item">
<div class="lang">用户电话:</div>
<div class="value">{{ orderDatalist.phone }}</div>
</li>
</ul>
</div>
<div v-show="orderDatalist.shippingType < 2 && orderDatalist.secondType !== 2" class="detailSection">
<div class="title">收货信息</div>
<ul class="list">
<li class="item">
<div class="lang">收货人:</div>
<div class="value">
{{ orderDatalist.realName }}
</div>
</li>
<li class="item">
<div class="lang">收货电话:</div>
<div class="value">
{{ orderDatalist.userPhone }}
</div>
</li>
<li class="item">
<div class="lang">收货地址:</div>
<div class="value">
{{ orderDatalist.userAddress }}
</div>
</li>
</ul>
</div>
<div class="detailSection">
<div class="title">订单信息</div>
<ul class="list">
<li class="item">
<div class="lang">商品总价:</div>
<div class="value">{{ orderDatalist.proTotalPrice }}</div>
</li>
<li class="item">
<div class="lang">商品总数:</div>
<div class="value">{{ orderDatalist.totalNum }}</div>
</li>
<li class="item">
<div class="lang">平台优惠金额:</div>
<div class="value">{{ orderDatalist.platCouponPrice }}</div>
</li>
<li class="item">
<div class="lang">支付状态:</div>
<div class="value">{{ orderDatalist.paid ? '已支付' : '未支付' }}</div>
</li>
<li class="item">
<div class="lang">实际支付:</div>
<div class="value">{{ orderDatalist.payPrice || '0.0' }}</div>
</li>
<li class="item">
<div class="lang">商户优惠金额:</div>
<div class="value">{{ orderDatalist.merCouponPrice || '0.0' }}</div>
</li>
<li class="item">
<div class="lang">商户会员抵扣:</div>
<div class="value">{{ orderDatalist.merchantMemberDiscountPrice || '0.0' }}</div>
</li>
<li class="item">
<div class="lang">SVIP抵扣</div>
<div class="value">{{ orderDatalist.svipDiscountPrice || '0.0' }}</div>
</li>
<li class="item">
<div class="lang">支付邮费:</div>
<div class="value">{{ orderDatalist.payPostage }}</div>
</li>
<li class="item">
<div class="lang">赠送积分:</div>
<div class="value">{{ orderDatalist.gainIntegral }}</div>
</li>
<li class="item">
<div class="lang">积分抵扣金额:</div>
<div class="value">{{ orderDatalist.integralPrice || '0.0' }}</div>
</li>
<li class="item">
<div class="lang">支付方式:</div>
<div class="value">{{ orderDatalist.payType | payTypeFilter }}</div>
</li>
<li class="item">
<div class="lang">支付时间:</div>
<div class="value">{{ orderDatalist.payTime | filterEmpty }}</div>
</li>
<li class="item" v-if="orderDatalist.outTradeNo">
<div class="lang">商户单号:</div>
<div class="value">{{ orderDatalist.outTradeNo }}</div>
</li>
</ul>
</div>
<!-- 次卡商品核销信息 -->
<div class="detailSection" v-if="orderDatalist.secondType == OrderSecondTypeEnum.PunchCard">
<div class="title">核销信息</div>
<ul class="list">
<li class="item">
<div class="lang">总核销次数:</div>
<div class="value">{{ orderDatalist.orderDetailList[0].verifyTotalTimes }}</div>
</li>
<li class="item">
<div class="lang">已核销次数:</div>
<div class="value">
{{
orderDatalist.orderDetailList[0].verifyTotalTimes -
orderDatalist.orderDetailList[0].verifyRemainingTimes
}}
</div>
</li>
<li class="item">
<div class="lang">有效期:</div>
<div v-if="orderDatalist.orderDetailList[0].verifyEndDate" class="value">
{{ orderDatalist.orderDetailList[0].verifyStartDate + ' ' }}~{{
' ' + orderDatalist.orderDetailList[0].verifyEndDate
}}
</div>
<div v-else class="value">无限期</div>
</li>
</ul>
</div>
<div class="detailSection">
<div class="title">买家留言</div>
<ul class="list">
<li class="item">
<div>{{ orderDatalist.userRemark | filterEmpty }}</div>
</li>
</ul>
</div>
<div class="detailSection">
<div class="title">商家备注</div>
<ul class="list">
<li class="item">
<div>{{ orderDatalist.merchantRemark | filterEmpty }}</div>
</li>
</ul>
</div>
<div v-if="orderExtend.length" class="detailSection">
<div class="title">自定义留言</div>
<ul class="orderExtends">
<li v-for="(items, indexs) in orderExtend" :key="indexs">
<div v-if="items.length">
<div class="item" v-for="(item, index) in items" :key="index">
<system-from-info :item="item"></system-from-info>
</div>
</div>
</li>
</ul>
</div>
</el-tab-pane>
<el-tab-pane label="商品信息" name="goods" class="tabBox">
<el-table class="mt20 orderDetailList" :data="orderDatalist.orderDetailList" size="small">
<el-table-column label="商品信息" min-width="400" :show-overflow-tooltip="true">
<template slot-scope="scope">
<div class="acea-row row-middle">
<div class="demo-image__preview mr15">
<el-image :src="scope.row.image" :preview-src-list="[scope.row.image]" />
</div>
<div style="width: 408px">
<div class="line1 mb-8 line-heightOne">{{ scope.row.productName }}</div>
<div class="line1 color-909399 line-heightOne">规格:{{ scope.row.sku }}</div>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="商品售价" min-width="90">
<template slot-scope="scope">
<div class="acea-row row-middle">
<div class="line1">
{{ scope.row.price }}
</div>
</div>
</template>
</el-table-column>
<el-table-column label="购买数量" min-width="90">
<template slot-scope="scope">
<div class="acea-row row-middle">
<div class="line1">
{{ scope.row.payNum }}
</div>
</div>
</template>
</el-table-column>
<el-table-column v-if="orderDatalist.shippingType === 1" label="发货数量" min-width="90">
<template slot-scope="scope">
<div class="acea-row row-middle">
<div class="line1">
{{ scope.row.deliveryNum }}
</div>
</div>
</template>
</el-table-column>
<el-table-column label="售后数量" min-width="90">
<template slot-scope="scope">
<div class="acea-row row-middle">
<div class="line1 mb10 line-heightOne">退款中:{{ scope.row.applyRefundNum }}</div>
<div class="line1 line-heightOne">退款成功:{{ scope.row.refundNum }}</div>
</div>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane
v-if="orderDatalist.secondType === OrderSecondTypeEnum.Reservation"
label="预约信息"
name="reservation"
>
<div class="detailSection" style="border: none">
<div class="title">预约信息</div>
<ul class="list">
<li class="item">
<div class="lang">联系人:</div>
<div class="value">{{ orderDatalist.realName }}</div>
</li>
<li class="item">
<div class="lang">联系电话:</div>
<div class="value">{{ orderDatalist.userPhone }}</div>
</li>
<li class="item">
<div class="lang">预约时间:</div>
<div class="value">
<span>{{ reservationInfo.reservationDate }}</span>
<span class="ml10">{{ reservationInfo.reservationTimeSlot }}</span>
</div>
</li>
</ul>
<div v-show="orderDatalist.shippingType === 5" class="item">
<div class="lang">上门地址:</div>
<div class="value">
{{ orderDatalist.userAddress }}
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane
v-if="
orderDatalist.status > 1 &&
orderDatalist.status < 9 &&
orderDatalist.secondType < 5 &&
orderDatalist.shippingType != 2 &&
orderDatalist.status !== 3
"
label="发货记录"
name="delivery"
class="tabBox"
>
<template v-for="item in InvoiceList">
<div v-if="InvoiceList.length">
<el-table class="mt20" :data="item.detailList" size="small" :key="item.id">
<el-table-column min-width="400">
<template slot="header" slot-scope="scope">
<template v-if="item.deliveryType === 'express'">
<span class="font-color">【快递配送】</span>
<span>{{ item.expressName + '' + item.trackingNumber }}</span>
<span class="ml30">{{ item.createTime }}</span>
</template>
<template v-else-if="item.deliveryType === 'merchant'">
<span class="font-color">【商家送货】</span>
<span>{{ item.deliveryCarrier + '' + item.carrierPhone }}</span>
<span class="ml30">{{ item.createTime }}</span>
</template>
<template v-else>
<span class="font-color"
>【{{
orderDatalist.secondType === OrderSecondTypeEnum.Fictitious ? '虚拟发货' : '无需配送'
}}】</span
>
<span>{{ item.createTime }}</span>
</template>
</template>
<template slot-scope="scope">
<div class="acea-row row-middle">
<div class="demo-image__preview mr15">
<el-image :src="scope.row.image" :preview-src-list="[scope.row.image]" fit="cover" />
</div>
<div style="width: 408px">
<div class="line1 mb10 line-heightOne">{{ scope.row.productName }}</div>
<div class="line1 color-909399 line-heightOne">规格:{{ scope.row.sku }}</div>
</div>
<div class="acea-row row-middle ml30">
<div class="line1 font12 color-text">X {{ scope.row.num }}</div>
</div>
</div>
</template>
</el-table-column>
<el-table-column width="400" fixed="right">
<template slot="header" slot-scope="scope">
<div class="flex mr10" style="justify-content: flex-end">
<a
@click="handleEditLogistics(item)"
style="line-height: 1; height: auto"
v-hasPermi="['merchant:order:invoice:update']"
>修改配送信息
</a>
<a
class="ml20"
@click="openLogistics(item.id, item.expressName)"
style="line-height: 1; height: auto"
v-if="checkPermi(['merchant:order:logistics:info']) && item.deliveryType === 'express'"
>查看物流
</a>
</div>
</template>
<template v-if="item.deliveryType === 'noNeed'" slot-scope="scope">
<div class="acea-row row-middle">
<div class="font12 color-text">发货备注:{{ item.deliveryMark }}</div>
</div>
</template>
</el-table-column>
</el-table>
</div>
</template>
<div v-if="orderDatalist.status > 4 && orderDatalist.status < 9 && orderDatalist.shippingType === 2">
<div class="detailSection">
<ul class="list">
<li class="item">
<div>核销员名称:</div>
<div class="value">{{ orderDatalist.clerkName }} | {{ orderDatalist.clerkId }}</div>
</li>
<li class="item">
<div>核销时间:</div>
<div class="value">{{ orderDatalist.verifyTime }}</div>
</li>
</ul>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="订单记录" name="record">
<el-table class="mt20 orderDetailList" :data="orderDatalist.flowRecordList" style="width: 100%">
<el-table-column prop="flowType" label="操作类型" min-width="150" />
<el-table-column prop="flowMessage" label="操作记录" min-width="280" />
<el-table-column prop="operatorName" label="操作人" min-width="280" />
<el-table-column prop="operatorTime" label="操作时间" min-width="200" />
</el-table>
</el-tab-pane>
<el-tab-pane
v-if="
orderDatalist.secondType == OrderSecondTypeEnum.PunchCard ||
orderDatalist.shippingType === 2 ||
orderDatalist.shippingType === 4
"
label="核销记录"
name="verRecord"
>
<el-table class="mt20 orderDetailList" :data="orderDatalist.verificationRecordList" style="width: 100%">
<el-table-column prop="id" label="ID" min-width="100" />
<el-table-column
v-if="orderDatalist.secondType == OrderSecondTypeEnum.PunchCard"
prop="verifyTimes"
label="核销次数"
min-width="200"
/>
<el-table-column prop="verifyTime" label="核销时间" min-width="200" />
<el-table-column prop="verifyName" label="核销人员" min-width="200" />
</el-table>
</el-tab-pane>
</el-tabs>
</div>
</el-drawer>
<el-dialog v-if="orderDatalist" title="提示" :visible.sync="modal2" width="600px">
<div class="logistics acea-row row-top">
<div class="logistics_img"><img src="@/assets/imgs/expressi.jpg" /></div>
<div class="logistics_cent">
<span class="mb10">物流公司:{{ expressName }}</span>
<span>物流单号:{{ resultInfo.number }}</span>
<span v-show="resultInfo.courierPhone">快递站:{{ resultInfo.courierPhone }}</span>
<span v-show="resultInfo.courierPhone">快递员电话:{{ resultInfo.courierPhone }}</span>
</div>
</div>
<div class="acea-row row-column-around trees-coadd">
<div class="scollhide">
<el-timeline :reverse="reverse">
<el-timeline-item v-for="(item, i) in result" :key="i">
<p class="time" v-text="item.time"></p>
<p class="content" v-text="item.status"></p>
</el-timeline-item>
</el-timeline>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="modal2 = false">关闭</el-button>
</span>
</el-dialog>
<!-- 修改配送信息-->
<editDelivery
:visible="editDeliveryDialogVisible"
:editData="editData"
@onCloseVisible="onCloseVisible"
@onSubmitSuccess="onSubmitSuccess"
></editDelivery>
</div>
</template>
<script>
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
import { merchantGetLogisticsInfoApi as getLogisticsInfoApi, merchantOrderInvoiceListApi as orderInvoiceListApi, merchantOrderDetailApi as orderDetailApi, merchantRefundOrderDetailApi as refundOrderDetailApi } from '@/api/merchantOrder';
import { orderRefundStatusFilter } from '@/filters';
import editDelivery from './editDelivery';
import { checkPermi } from '@/utils/permission';
import { OrderSecondTypeEnum } from '@/enums/productEnums';
import systemFromInfo from '@/views/merchantOrder/components/systemFromInfo'; // 权限判断函数
export default {
name: 'OrderDetail',
components: {
editDelivery,
systemFromInfo,
},
props: {
orderNo: {
type: String,
default: 0,
},
},
data() {
return {
OrderSecondTypeEnum: OrderSecondTypeEnum,
activeName: 'detail',
direction: 'rtl',
reverse: true,
dialogVisible: false,
orderDatalist: {},
loading: false,
modal2: false,
result: [],
resultInfo: {},
InvoiceList: [],
refundInfo: {},
editDeliveryDialogVisible: false,
editData: {},
orderExtend: [], //系统表单数据
expressName: '', //快递名称
reservationInfo: {}, //订单信息,预约信息
};
},
watch: {},
mounted() {},
methods: {
checkPermi,
orderRefundStatusFilter,
//修改物流信息
handleEditLogistics(row) {
this.editDeliveryDialogVisible = true;
this.editData = row;
},
//修改物流信息成功
onSubmitSuccess() {
this.getOrderInvoiceList(this.orderNo);
this.onCloseVisible();
},
//关闭配送信息
onCloseVisible() {
this.editDeliveryDialogVisible = false;
},
handleClose() {
this.dialogVisible = false;
},
openLogistics(id, expressName) {
this.expressName = expressName;
this.getOrderData(id);
this.modal2 = true;
},
// 获取订单退款信息
getRefundOrderDetail(id) {
refundOrderDetailApi(id).then(async (res) => {
this.refundInfo = res;
});
},
// 获取订单物流信息
getOrderData(id) {
getLogisticsInfoApi(id).then(async (res) => {
this.resultInfo = res;
this.result = res.list;
});
},
// 获取订单发货单列表
getOrderInvoiceList(id) {
this.InvoiceList = [];
orderInvoiceListApi(id)
.then((res) => {
this.InvoiceList = [...res];
})
.catch(() => {});
},
getDetail(id) {
this.loading = true;
orderDetailApi(id)
.then((res) => {
this.orderDatalist = res;
if (this.orderDatalist.secondType === OrderSecondTypeEnum.Reservation) {
this.orderExtend = res.orderExtend ? JSON.parse(res.orderExtend) : [];
}else{
this.orderExtend = res.orderExtend ? [JSON.parse(res.orderExtend)] : [];
}
this.reservationInfo = res.orderDetailList[0]; // 预约信息
this.activeName = 'detail';
this.loading = false;
})
.catch(() => {
this.orderDatalist = null;
this.loading = false;
});
},
},
};
</script>
<style scoped lang="scss">
.orderExtends {
li {
border-bottom: 1px dashed #eeeeee;
padding-bottom: 16px;
}
li:last-child {
border: none;
}
}
.ml5 {
margin-left: 5px;
}
::v-deep .el-drawer__header {
display: flex !important;
align-items: flex-start !important;
padding: 15px 15px 0 15px !important;
margin: 0 !important;
}
::v-deep .el-drawer__body {
padding: 0 0 30px 0 !important;
}
::v-deep .demo-drawer_title {
width: 90%;
}
::v-deep .el-tabs__content {
padding: 0 20px !important;
}
.detailSection {
padding: 25px 15px !important;
}
::v-deep .el-table th.el-table__cell > .cell,
::v-deep.el-table .cell,
.el-table--border .el-table__cell:first-child .cell {
padding-left: 15px;
}
.InvoiceList {
::v-deep.el-collapse-item__header {
font-size: 12px;
color: #606266;
}
}
.wrapper {
background-color: #fff;
margin-top: 7px;
padding: 10px 12px;
&-num {
font-size: 10px;
color: #999999;
}
&-title {
color: #666666;
font-size: 12px;
}
&-img {
width: 60px;
height: 60px;
margin-right: 10px;
border-radius: 7px;
overflow: hidden;
margin-bottom: 10px;
image {
width: 100%;
height: 100%;
}
&:nth-child(5n) {
margin-right: 0;
}
}
}
.title {
font-size: 36px;
}
.demo-drawer__content {
padding: 0 30px;
}
.demo-image__preview {
display: inline-block;
width: 36px;
height: 36px;
.el-image {
width: 50px;
height: 50px;
}
}
.logistics {
align-items: center;
padding: 10px 0px;
.logistics_img {
width: 45px;
height: 45px;
margin-right: 12px;
img {
width: 100%;
height: 100%;
}
}
.logistics_cent {
span {
display: block;
font-size: 12px;
}
}
}
.trees-coadd {
width: 100%;
height: 400px;
border-radius: 4px;
overflow: hidden;
.scollhide {
width: 100%;
height: 100%;
overflow: auto;
margin-left: 18px;
padding: 10px 0 10px 0;
box-sizing: border-box;
.content {
font-size: 12px;
}
.time {
font-size: 12px;
color: var(--prev-color-primary);
}
}
}
.title {
margin-bottom: 14px;
color: #303133;
font-weight: 500;
font-size: 14px;
}
.description {
&-term {
display: table-cell;
padding-bottom: 5px;
line-height: 20px;
width: 50%;
font-size: 12px;
color: #606266;
}
::v-deep .el-divider--horizontal {
margin: 12px 0 !important;
}
}
.mb-8 {
margin-bottom: 8px;
}
</style>

View File

@@ -0,0 +1,779 @@
<template>
<div>
<el-drawer :visible.sync="drawerVisible" :direction="direction" size="1000px" :before-close="handleClose">
<div v-loading="loading">
<DetailHeader
title="退款订单"
icon="icon-shouhou_tuikuan-2"
:titleLable="`退款单号:${refundInfo.refundOrderNo}`"
:orderNo="refundInfo.refundOrderNo"
:list="headerList"
>
<template slot="operation">
<!-- 审核 -->
<div
v-if="refundInfo.refundStatus === 0 && checkPermi(['merchant:refund:order:audit'])"
class="acea-row row-center-wrappe mr14"
>
<el-button
type="primary"
v-debounceClick="
() => {
handleApprovedReview('success');
}
"
>{{ loadingBtn ? '提交中 ...' : '审核通过' }}</el-button
>
<el-button
type="danger"
v-debounceClick="
() => {
handleOrderRefuse('refuse');
}
"
>拒绝</el-button
>
</div>
<!-- 商家收货 -->
<div v-if="refundInfo.refundStatus === 5" class="acea-row row-center-wrapper mr14">
<el-button
v-if="checkPermi(['merchant:refund:order:receiving'])"
type="primary"
v-debounceClick="handleConfirmReceipt"
>{{ loadingBtn ? '提交中 ...' : '确认收货' }}</el-button
>
<el-button
v-if="checkPermi(['merchant:refund:order:audit'])"
type="danger"
v-debounceClick="handleRefuseReceipt"
>拒绝</el-button
>
</div>
<el-button size="small" @click.native="onOrderMark()" v-if="checkPermi(['merchant:refund:order:mark'])"
>订单备注</el-button
>
</template>
</DetailHeader>
<el-tabs type="border-card" v-model="activeName">
<el-tab-pane label="售后信息" name="refund">
<div class="detailSection" style="border: none">
<div class="title">退款商品</div>
<ul class="list">
<li class="item row-middle">
<div class="image mr10">
<el-image
:src="refundInfo.image"
:preview-src-list="[refundInfo.image]"
style="width: 40px; height: 40px"
></el-image>
</div>
<div>
<div class="text666 mb10 productName line-height-15">{{ refundInfo.productName }}</div>
<div class="text999">
<span>{{ refundInfo.sku }}</span
><span class="ml30">售价:¥{{ refundInfo.price }}</span>
</div>
</div>
</li>
</ul>
</div>
<div class="detailSection">
<div class="title">退款明细</div>
<ul class="list">
<li class="item">
<div class="lang">退款数量:</div>
<div class="value">{{ refundInfo.applyRefundNum }}</div>
</li>
<li class="item">
<div class="lang">购买数量:</div>
<div class="value">{{ refundInfo.payNum }}</div>
</li>
<li class="item">
<div class="lang">预计退款方式:</div>
<div class="value">原支付返还</div>
</li>
<li v-show="refundInfo.refundStatus === 2 || refundInfo.refundStatus === 3" class="item">
<div class="lang">退回运费:</div>
<div class="value">{{ refundInfo.refundFreightFee }}</div>
</li>
<li v-show="refundInfo.refundStatus === 2 || refundInfo.refundStatus === 3" class="item">
<div class="lang">退一级佣金:</div>
<div class="value">{{ refundInfo.refundFirstBrokerageFee }}</div>
</li>
<li v-show="refundInfo.refundStatus === 2 || refundInfo.refundStatus === 3" class="item">
<div class="lang">退回抵扣积分:</div>
<div class="value">{{ refundInfo.refundUseIntegral }}</div>
</li>
<li v-show="refundInfo.refundStatus === 2 || refundInfo.refundStatus === 3" class="item">
<div class="lang">收回赠送积分:</div>
<div class="value">{{ refundInfo.refundGainIntegral }}</div>
</li>
<li v-show="refundInfo.refundStatus === 2 || refundInfo.refundStatus === 3" class="item">
<div class="lang">退二级返佣:</div>
<div class="value">{{ refundInfo.refundSecondBrokerageFee }}</div>
</li>
</ul>
</div>
<!-- 退款流程信息-->
<div class="detailSection">
<div class="title">退款流程信息</div>
<div class="detail-centent acea-row">
<div>
<!-- 操作类型apply-申请退款audit-商家审核returning-商品退回receiving-商家确认收货refund-退款compulsory-平台强制退款,revoke-撤销-->
<el-steps
direction="vertical"
:active="
refundInfo.promoterType === 'merchant' ? 2 : refundInfo.statusList && refundInfo.statusList.length
"
finish-status="success"
>
<el-step title="直接退款-商家" v-if="refundInfo.promoterType === 'merchant'">
<template slot="description">
<div class="mb10">
{{ refundInfo.refundTime }}
</div>
</template>
</el-step>
<el-step
title="申请退款-用户"
v-if="
refundInfo.statusList &&
refundInfo.statusList.length &&
refundInfo.statusList.find((item) => item.changeType === 'apply')
"
>
<template slot="description" v-if="refundInfo.statusList && refundInfo.statusList.length">
<div class="mb10">
{{ refundInfo.statusList.filter((item) => item.changeType === 'apply')[0].createTime }}
</div>
<div class="refundReasonWap">
<div class="acea-row">
<div class="detail-term" style="width: 58%">
<span class="detail-infoTitle">退款原因:</span
><span class="detail-info">{{ refundInfo.refundReasonWap }}</span>
</div>
<div class="detail-term">
<span class="detail-infoTitle">退货方式:</span
><span class="detail-info">{{
refundInfo.returnGoodsType === 1
? '快递退回'
: refundInfo.returnGoodsType === 2
? '到店退货'
: '不退货'
}}</span>
</div>
</div>
<div class="detail-term acea-row">
<span class="detail-infoTitle">备注说明:</span>
<div class="detail-info" style="width: 600px">
{{ refundInfo.refundReasonWapExplain | filterEmpty }}
</div>
</div>
<div class="detail-term">
<div class="acea-row">
<span class="detail-infoTitle">退款凭证:</span>
<div v-if="refundInfo.refundReasonWapImg">
<el-image
v-for="(item, index) in refundInfo.refundReasonWapImg.split(',')"
:key="index"
style="width: 60px; height: 60px"
:src="item"
class="mr10"
:preview-src-list="refundInfo.refundReasonWapImg.split(',')"
></el-image>
</div>
<div v-else>-</div>
</div>
</div>
</div>
</template>
</el-step>
<el-step
title="商家审核-商家"
v-if="
refundInfo.statusList &&
refundInfo.statusList.length &&
refundInfo.statusList.find((item) => item.changeType === 'audit')
"
>
<template slot="description" v-if="refundInfo.statusList && refundInfo.statusList.length">
<div class="mb10">
{{
refundInfo.statusList.filter((item) => item.changeType === 'audit').length
? refundInfo.statusList.filter((item) => item.changeType === 'audit')[0].createTime
: ''
}}
</div>
<div v-if="refundInfo.refundStatus > 0" class="refundReasonWap">
<div class="detail-term">
<span class="detail-infoTitle">审核结果:</span
><span class="detail-info">{{
refundInfo.refundStatus === 1 && refundInfo.statusList.length === 2
? '拒绝退款'
: '同意退款'
}}</span>
</div>
<div
v-if="refundInfo.refundStatus === 1 && refundInfo.statusList.length === 2"
class="detail-term"
>
<span class="detail-infoTitle">拒绝原因:</span>
<span class="detail-info">{{ refundInfo.refundReason | filterEmpty }}</span>
</div>
<div
v-if="refundInfo.returnGoodsType === 1 && refundInfo.refundStatus !== 1"
class="detail-term"
>
<div>
<span class="detail-infoTitle">退货地址:</span>
<span class="detail-info">{{ refundInfo.receiverAddressDetail }}</span>
</div>
<div>
<span class="detail-infoTitle"></span>
<span class="detail-info">{{ refundInfo.receiver }} {{ refundInfo.receiverPhone }}</span>
</div>
</div>
</div>
</template>
</el-step>
<el-step
title="商品退回信息-用户"
v-if="
refundInfo.statusList &&
refundInfo.statusList.length &&
refundInfo.statusList.find((item) => item.changeType === 'returning')
"
>
<template slot="description" v-if="refundInfo.statusList && refundInfo.statusList.length">
<div class="mb10">
{{ refundInfo.statusList.filter((item) => item.changeType === 'returning')[0].createTime }}
</div>
<div v-if="refundInfo.returnGoodsType === 1" class="refundReasonWap">
<div class="acea-row">
<div class="detail-term" style="width: 58%">
<span class="detail-infoTitle">物流公司:</span
><span class="detail-info">{{ refundInfo.expressName }}</span>
</div>
<div class="detail-term">
<span class="detail-infoTitle">物流单号:</span>
<span class="detail-info">{{ refundInfo.trackingNumber }}</span>
</div>
</div>
<div class="detail-term">
<div>
<span class="detail-infoTitle">联系电话:</span>
<span class="detail-info">{{ refundInfo.telephone }}</span>
</div>
</div>
</div>
<div v-if="refundInfo.returnGoodsType === 2" class="refundReasonWap">
<div class="detail-term">
<div>
<span class="detail-infoTitle">联系电话:</span>
<span class="detail-info">{{ refundInfo.telephone }}</span>
</div>
</div>
</div>
</template>
</el-step>
<el-step
title="商家确认收货-商家"
v-if="
refundInfo.refundStatus !== 1 &&
refundInfo.refundStatus !== 6 &&
refundInfo.statusList &&
refundInfo.statusList.length &&
refundInfo.statusList.find((item) => item.changeType === 'receiving')
"
>
<template slot="description" v-if="refundInfo.statusList && refundInfo.statusList.length">
<div class="mb10">
{{
refundInfo.statusList.filter((item) => item.changeType === 'receiving').length
? refundInfo.statusList.filter((item) => item.changeType === 'receiving')[0].createTime
: ''
}}
</div>
</template>
</el-step>
<el-step
title="商品拒绝收货-商家"
v-if="
refundInfo.statusList &&
refundInfo.statusList.length &&
refundInfo.statusList.find((item) => item.changeType === 'rejectionGoods')
"
>
<template slot="description" v-if="refundInfo.statusList && refundInfo.statusList.length">
<div class="mb10">
{{
refundInfo.statusList.filter((item) => item.changeType === 'rejectionGoods')[0].createTime
}}
</div>
<div v-if="refundInfo.refundStatus > 0" class="refundReasonWap">
<div class="detail-term">
<span class="detail-infoTitle">审核结果:</span
><span class="detail-info">{{
refundInfo.refundStatus === 1 ? '拒绝退款' : '同意退款'
}}</span>
</div>
<div v-if="refundInfo.refundStatus === 1" class="detail-term">
<span class="detail-infoTitle">拒绝原因:</span>
<span class="detail-info">{{ refundInfo.refundReason | filterEmpty }}</span>
</div>
<div
v-if="refundInfo.returnGoodsType === 1 && refundInfo.refundStatus !== 1"
class="detail-term"
>
<div>
<span class="detail-infoTitle">退货地址:</span>
<span class="detail-info">{{ refundInfo.receiverAddressDetail }}</span>
</div>
<div>
<span class="detail-infoTitle"></span>
<span class="detail-info">{{ refundInfo.receiver }} {{ refundInfo.receiverPhone }}</span>
</div>
</div>
</div>
</template>
</el-step>
<el-step
title="已撤销"
v-if="
refundInfo.refundStatus === 6 &&
refundInfo.statusList &&
refundInfo.statusList.length &&
refundInfo.statusList.find((item) => item.changeType === 'revoke')
"
>
<template slot="description" v-if="refundInfo.statusList && refundInfo.statusList.length">
<div class="mb10">
{{
refundInfo.statusList.filter((item) => item.changeType === 'revoke').length
? refundInfo.statusList.filter((item) => item.changeType === 'revoke')[0].createTime
: ''
}}
</div>
</template>
</el-step>
<el-step
title="平台强制退款成功"
v-if="
refundInfo.statusList &&
refundInfo.statusList.length &&
refundInfo.statusList.find((item) => item.changeType === 'compulsory')
"
>
<template slot="description" v-if="refundInfo.statusList && refundInfo.statusList.length">
<div class="mb10">
{{
refundInfo.statusList.filter((item) => item.changeType === 'compulsory').length
? refundInfo.statusList.filter((item) => item.changeType === 'compulsory')[0].createTime
: ''
}}
</div>
</template>
</el-step>
<el-step
:title="refundInfo.promoterType === 'user' ? '退款成功' : '退款成功-商家直接退款'"
v-if="
refundInfo.refundStatus !== 1 &&
refundInfo.refundStatus !== 6 &&
refundInfo.statusList &&
refundInfo.statusList.length &&
refundInfo.statusList.find((item) => item.changeType === 'refund')
"
>
<template slot="description" v-if="refundInfo.statusList && refundInfo.statusList.length">
<div class="mb10">
{{
refundInfo.statusList.filter((item) => item.changeType === 'refund').length
? refundInfo.statusList.filter((item) => item.changeType === 'refund')[0].createTime
: ''
}}
</div>
</template>
</el-step>
<el-step
title="商家审核-商家"
v-if="
refundInfo.refundStatus !== 1 &&
refundInfo.refundStatus !== 2 &&
refundInfo.refundStatus !== 6 &&
refundInfo.refundStatus === 0
"
>
</el-step>
<el-step
title="商品退回信息-用户"
v-if="
refundInfo.refundStatus !== 1 &&
refundInfo.refundStatus !== 2 &&
refundInfo.refundStatus !== 6 &&
refundInfo.afterSalesType === 2 &&
(refundInfo.refundStatus === 0 || refundInfo.refundStatus === 4)
"
>
</el-step>
<el-step
title="商家确认收货-商家"
v-if="
refundInfo.refundStatus !== 1 &&
refundInfo.refundStatus !== 2 &&
refundInfo.refundStatus !== 6 &&
refundInfo.afterSalesType === 2 &&
(refundInfo.refundStatus === 0 ||
refundInfo.refundStatus === 4 ||
refundInfo.refundStatus === 5)
"
>
</el-step>
<el-step
:title="refundInfo.promoterType === 'user' ? '退款成功' : '退款成功-商家直接退款'"
v-if="
refundInfo.refundStatus !== 1 &&
refundInfo.refundStatus !== 6 &&
(refundInfo.refundStatus === 0 ||
refundInfo.refundStatus === 2 ||
refundInfo.refundStatus === 4 ||
refundInfo.refundStatus === 5)
"
>
</el-step>
</el-steps>
</div>
</div>
</div>
<div class="detailSection">
<div class="title">平台备注</div>
<ul class="list">
<li class="item">
<div>{{ refundInfo.platformRemark | filterEmpty }}</div>
</li>
</ul>
</div>
<div class="detailSection">
<div class="title">商家备注</div>
<ul class="list">
<li class="item">
<div>{{ refundInfo.merRemark | filterEmpty }}</div>
</li>
</ul>
</div>
</el-tab-pane>
<el-tab-pane label="订单信息" name="detail" v-if="refundInfo.orderInfoVo">
<DetailInfo :list="orderDetailList">
<template slot="nickname">
<span class="mr5">{{ refundInfo.orderInfoVo.nickname }}</span>
<span class="mr5"> | </span>
<span>{{ refundInfo.orderInfoVo.uid }}</span>
</template>
<template slot="userAddress">
<div class="userAddress acea-row">
<div class="lang">收货地址:</div>
<div class="value">{{ refundInfo.orderInfoVo.userAddress | filterEmpty }}</div>
</div>
</template>
<template slot="orderStatus">
<span v-if="refundInfo.orderInfoVo.refundStatus === 3">已退款</span>
<span v-else>{{ refundInfo.orderInfoVo.status | orderStatusFilter }}</span>
</template>
</DetailInfo>
</el-tab-pane>
</el-tabs>
</div>
</el-drawer>
<!-- 同意退款,退货退款-->
<el-dialog
title="同意退款"
:visible.sync="dialogVisible"
width="900px"
:before-close="handleCloseAgreeToReturn"
class="dialog-bottom"
>
<agree-to-return
ref="agreeToReturn"
@onHandleCancel="handleCloseAgreeToReturn"
@onHandleSuccess="handleSuccess"
:refundInfo="refundInfo"
v-if="dialogVisible"
></agree-to-return>
</el-dialog>
</div>
</template>
<script>
import { merchantOrderAuditApi as orderAuditApi, merchantRefundMarkApi as refundMarkApi, merchantRefundOrderDetailApi as refundOrderDetailApi, merchantRefundOrderReceivingRejectApi as refundOrderReceivingRejectApi } from '@/api/merchantOrder';
import AgreeToReturn from './agreeToReturn.vue';
import DetailHeader from '@/components/base/DetailHeader.vue';
import DetailInfo from '@/components/base/DetailInfo.vue';
import { filterEmpty, refundStatusFilter } from '@/filters';
import useRefundOrder from '@/libs/useRefundOrder';
const { onConfirmReceipt, onApprovedReview } = useRefundOrder();
import { checkPermi } from '@/utils/permission';
import { OrderSecondTypeEnum } from '@/enums/productEnums';
export default {
name: 'refundOrderDetail',
props: {
//退款单号
refundOrderNo: {
type: String,
default: 0,
},
//是否显示隐藏
drawerVisible: {
type: Boolean,
default: false,
},
},
components: {
DetailHeader,
DetailInfo,
AgreeToReturn,
},
computed: {
headerList() {
return [
{
label: '退款状态',
value: refundStatusFilter(this.refundInfo.refundStatus),
color: 'color-warning',
},
{
label: '退款金额',
value: '¥ ' + (this.refundInfo.refundPrice || '0.0'),
},
{
label: '实付金额',
value: this.refundInfo.payPrice,
},
{
label: '创建时间',
value: this.refundInfo.orderInfoVo ? this.refundInfo.orderInfoVo.createTime : '',
},
];
},
orderDetailList() {
const info = this.refundInfo.orderInfoVo || {};
const { filterEmpty, payTypeFilter, orderStatusFilter } = this.$options.filters;
const list = [
{
title: '用户信息',
list: [
{ label: '用户昵称', slot: 'nickname' },
{ label: '用户电话', value: info.phone },
],
},
];
if (info.shippingType < 3 && info.secondType !== 2) {
list.push({
title: '配送信息',
bottomSlot: 'userAddress',
list: [
{ label: '配送方式', value: info.shippingType === 1 ? '商家配送' : '到店自提' },
{ label: '收货电话', value: filterEmpty(info.userPhone) },
{ label: '收货人', value: filterEmpty(info.realName) },
],
});
}
list.push({
title: '订单信息',
list: [
{ label: '订单号', value: info.orderNo },
{ label: '商品总数', value: info.totalNum },
{ label: '支付状态', value: info.paid ? '已支付' : '未支付' },
{ label: '支付方式', value: payTypeFilter ? payTypeFilter(info.payType) : info.payType },
{ label: '订单状态', slot: 'orderStatus', class: 'textE93323' },
{ label: '已发货数量', value: info.deliveryNum },
{ label: '创建时间', value: info.createTime },
{ label: '支付时间', value: info.payTime },
],
});
list.push({
title: '订单明细',
list: [
{ label: '商品总价', value: '¥' + info.proTotalPrice },
{ label: '平台优惠金额', value: '¥' + info.platCouponPrice },
{ label: '赠送积分', value: '¥' + info.gainIntegral },
{ label: '实际支付', value: '¥' + (info.payPrice || '0.0') },
{ label: '扣除抵扣积分', value: info.useIntegral },
{ label: '商家优惠金额', value: '¥' + (info.merCouponPrice || '0.0') },
{ label: '商户会员抵扣', value: '¥' + (info.merchantMemberDiscountPrice || '0.0') },
{ label: 'SVIP抵扣', value: '¥' + (info.svipDiscountPrice || '0.0') },
{ label: '支付邮费', value: '¥' + (info.payPostage || '0.0') },
{ label: '积分抵扣金额', value: '¥' + (info.integralPrice || '0.0') },
],
});
if (info.secondType == this.OrderSecondTypeEnum.PunchCard) {
list.push({
title: '核销信息',
list: [
{ label: '待核销次数', value: this.refundInfo.verifyRemainingTimes },
{ label: '总次数', value: this.refundInfo.verifyTotalTimes },
],
});
}
list.push({
title: '用户备注',
list: [{ label: '', value: filterEmpty(info.userRemark), class: 'productName', type: 'lang' }],
});
list.push({
title: '商家备注',
list: [{ label: '', value: filterEmpty(info.merchantRemark), class: 'productName', type: 'lang' }],
});
return list;
},
},
data() {
return {
activeName: 'refund',
dialogVisible: false,
loadingBtn: false,
direction: 'rtl',
reverse: true,
orderDatalist: {},
loading: false,
modal2: false,
result: [],
resultInfo: {},
refundInfo: {},
OrderSecondTypeEnum,
};
},
mounted() {
this.getRefundOrderDetail(this.refundOrderNo);
},
methods: {
checkPermi,
// 备注
onOrderMark() {
this.$modalPrompt('textarea', '备注', this.refundInfo.merRemark, '退款单备注').then((V) => {
refundMarkApi({ remark: V, refundOrderNo: this.refundOrderNo }).then(() => {
this.$message.success('操作成功');
this.getRefundOrderDetail(this.refundOrderNo);
this.$emit('getReviewSuccessful');
});
});
},
//拒绝收货
handleRefuseReceipt() {
this.$modalPrompt('textarea', '拒绝收货', null, '拒绝收货原因').then((V) => {
refundOrderReceivingRejectApi({ reason: V, refundOrderNo: this.refundOrderNo }).then(() => {
this.$message.success('拒绝收货成功');
this.getSuccessful();
});
});
},
//商家确认收货
handleConfirmReceipt() {
onConfirmReceipt(this.refundOrderNo).then(() => {
this.getSuccessful();
});
},
//审核同意
handleApprovedReview() {
if (this.refundInfo.returnGoodsType !== 1) {
onApprovedReview({
auditType: 'success',
refundOrderNo: this.refundInfo.refundOrderNo,
}).then(() => {
this.handleSuccess();
});
} else {
//退货退款
this.dialogVisible = true;
}
},
//审核成功回调
handleSuccess() {
this.dialogVisible = false;
this.getSuccessful();
},
//操作成功之后的回调,比如关闭弹窗,刷新列表等
getSuccessful() {
this.getRefundOrderDetail(this.refundOrderNo);
this.$emit('getReviewSuccessful');
this.handleClose();
},
//同意弹窗
handleCloseAgreeToReturn() {
this.dialogVisible = false;
},
//审核拒绝
handleOrderRefuse() {
this.$modalPrompt('textarea', '拒绝退款', null, '拒绝退款原因').then((V) => {
orderAuditApi({ auditType: 'refuse', reason: V, refundOrderNo: this.refundInfo.refundOrderNo }).then(() => {
this.$message.success('审核成功');
this.getSuccessful();
});
});
},
handleClose() {
this.$emit('onClosedrawerVisible');
},
// 获取订单退款信息
getRefundOrderDetail(id) {
this.loading = true;
refundOrderDetailApi(id)
.then(async (res) => {
this.refundInfo = res;
this.loading = false;
})
.catch(() => {
this.loading = false;
});
},
},
};
</script>
<style scoped lang="scss">
.userAddress {
width: 100%;
margin-top: 16px;
font-size: 13px;
color: #666;
}
.productName {
width: 633px;
}
.detail-centent {
margin-top: 16px;
}
::v-deep .el-step__main {
margin-bottom: 30px !important;
}
::v-deep .el-step__title {
font-size: 14px !important;
}
.flow-path {
margin-bottom: 70px;
}
.refundReasonWap {
width: 720px;
height: auto;
padding: 10px 25px 0 0;
border-radius: 14px;
background-color: #f3f8fe;
overflow: hidden;
}
.image {
width: 40px;
height: 40px;
border-radius: 4px;
overflow: hidden;
}
.refund {
&-title {
font-size: 17px;
color: #333333;
font-weight: 600;
}
&-orderNo {
font-size: 14px;
color: #333333;
}
&-price {
margin-right: 100px;
}
}
</style>

View File

@@ -0,0 +1,247 @@
<template>
<div>
<div v-if="formItem.deliveryType === 'express'">
<el-form-item label="发货类型:">
<el-radio-group v-model="formItem.expressRecordType" @change="changeSendTypeRadio(formItem.expressRecordType)">
<el-radio label="1">手动填写</el-radio>
<el-radio label="2" :disabled="merElectPrint == 0" v-if="checkPermi(['admin:order:sheet:info'])"
>电子面单打印</el-radio
>
</el-radio-group>
</el-form-item>
<el-form-item v-if="formItem.deliveryType === 'express'" label="快递公司:" prop="expressCode">
<el-select
@change="onChangeExpress"
v-model="formItem.expressCode"
filterable
:style="isShowBtn ? 'width: 82%' : 'width:100%'"
>
<el-option v-for="item in express" :key="item.id" :label="item.name" :value="item.code"
>{{ item.name }}
<span v-if="item.account" type="info" class="line-heightOne from-tips"> | 月结账号已配</span>
</el-option>
</el-select>
<el-button
v-show="isShowBtn"
class="ml24"
type="primary"
size="small"
@click="handleCreat"
v-hasPermi="['merchant:express:relate']"
>设置物流公司</el-button
>
</el-form-item>
<!--手动填写-->
<template v-if="formItem.expressRecordType === '1'">
<el-form-item v-if="formItem.deliveryType === 'express'" label="快递单号:" prop="expressNumber">
<el-input v-model.trim="formItem.expressNumber" placeholder="请输入快递单号"></el-input>
</el-form-item>
</template>
<!--电子面单打印-->
<template v-if="formItem.expressRecordType === '2'">
<el-form-item label="电子面单:" class="express_temp_id" prop="expressTempId" label-width="95px">
<div class="acea-row">
<el-select
v-model="formItem.expressTempId"
placeholder="请选择电子面单"
:class="[formItem.expressTempId ? 'width9' : 'width8']"
@change="onChangeImg"
>
<el-option
v-for="(item, i) in exportTempList"
:value="item.temp_id"
:key="i"
:label="item.title"
></el-option>
</el-select>
<div v-if="formItem.expressTempId" style="position: relative">
<div class="tempImgList ml10">
<div class="demo-image__preview">
<el-image style="width: 36px; height: 36px" :src="tempImg" :preview-src-list="[tempImg]" fit="cover"/>
</div>
</div>
</div>
</div>
</el-form-item>
<el-form-item label="寄件人姓名:" prop="toName">
<el-input v-model="formItem.toName" placeholder="请输入寄件人姓名" style="width: 80%"></el-input>
</el-form-item>
<el-form-item label="寄件人电话:" prop="toTel">
<el-input v-model="formItem.toTel" placeholder="请输入寄件人电话" style="width: 80%"></el-input>
</el-form-item>
<el-form-item label="寄件人地址:" prop="toAddr">
<el-input v-model="formItem.toAddr" placeholder="请输入寄件人地址" style="width: 80%"></el-input>
</el-form-item>
</template>
</div>
<el-form-item v-if="formItem.deliveryType === 'noNeed'" label="发货备注:">
<el-input
v-model.trim="formItem.deliveryMark"
show-word-limit
:autosize="{ minRows: 4 }"
maxlength="250"
type="textarea"
placeholder="请输入备注信息最多可输入250字"
></el-input>
</el-form-item>
<el-form-item v-if="formItem.deliveryType === 'merchant'" label="配送人员:" prop="deliveryCarrier">
<div class="acea-row">
<el-select
v-model="selectedValue"
value-key="id"
:style="isShowBtn ? 'width: 80%' : 'width:95%'"
filterable
clearable
@change="handleChangePersonnel"
>
<el-option v-for="item in personnelList" :key="item.id" :label="item.personnelName" :value="item" />
</el-select>
<el-button v-show="isShowBtn" class="ml24" @click="handleCreatPersonnel()">添加配送员</el-button>
</div>
</el-form-item>
<el-form-item v-if="formItem.deliveryType === 'merchant'" label="手机号码:" prop="carrierPhone">
<el-input v-model.trim="formItem.carrierPhone" disabled placeholder="请输入配送人员手机号码"></el-input>
</el-form-item>
<!--物流公司-->
<creat-express ref="craetExpressRef" @handlerSuccessSubmit="getList"></creat-express>
<!-- 添加配送员 -->
<creat-personnel
:dialogVisible="dialogVisible"
:editData="editData"
@handlerCloseFrom="handlerCloseFrom"
@handlerSuccessSubmit="handlerSuccessSubmit"
></creat-personnel>
</div>
</template>
<script>
import CreatExpress from '@/views/systemSetting/logisticsManagement/creatExpress.vue';
import { useLogistics } from '@/hooks/use-order';
import { defaultData } from '@/views/systemSetting/deliveryPersonnel/default';
import { personnelListApi } from '@/api/deliveryPersonnel';
import CreatPersonnel from '@/views/systemSetting/deliveryPersonnel/creatPersonnel';
import { checkPermi } from '@/utils/permission';
import { merchantElectrSheetInfo } from '@/api/systemSetting';
import Cookies from 'js-cookie';
import { exportTempApi } from '@/api/logistics';
export default {
name: 'sendFrom',
components: { CreatExpress, CreatPersonnel },
data() {
return {
selectedValue: null,
express: [],
dialogVisible: false,
editData: Object.assign({}, defaultData),
tableFrom: {
page: 1,
limit: 9999,
},
personnelList: [],
shipmentExpress: {}, // 电子面单发货信息
exportTempList: [],
merElectPrint: Cookies.get('merElectPrint'), // 商家小票打印开关状态
currentItemCode: '',
};
},
props: {
formItem: {
type: Object,
default: null,
},
isShowBtn: {
type: Boolean,
default: false,
},
},
mounted() {
this.getList();
this.getPersonnelList();
//if (checkPermi(['admin:pass:shipment:express']))
this.getShipmentExpress();
},
methods: {
checkPermi,
//选择配送员
handleChangePersonnel(e) {
this.formItem.deliveryCarrier = e.personnelName;
this.formItem.carrierPhone = e.personnelPhone;
},
// 取消选配送员弹窗
handlerCloseFrom() {
this.dialogVisible = false;
},
// 选配送员确定回调
handlerSuccessSubmit() {
this.getPersonnelList();
this.dialogVisible = false;
},
// 配送员列表
async getPersonnelList() {
const data = await personnelListApi(this.tableFrom);
this.personnelList = data.list;
if (!this.isShowBtn)
this.selectedValue = this.personnelList.filter((item) => item.personnelPhone === this.formItem.carrierPhone)[0];
},
// 添加
handleCreatPersonnel(row) {
this.editData = row ? row : Object.assign({}, defaultData);
this.dialogVisible = true;
},
// 添加
handleCreat() {
if (!localStorage.getItem('expressAllList')) this.$refs.craetExpressRef.getExpressList();
this.$refs.craetExpressRef.dialogVisible = true;
},
// 物流公司列表
async getList() {
const params = {
keywords: '',
page: 1,
limit: 50,
openStatus: true,
};
this.express = await useLogistics(params);
this.express.map((item) => {
if (item.isDefault && !this.formItem.id) this.formItem.expressCode = item.code;
});
},
getShipmentExpress() {
merchantElectrSheetInfo().then((data) => {
this.shipmentExpress = data;
this.formItem.toName = data.senderUsername;
this.formItem.toTel = data.senderPhone;
this.formItem.toAddr = data.senderAddr;
});
},
changeSendTypeRadio(expressRecordType) {
if (expressRecordType === '2') {
this.formItem.expressCode && this.exportTemp(this.formItem.expressCode);
this.getShipmentExpress();
}
},
// 快递公司选择
onChangeExpress: function (val) {
const currentItem = this.express.filter((item) => item.code == val)[0];
this.formItem.expressName = val.name;
this.formItem.expressTempId = '';
this.currentItemCode = currentItem.code;
if (this.formItem.expressRecordType === '2') this.exportTemp(currentItem.code);
},
// 电子面单模板
exportTemp(code) {
exportTempApi({ com: code }).then(async (res) => {
this.exportTempList = res.data.data || [];
});
},
onChangeImg(item) {
this.exportTempList.map((i) => {
if (i.temp_id === item) this.tempImg = i.pic;
});
},
},
};
</script>
<style scoped></style>

View File

@@ -0,0 +1,40 @@
<template>
<div class="acea-row">
<div v-if="item.title !== 'richTextEditor'" class="lang" :title="item.title">{{ item.title }}</div>
<div v-if="item.title !== 'richTextEditor'">{{ item.title.includes(':') ? '' : '' }}</div>
<div v-if="item.title === 'richTextEditor'">
<div class="contentPic" v-html="item.value"></div>
</div>
<div v-else-if="!Array.isArray(item.value)" class="value">{{ item.value | filterEmpty }}</div>
<div v-else class="flex conter">
<template v-if="item.value">
<div v-for="(pic, idx) in item.value" :key="idx">
<el-image v-if="pic.includes('http')" class="pictrue" :src="pic" :preview-src-list="[pic]" />
<div v-else class="text-14px fontColor333 ml-5px acea-row row-middle mr5">
{{ pic }}
<div style="margin-left: 6px" v-show="idx < item.value.length - 1">-</div>
</div>
</div>
</template>
<template v-else> - </template>
</div>
</div>
</template>
<script>
export default {
name: "systemFromInfo",
props: {
item: {
type: Object,
default: function() {
return {}
}
},
},
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,118 @@
<template>
<div>
<el-dialog
class="dialog-box"
title="核销"
v-if="writeOffData.infoResponseList"
:visible.sync="dialogTableVisible"
@submit.native.prevent
@closed="closeDialog"
>
<div>
<el-form label-width="70px">
<el-form-item label="已核销:">
<span>
{{
writeOffData.infoResponseList[0].verifyTotalTimes -
writeOffData.infoResponseList[0].verifyRemainingTimes
}}
</span>
<span>/</span>
<span>{{ writeOffData.infoResponseList[0].verifyTotalTimes }}</span>
</el-form-item>
<el-form-item label="核销次数:">
<el-input-number
v-model="verifyCount"
controls-position="right"
class="ver-count-input"
:step="1"
step-strictly
:min="1"
:max="writeOffData.infoResponseList[0].verifyRemainingTimes"
></el-input-number>
</el-form-item>
<el-form-item>
<div class="flex-right">
<el-button size="small" @click="closeDialog">取消</el-button>
<el-button type="primary" size="small" @click="writeOff">确认</el-button>
</div>
</el-form-item>
</el-form>
</div>
</el-dialog>
</div>
</template>
<script>
import { merchantWriteUpdateApi as writeUpdateApi } from '@/api/merchantOrder';
import { Debounce } from '@/utils/validate';
export default {
name: '',
components: {},
mixins: [],
props: {
writeOffData: {
type: Object,
default: () => {
return {};
},
},
},
data() {
return {
dialogTableVisible: false, // 弹窗是否显示
verifyCount: 1, // 核销次数
};
},
computed: {},
watch: {},
created() {},
mounted() {},
destoryed() {},
methods: {
// 开启弹窗
openDialog() {
this.dialogTableVisible = true;
},
// 关闭弹窗
closeDialog() {
this.dialogTableVisible = false;
this.verifyCount = 1
// this.$emit('handleSearchList');
},
// 核销
writeOff: Debounce(function () {
{
if (!this.verifyCount) {
this.$message.error('核销次数不能为空!');
return
}
const param = {
orderNo: this.writeOffData.orderNo,
verifyCount: this.verifyCount,
};
writeUpdateApi(param).then(() => {
this.$message.success('核销成功');
this.closeDialog();
this.$emit('handleSearchList');
this.verifyCount = 1;
});
}
}),
},
};
</script>
<style lang="scss" scoped>
.dialog-box {
.ver-count-input {
width: 100%;
}
::v-deep .el-dialog {
margin-top: 30vh !important;
width: 410px;
}
}
.flex-right {
display: flex;
justify-content: flex-end;
}
</style>

View File

@@ -0,0 +1,13 @@
import { validatePhone } from '@/utils/toolsValidate';
export const postRules = {
expressCode: [{ required: true, message: '请选择快递公司', trigger: 'change' }],
expressNumber: [{ required: true, message: '请输入快递单号', trigger: 'blur' }],
deliveryCarrier: [{ required: true, message: '请输入配送人员', trigger: 'blur' }],
carrierPhone: [{ required: true, validator: validatePhone, trigger: 'blur' }],
isSplit: [{ required: true, message: '请选择分单发货', trigger: 'change' }],
toName: [{ required: true, message: '请输入寄件人姓名', trigger: 'blur' }],
expressTempId: [{ required: true, message: '请选择电子面单', trigger: 'blur' }],
toTel: [{ required: true, message: '请输入寄件人电话', trigger: 'blur' }],
toAddr: [{ required: true, message: '请输入寄件人地址', trigger: 'blur' }],
};

View File

@@ -0,0 +1,789 @@
<template>
<div class="divBox relative">
<el-card
:bordered="false"
shadow="never"
class="ivu-mt"
:body-style="{ padding: 0 }"
v-if="checkPermi(['merchant:order:page:list'])"
>
<div class="padding-add">
<el-form inline label-position="right" @submit.native.prevent>
<el-form-item label="订单编号:" label-width="66px">
<el-input
v-model.trim="tableFrom.orderNo"
placeholder="请输入订单号"
class="form_content_width"
size="small"
@keyup.enter.native="handleSearchList"
clearable
>
</el-input>
</el-form-item>
<el-form-item label="订单类型:">
<el-select
v-model="tableFrom.type"
clearable
size="small"
placeholder="请选择"
class="form_content_width"
@change="handleSearchList"
>
<el-option v-for="(item, i) in fromType" :key="i" :label="item.text" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="时间选择:">
<el-date-picker
v-model="timeVal"
value-format="yyyy-MM-dd"
format="yyyy-MM-dd"
size="small"
type="daterange"
placement="bottom-end"
placeholder="自定义时间"
@change="onchangeTime"
class="form_content_width"
/>
</el-form-item>
<el-form-item label="用户搜索:" label-for="nickname">
<UserSearchInput v-model="tableFrom" />
</el-form-item>
<el-form-item>
<el-button type="primary" size="small" @click="handleSearchList">查询</el-button>
<el-button size="small" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</div>
</el-card>
<el-card
shadow="never"
:bordered="false"
class="box-card mt16"
:body-style="{ padding: '0 20px 20px', position: 'relative' }"
v-if="checkPermi(['merchant:order:status:num', 'merchant:order:page:list'])"
>
<el-tabs class="list-tabs" v-model="tableFrom.status" @tab-click="handleSearchList">
<el-tab-pane name="all" :label="`全部(${orderChartType.all || 0})`"></el-tab-pane>
<el-tab-pane name="notShipped" :label="`待发货(${orderChartType.notShipped || 0})`"></el-tab-pane>
<el-tab-pane name="spike" :label="`待收货(${orderChartType.spike || 0})`"></el-tab-pane>
<el-tab-pane name="awaitVerification" :label="`待使用(${orderChartType.verification || 0})`"></el-tab-pane>
<el-tab-pane name="receiving" :label="`已收货(${orderChartType.receiving || 0})`"></el-tab-pane>
<el-tab-pane name="complete" :label="`已完成(${orderChartType.complete || 0})`"></el-tab-pane>
<el-tab-pane name="refunded" :label="`已退款(${orderChartType.refunded || 0})`"></el-tab-pane>
<el-tab-pane name="deleted" :label="`已删除(${orderChartType.deleted || 0})`"></el-tab-pane>
</el-tabs>
<div class="mt5">
<!-- <el-button size="small" type="primary" @click="onWriteOff" v-hasPermi="['merchant:order:verification']"
>核销订单</el-button
> -->
<el-button size="small" @click="exports" v-hasPermi="['merchant:export:order:excel']">导出</el-button>
</div>
<el-table
v-loading="listLoading"
:data="tableData.data"
size="mini"
class="mt20"
highlight-current-row
:row-key="
(row) => {
return row.orderNo;
}
"
>
<el-table-column label="订单号" min-width="220" v-if="checkedCities.includes('订单号')">
<template slot-scope="scope">
<div class="acea-row">
<font v-show="scope.row.type === 1" class="mr5">[秒杀]</font>
<font v-show="scope.row.type === 2" class="mr5">[拼团]</font>
<span style="display: block" v-text="scope.row.orderNo" />
</div>
<div class="flex">
<span class="colorPrompt" v-show="parseInt(scope.row.refundStatus) > 0" style="display: block">{{
scope.row.refundStatus | orderRefundStatusFilter
}}</span>
<span v-show="scope.row.refundStatus == 2" class="colorPrompt">{{
`(已退款 ${scope.row.refundNum} / 购买 ${scope.row.totalNum}`
}}</span>
</div>
<span v-show="scope.row.isUserDel" class="colorPrompt" style="display: block">用户已删除</span>
</template>
</el-table-column>
<el-table-column label="用户昵称" min-width="150" v-if="checkedCities.includes('用户昵称')">
<template slot-scope="scope">
<span :class="scope.row.isLogoff == true ? 'colorPrompt' : ''">{{ scope.row.nickName }}</span>
<span :class="scope.row.isLogoff == true ? 'colorPrompt' : ''" v-if="scope.row.isLogoff == true">|</span>
<span v-if="scope.row.isLogoff == true" class="colorPrompt">(已注销)</span>
</template>
</el-table-column>
<el-table-column prop="payPrice" label="实际支付" min-width="80" v-if="checkedCities.includes('实际支付')" />
<el-table-column label="支付方式" min-width="80" v-if="checkedCities.includes('支付方式')">
<template slot-scope="scope">
<span>{{ scope.row.payType | payTypeFilter }}</span>
</template>
</el-table-column>
<el-table-column label="订单状态" min-width="100" v-if="checkedCities.includes('订单状态')">
<template slot-scope="scope">
<span class="textE93323 tag-background notStartTag tag-padding" v-if="scope.row.refundStatus === 3"
>已退款</span
>
<!-- 次卡商品部分使用状态 -->
<span
class="tag-background tag-padding doingTag"
v-else-if="
scope.row.secondType == OrderSecondTypeEnum.PunchCard &&
scope.row.status == 3 &&
scope.row.infoResponseList[0].verifyRemainingTimes != scope.row.infoResponseList[0].verifyTotalTimes
"
>部分使用</span
>
<span
:class="scope.row.status < 5 ? 'doingTag' : 'endTag'"
class="tag-background tag-padding"
v-else-if="
scope.row.groupBuyRecordStatus == 99 || scope.row.status == 9 || scope.row.groupBuyRecordStatus == 10
"
>{{ scope.row.status | orderStatusFilter }}</span
>
<span class="textE93323 tag-background notStartTag tag-padding" v-else>{{
scope.row.groupBuyRecordStatus == 0 ? '拼团中' : '拼团失败'
}}</span>
</template>
</el-table-column>
<el-table-column label="备注" min-width="150">
<template slot-scope="scope">
<span>{{ scope.row.merchantRemark | filterEmpty }}</span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="下单时间" min-width="160" v-if="checkedCities.includes('下单时间')" />
<el-table-column width="160" fixed="right">
<template slot="header">
<p>
<span style="padding-right: 5px">操作</span>
<i class="el-icon-setting" @click="handleAddItem"></i>
</p>
</template>
<template slot-scope="scope">
<a
@click="onOrderDetails(scope.row.orderNo)"
v-if="checkPermi(['merchant:order:info']) && scope.row.groupBuyRecordStatus != 0"
>详情
</a>
<el-divider direction="vertical" v-if="scope.row.groupBuyRecordStatus != 0"></el-divider>
<template
v-if="
(scope.row.status === 1 || scope.row.status === 2) &&
parseFloat(scope.row.refundStatus) < 3 &&
checkPermi(['merchant:order:send']) &&
scope.row.groupBuyRecordStatus != 0 &&
scope.row.secondType !== OrderSecondTypeEnum.Reservation
"
>
<a @click="sendOrder(scope.row)">发货 </a>
<el-divider direction="vertical"></el-divider>
</template>
<template
v-if="
scope.row.status === 3 &&
parseFloat(scope.row.refundStatus) < 3 &&
checkPermi(['merchant:order:verification']) &&
scope.row.groupBuyRecordStatus != 0 &&
scope.row.shippingType != 5
"
>
<a @click="writeOffInTable(scope.row)">核销 </a>
<el-divider direction="vertical"></el-divider>
</template>
<el-dropdown trigger="click" v-if="scope.row.groupBuyRecordStatus != 0">
<span class="el-dropdown-link"> 更多<i class="el-icon-arrow-down el-icon--right" /> </span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item
@click.native="onDirectRefund(scope.row)"
v-if="
scope.row.refundStatus == 0 &&
!scope.row.userRefundSign &&
checkPermi(['merchant:order:direct:refund'])
"
>直接退款
</el-dropdown-item>
<!-- 打印小票订单状态 待发货/待使用/待收货/已收货/已核销/已完成/已退款 原始订单状态
订单状态0待支付1待发货,2部分发货 3待使用4待收货,5已收货,6已完成9已取消-->
<el-dropdown-item
@click.native="handlePrintReceipt(scope.row)"
v-if="
(parseFloat(scope.row.status) < 7 || scope.row.refundStatus == 3) &&
merPrintStatus !== 2 &&
checkPermi(['merchant:order:print'])
"
>打印小票
</el-dropdown-item>
<el-dropdown-item
@click.native="handleOrderPrint(scope.row)"
>订单打印
</el-dropdown-item>
<el-dropdown-item @click.native="onOrderMark(scope.row)" v-if="checkPermi(['merchant:order:mark'])"
>订单备注
</el-dropdown-item>
<el-dropdown-item
v-if="scope.row.isUserDel === 1 && checkPermi(['merchant:order:delete'])"
@click.native="handleDelete(scope.row, scope.$index)"
>删除订单
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
</el-table>
<div class="block">
<el-pagination
background
:page-sizes="$constants.page.limit"
:page-size="tableFrom.limit"
:current-page="tableFrom.page"
layout="total, sizes, prev, pager, next, jumper"
:total="tableData.total"
@size-change="handleSizeChange"
@current-change="pageChange"
/>
</div>
<div class="card_abs" v-show="card_select_show">
<template>
<div class="cell_ht">
<el-checkbox :indeterminate="isIndeterminate" v-model="checkAll" @change="handleCheckAllChange"
>全选
</el-checkbox>
<el-button size="small" type="text" @click="checkSave()">保存</el-button>
</div>
<el-checkbox-group v-model="checkedCities" @change="handleCheckedCitiesChange">
<el-checkbox v-for="item in columnData" :label="item" :key="item" class="check_cell">{{
item
}}</el-checkbox>
</el-checkbox-group>
</template>
</div>
</el-card>
<!--记录-->
<el-dialog title="操作记录" :visible.sync="dialogVisibleJI" width="700px">
<el-table v-loading="LogLoading" border :data="tableDataLog.data" style="width: 100%">
<el-table-column prop="oid" label="ID" min-width="80" />
<el-table-column prop="changeMessage" label="操作记录" min-width="280" />
<el-table-column prop="createTime" label="操作时间" min-width="280" />
</el-table>
<div class="block">
<el-pagination
background
:page-sizes="$constants.page.limit"
:page-size="tableFromLog.limit"
:current-page="tableFromLog.page"
layout="total, sizes, prev, pager, next, jumper"
:total="tableDataLog.total"
@size-change="handleSizeChangeLog"
@current-change="pageChangeLog"
/>
</div>
</el-dialog>
<!--详情-->
<details-from ref="orderDetail" :orderNo="orderNo" />
<!-- 发送货 -->
<order-send ref="send" :orderNo="orderNo" :secondType="secondType" @submitFail="handleSearchList"></order-send>
<!-- 直接退款 -->
<direct-refund
v-if="dialogVisibleDirectRefund"
:dialogVisibleDirectRefund="dialogVisibleDirectRefund"
:type="type"
@handlerSuccessClose="handlerSuccessClose"
@handlerSuccessSubmit="handlerSuccessSubmit"
:orderNo="orderNo"
:secondType="secondType"
></direct-refund>
<!-- 核销弹窗 -->
<writeOffDialog
ref="writeOffDialog"
:writeOffData="writeOffData"
@handleSearchList="handleSearchList"
></writeOffDialog>
</div>
</template>
<script>
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
import {
merchantOrderStatusNumApi as orderStatusNumApi,
merchantOrderListApi as orderListApi,
merchantOrderLogApi as orderLogApi,
merchantOrderMarkApi as orderMarkApi,
merchantOrderDeleteApi as orderDeleteApi,
merchantOrderPrint as orderPrint,
merchantOrderDetailApi as orderDetailApi,
merchantWriteUpdateApi as writeUpdateApi,
merchantOrderExcelApi as orderExcelApi,
merchantOrderPrintReceiptApi as orderPrintReceiptApi,
} from '@/api/merchantOrder';
import detailsFrom from './components/orderDetail';
import writeOffDialog from './components/writeOffDialog';
import orderSend from './orderSend';
import Cookies from 'js-cookie';
import { isWriteOff } from '@/utils';
import { checkPermi } from '@/utils/permission';
import DirectRefund from '@/views/merchantOrder/components/directRefund.vue';
import * as $constants from '@/utils/constants';
import { handleDeleteTable } from '@/libs/public';
import { OrderSecondTypeEnum } from '@/enums/productEnums'; // 权限判断函数
let tableFroms = {
status: 'all',
dateLimit: '',
orderNo: '',
page: 1,
limit: $constants.page.limit[0],
type: '',
searchType: 'all',
content: '',
};
export default {
name: 'orderlistDetails',
components: {
DirectRefund,
detailsFrom,
orderSend,
writeOffDialog,
},
data() {
return {
RefuseVisible: false,
RefuseData: {},
orderNo: '',
refundVisible: false,
refundData: {},
dialogVisibleJI: false,
tableDataLog: {
data: [],
total: 0,
},
tableFromLog: {
page: 1,
limit: this.$constants.page.limit[0],
orderNo: 0,
},
LogLoading: false,
isCreate: 1,
editData: null,
dialogVisible: false,
tableData: {
data: [],
total: 0,
},
listLoading: false,
tableFrom: Object.assign({}, tableFroms),
orderChartType: {},
timeVal: [],
fromList: this.$constants.fromList,
fromType: [
{ value: '', text: '全部' },
{ value: '0', text: '普通' },
{ value: '1', text: '秒杀' },
{ value: '2', text: '拼团' },
],
selectionList: [],
ids: '',
orderids: '',
cardLists: [],
isWriteOff: isWriteOff(),
proType: 0,
active: false,
card_select_show: false,
checkAll: true,
checkedCities: ['订单号', '用户昵称', '实际支付', '支付方式', '订单状态', '下单时间'],
columnData: ['订单号', '用户昵称', '实际支付', '支付方式', '订单状态', '下单时间'],
isIndeterminate: false,
orderDatalist: null,
merPrintStatus: Cookies.get('merPrint'), // 商家小票打印开关状态
dialogVisibleDirectRefund: false,
secondType: 0, //订单二级类型:0-普通订单1-积分订单2-虚拟订单4-视频号订单5-云盘订单6-卡密订单
type: 0, //订单类型
writeOffData: {}, // 核销订单数据
OrderSecondTypeEnum: OrderSecondTypeEnum,
};
},
mounted() {
if (checkPermi(['merchant:order:page:list'])) this.getList();
if (checkPermi(['merchant:order:status:num'])) this.getOrderStatusNum();
},
methods: {
checkPermi,
//直接退款
onDirectRefund(row) {
this.secondType = row.secondType;
this.orderNo = row.orderNo;
this.type = row.type;
this.dialogVisibleDirectRefund = true;
},
//直接退款关闭
handlerSuccessClose() {
this.dialogVisibleDirectRefund = false;
},
//直接退款成功回调
handlerSuccessSubmit() {
this.dialogVisibleDirectRefund = false;
this.getList();
},
// 核销订单
onWriteOff(row) {
this.$modalPrompt('text', '核销订单', null, '核销码').then((V) => {
writeUpdateApi({ verifyCode: V, verifyCount: 1 }).then(() => {
this.$message.success('核销成功');
this.handleSearchList();
});
});
},
handleReset() {
this.tableFrom.type = '';
this.tableFrom.dateLimit = '';
this.tableFrom.orderNo = '';
this.tableFrom.page = 1;
this.tableFrom.content = '';
this.tableFrom.searchType = 'all';
this.selectChange();
},
resetFormRefundhandler() {
this.refundVisible = false;
},
resetFormRefusehand() {
this.RefuseVisible = false;
},
resetForm(formValue) {
this.dialogVisible = false;
},
handleSearchList() {
this.tableFrom.page = 1;
this.getList();
this.getOrderStatusNum();
},
// 发送
sendOrder(row) {
if (row.isLogoff) {
this.$modalSure('当前用户已注销,有责发货!').then(() => {
this.onSend(row);
});
} else {
if (row.refundStatus == 1) return this.$message.error('请先处理售后,再进行发货/核销操作');
this.onSend(row);
}
},
handlePrintReceipt(row) {
this.$modalSure('确认打印小票').then(() => {
orderPrintReceiptApi(row.orderNo).then((data) => {
this.$message.success('小票打印成功');
});
});
},
//发货操作
onSend(row) {
this.secondType = row.secondType;
this.orderNo = row.orderNo;
this.$refs.send.modals = true;
//this.$refs.send.getList();
this.$refs.send.orderProDetail(row.orderNo);
},
// 订单删除
handleDelete(row, idx) {
if (row.isDel) {
this.$modalSure().then(() => {
orderDeleteApi({ orderNo: row.orderNo }).then(() => {
this.$message.success('删除成功');
handleDeleteTable(this.tableData.data.length, this.tableFrom);
this.getList();
this.getOrderStatusNum();
});
});
} else {
this.$confirm('您选择的的订单存在用户未删除的订单,无法删除用户未删除的订单!', '提示', {
confirmButtonText: '确定',
type: 'error',
});
}
},
// 详情
onOrderDetails(id) {
this.orderNo = id;
this.$refs.orderDetail.getDetail(id);
this.$refs.orderDetail.getOrderInvoiceList(id);
this.$refs.orderDetail.dialogVisible = true;
},
getDetail(id) {
this.loading = true;
orderDetailApi(id)
.then((res) => {
this.orderDatalist = res;
this.loading = false;
})
.catch(() => {
this.orderDatalist = null;
this.loading = false;
});
},
// 订单记录
onOrderLog(id) {
this.dialogVisibleJI = true;
this.LogLoading = true;
this.tableFromLog.orderNo = id;
orderLogApi(this.tableFromLog)
.then((res) => {
this.tableDataLog.data = res.list;
this.tableDataLog.total = res.total;
this.LogLoading = false;
})
.catch(() => {
this.LogLoading = false;
});
},
pageChangeLog(page) {
this.tableFromLog.page = page;
this.onOrderLog();
},
handleSizeChangeLog(val) {
this.tableFromLog.limit = val;
this.onOrderLog();
},
handleClose() {
this.dialogVisible = false;
},
// 备注
onOrderMark(row) {
this.$modalPrompt('textarea', '备注', row.merchantRemark, '订单备注').then((V) => {
orderMarkApi({ remark: V, orderNo: row.orderNo }).then(() => {
this.$message.success('操作成功');
this.getList();
});
});
},
handleSelectionChange(val) {
this.selectionList = val;
const data = [];
this.selectionList.map((item) => {
data.push(item.orderNo);
});
this.ids = data.join(',');
},
// 选择时间
selectChange(tab) {
this.timeVal = [];
this.tableFrom.page = 1;
this.getList();
this.getOrderStatusNum();
},
// 具体日期
onchangeTime(e) {
this.timeVal = e;
this.tableFrom.dateLimit = e ? this.timeVal.join(',') : '';
this.tableFrom.page = 1;
this.getList();
this.getOrderStatusNum();
},
// 列表
getList() {
this.listLoading = true;
orderListApi(this.tableFrom)
.then((res) => {
this.tableData.data = res.list || [];
this.tableData.total = res.total;
this.listLoading = false;
this.checkedCities = this.$cache.local.has('order_stroge')
? this.$cache.local.getJSON('order_stroge')
: this.checkedCities;
})
.catch(() => {
this.listLoading = false;
});
},
// 获取各状态数量
getOrderStatusNum() {
let data = Object.assign({}, this.tableFrom);
delete data.page;
delete data.limit;
delete data.status;
orderStatusNumApi(data).then((res) => {
this.orderChartType = res;
});
},
pageChange(page) {
this.tableFrom.page = page;
this.getList();
},
handleSizeChange(val) {
this.tableFrom.limit = val;
this.getList();
},
exports() {
let data = {
dateLimit: this.tableFrom.dateLimit,
orderNo: this.tableFrom.orderNo,
status: this.tableFrom.status,
type: this.tableFrom.type,
};
orderExcelApi(data).then((res) => {
window.open(res.fileName);
});
},
handleAddItem() {
if (this.card_select_show) {
this.$set(this, 'card_select_show', false);
} else if (!this.card_select_show) {
this.$set(this, 'card_select_show', true);
}
},
handleCheckAllChange(val) {
this.checkAll = true;
this.checkedCities = val ? this.columnData : [];
this.isIndeterminate = false;
},
handleCheckedCitiesChange(value) {
let checkedCount = value.length;
this.checkAll = checkedCount === this.columnData.length;
this.isIndeterminate = checkedCount > 0 && checkedCount < this.columnData.length;
},
checkSave() {
this.$set(this, 'card_select_show', false);
this.$modal.loading('正在保存到本地,请稍候...');
this.$cache.local.setJSON('order_stroge', this.checkedCities);
setTimeout(this.$modal.closeLoading(), 1000);
},
// 订单打印(新窗口打开打印页)
handleOrderPrint(row) {
const routeData = this.$router.resolve({
path: `/merchantOrder/print/${row.orderNo}`,
});
window.open(routeData.href, '_blank');
},
//打印小票
onOrderPrint(data) {
orderPrint(data.orderNo)
.then((res) => {
this.$modal.msgSuccess('打印成功');
})
.catch((error) => {
this.$modal.msgError(error.message);
});
},
// 核销订单-table内核销
writeOffInTable(data) {
this.writeOffData = data
const param = {
orderNo: data.orderNo,
verifyCount: 1,
};
const orderSecondTpye = data.secondType;
if (orderSecondTpye == this.OrderSecondTypeEnum.PunchCard) {
this.$refs.writeOffDialog.openDialog();
} else {
this.$confirm('是否确认核销此订单?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
customClass: 'deleteConfirm'
}).then(() => {
writeUpdateApi(param).then(() => {
this.$message.success('核销成功');
this.handleSearchList();
});
}).catch(() => {
this.$message({
type: 'info',
message: '已取消核销'
});
});
}
},
},
};
</script>
<style lang="scss" scoped>
::v-deep .el-card {
overflow: auto !important;
}
font {
color: var(--prev-color-primary);
}
.el-table__body {
width: 100%;
table-layout: fixed !important;
}
.demo-table-expand {
::v-deep .label {
width: 83px !important;
}
}
.refunding {
span {
display: block;
}
}
.el-icon-arrow-down {
font-size: 12px;
}
.tabBox_tit {
font-size: 12px !important;
/*margin: 0 2px 0 10px;*/
letter-spacing: 1px;
/*padding: 5px 0;*/
box-sizing: border-box;
}
.text_overflow {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 400px;
}
.pup_card {
width: 200px;
border-radius: 5px;
padding: 5px;
box-sizing: border-box;
font-size: 12px;
line-height: 16px;
}
.flex-column {
display: flex;
flex-direction: column;
}
.relative {
position: relative;
}
.cell_ht {
height: 50px;
padding: 15px 20px;
box-sizing: border-box;
border-bottom: 1px solid #eeeeee;
display: flex;
justify-content: space-between;
align-items: center;
}
.check_cell {
width: 100%;
padding: 15px 20px 0;
}
::v-deep .el-checkbox__input.is-checked + .el-checkbox__label {
color: #606266;
}
</style>

View File

@@ -0,0 +1,375 @@
<template>
<div class="order-print-page" v-loading="loading">
<div class="print-header no-print">
<el-button type="primary" size="medium" icon="el-icon-printer" @click="handlePrint">打印</el-button>
<el-button size="medium" @click="goBack">返回</el-button>
</div>
<div class="print-content" v-if="orderData">
<div class="print-title">
<h2>订单详情</h2>
<p class="order-no">订单号{{ orderData.orderNo }}</p>
</div>
<!-- 订单基本信息 -->
<div class="print-section">
<div class="section-title">订单信息</div>
<table class="info-table">
<tr>
<td class="label">订单号</td>
<td>{{ orderData.orderNo }}</td>
<td class="label">下单时间</td>
<td>{{ orderData.createTime }}</td>
</tr>
<tr>
<td class="label">订单状态</td>
<td>{{ orderData.status | orderStatusFilter }}</td>
<td class="label">支付方式</td>
<td>{{ orderData.payType | payTypeFilter }}</td>
</tr>
<tr>
<td class="label">实际支付</td>
<td>¥ {{ orderData.payPrice || '0.00' }}</td>
<td class="label">支付时间</td>
<td>{{ orderData.payTime || '-' }}</td>
</tr>
</table>
</div>
<!-- 收货信息 -->
<div class="print-section" v-if="orderData.realName">
<div class="section-title">收货信息</div>
<table class="info-table">
<tr>
<td class="label">收货人</td>
<td>{{ orderData.realName }}</td>
<td class="label">联系电话</td>
<td>{{ orderData.userPhone }}</td>
</tr>
<tr>
<td class="label">收货地址</td>
<td colspan="3">{{ orderData.userAddress }}</td>
</tr>
</table>
</div>
<!-- 商品信息 -->
<div class="print-section">
<div class="section-title">商品信息</div>
<table class="goods-table">
<thead>
<tr>
<th style="width: 50px">序号</th>
<th>商品名称</th>
<th>商品详情</th>
<th style="width: 80px">规格</th>
<th style="width: 80px">单价</th>
<th style="width: 60px">数量</th>
<th style="width: 90px">小计</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in productList" :key="index">
<td class="center">{{ index + 1 }}</td>
<td>{{ item.productName }}</td>
<td>
<div class="product-info" v-if="item.info">
<template v-if="typeof parsedInfo(item.info) === 'object'">
<div v-for="(val, key) in parsedInfo(item.info)" :key="key" class="info-item">
<span class="info-key">{{ key }}:</span> {{ val }}
</div>
</template>
<template v-else>
{{ item.info }}
</template>
</div>
<span v-else>-</span>
</td>
<td class="center">{{ item.sku || '-' }}</td>
<td class="center">¥ {{ item.price }}</td>
<td class="center">{{ item.payNum }}</td>
<td class="center">¥ {{ (item.price * item.payNum).toFixed(2) }}</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="5" class="right"><strong>合计</strong></td>
<td class="center"><strong>{{ totalNum }}</strong></td>
<td class="center"><strong>¥ {{ totalPrice }}</strong></td>
</tr>
</tfoot>
</table>
</div>
<!-- 买家备注 -->
<div class="print-section" v-if="orderData.userRemark">
<div class="section-title">买家留言</div>
<p class="remark-text">{{ orderData.userRemark }}</p>
</div>
<!-- 商家备注 -->
<div class="print-section" v-if="orderData.merchantRemark">
<div class="section-title">商家备注</div>
<p class="remark-text">{{ orderData.merchantRemark }}</p>
</div>
<!-- 页脚 -->
<div class="print-footer">
<p>打印时间{{ printTime }}</p>
</div>
</div>
<div v-if="!loading && !orderData" class="no-data">
<p>未找到订单数据</p>
<el-button type="primary" @click="goBack">返回</el-button>
</div>
</div>
</template>
<script>
import { merchantOrderPrintDetailApi } from '@/api/merchantOrder';
import { parseTime } from '@/utils';
export default {
name: 'MerchantOrderPrint',
data() {
return {
loading: true,
orderData: null,
productList: [],
printTime: '',
};
},
computed: {
totalNum() {
return this.productList.reduce((sum, item) => sum + (item.payNum || 0), 0);
},
totalPrice() {
return this.productList.reduce((sum, item) => sum + (item.price * item.payNum || 0), 0).toFixed(2);
},
},
filters: {
orderStatusFilter(status) {
const statusMap = {
0: '待支付',
1: '待发货',
2: '部分发货',
3: '待使用',
4: '待收货',
5: '已收货',
6: '已完成',
9: '已取消',
};
return statusMap[status] || '未知';
},
payTypeFilter(type) {
const typeMap = {
weixin: '微信支付',
alipay: '支付宝',
yue: '余额支付',
shoppingCredits: '购物金',
};
return typeMap[type] || type || '-';
},
},
created() {
const orderNo = this.$route.params.orderNo;
if (orderNo) {
this.getPrintDetail(orderNo);
} else {
this.loading = false;
}
this.printTime = parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}');
},
methods: {
getPrintDetail(orderNo) {
this.loading = true;
merchantOrderPrintDetailApi(orderNo)
.then((res) => {
this.orderData = res.orderInfo || {};
this.productList = res.detailList || [];
this.loading = false;
})
.catch(() => {
this.loading = false;
});
},
parsedInfo(info) {
if (!info) return '';
try {
return JSON.parse(info);
} catch (e) {
return info;
}
},
handlePrint() {
window.print();
},
goBack() {
window.close();
},
},
};
</script>
<style lang="scss" scoped>
.order-print-page {
background: #fff;
min-height: 100vh;
padding: 20px;
}
.print-header {
position: fixed;
top: 0;
left: 0;
right: 0;
background: #fff;
padding: 15px 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
z-index: 100;
display: flex;
gap: 10px;
}
.print-content {
max-width: 800px;
margin: 80px auto 20px;
padding: 20px;
}
.print-title {
text-align: center;
margin-bottom: 30px;
h2 {
font-size: 22px;
margin-bottom: 8px;
color: #303133;
}
.order-no {
font-size: 14px;
color: #606266;
}
}
.print-section {
margin-bottom: 25px;
}
.section-title {
font-size: 15px;
font-weight: 600;
color: #303133;
padding-bottom: 8px;
border-bottom: 2px solid #409eff;
margin-bottom: 12px;
}
.info-table {
width: 100%;
border-collapse: collapse;
td {
padding: 8px 12px;
font-size: 13px;
color: #606266;
border: 1px solid #ebeef5;
}
.label {
width: 100px;
font-weight: 500;
color: #303133;
background: #f5f7fa;
white-space: nowrap;
}
}
.goods-table {
width: 100%;
border-collapse: collapse;
th,
td {
padding: 8px 10px;
font-size: 13px;
border: 1px solid #ebeef5;
}
th {
background: #f5f7fa;
color: #303133;
font-weight: 500;
text-align: center;
}
.center {
text-align: center;
}
.right {
text-align: right;
}
tfoot td {
background: #f5f7fa;
}
}
.product-info {
.info-item {
font-size: 12px;
line-height: 1.6;
color: #909399;
}
.info-key {
color: #606266;
font-weight: 500;
}
}
.remark-text {
font-size: 13px;
color: #606266;
line-height: 1.6;
padding: 10px;
background: #f5f7fa;
border-radius: 4px;
}
.print-footer {
text-align: right;
margin-top: 30px;
padding-top: 15px;
border-top: 1px dashed #dcdfe6;
font-size: 12px;
color: #909399;
}
.no-data {
text-align: center;
padding: 100px 0;
p {
font-size: 16px;
color: #909399;
margin-bottom: 20px;
}
}
/* 打印样式 */
@media print {
.no-print {
display: none !important;
}
.print-content {
margin: 0 auto;
padding: 0;
}
.order-print-page {
padding: 10px;
}
.section-title {
border-bottom-color: #000;
}
.info-table .label {
background: #eee;
}
.goods-table th,
.goods-table tfoot td {
background: #eee;
}
}
</style>

View File

@@ -0,0 +1,330 @@
<template>
<el-dialog
:visible.sync="modals"
:close-on-click-modal="false"
:title="secondType !== OrderSecondTypeEnum.Fictitious ? '订单发送货' : '虚拟商品发货'"
class="order_box"
:before-close="handleClose"
width="900px"
>
<el-form v-if="modals" ref="formItem" :model="formItem" label-width="95px" @submit.native.prevent :rules="rules">
<el-form-item v-show="secondType !== OrderSecondTypeEnum.Fictitious" label="配送方式:" prop="deliveryType">
<el-radio-group v-model="formItem.deliveryType" @change="changeRadio(formItem.deliveryType)" v-removeAriaHidden>
<el-radio label="express">快递配送</el-radio>
<el-radio label="noNeed">无需发货</el-radio>
<el-radio label="merchant">商家送货</el-radio>
</el-radio-group>
</el-form-item>
<SendFrom :formItem="formItem" :isShowBtn="true"></SendFrom>
<el-form-item v-show="secondType !== OrderSecondTypeEnum.Fictitious" label="分单发货:" prop="isSplit">
<el-switch
v-model="formItem.isSplit"
:active-value="true"
:inactive-value="false"
active-text="开启"
inactive-text="关闭"
/>
<p v-show="formItem.isSplit" class="from-tips">可选择表格中的商品单独发货请谨慎操作</p>
</el-form-item>
<template v-if="formItem.isSplit">
<el-table
style="padding-left: 75px"
ref="multipleSelection"
:data="productList"
tooltip-effect="dark"
class="tableSelection"
size="small"
:row-key="
(row) => {
return row.id;
}
"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" :selectable="selectable" :reserve-selection="true" min-width="50" />
<el-table-column label="商品信息" width="200">
<template slot-scope="scope">
<div class="acea-row" style="align-items: center">
<div class="demo-image__preview line-heightOne refundImg">
<el-image :src="scope.row.image" :preview-src-list="[scope.row.image]" />
<span class="priceBox product-info-text" style="width: 120px">{{ scope.row.productName }}</span>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="规格" min-width="120">
<template slot-scope="scope">
<span class="priceBox">{{ scope.row.sku }}</span>
</template>
</el-table-column>
<el-table-column label="总数" min-width="80">
<template slot-scope="scope">
<span class="priceBox">{{ scope.row.payNum }}</span>
<div class="priceBox textE93323">已发{{ scope.row.deliveryNum }}</div>
<div class="priceBox textE93323">已退款{{ scope.row.refundNum }}</div>
</template>
</el-table-column>
<el-table-column label="发货数量" min-width="120">
<template slot-scope="scope">
<el-input-number
:disabled="scope.row.deliveryNum === scope.row.payNum"
v-model.trim="scope.row['num']"
:min="1"
:max="
Number(scope.row.deliveryNum) == 0
? Number(scope.row.payNum) - Number(scope.row.refundNum)
: Number(scope.row.payNum) - Number(scope.row.deliveryNum)
"
class="priceBox"
:step="1"
@blur="limitCount(scope.row, scope.$index)"
/>
</template>
</el-table-column>
</el-table>
</template>
</el-form>
<div slot="footer" class="dialog-btn-top">
<el-button @click="cancel('formItem')">取消</el-button>
<el-button type="primary" @click="putSend('formItem')">提交</el-button>
</div>
</el-dialog>
</template>
<script>
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
import { merchantOrderProDetailApi as orderProDetailApi, merchantOrderSendApi as orderSendApi } from '@/api/merchantOrder';
import { checkPermi } from '@/utils/permission'; // 权限判断函数
import { Debounce } from '@/utils/validate';
import SendFrom from './components/sendFrom';
import { useLogistics } from '@/hooks/use-order';
import { postRules } from '@/views/merchantOrder/default';
import { OrderSecondTypeEnum } from '@/enums/productEnums';
const defaultObj = {
deliveryType: 'express',
isSplit: false,
deliveryMark: '',
carrierPhone: '',
deliveryCarrier: '',
expressCode: '',
expressNumber: '',
orderNo: '',
detailList: [],
// deliveryType: 'express',
expressRecordType: '1', // 1=手动填写 2=电子面单
// expressCode: '',
company: '', //快递公司
deliveryName: '',
deliveryTel: '',
expressName: '',
// expressNumber: '',
expressTempId: '',
toAddr: '',
toName: '',
toTel: '',
// orderNo: '',
shipment: {
sendRealName: '',
sendPhone: '',
sendAddress: '',
kuaidicom: '', //快递公司编码
serviceType: '', //快递业务类型
pickupStartTime: '', // 取件开始时间
pickupEndTime: '', // 取件结束时间
tempid: '', //电子面单模板id
dayType: 1, //时间
},
};
export default {
name: 'orderSend',
components: { SendFrom },
props: {
orderNo: {
type: String,
default: '',
},
secondType: {
type: Number,
default: 0,
},
},
watch: {
modals: {
handler(nVal, oVal) {
if (nVal) {
if (this.secondType === this.OrderSecondTypeEnum.Fictitious) this.formItem.deliveryType = 'noNeed';
}
},
deep: true,
},
},
data() {
return {
OrderSecondTypeEnum: OrderSecondTypeEnum,
productList: [],
formItem: { ...defaultObj },
modals: false,
express: [],
exportTempList: [],
tempImg: '',
rules: postRules,
multipleSelection: [],
};
},
mounted() {
this.getList();
if (this.secondType === this.OrderSecondTypeEnum.Integral) this.formItem.deliveryType = 'noNeed';
},
methods: {
checkPermi,
//决定这一行的 CheckBox 是否可以勾选
selectable(row, index) {
if (row.deliveryNum === row.payNum) {
return false;
} else {
return true;
}
},
limitCount(row, i) {
if (row.num > row.payNum) row.num = row.payNum;
},
// 分单发货选择商品
handleSelectionChange(val) {
this.multipleSelection = val;
},
changeRadio(o) {
this.formItem.deliveryType = o;
this.$nextTick(function () {
this.$refs.formItem.clearValidate();
});
},
// 商品信息
orderProDetail(orderNo) {
orderProDetailApi(orderNo).then(async (res) => {
this.productList = res;
this.productList.map((item) => this.$set(item, 'num', 1));
});
},
// 物流公司列表
async getList() {
const params = {
keywords: '',
page: 1,
limit: 50,
openStatus: true,
};
this.express = await useLogistics(params);
this.express.map((item) => {
if (item.isDefault) this.formItem.expressCode = item.code;
});
},
// 提交
putSend: Debounce(function (name) {
let data = {};
let attr = [];
this.formItem.orderNo = this.orderNo;
this.multipleSelection.map((item) => {
attr.push({ orderDetailId: item.id, num: item.num });
});
this.formItem.detailList = attr;
if (!this.formItem.isSplit) {
data = { ...this.formItem };
delete data.detailList;
} else {
if (!this.formItem.detailList.length) return this.$message.warning('请选择分单发货商品');
let flag = false;
this.formItem.detailList.map((item) => {
if (!item.num) {
flag = true;
}
});
if (flag) {
this.$message.warning('请填写发货数量');
return;
}
data = this.formItem;
}
if (this.formItem.expressRecordType == '2') {
if (!this.formItem.toAddr) {
this.$message.warning('请填写寄件人地址');
return;
}
if (!this.formItem.toTel) {
this.$message.warning('请填写寄件人电话');
return;
}
if (!this.formItem.toName) {
this.$message.warning('请填写寄件人姓名');
return;
}
if (!this.formItem.expressTempId) {
this.$message.warning('请选择电子面单');
return;
}
}
this.$refs[name].validate((valid) => {
if (valid) {
orderSendApi(data).then((async) => {
this.$message.success('发货成功');
this.cancel();
this.$emit('submitFail');
});
} else {
this.$message.error('请填写信息');
}
});
}),
handleClose() {
this.cancel();
},
cancel() {
this.modals = false;
this.formItem = { ...defaultObj, expressCode: this.formItem.expressCode };
},
},
};
</script>
<style scoped lang="scss">
.width8 {
width: 80%;
}
.width9 {
width: 70%;
}
.tempImgList {
// opacity: 1;
width: 38px !important;
height: 30px !important;
// margin-top: -30px;
cursor: pointer;
position: absolute;
z-index: 11;
img {
width: 38px !important;
height: 30px !important;
}
}
.refundImg {
display: flex;
align-items: center;
}
.product-info-text {
display: block;
white-space: nowrap; /* 确保文本在一行内显示 */
overflow: hidden; /* 超出容器部分隐藏 */
text-overflow: ellipsis; /* 使用省略号表示被截断的文本 */
margin-left: 5px;
}
</style>

View File

@@ -0,0 +1,125 @@
<template>
<el-dialog :visible.sync="modals" title="发货" class="order_box" :before-close="handleClose" width="600px">
<el-form ref="formItem" :model="formItem" label-width="110px" @submit.native.prevent :rules="rules">
<el-form-item label="快递公司:" prop="expressCode">
<el-select v-model="formItem.deliveryId" filterable style="width: 80%">
<el-option
v-for="(item, i) in express"
:value="item.deliveryId"
:key="i"
:label="item.deliveryName"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="快递单号:" prop="waybillId">
<el-input v-model.trim="formItem.waybillId" placeholder="请输入快递单号" style="width: 80%"></el-input>
</el-form-item>
</el-form>
<div slot="footer">
<el-button size="mini" type="primary" @click="putSend('formItem')" v-hasPermi="['merchant:order:send']"
>提交</el-button
>
<el-button size="mini" @click="cancel('formItem')">取消</el-button>
</div>
</el-dialog>
</template>
<script>
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
import { merchantVideoSendApi as videoSendApi, merchantSheetInfoApi as sheetInfoApi, merchantCompanyGetListApi as companyGetListApi } from '@/api/merchantOrder';
import { Debounce } from '@/utils/validate';
export default {
name: 'orderSend',
props: {
orderId: String,
},
data() {
return {
formItem: {
deliveryId: '',
orderNo: '',
waybillId: '',
},
modals: false,
express: [],
exportTempList: [],
tempImg: '',
rules: {
deliveryId: [{ required: true, message: '请选择快递公司', trigger: 'change' }],
waybillId: [{ required: true, message: '请输入快递单号', trigger: 'blur' }],
},
expressType: 'normal',
};
},
mounted() {
this.express = JSON.parse(sessionStorage.getItem('videoExpress'));
},
methods: {
// 视频号快递公司
companyGetList() {
companyGetListApi().then(async (res) => {
this.express = res;
sessionStorage.setItem('videoExpress', JSON.stringify(res));
});
},
// 提交
putSend: Debounce(function (name) {
this.formItem.orderNo = this.orderId;
this.$refs[name].validate((valid) => {
if (valid) {
videoSendApi(this.formItem).then((async) => {
this.$message.success('发货成功');
this.modals = false;
this.$refs[name].resetFields();
this.$emit('submitFail');
});
} else {
this.$message.error('请填写信息');
}
});
}),
handleClose() {
this.cancel('formItem');
},
cancel(name) {
this.modals = false;
this.$refs[name].resetFields();
this.formItem.type = '1';
this.formItem.expressRecordType = '1';
},
},
};
</script>
<style scoped lang="scss">
.width8 {
width: 80%;
}
.width9 {
width: 70%;
}
.tempImgList {
// opacity: 1;
width: 38px !important;
height: 30px !important;
// margin-top: -30px;
cursor: pointer;
position: absolute;
z-index: 11;
img {
width: 38px !important;
height: 30px !important;
}
}
</style>

View File

@@ -0,0 +1,639 @@
<template>
<div class="divBox relative">
<el-card
:bordered="false"
shadow="never"
class="ivu-mt"
:body-style="{ padding: 0 }"
v-if="checkPermi(['merchant:refund:order:page:list'])"
>
<div class="padding-add">
<el-form size="small" inline label-position="right" @submit.native.prevent>
<el-form-item label="退款单号:">
<el-input
v-model.trim="tableFrom.refundOrderNo"
placeholder="请输入退款单号"
class="form_content_width"
size="small"
clearable
@keyup.enter.native="handleSearchList"
>
</el-input>
</el-form-item>
<el-form-item label="订单编号:" label-width="66px">
<el-input
v-model.trim="tableFrom.orderNo"
placeholder="请输入订单号"
class="form_content_width"
size="small"
clearable
>
</el-input>
</el-form-item>
<el-form-item label="时间选择:">
<el-date-picker
v-model="timeVal"
value-format="yyyy-MM-dd"
format="yyyy-MM-dd"
size="small"
type="daterange"
placement="bottom-end"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
class="form_content_width"
@change="onchangeTime"
/>
</el-form-item>
<el-form-item label="用户搜索:" label-for="nickname">
<UserSearchInput v-model="tableFrom" />
</el-form-item>
<el-form-item label="退货物流:" label-width="66px">
<el-input
v-model.trim="tableFrom.trackingNumber"
placeholder="请输入退货物流单号"
class="selWidth"
size="small"
clearable
@keyup.enter.native="handleSearchList"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" size="small" @click="handleSearchList">查询</el-button>
<el-button size="small" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</div>
</el-card>
<el-card class="box-card mt14" :body-style="{ padding: '0 20px 20px',position: 'relative' }" shadow="never" :bordered="false">
<el-tabs class="list-tabs" v-model="tableFrom.refundStatus" @tab-click="handleSearchList">
<el-tab-pane name="9" :label="`全部(${orderChartType.all || 0})`"></el-tab-pane>
<el-tab-pane name="0" :label="`待审核(${orderChartType.await || 0})`"></el-tab-pane>
<el-tab-pane name="2" :label="`退款中(${orderChartType.refunding || 0})`"></el-tab-pane>
<el-tab-pane name="4" :label="`用户退货(${orderChartType.awaitReturning || 0})`"></el-tab-pane>
<el-tab-pane name="5" :label="`商家待收货(${orderChartType.awaitReceiving || 0})`"></el-tab-pane>
<el-tab-pane name="6" :label="`已撤销(${orderChartType.revoke || 0})`"></el-tab-pane>
<el-tab-pane name="1" :label="`审核未通过(${orderChartType.reject || 0})`"></el-tab-pane>
<el-tab-pane name="3" :label="`已退款(${orderChartType.refunded || 0})`"></el-tab-pane>
</el-tabs>
<el-table
v-loading="listLoading"
:data="tableData.data"
size="small"
class="table mt5"
highlight-current-row
:row-key="
(row) => {
return row.refundOrderNo;
}
"
>
<el-table-column label="退款单号" min-width="185" v-if="checkedCities.includes('退款单号')">
<template slot-scope="scope">
<div class="acea-row">
<span v-show="scope.row.type === 1" class="iconfont icon-shipinhao mr5" style="color: #f6ae02"></span>
<span style="display: block" v-text="scope.row.refundOrderNo" />
</div>
</template>
</el-table-column>
<el-table-column prop="orderNo" label="订单号" min-width="180" v-if="checkedCities.includes('订单号')" />
<el-table-column
prop="userNickName"
label="用户昵称"
min-width="180"
v-if="checkedCities.includes('用户昵称')"
/>
<el-table-column
prop="refundPrice"
label="退款金额"
min-width="100"
v-if="checkedCities.includes('退款金额')"
/>
<el-table-column label="退款状态" min-width="100" v-if="checkedCities.includes('退款状态')">
<template slot-scope="scope">
<span :class="tagClass[scope.row.refundStatus]" class="tag-padding">{{
scope.row.refundStatus | refundStatusFilter
}}</span>
</template>
</el-table-column>
<el-table-column label="售后类型" min-width="100" v-if="checkedCities.includes('售后类型')">
<template slot-scope="scope">
<span>{{ scope.row.afterSalesType === 1 ? '仅退款' : '退货退款' }}</span>
</template>
</el-table-column>
<el-table-column label="退货类型" min-width="100" v-if="checkedCities.includes('退货类型')">
<template slot-scope="scope">
<span>{{
scope.row.returnGoodsType === 0 ? '不退货' : scope.row.returnGoodsType === 1 ? '快递退回' : '到店退货'
}}</span>
</template>
</el-table-column>
<el-table-column label="强制退款" min-width="100" v-if="checkedCities.includes('强制退款')">
<template slot-scope="scope">
<span>{{ scope.row.isCompulsoryRefund ? '是' : '不是' }}</span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" min-width="150" v-if="checkedCities.includes('创建时间')" />
<el-table-column width="190" fixed="right">
<template slot="header">
<p>
<span style="padding-right: 5px">操作</span>
<i class="el-icon-setting" @click="handleAddItem"></i>
</p>
</template>
<!-- 售后状态0:待审核 1:商家拒绝 2退款中 3:已退款 4:用户退货 5:商家待收货 6:已撤销 9:全部-->
<template slot-scope="scope">
<a
v-if="
scope.row.refundStatus !== 0 &&
scope.row.refundStatus !== 5 &&
checkPermi(['merchant:refund:order:detail'])
"
@click="onOrderDetails(scope.row)"
>详情</a
>
<template v-if="scope.row.refundStatus === 0 && checkPermi(['merchant:refund:order:audit'])">
<a @click="handleApprovedReview(scope.row)">同意</a>
<el-divider direction="vertical"></el-divider>
</template>
<template v-if="scope.row.refundStatus === 0 && checkPermi(['merchant:refund:order:audit'])">
<a @click="handleOrderRefuse(scope.row)">拒绝</a>
</template>
<template v-if="scope.row.refundStatus === 5 && checkPermi(['merchant:refund:order:receiving:reject'])">
<a @click="handleRefuseReceipt(scope.row)">拒绝</a>
</template>
<template v-if="scope.row.refundStatus === 5 && checkPermi(['merchant:refund:order:receiving'])">
<el-divider direction="vertical"></el-divider>
<a
v-debounceClick="
() => {
handleConfirmReceipt(scope.row.refundOrderNo);
}
"
>确认收货</a
>
</template>
<el-divider direction="vertical"></el-divider>
<el-dropdown trigger="click" v-if="scope.row.refundStatus === 0 || scope.row.refundStatus === 5">
<span class="el-dropdown-link"> 更多<i class="el-icon-arrow-down el-icon--right" /> </span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item
v-if="checkPermi(['merchant:refund:order:detail'])"
@click.native="onOrderDetails(scope.row)"
>详情
</el-dropdown-item>
<el-dropdown-item
@click.native="onOrderMark(scope.row)"
v-if="checkPermi(['merchant:refund:order:mark'])"
>备注
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<a
@click="onOrderMark(scope.row)"
v-if="
checkPermi(['merchant:refund:order:mark']) &&
scope.row.refundStatus !== 0 &&
scope.row.refundStatus !== 5
"
>备注</a
>
</template>
</el-table-column>
</el-table>
<div class="block">
<el-pagination
background
:page-sizes="$constants.page.limit"
:page-size="tableFrom.limit"
:current-page="tableFrom.page"
layout="total, sizes, prev, pager, next, jumper"
:total="tableData.total"
@size-change="handleSizeChange"
@current-change="pageChange"
/>
</div>
<div class="card_abs" v-show="card_select_show">
<template>
<div class="cell_ht">
<el-checkbox :indeterminate="isIndeterminate" v-model="checkAll" @change="handleCheckAllChange"
>全选
</el-checkbox>
<el-button type="text" @click="checkSave()">保存</el-button>
</div>
<el-checkbox-group v-model="checkedCities" @change="handleCheckedCitiesChange">
<el-checkbox v-for="item in columnData" :label="item" :key="item" class="check_cell">{{ item }}</el-checkbox>
</el-checkbox-group>
</template>
</div>
</el-card>
<!--退款详情-->
<refund-order-detail
ref="orderDetail"
:drawerVisible="drawerVisible"
:refundOrderNo="refundOrderNo"
v-if="drawerVisible"
@onClosedrawerVisible="onClosedrawerVisible"
@getReviewSuccessful="getReviewSuccessful"
></refund-order-detail>
<!-- 同意退款,退货退款-->
<el-dialog
title="同意退款"
:visible.sync="dialogVisibleAgreeToReturn"
width="900px"
:before-close="handleCloseAgreeToReturn"
class="dialog-bottom"
>
<agree-to-return
ref="agreeToReturn"
@onHandleCancel="handleCloseAgreeToReturn"
@onHandleSuccess="handleSuccess"
:refundInfo="refundInfo"
v-if="dialogVisibleAgreeToReturn"
></agree-to-return>
</el-dialog>
</div>
</template>
<script>
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
import {
merchantRefundStatusNumApi as refundStatusNumApi,
merchantRefundListApi as refundListApi,
merchantRefundMarkApi as refundMarkApi,
orderPrint,
merchantOrderAuditApi as orderAuditApi,
merchantRefundOrderReceivingRejectApi as refundOrderReceivingRejectApi,
} from '@/api/merchantOrder';
import { isWriteOff } from '@/utils';
import { orderExcelApi } from '@/api/product';
import { checkPermi } from '@/utils/permission';
import RefundOrderDetail from '../components/refundOrderDetail.vue';
import { refundStatusFilter } from '@/filters';
import useRefundOrder from '@/libs/useRefundOrder';
import AgreeToReturn from '@/views/merchantOrder/components/agreeToReturn.vue';
import * as $constants from '@/utils/constants';
const { onConfirmReceipt, onApprovedReview } = useRefundOrder();
const tableFroms = {
refundStatus: '9',
dateLimit: '',
orderNo: '',
refundOrderNo: '',
page: 1,
limit: $constants.page.limit[0],
searchType: 'all',
content: '',
trackingNumber: '',
};
// 权限判断函数
export default {
name: 'orderRefund',
components: {
AgreeToReturn,
RefundOrderDetail,
},
data() {
return {
datekey: Date.now(),
RefuseData: {},
refundOrderNo: '', //退款单号
refundData: {},
tableDataLog: {
data: [],
total: 0,
},
LogLoading: false,
isCreate: 1,
editData: null,
dialogVisible: false,
tableData: {
data: [],
total: 0,
},
listLoading: true,
tableFrom: Object.assign({}, tableFroms),
orderChartType: {},
timeVal: [],
fromList: this.$constants.fromList,
selectionList: [],
ids: '',
orderids: '',
cardLists: [],
isWriteOff: isWriteOff(),
proType: 0,
active: false,
card_select_show: false,
checkAll: true,
checkedCities: [
'退款单号',
'订单号',
'用户昵称',
'退款金额',
'退款状态',
'售后类型',
'退货类型',
'强制退款',
'创建时间',
],
columnData: [
'退款单号',
'订单号',
'用户昵称',
'退款金额',
'退款状态',
'售后类型',
'退货类型',
'强制退款',
'创建时间',
],
isIndeterminate: false,
dialogVisibleAgreeToReturn: false,
refundInfo: null,
drawerVisible: false,
tagClass: ['doingTag', 'notStartTag', 'doingTag', 'endTag', 'notStartTag', 'doingTag', 'endTag'],
};
},
mounted() {
if (checkPermi(['merchant:refund:order:page:list'])) this.getList();
if (checkPermi(['merchant:refund:order:status:num'])) this.getOrderStatusNum();
},
methods: {
checkPermi,
//商家确认收货
handleConfirmReceipt(refundOrderNo) {
onConfirmReceipt(refundOrderNo).then(() => {
this.handleSearchList();
});
},
//审核同意
handleApprovedReview(row) {
if (row.returnGoodsType !== 1) {
onApprovedReview({
auditType: 'success',
refundOrderNo: row.refundOrderNo,
}).then(() => {
this.handleSearchList();
});
} else {
//退货退款
this.refundInfo = row;
this.dialogVisibleAgreeToReturn = true;
}
},
//审核成功回调
handleSuccess() {
this.dialogVisibleAgreeToReturn = false;
this.handleSearchList();
},
//同意弹窗
handleCloseAgreeToReturn() {
this.dialogVisibleAgreeToReturn = false;
},
//审核拒绝
handleOrderRefuse(row) {
this.$modalPrompt('textarea', '拒绝退款原因', null).then((V) => {
orderAuditApi({ auditType: 'refuse', reason: V, refundOrderNo: row.refundOrderNo }).then(() => {
this.$message.success('审核成功');
this.handleSearchList();
});
});
},
//拒绝收货
handleRefuseReceipt(row) {
this.$modalPrompt('textarea', '拒绝收货原因', null).then((V) => {
refundOrderReceivingRejectApi({ reason: V, refundOrderNo: row.refundOrderNo }).then(() => {
this.$message.success('拒绝收货成功');
this.handleSearchList();
});
});
},
//重置
handleReset() {
this.tableFrom.dateLimit = '';
this.tableFrom.orderNo = '';
this.tableFrom.refundOrderNo = '';
this.tableFrom.content = '';
this.tableFrom.searchType = 'all';
this.tableFrom.trackingNumber = '';
this.selectChange();
},
//搜索
handleSearchList() {
this.tableFrom.page = 1;
this.getList();
this.getOrderStatusNum();
},
onClosedrawerVisible() {
this.drawerVisible = false;
},
//详情中审核成功回调
getReviewSuccessful() {
this.handleSearchList();
},
// 详情
onOrderDetails(row) {
this.refundOrderNo = row.refundOrderNo;
this.drawerVisible = true;
},
// 备注
onOrderMark(row) {
this.$modalPrompt('textarea', '备注', row.merRemark, '退款单备注').then((V) => {
refundMarkApi({ remark: V, refundOrderNo: row.refundOrderNo }).then(() => {
this.$message.success('操作成功');
this.getList();
});
});
},
handleSelectionChange(val) {
this.selectionList = val;
const data = [];
this.selectionList.map((item) => {
data.push(item.orderNo);
});
this.ids = data.join(',');
},
// 选择时间
selectChange(tab) {
this.timeVal = [];
this.tableFrom.page = 1;
this.handleSearchList();
},
// 具体日期
onchangeTime(e) {
this.timeVal = e;
this.tableFrom.dateLimit = e ? this.timeVal.join(',') : '';
this.handleSearchList();
},
// 列表
getList() {
this.listLoading = true;
refundListApi(this.tableFrom)
.then((res) => {
this.tableData.data = res.list || [];
this.tableData.total = res.total;
this.listLoading = false;
this.checkedCities = this.$cache.local.has('order_refund_stroge')
? this.$cache.local.getJSON('order_refund_stroge')
: this.checkedCities;
})
.catch(() => {
this.listLoading = false;
});
},
// 获取各状态数量
getOrderStatusNum() {
let data = Object.assign({}, this.tableFrom);
delete data.page;
delete data.limit;
delete data.refundStatus;
refundStatusNumApi(data).then((res) => {
this.orderChartType = res;
});
},
pageChange(page) {
this.tableFrom.page = page;
this.getList();
},
handleSizeChange(val) {
this.tableFrom.limit = val;
this.getList();
},
exports() {
let data = {
dateLimit: this.tableFrom.dateLimit,
orderNo: this.tableFrom.orderNo,
refundStatus: this.tableFrom.status,
type: this.tableFrom.type,
};
orderExcelApi(data).then((res) => {
window.open(res.fileName);
});
},
handleAddItem() {
if (this.card_select_show) {
this.$set(this, 'card_select_show', false);
} else if (!this.card_select_show) {
this.$set(this, 'card_select_show', true);
}
},
handleCheckAllChange(val) {
this.checkedCities = val ? this.columnData : [];
this.isIndeterminate = false;
},
handleCheckedCitiesChange(value) {
let checkedCount = value.length;
this.checkAll = checkedCount === this.columnData.length;
this.isIndeterminate = checkedCount > 0 && checkedCount < this.columnData.length;
},
checkSave() {
this.$set(this, 'card_select_show', false);
this.$modal.loading('正在保存到本地,请稍候...');
this.$cache.local.setJSON('order_refund_stroge', this.checkedCities);
setTimeout(this.$modal.closeLoading(), 1000);
},
//打印小票
onOrderPrint(data) {
orderPrint(data.orderNo)
.then((res) => {
this.$modal.msgSuccess('打印成功');
})
.catch((error) => {
this.$modal.msgError(error.message);
});
},
},
};
</script>
<style lang="scss" scoped>
::v-deep .el-card{
overflow: auto !important;
}
.card_abs{
top: 123px !important;
}
.red {
color: #ed4014;
}
.el-table__body {
width: 100%;
table-layout: fixed !important;
}
.demo-table-expand {
::v-deep .label {
width: 83px !important;
}
}
.refunding {
span {
display: block;
}
}
.el-icon-arrow-down {
font-size: 12px;
}
.tabBox_tit {
font-size: 12px !important;
/*margin: 0 2px 0 10px;*/
letter-spacing: 1px;
/*padding: 5px 0;*/
box-sizing: border-box;
}
.text_overflow {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 400px;
}
.pup_card {
width: 200px;
border-radius: 5px;
padding: 5px;
box-sizing: border-box;
font-size: 12px;
line-height: 16px;
}
.flex-column {
display: flex;
flex-direction: column;
}
.relative {
position: relative;
}
.cell_ht {
height: 50px;
padding: 15px 20px;
box-sizing: border-box;
border-bottom: 1px solid #eeeeee;
display: flex;
justify-content: space-between;
align-items: center;
}
.check_cell {
width: 100%;
padding: 15px 20px 0;
}
::v-deep .el-checkbox__input.is-checked + .el-checkbox__label {
color: #606266;
}
</style>

View File

@@ -0,0 +1,110 @@
<template>
<div>
<el-dialog title="选择服务人员" :visible.sync="dialogTableVisible" :before-close="handleClose" width="600px">
<div class="mb10">
员工搜索
<el-input
v-model.trim="keywords"
placeholder="请输入员工姓名或手机号搜索"
clearable
class="form_content_width"
/>
<el-button type="primary" size="mini" class="ml20" @click="getSelectStaff">查询</el-button>
</div>
<el-table :data="staffList" height="400px">
<el-table-column width="55">
<template slot-scope="scope">
<span v-if="staffId == scope.row.id" class="iconfont icon-xuanzhong11" @click="iconChange(scope.row.id)" />
<span v-else class="iconfont icon-weixuan" @click="iconChange(scope.row.id)" />
</template>
</el-table-column>
<el-table-column property="id" label="Id" min-width="80"> </el-table-column>
<el-table-column property="name" label="服务人员" min-width="150"></el-table-column>
<el-table-column property="phone" label="手机号码" min-width="150"></el-table-column>
</el-table>
<div class="mt10" slot="footer">
<el-button @click="handleClose()" size="small">取消</el-button>
<el-button type="primary" size="small" @click="submitOk()">确定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { serviceStaffListApi } from '@/api/staff';
import { merchantWorkOrderAssignApi as workOrderAssignApi, merchantWorkOrderReassignApi as workOrderReassignApi } from '@/api/merchantReservation';
export default {
props: {
workOrderNoList: {
type: Array,
default: [],
},
},
data() {
return {
keywords: '',
staffList: [],
staffId: '', // 服务人员id
type: '',
dialogTableVisible: false,
};
},
methods: {
// 选择服务人员
async getSelectStaff() {
let res = await serviceStaffListApi({
page: 1,
limit: 999,
status: 1,
keywords: encodeURIComponent(this.keywords),
});
this.staffList = res.list;
},
handleClose() {
this.dialogTableVisible = false;
this.staffId = '';
},
iconChange(id) {
this.staffId = id;
},
submitOk() {
if (!this.staffId) return this.$message.warning('请选择服务人员');
this.$modalSure('确认派单给该服务人员吗?').then(() => {
this.type === 1
? workOrderAssignApi({ serviceStaffId: this.staffId, workOrderNoList: this.workOrderNoList }).then((data) => {
this.$message.success('派单成功');
this.$emit('selectStaff');
this.handleClose();
})
: workOrderReassignApi({ serviceStaffId: this.staffId, workOrderNoList: this.workOrderNoList }).then(
(data) => {
this.$message.success('改派成功');
this.$emit('selectStaff');
this.handleClose();
},
);
});
},
openBox(type) {
this.type = type;
this.getSelectStaff();
this.dialogTableVisible = true;
},
},
};
</script>
<style scoped lang="scss">
.icon-xuanzhong11 {
cursor: pointer;
font-size: 16px;
margin-left: 10px;
cursor: pointer;
color: var(--prev-color-primary);
}
.icon-weixuan {
cursor: pointer;
font-size: 16px;
margin-left: 10px;
cursor: pointer;
color: #cccccc;
}
</style>

View File

@@ -0,0 +1,274 @@
<template>
<div>
<el-dialog title="改约" :visible.sync="dialogTableVisible" width="600px">
<div class="form-box">
<div class="title mb20">预约信息</div>
<el-table v-show="productList.length" :data="productList" size="small" class="mt20 mb20">
<el-table-column label="商品信息" min-width="300">
<template slot-scope="scope">
<div class="tab">
<div class="demo-image__preview">
<el-image :src="scope.row.image" :preview-src-list="[scope.row.image]" />
</div>
<div>
<div>{{ scope.row.productName }}</div>
<div class="line1 gary">规格{{ scope.row.sku }}</div>
</div>
</div>
</template>
</el-table-column>
</el-table>
<el-form :model="form" ref="form" :rules="rules" label-width="auto">
<el-form-item label="预约时间:" prop="reservationDate">
<el-date-picker
v-model="form.reservationDate"
value-format="yyyy-MM-dd"
type="date"
format="yyyy-MM-dd"
:picker-options="pickerOptions"
placeholder="选择日期"
style="width: 200px"
class="mr10"
>
</el-date-picker>
<el-time-picker
is-range
value-format="HH:mm"
format="HH:mm"
type="datetimerange"
v-model="reservationTime"
placement="bottom-end"
placeholder="选择时间"
:picker-options="pickerOptions"
size="small"
style="width: 200px"
:clearable="false"
/>
</el-form-item>
<el-form-item v-if="productList.length" label="服务方式:" prop="serviceType">
<el-radio-group v-model="form.serviceType" disabled>
<el-radio :label="1">上门服务</el-radio>
<el-radio :label="2">到店服务</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="productList.length" label="联系人:" prop="userName">
<el-input v-model="form.userName" size="small" placeholder="请输入联系人" style="width: 300px"></el-input>
</el-form-item>
<el-form-item v-if="productList.length" label="联系电话:" prop="userPhone">
<el-input
v-model="form.userPhone"
size="small"
type="number"
placeholder="请输入联系电话"
style="width: 300px"
></el-input>
</el-form-item>
<el-form-item label="上门地址:" prop="userAddress" v-if="form.serviceType == 1 && productList.length">
<el-input
v-model="form.userAddress"
size="small"
placeholder="请输入上门地址"
style="width: 300px"
></el-input>
</el-form-item>
</el-form>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogTableVisible = false">取消</el-button>
<el-button type="primary" @click="submitFn">确定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { merchantWorkOrderUpdateAgreementApi as workOrderUpdateAgreementApi } from '@/api/merchantReservation';
const cacheAddress = {};
export default {
name: 'DialogReschedule',
props: {
orderDetailList: {
// 订单详情
type: Object,
default: () => ({}),
},
//商品列表
productList: {
type: Array,
default: () => [],
},
// 订单号
workOrderNoList: {
type: Array,
default: () => [],
},
isBatch: {
type: Boolean,
default: false,
}
},
data() {
return {
pickerOptions: {
disabledDate(time) {
return Date.now() - 8.64e7 > time.getTime();
},
},
formInfoData: {},
checkList: ['选中且禁用', '复选框 A'],
form: {
reservationDate: '',
reservationTimeSlot: '',
serviceType: 1,
userName: '',
userPhone: '',
userAddress: '',
workOrderNoList: [],
isBatch: false
},
reservationTime: ["",""], //时间段
requiredList: [], // 必填项数据
formProps: {},
citysData: [], // 所有省市区数据
rules: {
reservationDate: [{ required: true, message: '请选择日期', trigger: 'blur' }],
userName: [{ required: true, message: '请输入联系人', trigger: 'blur' }],
userPhone: [{ required: true, message: '请输入联系电话', trigger: 'blur' }],
userAddress: [{ required: true, message: '请输入上门地址', trigger: 'blur' }],
},
showPopover: false,
dialogTableVisible: false,
radio: null, // 初始化 radio 变量
};
},
watch: {
orderDetailList: {
handler(val) {
this.setForm(val);
},
deep: true,
},
},
methods: {
setForm(val) {
this.form.reservationDate = val.reservationDate;
this.reservationTime = val.reservationTimeSlot ? val.reservationTimeSlot.split('-') : '';
this.form.serviceType = val.serviceType;
this.form.userName = val.userName;
this.form.userPhone = val.userPhone;
this.form.userAddress = val.userAddress;
},
openBox() {
// 获取整个省市区数据
this.dialogTableVisible = true;
if (this.orderDetailList) {
this.setForm(this.orderDetailList);
}
},
submitFn() {
this.form.workOrderNoList = this.workOrderNoList;
this.form.reservationTimeSlot = this.reservationTime.join('-');
this.form.isBatch = this.isBatch
this.$refs.form.validate((valid) => {
if (valid) {
workOrderUpdateAgreementApi(this.form).then((res) => {
this.$message.success('改约成功');
this.dialogTableVisible = false;
this.$emit('selectStaff');
});
} else {
return false;
}
});
},
},
};
</script>
<style scoped lang="scss">
.form-box {
overflow-y: auto;
scrollbar-width: none; /* firefox */
-ms-overflow-style: none; /* IE 10+ */
.label {
.required {
color: red;
font-size: 13px;
margin-right: 5px;
}
}
.pictrue {
width: 60px;
height: 60px;
border: 1px dotted rgba(0, 0, 0, 0.1);
margin-right: 10px;
position: relative;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
.btndel {
position: absolute;
z-index: 1;
width: 20px !important;
height: 20px !important;
left: 46px;
top: -4px;
}
}
}
.title {
padding-left: 10px;
border-left: 3px solid var(--prev-color-primary);
font-size: 15px;
line-height: 15px;
color: #303133;
}
.tab {
display: flex;
align-items: center;
.el-image {
width: 36px;
height: 36px;
margin-right: 10px;
}
}
.plan-footer-one {
position: relative;
cursor: pointer;
-webkit-appearance: none;
background-color: #fff;
background-image: none;
border-radius: 4px;
border: 1px solid #dcdfe6;
-webkit-box-sizing: border-box;
box-sizing: border-box;
color: #c0c4cc;
display: inline-block;
font-size: inherit;
min-height: 32px;
line-height: 30px;
outline: none;
font-size: 13px;
padding: 0 10px;
-webkit-transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
width: 100%;
}
.el-icon-arrow-down {
font-weight: 400;
position: absolute;
right: 10px;
top: 8px;
}
.flex-box {
display: flex;
}
.item-box {
padding: 5px 10px;
font-size: 13px;
cursor: pointer;
}
</style>

View File

@@ -0,0 +1,772 @@
<template>
<div class="h-full relative">
<!-- 顶部日期选择组件 -->
<!-- <div class="top-date-picker">-->
<!-- <el-date-picker-->
<!-- v-model="selectedDate"-->
<!-- type="date"-->
<!-- value-format="yyyy-MM-dd"-->
<!-- @change="handleDateChange"-->
<!-- placeholder="选择日期"-->
<!-- style="margin-bottom: 10px;"-->
<!-- >-->
<!-- </el-date-picker>-->
<!-- </div>-->
<FullCalendar ref="fullCalendarRef" :options="calendarOptions">
<template v-slot:eventContent="arg">
<el-tooltip>
<div slot="content">
<div class="reservation-name flex">
<div
class="reservation-type border-color"
v-if="arg.event.extendedProps.serviceType == 2"
>
到店
</div>
<div
class="reservation-type border-color"
v-else-if="arg.event.extendedProps.serviceType === 1"
>
上门
</div>
<div style="display: inline-block" class="name">
{{ arg.event.extendedProps.userName }}
</div>
<div style="display: inline-block" class="phone text-1">
{{ arg.event.extendedProps.userPhone }}
<span v-if="arg.event.extendedProps.serviceType === 1">
{{ arg.event.extendedProps.userAddress }}</span
>
</div>
</div>
<div class="text-1" style="margin-top: 4px;">
{{ arg.event.extendedProps.productName }}
</div>
</div>
<div>
<div class="reservation-name">
<div
class="reservation-type bg-color-fff"
:style="{color:getColor(arg.event.extendedProps.serviceStatus)}"
v-if="arg.event.extendedProps.serviceType === 2"
>
到店
</div>
<div
class="reservation-type bg-color-fff"
:style="{color:getColor(arg.event.extendedProps.serviceStatus)}"
v-else-if="arg.event.extendedProps.serviceType === 1"
>
上门
</div>
<div style="display: inline-block" class="name">
{{ arg.event.extendedProps.userName }}
</div>
<div style="display: inline-block" class="phone text-1">
{{ arg.event.extendedProps.userPhone }}
<span v-if="arg.event.extendedProps.serviceType === 1">
{{ arg.event.extendedProps.userAddress }}</span
>
</div>
</div>
<div class="text-1">
{{ arg.event.extendedProps.productName }}
</div>
</div>
</el-tooltip>
</template>
</FullCalendar>
<div class="float-box">
<div class="list">
<div class="item">
<span
class="iconfont mark"
:class="
checkBox.includes(2)
? 'icon-xuanzhong11'
: 'icon-weixuan'
"
@click="handleMark(2)"
/>
待服务 {{`(${viewNum.toService})`}}
</div>
<div class="item">
<span
class="iconfont mark mark2"
:class="
checkBox.includes(3)
? 'icon-xuanzhong11'
: 'icon-weixuan'
"
@click="handleMark(3)"
></span>
服务中 {{`(${viewNum.servicing})`}}
</div>
<div class="item">
<span
class="iconfont mark mark3"
:class="
checkBox.includes(4)
? 'icon-xuanzhong11'
: 'icon-weixuan'
"
@click="handleMark(4)"
></span>
已完成 {{`(${viewNum.serviced})`}}
</div>
</div>
</div>
<div class="slider">
<el-slider
v-model="sliderValue"
:min="30"
:max="180"
:step="30"
:marks="marks"
show-stops
@change="setConfig"
style="width: 226;white-space: nowrap"
>
</el-slider>
</div>
<!-- 日期时间选择器组件 -->
<el-dialog title="日期时间选择" :visible.sync="dialogVisible" width="504px">
<div class="mb30">
<el-date-picker
v-model="selectedDate"
type="date"
value-format="yyyy-MM-dd"
@change="handleDateChange"
style="margin: 10px;"
>
</el-date-picker>
</div>
</el-dialog>
</div>
</template>
<script>
import resourceTimelinePlugin from "@fullcalendar/resource-timeline";
import {workOrderOverViewApi, workOrderOverViewNumApi} from "@/api/merchantReservation";
import moment from "moment";
export default {
components: {
FullCalendar: () => import("@fullcalendar/vue")
},
props: {
value: {
type: Object,
default: {},
}
},
watch: {
where: {
handler(newVal) {
this.$emit('input', newVal);
},
deep: true,
},
},
data() {
return {
marks: {
30: "30分钟",
60: "1小时",
90: "",
120: "2小时",
150: "",
180: "3小时"
},
sliderValue: 30,
checkBox: [2, 3, 4],
dialogVisible: false,
calendarOptions: {
height: "calc(100vh - 276px)", // 调整高度以适应顶部日期选择器
slotMinWidth: 90,
eventMaxStack: 50,
moreLinkContent: {
html:
'<a>全部预约<span class="fc-icon fc-icon-chevron-right"></span></a>'
},
// moreLinkClick: this.handleMoreLinkClick,
locale: "zh-cn",
plugins: [resourceTimelinePlugin],
initialView: "resourceTimelineDay",
aspectRatio: 3,
headerToolbar: {
left: "prev,customTitle,next,volume,number",
right: ""
},
customTitle: {
text: this.formatDate(this.selectedDate),
click: () => {
this.dialogVisible = !this.dialogVisible;
}
},
customButtons: {
volume: {
text: "当日预约量"
},
number: {
text: "0"
},
customTitle: {
text: this.formatDate(this.selectedDate),
click: () => {
this.dialogVisible = !this.dialogVisible;
}
}
},
slotLabelFormat: {
hour: "2-digit", // 两位数小时(如 10
minute: "2-digit", // 两位数分钟(如 00
hour12: false, // 24小时制
omitZeroMinute: false, // 不省略分钟(强制显示:00
locale: "zh-cn" // 本地化(需加载中文语言包)
},
nowIndicator: true,
editable: false,
droppable: false,
resourceAreaWidth: "15%",
scrollTime: "9:00",
resourceAreaHeaderContent: "",
resources: [],
events: [],
slotDuration: "00:30:00",
slotLabelInterval: "00:30", // 标签间隔2小时
datesSet: this.handleDatesSet,
dateClick: this.handleDateChange,
eventClick: this.handleEventClick,
schedulerLicenseKey: "GPL-My-Project-Is-Open-Source"
},
reservationTime: "",
where: this.value,
viewNum: {},
selectedDate: this.moment().format('YYYY-MM-DD'),
selectedTime: '',
calendarApi: null,
};
},
beforeDestroy() {
// 清理事件监听
document.removeEventListener("click", this.closePopover);
},
mounted() {
this.getConfig();
this.getList();
this.getViewNum();
// 初始化日期
this.where.reservationDate = this.selectedDate;
},
methods: {
getColor(serviceStatus){
const statusMap = {
1: '#9fdb1d',
2: '#377DFF',
3: '#FF8D30',
4: '#23C471'
};
return statusMap[serviceStatus];
},
async getViewNum(){
this.viewNum = await workOrderOverViewNumApi(this.where)
},
// 预约工单甘特图
getList() {
for (let key in this.tableFrom) {
this.where[key] = this.tableFrom[key];
}
workOrderOverViewApi(this.where).then(res => {
let data = res.list;
let staff = [];
let list = [];
data.forEach(dataItem => {
staff.push({
id: dataItem.staffId,
title: dataItem.staffId == 0 ? "未指派" : dataItem.name
});
// 蓝色 待服务,黄色 服务中,绿色 已完成serviceType 服务类型:1-上门服务2-到店服务serviceStatus 服务状态:1-未分配2-已分配3-服务中4-服务结束
dataItem.workOrderList.forEach(item => {
let color = "#9fdb1d";
if (item.serviceStatus === 2) {
// s上面 1
// 到店 01
color = "#377DFF";
} else if (item.serviceStatus === 3) {
color = "#FF8D30";
} else if (item.serviceStatus === 4) {
color = "#23C471";
}
let reservationTimeSlotAttr = item.reservationTimeSlot.split('-')
list.push({
...item,
id: item.workOrderNo,
staff_name: dataItem.name,
resourceId: dataItem.staffId + "",
start: this.moment(
item.reservationDate +
" " +
reservationTimeSlotAttr[0]
).format("YYYY-MM-DD HH:mm"),
end: this.moment(
item.reservationDate +
" " +
reservationTimeSlotAttr[1]
).format("YYYY-MM-DD HH:mm"),
color
});
});
});
this.calendarOptions.resources = staff;
this.$nextTick(function () {
this.$set(this.calendarOptions, "events", list);
});
this.calendarOptions.customButtons.number.text = res.count + "";
});
},
showPopover() {
this.dialogVisible = true;
},
handleMark(type) {
if (this.checkBox.includes(type)) {
this.checkBox = this.checkBox.filter(item => item != type);
this.where.serviceStatus = this.checkBox.join(",");
} else {
this.checkBox.push(type);
this.where.serviceStatus = this.checkBox.join(",");
}
this.getList();
this.getViewNum();
},
handleDateChange(date) {
if (date) {
// 如果传入的是日期对象更新selectedDate
if (date instanceof Date || typeof date === 'string') {
this.selectedDate = date instanceof Date ? this.moment(date).format('YYYY-MM-DD') : date;
}
this.where.reservationDate = this.selectedDate;
// 如果有选择时间更新到where对象中
if (this.selectedTime) {
this.where.reservationTime = this.selectedTime;
this.calendarOptions.customButtons.customTitle.text = `${this.selectedDate} ${this.selectedTime}`;
} else {
this.calendarOptions.customButtons.customTitle.text = this.selectedDate;
}
// 立即刷新数据不使用setTimeout
this.getList();
this.getViewNum();
// 获取日历实例并强制刷新视图
if (this.$refs.fullCalendarRef) {
this.calendarApi = this.$refs.fullCalendarRef.getApi();
if (this.calendarApi) {
// 设置日期视图到选中的日期
this.calendarApi.gotoDate(this.selectedDate);
// 强制重新渲染
this.calendarApi.refetchEvents();
}
}
}
// 关闭日期选择器
this.dialogVisible = false;
},
handleDatesSet(arg) {
this.where.reservationDate = this.moment(arg.start).format("YYYY-MM-DD");
this.selectedDate = this.where.reservationDate;
// 如果有选择时间,更新标题
if (this.selectedTime) {
this.calendarOptions.customButtons.customTitle.text = `${this.where.reservationDate} ${this.selectedTime}`;
} else {
this.calendarOptions.customButtons.customTitle.text = this.where.reservationDate;
}
this.getList();
this.getViewNum();
},
// 日期选择相关方法
formatDate(date) {
if (!date) {
return moment().format('YYYY年MM月DD日');
}
return moment(date).format('YYYY年MM月DD日');
},
handleEventClick({ event }) {
this.$emit("onOrderDetails", event.extendedProps.workOrderNo);
},
// 保存看板配置
setConfig() {
localStorage.setItem("sliderValue", this.sliderValue);
this.dialogVisible = false;
this.getConfig();
},
getConfig() {
let value = 30;
if (localStorage.getItem("sliderValue")) {
value = localStorage.getItem("sliderValue");
}
this.sliderValue = Number(value);
this.calendarOptions.slotDuration = this.moment
.utc(this.sliderValue * 60 * 1000)
.format("HH:mm:ss");
this.calendarOptions.slotLabelInterval = this.moment
.utc(this.sliderValue * 60 * 1000)
.format("HH:mm:ss");
},
close() {
this.modal = false;
},
handleRefresh() {
this.isRefresh = true;
},
handleMoreLinkClick(info) {
info.jsEvent.preventDefault();
const currentEvent = info.hiddenSegs[0].event;
const resources = currentEvent.getResources();
const rawEvents = resources[0].getEvents();
const overlapEvents = rawEvents.filter(({ start, end }) => {
return currentEvent.start <= end && start < currentEvent.end;
});
overlapEvents.sort((a, b) => {
return a.start - b.start;
});
let eventList = overlapEvents.map(({ extendedProps, id }) => {
return {
...extendedProps,
id
};
});
this.$emit("serviceTap", eventList);
}
}
};
</script>
<style lang="scss" scoped>
// 添加顶部日期选择器的样式
.top-date-picker {
padding: 10px 0 0 20px;
background-color: #fcfcfc;
border-bottom: 1px solid #ededed;
}
.bg-color-fff{
background-color: rgba(255,255,255,.7);
}
.slider {
position: absolute;
width: 500px;
top: 0;
right: 0;
padding: 20px;
padding-top: 0;
::v-deep .el-slider__marks-text {
font-size: 13px;
color: #303133;
}
::v-deep .el-slider__stop {
position: absolute;
height: 10px;
width: 2px;
background-color: #dddddd;
-webkit-transform: translateY(-50%);
transform: translateY(-80%);
}
::v-deep .el-slider__runway {
height: 2px;
background: #dddddd;
}
::v-deep .el-slider__button {
width: 12px;
height: 12px;
border-radius: 50%;
border: 2px solid var(--prev-color-primary);
}
::v-deep .el-slider__bar {
background-color: transparent;
}
}
::v-deep.fc {
.fc-button-primary, .fc-button-primary:focus, .fc-button:focus,{
background: none;
color: #303133;
border-style: none;
}
.fc-more-popover .fc-popover-body {
max-height: 300px;
overflow-y: scroll;
}
.fc-timeline-lane-frame{
padding-top: 6px;
}
.fc-popover {
z-index: 22;
}
.fc-datagrid-cell-frame {
display: flex;
align-items: center;
}
.fc-datagrid-header {
background-color: #fcfcfc;
}
.fc-datagrid-body {
background-color: #fcfcfc;
}
.fc-timeline-slot-minor {
border-style: none;
}
.fc-event {
padding: 5px 6px;
border-radius: 4px;
white-space: nowrap;
font-size: 12px;
overflow: hidden;
}
.reservation-name {
display: flex;
align-items: center;
margin-bottom: 2px;
}
.reservation-type {
flex-shrink: 0;
display: flex;
justify-content: center;
align-items: center;
width: 29px;
height: 17px;
border: 1px solid rgba(255, 255, 255, 0.5);
border-radius: 3px;
margin-right: 4px;
font-family: PingFang SC, PingFang SC;
font-weight: 400;
font-size: 12px;
color: #ffffff;
}
.fc-prev-button {
display: inline-flex;
justify-content: center;
align-items: center;
width: 30px;
height: 30px;
border: 1px solid #dddddd;
border-radius: 50% !important;
background: none;
font-size: 13px;
color: #303133;
vertical-align: middle;
}
.fc-prev-button:not(:disabled):active {
border: 1px solid #dddddd;
background: none;
color: #303133;
}
.fc-prev-button:focus, .fc-button-primary:focus{
box-shadow: none;
}
.fc-prev-button:not(:disabled):active:focus,.fc-button-primary:not(:disabled):active:focus {
box-shadow: none;
}
.fc-next-button {
display: inline-flex;
justify-content: center;
align-items: center;
width: 30px;
height: 30px;
border: 1px solid #dddddd;
border-radius: 50% !important;
background: none;
font-size: 16px;
color: #303133;
vertical-align: middle;
}
.fc-next-button:not(:disabled):active {
border: 1px solid #dddddd;
background: none;
color: #303133;
}
.fc-next-button:focus {
box-shadow: none;
}
.fc-next-button:not(:disabled):active:focus {
box-shadow: none;
}
.fc-toolbar-title {
display: inline-flex;
justify-content: center;
align-items: center;
width: auto;
min-width: 200px;
font-weight: 500;
font-size: 16px;
color: #303133;
vertical-align: middle;
}
.fc-volume-button {
border: 0;
background: none;
font-size: 16px;
color: #303133;
cursor: auto;
}
.fc-volume-button:not(:disabled):active {
border: 0;
background: none;
color: #303133;
}
.fc-volume-button:focus {
box-shadow: none;
}
.fc-volume-button:not(:disabled):active:focus {
box-shadow: none;
}
.fc-number-button {
padding: 0;
border: 0;
background: none;
font-weight: 500;
font-size: 16px;
color: #377dff;
cursor: auto;
}
.fc-number-button:not(:disabled):active {
border: 0;
background: none;
color: #303133;
}
.fc-number-button:focus {
box-shadow: none;
}
.fc-number-button:not(:disabled):active:focus {
box-shadow: none;
}
.fc-setup-button {
display: inline-flex;
justify-content: center;
align-items: center;
width: 100px;
height: 32px;
border: 1px solid var(--prev-color-primary);
border-radius: 20px;
background: var(--prev-color-primary);
font-size: 13px;
vertical-align: middle;
}
.fc-setup-button:not(:disabled):active {
border: 1px solid var(--prev-color-primary);
background: var(--prev-color-primary);
}
.fc-setup-button:focus {
box-shadow: none;
}
.fc-setup-button:not(:disabled):active:focus {
box-shadow: none;
}
.fc-timeline-slot-cushion {
font-weight: 400;
font-size: 14px;
color: #303133;
}
.fc-datagrid-cell-cushion {
font-size: 14px;
color: #303133;
white-space: pre-wrap;
}
.fc-timeline-now-indicator-container {
z-index: auto;
}
.fc-timeline-now-indicator-arrow {
border-top-color: #377dff;
}
.fc-timeline-now-indicator-line {
border-width: 0px 0px 0px 2px;
border-color: #377dff;
}
td {
border-color: #ededed;
cursor: auto;
}
.fc-scrollgrid-section-header > th:last-child {
border-color: transparent;
}
.fc-scrollgrid-section-body > td:last-child {
border-color: transparent;
}
th {
border-color: #ededed;
}
.fc-scrollgrid {
border-color: transparent;
}
.ivu-tooltip {
width: 100%;
}
}
.text-1 {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
color: #fff;
}
.name {
font-family: PingFang SC, PingFang SC;
font-weight: 400;
font-size: 12px;
color: #ffffff;
margin: 0 4px;
}
.phone {
color: rgba(255, 255, 255, 0.5);
font-family: PingFang SC, PingFang SC;
font-weight: 400;
font-size: 12px;
}
.float-box {
position: fixed;
right: 44px;
bottom: 87px;
z-index: 9;
display: flex;
align-items: center;
height: 61px;
padding: 0 13px 0 26px;
border-radius: 84px;
background: #ffffff;
box-shadow: 0px 4px 16px 0px rgba(0, 0, 0, 0.08);
.list {
display: flex;
align-items: center;
font-size: 14px;
color: #303133;
}
.item {
cursor: pointer;
display: flex;
align-items: center;
margin-right: 18px;
}
.mark {
font-size: 14px;
color: #377dff;
margin-right: 8px;
&.mark2 {
color: #ff8d30;
}
&.mark3 {
color: #23c471;
}
&.mark4 {
color: #f95e45;
}
}
}
.ml0 {
margin-left: 0 !important;
}
</style>

View File

@@ -0,0 +1,504 @@
<template>
<div>
<el-drawer :visible.sync="dialogVisible" :direction="direction" size="1000px" :show-close="false">
<div v-if="orderDatalist" v-loading="loading">
<div class="detailHead">
<div class="acea-row row-between headerBox">
<div class="full">
<div class="order_icon"><span class="iconfont icon-dingdan"></span></div>
<div class="text">
<div class="title">
{{ orderDatalist.serviceType === 1 ? '上门服务' : '到店服务' }} --
{{ orderDatalist.allocateType === 0 ? '未分配' : orderDatalist.allocateType === 1 ? '派单' : '抢单' }}
</div>
<div>
<span class="mr20">工单号{{ orderDatalist.workOrderNo }}</span>
</div>
</div>
</div>
<div v-show="orderDatalist.refundStatus===0" class="acea-row justify-content">
<el-button size="small" v-if="orderDatalist.serviceStatus === 1 && checkPermi(['merchant:workOrder:assign'])" type="primary" @click="handleDispatch(1)">派单</el-button>
<el-button size="small" v-if="(orderDatalist.serviceStatus === 2 || orderDatalist.serviceStatus === 3) && checkPermi(['merchant:workOrder:reassign'])" type="primary" @click="handleDispatch(2)" style="margin-left: 0">改派</el-button>
<el-button size="small" v-if="orderDatalist.serviceStatus < 3 && checkPermi(['merchant:workOrder:updateAgreement'])" v-debounceClick="handleRescheduling" type="primary" style="margin-left: 0">改约</el-button>
</div>
</div>
<ul class="list">
<li class="item">
<div class="title">工单状态</div>
<div class="color-warning">
<span v-if="orderDatalist.refundStatus === 2">已退款</span>
<span v-else>{{ orderDatalist.serviceStatus | serviceStatusFilter }}</span>
</div>
</li>
<li class="item">
<div class="title">预约时间</div>
<div>
<span>{{ orderDatalist.reservationDate }}</span>
<span class="ml10">{{ orderDatalist.reservationTimeSlot }}</span>
</div>
</li>
</ul>
</div>
<el-tabs type="border-card" v-model="activeName">
<el-tab-pane label="基本信息" name="detail">
<div class="detailSection" style="border: none">
<div class="title">预约信息</div>
<ul class="list">
<li class="item">
<div class="lang">联系人</div>
<div class="value">{{ orderDatalist.userName }}</div>
</li>
<li class="item">
<div class="lang">联系电话</div>
<div class="value">{{ orderDatalist.userPhone }}</div>
</li>
<li class="item">
<div class="lang">预约时间</div>
<div class="value">
<span>{{ orderDatalist.reservationDate }}</span>
<span class="ml10">{{ orderDatalist.reservationTimeSlot }}</span>
</div>
</li>
</ul>
<div v-show="orderDatalist.serviceType === 1" class="item">
<div class="lang">上门地址</div>
<div class="value">
{{ orderDatalist.userAddress }}
</div>
</div>
</div>
<div class="detailSection">
<div class="title">商品信息</div>
<el-table class="mt20 orderDetailList" :data="productList" size="small">
<el-table-column label="商品信息" min-width="350" :show-overflow-tooltip="true">
<template slot-scope="scope">
<div class="acea-row row-middle">
<div class="demo-image__preview mr15">
<el-image :src="scope.row.image" :preview-src-list="[scope.row.image]" />
</div>
<div style="width: 300px">
<div class="line1 mb10">{{ scope.row.productName }}</div>
<div class="line1 color-909399 line-heightOne">规格{{ scope.row.sku }}</div>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="商品售价" min-width="120">
<template slot-scope="scope">
<div class="acea-row row-middle">
<div class="line1">
{{ scope.row.price }}
</div>
</div>
</template>
</el-table-column>
<el-table-column label="购买数量" min-width="120">
<template slot-scope="scope">
<div class="acea-row row-middle">
<div class="line1">
{{ scope.row.payNum }}
</div>
</div>
</template>
</el-table-column>
</el-table>
</div>
<div class="detailSection">
<div class="title">买家留言</div>
<ul class="list">
<li class="item">
<div>{{ orderDatalist.userRemark | filterEmpty }}</div>
</li>
</ul>
</div>
<div class="detailSection">
<div class="title">工单备注</div>
<ul class="list">
<li class="item">
<div>{{ orderDatalist.remark | filterEmpty }}</div>
</li>
</ul>
</div>
<div v-if="reservationFormData.length" class="detailSection">
<div class="title">自定义留言</div>
<ul class="">
<li class="item" v-for="(item, index) in reservationFormData" :key="index">
<system-from-info :item="item"></system-from-info>
<!-- <div class="lang" :title="item.title">{{ item.title }}</div>-->
<!-- <div>{{ item.title.includes(':') ? '' : '' }}</div>-->
<!-- <div v-if="!Array.isArray(item.value)" class="value">{{ item.value | filterEmpty }}</div>-->
<!-- <div v-else class="flex conter">-->
<!-- <template v-if="item.value">-->
<!-- <div v-for="(pic, idx) in item.value" :key="idx">-->
<!-- <el-image v-if="pic.includes('http')" class="pictrue" :src="pic" :preview-src-list="[pic]" />-->
<!-- <div v-else class="text-14px fontColor333 ml-5px acea-row row-middle mr5">-->
<!-- {{ pic }}-->
<!-- <div style="margin-left: 6px" v-show="idx < item.value.length - 1">-</div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </template>-->
<!-- <template v-else> - </template>-->
<!-- </div>-->
</li>
</ul>
</div>
</el-tab-pane>
<el-tab-pane label="服务信息" name="goods">
<div class="detailSection" style="border: none">
<div class="title">服务信息</div>
<ul class="list">
<li class="item">
<div class="">服务人员</div>
<div class="value">{{ orderDatalist.serviceStaff ? orderDatalist.serviceStaff.name : '-' }}</div>
</li>
<li class="item">
<div class="">联系电话</div>
<div class="value">{{ orderDatalist.serviceStaff ? orderDatalist.serviceStaff.phone : '-' }}</div>
</li>
<li class="item">
<div class="">开始服务时间</div>
<div class="value">
<span>{{ orderDatalist.serviceStartTime || '-' }}</span>
</div>
</li>
<li class="item">
<div class="">结束服务时间</div>
<div class="value">
<span>{{ orderDatalist.serviceEndTime || '-' }}</span>
</div>
</li>
<li class="item">
<div class="">服务时长</div>
<div class="value">
<span>{{ orderDatalist.serviceDuration }}</span>
</div>
</li>
<li v-if="orderDatalist.collaboratorStaffs && orderDatalist.collaboratorStaffs.length">
<div v-for="item in orderDatalist.collaboratorStaffs" :key="item.id" class="item">
<div class="">协作者</div>
<div class="value">
<span>{{ item.name }}</span>
</div>
</div>
</li>
</ul>
</div>
<div class="detailSection" v-if="orderDatalist.serviceType === 1">
<div class="title">打卡信息</div>
<ul class="list">
<li class="item item100">
<div>打卡备注</div>
<div class="value">
{{ orderDatalist.clockInRemark | filterEmpty }}
</div>
</li>
<li class="item item100">
<div>打卡照片</div>
<template v-if="orderDatalist.clockInPhoto">
<div class="flex" v-for="(item, index) in orderDatalist.clockInPhoto.split(',')" :key="index">
<el-image
:src="item"
:preview-src-list="[item]"
style="width: 40px; height: 40px; margin-right: 12px"
/>
</div>
</template>
<div v-else>--</div>
</li>
</ul>
</div>
<div class="detailSection" v-if="orderDatalist.serviceType === 1 && serviceEvidenceForm.length">
<div class="title">服务过程留凭</div>
<ul class="list">
<li class="item" v-for="(item, index) in serviceEvidenceForm" :key="index">
<div class="lang" :title="item.title">{{ item.title }}</div>
<div>{{ item.title.includes(':') ? '' : '' }}</div>
<div v-if="!Array.isArray(item.value)" class="value">{{ item.value | filterEmpty }}</div>
<div v-else class="flex conter">
<template v-if="item.value">
<div v-for="(pic, idx) in item.value" :key="idx">
<el-image v-if="pic.includes('http')" class="pictrue" :src="pic" :preview-src-list="[pic]" />
<div v-else class="text-14px fontColor333 ml-5px acea-row row-middle mr5">
{{ pic }}
<div style="margin-left: 6px" v-show="idx < item.value.length - 1">-</div>
</div>
</div>
</template>
<template v-else> - </template>
</div>
</li>
</ul>
</div>
</el-tab-pane>
</el-tabs>
</div>
</el-drawer>
<!-- 派单 -->
<dialogDispatch ref="dialogDispatchRef" @selectStaff="selectStaff" :workOrderNoList="[workOrderNo]"/>
<!-- 改约 -->
<dialogReschedule
ref="dialogRescheduleRef"
:orderDetailList="orderDatalist"
:productList="productList"
@selectStaff="selectStaff"
:workOrderNoList="[workOrderNo]"
/>
</div>
</template>
<script>
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
import { orderRefundStatusFilter } from '@/filters';
import { checkPermi } from '@/utils/permission';
import { OrderSecondTypeEnum } from '@/enums/productEnums';
import { merchantWorkOrderDetailApi as workOrderDetailApi } from '@/api/merchantReservation'; // 权限判断函数
import dialogDispatch from "./dialogDispatch";
import dialogReschedule from "./dialogReschedule";
import systemFromInfo from "@/views/merchantOrder/components/systemFromInfo";
export default {
name: 'WorkOrderDetail',
components: {
dialogDispatch,
dialogReschedule,
systemFromInfo
},
props: {
workOrderNo: {
type: String,
default: 0,
},
},
data() {
return {
OrderSecondTypeEnum: OrderSecondTypeEnum,
activeName: 'detail',
direction: 'rtl',
reverse: true,
dialogVisible: false,
orderDatalist: {},
loading: false,
result: [],
resultInfo: {},
InvoiceList: [],
refundInfo: {},
editData: {},
reservationFormData: [], //系统表单数据,用户下单填写
serviceEvidenceForm: [], //系统表单数据,用户下单填写
expressName: '', //快递名称
productList: [], //商品信息
};
},
watch: {},
mounted() {},
methods: {
checkPermi,
// 派单
handleDispatch(type){
this.$refs.dialogDispatchRef.openBox(type, this.orderDatalist);
},
// 改约
handleRescheduling(){
this.$refs.dialogRescheduleRef.openBox();
},
// 服务时长
getTimes() {
let str = '';
if (this.orderDatalist.serviceStartTime && this.orderDatalist.serviceEndTime) {
let startTime = this.orderDatalist.serviceStartTime;
let endTime = this.orderDatalist.serviceEndTime;
const formatISOTime = (timeStr) => timeStr.replace(' ', 'T') + 'Z';
const start = new Date(formatISOTime(startTime));
const end = new Date(formatISOTime(endTime));
// 计算时间差
const diffMs = end - start;
const diffMinute = Math.ceil(diffMs / 60000);
return (diffMinute > 0 ? diffMinute : 1) + '分钟'; // 1分钟 = 60,000毫秒
} else {
return (str = '--');
}
},
orderRefundStatusFilter,
handleClose() {
this.dialogVisible = false;
},
// 操作回调
selectStaff() {
this.getDetail(this.workOrderNo);
this.$emit('changeStaff')
},
getDetail(id) {
this.loading = true;
workOrderDetailApi(id)
.then((res) => {
this.orderDatalist = res;
this.reservationFormData = res.reservationFormData ? JSON.parse(res.reservationFormData) : [];
this.serviceEvidenceForm = res.serviceEvidenceForm ? JSON.parse(res.serviceEvidenceForm) : [];
this.activeName = 'detail';
this.productList = res.orderDetail ? [res.orderDetail] : [];
this.loading = false;
})
.catch(() => {
this.orderDatalist = null;
this.loading = false;
});
},
},
};
</script>
<style scoped lang="scss">
::v-deep .el-drawer__header {
display: flex !important;
align-items: flex-start !important;
padding: 15px 15px 0 15px !important;
margin: 0 !important;
}
::v-deep .el-drawer__body {
padding: 0 0 30px 0 !important;
}
::v-deep .demo-drawer_title {
width: 90%;
}
::v-deep .el-tabs__content {
padding: 0 20px !important;
}
.detailSection {
padding: 25px 15px !important;
}
::v-deep .el-table th.el-table__cell > .cell,
::v-deep.el-table .cell,
.el-table--border .el-table__cell:first-child .cell {
padding-left: 15px;
}
.InvoiceList {
::v-deep.el-collapse-item__header {
font-size: 12px;
color: #606266;
}
}
.wrapper {
background-color: #fff;
margin-top: 7px;
padding: 10px 12px;
&-num {
font-size: 10px;
color: #999999;
}
&-title {
color: #666666;
font-size: 12px;
}
&-img {
width: 60px;
height: 60px;
margin-right: 10px;
border-radius: 7px;
overflow: hidden;
margin-bottom: 10px;
image {
width: 100%;
height: 100%;
}
&:nth-child(5n) {
margin-right: 0;
}
}
}
.title {
font-size: 36px;
}
.demo-drawer__content {
padding: 0 30px;
}
.demo-image__preview {
display: inline-block;
.el-image {
width: 50px;
height: 50px;
}
}
.logistics {
align-items: center;
padding: 10px 0px;
.logistics_img {
width: 45px;
height: 45px;
margin-right: 12px;
img {
width: 100%;
height: 100%;
}
}
.logistics_cent {
span {
display: block;
font-size: 12px;
}
}
}
.trees-coadd {
width: 100%;
height: 400px;
border-radius: 4px;
overflow: hidden;
.scollhide {
width: 100%;
height: 100%;
overflow: auto;
margin-left: 18px;
padding: 10px 0 10px 0;
box-sizing: border-box;
.content {
font-size: 12px;
}
.time {
font-size: 12px;
color: var(--prev-color-primary);
}
}
}
.title {
margin-bottom: 14px;
color: #303133;
font-weight: 500;
font-size: 14px;
}
.description {
&-term {
display: table-cell;
padding-bottom: 5px;
line-height: 20px;
width: 50%;
font-size: 12px;
color: #606266;
}
::v-deep .el-divider--horizontal {
margin: 12px 0 !important;
}
}
</style>

View File

@@ -0,0 +1,9 @@
<template>
<div>
<router-view />
</div>
</template>
<script>
export default {};
</script>

View File

@@ -0,0 +1,133 @@
<template>
<div class="divBox">
<el-card
:bordered="false"
shadow="never"
class="ivu-mt"
:body-style="{ padding: 0 }"
v-if="checkPermi(['merchant:order:page:list'])"
>
<div class="padding-add">
<el-form inline label-position="right" @submit.native.prevent>
<el-form-item label="预约人员:" label-width="66px">
<el-input
v-model.trim="tableFrom.reservationKeyword"
placeholder="请输入预约人员姓名电话搜索"
class="form_content_width"
size="small"
@keyup.enter.native="handleSearchList"
clearable
>
</el-input>
</el-form-item>
<el-form-item label="服务方式:">
<el-select
v-model="tableFrom.serviceType"
placeholder="请选择服务方式"
class="selWidth"
clearable
style="width: 200px"
@change="handleSearchList"
>
<el-option v-for="item in serviceList" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" size="small" @click="handleSearchList">查询</el-button>
<el-button size="small" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</div>
</el-card>
<el-card class="mt14 board-card">
<!-- 预约服务展示面板 -->
<fullCalendar ref="fullCalendar" v-model="tableFrom" @onOrderDetails="onOrderDetails" />
</el-card>
<!--详情-->
<details-from ref="orderDetail" :workOrderNo="workOrderNo" />
</div>
</template>
<script>
import detailsFrom from './components/workOrderDetail';
import fullCalendar from './components/fullCalendar';
import { checkPermi } from '@/utils/permission';
import { serviceStaffListApi } from '@/api/staff';
export default {
name: '',
components: { fullCalendar, detailsFrom },
props: {},
data() {
return {
tableFrom: {
reservationDate: '',
reservationKeyword: '',
serviceType: '',
serviceStatus: '2,3,4',
},
workOrderNo: '',
drawer: false,
select: '',
staffList: [],
serviceList: [
{
id: '1',
name: '上门服务',
},
{
id: 2,
name: '到店服务',
},
],
};
},
computed: {},
watch: {},
created() {},
mounted() {
this.getSelectStaff();
},
methods: {
checkPermi,
// 查询
handleSearchList() {
this.$refs.fullCalendar.getList();
this.$refs.fullCalendar.getViewNum();
},
// 重置
handleReset() {
this.tableFrom.reservationKeyword = '';
this.tableFrom.serviceType = '';
this.tableFrom.searchType = 'all';
this.tableFrom.content = ''
this.handleSearchList();
},
// 选择服务人员
async getSelectStaff() {
let res = await serviceStaffListApi({
page: 1,
limit: 999,
status: 1,
});
this.staffList = res.list;
},
onOrderDetails(id) {
this.workOrderNo = id;
this.$refs.orderDetail.getDetail(id);
this.$refs.orderDetail.dialogVisible = true;
},
closeDrawer() {
this.drawer = false;
},
changeDrawer(v) {
this.drawer = v;
},
getSelectList(val) {
this.tableFrom.nickname = val.nickname;
this.tableFrom.phone = val.phone;
this.handleSearchList();
},
},
};
</script>
<style scoped lang="scss"></style>

View File

@@ -0,0 +1,483 @@
<template>
<div class="divBox relative">
<el-card
:bordered="false"
shadow="never"
class="ivu-mt"
:body-style="{ padding: 0 }"
v-if="checkPermi(['merchant:workOrder:page:list'])"
>
<div class="padding-add">
<el-form inline label-position="right" @submit.native.prevent>
<el-form-item label="工单编号:" label-width="66px">
<el-input
v-model.trim="tableFrom.workOrderNo"
placeholder="请输入工单号"
class="form_content_width"
size="small"
@keyup.enter.native="handleSearchList"
clearable
>
</el-input>
</el-form-item>
<el-form-item label="订单编号:" label-width="66px">
<el-input
v-model.trim="tableFrom.orderNo"
placeholder="请输入订单号"
class="form_content_width"
size="small"
@keyup.enter.native="handleSearchList"
clearable
>
</el-input>
</el-form-item>
<el-form-item label="预约类型:">
<el-select
v-model="tableFrom.serviceType"
clearable
size="small"
placeholder="请选择"
class="form_content_width"
@change="handleSearchList"
>
<el-option v-for="(item, i) in fromType" :key="i" :label="item.text" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="时间选择:">
<el-date-picker
v-model="timeVal"
value-format="yyyy-MM-dd"
format="yyyy-MM-dd"
size="small"
type="daterange"
placement="bottom-end"
placeholder="自定义时间"
@change="onchangeTime"
class="form_content_width"
/>
</el-form-item>
<el-form-item label="用户搜索:" label-for="nickname">
<UserSearchInput v-model="tableFrom" />
</el-form-item>
<el-form-item>
<el-button type="primary" size="small" @click="handleSearchList">查询</el-button>
<el-button size="small" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</div>
</el-card>
<el-card
shadow="never"
:bordered="false"
class="box-card mt16"
:body-style="{ padding: '0 20px 20px', position: 'relative' }"
v-if="checkPermi(['merchant:workOrder:status:num', 'merchant:workOrder:page:list'])"
>
<el-tabs class="list-tabs" v-model="tableFrom.status" @tab-click="handleSearchList">
<el-tab-pane name="0" :label="`全部(${orderChartType.all || 0})`"></el-tab-pane>
<el-tab-pane name="1" :label="`待领取(${orderChartType.unReceive || 0})`"></el-tab-pane>
<el-tab-pane name="2" :label="`已领取(${orderChartType.received || 0})`"></el-tab-pane>
<el-tab-pane name="3" :label="`服务中(${orderChartType.inService || 0})`"></el-tab-pane>
<el-tab-pane name="4" :label="`服务结束(${orderChartType.endService || 0})`"></el-tab-pane>
<el-tab-pane name="9" :label="`已退款(${orderChartType.refunded || 0})`"></el-tab-pane>
</el-tabs>
<div class="mt5">
<el-dropdown size="small">
<el-button :class="checkedIds.length > 0 ? '' : 'active'" :disabled="isBatch">
批量设置<i class="el-icon-arrow-down el-icon--right"></i>
</el-button>
<el-dropdown-menu slot="dropdown">
<template>
<el-dropdown-item
v-if="tableFrom.status == 1 && checkPermi(['merchant:workOrder:assign']) && checkedIds.length > 0"
@click.native="handleDispatch(1)"
>派单</el-dropdown-item
>
<el-dropdown-item v-if="dispatch" @click.native="handleDispatch(2)">改派</el-dropdown-item>
<el-dropdown-item v-if="rescheduling" @click.native="handleRescheduling">改约</el-dropdown-item>
</template>
</el-dropdown-menu>
</el-dropdown>
</div>
<el-table
v-loading="listLoading"
:data="tableData.data"
size="mini"
class="mt20"
highlight-current-row
:highlight-current-row="true"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55"> </el-table-column>
<el-table-column label="工单号" min-width="190">
<template slot-scope="scope">
<div class="acea-row">
<span style="display: block" v-text="scope.row.workOrderNo" />
</div>
<div class="flex">
<span class="colorPrompt" v-show="parseInt(scope.row.refundStatus) > 0" style="display: block">{{
scope.row.refundStatus === 1 ? '退款审核中' : '已退款'
}}</span>
</div>
<span v-show="scope.row.isUserDel" class="colorPrompt" style="display: block">用户已删除</span>
</template>
</el-table-column>
<el-table-column prop="orderNo" label="订单号" min-width="190" />
<el-table-column prop="userPhone" label="服务类型" min-width="80">
<template slot-scope="scope">
{{ scope.row.serviceType === 1 ? '上门服务' : '到店服务' }}
</template>
</el-table-column>
<el-table-column label="服务状态" min-width="100">
<template slot-scope="scope">
<span v-if="scope.row.refundStatus === 0"
class="statusBox"
:style="{
color: orderColorFilter(scope.row.serviceStatus),
borderColor: orderColorFilter(scope.row.serviceStatus),
}"
>
{{ scope.row.serviceStatus | serviceStatusFilter }}
</span>
<span v-else class="statusBox notStartTag">已退款</span>
</template>
</el-table-column>
<el-table-column label="预约时间" min-width="160">
<template slot-scope="scope">
<span> {{ scope.row.reservationDate }} </span>
<span class="ml10"> {{ scope.row.reservationTimeSlot }} </span>
</template>
</el-table-column>
<el-table-column label="预约人名称" min-width="150" prop="userName" />
<el-table-column prop="userPhone" label="预约人电话" min-width="120" />
<el-table-column prop="userAddress" label="预约地址" min-width="200" :show-overflow-tooltip="true" />
<el-table-column width="185" fixed="right" label="操作">
<template slot-scope="scope">
<a @click="onOrderDetails(scope.row.workOrderNo)" v-if="checkPermi(['merchant:workOrder:detail'])">详情 </a>
<el-divider direction="vertical" v-if="scope.row.groupBuyRecordStatus != 0"></el-divider>
<a @click="handleOrderMark(scope.row)" v-if="checkPermi(['merchant:workOrder:mark'])">工单备注 </a>
<template v-if="scope.row.serviceStatus === 3 && checkPermi(['merchant:workOrder:forceFinish']) &&scope.row.refundStatus===0 ">
<el-divider direction="vertical"></el-divider>
<a @click="handleForceFinish(scope.row)">完成工单 </a>
</template>
</template>
</el-table-column>
</el-table>
<div class="block">
<el-pagination
background
:page-sizes="$constants.page.limit"
:page-size="tableFrom.limit"
:current-page="tableFrom.page"
layout="total, sizes, prev, pager, next, jumper"
:total="tableData.total"
@size-change="handleSizeChange"
@current-change="pageChange"
/>
</div>
</el-card>
<!-- 派单 -->
<dialogDispatch ref="dialogDispatchRef" @selectStaff="handleSearchList" :workOrderNoList="checkedIds" />
<!-- 改约 -->
<dialogReschedule ref="dialogRescheduleRef" @selectStaff="handleSearchList" :workOrderNoList="checkedIds" :isBatch="true"/>
<!--详情-->
<details-from ref="orderDetail" :workOrderNo="workOrderNo" @changeStaff="handleSearchList"/>
</div>
</template>
<script>
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
import detailsFrom from './components/workOrderDetail';
import { checkPermi } from '@/utils/permission';
import DirectRefund from '@/views/merchantOrder/components/directRefund.vue';
import * as $constants from '@/utils/constants';
import { OrderSecondTypeEnum } from '@/enums/productEnums';
import { merchantWorkOrderForceFinishApi as workOrderForceFinishApi, merchantWorkOrderListApi as workOrderListApi, merchantWorkOrderMarkApi as workOrderMarkApi, merchantWorkOrderStatusNumApi as workOrderStatusNumApi } from '@/api/merchantReservation';
import dialogDispatch from './components/dialogDispatch';
import dialogReschedule from './components/dialogReschedule';
let tableFroms = {
status: '0',
dateLimit: '',
orderNo: '',
page: 1,
limit: $constants.page.limit[0],
serviceType: '',
searchType: 'all',
content: '',
workOrderNo: '',
};
export default {
name: 'orderlistDetails',
components: {
dialogDispatch,
dialogReschedule,
DirectRefund,
detailsFrom,
},
computed: {
// 批量改派
dispatch() {
return (
(this.tableFrom.status == 2 || this.tableFrom.status == 3) &&
checkPermi(['merchant:workOrder:reassign']) &&
this.checkedIds.length > 0
);
},
// 批量改约
rescheduling() {
return (
checkPermi(['merchant:workOrder:updateAgreement']) &&
(this.tableFrom.status == 2 || this.tableFrom.status == 1) &&
this.checkedIds.length > 0
);
},
// 是否能批量操作
isBatch() {
return this.checkedIds.length > 0 && Number(this.tableFrom.status) > 0 && Number(this.tableFrom.status) < 4
? false
: true;
},
},
data() {
return {
workOrderNo: '',
dialogVisible: false,
tableData: {
data: [],
total: 0,
},
listLoading: false,
tableFrom: Object.assign({}, tableFroms),
orderChartType: {},
timeVal: [],
fromType: [
{ value: 1, text: '上门服务' },
{ value: 2, text: '到店服务' },
],
selectionList: [],
type: 0, //订单类型
OrderSecondTypeEnum: OrderSecondTypeEnum,
checkedIds: [], //选中的id
};
},
mounted() {
if (checkPermi(['mmerchant:workOrder:page:list'])) this.getList();
if (checkPermi(['merchant:workOrder:status:num'])) this.getOrderStatusNum();
},
methods: {
checkPermi,
// 派单
handleDispatch(type) {
this.$refs.dialogDispatchRef.openBox(type);
},
// 改约
handleRescheduling() {
this.$refs.dialogRescheduleRef.openBox();
},
// 服务状态
orderColorFilter(status) {
const statusMap = {
3: '#0FC6C2',
1: '#FF7D00',
2: '#3491FA',
4: '#CCCCCC',
9: '#F56464',
};
return statusMap[status];
},
handleReset() {
this.tableFrom.type = '';
this.tableFrom.dateLimit = '';
this.tableFrom.orderNo = '';
this.tableFrom.workOrderNo = '';
this.tableFrom.page = 1;
this.tableFrom.content = '';
this.tableFrom.searchType = 'all';
this.tableFrom.serviceType = ''
this.selectChange();
},
handleSearchList() {
this.tableFrom.page = 1;
this.getList();
this.getOrderStatusNum();
},
// 详情
onOrderDetails(id) {
this.workOrderNo = id;
this.$refs.orderDetail.getDetail(id);
this.$refs.orderDetail.dialogVisible = true;
},
handleClose() {
this.dialogVisible = false;
},
// 备注
handleOrderMark(row) {
this.$modalPrompt('textarea', '备注', row.remark, '工单备注').then((V) => {
workOrderMarkApi({ remark: V, workOrderNo: row.workOrderNo }).then(() => {
this.$message.success('操作成功');
this.getList();
});
});
},
// 完成工单
handleForceFinish(row) {
this.$modalSure('确认强制完成工单吗?').then(() => {
workOrderForceFinishApi({ workOrderNoList: [row.workOrderNo] }).then((res) => {
this.$message.success('完成工单成功');
this.getList();
this.getOrderStatusNum();
});
});
},
handleSelectionChange(val) {
this.selectionList = val;
const data = [];
this.selectionList.map((item) => {
data.push(item.workOrderNo);
});
this.checkedIds = data;
},
// 选择时间
selectChange(tab) {
this.timeVal = [];
this.tableFrom.page = 1;
this.getList();
this.getOrderStatusNum();
},
// 具体日期
onchangeTime(e) {
this.timeVal = e;
this.tableFrom.dateLimit = e ? this.timeVal.join(',') : '';
this.tableFrom.page = 1;
this.getList();
this.getOrderStatusNum();
},
// 列表
getList() {
this.listLoading = true;
workOrderListApi(this.tableFrom)
.then((res) => {
this.tableData.data = res.list || [];
this.tableData.total = res.total;
this.listLoading = false;
})
.catch(() => {
this.listLoading = false;
});
},
// 获取各状态数量
getOrderStatusNum() {
let data = Object.assign({}, this.tableFrom);
delete data.page;
delete data.limit;
delete data.status;
workOrderStatusNumApi(data).then((res) => {
this.orderChartType = res;
});
},
pageChange(page) {
this.tableFrom.page = page;
this.getList();
},
handleSizeChange(val) {
this.tableFrom.limit = val;
this.getList();
},
},
};
</script>
<style lang="scss" scoped>
.statusBox {
display: inline-block;
padding: 2px 10px;
border-radius: 4px;
border: 1px solid #cccccc;
}
font {
color: var(--prev-color-primary);
}
.el-table__body {
width: 100%;
table-layout: fixed !important;
}
.demo-table-expand {
::v-deep .label {
width: 83px !important;
}
}
.refunding {
span {
display: block;
}
}
.el-icon-arrow-down {
font-size: 12px;
}
.tabBox_tit {
font-size: 12px !important;
/*margin: 0 2px 0 10px;*/
letter-spacing: 1px;
/*padding: 5px 0;*/
box-sizing: border-box;
}
.text_overflow {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 400px;
}
.pup_card {
width: 200px;
border-radius: 5px;
padding: 5px;
box-sizing: border-box;
font-size: 12px;
line-height: 16px;
}
.flex-column {
display: flex;
flex-direction: column;
}
.relative {
position: relative;
}
.cell_ht {
height: 50px;
padding: 15px 20px;
box-sizing: border-box;
border-bottom: 1px solid #eeeeee;
display: flex;
justify-content: space-between;
align-items: center;
}
.check_cell {
width: 100%;
padding: 15px 20px 0;
}
::v-deep .el-checkbox__input.is-checked + .el-checkbox__label {
color: #606266;
}
</style>