优化摸大红前台交互与主题按钮样式
列表改为滚动触底自动分页,详情与订单页铺满主内容区,按钮统一为橙色主题描边/实心样式。
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { showToast } from 'vant'
|
||||
import MobileBottomNav from '@/components/MobileBottomNav.vue'
|
||||
@@ -24,12 +24,22 @@ const loadingMore = ref(false)
|
||||
const keyword = ref('')
|
||||
const activeCategoryId = ref<number | null>(null)
|
||||
const failedCover = ref<Record<number, boolean>>({})
|
||||
const listPanel = ref<HTMLElement | null>(null)
|
||||
const loadMoreSentinel = ref<HTMLElement | null>(null)
|
||||
let searchTimer: ReturnType<typeof setTimeout> | null = null
|
||||
let loadMoreObserver: IntersectionObserver | null = null
|
||||
|
||||
onMounted(async () => {
|
||||
await loadCategories()
|
||||
applyRouteQuery()
|
||||
await loadProducts(true)
|
||||
setupLoadMoreObserver()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
loadMoreObserver?.disconnect()
|
||||
loadMoreObserver = null
|
||||
if (searchTimer) clearTimeout(searchTimer)
|
||||
})
|
||||
|
||||
watch(
|
||||
@@ -64,13 +74,13 @@ async function loadCategories() {
|
||||
}
|
||||
|
||||
async function loadProducts(reset = true) {
|
||||
if (loadingMore.value) return
|
||||
if (reset) {
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
page.value = 1
|
||||
hasMore.value = true
|
||||
} else {
|
||||
if (!hasMore.value) return
|
||||
if (loading.value || loadingMore.value || !hasMore.value) return
|
||||
loadingMore.value = true
|
||||
}
|
||||
try {
|
||||
@@ -92,6 +102,25 @@ async function loadProducts(reset = true) {
|
||||
}
|
||||
}
|
||||
|
||||
function setupLoadMoreObserver() {
|
||||
loadMoreObserver?.disconnect()
|
||||
loadMoreObserver = new IntersectionObserver(
|
||||
entries => {
|
||||
if (!entries.some(item => item.isIntersecting)) return
|
||||
if (loading.value || loadingMore.value || !hasMore.value) return
|
||||
loadProducts(false)
|
||||
},
|
||||
{ root: listPanel.value, rootMargin: '160px 0px', threshold: 0 }
|
||||
)
|
||||
nextTick(() => {
|
||||
if (loadMoreSentinel.value) loadMoreObserver?.observe(loadMoreSentinel.value)
|
||||
})
|
||||
}
|
||||
|
||||
watch([listPanel, loadMoreSentinel], () => {
|
||||
setupLoadMoreObserver()
|
||||
})
|
||||
|
||||
function selectCategory(id: number | null) {
|
||||
activeCategoryId.value = id
|
||||
syncQuery()
|
||||
@@ -166,7 +195,7 @@ function showCover(item: MohongProduct) {
|
||||
</button>
|
||||
</aside>
|
||||
|
||||
<section class="list-panel">
|
||||
<section ref="listPanel" class="list-panel">
|
||||
<van-loading v-if="loading" class="state-loading" size="24px" vertical>加载中...</van-loading>
|
||||
<van-empty v-else-if="products.length === 0" description="暂无商品" />
|
||||
<div v-else class="product-list">
|
||||
@@ -196,17 +225,14 @@ function showCover(item: MohongProduct) {
|
||||
<span class="buy-icon">购</span>
|
||||
</button>
|
||||
</div>
|
||||
<van-button
|
||||
v-if="hasMore && products.length"
|
||||
size="small"
|
||||
plain
|
||||
block
|
||||
:loading="loadingMore"
|
||||
class="load-more"
|
||||
@click="loadProducts(false)"
|
||||
>
|
||||
加载更多
|
||||
</van-button>
|
||||
<div
|
||||
v-if="products.length"
|
||||
ref="loadMoreSentinel"
|
||||
class="load-sentinel"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div v-if="loadingMore" class="load-tip">加载中...</div>
|
||||
<div v-else-if="!loading && products.length && !hasMore" class="load-tip">已经到底了</div>
|
||||
</section>
|
||||
</div>
|
||||
<MobileBottomNav />
|
||||
@@ -379,8 +405,14 @@ function showCover(item: MohongProduct) {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.load-more {
|
||||
margin: 12px;
|
||||
width: calc(100% - 24px);
|
||||
.load-sentinel {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
}
|
||||
.load-tip {
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
color: #b0b8c4;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -214,7 +214,9 @@ async function handleBuy() {
|
||||
>
|
||||
{{ canBuy ? '立即购买' : '暂时缺货' }}
|
||||
</el-button>
|
||||
<el-button size="large" @click="router.push('/mohong')">返回列表</el-button>
|
||||
<el-button size="large" class="btn-outline" @click="router.push('/mohong')">
|
||||
返回列表
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div class="tips">
|
||||
@@ -288,14 +290,15 @@ async function handleBuy() {
|
||||
<style scoped>
|
||||
.mohong-detail-page {
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
.detail-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.05fr) minmax(320px, 0.95fr);
|
||||
gap: 28px;
|
||||
/* 左图固定合理宽度,右侧信息铺满剩余空间,避免整页右侧大片空白 */
|
||||
grid-template-columns: minmax(320px, 420px) minmax(0, 1fr);
|
||||
gap: 24px;
|
||||
align-items: start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.gallery-panel,
|
||||
@@ -304,13 +307,15 @@ async function handleBuy() {
|
||||
border: 1px solid #eef1f5;
|
||||
border-radius: 16px;
|
||||
padding: 18px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.main-image {
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
background: #f3f6fb;
|
||||
background: #111;
|
||||
}
|
||||
|
||||
.main-image img {
|
||||
@@ -436,10 +441,27 @@ async function handleBuy() {
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.actions :deep(.el-button--primary) {
|
||||
--el-button-bg-color: #ff6a00;
|
||||
--el-button-border-color: #ff6a00;
|
||||
--el-button-hover-bg-color: #e55d00;
|
||||
--el-button-hover-border-color: #e55d00;
|
||||
}
|
||||
|
||||
.actions :deep(.btn-outline) {
|
||||
--el-button-bg-color: #fff8f2;
|
||||
--el-button-border-color: rgba(255, 106, 0, 0.32);
|
||||
--el-button-text-color: #ff6a00;
|
||||
--el-button-hover-bg-color: #fff3e8;
|
||||
--el-button-hover-border-color: #ff6a00;
|
||||
--el-button-hover-text-color: #ff6a00;
|
||||
}
|
||||
|
||||
.tips {
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid #eef1f5;
|
||||
@@ -527,5 +549,9 @@ async function handleBuy() {
|
||||
.detail-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.gallery-panel {
|
||||
max-width: 420px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Search, Tickets } from '@element-plus/icons-vue'
|
||||
@@ -23,12 +23,21 @@ const page = ref(1)
|
||||
const hasMore = ref(true)
|
||||
const keyword = ref('')
|
||||
const activeCategoryId = ref<number | null>(null)
|
||||
const loadMoreSentinel = ref<HTMLElement | null>(null)
|
||||
let searchTimer: ReturnType<typeof setTimeout> | null = null
|
||||
let loadMoreObserver: IntersectionObserver | null = null
|
||||
|
||||
onMounted(async () => {
|
||||
await loadCategories()
|
||||
applyRouteQuery()
|
||||
await loadProducts(true)
|
||||
setupLoadMoreObserver()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
loadMoreObserver?.disconnect()
|
||||
loadMoreObserver = null
|
||||
if (searchTimer) clearTimeout(searchTimer)
|
||||
})
|
||||
|
||||
watch(
|
||||
@@ -64,13 +73,13 @@ async function loadCategories() {
|
||||
}
|
||||
|
||||
async function loadProducts(reset = true) {
|
||||
if (loadingMore.value) return
|
||||
if (reset) {
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
page.value = 1
|
||||
hasMore.value = true
|
||||
} else {
|
||||
if (!hasMore.value) return
|
||||
if (loading.value || loadingMore.value || !hasMore.value) return
|
||||
loadingMore.value = true
|
||||
}
|
||||
try {
|
||||
@@ -93,6 +102,26 @@ async function loadProducts(reset = true) {
|
||||
}
|
||||
}
|
||||
|
||||
function setupLoadMoreObserver() {
|
||||
loadMoreObserver?.disconnect()
|
||||
loadMoreObserver = new IntersectionObserver(
|
||||
entries => {
|
||||
if (!entries.some(item => item.isIntersecting)) return
|
||||
if (loading.value || loadingMore.value || !hasMore.value) return
|
||||
loadProducts(false)
|
||||
},
|
||||
{ root: null, rootMargin: '200px 0px', threshold: 0 }
|
||||
)
|
||||
nextTick(() => {
|
||||
if (loadMoreSentinel.value) loadMoreObserver?.observe(loadMoreSentinel.value)
|
||||
})
|
||||
}
|
||||
|
||||
watch(loadMoreSentinel, el => {
|
||||
loadMoreObserver?.disconnect()
|
||||
if (el) loadMoreObserver?.observe(el)
|
||||
})
|
||||
|
||||
function selectCategory(id: number | null) {
|
||||
activeCategoryId.value = id
|
||||
syncQuery()
|
||||
@@ -138,10 +167,13 @@ const activeCategoryName = () => {
|
||||
@input="onKeywordInput"
|
||||
/>
|
||||
</div>
|
||||
<el-button size="small" :icon="Tickets" @click="router.push('/mohong/orders')">
|
||||
我的订单
|
||||
</el-button>
|
||||
<el-button size="small" plain @click="router.push('/')">租号大厅</el-button>
|
||||
<button type="button" class="toolbar-btn outline" @click="router.push('/mohong/orders')">
|
||||
<el-icon><Tickets /></el-icon>
|
||||
<span>我的订单</span>
|
||||
</button>
|
||||
<button type="button" class="toolbar-btn outline" @click="router.push('/')">
|
||||
租号大厅
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -176,11 +208,17 @@ const activeCategoryName = () => {
|
||||
<div v-else v-loading="loading" class="product-grid">
|
||||
<MohongProductCard v-for="item in products" :key="item.id" :product="item" />
|
||||
</div>
|
||||
<div v-if="!loading && products.length" class="load-state">
|
||||
<el-button v-if="hasMore" :loading="loadingMore" @click="loadProducts(false)">
|
||||
加载更多
|
||||
</el-button>
|
||||
<span v-else class="end">已经到底了</span>
|
||||
<div
|
||||
v-if="products.length"
|
||||
ref="loadMoreSentinel"
|
||||
class="load-sentinel"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div v-if="loadingMore" class="load-state">
|
||||
<span class="loading-more">加载中...</span>
|
||||
</div>
|
||||
<div v-else-if="!loading && products.length && !hasMore" class="load-state">
|
||||
<span class="end">已经到底了</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -244,12 +282,20 @@ const activeCategoryName = () => {
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
width: 220px;
|
||||
height: 32px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 8px;
|
||||
height: 36px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid #eef1f5;
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
color: #94a3b8;
|
||||
transition:
|
||||
border-color 0.2s,
|
||||
box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.search-box:focus-within {
|
||||
border-color: rgba(255, 106, 0, 0.45);
|
||||
box-shadow: 0 0 0 3px rgba(255, 106, 0, 0.08);
|
||||
}
|
||||
|
||||
.search-box input {
|
||||
@@ -262,6 +308,36 @@ const activeCategoryName = () => {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 对齐顶栏客服/发布账号的主题按钮风格 */
|
||||
.toolbar-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
height: 36px;
|
||||
padding: 0 14px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid transparent;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.toolbar-btn.outline {
|
||||
border-color: rgba(255, 106, 0, 0.32);
|
||||
background: #fff8f2;
|
||||
color: #ff6a00;
|
||||
}
|
||||
|
||||
.toolbar-btn.outline:hover {
|
||||
border-color: #ff6a00;
|
||||
background: #fff3e8;
|
||||
box-shadow: 0 4px 12px rgba(255, 106, 0, 0.1);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.shop-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 140px minmax(0, 1fr);
|
||||
@@ -357,6 +433,11 @@ const activeCategoryName = () => {
|
||||
color: #17233d;
|
||||
}
|
||||
|
||||
.load-sentinel {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.load-state {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -364,6 +445,7 @@ const activeCategoryName = () => {
|
||||
padding: 12px 0 8px;
|
||||
}
|
||||
|
||||
.loading-more,
|
||||
.end {
|
||||
color: #b0b8c4;
|
||||
font-size: 12px;
|
||||
|
||||
@@ -141,7 +141,9 @@ function openChat() {
|
||||
<h1>{{ statusText }}</h1>
|
||||
<p class="sub">订单号 {{ order.order_no }} · {{ formatDateTime(order.created_at) }}</p>
|
||||
</div>
|
||||
<el-button @click="router.push('/mohong/orders')">返回列表</el-button>
|
||||
<button type="button" class="toolbar-btn outline" @click="router.push('/mohong/orders')">
|
||||
返回列表
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<div class="layout">
|
||||
@@ -164,7 +166,7 @@ function openChat() {
|
||||
<div v-if="order.copy_text" class="copy-box">
|
||||
<div class="copy-head">
|
||||
<h3>订单信息(发给客服)</h3>
|
||||
<el-button type="primary" plain size="small" @click="copyText">一键复制</el-button>
|
||||
<button type="button" class="btn outline sm" @click="copyText">一键复制</button>
|
||||
</div>
|
||||
<pre>{{ order.copy_text }}</pre>
|
||||
</div>
|
||||
@@ -177,27 +179,38 @@ function openChat() {
|
||||
|
||||
<aside class="side-card">
|
||||
<h3>操作</h3>
|
||||
<el-button
|
||||
<button
|
||||
v-if="order.status === 'pending_payment'"
|
||||
type="primary"
|
||||
color="#ff6a00"
|
||||
:loading="acting"
|
||||
type="button"
|
||||
class="btn solid"
|
||||
:disabled="acting"
|
||||
@click="handlePay"
|
||||
>
|
||||
去支付
|
||||
</el-button>
|
||||
<el-button
|
||||
{{ acting ? '处理中...' : '去支付' }}
|
||||
</button>
|
||||
<button
|
||||
v-if="order.status === 'pending_payment'"
|
||||
:loading="acting"
|
||||
type="button"
|
||||
class="btn outline"
|
||||
:disabled="acting"
|
||||
@click="handleCancel"
|
||||
>
|
||||
取消订单
|
||||
</el-button>
|
||||
<el-button v-if="order.conversation_id" type="primary" plain @click="openChat">
|
||||
</button>
|
||||
<button
|
||||
v-if="order.conversation_id"
|
||||
type="button"
|
||||
class="btn outline"
|
||||
@click="openChat"
|
||||
>
|
||||
进入订单群
|
||||
</el-button>
|
||||
<el-button v-if="order.copy_text" plain @click="copyText">复制订单信息</el-button>
|
||||
<el-button plain @click="router.push('/mohong')">继续选购</el-button>
|
||||
</button>
|
||||
<button v-if="order.copy_text" type="button" class="btn outline" @click="copyText">
|
||||
复制订单信息
|
||||
</button>
|
||||
<button type="button" class="btn outline" @click="router.push('/mohong')">
|
||||
继续选购
|
||||
</button>
|
||||
</aside>
|
||||
</div>
|
||||
</template>
|
||||
@@ -213,9 +226,11 @@ function openChat() {
|
||||
<div class="pay-way-options">
|
||||
<button type="button" class="pay-way-option" @click="choosePayWay('WXZF')">
|
||||
<strong>微信支付</strong>
|
||||
<small>使用微信扫码完成支付</small>
|
||||
</button>
|
||||
<button type="button" class="pay-way-option" @click="choosePayWay('ZFBZF')">
|
||||
<strong>支付宝支付</strong>
|
||||
<small>使用支付宝扫码完成支付</small>
|
||||
</button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
@@ -225,6 +240,7 @@ function openChat() {
|
||||
title="订单支付"
|
||||
width="520px"
|
||||
append-to-body
|
||||
class="mohong-pay-dialog"
|
||||
@closed="cashier.stopPaymentPolling"
|
||||
>
|
||||
<div v-if="cashier.activePayment.value" class="pay-dialog-body">
|
||||
@@ -238,13 +254,14 @@ function openChat() {
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="cashier.checkingPayment.value"
|
||||
<button
|
||||
type="button"
|
||||
class="btn solid"
|
||||
:disabled="cashier.checkingPayment.value"
|
||||
@click="cashier.refreshPaymentStatus(false)"
|
||||
>
|
||||
我已支付,刷新状态
|
||||
</el-button>
|
||||
{{ cashier.checkingPayment.value ? '查询中...' : '我已支付,刷新状态' }}
|
||||
</button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</section>
|
||||
@@ -253,7 +270,6 @@ function openChat() {
|
||||
<style scoped>
|
||||
.order-detail-page {
|
||||
width: 100%;
|
||||
max-width: 1100px;
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
}
|
||||
@@ -263,6 +279,7 @@ function openChat() {
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
@@ -274,7 +291,8 @@ function openChat() {
|
||||
|
||||
.page-header h1 {
|
||||
margin: 0 0 6px;
|
||||
font-size: 28px;
|
||||
font-size: 24px;
|
||||
font-weight: 800;
|
||||
color: #17233d;
|
||||
}
|
||||
|
||||
@@ -284,11 +302,65 @@ function openChat() {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.toolbar-btn,
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
height: 36px;
|
||||
padding: 0 14px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid transparent;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.toolbar-btn.outline,
|
||||
.btn.outline {
|
||||
border-color: rgba(255, 106, 0, 0.32);
|
||||
background: #fff8f2;
|
||||
color: #ff6a00;
|
||||
}
|
||||
|
||||
.toolbar-btn.outline:hover,
|
||||
.btn.outline:hover:not(:disabled) {
|
||||
border-color: #ff6a00;
|
||||
background: #fff3e8;
|
||||
box-shadow: 0 4px 12px rgba(255, 106, 0, 0.1);
|
||||
}
|
||||
|
||||
.btn.solid {
|
||||
background: #ff6a00;
|
||||
border-color: #ff6a00;
|
||||
color: #fff;
|
||||
box-shadow: 0 4px 12px rgba(255, 106, 0, 0.2);
|
||||
}
|
||||
|
||||
.btn.solid:hover:not(:disabled) {
|
||||
background: #e55d00;
|
||||
border-color: #e55d00;
|
||||
}
|
||||
|
||||
.btn.sm {
|
||||
height: 30px;
|
||||
padding: 0 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.65;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 240px;
|
||||
grid-template-columns: minmax(0, 1fr) 280px;
|
||||
gap: 16px;
|
||||
align-items: start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.main-card,
|
||||
@@ -388,11 +460,11 @@ function openChat() {
|
||||
.side-card {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
.side-card :deep(.el-button) {
|
||||
.side-card .btn {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.pay-way-options {
|
||||
@@ -401,6 +473,8 @@ function openChat() {
|
||||
}
|
||||
|
||||
.pay-way-option {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
width: 100%;
|
||||
padding: 14px 16px;
|
||||
border: 1px solid #e7edf6;
|
||||
@@ -408,6 +482,29 @@ function openChat() {
|
||||
background: #fff;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
border-color 0.2s,
|
||||
background 0.2s;
|
||||
}
|
||||
|
||||
.pay-way-option:hover {
|
||||
border-color: #ff6a00;
|
||||
background: #fff8f2;
|
||||
}
|
||||
|
||||
.pay-way-option strong {
|
||||
color: #17233d;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.pay-way-option small {
|
||||
color: #8a94a6;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.mohong-pay-dialog :deep(.el-dialog__footer) {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.pay-dialog-body {
|
||||
|
||||
@@ -34,61 +34,152 @@ async function loadOrders() {
|
||||
<section class="mohong-orders-page">
|
||||
<header class="page-header">
|
||||
<div>
|
||||
<p class="eyebrow">My Orders</p>
|
||||
<h1>摸大红订单</h1>
|
||||
<p class="sub">查看支付状态、复制订单信息、进入客服群</p>
|
||||
</div>
|
||||
<el-button @click="router.push('/mohong')">返回商品列表</el-button>
|
||||
<button type="button" class="toolbar-btn outline" @click="router.push('/mohong')">
|
||||
返回商品列表
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<el-table v-loading="loading" :data="orders" border stripe empty-text="暂无订单">
|
||||
<el-table-column prop="order_no" label="订单号" min-width="180" />
|
||||
<el-table-column prop="product_title" label="商品" min-width="160" />
|
||||
<el-table-column label="数量" width="80" prop="quantity" />
|
||||
<el-table-column label="金额" width="110">
|
||||
<template #default="{ row }">¥{{ row.amount || formatCent(row.amount_cent) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="110">
|
||||
<template #default="{ row }">{{ mohongOrderStatusLabel(row.status) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="下单时间" min-width="170">
|
||||
<template #default="{ row }">{{ formatDateTime(row.created_at) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="router.push(`/mohong/orders/${row.id}`)">
|
||||
详情
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="table-wrap">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="orders"
|
||||
border
|
||||
stripe
|
||||
empty-text="暂无订单"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column prop="order_no" label="订单号" min-width="200" />
|
||||
<el-table-column prop="product_title" label="商品" min-width="180" />
|
||||
<el-table-column label="数量" width="90" prop="quantity" align="center" />
|
||||
<el-table-column label="金额" width="120" align="right">
|
||||
<template #default="{ row }">¥{{ row.amount || formatCent(row.amount_cent) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<span class="status-tag" :class="row.status">
|
||||
{{ mohongOrderStatusLabel(row.status) }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="下单时间" min-width="180">
|
||||
<template #default="{ row }">{{ formatDateTime(row.created_at) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<button type="button" class="link-btn" @click="router.push(`/mohong/orders/${row.id}`)">
|
||||
详情
|
||||
</button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.mohong-orders-page {
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
gap: 16px;
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
margin: 0 0 8px;
|
||||
color: #ff6a00;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
margin: 0;
|
||||
font-size: 28px;
|
||||
margin: 0 0 4px;
|
||||
font-size: 22px;
|
||||
font-weight: 800;
|
||||
color: #17233d;
|
||||
}
|
||||
|
||||
.sub {
|
||||
margin: 0;
|
||||
color: #8a94a6;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.toolbar-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 36px;
|
||||
padding: 0 14px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(255, 106, 0, 0.32);
|
||||
background: #fff8f2;
|
||||
color: #ff6a00;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.toolbar-btn:hover {
|
||||
border-color: #ff6a00;
|
||||
background: #fff3e8;
|
||||
box-shadow: 0 4px 12px rgba(255, 106, 0, 0.1);
|
||||
}
|
||||
|
||||
.table-wrap {
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
border: 1px solid #eef1f5;
|
||||
border-radius: 14px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
display: inline-flex;
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
background: #f1f5f9;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.status-tag.pending_payment {
|
||||
background: #fff7ed;
|
||||
color: #ff6a00;
|
||||
}
|
||||
|
||||
.status-tag.paid {
|
||||
background: #ecfdf5;
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
.status-tag.completed {
|
||||
background: #eff6ff;
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.status-tag.cancelled,
|
||||
.status-tag.refunded {
|
||||
background: #f8fafc;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.link-btn {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #ff6a00;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.link-btn:hover {
|
||||
color: #e55d00;
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user