admin模块接入数据库
This commit is contained in:
@@ -1,25 +1,414 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面已修复 (UTF-8)</text>
|
||||
<view class="order-management-page">
|
||||
<!-- 1. 顶部筛选卡片 -->
|
||||
<view class="filter-card">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<text class="label">订单号:</text>
|
||||
<input class="filter-input" v-model="searchOrderNo" placeholder="请输入订单号" @confirm="onSearch" />
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<text class="label">用户信息:</text>
|
||||
<input class="filter-input" v-model="searchUser" placeholder="姓名/手机号" @confirm="onSearch" />
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<text class="label">下单时间:</text>
|
||||
<AnalyticsDateRangePicker
|
||||
:initialStartDate="startDate"
|
||||
:initialEndDate="endDate"
|
||||
@apply="onApplyRange"
|
||||
@clear="onClearRange"
|
||||
/>
|
||||
</view>
|
||||
<view class="filter-btns">
|
||||
<button class="btn primary" @click="onSearch">查询</button>
|
||||
<button class="btn reset" @click="onReset">重置</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 2. 状态选项卡 -->
|
||||
<view class="status-tabs-card">
|
||||
<view class="status-tabs">
|
||||
<view
|
||||
v-for="tab in statusTabs"
|
||||
:key="tab.value"
|
||||
class="tab-item"
|
||||
:class="{ active: activeStatus === tab.value }"
|
||||
@click="onStatusChange(tab.value)"
|
||||
>
|
||||
<text class="tab-text">{{ tab.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 3. 订单列表表格 -->
|
||||
<view class="content-card">
|
||||
<view class="table-container">
|
||||
<!-- Loading 遮罩 -->
|
||||
<view v-if="isLoading" class="loading-mask">
|
||||
<text class="loading-text">订单数据加载中...</text>
|
||||
</view>
|
||||
|
||||
<view class="table-header">
|
||||
<view class="col col-no"><text>订单号</text></view>
|
||||
<view class="col col-user"><text>用户信息</text></view>
|
||||
<view class="col col-amount"><text>支付金额</text></view>
|
||||
<view class="col col-status"><text>订单状态</text></view>
|
||||
<view class="col col-time"><text>下单时间</text></view>
|
||||
<view class="col col-ops"><text>操作</text></view>
|
||||
</view>
|
||||
|
||||
<view class="table-body">
|
||||
<view v-if="orderList.length === 0 && !isLoading" class="empty-row">
|
||||
<text>未找到相关订单</text>
|
||||
</view>
|
||||
<view v-for="item in orderList" :key="item.id" class="table-row">
|
||||
<view class="col col-no">
|
||||
<text class="order-no">{{ item.order_no }}</text>
|
||||
</view>
|
||||
<view class="col col-user">
|
||||
<view class="user-info-box">
|
||||
<text class="u-name">{{ item.username || '匿名用户' }}</text>
|
||||
<text class="u-phone">{{ item.phone || '-' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="col col-amount">
|
||||
<text class="amount-val">¥{{ item.paid_amount?.toFixed(2) || '0.00' }}</text>
|
||||
</view>
|
||||
<view class="col col-status">
|
||||
<text class="status-tag" :class="getStatusClass(item.order_status)">
|
||||
{{ getStatusLabel(item.order_status) }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="col col-time">
|
||||
<text class="time-text">{{ formatDateTime(item.created_at) }}</text>
|
||||
</view>
|
||||
<view class="col col-ops">
|
||||
<text class="op-link" @click="goDetail(item.id)">详情</text>
|
||||
<text class="op-divider">|</text>
|
||||
<text class="op-link">备注</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 4. 分页控制 -->
|
||||
<view class="pagination">
|
||||
<view class="pager-btns">
|
||||
<button class="btn small" :disabled="page <= 1" @click="onPrevPage">上一页</button>
|
||||
<text class="page-num">第 {{ page }} 页</text>
|
||||
<button class="btn small" :disabled="orderList.length < pageSize" @click="onNextPage">下一页</button>
|
||||
</view>
|
||||
<text class="page-info">当前页 {{ orderList.length }} 条 / 总计 {{ total }} 条</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('order-list')
|
||||
const title = ref<string>('index')
|
||||
import AnalyticsDateRangePicker from '@/components/analytics/AnalyticsDateRangePicker.uvue'
|
||||
import { fetchOrderPage } from '@/services/orderService.uts'
|
||||
import AkReq from '@/uni_modules/ak-req/ak-req.uts'
|
||||
|
||||
const currentPage = ref<string>('OrderList')
|
||||
|
||||
// --- 数据状态 ---
|
||||
const orderList = ref<any[]>([])
|
||||
const total = ref(0)
|
||||
const isLoading = ref(false)
|
||||
|
||||
// --- 筛选条件 ---
|
||||
const searchOrderNo = ref('')
|
||||
const searchUser = ref('')
|
||||
const activeStatus = ref<number | null>(null)
|
||||
const startDate = ref('')
|
||||
const endDate = ref('')
|
||||
|
||||
const page = ref(1)
|
||||
const pageSize = 20
|
||||
|
||||
const statusTabs = [
|
||||
{ label: '全部', value: null },
|
||||
{ label: '待付款', value: 1 },
|
||||
{ label: '待发货', value: 2 },
|
||||
{ label: '待收货', value: 3 },
|
||||
{ label: '已完成', value: 4 },
|
||||
{ label: '已取消', value: 5 },
|
||||
{ label: '退款中', value: 6 },
|
||||
{ label: '已退款', value: 7 }
|
||||
]
|
||||
|
||||
onMounted(() => {
|
||||
if (AkReq.getToken() == null || AkReq.getToken() === '') return
|
||||
loadOrders()
|
||||
})
|
||||
|
||||
async function loadOrders() {
|
||||
isLoading.value = true
|
||||
try {
|
||||
const st = startDate.value ? (startDate.value + ' 00:00:00') : null
|
||||
const et = endDate.value ? (endDate.value + ' 23:59:59') : null
|
||||
|
||||
// 合并搜索词
|
||||
const combinedSearch = (searchOrderNo.value || searchUser.value)
|
||||
? `${searchOrderNo.value} ${searchUser.value}`.trim()
|
||||
: null
|
||||
|
||||
const res = await fetchOrderPage(
|
||||
page.value,
|
||||
pageSize,
|
||||
activeStatus.value,
|
||||
combinedSearch,
|
||||
st,
|
||||
et
|
||||
)
|
||||
|
||||
orderList.value = res.items
|
||||
total.value = res.total
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '加载订单失败', icon: 'none' })
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// --- 事件处理 ---
|
||||
|
||||
function onSearch() {
|
||||
page.value = 1
|
||||
loadOrders()
|
||||
}
|
||||
|
||||
function onReset() {
|
||||
searchOrderNo.value = ''
|
||||
searchUser.value = ''
|
||||
activeStatus.value = null
|
||||
startDate.value = ''
|
||||
endDate.value = ''
|
||||
page.value = 1
|
||||
loadOrders()
|
||||
}
|
||||
|
||||
function onStatusChange(status: number | null) {
|
||||
activeStatus.value = status
|
||||
page.value = 1
|
||||
loadOrders()
|
||||
}
|
||||
|
||||
function onApplyRange(payload: any) {
|
||||
startDate.value = payload?.start ?? ''
|
||||
endDate.value = payload?.end ?? ''
|
||||
page.value = 1
|
||||
loadOrders()
|
||||
}
|
||||
|
||||
function onClearRange() {
|
||||
startDate.value = ''
|
||||
endDate.value = ''
|
||||
page.value = 1
|
||||
loadOrders()
|
||||
}
|
||||
|
||||
function onPrevPage() {
|
||||
if (page.value <= 1) return
|
||||
page.value--
|
||||
loadOrders()
|
||||
}
|
||||
|
||||
function onNextPage() {
|
||||
if (orderList.value.length < pageSize) return
|
||||
page.value++
|
||||
loadOrders()
|
||||
}
|
||||
|
||||
// --- 辅助方法 ---
|
||||
|
||||
function getStatusLabel(status: number): string {
|
||||
const found = statusTabs.find(t => t.value === status)
|
||||
return found ? found.label : '未知'
|
||||
}
|
||||
|
||||
function getStatusClass(status: number): string {
|
||||
switch (status) {
|
||||
case 1: return 'status-pending'
|
||||
case 2: return 'status-shipping'
|
||||
case 3: return 'status-delivery'
|
||||
case 4: return 'status-completed'
|
||||
case 5: return 'status-cancelled'
|
||||
case 6: case 7: return 'status-refund'
|
||||
default: return ''
|
||||
}
|
||||
}
|
||||
|
||||
function formatDateTime(iso: string | null): string {
|
||||
if (!iso) return '-'
|
||||
return iso.replace('T', ' ').split('.')[0]
|
||||
}
|
||||
|
||||
function goDetail(id: string) {
|
||||
uni.showToast({ title: '详情页开发中', icon: 'none' })
|
||||
}
|
||||
</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; }
|
||||
.order-management-page {
|
||||
padding: 20px;
|
||||
background-color: #f5f7f9;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.filter-card {
|
||||
background: #fff;
|
||||
padding: 24px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
.label { font-size: 14px; color: #333; margin-right: 8px; }
|
||||
}
|
||||
|
||||
.filter-input {
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
height: 32px;
|
||||
width: 180px;
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.filter-btns {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.status-tabs-card {
|
||||
background: #fff;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.status-tabs {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
padding: 16px 20px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
.tab-text { font-size: 14px; color: #666; }
|
||||
&.active {
|
||||
.tab-text { color: #1890ff; font-weight: bold; }
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0; left: 0; right: 0;
|
||||
height: 2px; background: #1890ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content-card {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
padding: 24px;
|
||||
min-height: 400px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: #f8faff;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
padding: 16px 0;
|
||||
align-items: center;
|
||||
&:hover { background: #fafafa; }
|
||||
}
|
||||
|
||||
.col { padding: 0 12px; font-size: 14px; color: #333; display: flex; align-items: center; }
|
||||
.col-no { width: 200px; }
|
||||
.col-user { flex: 1; }
|
||||
.col-amount { width: 120px; justify-content: center; }
|
||||
.col-status { width: 120px; justify-content: center; }
|
||||
.col-time { width: 180px; justify-content: center; }
|
||||
.col-ops { width: 150px; justify-content: flex-end; }
|
||||
|
||||
.order-no { font-family: monospace; color: #1890ff; }
|
||||
.user-info-box { display: flex; flex-direction: column; gap: 4px; }
|
||||
.u-name { font-weight: 500; }
|
||||
.u-phone { font-size: 12px; color: #999; }
|
||||
.amount-val { color: #f5222d; font-weight: bold; }
|
||||
|
||||
.status-tag {
|
||||
padding: 2px 10px; border-radius: 4px; font-size: 12px;
|
||||
&.status-pending { background: #fff7e6; color: #faad14; border: 1px solid #ffe58f; }
|
||||
&.status-shipping { background: #e6f7ff; color: #1890ff; border: 1px solid #bae7ff; }
|
||||
&.status-completed { background: #f6ffed; color: #52c41a; border: 1px solid #b7eb8f; }
|
||||
&.status-cancelled { background: #f5f5f5; color: #999; border: 1px solid #d9d9d9; }
|
||||
&.status-refund { background: #fff1f0; color: #f5222d; border: 1px solid #ffa39e; }
|
||||
}
|
||||
|
||||
.op-link { color: #1890ff; cursor: pointer; }
|
||||
.op-divider { margin: 0 8px; color: #eee; }
|
||||
|
||||
.pagination {
|
||||
padding: 16px 24px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.pager-btns { display: flex; flex-direction: row; align-items: center; gap: 12px; }
|
||||
.page-num { font-size: 14px; color: #333; }
|
||||
.page-info { font-size: 14px; color: #999; }
|
||||
|
||||
.loading-mask {
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
z-index: 10;
|
||||
}
|
||||
.empty-row { padding: 60px 0; text-align: center; color: #999; }
|
||||
|
||||
.btn {
|
||||
height: 32px; padding: 0 16px; font-size: 14px; border-radius: 4px; cursor: pointer;
|
||||
&.primary { background: #1890ff; color: #fff; border: none; }
|
||||
&.reset { background: #fff; color: #666; border: 1px solid #d9d9d9; }
|
||||
&.small { height: 28px; padding: 0 12px; font-size: 13px; }
|
||||
&[disabled] { opacity: 0.5; cursor: not-allowed; }
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user