This commit is contained in:
zpc 2025-06-24 00:34:39 +08:00
parent 3d44b8af3f
commit e544788855
23 changed files with 2575 additions and 273 deletions

View File

@ -8,5 +8,20 @@ class AppPlatform extends BasePlatform {
this.code = 'APP_ANDROID';
this.env = 'app';
}
async pay(data, event) {
console.log('pay', data, event);
return new Promise((resolve, reject) => {
uni.requestPayment({
provider: 'alipay',
orderInfo: data,
success: (res) => {
resolve({ isPay: true });
},
fail: (err) => {
resolve({ isPay: false });
}
});
});
}
}
export default AppPlatform;

View File

@ -50,9 +50,7 @@ class BasePlatform {
* @param {number} amount - 支付金额
* @param {string} orderId - 订单号
*/
pay({
data
}, event) {
async pay(data, event) {
throw new Error('子类必须实现 pay 方法');
}

184
common/server/order.js Normal file
View File

@ -0,0 +1,184 @@
import HttpRequest from "../system/request";
/**
* 立即购买
* @param {Number} product_id 商品id
* @returns {Promise} 立即购买
*/
export const pay_by_order = async (product_id) => {
const res = await HttpRequest.post('pay_by_order', {
product_id: product_id
});
if (res.status == 1) {
return res.data;
}
uni.showToast({
title: res.msg,
icon: 'none'
});
return null;
}
/**
* 创建订单
* @param {Number} product_id 商品id
* @param {Number} address_id 地址id
* @param {Number} coupon_id 优惠券id
* @returns {Promise} 创建订单
*/
export const pay_by_create_order = async (product_id, address_id, coupon_id) => {
const res = await HttpRequest.post('pay_by_create_order', {
product_id: product_id,
address_id: address_id,
coupon_id: coupon_id
});
if (res.status == 1) {
return res.data;
}
return null;
}
/**
* 支付成功
* @param {String} order_no 订单号
* @returns {Promise} 支付成功
*/
export const pay_by_pay = async (order_no) => {
const res = await HttpRequest.post('order_pay_success', {
order_no: order_no
});
if (res.status == 1) {
return res.data;
}
return null;
}
/**
* 获取订单列表
* @param {Number} status 订单状态
* @param {Number} page 页码
* @param {Number} page_size 每页条数
* @param {String} title 商品标题搜索关键词
* @returns {Promise} 获取订单列表
*/
export const get_order_list = async (status, page, page_size, title = "") => {
// 接收参数
const res = await HttpRequest.post('get_order_list', {
status: status,
keyword: title,
page: page,
limit: page_size
});
return res.data;
}
/**
* 订单收货
* @param {String} order_no 订单号
* @returns {Promise} 订单收货结果
*/
export const order_receive = async (order_no) => {
const res = await HttpRequest.post('order_receive', {
order_no: order_no
});
if (res.status == 1) {
return res.data;
}
return null;
}
/**
* 申请售后
* @param {String} order_no 订单号
* @param {String} extend_info 扩展信息
* @returns {Promise} 申请售后结果
*/
export const apply_refund = async (order_no, extend_info = "") => {
const res = await HttpRequest.post('apply_refund', {
order_no: order_no,
extend_info: extend_info
});
if (res.status == 1) {
return res.data;
}
return null;
}
/**
* 取消售后
* @param {String} order_no 订单号
* @returns {Promise} 取消售后结果
*/
export const cancel_refund = async (order_no) => {
const res = await HttpRequest.post('cancel_refund', {
order_no: order_no
});
if (res.status == 1) {
return res.data;
}
return null;
}
/**
* 删除订单
* @param {String} order_no 订单号
* @returns {Promise} 删除订单结果
*/
export const delete_order = async (order_no) => {
const res = await HttpRequest.post('delete_order', {
order_no: order_no
});
if (res.status == 1) {
return res.data;
}
return null;
}
/**
* 获取订单详情
* @param {String} order_no 订单号
* @returns {Promise} 获取订单详情
*/
export const get_order_detail = async (order_no) => {
const res = await HttpRequest.post('get_order_detail', {
order_no: order_no
});
if (res.status == 1) {
return res.data;
}
return null;
}
/**
* 申请发票
* @param {String} order_no 订单号
* @param {String} invoice_title 发票抬头
* @param {String} invoice_content 发票内容
* @param {Number} invoice_type 发票类型
* @param {String} user_email 用户邮箱
* @returns {Promise} 申请发票结果
*/
export const apply_invoice = async (order_no, invoice_title, invoice_content, invoice_type, user_email) => {
const res = await HttpRequest.post('apply_invoice', {
order_no: order_no,
invoice_title: invoice_title,
invoice_content: invoice_content,
invoice_type: invoice_type,
user_email: user_email
});
if (res.status == 1) {
return true;
}
return false;
}
/**
* 获取订单统计
* @returns {Promise} 获取订单统计
*/
export const get_order_statistics = async () => {
const res = await HttpRequest.post('get_order_statistics');
if (res.status == 1) {
return res.data;
}
return null;
}

View File

@ -22,6 +22,8 @@ export const getAgreement = async (type) => {
type_id = 4;
} else if (type == "privacy") {
type_id = 5;
} else if (type == "product") {
type_id = 32;
}
const res = await HttpRequest.get('/getAgreement', {
type: type_id

View File

@ -43,3 +43,72 @@ export const getProductDetail = async (id) => {
}
return null;
}
/**
* 获取用户收藏商品列表
* @param {Number} page 页码
* @param {Number} limit 每页条数
* @returns {Promise} 收藏商品列表
*/
export const getFavoriteList = async (page = 1, limit = 10) => {
const res = await HttpRequest.get('/get_favorite_list', {
page: page,
limit: limit
});
if (res.status == 1) {
return res.data;
}
return [];
}
/**
* 添加商品收藏
* @param {Number} product_id 商品ID
* @returns {Promise} 收藏结果
*/
export const addFavorite = async (product_id) => {
const res = await HttpRequest.post('/add_favorite', {
product_id: product_id
});
return res.status == 1;
}
/**
* 取消商品收藏
* @param {Number} product_id 商品ID
* @returns {Promise} 取消收藏结果
*/
export const cancelFavorite = async (product_id) => {
const res = await HttpRequest.post('/cancel_favorite', {
product_id: product_id
});
return res.status == 1;
}
/**
* 批量取消商品收藏
* @param {Array} product_ids 商品ID数组
* @returns {Promise} 批量取消收藏结果
*/
export const batchCancelFavorite = async (product_ids) => {
const res = await HttpRequest.post('/batch_cancel_favorite', {
product_ids: product_ids
});
return res.status == 1;
}
/**
* 检查商品是否已收藏
* @param {Number} product_id 商品ID
* @returns {Promise} 是否已收藏
*/
export const checkIsFavorite = async (product_id) => {
const res = await HttpRequest.get('/check_is_favorite', {
product_id: product_id
});
if (res.status == 1) {
return res.data.is_favorite;
}
return false;
}

2
components.d.ts vendored
View File

@ -14,9 +14,11 @@ declare module 'vue' {
OrderListItem: typeof import('./components/youdas-container/order-list-item.vue')['default']
PageBaseContainer: typeof import('./components/youdas-container/page-base-container.vue')['default']
PageContainer: typeof import('./components/youdas-container/page-container.vue')['default']
PageKefu: typeof import('./components/youdas-container/page-kefu.vue')['default']
PageLine: typeof import('./components/youdas-container/page-line.vue')['default']
PageNoContainer: typeof import('./components/youdas-container/page-no-container.vue')['default']
PagePopup: typeof import('./components/youdas-container/page-popup.vue')['default']
PaymentPopup: typeof import('./components/youdas-container/payment-popup.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
TextSearch: typeof import('./components/youdas-container/text-search.vue')['default']

View File

@ -4,11 +4,55 @@
<!-- :enable-back-to-top="currentIndex===tabIndex" 在微信小程序上可以多加这一句因为默认是允许点击返回顶部的但是这个页面有多个scroll-view会全部返回顶部所以需要控制是当前index才允许点击返回顶部 -->
<!-- 如果当前页已经加载过数据或者当前切换到的tab是当前页才展示当前页数据懒加载 -->
<z-paging v-if="firstLoaded || isCurrentPage" ref="paging" v-model="dataList" @query="queryList" :fixed="false">
<!-- 搜索框 -->
<text-search v-model="searchKeyword" placeholder="搜索订单" @search="onSearch" @clear="onClear" />
<!-- 如果希望其他view跟着页面滚动可以放在z-paging标签内 -->
<view class="item" v-for="(item, index) in dataList" :key="index" @click="itemClick(item)">
<view class="item-title">{{ item.title }}</view>
<view class="item-detail">{{ item.detail }}</view>
<view class="item-line"></view>
<view class="order-item" v-for="(item, index) in dataList" :key="index" @click="itemClick(item)">
<view class="order-header">
<text class="order-no" @click.stop="copyOrderNo(item.order_no)">订单号{{ item.order_no }}</text>
<text class="order-status">{{ item.order_status_text }}</text>
</view>
<view class="order-content">
<image class="product-cover" :src="item.product_cover" mode="aspectFill"></image>
<view class="product-info">
<text class="product-title">{{ item.product_title }}</text>
<text class="payment-amount">{{ item.payment_amount }}</text>
</view>
</view>
<view class="order-footer">
<text class="order-time">{{ item.payment_time }}</text>
<view class="order-actions">
<!-- 已发货状态 -->
<template v-if="item.order_status === 2">
<view class="btn default" @click.stop="applyAfterSale(item)">申请售后</view>
<view class="btn primary" @click.stop="confirmReceive(item)">确认收货</view>
</template>
<!-- 已收货状态 -->
<template v-else-if="item.order_status === 3">
<view class="btn default" v-if="item.is_invoice !== 1" @click.stop="applyInvoice(item)">申请发票
</view>
<view class="btn default" v-else>已申请发票</view>
<view class="btn default"
v-if="item.is_invoice !== 1 && isWithinSevenDays(item.receive_time)"
@click.stop="applyAfterSale(item)">
申请售后</view>
<view class="btn default" @click.stop="deleteOrder(item)">删除订单</view>
<view class="btn default" @click.stop="buyAgain(item)">再次购买</view>
</template>
<!-- 已取消状态 -->
<template v-else-if="item.order_status === 5">
<view class="btn default" @click.stop="buyAgain(item)">再次购买</view>
</template>
<!-- 申请售后状态 -->
<template v-else-if="item.order_status === 6">
<view class="btn default" @click.stop="cancelAfterSale(item)">取消售后</view>
</template>
</view>
</view>
</view>
<template #empty>
@ -18,12 +62,128 @@
<loading-data />
</template>
</z-paging>
<!-- 申请发票弹窗 -->
<view class="invoice-popup" v-if="showInvoicePopup">
<view class="popup-mask" @click="closeInvoicePopup"></view>
<view class="popup-content">
<view class="popup-header">
<text class="popup-title">申请发票</text>
<text class="popup-close" @click="closeInvoicePopup">×</text>
</view>
<view class="product-info-box">
<image class="product-popup-cover" :src="currentProduct.product_cover" mode="aspectFill"></image>
<view class="product-popup-info">
<text class="product-popup-title">{{ currentProduct.product_title }}</text>
<text class="product-popup-price">{{ currentProduct.payment_amount }}</text>
</view>
</view>
<view class="form-item">
<view class="form-label">发票类型</view>
<view class="form-content radio-group">
<view v-for="(type, index) in invoiceTypes" :key="index" class="radio-item"
:class="{ active: invoiceForm.invoice_type === type.value }"
@click="invoiceForm.invoice_type = type.value">
<text>{{ type.label }}</text>
</view>
</view>
</view>
<view class="form-item">
<view class="form-label">发票抬头<text class="required">*</text></view>
<view class="form-content">
<input type="text" class="input" v-model="invoiceForm.invoice_title" placeholder="请输入发票抬头" />
</view>
</view>
<view class="form-item">
<view class="form-label">发票内容<text class="required">*</text></view>
<view class="form-content">
<input type="text" class="input" v-model="invoiceForm.invoice_content" placeholder="请输入发票内容" />
</view>
</view>
<view class="form-item">
<view class="form-label">申请邮箱<text class="required">*</text></view>
<view class="form-content">
<input type="text" class="input" v-model="invoiceForm.user_email" placeholder="请输入接收发票的邮箱" />
</view>
</view>
<view class="popup-footer">
<view class="btn default" @click="closeInvoicePopup">取消</view>
<view class="btn primary" @click="submitInvoice">提交申请</view>
</view>
</view>
</view>
<!-- 申请售后弹窗 -->
<view class="after-sale-popup" v-if="showAfterSalePopup">
<view class="popup-mask" @click="closeAfterSalePopup"></view>
<view class="popup-content">
<view class="popup-header">
<text class="popup-title">申请售后</text>
<text class="popup-close" @click="closeAfterSalePopup">×</text>
</view>
<view class="product-info-box">
<image class="product-popup-cover" :src="currentProduct.product_cover" mode="aspectFill"></image>
<view class="product-popup-info">
<text class="product-popup-title">{{ currentProduct.product_title }}</text>
<text class="product-popup-price">{{ currentProduct.payment_amount }}</text>
</view>
</view>
<view class="form-item">
<view class="form-label">售后类型</view>
<view class="form-content radio-group">
<view v-for="(type, index) in afterSaleTypes" :key="index" class="radio-item"
:class="{ active: afterSaleForm.type === type.value }"
@click="afterSaleForm.type = type.value">
<text>{{ type.label }}</text>
</view>
</view>
</view>
<view class="form-item">
<view class="form-label">申请原因</view>
<view class="form-content radio-group">
<view v-for="(reason, index) in afterSaleReasons" :key="index" class="radio-item"
:class="{ active: afterSaleForm.reason === reason }" @click="afterSaleForm.reason = reason">
<text>{{ reason }}</text>
</view>
</view>
</view>
<view class="form-item">
<view class="form-label">申请说明<text class="required">*</text></view>
<view class="form-content">
<textarea class="input-textarea" v-model="afterSaleForm.remark" placeholder="请详细描述您的问题" />
</view>
</view>
<view class="form-item">
<view class="form-label">联系电话</view>
<view class="form-content">
<input type="text" class="input" v-model="afterSaleForm.phone" placeholder="请输入联系电话" />
</view>
</view>
<view class="popup-footer">
<view class="btn default" @click="closeAfterSalePopup">取消</view>
<view class="btn primary" @click="submitAfterSale">提交申请</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, watch, nextTick, onMounted } from 'vue';
import { ref, watch, nextTick, onMounted, reactive } from 'vue';
import { order_receive, apply_refund, cancel_refund, delete_order, apply_invoice } from '@/common/server/order';
import TextSearch from '@/components/youdas-container/text-search.vue';
// props
const props = defineProps({
// indexswiper
@ -41,7 +201,7 @@ const props = defineProps({
default: null
}
});
// tabIndex ['', '', '', '', '']
//
// v-model
const dataList = ref([]);
@ -51,6 +211,37 @@ const firstLoaded = ref(false);
const isCurrentPage = ref(false);
// ref
const paging = ref(null);
//
const searchKeyword = ref('');
//
const showAfterSalePopup = ref(false);
const currentProduct = ref({});
const afterSaleTypes = [
{ label: '退货退款', value: 1 },
{ label: '换货', value: 2 },
{ label: '其它', value: 3 }
];
const afterSaleReasons = ['不想要了', '材质与商品不符', '质量问题', '发错货', '其他原因'];
const afterSaleForm = reactive({
type: 1,
reason: '不想要了',
remark: '',
phone: ''
});
//
const showInvoicePopup = ref(false);
const invoiceTypes = [
{ label: '个人', value: 1 },
{ label: '企业', value: 2 }
];
const invoiceForm = reactive({
invoice_type: 1,
invoice_title: '',
invoice_content: '',
user_email: ''
});
// currentIndex
watch(
@ -85,12 +276,15 @@ const queryList = (pageNo, pageSize) => {
const params = {
pageNo: pageNo,
pageSize: pageSize,
type: props.tabIndex
type: props.tabIndex,
keyword: searchKeyword.value //
};
try {
props.responseCallback(params).then(res => {
if (paging.value) {
console.log(res);
paging.value.complete(res.list || []);
firstLoaded.value = true;
}
@ -108,9 +302,275 @@ const queryList = (pageNo, pageSize) => {
}
};
//
const onSearch = (value) => {
searchKeyword.value = value;
//
reload();
};
//
const onClear = () => {
searchKeyword.value = '';
//
reload();
};
//
const itemClick = (item) => {
console.log('点击了', item.title);
yds.navigateTo("/pages/mall/order-detail?order_no=" + item.order_no);
};
const applyInvoice = async (item) => {
currentProduct.value = item;
//
try {
const cachedInvoiceData = uni.getStorageSync('lastInvoiceData');
if (cachedInvoiceData) {
// 使
invoiceForm.invoice_type = cachedInvoiceData.invoice_type || 1;
invoiceForm.invoice_title = cachedInvoiceData.invoice_title || '';
invoiceForm.invoice_content = cachedInvoiceData.invoice_content || '';
invoiceForm.user_email = cachedInvoiceData.user_email || '';
} else {
//
invoiceForm.invoice_type = 1;
invoiceForm.invoice_title = '';
invoiceForm.invoice_content = '';
invoiceForm.user_email = '';
}
} catch (e) {
console.error('获取缓存发票数据失败', e);
//
invoiceForm.invoice_type = 1;
invoiceForm.invoice_title = '';
invoiceForm.invoice_content = '';
invoiceForm.user_email = '';
}
//
showInvoicePopup.value = true;
};
//
const copyOrderNo = (orderNo) => {
uni.setClipboardData({
data: orderNo,
success: function () {
uni.showToast({
title: '订单号已复制',
icon: 'none'
});
}
});
};
//
const showConfirmDialog = (title, content) => {
return new Promise((resolve) => {
uni.showModal({
title: title,
content: content,
confirmText: '取消',
cancelText: '确定',
success: function (res) {
resolve(!res.confirm);
}
});
});
};
// API
const callApiWithLoading = async (apiFunc, successMsg) => {
try {
uni.showLoading({
title: '正在处理'
});
const res = await apiFunc();
uni.hideLoading();
if (res) {
uni.showToast({
title: successMsg,
icon: 'none'
});
//
reload();
return true;
} else {
uni.showToast({
title: '操作失败,请重试',
icon: 'none'
});
return false;
}
} catch (error) {
uni.hideLoading();
uni.showToast({
title: '网络异常,请重试',
icon: 'none'
});
return false;
}
};
//
const confirmReceive = async (item) => {
const confirmed = await showConfirmDialog('确认提示', '您确定要确认收货吗?');
if (confirmed) {
await callApiWithLoading(() => order_receive(item.order_no), '确认收货成功');
}
};
//
const applyAfterSale = (item) => {
currentProduct.value = item;
//
afterSaleForm.type = 1;
afterSaleForm.reason = '不想要了';
afterSaleForm.remark = '';
afterSaleForm.phone = '';
//
showAfterSalePopup.value = true;
};
//
const deleteOrder = async (item) => {
const confirmed = await showConfirmDialog('删除提示', '您确定要删除此订单吗?');
if (confirmed) {
await callApiWithLoading(() => delete_order(item.order_no), '删除成功');
}
};
//
const buyAgain = (item) => {
yds.navigateTo("/pages/mall/product-detail?id=" + item.product_id);
};
// 7
const isWithinSevenDays = (receiveTime) => {
if (!receiveTime) return false;
const receiveDate = new Date(receiveTime);
const currentDate = new Date();
//
const diffTime = currentDate - receiveDate;
//
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
// 7true
return diffDays <= 7;
};
//
const cancelAfterSale = async (item) => {
const confirmed = await showConfirmDialog('取消提示', '您确定要取消售后申请吗?');
if (confirmed) {
await callApiWithLoading(() => cancel_refund(item.order_no), '取消售后成功');
}
};
//
const closeAfterSalePopup = () => {
showAfterSalePopup.value = false;
};
//
const closeInvoicePopup = () => {
showInvoicePopup.value = false;
};
//
const submitInvoice = async () => {
//
if (!invoiceForm.invoice_title) {
uni.showToast({
title: '请填写发票抬头',
icon: 'none'
});
return;
}
if (!invoiceForm.invoice_content) {
uni.showToast({
title: '请填写发票内容',
icon: 'none'
});
return;
}
if (!invoiceForm.user_email) {
uni.showToast({
title: '请填写申请邮箱',
icon: 'none'
});
return;
}
//
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(invoiceForm.user_email)) {
uni.showToast({
title: '请输入正确的邮箱格式',
icon: 'none'
});
return;
}
const result = await callApiWithLoading(
() => apply_invoice(
currentProduct.value.order_no,
invoiceForm.invoice_title,
invoiceForm.invoice_content,
invoiceForm.invoice_type,
invoiceForm.user_email
),
'申请发票成功,发票抬头文件会在48小时内发送到您的邮箱。'
);
//
if (result) {
try {
// 使
uni.setStorageSync('lastInvoiceData', {
invoice_type: invoiceForm.invoice_type,
invoice_title: invoiceForm.invoice_title,
invoice_content: invoiceForm.invoice_content,
user_email: invoiceForm.user_email
});
} catch (e) {
console.error('保存发票数据到缓存失败', e);
}
}
//
closeInvoicePopup();
};
//
const submitAfterSale = async () => {
//
if (!afterSaleForm.remark) {
uni.showToast({
title: '请填写申请说明',
icon: 'none'
});
return;
}
//
const extendInfo = JSON.stringify({
type: afterSaleForm.type,
reason: afterSaleForm.reason,
remark: afterSaleForm.remark,
phone: afterSaleForm.phone
});
await callApiWithLoading(
() => apply_refund(currentProduct.value.order_no, extendInfo),
'申请售后成功客服会在24小时内联系您处理。'
);
//
closeAfterSalePopup();
};
//
@ -119,35 +579,298 @@ defineExpose({
});
</script>
<style>
<style lang="scss" scoped>
/* 注意:父节点需要固定高度z-paging的height:100%才会生效 */
.content {
height: 100%;
}
.item {
.order-item {
background-color: #ffffff;
border-radius: 12rpx;
margin: 20rpx 30rpx;
padding: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.order-header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 20rpx;
border-bottom: 1px solid #f5f5f5;
}
.order-no {
font-size: 24rpx;
color: #999999;
position: relative;
height: 150rpx;
&::after {
content: "";
position: absolute;
left: 0;
bottom: -2rpx;
width: 100%;
height: 1rpx;
background-color: #999999;
opacity: 0.5;
}
}
.order-status {
font-size: 28rpx;
color: #ff6700;
font-weight: 500;
}
.order-content {
display: flex;
padding: 20rpx 0;
}
.product-cover {
width: 160rpx;
height: 160rpx;
border-radius: 8rpx;
}
.product-info {
flex: 1;
padding-left: 20rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.product-title {
font-size: 28rpx;
color: #333333;
font-weight: 500;
line-height: 40rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.payment-amount {
font-size: 32rpx;
color: #ff6700;
font-weight: 500;
}
.order-footer {
display: flex;
flex-direction: column;
padding-top: 15rpx;
border-top: 1px solid #f5f5f5;
}
.order-time {
font-size: 24rpx;
color: #999999;
}
.order-actions {
display: flex;
justify-content: flex-end;
margin-top: 20rpx;
flex-wrap: wrap;
}
/* 按钮样式 */
.btn {
display: inline-block;
font-size: 24rpx;
padding: 10rpx 24rpx;
margin-left: 16rpx;
border-radius: 8rpx;
text-align: center;
transition: all 0.15s ease;
-webkit-tap-highlight-color: transparent;
&:active {
transform: scale(0.95);
}
&.default {
background-color: #ffffff;
color: #333333;
border: 1px solid #dddddd;
}
&.primary {
background-color: #e94e42;
color: #ffffff;
border: 1px solid #e94e42;
}
}
/* 售后弹窗样式 */
.after-sale-popup,
.invoice-popup {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0rpx 30rpx;
justify-content: center;
}
.item-detail {
padding: 5rpx 15rpx;
border-radius: 10rpx;
font-size: 28rpx;
color: white;
background-color: #007AFF;
}
.item-line {
.popup-mask {
position: absolute;
bottom: 0rpx;
left: 0rpx;
height: 1px;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
}
.popup-content {
position: relative;
width: 92%;
max-height: 90vh;
background-color: #fff;
border-radius: 12rpx;
padding: 30rpx;
box-sizing: border-box;
overflow-y: auto;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
}
.popup-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.popup-close {
font-size: 40rpx;
color: #999;
padding: 0 10rpx;
}
.product-info-box {
display: flex;
padding: 20rpx;
background-color: #f8f8f8;
border-radius: 8rpx;
margin-bottom: 20rpx;
}
.product-popup-cover {
width: 120rpx;
height: 120rpx;
border-radius: 8rpx;
}
.product-popup-info {
flex: 1;
padding-left: 20rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.product-popup-title {
font-size: 26rpx;
color: #333;
line-height: 36rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.product-popup-price {
font-size: 28rpx;
color: #ff6700;
font-weight: 500;
}
.form-item {
margin-bottom: 20rpx;
}
.form-label {
font-size: 28rpx;
color: #333;
margin-bottom: 10rpx;
.required {
color: #ff4d4f;
margin-left: 4rpx;
}
}
.form-content {
width: 100%;
background-color: #eeeeee;
}
.radio-group {
display: flex;
flex-wrap: wrap;
margin: 0 -10rpx;
}
.radio-item {
margin: 10rpx;
padding: 10rpx 20rpx;
border: 1px solid #ddd;
border-radius: 8rpx;
font-size: 26rpx;
color: #666;
&.active {
border-color: #ff6700;
color: #ff6700;
background-color: rgba(255, 103, 0, 0.05);
}
}
.input {
width: 100%;
height: 80rpx;
border: 1px solid #ddd;
border-radius: 8rpx;
padding: 0 20rpx;
font-size: 28rpx;
box-sizing: border-box;
}
.input-textarea {
width: 100%;
height: 180rpx;
border: 1px solid #ddd;
border-radius: 8rpx;
padding: 20rpx;
font-size: 28rpx;
box-sizing: border-box;
}
.popup-footer {
display: flex;
justify-content: flex-end;
margin-top: 30rpx;
.btn {
margin-left: 20rpx;
min-width: 160rpx;
height: 70rpx;
line-height: 70rpx;
font-size: 28rpx;
}
}
</style>

View File

@ -0,0 +1,88 @@
<template>
<!-- 群聊弹窗 -->
<view v-if="isShow" class="popup-mask" @click.stop>
<view class="popup-content">
<view class="pop-ball">
<image show-menu-by-longpress
src="https://image.zfunbox.cn/topic/20250418/0b40de65d2fc4801517569193bfd4cac.png" mode="aspectFit"
style="width:80vw;height:468px;position:relative;top:-14%;left:0;">
</image>
</view>
<view class="pop-ball-close" @click="close">
<view style="width: 48rpx;height: 48rpx;border-radius: 50%;opacity: 0.5;">
<image show-menu-by-longpress src="/static/ic_close.png" style="width: 48rpx;height: 48rpx;" />
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
const isShow = ref(false);
const open = () => {
isShow.value = true;
};
const close = () => {
isShow.value = false;
};
defineExpose({
open,
close
});
</script>
<style lang="scss" scoped>
.popup-mask {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0,0,0,0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
}
.popup-content {
display: flex;
flex-direction: column;
align-items: center;
animation: popup-in 0.2s ease-out;
}
@keyframes popup-in {
from {
transform: scale(0.8);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}
.pop-ball {
width: 85vw;
position: relative;
border-radius: 25rpx;
display: flex;
align-items: center;
justify-content: center;
}
.pop-ball-close {
margin-top: 20rpx;
width: 100%;
height: 50rpx;
display: flex;
align-items: center;
justify-content: center;
}
</style>

View File

@ -0,0 +1,244 @@
<template>
<uni-popup ref="popup" type="bottom">
<view
style="width: 100%; height: 965.65rpx; background-color: #F7F7F7; border-radius: 15.27rpx 15.27rpx 0rpx 0rpx;">
<view class="" style="width: 688.93rpx; height: 100%; margin: 0 auto;">
<view class=""
style="width: 100%; height: 30rpx; display: flex; flex-direction: row; align-items: center; justify-content: space-between; padding-top: 44rpx;">
<view class="" style="width: 24.81rpx; height: 24.81rpx;"></view>
<text style="font-size: 26.72rpx;">确认订单</text>
<image src="/static/ic_close.png" style="width: 24.81rpx; height: 24.81rpx;" @click="close" mode="">
</image>
</view>
<view class=""
style="width: 100%; height: 227.1rpx; background-color: white; position: relative; margin-top: 42rpx; border-radius: 15.27rpx;">
<image :src="product.image"
style="width: 180.12rpx; height: 180.12rpx; background-color: #D8D8D8; border-radius: 15.27rpx; position: absolute; left: 24.81rpx; top: 24.81rpx;"
mode=""></image>
<text style="font-size: 22.9rpx; position: absolute; left: 238.55rpx; top: 41.98rpx;">{{
product.title }}</text>
<text
style="font-size: 19.08rpx; position: absolute; left: 238.55rpx; top: 91.6rpx; color: #999999;">类型实物</text>
<view class=""
style="position: absolute; display: flex; flex-direction: row; left: 238.55rpx; bottom: 44rpx; align-items: center;">
<text class=" " style="font-size: 19.08rpx; margin-top: 15rpx; color: #FF6A6A;"></text>
<text class=" " style="font-size: 41.98rpx; font-weight: bold; color: #FF6A6A;">{{
product.price }}</text>
</view>
<text
style="position: absolute; right: 24.81rpx; bottom: 41.98rpx; font-size: 19.08rpx; color: #999999;">x1</text>
</view>
<view class="" @click="toAddress"
style="width: 100%; height: 82.06rpx; background-color: white; border-radius: 15.27rpx; margin-top: 28.63rpx; display: flex; align-items: center; position: relative;">
<text style="left: 24.81rpx; font-size: 22.9rpx; position: absolute;">收货地址</text>
<text style="font-size: 22.9rpx; position: absolute; right:50rpx;">{{ address.receiver_name || '未设置'
}}</text>
<image src="/static/ic_arrow.png" class="arrow-icon"></image>
</view>
<view class="" @click="toCoupon"
style="width: 100%; height: 82.06rpx; background-color: white; border-radius: 15.27rpx; margin-top: 28.63rpx; display: flex; align-items: center; position: relative;">
<text style="left: 24.81rpx; font-size: 22.9rpx; position: absolute;">优惠券</text>
<text style="right: 45.8rpx; font-size: 19.08rpx; position: absolute; color: #999999;">未选择</text>
<image src="/static/ic_arrow.png" class="arrow-icon"></image>
</view>
<view class="" @click="toAgreement('product')"
style="width: 100%; height: 82.06rpx; background-color: #F9F8E1; border-radius: 15.27rpx; margin-top: 28.63rpx; display: flex; align-items: center; position: relative;">
<text style="left: 24.81rpx; font-size: 22.9rpx; position: absolute;">售前·售后须知</text>
<image src="/static/ic_arrow.png" class="arrow-icon"></image>
</view>
<view class=""
style="width: 100%; display: flex; flex-direction: row; align-items: center; justify-content: left; margin-top: 45.8rpx;">
<image :src="isCheck ? '/static/ic_check_s.png' : '/static/ic_check.png'" @click="setCheck"
style="width: 24.02rpx; height: 24.02rpx;" mode=""></image>
<text style="font-size: 19.08rpx; color: #999999; margin-left: 11.45rpx;">我已满18岁阅读并同意
<text style="color: #333333;" @click="toAgreement('user')">用户协议</text>
<text style="color: #333333;" @click="toAgreement('privacy')">隐私政策</text>
</text>
</view>
<view class="" @click="confirmPayment"
style="width: 100%; height: 83.97rpx; background-color: #333333; border-radius: 15.27rpx; display: flex; align-items: center; justify-content: center; margin-top: 30.53rpx;">
<text style="font-size: 22.9rpx; color: #CDEF27;">确认支付</text>
</view>
</view>
</view>
</uni-popup>
</template>
<script setup>
import { ref } from 'vue';
import { pay_by_order, pay_by_create_order, pay_by_pay } from '@/common/server/order';
import { platform } from '@/common/platform/PlatformFactory';
const props = defineProps({
onSuccess: {
type: Function,
default: () => { }
}
});
let product = ref(null);
// const emit = defineEmits(['update:address']);
//
const isCheck = ref(true);
let address = ref('');
let coupon = ref('');
const popup = ref(null);
const toAgreement = (type) => {
uni.navigateTo({
url: '/pages/other/agreement?type=' + type,
});
};
//
const open = async (product_id) => {
const res = await pay_by_order(product_id);
console.log(res);
if (res) {
product.value = res;
if (res.default_address != null) {
address.value = res.default_address;
}
popup.value.open();
}
};
const close = () => {
popup.value.close();
};
const setCheck = () => {
isCheck.value = !isCheck.value;
};
const toAddress = () => {
uni.navigateTo({
url: '/pages/address/address-list?selsect=true',
events: {
//
selectAddress: function (data) {
console.log(data);
address.value = data;
}
},
success: function (res) {
// eventChannel
res.eventChannel.emit('acceptDataFromOpenerPage', {
data: address.value.id
})
}
});
};
const toCoupon = () => {
uni.navigateTo({
url: '/pages/other/choose-coupon',
events: {
//
selectCoupon: function (data) {
console.log(data);
coupon.value = data;
}
},
success: function (res) {
// eventChannel
res.eventChannel.emit('acceptDataFromOpenerPage', {
data: coupon.value
})
}
});
};
const confirmPayment = async () => {
if (!isCheck.value) {
uni.showToast({
title: '请先同意用户协议和隐私政策',
icon: 'none'
});
return;
}
if (address.value == null || address.value.id == 0) {
uni.showToast({
title: '请选择收货地址',
icon: 'none'
});
return;
}
//
//
uni.showLoading({
title: '支付处理中'
});
const order_res = await pay_by_create_order(product.value.product_id, address.value.id, coupon.value.id ?? 0);
if (!order_res) {
uni.showToast({
title: '支付失败,请刷新后重试!',
icon: 'none'
});
return;
}
console.log('支付成功', order_res);
let isPaySuccess = true;
if (order_res.user_info.is_test == 2) {
const isTestPay = await uni.showModal({
title: '支付提示',
content: '检测到您是测试账号,是否直接支付完成!取消后将正常拉起支付!(测试账号购买订单将自动标记为“已发货”)',
confirmText: "取消",
cancelText: "确定"
});
if (isTestPay.cancel) {
isPaySuccess = false;
}
}
if (isPaySuccess) {
let orderInfo = order_res.payment_data;
const payRes = await platform.pay(orderInfo, this);
console.log("paypaypaypay", payRes);
if (!payRes.isPay) {
uni.showToast({
title: '支付已取消',
icon: 'none'
});
uni.hideLoading();
return;
}
}
const by_pay_res = await pay_by_pay(order_res.order_no);
console.log("t", by_pay_res);
uni.hideLoading();
if (by_pay_res.pay_status == 2) {
props.onSuccess && props.onSuccess();
}
//
// setTimeout(() => {
// uni.hideLoading();
// props.onSuccess && props.onSuccess();
// close();
// }, 1500);
};
//
defineExpose({
open,
close
});
</script>
<style lang="scss">
.arrow-icon {
width: 10.67rpx;
height: 19.66rpx;
right: 25rpx;
position: absolute;
}
</style>

View File

@ -95,14 +95,33 @@
}
},
{
"path" : "pages/mall/receiving-address",
"style" :
{
"path": "pages/mall/receiving-address",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/other/choose-coupon",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/mall/order-detail",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/other/user-agreement",
"style": {
"navigationStyle": "custom"
}
}
],
"usingComponents": {
"web-view": "pages/other/user-agreement"
},
// "globalStyle": {
// "navigationBarTextStyle": "black",
// "navigationBarTitleText": "uni-app",

View File

@ -1,8 +1,16 @@
<template>
<PageContainer ref="pageContainer" title="我的地址管理" show-back :show-not-data="showNotData" :refresh="onRefresh">
<PageContainer ref="pageContainer" :title="pageTitle" show-back :show-not-data="showNotData" :refresh="onRefresh">
<view class="address-list">
<view v-for="(item, index) in addressList" :key="index" class="address-item">
<view class="address-content">
<view v-if="chooseSelect" class="radio-box" @click.stop="selectAddress(item)">
<view class="radio-circle" :class="{ 'checked': selectedAddressId === item.id }">
<view v-if="selectedAddressId === item.id" class="radio-inner"></view>
</view>
</view>
<view class="address-content" @click.stop="selectAddress(item)">
<view class="address-top">
<text class="name">{{ item.receiver_name }}</text>
<text class="phone">{{ item.receiver_phone }}</text>
@ -12,32 +20,39 @@
<text class="address-text">{{ item.detailed_address }}</text>
</view>
</view>
<view class="edit-btn" @click="editAddress(item)">
<view class="edit-btn" @click.stop="editAddress(item)">
<image src="/static/app-plus/edit.png" mode="widthFix" style="width:32px;height: 32px;"></image>
</view>
<view class="delete-btn" @click="showDeleteConfirm(item)">
<view class="delete-btn" @click.stop="showDeleteConfirm(item)">
<image src="/static/app-plus/del.png" mode="widthFix" style="width: 32px;height: 32px;"></image>
</view>
</view>
<view v-if="addressList.length === 0" class="empty-tip">
<text>暂无收货地址</text>
<view v-if="chooseSelect" class="add-item" @click="addNewAddress">
<view class="add-icon">+</view>
<text class="add-text">新增地址</text>
</view>
</view>
<view class="add-btn" @click="addNewAddress">
<view v-if="!chooseSelect" class="add-btn" @click="addNewAddress">
<text>新增地址</text>
</view>
<view v-if="chooseSelect" class="confirm-btn" @click="confirmAddress">
<text>确定</text>
</view>
</PageContainer>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ref, onMounted, getCurrentInstance } from 'vue';
import { getAddressList, deleteAddress } from '@/common/server/address';
const pageContainer = ref(null);
const showNotData = ref(false);
const addressList = ref([]);
let pageTitle = ref('我的地址管理');
let chooseSelect = ref(false);
const selectedAddressId = ref(null);
//
const onRefresh = async (paging) => {
@ -47,11 +62,22 @@ const onRefresh = async (paging) => {
onShow(() => {
loadAddressList();
});
onLoad((options) => {
if (options.selsect) {
chooseSelect.value = true;
pageTitle.value = '选择收货地址';
}
});
let eventChannel = null;
onMounted(() => {
const instance = getCurrentInstance().proxy
eventChannel = instance.getOpenerEventChannel();
eventChannel.on('acceptDataFromOpenerPage', (data) => {
selectedAddressId.value = data.data;
});
});
// //
// onMounted(() => {
// loadAddressList();
// });
//
const loadAddressList = async () => {
try {
@ -59,6 +85,11 @@ const loadAddressList = async () => {
if (res.status === 1 && res.data) {
addressList.value = res.data;
showNotData.value = addressList.value.length === 0;
// ID
if (chooseSelect.value && addressList.value.length > 0 && selectedAddressId.value == null) {
selectedAddressId.value = addressList.value[0].id;
}
} else {
uni.showToast({
title: res.msg || '获取地址列表失败',
@ -74,6 +105,11 @@ const loadAddressList = async () => {
}
};
//
const selectAddress = (item) => {
selectedAddressId.value = item.id;
};
//
const editAddress = (item) => {
uni.navigateTo({
@ -88,6 +124,31 @@ const addNewAddress = () => {
});
};
const confirmAddress = () => {
if (!selectedAddressId.value) {
uni.showToast({
title: '请选择一个地址',
icon: 'none'
});
return;
}
// ID
const selectedAddress = addressList.value.find(item => item.id === selectedAddressId.value);
if (!selectedAddress) {
uni.showToast({
title: '地址数据异常',
icon: 'none'
});
return;
}
if (eventChannel) {
eventChannel.emit('selectAddress', selectedAddress);
}
uni.navigateBack();
}
//
const showDeleteConfirm = (item) => {
uni.showModal({
@ -120,6 +181,11 @@ const deleteAddressItem = async (id) => {
icon: 'success'
});
//
if (selectedAddressId.value === id) {
selectedAddressId.value = null;
}
//
loadAddressList();
} else {
@ -162,7 +228,33 @@ const deleteAddressItem = async (id) => {
}
&:last-child {
margin-bottom: 100rpx;
margin-bottom: 20rpx;
}
.radio-box {
margin-right: 20rpx;
padding: 5rpx;
.radio-circle {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
border: 2rpx solid #dddddd;
display: flex;
align-items: center;
justify-content: center;
&.checked {
border-color: #1296db;
}
.radio-inner {
width: 24rpx;
height: 24rpx;
border-radius: 50%;
background-color: #1296db;
}
}
}
.address-content {
@ -228,6 +320,43 @@ const deleteAddressItem = async (id) => {
}
}
.add-item {
display: flex;
align-items: center;
padding: 30rpx;
margin-bottom: 100rpx;
border-radius: 12rpx;
background-color: #fff;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
&:active {
transform: scale(0.98);
box-shadow: 0 1rpx 5rpx rgba(0, 0, 0, 0.03);
}
.add-icon {
width: 48rpx;
height: 48rpx;
border-radius: 50%;
background-color: #1296db;
color: #fff;
font-size: 40rpx;
font-weight: 400;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
line-height: 1;
}
.add-text {
font-size: 32rpx;
color: #333;
font-weight: 500;
}
}
.empty-tip {
text-align: center;
padding: 120rpx 0;
@ -282,4 +411,34 @@ const deleteAddressItem = async (id) => {
font-weight: 400;
}
}
.confirm-btn {
position: fixed;
bottom: 40rpx;
left: 50%;
transform: translateX(-50%);
width: 90%;
height: 90rpx;
background: linear-gradient(to right, #1296db, #0e80c0);
color: #fff;
display: flex;
justify-content: center;
align-items: center;
font-size: 32rpx;
font-weight: 600;
border-radius: 45rpx;
box-shadow: 0 6rpx 16rpx rgba(18, 150, 219, 0.3);
transition: transform 0.2s ease, box-shadow 0.2s ease;
&:active {
transform: translateX(-50%) scale(0.98);
box-shadow: 0 3rpx 8rpx rgba(18, 150, 219, 0.2);
}
&::before {
font-size: 40rpx;
margin-right: 10rpx;
font-weight: 400;
}
}
</style>

View File

@ -27,7 +27,8 @@ onLoad(async () => {
return;
}
navigateTo('/pages/news/news');
// navigateTo('/pages/news/news');
navigateTo('/pages/other/user-agreement?type=user');
});
/**

View File

@ -1,18 +1,182 @@
<template>
<PageContainer ref="pageContainer" title="收藏管理" show-back :show-not-data="showNotData" :refresh="onRefresh">
<view class="collect-container">
<view v-if="favoriteList.length > 0" class="favorite-list">
<view v-for="item in favoriteList" :key="item.id" class="favorite-item">
<image class="product-image" :src="item.image" mode="aspectFill" />
<view class="product-info">
<view class="product-title">{{ item.title }}</view>
<view class="product-price">¥{{ item.price }}</view>
<view class="product-meta">
<text>库存: {{ item.stock }}</text>
</view>
<view class="favorite-time">收藏于: {{ formatDate(item.favorite_time) }}</view>
</view>
<view class="product-actions">
<view class="btn view-btn" @click="viewDetail(item)">查看</view>
<view class="btn cancel-btn" @click="handleCancelFavorite(item)">取消收藏</view>
</view>
</view>
</view>
</view>
</PageContainer>
</template>
<script setup>
import { getFavoriteList, cancelFavorite } from '@/common/server/product';
let pageContainer = ref(null);
let showNotData = ref(false);
let favoriteList = ref([]);
let isLoading = ref(false);
onLoad(async () => {
await loadFavoriteData();
})
//
const loadFavoriteData = async () => {
isLoading.value = true;
try {
const result = await getFavoriteList();
favoriteList.value = result || [];
showNotData.value = favoriteList.value.length === 0;
} catch (error) {
console.error('获取收藏列表失败', error);
showNotData.value = true;
} finally {
isLoading.value = false;
}
};
//
const viewDetail = (item) => {
uni.navigateTo({
url: `/pages/mall/product-detail?id=${item.id}`
});
};
//
const handleCancelFavorite = async (item) => {
try {
// API
await cancelFavorite(item.id);
//
favoriteList.value = favoriteList.value.filter(i => i.id !== item.id);
showNotData.value = favoriteList.value.length === 0;
uni.showToast({
title: '取消收藏成功',
icon: 'success'
});
} catch (error) {
uni.showToast({
title: '操作失败',
icon: 'none'
});
}
};
//
const formatDate = (dateStr) => {
return dateStr ? dateStr.substring(0, 10) : '';
};
//
const onRefresh = async (paging) => {
await loadFavoriteData();
await yds.sleep(500);
paging.complete();
};
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
.collect-container {
padding: 20rpx;
}
.favorite-list {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.favorite-item {
display: flex;
background-color: #fff;
border-radius: 12rpx;
padding: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.product-image {
width: 180rpx;
height: 180rpx;
border-radius: 8rpx;
margin-right: 20rpx;
}
.product-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.product-title {
font-size: 28rpx;
font-weight: bold;
margin-bottom: 10rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.product-price {
font-size: 32rpx;
color: #ff6b00;
font-weight: bold;
margin-bottom: 10rpx;
}
.product-meta {
font-size: 24rpx;
color: #999;
display: flex;
gap: 20rpx;
margin-bottom: 10rpx;
}
.favorite-time {
font-size: 22rpx;
color: #999;
}
.product-actions {
display: flex;
flex-direction: column;
justify-content: center;
gap: 20rpx;
}
.btn {
width: 120rpx;
height: 60rpx;
line-height: 60rpx;
text-align: center;
border-radius: 30rpx;
font-size: 24rpx;
}
.view-btn {
background-color: #f0f0f0;
color: #333;
border: none;
}
.cancel-btn {
background-color: #ff6b00;
color: white;
}
</style>

View File

@ -13,7 +13,7 @@
<!-- 搜索 -->
<text-search v-model="searchKeyword" placeholder="搜索商品" @search="handleSearch" @clear="clearSearch" />
<!-- 顶部三个商品 -->
<swiper v-if="!isSearch" :display-multiple-items="3" class="top" :duration="500">
<swiper v-if="!isSearch && topDataList.length > 0" :display-multiple-items="3" class="top" :duration="500">
<swiper-item v-for="(item, i) in topDataList" :key="i" @click="goToDetail(item)">
<view class="item">
<view class="img">

323
pages/mall/order-detail.vue Normal file
View File

@ -0,0 +1,323 @@
<template>
<PageContainer ref="pageContainer" title="订单详情" show-back :show-not-data="showNotData" :refresh="onRefresh">
<template v-if="orderDetail">
<!-- 订单状态 -->
<view class="status-card" :class="orderStatusClass">
<view class="status-text">{{ orderDetail.order_status_text }}</view>
</view>
<!-- 商品信息 -->
<view class="info-card">
<view class="card-title">商品信息</view>
<view class="product-item" @click.stop="toProductDetail(orderDetail.product_id)">
<image class="product-image" :src="orderDetail.product_cover" mode="aspectFill"></image>
<view class="product-info">
<view class="product-title">{{ orderDetail.product_title }}</view>
<view class="product-price">¥{{ orderDetail.product_price }}</view>
</view>
</view>
</view>
<!-- 订单信息 -->
<view class="info-card">
<view class="card-title">订单信息</view>
<view class="info-item">
<text class="label">订单编号</text>
<text class="value">{{ orderDetail.order_no }}</text>
</view>
<view class="info-item">
<text class="label">创建时间</text>
<text class="value">{{ orderDetail.create_time }}</text>
</view>
<view class="info-item">
<text class="label">支付方式</text>
<text class="value">{{ orderDetail.payment_type === 1 ? '微信支付' : '其他' }}</text>
</view>
<view class="info-item">
<text class="label">支付时间</text>
<text class="value">{{ orderDetail.payment_time || '未支付' }}</text>
</view>
</view>
<!-- 配送信息 -->
<view class="info-card" v-if="orderDetail.order_status >= 2">
<view class="card-title">配送信息</view>
<view class="info-item">
<text class="label">快递公司</text>
<text class="value">{{ orderDetail.shipping_company }}</text>
</view>
<view class="info-item">
<text class="label">快递单号</text>
<text class="value">{{ orderDetail.shipping_no }}</text>
</view>
<view class="info-item">
<text class="label">发货时间</text>
<text class="value">{{ orderDetail.shipping_time || '未发货' }}</text>
</view>
<view class="info-item">
<text class="label">收货时间</text>
<text class="value">{{ orderDetail.receive_time || '未收货' }}</text>
</view>
</view>
<!-- 收货人信息 -->
<view class="info-card">
<view class="card-title">收货人信息</view>
<view class="info-item">
<text class="label">收货人</text>
<text class="value">{{ orderDetail.receiver_name }}</text>
</view>
<view class="info-item">
<text class="label">联系电话</text>
<text class="value">{{ orderDetail.receiver_phone }}</text>
</view>
<view class="info-item">
<text class="label">收货地址</text>
<text class="value">{{ orderDetail.receiver_address }}</text>
</view>
</view>
<!-- 发票信息 -->
<view class="info-card" v-if="orderDetail.invoice_title || orderDetail.invoice_content">
<view class="card-title">发票信息</view>
<view class="info-item" v-if="orderDetail.invoice_title">
<text class="label">发票抬头</text>
<text class="value">{{ orderDetail.invoice_title }}</text>
</view>
<view class="info-item" v-if="orderDetail.invoice_content">
<text class="label">发票内容</text>
<text class="value">{{ orderDetail.invoice_content }}</text>
</view>
<view class="info-item" v-if="orderDetail.invoice_type_text">
<text class="label">发票类型</text>
<text class="value">{{ orderDetail.invoice_type_text }}</text>
</view>
</view>
<!-- 金额信息 -->
<view class="price-card">
<view class="price-item">
<text>商品金额</text>
<text>¥{{ orderDetail.product_price }}</text>
</view>
<view class="price-item">
<text>运费</text>
<text>¥{{ orderDetail.shipping_fee }}</text>
</view>
<view class="price-item" v-if="Number(orderDetail.discount_amount) > 0">
<text>优惠金额</text>
<text>-¥{{ orderDetail.discount_amount }}</text>
</view>
<view class="price-item total">
<text>实付款</text>
<text class="total-price">¥{{ orderDetail.payment_amount }}</text>
</view>
</view>
</template>
</PageContainer>
</template>
<script setup>
import { get_order_detail } from '@/common/server/order';
import { computed } from 'vue';
let pageContainer = ref(null);
let showNotData = ref(false);
let order_no = ref(null);
let orderDetail = ref(null);
onLoad((options) => {
console.log(options);
if (options.order_no) {
order_no.value = options.order_no;
queryOrderDetail();
}
});
const toProductDetail = (product_id) => {
yds.navigateTo("/pages/mall/product-detail?id=" + product_id);
};
const queryOrderDetail = async () => {
const res = await get_order_detail(order_no.value);
console.log(res);
if (res) {
orderDetail.value = res;
} else {
showNotData.value = true;
}
};
//
const orderStatusClass = computed(() => {
if (!orderDetail.value) return '';
const status = orderDetail.value.order_status;
//
switch (status) {
case 0: // /
return 'status-unpaid';
case 1: // /
return 'status-paid';
case 2: // /
return 'status-shipped';
case 3: //
return 'status-completed';
case 4: //
return 'status-cancelled';
case 5: // 退
case 6:
case 7:
return 'status-refunded';
default:
return '';
}
});
//
const onRefresh = async (paging) => {
await yds.sleep(500);
await queryOrderDetail();
paging.complete();
};
</script>
<style lang="scss" scoped>
.status-card {
padding: 40rpx 30rpx;
.status-text {
color: #fff;
font-size: 32rpx;
font-weight: 500;
}
//
background-color: #4080ff;
// /
&.status-unpaid {
background-color: #ff9500;
}
// /
&.status-paid {
background-color: #4080ff;
}
// /绿
&.status-shipped {
background-color: #10b981;
}
// 绿
&.status-completed {
background-color: #52c41a;
}
//
&.status-cancelled {
background-color: #909399;
}
// 退
&.status-refunded {
background-color: #f56c6c;
}
}
.info-card {
margin: 20rpx;
border-radius: 12rpx;
background-color: #fff;
padding: 20rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
.card-title {
font-size: 28rpx;
font-weight: 500;
color: #333;
margin-bottom: 20rpx;
border-bottom: 1rpx solid #f5f5f5;
padding-bottom: 10rpx;
}
.info-item {
display: flex;
justify-content: space-between;
padding: 12rpx 0;
font-size: 26rpx;
.label {
color: #666;
}
.value {
color: #333;
max-width: 65%;
text-align: right;
}
}
}
.product-item {
display: flex;
padding: 20rpx 0;
.product-image {
width: 160rpx;
height: 160rpx;
border-radius: 8rpx;
background-color: #f5f5f5;
}
.product-info {
flex: 1;
margin-left: 20rpx;
.product-title {
font-size: 28rpx;
color: #333;
line-height: 1.4;
margin-bottom: 10rpx;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.product-price {
font-size: 30rpx;
color: #ff4d4f;
font-weight: 500;
}
}
}
.price-card {
margin: 20rpx;
border-radius: 12rpx;
background-color: #fff;
padding: 20rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
.price-item {
display: flex;
justify-content: space-between;
padding: 12rpx 0;
font-size: 26rpx;
color: #666;
&.total {
margin-top: 10rpx;
padding-top: 15rpx;
border-top: 1rpx solid #f5f5f5;
font-weight: 500;
color: #333;
.total-price {
color: #ff4d4f;
font-size: 32rpx;
}
}
}
}
</style>

View File

@ -24,11 +24,12 @@
<script setup>
import { ref } from 'vue';
import { get_order_list } from '@/common/server/order';
const paging = ref(null);
const tabs = ref(null);
const current = ref(0);
const tabList = ref(['全部', '待发货', '待收货', '评价', '售后']);
const tabList = ref(['全部', '待发货', '待收货', '已收货', '售后']);
onLoad((options) => {
console.log(options);
@ -52,17 +53,18 @@ const swiperAnimationfinish = (e) => {
tabs.value.unlockDx();
}
const responseCallback = (params) => {
return new Promise((resolve, reject) => {
resolve({ list: [] });
return new Promise(async (resolve, reject) => {
let title = params.keyword ?? "";
const res = await get_order_list(params.type, params.pageNo, params.pageSize, title);
resolve({ list: res.list || [] });
});
}
</script>
<style lang="scss" scoped>
.swiper {
height:90vh;
height: 90vh;
}
.swiper-item {
}
.swiper-item {}
</style>

View File

@ -5,12 +5,16 @@
<image src="/static/ic_back.png" @click="toBack()"
style="width: 41.98rpx; height: 41.98rpx; position: fixed; left: 30rpx; top: 137rpx;z-index: 1000;" mode="">
</image>
<image :src="favorite_src" @click.stop="favoriteAction()"
style="width:64rpx; height: 64rpx;position: fixed;top: 137rpx;right: 30rpx;z-index: 1000;"></image>
<swiper v-if="productDetail" :duration="500" circular :autoplay="true" :interval="3000"
style="width: 100%; height: 715.65rpx; background-color: #D8D8D8;">
<swiper-item v-for="(item, i) in productDetail.detail_image" :key="i">
<image :src="item" style="width: 100%; height: 100%;"></image>
</swiper-item>
</swiper>
<view v-if="productDetail" class=""
style="width: 100%; height: 244.27rpx; background-color: white; position: relative;">
@ -39,6 +43,8 @@
<rich-text :nodes="productDetail.detail_html"></rich-text>
</view>
<view class="" style="width: 100%; height:400rpx; background-color: white;"></view>
<view class="" style="width: 100%; height: 188.94rpx; position: fixed; bottom: 0; background-color: white;">
<view class="" @click="bayOpen"
@ -47,135 +53,63 @@
</view>
</view>
<uni-popup ref="bayPop" type="bottom">
<view
style="width: 100%; height: 965.65rpx; background-color: #F7F7F7; border-radius: 15.27rpx 15.27rpx 0rpx 0rpx;">
<view class="" style="width: 688.93rpx; height: 100%; margin: 0 auto;">
<view class=""
style="width: 100%; height: 30rpx; display: flex; flex-direction: row; align-items: center; justify-content: space-between; padding-top: 44rpx;">
<view class="" style="width: 24.81rpx; height: 24.81rpx;"></view>
<text style="font-size: 26.72rpx;">确认订单</text>
<image src="/static/ic_close.png" style="width: 24.81rpx; height: 24.81rpx;" @click="closePop"
mode="">
</image>
</view>
<view class=""
style="width: 100%; height: 227.1rpx; background-color: white; position: relative; margin-top: 42rpx; border-radius: 15.27rpx;">
<image :src="productDetail.image"
style="width: 180.12rpx; height: 180.12rpx; background-color: #D8D8D8; border-radius: 15.27rpx; position: absolute; left: 24.81rpx; top: 24.81rpx;"
mode=""></image>
<text
style="font-size: 22.9rpx; position: absolute; left: 238.55rpx; top: 41.98rpx;">{{ productDetail.title }}</text>
<text
style="font-size: 19.08rpx; position: absolute; left: 238.55rpx; top: 91.6rpx; color: #999999;">类型明信片</text>
<view class=""
style="position: absolute; display: flex; flex-direction: row; left: 238.55rpx; bottom: 44rpx; align-items: center;">
<text class=" " style="font-size: 19.08rpx; margin-top: 15rpx; color: #FF6A6A;"></text>
<text class=" "
style="font-size: 41.98rpx; font-weight: bold; color: #FF6A6A;">{{ productDetail.price }}</text>
</view>
<text
style="position: absolute; right: 24.81rpx; bottom: 41.98rpx; font-size: 19.08rpx; color: #999999;">x1</text>
</view>
<view class="" @click="toAddress"
style="width: 100%; height: 82.06rpx; background-color: white; border-radius: 15.27rpx; margin-top: 28.63rpx; display: flex; align-items: center; position: relative;">
<text style="left: 24.81rpx; font-size: 22.9rpx; position: absolute;">地址管理</text>
<image src="/static/ic_arrow.png" class="arrow-icon"></image>
</view>
<view class=""
style="width: 100%; height: 82.06rpx; background-color: white; border-radius: 15.27rpx; margin-top: 28.63rpx; display: flex; align-items: center; position: relative;">
<text style="left: 24.81rpx; font-size: 22.9rpx; position: absolute;">优惠券</text>
<text
style="right: 45.8rpx; font-size: 19.08rpx; position: absolute; color: #999999;">未选择</text>
<image src="/static/ic_arrow.png" class="arrow-icon"></image>
</view>
<view class=""
style="width: 100%; height: 82.06rpx; background-color: #F9F8E1; border-radius: 15.27rpx; margin-top: 28.63rpx; display: flex; align-items: center; position: relative;">
<text style="left: 24.81rpx; font-size: 22.9rpx; position: absolute;">售前·售后须知</text>
<image src="/static/ic_arrow.png" class="arrow-icon"></image>
</view>
<view class=""
style="width: 100%; display: flex; flex-direction: row; align-items: center; justify-content: left; margin-top: 45.8rpx;">
<image :src="isCheck ? '/static/ic_check_s.png' : '/static/ic_check.png'" @click="setCheck"
style="width: 24.02rpx; height: 24.02rpx;" mode=""></image>
<text
style="font-size: 19.08rpx; color: #999999; margin-left: 11.45rpx;">我已满18岁阅读并同意
<text style="color: #333333;">用户协议</text>
<text style="color: #333333;">隐私政策</text>
</text>
</view>
<view class=""
style="width: 100%; height: 83.97rpx; background-color: #333333; border-radius: 15.27rpx; display: flex; align-items: center; justify-content: center; margin-top: 30.53rpx;">
<text style="font-size: 22.9rpx; color: #CDEF27;">确认支付</text>
</view>
</view>
</view>
</uni-popup>
<!-- 使用支付弹窗组件 -->
<payment-popup ref="paymentPopup_ref" :onSuccess="onPaymentSuccess" />
</z-paging>
</template>
<script setup>
import { ref } from 'vue';
import { getProductDetail } from '@/common/server/product';
import { getProductDetail, addFavorite, cancelFavorite } from '@/common/server/product';
import { pay_by_order } from '@/common/server/order';
import paymentPopup from '@/components/youdas-container/payment-popup.vue';
let is_favorite = ref(false);
let productDetail = ref(null);
let paging = ref(null);
let productId = null;
let paymentPopup_ref = ref(null);
let favorite_src = computed(() => {
return is_favorite.value ? "/static/app-plus/user/sc1.png" : "/static/app-plus/user/sc2.png";
})
onLoad(async (options) => {
const id = options.id;
productId = id;
queryProductDetail();
});
const onRefresh = () => {
console.log("onRefresh");
queryProductDetail().then(() => {
paging.value.complete();
});
};
const favoriteAction = async () => {
if (!yds.userInfo.isAccountLogin()) {
yds.showToast("未登录,请先登录");
return;
}
if (is_favorite.value) {
await cancelFavorite(productId);
yds.showToast("取消收藏成功");
is_favorite.value = false;
} else {
await addFavorite(productId);
yds.showToast("收藏成功");
is_favorite.value = true;
}
};
const queryProductDetail = async () => {
const res = await getProductDetail(productId);
if (res) {
productDetail.value = res;
console.log(productDetail.value);
if (res.is_favorite != null) {
is_favorite.value = res.is_favorite;
}
}
};
//
const detailList = ref([1, 2, 3, 4, 5]);
const isCheck = ref(true);
const bayPop = ref(null);
//
const toBack = () => {
@ -184,21 +118,17 @@ const toBack = () => {
};
const bayOpen = () => {
bayPop.value.open();
paymentPopup_ref.value.open(productId);
};
const closePop = () => {
bayPop.value.close();
};
const setCheck = () => {
isCheck.value = !isCheck.value;
};
const toAddress = () => {
uni.navigateTo({
url: '/pages/mall/receiving-address'
const onPaymentSuccess = () => {
uni.showToast({
title: '支付成功',
icon: 'success'
});
paymentPopup_ref.value.close();
queryProductDetail();
//
};
</script>
@ -255,7 +185,7 @@ const toAddress = () => {
//
::v-deep rich-text img {
max-width: 95% !important;
border-radius: 10rpx;
// border-radius: 10rpx;
height: auto !important;
display: block !important;
margin: 0 auto;

View File

@ -16,20 +16,20 @@
<scroll-view class="" scroll-y="true" style="width: 688.93rpx; margin: 34.44rpx auto; height: 1200rpx;">
<view class="" v-for="(item,index) in addressList"
<view class="" v-for="(item, index) in addressList"
style="width: 100%; height: 227.1rpx; background-color: white; margin-bottom: 22.9rpx; border-radius: 15.27rpx; position: relative;">
<view class=""
style="display: flex; flex-direction: row; position: absolute; left: 32.44rpx; top: 30.53rpx;">
<text style="font-size: 22.9rpx;">{{item.name}}</text>
<text style="font-size: 22.9rpx;">{{ item.name }}</text>
<text style="font-size: 22.9rpx; margin-left: 30.53rpx;">{{item.phoneNum}}</text>
<text style="font-size: 22.9rpx; margin-left: 30.53rpx;">{{ item.phoneNum }}</text>
</view>
<text
style="font-size: 20.99rpx; color: #575757; position: absolute; left: 32.44rpx; top: 91.6rpx;">{{item.address}}</text>
<text style="font-size: 20.99rpx; color: #575757; position: absolute; left: 32.44rpx; top: 91.6rpx;">{{
item.address }}</text>
<view class=""
@ -50,79 +50,87 @@
</scroll-view>
<view class="" style="width: 100%; height: 188.94rpx; background-color: white;">
<view class="" style="width: 100%; height: 120rpx; background-color: white; display: flex; flex-direction: row; align-items: center; justify-content: space-around; padding: 0 30rpx;">
<view class=""
style="width: 688.93rpx; height: 83.97rpx; background-color: #333333; border-radius: 15.27rpx; margin: 20.99rpx auto; display: flex; align-items: center; justify-content: center;">
<text style="color: #CDEF27; font-size: 22.9rpx;">新增收货地址</text>
style="width: 30%; height: 83.97rpx; background-color: #333333; border-radius: 15.27rpx; display: flex; align-items: center; justify-content: center;">
<text style="color: #CDEF27; font-size: 22.9rpx;">新增地址</text>
</view>
<view class=""
style="width: 40%; height: 83.97rpx; background-color: #333333; border-radius: 15.27rpx; display: flex; align-items: center; justify-content: center;">
<text style="color: #CDEF27; font-size: 22.9rpx;">确定收货地址</text>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
addressList: [{
name: "苏家辉",
phoneNum: "130000000",
address: "江苏省徐州市睢宁县"
}, {
name: "苏家辉",
phoneNum: "130000000",
address: "江苏省徐州市睢宁县"
}, {
name: "苏家辉",
phoneNum: "130000000",
address: "江苏省徐州市睢宁县"
}, {
name: "苏家辉",
phoneNum: "130000000",
address: "江苏省徐州市睢宁县"
}, {
name: "苏家辉",
phoneNum: "130000000",
address: "江苏省徐州市睢宁县"
}, {
name: "苏家辉",
phoneNum: "130000000",
address: "江苏省徐州市睢宁县"
}, {
name: "苏家辉",
phoneNum: "130000000",
address: "江苏省徐州市睢宁县"
}, ],
}
},
methods: {
toBack() {
console.log("777777");
uni.navigateBack();
},
export default {
data() {
return {
eventChannel: null,
addressList: [{
name: "苏家辉",
phoneNum: "130000000",
address: "江苏省徐州市睢宁县"
}, {
name: "苏家辉",
phoneNum: "130000000",
address: "江苏省徐州市睢宁县"
}, {
name: "苏家辉",
phoneNum: "130000000",
address: "江苏省徐州市睢宁县"
}, {
name: "苏家辉",
phoneNum: "130000000",
address: "江苏省徐州市睢宁县"
}, {
name: "苏家辉",
phoneNum: "130000000",
address: "江苏省徐州市睢宁县"
}, {
name: "苏家辉",
phoneNum: "130000000",
address: "江苏省徐州市睢宁县"
}, {
name: "苏家辉",
phoneNum: "130000000",
address: "江苏省徐州市睢宁县"
},],
}
},
onLoad(options) {
this.eventChannel = this.getOpenerEventChannel();
},
methods: {
toBack() {
console.log("777777");
uni.navigateBack();
},
}
}
</script>
<style lang="scss">
.content {
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
background-color: #F7F7F7;
}
.content {
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
background-color: #F7F7F7;
}
.title {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: 32.44rpx;
font-weight: 600;
position: absolute;
bottom: 38.17rpx;
}
.title {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: 32.44rpx;
font-weight: 600;
position: absolute;
bottom: 38.17rpx;
}
</style>

View File

@ -6,37 +6,49 @@
<view class="main-container">
<!-- 用户信息区域 -->
<view class="user-info-section" v-if="userInfo" @click="navigateTo('/pages/me/account-info');">
<view class="user-info-section login-section" v-if="userInfo"
@click="navigateTo('/pages/me/account-info');">
<view class="avatar">
<image :src="userInfo.userIcon" mode="aspectFill"
style="width: 80.15rpx;height: 80.15rpx;border-radius: 10rpx;"></image>
</view>
<view class="user-id">{{ userInfo.username }}</view>
<view class="user-days">已陪你走过了{{ userInfo.days }}</view>
</view>
<view class="user-info-section" v-else @click="navigateToAccountLogin();">
<view class="avatar">
<view class="login-content">
<view class="user-id">{{ userInfo.username }}</view>
<view class="user-days">已陪你走过了{{ userInfo.days }}</view>
</view>
<view class="login-arrow">
<image src="/static/ic_arrow.png" class="arrow-icon"></image>
</view>
</view>
<view class="user-info-section login-section" v-else @click="navigateToAccountLogin();">
<view class="avatar">
<view class="default-avatar"></view>
</view>
<view class="login-content">
<view class="user-id login-text">点击登录</view>
<view class="user-days login-tip">登录后体验更多功能</view>
</view>
<view class="login-arrow">
<image src="/static/ic_arrow.png" class="arrow-icon"></image>
</view>
<view class="user-id">点击登录</view>
<view class="user-days"></view>
</view>
<!-- 订单状态区域 -->
<view class="order-status-section">
<view class="status-item" @click="navigateTo('/pages/mall/order-list?type=1');">
<text class="status-count">0</text>
<view class="status-item" @click="order_goto(1);">
<text class="status-count">{{ orderStatistics.waiting_ship_count }}</text>
<text class="status-label">待发货</text>
</view>
<view class="status-item" @click="navigateTo('/pages/mall/order-list?type=2');">
<text class="status-count">0</text>
<view class="status-item" @click="order_goto(2);">
<text class="status-count">{{ orderStatistics.waiting_receive_count }}</text>
<text class="status-label">待收货</text>
</view>
<view class="status-item" @click="navigateTo('/pages/mall/order-list?type=3');">
<text class="status-count">0</text>
<text class="status-label">待评价</text>
<view class="status-item" @click="order_goto(3);">
<text class="status-count">{{ orderStatistics.received_count }}</text>
<text class="status-label">已收货</text>
</view>
<view class="status-item" @click="navigateTo('/pages/mall/order-list?type=4');">
<text class="status-count">0</text>
<text class="status-label">退款售后</text>
<view class="status-item" @click="order_goto(4);">
<text class="status-count">{{ orderStatistics.after_sales_count }}</text>
<text class="status-label">申请售后</text>
</view>
</view>
@ -50,18 +62,26 @@
</view>
</view>
<page-popup ref="_pagePopup" />
<page-kefu ref="page_kefu_show" />
</view>
</template>
<script setup>
import { getUserInfo } from '@/common/server/user';
import { get_order_statistics } from '@/common/server/order';
import { onShow } from '@dcloudio/uni-app'
import { navigateTo, navigateToAccountLogin } from '@/common/system/router';
let _pagePopup = ref(null);
const itemList = ref([
]);
let orderStatistics = reactive({
"waiting_ship_count": 0,
"waiting_receive_count": 0,
"received_count": 0,
"after_sales_count": 0
});
let page_kefu_show = ref(null);
let userInfo = ref(null);
onShow(async () => {
@ -70,32 +90,68 @@ onShow(async () => {
console.log("res", res);
userInfo.value = res;
loadMenu();
await loadOrderStatistics();
yds.hideLoading();
});
const order_goto = (type) => {
if (!yds.userInfo.IsUserLogin()) {
return;
}
navigateTo(`/pages/mall/order-list?type=${type}`);
}
const loadOrderStatistics = async () => {
const res = await get_order_statistics();
console.log("res", res);
if (res != null) {
orderStatistics.waiting_ship_count = res.waiting_ship_count;
orderStatistics.waiting_receive_count = res.waiting_receive_count;
orderStatistics.received_count = res.received_count;
orderStatistics.after_sales_count = res.after_sales_count;
} else {
orderStatistics.waiting_ship_count = 0;
orderStatistics.waiting_receive_count = 0;
orderStatistics.received_count = 0;
orderStatistics.after_sales_count = 0;
}
}
const loadMenu = async () => {
const menuList = [
{
id: 1,
title: "消费记录",
onClick: (res) => {
if (!yds.userInfo.IsUserLogin()) {
return;
}
navigateTo("/pages/mall/order-list");
}
}, {
id: 2,
title: "我的收藏",
onClick: (res) => {
if (!yds.userInfo.IsUserLogin()) {
return;
}
navigateTo("/pages/mall/collect");
}
}, {
id: 3,
title: "优惠券",
onClick: (res) => {
if (!yds.userInfo.IsUserLogin()) {
return;
}
navigateTo("/pages/other/coupon");
}
}, {
},
{
id: 4,
title: "收货地址管理",
onClick: (res) => {
if (!yds.userInfo.IsUserLogin()) {
return;
}
navigateTo("/pages/address/address-list");
}
},
@ -113,6 +169,13 @@ const loadMenu = async () => {
yds.navigateToAgreement("privacy");
}
},
{
id: 9,
title: "在线客服",
onClick: (res) => {
page_kefu_show.value.open();
}
},
];
if (yds.userInfo.isAccountLogin()) {
menuList.push({
@ -175,32 +238,113 @@ const loadMenu = async () => {
//
.user-info-section {
width: 100%;
height: 80.15rpx;
width: 95%;
height: auto;
min-height: 80.15rpx;
position: relative;
}
.login-section {
position: relative;
padding: 20rpx;
background-color: #FFFFFF;
border-radius: 15.27rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
display: flex;
align-items: center;
height: auto;
margin-top: 10rpx;
}
.login-section:active {
background-color: #F5F7FF;
}
.login-content {
margin-left: 23rpx;
display: flex;
flex-direction: column;
justify-content: center;
flex: 1;
}
.avatar {
width: 80.15rpx;
height: 80.15rpx;
border-radius: 10rpx;
background-color: #D8D8D8;
flex-shrink: 0;
}
.user-id {
position: absolute;
left: 103rpx;
top: 11rpx;
font-size: 22.9rpx;
color: #333333;
margin-top: 0;
margin-left: 0;
}
.user-days {
position: absolute;
left: 103rpx;
top: 50rpx;
font-size: 19.08rpx;
color: #8A8A8A;
margin-top: 5rpx;
margin-left: 0;
}
.login-text {
font-weight: bold;
color: #4A6FEA;
font-size: 28rpx;
}
.login-tip {
font-size: 19.08rpx;
color: #8A8A8A;
}
.login-arrow {
position: absolute;
right: 25rpx;
top: 50%;
transform: translateY(-50%);
}
.arrow-icon {
width: 10.67rpx;
height: 19.66rpx;
margin-right: 25rpx;
}
.default-avatar {
width: 80.15rpx;
height: 80.15rpx;
border-radius: 10rpx;
background-color: #EAEAEA;
position: relative;
}
.default-avatar::before,
.default-avatar::after {
content: '';
position: absolute;
background-color: #BBBBBB;
}
.default-avatar::before {
width: 36rpx;
height: 36rpx;
border-radius: 50%;
top: 15rpx;
left: 50%;
transform: translateX(-50%);
}
.default-avatar::after {
width: 50rpx;
height: 25rpx;
border-radius: 50% 50% 0 0;
bottom: 10rpx;
left: 50%;
transform: translateX(-50%);
}
//
@ -257,10 +401,4 @@ const loadMenu = async () => {
font-size: 22.9rpx;
color: #333333;
}
.arrow-icon {
width: 10.67rpx;
height: 19.66rpx;
margin-right: 25rpx;
}
</style>

View File

@ -0,0 +1,221 @@
<template>
<page-container ref="pageContainer" title="选择优惠卷" show-back :show-not-data="showNotData" :refresh="onRefresh"
:showLoading="showLoading">
<view v-if="listData.length > 0">
<view class="list-item" v-for="(item, i) in listData" :key="i" :class="{ dis: tabList[tabCur].type == 2 }">
<view class="money">
¥
<text>{{ Number(item.price) }}</text>
</view>
<view class="info">
<view class="title">{{ item.man_price }}{{ item.price }}</view>
<view class="time">{{ item.end_time }}到期</view>
</view>
<view class="btn">
{{ item.mark }}
</view>
</view>
</view>
<view v-else class="not-data">
<NoData></NoData>
</view>
</page-container>
</template>
<script setup>
import { getUserCoupon } from '@/common/server/user';
//#
import PageContainer from '@/components/youdas-container/page-container.vue'
let showLoading = ref(false);
let pageContainer = ref(null);
let showNotData = ref(false);
let listData = ref([]);
const getList = async () => {
showLoading.value = true;
let res = await getUserCoupon(0, 1, 50);
listData.value = res.data.list;
showLoading.value = false;
};
//
const onRefresh = async (paging) => {
await getList();
paging.complete();
};
</script>
<style lang="scss" scoped>
.tab-list {
display: flex;
padding: 30rpx;
background: #fff;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
border-radius: 16rpx;
margin: 20rpx 20rpx 30rpx;
.tab-list-item {
flex: 1;
text-align: center;
position: relative;
padding: 20rpx 18rpx;
font-size: 24rpx;
font-weight: 500;
border-radius: 8rpx;
color: #999999;
transition: all 0.3s;
&.active {
font-size: 24rpx;
color: #333333;
background-color: #E6F791;
box-shadow: 0 2rpx 8rpx rgba(230, 247, 145, 0.6);
.arrow {
position: absolute;
bottom: -10rpx;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 10rpx solid transparent;
border-right: 10rpx solid transparent;
border-top: 10rpx solid #E6F791;
}
}
}
}
.list-item {
width: 710rpx;
box-sizing: border-box;
padding: 0;
display: flex;
align-items: center;
margin: 0 auto 30rpx;
background: #fff;
border-radius: 16rpx;
box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.08);
overflow: hidden;
position: relative;
&::before {
content: '';
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 8rpx;
background: linear-gradient(to bottom, #F39205, #FFBB5C);
}
.money {
width: 180rpx;
text-align: center;
position: relative;
font-size: 28rpx;
font-weight: 500;
color: #F39205;
padding: 30rpx 0;
background: rgba(243, 146, 5, 0.05);
text {
font-weight: 600;
font-size: 70rpx;
color: #F39205;
}
&::after {
content: '';
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
width: 1rpx;
height: 116rpx;
background: #EEEEEE;
}
}
.info {
flex: 1;
padding: 30rpx 20rpx;
.title {
font-size: 28rpx;
font-weight: 500;
color: #333333;
}
.time {
margin-top: 20rpx;
font-size: 20rpx;
font-weight: 400;
color: #8A8A8A;
display: flex;
align-items: center;
&::before {
content: '';
display: inline-block;
width: 20rpx;
height: 20rpx;
background: #E6F791;
border-radius: 50%;
margin-right: 8rpx;
}
}
}
.btn {
width: 120rpx;
height: 60rpx;
background: linear-gradient(to right, #E6F791, #D7E87D);
border-radius: 30rpx;
display: flex;
justify-content: center;
align-items: center;
font-size: 22rpx;
font-weight: 500;
color: #333333;
margin-right: 20rpx;
box-shadow: 0 4rpx 8rpx rgba(230, 247, 145, 0.4);
transition: all 0.3s;
&:active {
transform: scale(0.95);
}
}
&.dis {
position: relative;
opacity: 0.8;
&::after {
content: '';
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
background: rgba(0, 0, 0, 0.2);
backdrop-filter: grayscale(100%);
}
.btn {
background: #CCCCCC;
box-shadow: none;
}
}
}
.not-data {
margin-top: 200rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
</style>

View File

@ -0,0 +1,12 @@
<template>
<web-view :src="url"></web-view>
</template>
<script setup>
let url = ref("");
onLoad((options) => {
url.value = "http://testweb.zfunbox.cn/";
});
</script>
<style lang="scss" scoped></style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB