引入订单数据
This commit is contained in:
@@ -38,7 +38,7 @@ import ProductLabel from '@/pages/mall/admin/product/labels/index.uvue'
|
||||
import ProductProtection from '@/pages/mall/admin/product/protection/index.uvue'
|
||||
|
||||
// --- 订单模块 ---
|
||||
import OrderList from '@/pages/mall/admin/order/list.uvue'
|
||||
import OrderList from '@/pages/mall/admin/order/order-management/index.uvue'
|
||||
import OrderStatistic from '@/pages/mall/admin/order/order-statistics/index.uvue'
|
||||
import OrderRefund from '@/pages/mall/admin/order/aftersales-order/index.uvue'
|
||||
import OrderCashier from '@/pages/mall/admin/order/cashier-order/index.uvue'
|
||||
|
||||
@@ -79,7 +79,7 @@ export const topMenus: TopMenu[] = [
|
||||
id: 'order',
|
||||
title: '订单',
|
||||
icon: 'order',
|
||||
path: '/pages/mall/admin/order/list',
|
||||
path: '/pages/mall/admin/order/order-management/index',
|
||||
order: 3,
|
||||
groups: [
|
||||
{ id: 'order-manage', title: '', order: 1 }
|
||||
@@ -400,7 +400,7 @@ export const routes: RouteRecord[] = [
|
||||
{
|
||||
id: 'order_list',
|
||||
title: '订单管理',
|
||||
path: '/pages/mall/admin/order/list',
|
||||
path: '/pages/mall/admin/order/order-management/index',
|
||||
componentKey: 'OrderList',
|
||||
parentId: 'order',
|
||||
groupId: 'order-manage',
|
||||
|
||||
11
pages.json
11
pages.json
@@ -452,22 +452,15 @@
|
||||
{
|
||||
"root": "pages/mall/admin",
|
||||
"pages": [
|
||||
{
|
||||
"path": "order-management",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订单管理",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/mall/admin/order",
|
||||
"pages": [
|
||||
{
|
||||
"path": "list",
|
||||
"path": "order-management/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订单列表",
|
||||
"navigationBarTitleText": "订单管理",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="admin-page">
|
||||
<view class="admin-sections">
|
||||
<view class="admin-card Header">
|
||||
<text class="Title">订单</text>
|
||||
<text class="SubTitle">order-management</text>
|
||||
</view>
|
||||
|
||||
<view class="admin-card Card">
|
||||
<text class="Label">页面参数(query)</text>
|
||||
<text class="Mono">{{ params }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
|
||||
const params = ref('')
|
||||
const currentPage = ref('order-list')
|
||||
|
||||
onLoad((options: Record<string, string>) => {
|
||||
params.value = JSON.stringify(options ?? {})
|
||||
const tab = options['tab'] || ''
|
||||
if (tab == 'stats') currentPage.value = 'order-stats'
|
||||
else if (tab == 'aftersale') currentPage.value = 'order-aftersale'
|
||||
else if (tab == 'cashier') currentPage.value = 'order-cashier'
|
||||
else if (tab == 'verify') currentPage.value = 'order-verify'
|
||||
else if (tab == 'config') currentPage.value = 'order-config'
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.Header {
|
||||
}
|
||||
.Title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.SubTitle {
|
||||
margin-top: 8rpx;
|
||||
font-size: 24rpx;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.Card {
|
||||
}
|
||||
.Label {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
.Mono {
|
||||
font-size: 24rpx;
|
||||
font-family: monospace;
|
||||
line-height: 36rpx;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
@@ -1,481 +0,0 @@
|
||||
<template>
|
||||
<view class="admin-page">
|
||||
<view class="admin-sections">
|
||||
<!-- 筛选区域 -->
|
||||
<view class="admin-card filter-card">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<text class="label">订单类型:</text>
|
||||
<view class="mock-select">
|
||||
<text>全部订单</text>
|
||||
<view class="arrow-down"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<text class="label">支付方式:</text>
|
||||
<view class="mock-select">
|
||||
<text>全部</text>
|
||||
<view class="arrow-down"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item long">
|
||||
<text class="label">创建时间:</text>
|
||||
<view class="mock-date-range">
|
||||
<image class="cal-icon" src="/static/icons/calendar.png" mode="aspectFit" />
|
||||
<text class="placeholder">开始日期 - 结束日期</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item search">
|
||||
<text class="label">订单搜索:</text>
|
||||
<view class="search-group">
|
||||
<view class="search-select">
|
||||
<text>全部</text>
|
||||
<view class="arrow-down"></view>
|
||||
</view>
|
||||
<input class="search-input" placeholder="请输入" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="btn-row">
|
||||
<button class="btn btn-primary">查询</button>
|
||||
<button class="btn btn-default">重置</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 列表数据区域 -->
|
||||
<view class="admin-card content-card">
|
||||
<!-- 状态 Tabs -->
|
||||
<view class="status-tabs">
|
||||
<view
|
||||
v-for="(tab, index) in statusTabs"
|
||||
:key="index"
|
||||
class="tab-item"
|
||||
:class="{ active: activeTab === index }"
|
||||
@click="activeTab = index"
|
||||
>
|
||||
<text class="tab-text">{{ tab.name }}</text>
|
||||
<text v-if="tab.count" class="tab-count">({{ tab.count }})</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="action-bar">
|
||||
<button class="action-btn btn-blue">订单核销</button>
|
||||
<button class="action-btn btn-outline">批量发货</button>
|
||||
<button class="action-btn btn-outline">批量删除</button>
|
||||
<button class="action-btn btn-outline">订单导出</button>
|
||||
</view>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<view class="order-table">
|
||||
<view class="thead">
|
||||
<view class="th col-check">
|
||||
<checkbox :checked="false" color="#1890ff" />
|
||||
</view>
|
||||
<view class="th col-order">订单号 | 类型</view>
|
||||
<view class="th col-product">商品信息</view>
|
||||
<view class="th col-user">用户信息</view>
|
||||
<view class="th col-price">实际支付</view>
|
||||
<view class="th col-pay">支付方式</view>
|
||||
<view class="th col-time">支付时间</view>
|
||||
<view class="th col-status">订单状态</view>
|
||||
<view class="th col-op">操作</view>
|
||||
</view>
|
||||
|
||||
<view class="tbody">
|
||||
<view v-for="(item, index) in orderData" :key="index" class="tr">
|
||||
<view class="td col-check">
|
||||
<checkbox :checked="false" color="#1890ff" />
|
||||
</view>
|
||||
<!-- 订单号|类型 -->
|
||||
<view class="td col-order">
|
||||
<text class="order-sn">{{ item.sn }}</text>
|
||||
<text class="order-type" :class="item.typeColor">[{{ item.typeName }}]</text>
|
||||
<text v-if="item.cancelStatus" class="cancel-text">{{ item.cancelStatus }}</text>
|
||||
</view>
|
||||
<!-- 商品信息 -->
|
||||
<view class="td col-product">
|
||||
<view class="product-info-wrap">
|
||||
<image class="p-img" :src="item.product.img" mode="aspectFill" />
|
||||
<text class="p-name">{{ item.product.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 用户信息 -->
|
||||
<view class="td col-user">
|
||||
<text class="u-info">{{ item.user.phone }} | {{ item.user.id }}</text>
|
||||
</view>
|
||||
<!-- 实际支付 -->
|
||||
<view class="td col-price">
|
||||
<text class="price-val">{{ item.actualPrice }}</text>
|
||||
</view>
|
||||
<!-- 支付方式 -->
|
||||
<view class="td col-pay">
|
||||
<text class="pay-text">{{ item.payMethod }}</text>
|
||||
</view>
|
||||
<!-- 支付时间 -->
|
||||
<view class="td col-time">
|
||||
<text class="time-text">{{ item.payTime }}</text>
|
||||
</view>
|
||||
<!-- 订单状态 -->
|
||||
<view class="td col-status">
|
||||
<text class="status-text">{{ item.statusName }}</text>
|
||||
</view>
|
||||
<!-- 操作 -->
|
||||
<view class="td col-op">
|
||||
<view class="op-links">
|
||||
<text class="op-link primary" v-if="item.primaryAction">{{ item.primaryAction }}</text>
|
||||
<view class="op-link-more">
|
||||
<text class="more-text">更多</text>
|
||||
<view class="arrow-down-blue"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const activeTab = ref(2) // 默认选中待发货或待核销(手动对齐截图)
|
||||
|
||||
const statusTabs = [
|
||||
{ name: '全部', count: null },
|
||||
{ name: '待支付', count: 793 },
|
||||
{ name: '待发货', count: 3695 },
|
||||
{ name: '待核销', count: null },
|
||||
{ name: '待收货', count: null },
|
||||
{ name: '待评价', count: null },
|
||||
{ name: '已完成', count: null },
|
||||
{ name: '已退款', count: null },
|
||||
{ name: '已删除', count: null }
|
||||
]
|
||||
|
||||
const orderData = ref([
|
||||
{
|
||||
sn: 'cp541336970228400128',
|
||||
typeName: '秒杀订单',
|
||||
typeColor: 'blue',
|
||||
cancelStatus: '用户已取消',
|
||||
product: {
|
||||
img: '/static/logo.png',
|
||||
name: '爱奇艺智能 奇遇LT01 投影仪 家用卧室 超高清手机便携投影机 (4K超清 支持...'
|
||||
},
|
||||
user: { phone: '188****4074', id: '82694' },
|
||||
actualPrice: '未支付',
|
||||
payMethod: '--',
|
||||
payTime: '--',
|
||||
statusName: '未支付',
|
||||
primaryAction: ''
|
||||
},
|
||||
{
|
||||
sn: 'cp541289248708362240',
|
||||
typeName: '核销订单',
|
||||
typeColor: 'purple',
|
||||
cancelStatus: '',
|
||||
product: {
|
||||
img: '/static/logo.png',
|
||||
name: '阿迪达斯官网 adidas BBALL CAP COT 男女训练运动帽子FQ5270 传奇墨水...'
|
||||
},
|
||||
user: { phone: '你就给', id: '82703' },
|
||||
actualPrice: '90.1',
|
||||
payMethod: '余额支付',
|
||||
payTime: '2026-02-02 16:10:17',
|
||||
statusName: '未核销',
|
||||
primaryAction: '立即核销'
|
||||
},
|
||||
{
|
||||
sn: 'cp541268226856714240',
|
||||
typeName: '普通订单',
|
||||
typeColor: 'green',
|
||||
cancelStatus: '',
|
||||
product: {
|
||||
img: '/static/logo.png',
|
||||
name: 'UR2024夏季新款女装复古纯欲氛围感一字肩短款T恤衫UWG440060'
|
||||
},
|
||||
user: { phone: '王毅不睡了', id: '82689' },
|
||||
actualPrice: '未支付',
|
||||
payMethod: '--',
|
||||
payTime: '--',
|
||||
statusName: '未支付',
|
||||
primaryAction: '编辑'
|
||||
},
|
||||
{
|
||||
sn: 'cp541262080745930752',
|
||||
typeName: '秒杀订单',
|
||||
typeColor: 'blue',
|
||||
cancelStatus: '',
|
||||
product: {
|
||||
img: '/static/logo.png',
|
||||
name: '爱奇艺智能 奇遇LT01 投影仪 家用卧室 超高清手机便携投影机 (4K超清 支持...'
|
||||
},
|
||||
user: { phone: '177****8361', id: '82697' },
|
||||
actualPrice: '未支付',
|
||||
payMethod: '--',
|
||||
payTime: '--',
|
||||
statusName: '未支付',
|
||||
primaryAction: '编辑'
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.admin-page {
|
||||
/* 使用 Layout 的背景和内边距 */
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.filter-card {
|
||||
padding: var(--admin-card-padding);
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.label {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
width: 70px;
|
||||
}
|
||||
}
|
||||
|
||||
.mock-select {
|
||||
width: 160px;
|
||||
height: 32px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
padding: 0 12px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
text { font-size: 14px; color: #595959; }
|
||||
}
|
||||
|
||||
.mock-date-range {
|
||||
width: 240px;
|
||||
height: 32px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
padding: 0 12px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
.cal-icon { width: 14px; height: 14px; margin-right: 8px; opacity: 0.4; }
|
||||
.placeholder { font-size: 14px; color: #bfbfbf; }
|
||||
}
|
||||
|
||||
.search-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
height: 32px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.search-select {
|
||||
width: 80px;
|
||||
border-right: 1px solid #d9d9d9;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
background-color: #fafafa;
|
||||
text { font-size: 14px; color: #595959; }
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
border: none;
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.arrow-down {
|
||||
width: 0; height: 0;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-top: 5px solid #bfbfbf;
|
||||
}
|
||||
|
||||
.btn-row {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
height: 32px;
|
||||
padding: 0 16px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.btn-primary { background-color: #1890ff; color: #fff; border: none; }
|
||||
.btn-default { background-color: #fff; color: #595959; border: 1px solid #d9d9d9; }
|
||||
|
||||
.content-card {
|
||||
}
|
||||
|
||||
.status-tabs {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
padding: 16px 20px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 2px;
|
||||
|
||||
.tab-text { font-size: 14px; color: #595959; }
|
||||
.tab-count { font-size: 14px; color: #595959; }
|
||||
|
||||
&.active {
|
||||
.tab-text, .tab-count { color: #1890ff; font-weight: 500; }
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute; bottom: 0; left: 0; right: 0; height: 2px; background: #1890ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
padding: 16px 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
height: 32px;
|
||||
padding: 0 16px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.btn-blue { background-color: #1890ff; color: #fff; border: none; }
|
||||
.btn-outline { background-color: #fff; color: #595959; border: 1px solid #d9d9d9; }
|
||||
|
||||
/* 表格样式 */
|
||||
.order-table {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.thead {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: #f0f7ff;
|
||||
min-width: 1200px;
|
||||
}
|
||||
|
||||
.th {
|
||||
padding: 12px 8px;
|
||||
font-size: 14px;
|
||||
color: #595959;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tr {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
min-width: 1200px;
|
||||
&:hover { background-color: #fafafa; }
|
||||
}
|
||||
|
||||
.td {
|
||||
padding: 16px 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 列宽控制 */
|
||||
.col-check { width: 50px; justify-content: center; align-items: center; }
|
||||
.col-order { width: 220px; }
|
||||
.col-product { flex: 1; }
|
||||
.col-user { width: 160px; }
|
||||
.col-price { width: 100px; }
|
||||
.col-pay { width: 100px; }
|
||||
.col-time { width: 160px; }
|
||||
.col-status { width: 100px; }
|
||||
.col-op { width: 120px; }
|
||||
|
||||
/* 单元格具体内容样式 */
|
||||
.order-sn { font-size: 13px; color: #262626; margin-bottom: 4px; }
|
||||
.order-type { font-size: 12px; }
|
||||
.blue { color: #1890ff; }
|
||||
.purple { color: #722ed1; }
|
||||
.green { color: #52c41a; }
|
||||
.cancel-text { font-size: 12px; color: #ff4d4f; margin-top: 4px; }
|
||||
|
||||
.product-info-wrap {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.p-img { width: 44px; height: 44px; border-radius: 4px; background-color: #f5f5f5; flex-shrink: 0; }
|
||||
.p-name { font-size: 13px; color: #595959; line-height: 1.5; }
|
||||
|
||||
.u-info { font-size: 13px; color: #595959; }
|
||||
.price-val { font-size: 14px; color: #262626; }
|
||||
.pay-text, .time-text, .status-text { font-size: 13px; color: #595959; }
|
||||
|
||||
.op-links {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.op-link { font-size: 13px; cursor: pointer; }
|
||||
.primary { color: #1890ff; }
|
||||
|
||||
.op-link-more {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.more-text { font-size: 13px; color: #1890ff; }
|
||||
.arrow-down-blue {
|
||||
width: 0; height: 0;
|
||||
border-left: 3px solid transparent;
|
||||
border-right: 3px solid transparent;
|
||||
border-top: 4px solid #1890ff;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,414 @@
|
||||
<template>
|
||||
<view v-if="visible" class="drawer-mask" @click="close">
|
||||
<view class="drawer-container" @click.stop>
|
||||
<view class="drawer-header">
|
||||
<view class="header-left">
|
||||
<text class="title">订单详情</text>
|
||||
</view>
|
||||
<view class="close-btn" @click="close">
|
||||
<text class="close-icon">×</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view class="drawer-body" scroll-y>
|
||||
<!-- 订单概况 KPIS -->
|
||||
<view class="order-summary-card">
|
||||
<view class="order-type-icon">
|
||||
<image src="/static/icons/order_blue.png" mode="aspectFit" class="type-icon" />
|
||||
</view>
|
||||
<view class="summary-info">
|
||||
<view class="top-row">
|
||||
<text class="order-type-text">{{ orderInfo['typeName'] || '普通订单' }}</text>
|
||||
<text class="order-sn-text">订单号:{{ orderInfo['sn'] }}</text>
|
||||
<text class="shop-tag" v-if="orderInfo['store_name'] != '--'">{{ orderInfo['store_name'] }}</text>
|
||||
</view>
|
||||
<view class="bottom-grids">
|
||||
<view class="summary-grid">
|
||||
<text class="label">订单状态</text>
|
||||
<text class="value status-val">{{ orderInfo['statusName'] }}</text>
|
||||
</view>
|
||||
<view class="summary-grid">
|
||||
<text class="label">总金额</text>
|
||||
<text class="value price-val">¥ {{ orderInfo['actualPrice'] }}</text>
|
||||
</view>
|
||||
<view class="summary-grid">
|
||||
<text class="label">已支付</text>
|
||||
<text class="value status-val">¥ {{ orderInfo['paidAmount'] }}</text>
|
||||
</view>
|
||||
<view class="summary-grid">
|
||||
<text class="label">支付方式</text>
|
||||
<text class="value">{{ orderInfo['payMethod'] }}</text>
|
||||
</view>
|
||||
<view class="summary-grid">
|
||||
<text class="label">配送人员</text>
|
||||
<text class="value">{{ orderInfo['delivery_name'] }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Tabs -->
|
||||
<view class="drawer-tabs">
|
||||
<view v-for="(tab, index) in tabs" :key="index" class="tab-item" :class="{ active: activeTab === index }" @click="activeTab = index">
|
||||
<text class="tab-text">{{ tab }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Tab Content -->
|
||||
<view class="tab-content">
|
||||
<!-- 订单信息 -->
|
||||
<view v-if="activeTab === 0" class="info-section">
|
||||
<view class="section-block">
|
||||
<view class="section-title">
|
||||
<view class="blue-bar"></view>
|
||||
<text>用户信息</text>
|
||||
</view>
|
||||
<view class="info-grid">
|
||||
<view class="info-item">
|
||||
<text class="label">用户名称:</text>
|
||||
<text class="value">{{ orderInfo['user']['name'] }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">绑定电话:</text>
|
||||
<text class="value">{{ orderInfo['user']['phone'] }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section-block">
|
||||
<view class="section-title">
|
||||
<view class="blue-bar"></view>
|
||||
<text>收货信息</text>
|
||||
</view>
|
||||
<view class="info-grid">
|
||||
<view class="info-item">
|
||||
<text class="label">收货人:</text>
|
||||
<text class="value">{{ orderInfo['real_name'] || orderInfo['user']['name'] }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">收货电话:</text>
|
||||
<text class="value">{{ orderInfo['user_phone'] || orderInfo['user']['phone'] }}</text>
|
||||
</view>
|
||||
<view class="info-item full">
|
||||
<text class="label">收货地址:</text>
|
||||
<text class="value">{{ orderInfo['user_address'] || '暂无地址信息' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section-block">
|
||||
<view class="section-title">
|
||||
<view class="blue-bar"></view>
|
||||
<text>订单信息</text>
|
||||
</view>
|
||||
<view class="info-grid">
|
||||
<view class="info-item">
|
||||
<text class="label">创建时间:</text>
|
||||
<text class="value">{{ orderInfo['created_at'] || '--' }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">商品总数:</text>
|
||||
<text class="value">{{ orderInfo['total_num'] || '0' }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">产品金额:</text>
|
||||
<text class="value">¥ {{ orderInfo['total_price'] || '0.00' }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">运费:</text>
|
||||
<text class="value">¥ {{ orderInfo['shipping_fee'] || '0.00' }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">折扣金额:</text>
|
||||
<text class="value">- ¥ {{ orderInfo['coupon_price'] || '0.00' }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">总金额:</text>
|
||||
<text class="value">¥ {{ orderInfo['actualPrice'] }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">支付金额:</text>
|
||||
<text class="value price-red">¥ {{ orderInfo['paidAmount'] }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section-block">
|
||||
<view class="section-title">
|
||||
<view class="blue-bar"></view>
|
||||
<text>买家留言</text>
|
||||
</view>
|
||||
<view class="info-grid">
|
||||
<view class="info-item full">
|
||||
<text class="value">{{ orderInfo['mark'] || '-' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section-block">
|
||||
<view class="section-title">
|
||||
<view class="blue-bar"></view>
|
||||
<text>订单备注</text>
|
||||
</view>
|
||||
<view class="info-grid">
|
||||
<view class="info-item full">
|
||||
<text class="value">{{ orderInfo['remark'] || '-' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 商品信息 -->
|
||||
<view v-if="activeTab === 1" class="info-section">
|
||||
<view class="product-table">
|
||||
<view class="product-thead">
|
||||
<text class="p-th p-info">商品信息</text>
|
||||
<text class="p-th p-sku">规格</text>
|
||||
<text class="p-th p-price">单价</text>
|
||||
<text class="p-th p-num">数量</text>
|
||||
<text class="p-th p-total">小计</text>
|
||||
</view>
|
||||
<view class="product-tbody">
|
||||
<view v-for="(p, pi) in productItems" :key="pi" class="p-tr">
|
||||
<view class="p-td p-info">
|
||||
<image :src="p['image'] || '/static/logo.png'" mode="aspectFill" class="p-img" />
|
||||
<text class="p-name">{{ p['name'] }}</text>
|
||||
</view>
|
||||
<view class="p-td p-sku">
|
||||
<text class="p-sku-txt">{{ p['sku_info'] || '-' }}</text>
|
||||
</view>
|
||||
<view class="p-td p-price">¥{{ p['price'] }}</view>
|
||||
<view class="p-td p-num">{{ p['quantity'] }}</view>
|
||||
<view class="p-td p-total">¥{{ (parseFloat(p['price'] as string) * parseInt(p['quantity'] as string)).toFixed(2) }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 订单记录 -->
|
||||
<view v-if="activeTab === 2" class="info-section">
|
||||
<view class="timeline">
|
||||
<view v-for="(log, li) in logs" :key="li" class="timeline-item">
|
||||
<view class="dot"></view>
|
||||
<view class="line" v-if="li !== logs.length - 1"></view>
|
||||
<view class="log-content">
|
||||
<text class="log-title">{{ log.title }}</text>
|
||||
<text class="log-time">{{ log.time }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
visible: { type: Boolean, default: false },
|
||||
orderInfo: { type: Object, default: () : UTSJSONObject => ({}) as UTSJSONObject }
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:visible'])
|
||||
|
||||
const activeTab = ref(0)
|
||||
const tabs = ['订单信息', '商品信息', '订单记录']
|
||||
|
||||
const productItems = computed<UTSJSONObject[]>(() => {
|
||||
return (props.orderInfo['items'] || []) as UTSJSONObject[]
|
||||
})
|
||||
|
||||
const logs = ref([
|
||||
{ title: '订单生成', time: '2026-02-27 15:47:25' },
|
||||
{ title: '支付成功', time: '2026-02-27 15:48:30' }
|
||||
])
|
||||
|
||||
const close = () => {
|
||||
emit('update:visible', false)
|
||||
}
|
||||
|
||||
watch(() => props.visible, (newVal) => {
|
||||
if (newVal) {
|
||||
activeTab.value = 0
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.drawer-mask {
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
z-index: 2000;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.drawer-container {
|
||||
width: 800px;
|
||||
max-width: 90%;
|
||||
height: 100vh;
|
||||
background-color: #fff;
|
||||
box-shadow: -2px 0 8px rgba(0,0,0,0.1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.drawer-header {
|
||||
height: 56px;
|
||||
padding: 0 24px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.title { font-size: 16px; font-weight: 600; color: #333; }
|
||||
.close-btn {
|
||||
width: 32px; height: 32px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
.close-icon { font-size: 24px; color: #999; line-height: 1; }
|
||||
|
||||
.drawer-body {
|
||||
flex: 1;
|
||||
background-color: #f5f7f9;
|
||||
}
|
||||
|
||||
.order-summary-card {
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.type-icon { width: 48px; height: 48px; }
|
||||
|
||||
.summary-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.top-row {
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.order-type-text { font-size: 16px; font-weight: 600; color: #333; }
|
||||
.order-sn-text { font-size: 14px; color: #666; }
|
||||
.shop-tag {
|
||||
font-size: 12px; color: #1890ff; background: #e6f7ff;
|
||||
border: 1px solid #91d5ff; padding: 2px 8px; border-radius: 2px;
|
||||
}
|
||||
|
||||
.bottom-grids {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 40px;
|
||||
}
|
||||
|
||||
.summary-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
.label { font-size: 12px; color: #999; }
|
||||
.value { font-size: 14px; color: #333; }
|
||||
.status-val { font-weight: 600; }
|
||||
.price-val { color: #f5222d; font-weight: 600; }
|
||||
}
|
||||
|
||||
.drawer-tabs {
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
padding: 12px 20px;
|
||||
margin-right: 12px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
.tab-text { font-size: 14px; color: #595959; }
|
||||
&.active {
|
||||
.tab-text { color: #1890ff; font-weight: 500; }
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute; bottom: 0; left: 0; right: 0; height: 2px; background: #1890ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.section-block {
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 20px;
|
||||
text { font-size: 14px; font-weight: 600; color: #333; }
|
||||
}
|
||||
|
||||
.blue-bar { width: 3px; height: 14px; background-color: #1890ff; }
|
||||
|
||||
.info-grid {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: y;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
width: 33.33%;
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
.label { font-size: 13px; color: #666; width: 80px; flex-shrink: 0; }
|
||||
.value { font-size: 13px; color: #333; line-height: 1.4; word-break: break-all; }
|
||||
.price-red { color: #f5222d; font-weight: 600; }
|
||||
&.full { width: 100%; }
|
||||
}
|
||||
|
||||
/* 商品表格 */
|
||||
.product-table { padding: 8px; background-color: #fff; }
|
||||
.product-thead { display: flex; flex-direction: row; background-color: #fafafa; border-bottom: 1px solid #f0f0f0; }
|
||||
.p-th { padding: 12px 8px; font-size: 13px; font-weight: 500; color: #333; text-align: left; }
|
||||
.p-tr { display: flex; flex-direction: row; border-bottom: 1px solid #f0f0f0; }
|
||||
.p-td { padding: 12px 8px; font-size: 13px; color: #595959; display: flex; align-items: center; }
|
||||
|
||||
.p-info { flex: 1; display: flex; flex-direction: row; align-items: center; gap: 8px; }
|
||||
.p-img { width: 40px; height: 40px; border-radius: 4px; }
|
||||
.p-sku { width: 120px; }
|
||||
.p-price { width: 100px; }
|
||||
.p-num { width: 80px; }
|
||||
.p-total { width: 100px; }
|
||||
|
||||
/* 记录 */
|
||||
.timeline { padding: 24px; background-color: #fff; }
|
||||
.timeline-item { position: relative; padding-left: 24px; padding-bottom: 24px; }
|
||||
.dot { position: absolute; left: 0; top: 4px; width: 10px; height: 10px; border-radius: 5px; background-color: #1890ff; z-index: 2; }
|
||||
.line { position: absolute; left: 4.5px; top: 14px; bottom: -4px; width: 1px; background-color: #e8e8e8; }
|
||||
.log-content { display: flex; flex-direction: column; gap: 4px; }
|
||||
.log-title { font-size: 14px; color: #333; }
|
||||
.log-time { font-size: 12px; color: #999; }
|
||||
</style>
|
||||
@@ -1,25 +1,828 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面已修复 (UTF-8)</text>
|
||||
<view class="admin-page">
|
||||
<view class="admin-sections" @click="closeDropdowns">
|
||||
<!-- 筛选区域 -->
|
||||
<view class="admin-card filter-card">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<text class="label">订单类型:</text>
|
||||
<view class="mock-select">
|
||||
<text>全部订单</text>
|
||||
<view class="arrow-down"></view>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
<view class="filter-item">
|
||||
<text class="label">支付方式:</text>
|
||||
<view class="mock-select">
|
||||
<text>全部</text>
|
||||
<view class="arrow-down"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item long">
|
||||
<text class="label">创建时间:</text>
|
||||
<view class="mock-date-range">
|
||||
<image class="cal-icon" src="/static/icons/calendar.png" mode="aspectFit" />
|
||||
<text class="placeholder">开始日期 - 结束日期</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item search">
|
||||
<text class="label">订单搜索:</text>
|
||||
<view class="search-group">
|
||||
<view class="search-select">
|
||||
<text>全部</text>
|
||||
<view class="arrow-down"></view>
|
||||
</view>
|
||||
<input class="search-input" v-model="searchKeyword" placeholder="请输入订单号/手机号" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="btn-row">
|
||||
<button class="btn btn-primary" @click="fetchData">查询</button>
|
||||
<button class="btn btn-default" @click="resetQuery">重置</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 列表数据区域 -->
|
||||
<view class="admin-card content-card">
|
||||
<!-- 状态 Tabs -->
|
||||
<view class="status-tabs">
|
||||
<view
|
||||
v-for="(tab, index) in statusTabs"
|
||||
:key="index"
|
||||
class="tab-item"
|
||||
:class="{ active: activeTab === index }"
|
||||
@click="handleTabClick(index)"
|
||||
>
|
||||
<text class="tab-text">{{ tab.name }}</text>
|
||||
<text v-if="tab.count !== null" class="tab-count">({{ tab.count }})</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="action-bar">
|
||||
<button class="action-btn btn-blue">订单核销</button>
|
||||
<button class="action-btn btn-outline">批量发货</button>
|
||||
<button class="action-btn btn-outline">批量删除</button>
|
||||
<button class="action-btn btn-outline">订单导出</button>
|
||||
</view>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<view class="order-table">
|
||||
<view class="thead">
|
||||
<view class="th col-expand"></view>
|
||||
<view class="th col-check">
|
||||
<checkbox :checked="false" color="#1890ff" />
|
||||
</view>
|
||||
<view class="th col-order">订单号 | 类型</view>
|
||||
<view class="th col-product">商品信息</view>
|
||||
<view class="th col-user">用户信息</view>
|
||||
<view class="th col-price">实际支付</view>
|
||||
<view class="th col-pay">支付方式</view>
|
||||
<view class="th col-time">支付时间</view>
|
||||
<view class="th col-status">订单状态</view>
|
||||
<view class="th col-op">操作</view>
|
||||
</view>
|
||||
|
||||
<view class="tbody">
|
||||
<!-- Loading 状态 -->
|
||||
<view v-if="loading" class="loading-state">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 无数据状态 -->
|
||||
<view v-else-if="filteredOrders.length === 0" class="empty-state">
|
||||
<text>暂无订单数据</text>
|
||||
</view>
|
||||
|
||||
<view v-for="(item, index) in filteredOrders" :key="index" class="tr">
|
||||
<view class="td col-expand">
|
||||
<text class="expand-arrow">›</text>
|
||||
</view>
|
||||
<view class="td col-check">
|
||||
<checkbox :checked="false" color="#1890ff" />
|
||||
</view>
|
||||
<!-- 订单号|类型 -->
|
||||
<view class="td col-order">
|
||||
<text class="order-sn">{{ item['sn'] }}</text>
|
||||
<text class="order-type" :class="item['typeColor']">[{{ item['typeName'] }}]</text>
|
||||
<text v-if="item['cancelStatus'] != ''" class="cancel-text">{{ item['cancelStatus'] }}</text>
|
||||
</view>
|
||||
<!-- 商品信息 -->
|
||||
<view class="td col-product">
|
||||
<view class="product-info-list" v-if="item['items'] != null">
|
||||
<view v-for="(prod, pidx) in (item['items'] as UTSJSONObject[])" :key="pidx" class="product-info-wrap">
|
||||
<image class="p-img" :src="prod['image'] != null ? (prod['image'] as string) : '/static/logo.png'" mode="aspectFill" />
|
||||
<text class="p-name">{{ prod['name'] }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="product-info-wrap">
|
||||
<image class="p-img" :src="item['product']['img']" mode="aspectFill" />
|
||||
<text class="p-name">{{ item['product']['name'] }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 用户信息 -->
|
||||
<view class="td col-user">
|
||||
<text class="u-info">{{ item['user']['name'] }} | {{ item['user']['id'] || '--' }}</text>
|
||||
</view>
|
||||
<!-- 实际支付 -->
|
||||
<view class="td col-price">
|
||||
<text class="price-val">{{ item['isPaid'] === true ? '¥' + item['actualPrice'] : '未支付' }}</text>
|
||||
</view>
|
||||
<!-- 支付方式 -->
|
||||
<view class="td col-pay">
|
||||
<text class="pay-text">{{ item['payMethod'] }}</text>
|
||||
</view>
|
||||
<!-- 支付时间 -->
|
||||
<view class="td col-time">
|
||||
<text class="time-text">{{ item['payTime'] }}</text>
|
||||
</view>
|
||||
<!-- 订单状态 -->
|
||||
<view class="td col-status">
|
||||
<text class="status-text">{{ item['statusName'] }}</text>
|
||||
</view>
|
||||
<!-- 操作 -->
|
||||
<view class="td col-op overflow-visible no-wrap">
|
||||
<view class="op-links">
|
||||
<text class="op-link primary" @click.stop="handleAction('edit', item['sn'] as string)">编辑</text>
|
||||
<view class="op-dropdown-container">
|
||||
<view class="op-link-more" @click.stop="toggleDropdown(item['sn'] as string)">
|
||||
<text :class="{ 'more-text-active': activeDropdownId === item['sn'] }" class="more-text">更多</text>
|
||||
<view :class="{ 'arrow-up-blue': activeDropdownId === item['sn'], 'arrow-down-blue': activeDropdownId !== item['sn'] }"></view>
|
||||
</view>
|
||||
<!-- 浮动菜单 -->
|
||||
<view v-if="activeDropdownId === (item['sn'] as string)" class="dropdown-menu">
|
||||
<view class="dropdown-item" @click.stop="viewDetail(item)">
|
||||
<text class="item-text">订单详情</text>
|
||||
</view>
|
||||
<view class="dropdown-item danger" @click.stop="deleteOrder(item)">
|
||||
<text class="item-text text-red">删除订单</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页 -->
|
||||
<view class="pagination-footer">
|
||||
<view class="page-left">
|
||||
<text class="count-text">共 {{ orderData.length }} 条</text>
|
||||
<view class="page-size-select">
|
||||
<text>10条/页</text>
|
||||
<view class="arrow-down"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="page-right">
|
||||
<view class="page-btn disabled"><text>‹</text></view>
|
||||
<view class="page-num active"><text>1</text></view>
|
||||
<view class="page-num"><text>2</text></view>
|
||||
<view class="page-num"><text>3</text></view>
|
||||
<view class="page-btns-more"><text>...</text></view>
|
||||
<view class="page-num"><text>10</text></view>
|
||||
<view class="page-btn"><text>›</text></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 抽屉详情 -->
|
||||
<OrderDetailDrawer
|
||||
v-model:visible="showDetail"
|
||||
:order-info="selectedOrder"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('order-list')
|
||||
const title = ref<string>('index')
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { supabase } from '@/components/supadb/aksupainstance.uts'
|
||||
import OrderDetailDrawer from './components/OrderDetailDrawer.uvue'
|
||||
|
||||
const activeTab = ref(0)
|
||||
const loading = ref(false)
|
||||
const searchKeyword = ref('')
|
||||
|
||||
// Dropdown 状态
|
||||
const activeDropdownId = ref('')
|
||||
const showDetail = ref(false)
|
||||
const selectedOrder = ref<UTSJSONObject>({} as UTSJSONObject)
|
||||
|
||||
// 时间显示格式化
|
||||
const formatTime = (timeStr : string | null) : string => {
|
||||
if (timeStr == null || timeStr == '' || timeStr == '--') return '--'
|
||||
if (timeStr.indexOf('T') > -1) {
|
||||
const parts = timeStr.split('T')
|
||||
const date = parts[0]
|
||||
const timeFull = parts[1]
|
||||
const time = timeFull.split('.')[0]
|
||||
return date + ' ' + time
|
||||
}
|
||||
return timeStr
|
||||
}
|
||||
|
||||
const statusTabs = [
|
||||
{ name: '全部', count: null, status: 0 },
|
||||
{ name: '待支付', count: null, status: 1 },
|
||||
{ name: '待发货', count: null, status: 2 },
|
||||
{ name: '待收货', count: null, status: 3 },
|
||||
{ name: '已完成', count: null, status: 4 },
|
||||
{ name: '已取消', count: null, status: 5 },
|
||||
{ name: '退款中', count: null, status: 6 },
|
||||
{ name: '已退款', count: null, status: 7 }
|
||||
]
|
||||
|
||||
const orderData = ref<UTSJSONObject[]>([])
|
||||
|
||||
const filteredOrders = computed<UTSJSONObject[]>(() => {
|
||||
let list = orderData.value
|
||||
|
||||
// Tab 过滤
|
||||
if (activeTab.value > 0) {
|
||||
const targetStatus = statusTabs[activeTab.value].status
|
||||
list = list.filter((o : UTSJSONObject) : boolean => (o['orderStatus'] as number) === targetStatus)
|
||||
}
|
||||
|
||||
// 搜索过滤
|
||||
if (searchKeyword.value.trim() !== '') {
|
||||
const kw = searchKeyword.value.toLowerCase()
|
||||
list = list.filter((o : UTSJSONObject) : boolean => {
|
||||
const sn = (o['sn'] as string).toLowerCase()
|
||||
const user = o['user'] as UTSJSONObject
|
||||
const phone = user['phone'] as string
|
||||
return sn.includes(kw) || phone.includes(kw)
|
||||
})
|
||||
}
|
||||
|
||||
return list
|
||||
})
|
||||
|
||||
const fetchData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 从 ml_orders_detail_view 视图读取数据
|
||||
const res = await supabase
|
||||
.from('ml_orders_detail_view')
|
||||
.select('*')
|
||||
.order('created_at', { ascending: false })
|
||||
.execute()
|
||||
|
||||
if (res.error == null && res.data != null) {
|
||||
const rawData = res.data as UTSJSONObject[]
|
||||
orderData.value = rawData.map((item: UTSJSONObject) : UTSJSONObject => {
|
||||
return {
|
||||
sn: item['order_no'],
|
||||
typeName: '普通订单',
|
||||
typeColor: 'green',
|
||||
orderStatus: item['order_status'],
|
||||
cancelStatus: item['order_status'] === 5 ? '用户已取消' : '',
|
||||
product: {
|
||||
img: '/static/logo.png', // 默认占位图
|
||||
name: '订单概要 (详情查看)'
|
||||
} as UTSJSONObject,
|
||||
items: item['order_items'] as UTSJSONObject[], // 尝试获取子项
|
||||
user: {
|
||||
name: (item['customer_name'] || '未知用户') as string,
|
||||
id: (item['user_id'] || '--') as string,
|
||||
phone: (item['customer_phone'] || '--') as string
|
||||
} as UTSJSONObject,
|
||||
isPaid: item['payment_status'] === 2,
|
||||
actualPrice: item['total_amount'], // 总金额
|
||||
paidAmount: item['paid_amount'] != null ? item['paid_amount'] : 0.00, // 支付金额
|
||||
payMethod: item['payment_status_name'] || '--',
|
||||
payTime: formatTime(item['paid_at'] as string | null),
|
||||
created_at: formatTime(item['created_at'] as string | null),
|
||||
statusName: item['order_status_name'] || '未知',
|
||||
primaryAction: item['order_status'] === 1 ? '立即支付' : '',
|
||||
total_num: Array.isArray(item['order_items']) ? (item['order_items'] as Array<any>).length : 0,
|
||||
total_price: item['product_amount'] != null ? item['product_amount'] : 0.00, // 产品价格
|
||||
coupon_price: item['discount_amount'] != null ? item['discount_amount'] : 0.00, // 折扣价
|
||||
shipping_fee: item['shipping_fee'] != null ? item['shipping_fee'] : 0.00, // 运费
|
||||
deduction_price: 0,
|
||||
user_address: item['shipping_address'] != null ? (typeof item['shipping_address'] === 'string' ? item['shipping_address'] : JSON.stringify(item['shipping_address'])) : '暂无地址信息',
|
||||
real_name: item['customer_name'] != null ? item['customer_name'] : '', // 消费者名字
|
||||
user_phone: item['customer_phone'] != null ? item['customer_phone'] : '',
|
||||
delivery_name: item['merchant_name'] != null ? item['merchant_name'] : '--', // 配送人员
|
||||
store_name: item['shop_name'] != null ? item['shop_name'] : '--', // 店铺名字
|
||||
mark: item['remark'] != null ? item['remark'] : '-',
|
||||
remark: item['merchant_memo'] != null ? item['merchant_memo'] : '-'
|
||||
} as UTSJSONObject
|
||||
})
|
||||
|
||||
// 更新统计数据
|
||||
updateTabCounts()
|
||||
} else {
|
||||
console.error('Fetch orders error:', res.error)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Fetch orders exception:', e)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const updateTabCounts = () => {
|
||||
statusTabs.forEach((tab, index) => {
|
||||
if (index === 0) {
|
||||
tab.count = orderData.value.length
|
||||
} else {
|
||||
tab.count = orderData.value.filter((o : UTSJSONObject) : boolean => (o['orderStatus'] as number) === tab.status).length
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleTabClick = (index: number) => {
|
||||
activeTab.value = index
|
||||
}
|
||||
|
||||
const resetQuery = () => {
|
||||
searchKeyword.value = ''
|
||||
activeTab.value = 0
|
||||
fetchData()
|
||||
}
|
||||
|
||||
// 更多菜单控制
|
||||
const toggleDropdown = (id: string) => {
|
||||
if (activeDropdownId.value === id) {
|
||||
activeDropdownId.value = ''
|
||||
} else {
|
||||
activeDropdownId.value = id
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭下拉
|
||||
const closeDropdowns = () => {
|
||||
activeDropdownId.value = ''
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
const viewDetail = (order: UTSJSONObject) => {
|
||||
selectedOrder.value = order
|
||||
showDetail.value = true
|
||||
closeDropdowns()
|
||||
}
|
||||
|
||||
// 删除订单 (权限占位代码)
|
||||
const deleteOrder = async (order: UTSJSONObject) => {
|
||||
closeDropdowns()
|
||||
// 模拟权限验证: admin-order-storeOrder-index
|
||||
const hasAuth = true // 实际项目中应通过 store 读取权限
|
||||
if (!hasAuth) {
|
||||
uni.showToast({ title: '无权限操作', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
uni.showModal({
|
||||
title: '确认删除',
|
||||
content: `确定要删除订单 ${order['sn']} 吗?删除后不可恢复。`,
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
const { error } = await supabase
|
||||
.from('ml_orders')
|
||||
.delete()
|
||||
.eq('order_no', order['sn'])
|
||||
.execute()
|
||||
|
||||
if (!error) {
|
||||
uni.showToast({ title: '删除成功' })
|
||||
fetchData()
|
||||
} else {
|
||||
uni.showToast({ title: '删除失败: ' + error.message, icon: 'none' })
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleAction = (action: string, sn: string) => {
|
||||
uni.showToast({ title: `执行操作: ${action} - ${sn}`, icon: 'none' })
|
||||
closeDropdowns()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchData()
|
||||
// 点击空白处关闭下拉
|
||||
// uni.onWindowClick(() => closeDropdowns())
|
||||
// UVUE 不支持 window click,可用 view 遮罩或在主容器加点击
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
.admin-page {
|
||||
/* 基础背景由 Layout 提供 */
|
||||
min-height: calc(100vh - 120px);
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.admin-card {
|
||||
background-color: #ffffff;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
|
||||
overflow: visible !important; /* 关键:确保内部内容不被裁切 */
|
||||
}
|
||||
|
||||
.content-card {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.filter-card {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.label {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
width: 80px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mock-select {
|
||||
width: 160px;
|
||||
height: 32px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
padding: 0 12px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
text { font-size: 14px; color: #595959; }
|
||||
}
|
||||
|
||||
.mock-date-range {
|
||||
width: 240px;
|
||||
height: 32px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
padding: 0 12px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
.cal-icon { width: 14px; height: 14px; margin-right: 8px; opacity: 0.4; }
|
||||
.placeholder { font-size: 14px; color: #bfbfbf; }
|
||||
}
|
||||
|
||||
.search-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
height: 32px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.search-select {
|
||||
width: 80px;
|
||||
border-right: 1px solid #d9d9d9;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
background-color: #fafafa;
|
||||
text { font-size: 14px; color: #595959; }
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
border: none;
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
.arrow-down {
|
||||
width: 0; height: 0;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-top: 5px solid #bfbfbf;
|
||||
}
|
||||
|
||||
.btn-row {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
height: 32px;
|
||||
padding: 0 16px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.btn-primary { background-color: #1890ff; color: #fff; border: none; }
|
||||
.btn-default { background-color: #fff; color: #595959; border: 1px solid #d9d9d9; }
|
||||
|
||||
.status-tabs {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
padding: 16px 20px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 2px;
|
||||
|
||||
.tab-text { font-size: 14px; color: #595959; }
|
||||
.tab-count { font-size: 14px; color: #595959; }
|
||||
|
||||
&.active {
|
||||
.tab-text, .tab-count { color: #1890ff; font-weight: 500; }
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute; bottom: 0; left: 0; right: 0; height: 2px; background: #1890ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
padding: 16px 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
height: 32px;
|
||||
padding: 0 16px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.btn-blue { background-color: #1890ff; color: #fff; border: none; }
|
||||
.btn-outline { background-color: #fff; color: #595959; border: 1px solid #d9d9d9; }
|
||||
|
||||
/* 表格样式 */
|
||||
.order-table {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: visible !important; /* 关键:确保垂直方向不被裁切 */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.thead {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: #f0f7ff;
|
||||
min-width: 1300px;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.th {
|
||||
padding: 12px 8px;
|
||||
font-size: 14px;
|
||||
color: #595959;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start; /* 左对齐内容 */
|
||||
}
|
||||
|
||||
.tbody {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.tr {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
min-width: 1300px;
|
||||
position: relative; /* 定位参考 */
|
||||
z-index: 10;
|
||||
overflow: visible !important; /* 关键:确保子菜单可溢出 */
|
||||
&:hover { background-color: #fafafa; z-index: 100; }
|
||||
}
|
||||
|
||||
.td {
|
||||
padding: 16px 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start; /* 左对齐内容 */
|
||||
}
|
||||
|
||||
.no-wrap {
|
||||
flex-direction: row !important;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.overflow-visible {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
/* 更多下拉菜单 */
|
||||
.op-dropdown-container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
width: 100px;
|
||||
background-color: #ffffff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
|
||||
margin-top: 5px;
|
||||
z-index: 99999; /* 极大层级 */
|
||||
border: 1px solid #f0f0f0;
|
||||
pointer-events: auto;
|
||||
display: block; /* 显式设置为 block */
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
height: 36px;
|
||||
padding: 0 12px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
transition: background-color 0.2s;
|
||||
cursor: pointer;
|
||||
|
||||
.item-text { font-size: 13px; color: #595959; }
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
}
|
||||
|
||||
.text-red { color: #ff4d4f !important; }
|
||||
|
||||
.loading-state, .empty-state {
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 列宽控制 */
|
||||
.col-expand { width: 40px; justify-content: center; align-items: center; }
|
||||
.expand-arrow { color: #bfbfbf; font-size: 18px; }
|
||||
|
||||
.col-check { width: 50px; justify-content: center; align-items: center; }
|
||||
.col-order { width: 220px; }
|
||||
.col-product { flex: 1; min-width: 300px; }
|
||||
.col-user { width: 180px; }
|
||||
.col-price { width: 100px; }
|
||||
.col-pay { width: 100px; }
|
||||
.col-time { width: 160px; }
|
||||
.col-status { width: 100px; }
|
||||
.col-op { width: 140px; }
|
||||
|
||||
.order-sn { font-size: 13px; color: #262626; margin-bottom: 4px; text-align: left; }
|
||||
.order-type { font-size: 12px; text-align: left; }
|
||||
|
||||
.product-info-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.product-info-wrap {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
gap: 12px; /* 间距拉开一点 */
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.p-img { width: 44px; height: 44px; border-radius: 4px; background-color: #f5f5f5; flex-shrink: 0; }
|
||||
.p-name { font-size: 13px; color: #595959; line-height: 1.5; }
|
||||
|
||||
.u-info { font-size: 13px; color: #595959; }
|
||||
.price-val { font-size: 14px; color: #262626; }
|
||||
.pay-text, .time-text, .status-text { font-size: 13px; color: #595959; }
|
||||
|
||||
.op-links {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.op-link { font-size: 13px; cursor: pointer; }
|
||||
.primary { color: #1890ff; }
|
||||
|
||||
.op-link-more {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.more-text { font-size: 13px; color: #1890ff; transition: color 0.1s; }
|
||||
.more-text-active { font-weight: bold; }
|
||||
|
||||
.arrow-down-blue {
|
||||
width: 0; height: 0;
|
||||
border-left: 3px solid transparent;
|
||||
border-right: 3px solid transparent;
|
||||
border-top: 4px solid #1890ff;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.arrow-up-blue {
|
||||
width: 0; height: 0;
|
||||
border-left: 3px solid transparent;
|
||||
border-right: 3px solid transparent;
|
||||
border-bottom: 4px solid #1890ff;
|
||||
}
|
||||
|
||||
/* 分页 */
|
||||
.pagination-footer {
|
||||
padding: 16px 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.page-left {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
.count-text { font-size: 14px; color: #595959; }
|
||||
}
|
||||
|
||||
.page-size-select {
|
||||
height: 28px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
text { font-size: 12px; color: #595959; }
|
||||
}
|
||||
|
||||
.page-right {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.page-num, .page-btn, .page-btns-more {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
text { font-size: 14px; color: #595959; }
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.page-num.active {
|
||||
border-color: #1890ff;
|
||||
text { color: #1890ff; }
|
||||
}
|
||||
|
||||
.page-btn.disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.page-btns-more { border: none; }
|
||||
</style>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user