pull/2/head
张宇 2 years ago
parent 1e0c371420
commit 6dfb2f37c5

@ -51,6 +51,49 @@ export default {
}); });
}, },
after_sale: {
index(data) {
return request({
url: "/admin/after_sale/index",
method: "POST",
data,
});
},
update(data) {
return request({
url: "/admin/after_sale/update",
method: "POST",
data,
});
},
show(data) {
return request({
url: "/admin/after_sale/show",
method: "POST",
data,
});
},
express(data) {
return request({
url: "/client/common/express",
method: "POST",
data,
});
},
enums(data) {
return request({
url: "/client/after_sale/enums",
method: "POST",
data,
});
},
}
}; };

@ -65,6 +65,30 @@
} }
} }
}, },
{
"path": "afterSale/index",
"style": {
"navigationBarTitleText": "订单售后",
"enablePullDownRefresh": false,
"mp-alipay": {
"transparentTitle": "always",
"titlePenetrate": "YES",
"gestureBack": "YES"
}
}
},
{
"path": "afterSale/details",
"style": {
"navigationBarTitleText": "订单售后",
"enablePullDownRefresh": false,
"mp-alipay": {
"transparentTitle": "always",
"titlePenetrate": "YES",
"gestureBack": "YES"
}
}
},
{ {
"path": "setup/index", "path": "setup/index",
"style": { "style": {

@ -76,6 +76,8 @@ function handleSubmit() {
loading.value = false loading.value = false
}) })
} else {
loading.value = false
} }
}) })
.catch((error) => { .catch((error) => {

@ -0,0 +1,159 @@
<template>
<div class="floor">
<div v-if="saleDetails.after_sale && saleDetails.after_sale != '' && saleDetails.after_sale">
<div class="list">
<div class="name">申请类型</div>
<div class="text">{{ ['', '退货退款', '换货'][saleDetails.after_sale.apply_type] }}</div>
</div>
<div class="list">
<div class="name">申请原因</div>
<div class="text"
v-if="afterSaleEnumsList && saleDetails && saleDetails.after_sale && saleDetails.after_sale.apply_type && saleDetails.after_sale.apply_reason && afterSaleEnumsList[saleDetails.after_sale.apply_type] && afterSaleEnumsList[saleDetails.after_sale.apply_type][saleDetails.after_sale.apply_reason]">
{{ afterSaleEnumsList[saleDetails.after_sale.apply_type][saleDetails.after_sale.apply_reason].label }}
</div>
<div v-else>
--
</div>
</div>
<div class="list" v-if="saleDetails.after_sale.apply_files != ''">
<div class="name">图片描述</div>
<image style="width: 100px; height: 100px;margin-right: 15px;"
v-for="(img, i) in saleDetails.after_sale.apply_files" :key="i" :src="img" :zoom-rate="1.2"
:preview-src-list="saleDetails.after_sale.apply_files" fit="cover">
</image>
</div>
<div class="list" v-if="saleDetails.after_sale.apply_type == 1">
<div class="name">退款金额</div>
<div class="text">{{ saleDetails.after_sale.refund_amount }}</div>
</div>
<div v-if="saleDetails.after_sale.apply_type == 2">
<div class="list">
<div class="name">换货类型</div>
<div class="text">
<div v-for="item in saleDetails.after_sale.goods_json.goods_sku_json" :key="item.attr_group_id">
{{ item.group_name }}{{ item.name }}</div>
</div>
</div>
<div class="list">
<div class="name">换货数量</div>
<div class="text">
{{ saleDetails.after_sale.goods_json.num }}
</div>
</div>
<div class="list" v-if="saleDetails.after_sale.address_json">
<div class="name">买家地址</div>
<div class="text">
<div>收货人{{ saleDetails.after_sale.address_json.name }}</div>
<div>收货电话{{ saleDetails.after_sale.address_json.mobile }}</div>
<div>收货地址{{ saleDetails.after_sale.address_json.address }}</div>
</div>
</div>
<div class="list" v-if="saleDetails.after_sale.apply_type == 2 && saleDetails.after_sale.shop_express_json">
<div class="name">快递公司</div>
<div class="text">
{{ express_company }}
</div>
</div>
<div class="list" v-if="saleDetails.after_sale.apply_type == 2 && saleDetails.after_sale.shop_express_json">
<div class="name">快递单号</div>
<div class="text">
{{ saleDetails.after_sale.shop_express_json.express_number }}
</div>
</div>
</div>
<div class="list">
<div class="name">售后状态</div>
<div class="text">{{ saleDetails.after_sale.status_text }}</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import order from '@/api/store/order.js';
const saleDetails = ref({})
//
const express_company = ref()
const express = async () => {
try {
const res = await order.after_sale.express()
if (res.code !== 0) throw new Error(res.message)
res.data.forEach((item) => {
if (item.value == saleDetails.value.after_sale.shop_express_json.express_company) {
express_company.value = item.label;
}
})
} catch (error) {
console.dir(error)
}
}
onLoad(({ id }) => {
const getSaleDetail = async ({ id }) => {
try {
const res = await order.after_sale.show({ id })
console.log(res)
if (res.code !== 0) throw new Error(res.message)
saleDetails.value = res.data;
express();
} catch (error) {
console.dir(error)
}
}
getSaleDetail({ id });
})
const afterSaleEnumsList = ref([])
const afterSaleEnums = async () => {
try {
const res = await order.after_sale.enums()
if (res.code !== 0) throw new Error(res.message)
afterSaleEnumsList.value = res.data.reasonMap
} catch (error) {
console.dir(error)
}
}
afterSaleEnums();
</script>
<style scoped lang="scss">
.floor {
background: #fff;
padding: 30px 50px;
height: 100%;
.list {
display: flex;
justify-content: flex-start;
padding-bottom: 15px;
font-size: 14px;
line-height: 36px;
.name {
padding-right: 20px;
color: #888;
}
text {
margin-right: 20px;
}
img {
width: 300px;
}
}
}
</style>

@ -0,0 +1,569 @@
<template>
<view class="content">
<wd-message-box></wd-message-box>
<wd-message-box selector="wd-message-box-slot" use-slot>
<view>
<wd-form :model="shipmentsModel">
<wd-cell-group border>
<view class="mb-4">
<wd-radio-group v-model="shipmentsModel.send_type" shape="button" @change="change">
<wd-radio :value="1">快递发货</wd-radio>
<wd-radio :value="2">无需物流</wd-radio>
</wd-radio-group>
</view>
<view class="mb-4 overflow-hidden bg-white border-gray-100 border-solid border-[8px] p-3 rounded"
v-if="shipmentsModel.send_type == 1">
<view class="mb-2">
<wd-radio-group shape="button" v-model="shipmentsModel.delivery_company">
<view class="flex overflow-auto">
<wd-radio v-for="item of expressList" :value="item.value">{{ item.label }}</wd-radio>
</view>
</wd-radio-group>
</view>
<wd-input label-width="100px" clearable v-model="shipmentsModel.delivery_no" placeholder="快递单号">
</wd-input>
</view>
</wd-cell-group>
</wd-form>
</view>
</wd-message-box>
<view class=" bg-white sticky top-0 z-50">
<div class="flex items-center">
<view class="flex-1">
<wd-search @search="search" @clear="search({ value: '' })" v-model="params.keywords" hide-cancel>
<template #prefix>
<wd-popover v-model="popover" mode="menu" :content="menu" @menuclick="changeSearchType">
<view class="search-type">
<text>{{ searchType }}</text>
<wd-icon custom-class="icon-arrow" name="fill-arrow-down"></wd-icon>
</view>
</wd-popover>
</template>
</wd-search>
</view>
<div class="mr-2">
<wd-button @click="utils.toUrl('/store/order/index')" size="small">普通订单</wd-button>
</div>
</div>
<wd-tabs @change="(e) => {
params.status = e.index;
search({ value: params.keywords })
}" v-model="params.status">
<wd-tab v-for="item in status" :key="item" :title="`${item.content}`"></wd-tab>
</wd-tabs>
</view>
<view class="goodsBox p-2">
<myList ref="myListRef" :apiObj="order.after_sale.index" :params="{ ...params, status: params.status - 1 }">
<template #default="{ list }">
<view>
<view v-for="item of list" class="goods">
<wd-card type="rectangle">
<template #title>
<view class="title">
<view>
<span class="mr-2" v-if="item.shop">{{ item.shop.name }}</span>
<span class="mr-2" v-else></span>
<span class="font-bold">({{ ["待审核", "审核通过", "审核不通过", "买家发货", "商家发货", "已完成"][item.status] }})</span>
</view>
<view @click="utils.copy(item.order.order_no, '已复制单号')" class="title-tip">
<span style="font-size: 24rpx;margin-left: 8px;">单号{{ item.order.order_no }}</span>
</view>
</view>
</template>
<view v-for="subItem of [item.order_detail]" style="height: 60px;" class="content mb-4">
<image :src="subItem.goods_pic" width="40" height="40" alt="joy"
style="border-radius: 4px; margin-right: 12px;width: 60px;height: 60px;min-width: 60px;" />
<view>
<view class="line2" style="color: rgba(0,0,0,0.85); font-size: 16px;">{{ subItem.goods_name }}</view>
<!-- <view class="font-bold" style="color: rgb(255, 0, 0); font-size: 16px;">{{ subItem.price }}
</view> -->
<view class="font-bold" style="font-size: 22rpx;">小计: {{ subItem.price }} <text
style="color: rgb(255, 0, 0); font-size: 24rpx;">({{
subItem.num }}) </text></view>
<div class="goods_sky" style="font-size: 22rpx;">
<span v-for="(sku, _index) of subItem.goods_sku_json" :key="_index">
{{ sku.group_name }}{{ sku.name }}</span>
</div>
</view>
</view>
<view class="bg-gray-50 p-2">
<view class="text-[12px]" v-if="item.user">
<view class="mb-1">
用户{{ item.user.nickname }}
<text class="font-bold">
( 会员ID{{ item.user.id }} )
</text>
<view class="bg-gray-100 my-2 p-2 relative">
<view>收货人{{ item.order.receiver_name }} &nbsp; 电话{{ item.order.receiver_phone }}</view>
<view v-if="item.order.delivery_address">
收货地址{{ item.order.delivery_address }} {{
item.order.delivery_address_detail }}
</view>
<view
@click="utils.copy(`收货人:${item.order.receiver_name} 电话:${item.order.receiver_phone} 收货地址:${item.order.delivery_address}`, '已复制快递信息')"
class="absolute right-2 top-2">
<wd-icon name="file-copy" size="14px"></wd-icon>
</view>
</view>
</view>
<view class="flex items-center" v-if='[0, 1, 2][item.delivery_type]'>
配送方式:
<wd-tag v-if="item.apply_type == 1" class="ml-2" type="success">
退货退款
</wd-tag>
<wd-tag v-if="item.apply_type == 2" class="ml-2" type="success">
换货
</wd-tag>
</view>
</view>
</view>
<template #footer>
<!-- <view class="bg-gray-50 p-2 mb-2.5 flex justify-between" style="font-size: 22rpx;">
<view>
售后状态:
</view>
</view> -->
<view class="flex justify-between items-center">
<!-- <view class="font-bold">实付: {{ item.pay_price }} </view> -->
<view></view>
<view class="flex-1 flex justify-end">
<!-- <view v-if="item.apply_cancel === 1" class="mr-2">
<wd-button type="error" @click="orderApproval(item)" size="small">退款</wd-button>
</view>
<view class="mr-2">
<wd-button @click="printing(item)" size="small">小票打印</wd-button>
</view>
<view class="mr-2">
<wd-button @click="orderNotes(item)" size="small">备注</wd-button>
</view>
<view v-if="item.status == 1" class="">
<wd-button @click="shipments(item)" size="small">发货</wd-button>
</view> -->
<view style="margin-right: 16rpx;">
<wd-button @click="utils.toUrl('/store/afterSale/details?id=' + item.id)"
size="small">退款详情</wd-button>
</view>
<template v-if="item.status == 0">
<view style="margin-right: 16rpx;">
<wd-button @click="orderApproval(item)" size="small">退款审批</wd-button>
</view>
</template>
<template v-if="item.status == 3 && item.apply_type == 2">
<view style="margin-right: 16rpx;">
<wd-button @click="shipments(item)" size="small">待发货</wd-button>
</view>
</template>
<template v-if="item.status == 3 && item.apply_type == 1">
<view style="margin-right: 16rpx;">
<wd-button @click="showActions(item)" size="small">确认收货</wd-button>
</view>
</template>
</view>
</view>
</template>
</wd-card>
</view>
</view>
</template>
</myList>
</view>
<wd-toast />
<wd-action-sheet v-model="show" :actions="actions" @select="select" />
</view>
</template>
<script setup>
import utils from '@/utils/utils.js';
import order from '@/api/store/order.js';
import myList from "/components/myList/index.vue";
import { useMessage } from '@/uni_modules/wot-design-uni';
import { useToast } from '@/uni_modules/wot-design-uni';
import { ref } from 'vue';
const message = useMessage('wd-message-box-slot');
const message1 = useMessage();
const toast = useToast();
const show = ref(false)
let afterType = 1
const actions = ref([
{
name: '线上退款',
subname: '(退回原支付账户)'
},
{
name: '线下退款'
},
])
/**
* 处理订单备注操作
* @param {Object} row - 行数据
*/
const orderNotes = (row, text) => {
message1
.prompt({
title: text,
inputValue: row.refund_amount
})
.then((resp) => {
order.after_sale.update({
id: row.id,
status: 5,
refund_amount: resp.value,
refund_type: afterType,
shop_description: 1,
}).then(res => {
if (res.code == 0) {
toast.success('操作成功');
row.status = 5;
} else {
toast.error('操作失败');
}
});
})
.catch((error) => {
console.log(error);
});
};
let afterRow = {}
function showActions(row) {
afterRow = row
show.value = true
}
function select({ item, index }) {
console.log(item);
if (item.name == '线上退款') {
afterType = 1
orderNotes(afterRow, '线上退款')
}
if (item.name == '线下退款') {
afterType = 2
orderNotes(afterRow, '线下退款')
}
}
const classify_id = ref(0);
const params = ref({
keywords: "",
status: "",
type: 'order_no'
});
const myListRef = ref(null);
const search = ({ value }) => {
myListRef.value.upData({
keywords: value,
classify_id: classify_id.value,
status: params.value.status - 1
});
};
const searchType = ref('订单编号');
const popover = ref(false);
const status = ref([
{ content: '全部' },
{ content: '审核中' },
{ content: '审核通过' },
{ content: '审核不通过' },
{ content: '买家已发货' },
{ content: '商家已发货' },
{ content: '已完成' },
]);
const menu = ref([
{ content: '订单编号' },
{ content: '商品名称' },
{ content: '手机号' },
{ content: '会员昵称' },
{ content: '收货人' },
]);
/**
* 切换搜索类型
* @param {Object} item - 切换的项
*/
function changeSearchType({ item, index }) {
searchType.value = item.content;
if (item.content == '订单编号') {
params.value.type = "order_no";
}
if (item.content == '商品名称') {
params.value.type = "goods_name";
}
if (item.content == '手机号') {
params.value.type = "receiver_phone";
}
if (item.content == '会员昵称') {
params.value.type = "nickname";
}
if (item.content == '收货人') {
params.value.type = "receiver_name";
}
}
/**
* 更改状态
* @param {Object} row - 行数据
*/
const changeS = (row) => {
//
// goods.goodsEditAttribute({
// id: item.id,
// value: [1, 0][item.status],
// type: "status"
// }).then(res => {
// if (res.code == 0) {
// toast.success('')
// item.status = [1, 0][item.status]
// } else {
// showNotify({ type: 'error', message: '' })
// }
// })
};
const expressList = ref([]);
/**
* 获取物流公司列表
*/
const getExpressList = () => {
order.GetExpressList().then(res => {
expressList.value = Object.entries(res.data).reduce((express, item) => {
express.push({ label: item[1], value: item[0] });
return express;
}, []);
});
};
//
getExpressList();
const shipmentsModel = ref({
send_type: 2,
delivery_company: ""
});
/**
* 处理发货操作
* @param {Object} row - 行数据
*/
const shipments = (row) => {
message
.confirm({
title: '订单发货',
})
.then((resp) => {
if (shipmentsModel.value.send_type == 2) {
order.orderSend({
id: row.id,
send_type: "2"
});
row.status = 2;
toast.success('操作成功');
setTimeout(() => {
shipmentsModel.value = {
send_type: 2,
delivery_company: ""
};
}, 300);
}
if (shipmentsModel.value.send_type == 1) {
if (shipmentsModel.value.delivery_company && shipmentsModel.value.delivery_no) {
order.orderSend({
id: row.id,
status: "4",
express_company: shipmentsModel.value.delivery_company,
express_number: shipmentsModel.value.delivery_no,
});
row.status = 4;
row.success('操作成功');
setTimeout(() => {
shipmentsModel.value = {
send_type: 2,
delivery_company: ""
};
}, 300);
} else {
toast.error('发货失败,请完善选择填写');
shipments(row);
}
}
})
.catch((error) => {
console.log(error);
setTimeout(() => {
shipmentsModel.value = {
send_type: 2,
delivery_company: ""
};
}, 300);
});
};
/**
* 处理打印小票操作
* @param {Object} row - 行数据
*/
const printing = (row) => {
message1
.confirm({
title: '确认收货',
})
.then((resp) => {
order.printOrder({
id: row.id,
send_type: "2"
}).then(res => {
if (res.code == 0) {
toast.success('操作成功');
} else {
toast.error('操作失败');
}
});
})
.catch((error) => {
console.log(error);
});
};
/**
* 处理退款审批操作
* @param {Object} row - 行数据
*/
const orderApproval = (row) => {
message1
.confirm({
title: '退款审批',
confirmButtonText: "同意",
cancelButtonText: "拒绝",
})
.then((resp) => {
order.after_sale.update({
id: row.id,
shop_description: "同意",
status: 1
}).then(res => {
if (res.code == 0) {
toast.success('操作成功');
row.status = 1;
} else {
toast.error('操作失败');
}
});
})
.catch((error) => {
if (error.action == 'cancel') {
order.after_sale.update({
id: row.id,
shop_description: "拒绝",
status: 0
}).then(res => {
if (res.code == 0) {
toast.success('操作成功');
row.status = 2;
} else {
toast.error('操作失败');
}
});
}
});
};
</script>
<style lang="scss" scoped>
.search-type {
position: relative;
height: 30px;
line-height: 30px;
padding: 0 8px 0 16px;
font-size: 24rpx;
color: rgba(0, 0, 0, .45);
}
.search-type::after {
position: absolute;
content: '';
width: 1px;
right: 0;
top: 5px;
bottom: 5px;
background: rgba(0, 0, 0, 0.25);
}
.search-type {
:deep(.icon-arrow) {
display: inline-block;
font-size: 20px;
vertical-align: middle;
}
}
.goodsBox {
.content,
.title {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
}
.content {
justify-content: flex-start;
}
.title {
justify-content: space-between;
}
.title-tip {
color: rgba(0, 0, 0, 0.25);
font-size: 12px;
}
}
</style>

@ -46,6 +46,22 @@
</div> </div>
</view> </view>
</view> </view>
<!-- <view @click="utils.toUrl('/store/order/index')" class="text-white rounded-3xl p-4 pb-0 shadow-lg overflow-hidden"
style="background-image: linear-gradient(to top, #7ea0ff 0%, #a9bdf5 100%);">
<view class="font-bold">订单管理</view>
<view class="h-0.5 w-5 mt-0.5 bg-white"></view>
<view class="flex justify-end">
<div class="rotate-[-28deg] translate-y-3 translate-x-5">
<wd-icon name="gift opacity-80" size="42px"></wd-icon>
</div>
</view>
</view> -->
</view> </view>
<view style="transition: 1s;" class="w-full h-full bg-white pt-4 rounded-tr-3xl rounded-tl-3xl shadow-lg oh"> <view style="transition: 1s;" class="w-full h-full bg-white pt-4 rounded-tr-3xl rounded-tl-3xl shadow-lg oh">
@ -70,11 +86,20 @@
<wd-tab :title="`本年`" :name="4"></wd-tab> <wd-tab :title="`本年`" :name="4"></wd-tab>
</wd-tabs> </wd-tabs>
</div> </div>
<div class="grid grid-cols-2 gap-2 my-4">
<div class="item rounded-1xl bg-slate-50 p-2 flex justify-between" style="font-size: 24rpx;"
v-for="(item, index) of orderProfit.categories">
{{ item }}
<text>{{ orderProfit.series[0].data[index] }}</text>
</div>
</div>
<bar :barData="orderProfit"></bar> <bar :barData="orderProfit"></bar>
</view> </view>
</view> </view>
<view class="flex-1 bg-white p-2 pb-4 "> <view class="flex-1 bg-white p-2 pb-4 ">
<view class=" p-2 rounded-md"> <view class=" p-2 rounded-md">
<div class="rounded-md overflow-hidden"> <div class="rounded-md overflow-hidden">
@ -85,6 +110,16 @@
<wd-tab :title="`本年`" :name="4"></wd-tab> <wd-tab :title="`本年`" :name="4"></wd-tab>
</wd-tabs> </wd-tabs>
</div> </div>
<div class="grid grid-cols-2 gap-2 my-4">
<div class="item rounded-1xl bg-slate-50 p-2 flex justify-between" style="font-size: 24rpx;"
v-for="(item, index) of orderGood.categories">
{{ item }}
<text>{{ orderGood.series[0].data[index] }}</text>
</div>
</div>
<bar :barData="orderGood"></bar> <bar :barData="orderGood"></bar>
</view> </view>
</view> </view>

@ -34,6 +34,8 @@
</wd-message-box> </wd-message-box>
<view class=" bg-white sticky top-0 z-50"> <view class=" bg-white sticky top-0 z-50">
<div class="flex items-center">
<view class="flex-1">
<wd-search @search="search" @clear="search({ value: '' })" v-model="params.keywords" hide-cancel> <wd-search @search="search" @clear="search({ value: '' })" v-model="params.keywords" hide-cancel>
<template #prefix> <template #prefix>
<wd-popover v-model="popover" mode="menu" :content="menu" @menuclick="changeSearchType"> <wd-popover v-model="popover" mode="menu" :content="menu" @menuclick="changeSearchType">
@ -44,6 +46,12 @@
</wd-popover> </wd-popover>
</template> </template>
</wd-search> </wd-search>
</view>
<div class="mr-2">
<wd-button @click="utils.toUrl('/store/afterSale/index')" size="small">售后订单</wd-button>
</div>
</div>
<wd-tabs @change="(e) => { <wd-tabs @change="(e) => {
params.status = e.index; params.status = e.index;
@ -81,9 +89,13 @@
<view class="line2" style="color: rgba(0,0,0,0.85); font-size: 16px;">{{ subItem.goods_name }}</view> <view class="line2" style="color: rgba(0,0,0,0.85); font-size: 16px;">{{ subItem.goods_name }}</view>
<!-- <view class="font-bold" style="color: rgb(255, 0, 0); font-size: 16px;">{{ subItem.price }} <!-- <view class="font-bold" style="color: rgb(255, 0, 0); font-size: 16px;">{{ subItem.price }}
</view> --> </view> -->
<view class="font-bold">小计: {{ subItem.price }} <text <view class="font-bold" style="font-size: 22rpx;">小计: {{ subItem.price }} <text
style="color: rgb(255, 0, 0); font-size: 24rpx;">({{ style="color: rgb(255, 0, 0); font-size: 24rpx;">({{
subItem.num }}) </text></view> subItem.num }}) </text></view>
<div class="goods_sky" style="font-size: 22rpx;">
<span v-for="(sku, _index) of subItem.goods_sku_json" :key="_index">
{{ sku.group_name }}{{ sku.name }}</span>
</div>
</view> </view>
</view> </view>

Loading…
Cancel
Save