364 lines
10 KiB
Vue
364 lines
10 KiB
Vue
<template>
|
||
<el-scrollbar height="100%" @end-reached="loadMore">
|
||
<div class="order-management">
|
||
<div class="orders-section">
|
||
<div class="filter-section">
|
||
<div class="filter-row">
|
||
<div class="filter-group">
|
||
<label class="filter-label">{{ t('orderManagement.filters.status') }}</label>
|
||
<div class="status-filter">
|
||
<button
|
||
v-for="status in statusFilters"
|
||
:key="status.key"
|
||
:class="['status-tab', { active: selectedStatus === status.key }]"
|
||
@click="selectStatus(status.key)"
|
||
>
|
||
{{ t(status.label) }}
|
||
<!-- <el-badge
|
||
v-if="status.count !== undefined"
|
||
:value="status.count"
|
||
:max="99"
|
||
:type="selectedStatus === status.key ? 'primary' : 'info'"
|
||
/> -->
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="filter-group">
|
||
<label class="filter-label">{{ t('orderManagement.filters.search') }}</label>
|
||
<el-input
|
||
v-model="order_no"
|
||
:placeholder="t('orderManagement.searchPlaceholder')"
|
||
class="search-input"
|
||
clearable
|
||
>
|
||
<template #prefix>
|
||
<el-icon><Search /></el-icon>
|
||
</template>
|
||
</el-input>
|
||
</div>
|
||
<div class="filter-group">
|
||
<label class="filter-label">{{ t('orderManagement.filters.sort') }}</label>
|
||
<el-select v-model="sort_by" class="sort-select">
|
||
<el-option v-for="opt in sortOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
|
||
</el-select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="orders-grid">
|
||
<OrderCard
|
||
v-for="order in order_list"
|
||
:key="order.id"
|
||
:order="order"
|
||
@view-details="viewOrderDetails"
|
||
@pay-order="handlePayOrder"
|
||
@cancel-order="handleCancelOrder"
|
||
@expired-order="handleExpiredOrder"
|
||
@confirm-order="handleConfirmOrder"
|
||
/>
|
||
</div>
|
||
<div v-if="order_list.length === 0" class="empty-state">
|
||
<div class="empty-icon">
|
||
<el-icon><DocumentDelete /></el-icon>
|
||
</div>
|
||
<h3 class="empty-title">{{ t('orderManagement.empty.title') }}</h3>
|
||
<p class="empty-description">{{ t('orderManagement.empty.description') }}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-scrollbar>
|
||
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed, onMounted, watch } from 'vue'
|
||
import { useI18n } from 'vue-i18n'
|
||
import { useRouter } from 'vue-router'
|
||
import { DocumentDelete, Search } from '@element-plus/icons-vue'
|
||
import { ElMessageBox, ElMessage } from 'element-plus'
|
||
import OrderCard from '@/components/OrderCard.vue'
|
||
import { useOrderStore } from '@/stores/orders'
|
||
import {OrderManagement} from './OrderManagement'
|
||
import {orderStatus,isWeChatBrowser,WechatBus} from '@deotaland/utils'
|
||
const orderPlug = new OrderManagement()
|
||
const { t } = useI18n()
|
||
const router = useRouter()
|
||
const orderStore = useOrderStore()
|
||
const selectedStatus = ref('all')
|
||
const statusFilters = orderStatus.selectList('1');
|
||
const sortOptions = ref([
|
||
{ label: t('orderManagement.sort.created_at'), value: 'created_at' },
|
||
{ label: t('orderManagement.sort.total'), value: 'amount' },
|
||
])
|
||
const viewOrderDetails = (orderData) => {
|
||
console.log(orderData);
|
||
router.push({ name: 'order-detail', params: { orderId:orderData.id } })
|
||
}
|
||
const handlePayOrder = (orderData) => {//处理支付订单
|
||
console.log(orderData);
|
||
if(isWeChatBrowser()){
|
||
const order_info = orderData.order_info
|
||
if(!order_info.pay_params){
|
||
ElMessage.error('当前订单不属于小程序订单')
|
||
return
|
||
}
|
||
const token = window.localStorage.getItem('token')
|
||
WechatBus.BusWechartForNavigate('/pages/pay/pay',{
|
||
method:'pay',
|
||
payData:JSON.stringify({
|
||
...orderData.order_info,
|
||
token:token,
|
||
amount:orderData.order_info.amount,
|
||
order_no:orderData.order_no,
|
||
})
|
||
})
|
||
return
|
||
}
|
||
window.location.href = orderData.stripe_url
|
||
}
|
||
//确认收货
|
||
const handleConfirmOrder = (id)=>{
|
||
ElMessageBox.confirm(
|
||
t('orderManagement.confirm.message'),
|
||
t('orderManagement.confirm.title'),
|
||
{
|
||
confirmButtonText: t('common.confirm'),
|
||
cancelButtonText: t('common.cancel'),
|
||
type: 'warning'
|
||
}
|
||
).then(() => {
|
||
orderPlug.receiveAddress(id).then(res => {
|
||
init()
|
||
})
|
||
})
|
||
}
|
||
const handleCancelOrder = (orderData) => {
|
||
ElMessageBox.confirm(
|
||
t('orderManagement.cancelConfirm.message'),
|
||
t('orderManagement.cancelConfirm.title'),
|
||
{
|
||
confirmButtonText: t('common.confirm'),
|
||
cancelButtonText: t('common.cancel'),
|
||
type: 'warning'
|
||
}
|
||
).then(() => {
|
||
orderPlug.orderCancel({
|
||
id: orderData.id
|
||
}).then(res => {
|
||
if (res.code === 0) {
|
||
ElMessage.success(t('orderManagement.cancelSuccess'))
|
||
init()
|
||
} else {
|
||
ElMessage.error(res.message || t('orderManagement.cancelFail'))
|
||
}
|
||
})
|
||
}).catch(() => {
|
||
// 用户点击取消,无需处理
|
||
})
|
||
}
|
||
|
||
const handleExpiredOrder = (orderId) => {
|
||
orderStore.updateOrder(orderId, { status: 'expired' })
|
||
}
|
||
|
||
const page = ref(1);
|
||
const page_size = ref(10);
|
||
//订单筛选
|
||
const order_no = ref('');
|
||
const loading = ref(false);
|
||
const finished = ref(false);
|
||
//排序方式
|
||
const sort_by = ref('created_at')
|
||
const sort_order = ref('desc');//asc/desc
|
||
const order_list = ref([]);
|
||
const total = ref(0);
|
||
//剩余筛选条件
|
||
const sxtjjson = ref({});
|
||
const loadMore = ()=>{
|
||
getOrderList();
|
||
}
|
||
//获取订单列表
|
||
const getOrderList = ()=>{
|
||
if (loading.value || finished.value) return;
|
||
loading.value = true;
|
||
orderPlug.getOrderList({
|
||
page: page.value,
|
||
page_size: page_size.value,
|
||
order_no: order_no.value,
|
||
sort_by: sort_by.value,
|
||
sort_order: sort_order.value,
|
||
...sxtjjson.value
|
||
}).then(res=>{
|
||
if(res.code==0){
|
||
let data = res.data;
|
||
if (page.value === 1) {
|
||
order_list.value = data.items;
|
||
} else {
|
||
order_list.value = [...order_list.value, ...data.items];
|
||
}
|
||
total.value = data.total;
|
||
finished.value = order_list.value.length >= total.value;
|
||
page.value++;
|
||
}
|
||
})
|
||
}
|
||
const init = ()=>{
|
||
loading.value = false;
|
||
finished.value = false;
|
||
order_list.value = [];
|
||
page.value = 1;
|
||
page_size.value = 10;
|
||
getOrderList();
|
||
}
|
||
watch(order_no, () => {
|
||
// 防抖:300ms 内无新输入再触发
|
||
clearTimeout(window._orderNoDebounce)
|
||
window._orderNoDebounce = setTimeout(() => {
|
||
init()
|
||
}, 300)
|
||
})
|
||
onMounted(() => {
|
||
init();
|
||
})
|
||
|
||
const selectStatus = (status) => {
|
||
const json = orderStatus.selectOrderStatusOptions(status);
|
||
sxtjjson.value = json;
|
||
selectedStatus.value = status;
|
||
init()
|
||
}
|
||
|
||
watch(sort_by, () => {
|
||
init();
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.order-management {
|
||
padding: 24px;
|
||
height: 100%;
|
||
}
|
||
|
||
.page-header { margin-bottom: 24px; }
|
||
.header-content { display: flex; justify-content: space-between; align-items: center; gap: 16px; }
|
||
.page-title { font-size: 24px; font-weight: 700; margin: 0; }
|
||
.page-description { font-size: 14px; color: var(--text-secondary, #6b7280); margin: 4px 0 0; }
|
||
.create-order-btn { height: 40px; }
|
||
|
||
.section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
|
||
.section-title { font-size: 18px; font-weight: 600; margin: 0; }
|
||
|
||
.filter-section {
|
||
background: var(--card-bg, #fff);
|
||
border: 1px solid var(--border-color, #e5e7eb);
|
||
border-radius: 12px;
|
||
padding: 16px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
/* Dark theme styles for filter-section */
|
||
html.dark .filter-section {
|
||
background: var(--card-bg, #1f2937);
|
||
border-color: var(--border-color, #374151);
|
||
}
|
||
|
||
.filter-row { display: flex; gap: 24px; align-items: flex-end; flex-wrap: wrap; }
|
||
.filter-group { display: flex; flex-direction: column; gap: 8px; min-width: 220px; }
|
||
.filter-label { font-size: 12px; font-weight: 600; color: var(--text-secondary, #6b7280); }
|
||
|
||
/* Dark theme styles for filter-label */
|
||
html.dark .filter-label {
|
||
color: var(--text-secondary, #d1d5db);
|
||
}
|
||
|
||
.status-filter { display: flex; gap: 8px; flex-wrap: wrap; }
|
||
.status-tab {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 8px 12px;
|
||
border: 1px solid var(--border-color, #d1d5db);
|
||
border-radius: 6px;
|
||
background: var(--card-bg, #fff);
|
||
color: var(--text-secondary, #6b7280);
|
||
font-size: 13px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
/* Dark theme styles for status-tab */
|
||
html.dark .status-tab {
|
||
background: var(--bg-secondary, #374151);
|
||
border-color: var(--border-color, #4b5563);
|
||
color: var(--text-secondary, #9ca3af);
|
||
opacity: 0.8;
|
||
}
|
||
|
||
/* Dark theme hover state for status-tab */
|
||
html.dark .status-tab:hover {
|
||
border-color: #8b5cf6;
|
||
color: #c4b5fd;
|
||
background: rgba(139, 92, 246, 0.15);
|
||
opacity: 1;
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
/* Dark theme active state for status-tab */
|
||
html.dark .status-tab.active {
|
||
background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);
|
||
border-color: #8b5cf6;
|
||
color: white;
|
||
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.4), 0 0 0 2px rgba(139, 92, 246, 0.2);
|
||
opacity: 1;
|
||
font-weight: 600;
|
||
transform: translateY(0);
|
||
}
|
||
|
||
.status-tab:hover {
|
||
border-color: var(--el-color-primary, #6B46C1);
|
||
color: var(--el-color-primary, #6B46C1);
|
||
}
|
||
|
||
.status-tab.active {
|
||
background: var(--el-color-primary, #6B46C1);
|
||
border-color: var(--el-color-primary, #6B46C1);
|
||
color: white;
|
||
}
|
||
|
||
.search-input { width: 280px; }
|
||
.sort-select { width: 180px; }
|
||
|
||
.orders-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); gap: 16px; }
|
||
|
||
.pay-dialog :deep(.el-dialog) { width: 560px; max-width: 90vw; }
|
||
|
||
.empty-state { text-align: center; padding: 48px 24px; }
|
||
.empty-icon { font-size: 48px; color: #d1d5db; margin-bottom: 12px; }
|
||
|
||
/* Dark theme styles for empty-icon */
|
||
html.dark .empty-icon {
|
||
color: var(--text-secondary, #d1d5db);
|
||
}
|
||
|
||
.empty-title { font-size: 18px; font-weight: 600; margin: 0 0 8px 0; }
|
||
|
||
/* Dark theme styles for empty-title */
|
||
html.dark .empty-title {
|
||
color: var(--text-primary, #f9fafb);
|
||
}
|
||
|
||
.empty-description { font-size: 14px; color: var(--text-secondary, #6b7280); margin: 0 0 16px 0; }
|
||
|
||
/* Dark theme styles for empty-description */
|
||
html.dark .empty-description {
|
||
color: var(--text-secondary, #d1d5db);
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.order-management { padding: 16px; }
|
||
.orders-grid { grid-template-columns: 1fr; }
|
||
.filter-row { flex-direction: column; gap: 16px; }
|
||
.filter-group { min-width: auto; }
|
||
.search-input, .sort-select { width: 100%; }
|
||
}
|
||
</style>
|