Browse Source

feat:订单列表

dev_des
1054425342@qq.com 1 month ago
parent
commit
a7fdf12ff1
  1. 24
      pages.json
  2. 878
      subPackages/haveFeeling/aftersale.vue
  3. 1107
      subPackages/haveFeeling/detail.vue
  4. 810
      subPackages/haveFeeling/list.vue
  5. 592
      subPackages/haveFeeling/logistics.vue
  6. 20
      subPackages/techan/order.vue

24
pages.json

@ -260,6 +260,30 @@
"navigationBarTitleText": "核销码"
}
},
{
"path": "haveFeeling/list",
"style": {
"navigationBarTitleText": "有感商品订单"
}
},
{
"path": "haveFeeling/detail",
"style": {
"navigationBarTitleText": "订单详情"
}
},
{
"path": "haveFeeling/logistics",
"style": {
"navigationBarTitleText": "物流详情"
}
},
{
"path": "haveFeeling/aftersale",
"style": {
"navigationBarTitleText": "申请售后"
}
},
{
"path": "memorialAlbum/index",

878
subPackages/haveFeeling/aftersale.vue

@ -0,0 +1,878 @@
<template>
<view class="aftersale-container">
<!-- 订单信息 -->
<view class="order-section">
<view class="section-title">订单信息</view>
<view class="order-card">
<view class="order-header">
<text class="order-no">订单号{{ orderInfo.orderNo }}</text>
<text class="order-status">{{ getOrderStatusText(orderInfo.status) }}</text>
</view>
<view class="supplier-info">
<text class="supplier-name">供应商{{ orderInfo.supplierName }}</text>
</view>
</view>
</view>
<!-- 商品信息 -->
<view class="goods-section">
<view class="section-title">选择售后商品</view>
<view class="goods-list">
<view
class="goods-item"
v-for="goods in orderInfo.orderGoods"
:key="goods.id"
:class="{ 'selected': selectedGoods.includes(goods.id) }"
@click="toggleGoodsSelect(goods)"
>
<view class="goods-checkbox">
<view class="checkbox" :class="{ 'checked': selectedGoods.includes(goods.id) }">
<text class="checkbox-icon" v-if="selectedGoods.includes(goods.id)"></text>
</view>
</view>
<view class="goods-image">
<image :src="showImgJdsz(goods.goodsImg)" mode="aspectFill" />
</view>
<view class="goods-info">
<view class="goods-name">{{ goods.goodsTitle }}</view>
<view class="goods-sku" v-if="goods.skuName">{{ goods.skuName }}</view>
<view class="goods-meta">
<text class="goods-price">¥{{ goods.price }}</text>
<text class="goods-quantity">×{{ goods.num }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 售后类型 -->
<view class="type-section">
<view class="section-title">售后类型</view>
<view class="type-list">
<view
class="type-item"
v-for="type in aftersaleTypes"
:key="type.value"
:class="{ 'selected': aftersaleForm.type === type.value }"
@click="selectType(type.value)"
>
<view class="type-radio">
<view class="radio" :class="{ 'checked': aftersaleForm.type === type.value }">
<view class="radio-dot" v-if="aftersaleForm.type === type.value"></view>
</view>
</view>
<view class="type-info">
<text class="type-name">{{ type.name }}</text>
<text class="type-desc">{{ type.desc }}</text>
</view>
</view>
</view>
</view>
<!-- 售后原因 -->
<view class="reason-section">
<view class="section-title">售后原因</view>
<view class="reason-list">
<view
class="reason-item"
v-for="reason in reasonList"
:key="reason.value"
:class="{ 'selected': aftersaleForm.reason === reason.value }"
@click="selectReason(reason.value)"
>
<text class="reason-text">{{ reason.name }}</text>
<view class="reason-check" v-if="aftersaleForm.reason === reason.value"></view>
</view>
</view>
</view>
<!-- 问题描述 -->
<view class="description-section">
<view class="section-title">问题描述</view>
<textarea
class="description-input"
v-model="aftersaleForm.description"
placeholder="请详细描述遇到的问题,以便我们更好地为您处理"
maxlength="500"
/>
<view class="char-count">{{ aftersaleForm.description.length }}/500</view>
</view>
<!-- 上传凭证 -->
<view class="upload-section">
<view class="section-title">上传凭证选填</view>
<view class="upload-area">
<view class="image-list">
<view
class="image-item"
v-for="(image, index) in aftersaleForm.images"
:key="index"
>
<image class="uploaded-image" :src="image" mode="aspectFill" />
<view class="delete-btn" @click="deleteImage(index)">×</view>
</view>
<view class="upload-btn" @click="chooseImage" v-if="aftersaleForm.images.length < 6">
<text class="upload-icon">+</text>
<text class="upload-text">添加图片</text>
</view>
</view>
<view class="upload-tip">最多可上传6张图片支持jpgpng格式</view>
</view>
</view>
<!-- 联系方式 -->
<view class="contact-section">
<view class="section-title">联系方式</view>
<view class="contact-form">
<view class="form-item">
<text class="form-label">联系人</text>
<input
class="form-input"
v-model="aftersaleForm.contactName"
placeholder="请输入联系人姓名"
/>
</view>
<view class="form-item">
<text class="form-label">联系电话</text>
<input
class="form-input"
v-model="aftersaleForm.contactPhone"
placeholder="请输入联系电话"
type="number"
/>
</view>
</view>
</view>
<!-- 底部提交按钮 -->
<view class="submit-section">
<button class="submit-btn" @click="submitAftersale" :disabled="!canSubmit">
提交售后申请
</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
orderId: '',
orderInfo: {
orderNo: '',
status: 0,
supplierName: '',
orderGoods: []
},
selectedGoods: [], // ID
aftersaleForm: {
type: 1, // 1-退退2-3-退
reason: '', //
description: '', //
images: [], //
contactName: '', //
contactPhone: '' //
},
aftersaleTypes: [
{
value: 1,
name: '退货退款',
desc: '商品有质量问题,需要退货并退款'
},
{
value: 2,
name: '换货',
desc: '商品有问题,需要更换同款商品'
},
{
value: 3,
name: '仅退款',
desc: '未收到商品或商品严重损坏'
}
],
reasonList: [
{ value: 'quality', name: '商品质量问题' },
{ value: 'damage', name: '商品破损' },
{ value: 'wrong', name: '发错商品' },
{ value: 'description', name: '与描述不符' },
{ value: 'logistics', name: '物流问题' },
{ value: 'other', name: '其他原因' }
]
};
},
computed: {
//
canSubmit() {
return (
this.selectedGoods.length > 0 &&
this.aftersaleForm.type &&
this.aftersaleForm.reason &&
this.aftersaleForm.description.trim() &&
this.aftersaleForm.contactName.trim() &&
this.aftersaleForm.contactPhone.trim()
);
}
},
onLoad(options) {
if (options.orderId) {
this.orderId = options.orderId;
this.loadOrderInfo();
this.loadUserInfo();
}
},
methods: {
showImgJdsz(img) {
if (!img) return '/static/images/default-goods.png';
if (img.indexOf("https://") != -1 || img.indexOf("http://") != -1) {
return img;
} else {
return this.JDSU_IMG_URL + img;
}
},
//
async loadOrderInfo() {
try {
this.Post(
{ orderId: this.orderId },
'/framework/haveFeeling/order/detail',
'DES'
).then((res) => {
if (res.code == 200) {
this.orderInfo = res.data;
} else {
uni.showToast({
title: res.msg,
icon: 'none'
});
}
});
} catch (error) {
console.error('加载订单信息失败:', error);
}
},
//
loadUserInfo() {
const userInfo = uni.getStorageSync('userInfo');
if (userInfo) {
const user = JSON.parse(userInfo);
this.aftersaleForm.contactName = user.nickName || '';
this.aftersaleForm.contactPhone = user.phone || '';
}
},
//
getOrderStatusText(status) {
const statusMap = {
"-1": "已取消",
0: "待支付",
1: "已支付",
2: "已发货",
3: "已完成"
};
return statusMap[status] || "未知";
},
//
toggleGoodsSelect(goods) {
const index = this.selectedGoods.indexOf(goods.id);
if (index > -1) {
this.selectedGoods.splice(index, 1);
} else {
this.selectedGoods.push(goods.id);
}
},
//
selectType(type) {
this.aftersaleForm.type = type;
},
//
selectReason(reason) {
this.aftersaleForm.reason = reason;
},
//
chooseImage() {
const remainCount = 6 - this.aftersaleForm.images.length;
uni.chooseImage({
count: remainCount,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
this.uploadImages(res.tempFilePaths);
}
});
},
//
async uploadImages(filePaths) {
uni.showLoading({
title: '上传中...'
});
try {
for (let filePath of filePaths) {
const uploadResult = await this.uploadSingleImage(filePath);
if (uploadResult) {
this.aftersaleForm.images.push(uploadResult);
}
}
} catch (error) {
console.error('图片上传失败:', error);
uni.showToast({
title: '图片上传失败',
icon: 'none'
});
} finally {
uni.hideLoading();
}
},
//
uploadSingleImage(filePath) {
return new Promise((resolve, reject) => {
uni.uploadFile({
url: this.BASE_URL + '/framework/upload/image',
filePath: filePath,
name: 'file',
success: (res) => {
try {
const data = JSON.parse(res.data);
if (data.code === 200) {
resolve(data.data.url);
} else {
reject(new Error(data.msg));
}
} catch (error) {
reject(error);
}
},
fail: (error) => {
reject(error);
}
});
});
},
//
deleteImage(index) {
this.aftersaleForm.images.splice(index, 1);
},
//
async submitAftersale() {
if (!this.canSubmit) {
uni.showToast({
title: '请完善售后信息',
icon: 'none'
});
return;
}
//
const phoneReg = /^1[3-9]\d{9}$/;
if (!phoneReg.test(this.aftersaleForm.contactPhone)) {
uni.showToast({
title: '请输入正确的手机号',
icon: 'none'
});
return;
}
uni.showLoading({
title: '提交中...'
});
try {
const params = {
orderId: this.orderId,
goodsIds: this.selectedGoods,
type: this.aftersaleForm.type,
reason: this.aftersaleForm.reason,
description: this.aftersaleForm.description,
images: this.aftersaleForm.images.join(','),
contactName: this.aftersaleForm.contactName,
contactPhone: this.aftersaleForm.contactPhone
};
this.Post(
params,
'/framework/haveFeeling/aftersale/submit',
'DES'
).then((res) => {
uni.hideLoading();
if (res.code == 200) {
uni.showToast({
title: '售后申请提交成功',
icon: 'success'
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
} else {
uni.showToast({
title: res.msg || '提交失败',
icon: 'none'
});
}
});
} catch (error) {
uni.hideLoading();
console.error('提交售后申请失败:', error);
uni.showToast({
title: '提交失败',
icon: 'none'
});
}
}
}
};
</script>
<style lang="scss" scoped>
.aftersale-container {
min-height: 100vh;
background-color: #f5f5f5;
padding-bottom: 100rpx;
}
//
.section-title {
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 24rpx;
}
//
.order-section {
background-color: #fff;
padding: 30rpx;
margin-bottom: 20rpx;
}
.order-card {
background-color: #f8f9fa;
border-radius: 12rpx;
padding: 24rpx;
}
.order-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12rpx;
}
.order-no {
font-size: 26rpx;
color: #333;
font-weight: 500;
}
.order-status {
font-size: 24rpx;
color: #007aff;
background-color: #e6f3ff;
padding: 4rpx 12rpx;
border-radius: 8rpx;
}
.supplier-info {
.supplier-name {
font-size: 24rpx;
color: #666;
}
}
//
.goods-section {
background-color: #fff;
padding: 30rpx;
margin-bottom: 20rpx;
}
.goods-list {
.goods-item {
display: flex;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #f0f0f0;
transition: all 0.3s ease;
&:last-child {
border-bottom: none;
}
&.selected {
background-color: #f0f8ff;
}
}
}
.goods-checkbox {
margin-right: 20rpx;
.checkbox {
width: 40rpx;
height: 40rpx;
border: 2rpx solid #ddd;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
&.checked {
background-color: #007aff;
border-color: #007aff;
.checkbox-icon {
color: #fff;
font-size: 24rpx;
font-weight: bold;
}
}
}
}
.goods-image {
width: 80rpx;
height: 80rpx;
border-radius: 8rpx;
overflow: hidden;
margin-right: 20rpx;
image {
width: 100%;
height: 100%;
}
}
.goods-info {
flex: 1;
}
.goods-name {
font-size: 26rpx;
color: #333;
font-weight: 600;
margin-bottom: 8rpx;
}
.goods-sku {
font-size: 22rpx;
color: #666;
margin-bottom: 8rpx;
}
.goods-meta {
display: flex;
justify-content: space-between;
align-items: center;
}
.goods-price {
font-size: 24rpx;
color: #f56565;
font-weight: 600;
}
.goods-quantity {
font-size: 22rpx;
color: #999;
}
//
.type-section {
background-color: #fff;
padding: 30rpx;
margin-bottom: 20rpx;
}
.type-list {
.type-item {
display: flex;
align-items: center;
padding: 24rpx 0;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
&.selected {
background-color: #f0f8ff;
}
}
}
.type-radio {
margin-right: 20rpx;
.radio {
width: 40rpx;
height: 40rpx;
border: 2rpx solid #ddd;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
&.checked {
border-color: #007aff;
.radio-dot {
width: 20rpx;
height: 20rpx;
background-color: #007aff;
border-radius: 50%;
}
}
}
}
.type-info {
flex: 1;
}
.type-name {
display: block;
font-size: 28rpx;
color: #333;
font-weight: 600;
margin-bottom: 6rpx;
}
.type-desc {
font-size: 22rpx;
color: #666;
line-height: 1.4;
}
//
.reason-section {
background-color: #fff;
padding: 30rpx;
margin-bottom: 20rpx;
}
.reason-list {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
.reason-item {
position: relative;
display: flex;
align-items: center;
justify-content: center;
padding: 16rpx 24rpx;
background-color: #f5f5f5;
border-radius: 24rpx;
border: 1rpx solid transparent;
transition: all 0.3s ease;
min-width: 140rpx;
&.selected {
background-color: #e6f3ff;
border-color: #007aff;
color: #007aff;
}
}
.reason-text {
font-size: 24rpx;
color: #333;
}
.reason-check {
position: absolute;
top: -8rpx;
right: -8rpx;
width: 24rpx;
height: 24rpx;
background-color: #007aff;
color: #fff;
border-radius: 50%;
font-size: 16rpx;
display: flex;
align-items: center;
justify-content: center;
}
//
.description-section {
background-color: #fff;
padding: 30rpx;
margin-bottom: 20rpx;
}
.description-input {
width: 100%;
min-height: 200rpx;
background-color: #f8f9fa;
border-radius: 12rpx;
padding: 20rpx;
font-size: 26rpx;
color: #333;
line-height: 1.6;
border: none;
resize: none;
}
.char-count {
text-align: right;
font-size: 22rpx;
color: #999;
margin-top: 12rpx;
}
//
.upload-section {
background-color: #fff;
padding: 30rpx;
margin-bottom: 20rpx;
}
.upload-area {
.image-list {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
margin-bottom: 16rpx;
}
.image-item {
position: relative;
width: 160rpx;
height: 160rpx;
border-radius: 12rpx;
overflow: hidden;
.uploaded-image {
width: 100%;
height: 100%;
}
.delete-btn {
position: absolute;
top: -8rpx;
right: -8rpx;
width: 32rpx;
height: 32rpx;
background-color: #ff4757;
color: #fff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 20rpx;
font-weight: bold;
}
}
.upload-btn {
width: 160rpx;
height: 160rpx;
background-color: #f8f9fa;
border: 2rpx dashed #ddd;
border-radius: 12rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #666;
.upload-icon {
font-size: 48rpx;
margin-bottom: 8rpx;
}
.upload-text {
font-size: 22rpx;
}
}
.upload-tip {
font-size: 22rpx;
color: #999;
text-align: center;
}
}
//
.contact-section {
background-color: #fff;
padding: 30rpx;
margin-bottom: 20rpx;
}
.contact-form {
.form-item {
display: flex;
align-items: center;
margin-bottom: 24rpx;
&:last-child {
margin-bottom: 0;
}
}
.form-label {
width: 160rpx;
font-size: 26rpx;
color: #333;
font-weight: 500;
}
.form-input {
flex: 1;
height: 80rpx;
background-color: #f8f9fa;
border-radius: 8rpx;
padding: 0 20rpx;
font-size: 26rpx;
color: #333;
border: none;
}
}
//
.submit-section {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #fff;
padding: 20rpx 30rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
border-top: 1rpx solid #eee;
}
.submit-btn {
width: 100%;
height: 88rpx;
background-color: #007aff;
color: #fff;
border-radius: 12rpx;
font-size: 28rpx;
font-weight: 600;
border: none;
transition: all 0.3s ease;
&:disabled {
background-color: #ccc;
color: #999;
}
&:not(:disabled):active {
transform: scale(0.98);
}
}
</style>

1107
subPackages/haveFeeling/detail.vue

File diff suppressed because it is too large

810
subPackages/haveFeeling/list.vue

@ -0,0 +1,810 @@
<template>
<view class="order-list-container">
<!-- Tab切换 -->
<view class="tab-container">
<view
class="tab-item"
:class="[{ active: currentTab == index }]"
v-for="(tab, index) in tabs"
:key="index"
@click="switchTab(index)"
>
<text class="tab-text">{{ tab.name }}</text>
</view>
</view>
<!-- 订单列表 -->
<scroll-view
class="order-scroll"
scroll-y
@scrolltolower="loadMore"
:show-scrollbar="false"
enhanced
>
<!-- 订单项 -->
<view
class="order-item"
v-for="order in orderList"
:key="order.id"
@click="goToOrderDetail(order)"
>
<!-- 订单头部 - 供应商信息 -->
<view class="order-header">
<view class="supplier-info">
<view class="supplier-name">{{
order.supplierName || "默认供应商"
}}</view>
<view class="order-number">订单号{{ order.orderNo }}</view>
</view>
<view class="order-status-wrapper">
<text class="status-name" :class="[getStatusClass(order.status)]">{{
getStatusText(order.status)
}}</text>
</view>
</view>
<!-- 商品列表 -->
<view class="goods-section">
<view class="goods-list">
<view
class="goods-item"
v-for="goods in order.orderItems"
:key="goods.id"
>
<view class="goods-image-container">
<image
v-if="goods.specImage"
class="goods-image"
:src="goods.specImage"
mode="aspectFill"
/>
</view>
<view class="goods-info">
<text class="goods-name">{{ goods.goodsName }}</text>
<view class="goods-specs">
<view class="spec-tag">
{{ goods.specValueOne }}-{{ goods.specValueTwo }}
</view>
</view>
<view class="goods-meta">
<text class="goods-price">¥{{ goods.price }}</text>
<text class="goods-quantity">×{{ goods.quantity || 1 }}</text>
</view>
<!-- 规格信息 -->
</view>
</view>
</view>
</view>
<!-- 订单底部 -->
<view class="order-footer">
<view class="order-info">
<view class="order-total-section">
<text class="total-label">实付</text>
<text class="order-total">¥{{ order.payAmount || 0 }}</text>
</view>
<view class="order-time">{{ order.createTime }}</view>
</view>
<view class="order-actions">
<!-- 基于订单状态的操作按钮 -->
<button
class="action-btn"
@click.stop="handleOrderAction(order, 'pay')"
v-if="order.status === 0"
>
去支付
</button>
<button
class="action-btn"
@click.stop="handleOrderAction(order, 'logistics')"
v-if="[2, 3].includes(order.status)"
>
查看物流
</button>
<button
class="action-btn confirm-btn"
@click.stop="handleOrderAction(order, 'confirm')"
v-if="order.status === 2"
>
确认收货
</button>
<button
class="action-btn"
@click.stop="handleOrderAction(order, 'contact')"
v-if="[1, 2].includes(order.status)"
>
联系供应商
</button>
<button
class="action-btn secondary-btn"
@click.stop="handleOrderAction(order, 'aftersale')"
v-if="order.status === 3"
>
申请售后
</button>
</view>
</view>
</view>
<!-- 空状态 -->
<view
class="empty-state"
v-if="(orderList && orderList.length == 0) || loading"
>
<image
class="empty-image"
:src="
showImg('/uploads/20250808/c16267f9cc2b7a68bf23713b5847987e.png')
"
mode="aspectFit"
/>
<text class="empty-text">暂无订单</text>
</view>
<!-- 加载更多 -->
<view class="load-more" v-if="hasMore && orderList.length > 0">
<text class="load-text">{{
loading ? "加载中..." : "上拉加载更多"
}}</text>
</view>
<!-- 没有更多数据 -->
<view class="no-more" v-if="!hasMore && orderList.length > 0">
<text class="no-more-text">没有更多数据了</text>
</view>
</scroll-view>
</view>
</template>
<script>
export default {
data() {
return {
currentTab: 0,
tabs: [
{
name: "全部",
status: "",
},
{
name: "待支付",
status: 0,
},
{
name: "已支付",
status: 1,
},
{
name: "已发货",
status: 2,
},
{
name: "已完成",
status: 3,
},
],
orderList: [],
loading: false,
refresherTriggered: false,
hasMore: true,
currentPage: 1,
pageSize: 10,
};
},
onLoad(e) {
if (e.status) {
this.currentTab = e.status;
}
},
onShow() {
this.resetList();
this.loadOrderList();
},
methods: {
showImgJdsz(img) {
if (!img) return;
if (img.indexOf("https://") != -1 || img.indexOf("http://") != -1) {
return img;
} else {
return this.JDSU_IMG_URL + img;
}
},
// Tab
switchTab(index) {
this.currentTab = index;
this.resetList();
this.loadOrderList();
},
//
resetList() {
this.orderList = [];
this.currentPage = 1;
this.hasMore = true;
},
//
async loadOrderList() {
if (this.loading || !this.hasMore) return;
this.loading = true;
try {
const params = {
pageNum: this.currentPage,
pageSize: this.pageSize,
status: this.tabs[this.currentTab].status,
};
this.Post(
{
...params,
},
"/framework/ygOrder/pageList",
"DES"
).then((res) => {
if (res.code == 200) {
this.orderList.push(...res.rows);
console.log(this.orderList);
this.hasMore = res.rows.length === this.pageSize;
this.currentPage++;
} else {
uni.showToast({
title: res.msg,
icon: "none",
});
}
});
} catch (error) {
console.error("加载订单列表失败:", error);
uni.showToast({
title: "加载失败",
icon: "none",
});
} finally {
this.loading = false;
this.refresherTriggered = false;
}
},
//
loadMore() {
this.loadOrderList();
},
//
onRefresh() {
this.refresherTriggered = true;
this.resetList();
this.loadOrderList();
},
//
getStatusText(status) {
const statusMap = {
"-1": "已取消",
0: "待支付",
1: "已支付",
2: "已发货",
3: "已完成",
};
return statusMap[status] || "未知";
},
//
getStatusClass(status) {
const classMap = {
"-1": "status-cancelled",
0: "status-pending",
1: "status-paid",
2: "status-shipping",
3: "status-completed",
};
return classMap[status] || "";
},
//
handleOrderAction(order, action) {
switch (action) {
case "pay":
this.goToPay(order);
break;
case "logistics":
this.viewLogistics(order);
break;
case "confirm":
this.confirmReceipt(order);
break;
case "contact":
this.contactSupplier(order);
break;
case "aftersale":
this.applyAfterSale(order);
break;
}
},
//
goToPay(order) {
this.Post(
{
method: "POST",
orderNo: order.orderNo,
fromType: 2,
payAmount: order.payAmount,
},
"/framework/wxPay/submitShopPurOrder",
"DES"
).then((res) => {
uni.requestPayment({
nonceStr: res.data.wxInfo.nonceStr,
package: res.data.wxInfo.package,
paySign: res.data.wxInfo.paySign,
signType: res.data.wxInfo.signType,
timeStamp: res.data.wxInfo.timeStamp,
success: () => {
this.resetList();
this.loadOrderList();
},
fail() {
// uni.navigateTo({
// url: '/subPackages/order/trades'
// })
},
});
});
},
//
viewLogistics(order) {
uni.navigateTo({
url: `/subPackages/haveFeeling/logistics?orderId=${order.id}`,
});
},
//
confirmReceipt(order) {
uni.showModal({
title: "确认收货",
content: "确认已收到所有商品吗?",
success: (res) => {
if (res.confirm) {
this.confirmReceiptApi(order.id);
}
},
});
},
// API
confirmReceiptApi(orderId) {
uni.showLoading({
title: "确认中...",
});
this.Post(
{
orderId: orderId,
},
"/framework/haveFeeling/order/confirm",
"DES"
).then((res) => {
uni.hideLoading();
if (res.code == 200) {
uni.showToast({
title: "确认收货成功",
icon: "success",
});
setTimeout(() => {
this.resetList();
this.loadOrderList();
}, 800);
} else {
uni.showToast({
title: res.msg || "确认收货失败",
icon: "none",
});
}
});
},
//
contactSupplier(order) {
uni.showToast({
title: "正在联系供应商",
icon: "none",
});
//
},
//
applyAfterSale(order) {
uni.navigateTo({
url: `/subPackages/haveFeeling/aftersale?orderId=${order.id}`,
});
},
//
goToOrderDetail(order) {
uni.navigateTo({
url: `/subPackages/haveFeeling/detail?id=${order.id}`,
});
},
},
};
</script>
<style lang="scss" scoped>
//
$primary-color: #667eea;
$secondary-color: #f8f9fa;
$text-primary: #2d3748;
$text-secondary: #718096;
$text-muted: #a0aec0;
$border-color: #e2e8f0;
$success-color: #48bb78;
$warning-color: #ed8936;
$danger-color: #f56565;
$bg-light: #f7fafc;
.order-list-container {
min-height: 100vh;
background-color: $bg-light;
}
// Tab
.tab-container {
display: flex;
background-color: #ffffff;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
color: black;
position: sticky;
top: 0;
z-index: 10;
}
.tab-item {
flex: 1;
padding: 32rpx 0;
text-align: center;
position: relative;
transition: all 0.3s ease;
color: #333333;
&.active {
.tab-text {
color: #77f3f9;
font-weight: 600;
}
&::after {
content: "";
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60rpx;
height: 4rpx;
background-color: #77f3f9;
border-radius: 2rpx;
}
}
&:not(.active) {
.tab-text {
opacity: 1;
}
}
}
.tab-text {
font-size: 26rpx;
color: #333333;
font-weight: 600;
transition: all 0.3s ease;
}
//
.order-scroll {
height: calc(100vh - 120rpx);
padding: 0 24rpx;
width: 702rpx;
}
.order-item {
background-color: #ffffff;
border-radius: 20rpx;
overflow: hidden;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
border: none;
transition: all 0.3s ease;
margin-top: 40rpx;
&:active {
transform: scale(0.98);
}
}
// -
.order-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 28rpx 20rpx;
background: white;
}
.supplier-info {
flex: 1;
}
.supplier-name {
font-size: 30rpx;
color: $text-primary;
font-weight: 600;
margin-bottom: 8rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 490rpx;
}
.order-number {
font-size: 24rpx;
color: $text-secondary;
font-weight: 500;
}
.order-status-wrapper {
display: flex;
align-items: center;
}
.status-name {
font-size: 26rpx;
padding: 12rpx 20rpx;
border-radius: 24rpx;
font-weight: 600;
letter-spacing: 0.5rpx;
&.status-pending {
background: #fff3cd;
color: #856404;
}
&.status-paid {
background: #e6f3ff;
color: #0c5460;
}
&.status-cancelled {
background: #f8d7da;
color: #721c24;
}
&.status-shipping {
background: #d4edda;
color: #155724;
}
&.status-completed {
background: #d1ecf1;
color: #0c5460;
}
}
//
.goods-section {
margin: 20rpx 0;
}
.goods-list {
padding: 0 20rpx;
}
.goods-item {
display: flex;
align-items: center;
padding: 20rpx;
margin-bottom: 12rpx;
background: #ffffff;
border-radius: 16rpx;
border: 1px solid rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
&:last-child {
margin-bottom: 0;
}
}
.goods-image-container {
position: relative;
margin-right: 20rpx;
flex-shrink: 0;
}
.goods-image {
width: 100rpx;
height: 100rpx;
border-radius: 12rpx;
}
.goods-info {
flex: 1;
margin-right: 20rpx;
}
.goods-name {
display: block;
font-size: 26rpx;
color: $text-primary;
font-weight: 600;
margin-bottom: 12rpx;
line-height: 1.4;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 490rpx;
}
.goods-meta {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10rpx;
}
.goods-price {
font-size: 26rpx;
color: $danger-color;
font-weight: 600;
font-family: "SF Mono", "Monaco", "Cascadia Code", monospace;
}
.goods-quantity {
font-size: 22rpx;
color: $text-muted;
font-weight: 500;
}
//
.goods-specs {
margin-top: 10rpx;
display: flex;
flex-wrap: wrap;
gap: 8rpx;
}
.spec-tag {
background: #f0f8ff;
border: 1rpx solid #e6f3ff;
border-radius: 12rpx;
padding: 4rpx 12rpx;
display: inline-block;
margin-bottom: 6rpx;
font-size: 22rpx;
color: #4a90e2;
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 490rpx;
}
//
.order-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 28rpx;
background: white;
margin-top: 20rpx;
}
.order-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 8rpx;
}
.order-total-section {
display: flex;
align-items: center;
gap: 8rpx;
}
.total-label {
font-size: 22rpx;
color: $text-muted;
font-weight: 500;
}
.order-total {
font-size: 32rpx;
color: $danger-color;
font-weight: 600;
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC",
"Helvetica Neue", STHeiti, "Microsoft Yahei", Tahoma, Simsun, sans-serif;
}
.order-time {
font-size: 22rpx;
color: $text-muted;
font-weight: 500;
}
.order-actions {
display: flex;
gap: 12rpx;
flex-direction: column;
.action-btn {
padding: 8rpx 20rpx;
border-radius: 10rpx;
font-size: 24rpx;
font-weight: 600;
border: none;
color: #333333;
transition: all 0.3s ease;
min-width: 100rpx;
text-align: center;
background-color: #77f3f9;
display: flex;
align-items: center;
justify-content: center;
height: 58rpx;
&:active {
transform: scale(0.95);
}
&.confirm-btn {
background-color: $success-color;
color: #ffffff;
}
&.secondary-btn {
background-color: #f5f5f5;
color: #666666;
border: 1rpx solid #ddd;
}
}
}
//
.empty-state {
text-align: center;
padding: 240rpx 40rpx;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.empty-image {
width: 240rpx;
height: 240rpx;
margin-bottom: 40rpx;
opacity: 0.8;
}
.empty-text {
font-size: 32rpx;
color: $text-muted;
font-weight: 500;
}
//
.load-more,
.no-more {
text-align: center;
padding: 40rpx;
}
.load-text,
.no-more-text {
font-size: 28rpx;
color: $text-muted;
font-weight: 500;
}
</style>

592
subPackages/haveFeeling/logistics.vue

@ -0,0 +1,592 @@
<template>
<view class="logistics-container">
<!-- 物流状态头部 -->
<view class="status-header">
<view class="status-info">
<view class="status-icon">
<image :src="getStatusIcon(logisticsInfo.status)" mode="aspectFit" />
</view>
<view class="status-content">
<view class="status-title">{{ getStatusText(logisticsInfo.status) }}</view>
<view class="status-desc" v-if="logisticsInfo.lastUpdate">
最新状态{{ logisticsInfo.lastUpdate }}
</view>
</view>
</view>
</view>
<!-- 快递信息 -->
<view class="express-section">
<view class="section-title">快递信息</view>
<view class="express-card">
<view class="express-item">
<text class="express-label">快递公司</text>
<text class="express-value">{{ logisticsInfo.expressCompany || '--' }}</text>
</view>
<view class="express-item">
<text class="express-label">快递单号</text>
<text class="express-value" @longpress="copyExpressNo">{{ logisticsInfo.expressNo || '--' }}</text>
</view>
<view class="express-item">
<text class="express-label">发货时间</text>
<text class="express-value">{{ logisticsInfo.shippingTime || '--' }}</text>
</view>
<view class="express-item">
<text class="express-label">预计送达</text>
<text class="express-value">{{ logisticsInfo.estimatedTime || '--' }}</text>
</view>
</view>
</view>
<!-- 收货地址 -->
<view class="address-section">
<view class="section-title">收货地址</view>
<view class="address-card">
<view class="address-info">
<view class="recipient-info">
<text class="recipient-name">{{ addressInfo.linkName }}</text>
<text class="recipient-phone">{{ addressInfo.phone }}</text>
</view>
<view class="address-detail">
{{ addressInfo.province }}{{ addressInfo.city }}{{ addressInfo.area }}{{ addressInfo.address }}
</view>
</view>
</view>
</view>
<!-- 物流轨迹 -->
<view class="trace-section">
<view class="section-title">物流轨迹</view>
<view class="trace-list">
<view
class="trace-item"
v-for="(trace, index) in logisticsTrace"
:key="index"
:class="{ 'is-current': index === 0 }"
>
<view class="trace-time">{{ trace.time }}</view>
<view class="trace-content">
<view class="trace-status">{{ trace.status }}</view>
<view class="trace-location" v-if="trace.location">{{ trace.location }}</view>
<view class="trace-remark" v-if="trace.remark">{{ trace.remark }}</view>
</view>
</view>
</view>
<!-- 空状态 -->
<view class="empty-trace" v-if="!logisticsTrace || logisticsTrace.length === 0">
<image class="empty-image" src="/static/images/empty-logistics.png" mode="aspectFit" />
<text class="empty-text">暂无物流轨迹信息</text>
</view>
</view>
<!-- 联系供应商 -->
<view class="contact-section">
<view class="section-title">联系供应商</view>
<view class="contact-card">
<view class="supplier-info">
<view class="supplier-name">{{ supplierInfo.name || '默认供应商' }}</view>
<view class="supplier-phone">联系电话{{ supplierInfo.phone || '暂无' }}</view>
</view>
<button class="contact-btn" @click="contactSupplier">
<text>联系供应商</text>
</button>
</view>
</view>
<!-- 订单操作 -->
<view class="order-actions">
<button
class="action-btn confirm-btn"
@click="confirmReceipt"
v-if="logisticsInfo.status === 3"
>
确认收货
</button>
<button
class="action-btn secondary-btn"
@click="applyAfterSale"
v-if="[2, 3, 4].includes(logisticsInfo.status)"
>
申请售后
</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
orderId: '',
logisticsInfo: {
status: 0,
expressCompany: '',
expressNo: '',
shippingTime: '',
estimatedTime: '',
lastUpdate: ''
},
addressInfo: {
linkName: '',
phone: '',
province: '',
city: '',
area: '',
address: ''
},
supplierInfo: {
name: '',
phone: ''
},
logisticsTrace: []
};
},
onLoad(options) {
if (options.orderId) {
this.orderId = options.orderId;
this.loadLogisticsInfo();
}
},
methods: {
//
async loadLogisticsInfo() {
try {
uni.showLoading({
title: '加载中...'
});
this.Post(
{ orderId: this.orderId },
'/framework/haveFeeling/order/logistics',
'DES'
).then((res) => {
if (res.code == 200) {
this.logisticsInfo = res.data.logistics || {};
this.addressInfo = res.data.address || {};
this.supplierInfo = res.data.supplier || {};
this.logisticsTrace = res.data.trace || [];
} else {
uni.showToast({
title: res.msg,
icon: 'none'
});
}
});
} catch (error) {
console.error('加载物流信息失败:', error);
uni.showToast({
title: '加载失败',
icon: 'none'
});
} finally {
uni.hideLoading();
}
},
//
getStatusText(status) {
const statusMap = {
0: '待发货',
1: '已发货',
2: '运输中',
3: '派送中',
4: '已签收',
5: '异常'
};
return statusMap[status] || '待发货';
},
//
getStatusIcon(status) {
const iconMap = {
0: '/static/images/logistics-waiting.png',
1: '/static/images/logistics-shipped.png',
2: '/static/images/logistics-transit.png',
3: '/static/images/logistics-delivering.png',
4: '/static/images/logistics-completed.png',
5: '/static/images/logistics-exception.png'
};
return iconMap[status] || '/static/images/logistics-waiting.png';
},
//
copyExpressNo() {
if (this.logisticsInfo.expressNo) {
uni.setClipboardData({
data: this.logisticsInfo.expressNo,
success: () => {
uni.showToast({
title: '快递单号已复制',
icon: 'success'
});
}
});
}
},
//
contactSupplier() {
if (this.supplierInfo.phone) {
uni.makePhoneCall({
phoneNumber: this.supplierInfo.phone
});
} else {
uni.showToast({
title: '暂无供应商联系方式',
icon: 'none'
});
}
},
//
confirmReceipt() {
uni.showModal({
title: '确认收货',
content: '确认已收到所有商品吗?',
success: (res) => {
if (res.confirm) {
this.confirmReceiptApi();
}
}
});
},
// API
confirmReceiptApi() {
uni.showLoading({
title: '确认中...'
});
this.Post(
{ orderId: this.orderId },
'/framework/haveFeeling/order/confirm',
'DES'
).then((res) => {
uni.hideLoading();
if (res.code == 200) {
uni.showToast({
title: '确认收货成功',
icon: 'success'
});
setTimeout(() => {
uni.navigateBack();
}, 800);
} else {
uni.showToast({
title: res.msg || '确认收货失败',
icon: 'none'
});
}
});
},
//
applyAfterSale() {
uni.navigateTo({
url: `/subPackages/haveFeeling/aftersale?orderId=${this.orderId}`
});
}
}
};
</script>
<style lang="scss" scoped>
.logistics-container {
min-height: 100vh;
background-color: #f5f5f5;
padding-bottom: 100rpx;
}
//
.status-header {
background-color: #fff;
padding: 40rpx 30rpx;
margin-bottom: 20rpx;
}
.status-info {
display: flex;
align-items: center;
}
.status-icon {
width: 80rpx;
height: 80rpx;
margin-right: 24rpx;
image {
width: 100%;
height: 100%;
}
}
.status-content {
flex: 1;
}
.status-title {
font-size: 32rpx;
color: #333;
font-weight: 600;
margin-bottom: 8rpx;
}
.status-desc {
font-size: 26rpx;
color: #666;
}
//
.section-title {
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 24rpx;
}
//
.express-section {
background-color: #fff;
padding: 30rpx;
margin-bottom: 20rpx;
}
.express-card {
background-color: #f8f9fa;
border-radius: 12rpx;
padding: 24rpx;
}
.express-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
}
.express-label {
font-size: 26rpx;
color: #666;
}
.express-value {
font-size: 26rpx;
color: #333;
font-weight: 500;
}
//
.address-section {
background-color: #fff;
padding: 30rpx;
margin-bottom: 20rpx;
}
.address-card {
background-color: #f8f9fa;
border-radius: 12rpx;
padding: 24rpx;
}
.recipient-info {
display: flex;
align-items: center;
gap: 20rpx;
margin-bottom: 16rpx;
}
.recipient-name {
font-size: 28rpx;
color: #333;
font-weight: 600;
}
.recipient-phone {
font-size: 26rpx;
color: #666;
}
.address-detail {
font-size: 26rpx;
color: #333;
line-height: 1.5;
}
//
.trace-section {
background-color: #fff;
padding: 30rpx;
margin-bottom: 20rpx;
}
.trace-list {
.trace-item {
display: flex;
padding: 24rpx 0;
border-left: 2rpx solid #e2e8f0;
padding-left: 40rpx;
position: relative;
margin-bottom: 16rpx;
&:before {
content: '';
position: absolute;
left: -8rpx;
top: 30rpx;
width: 14rpx;
height: 14rpx;
border-radius: 50%;
background-color: #e2e8f0;
}
&.is-current {
border-left-color: #007aff;
&:before {
background-color: #007aff;
box-shadow: 0 0 0 4rpx rgba(0, 122, 255, 0.2);
}
.trace-time,
.trace-status {
color: #007aff;
font-weight: 600;
}
}
&:last-child {
border-left-color: transparent;
}
}
}
.trace-time {
font-size: 22rpx;
color: #999;
margin-bottom: 8rpx;
min-width: 120rpx;
flex-shrink: 0;
}
.trace-content {
flex: 1;
margin-left: 20rpx;
}
.trace-status {
font-size: 26rpx;
color: #333;
margin-bottom: 6rpx;
font-weight: 500;
}
.trace-location {
font-size: 22rpx;
color: #666;
margin-bottom: 4rpx;
}
.trace-remark {
font-size: 22rpx;
color: #999;
}
//
.empty-trace {
text-align: center;
padding: 80rpx 40rpx;
}
.empty-image {
width: 200rpx;
height: 200rpx;
margin-bottom: 30rpx;
opacity: 0.6;
}
.empty-text {
font-size: 28rpx;
color: #999;
}
//
.contact-section {
background-color: #fff;
padding: 30rpx;
margin-bottom: 20rpx;
}
.contact-card {
display: flex;
align-items: center;
justify-content: space-between;
background-color: #f8f9fa;
border-radius: 12rpx;
padding: 24rpx;
}
.supplier-info {
flex: 1;
}
.supplier-name {
font-size: 28rpx;
color: #333;
font-weight: 600;
margin-bottom: 8rpx;
}
.supplier-phone {
font-size: 24rpx;
color: #666;
}
.contact-btn {
padding: 12rpx 24rpx;
border-radius: 20rpx;
font-size: 24rpx;
background-color: #007aff;
color: #fff;
border: none;
}
//
.order-actions {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #fff;
padding: 20rpx 30rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
border-top: 1rpx solid #eee;
display: flex;
gap: 16rpx;
}
.action-btn {
flex: 1;
padding: 20rpx 0;
border-radius: 10rpx;
font-size: 28rpx;
font-weight: 600;
border: none;
text-align: center;
&.confirm-btn {
background-color: #48bb78;
color: #fff;
}
&.secondary-btn {
background-color: #f5f5f5;
color: #666;
border: 1rpx solid #ddd;
}
}
</style>

20
subPackages/techan/order.vue

@ -964,6 +964,7 @@ export default {
interval: 2000,
onSuccess: (data) => {
this.isOrderLoading = false; //
this.getTotalPoints();
//
// uni.showToast({
// title: "",
@ -1011,23 +1012,24 @@ export default {
orderNo:token,
fromType:1,
payAmount:this.allprice,
userId:124
},
"/framework/wxPay/submitShopPurOrder",
"DES"
).then(res =>{
uni.requestPayment({
nonceStr: res.data.nonceStr,
package: res.data.package,
paySign: res.data.paySign,
signType: res.data.signType,
timeStamp: res.data.timeStamp,
nonceStr: res.data.wxInfo.nonceStr,
package: res.data.wxInfo.package,
paySign: res.data.wxInfo.paySign,
signType: res.data.wxInfo.signType,
timeStamp: res.data.wxInfo.timeStamp,
success: () => {
this.getSubscribeMessage()
uni.redirectTo({
url:'/subPackages/haveFeeling/list'
})
},
fail() {
uni.navigateTo({
url: '/subPackages/order/trades'
uni.redirectTo({
url:'/subPackages/haveFeeling/list'
})
}
});

Loading…
Cancel
Save