Files
medical-mall/pages/mall/admin/order/order-management/index.uvue

1028 lines
30 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<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>
<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="fetchError != ''" class="error-state">
<text class="error-text">{{ fetchError }}</text>
<button class="retry-btn" @click="fetchData">重新加载</button>
</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 class="order-time">{{ item['created_at'] }}</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" :style="{ color: item['statusColor'] }">{{ item['statusName'] }}</text>
</view>
<!-- 操作 -->
<view class="td col-op overflow-visible no-wrap">
<view class="op-links">
<text class="op-link primary" @click.stop="viewDetail(item)">详情</text>
<template v-if="(item['orderStatus'] as number) === 2">
<view class="divider-v"></view>
<text class="op-link primary" @click.stop="shipOrder(item)">发货</text>
</template>
<view class="divider-v"></view>
<view class="op-dropdown-container" @mouseenter="activeDropdownId = (item['sn'] as string)" @mouseleave="activeDropdownId = ''">
<view class="op-link-more">
<text 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="handleMore('print_receipt', item)">
<text class="item-text">小票打印</text>
</view>
<view class="dropdown-item" @click.stop="handleMore('edit_address', item)">
<text class="item-text">修改地址</text>
</view>
<view class="dropdown-item" @click.stop="handleMore('remark', item)">
<text class="item-text">订单备注</text>
</view>
<view class="dropdown-item" @click.stop="handleMore('refund', item)">
<text class="item-text">立即退款</text>
</view>
<view class="dropdown-item item-danger" @click.stop="deleteOrder(item)">
<text class="item-text text-red">删除订单</text>
</view>
<view class="dropdown-item" @click.stop="handleMore('packing_slip', item)">
<text class="item-text">配货单打印</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 分页 -->
<view class="pagination-footer">
<view class="page-left">
<text class="count-text">共 {{ filteredOrders.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, 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 fetchError = ref('')
// 时间显示格式化
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 getStatusColor = (status : number) : string => {
if (status === 1) return '#faad14'
if (status === 2) return '#1890ff'
if (status === 3) return '#1890ff'
if (status === 4) return '#52c41a'
if (status === 5) return '#999999'
if (status === 6) return '#fa8c16'
if (status === 7) return '#f5222d'
return '#333333'
}
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'] != null ? (o['sn'] as string).toLowerCase() : ''
const user = o['user'] as UTSJSONObject
const phone = (user != null && user['phone'] != null) ? (user['phone'] as string) : ''
return sn.includes(kw) || phone.includes(kw)
})
}
return list
})
const fetchData = async () => {
loading.value = true
fetchError.value = ''
// 获取当前商家 IDmerchant_id = ak_users.id = Supabase Auth UUID
const currentMerchantId = supabase.getSession().user?.getString('id')
if (currentMerchantId == null || currentMerchantId == '') {
loading.value = false
fetchError.value = '未获取到商家身份信息,请重新登录后再试'
return
}
try {
const res = await supabase
.from('ml_orders_detail_view')
.select('*')
.eq('merchant_id', currentMerchantId)
.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 => {
// 解析 shipping_address JSONB含 receiver_name/receiver_phone/province/city/district/address_detail
const rawAddr = item['shipping_address']
let receiverName = item['customer_name'] != null ? (item['customer_name'] as string) : ''
let receiverPhone = item['customer_phone'] != null ? (item['customer_phone'] as string) : ''
let fullAddress = '暂无地址信息'
if (rawAddr != null) {
const addr = rawAddr as UTSJSONObject
const rn = addr['receiver_name']
const rp = addr['receiver_phone']
if (rn != null && rn !== '') receiverName = rn as string
if (rp != null && rp !== '') receiverPhone = rp as string
const prov = addr['province'] != null ? (addr['province'] as string) : ''
const cit = addr['city'] != null ? (addr['city'] as string) : ''
const dist = addr['district'] != null ? (addr['district'] as string) : ''
const det = addr['address_detail'] != null ? (addr['address_detail'] as string) : ''
const combined = prov + cit + dist + det
if (combined !== '') fullAddress = combined
}
const orderStatusNum = item['order_status'] as number
// cancelStatus取消原因展示
let cancelStatus = ''
if (orderStatusNum === 5) {
const reason = item['cancel_reason']
cancelStatus = (reason != null && reason !== '') ? ('已取消:' + (reason as string)) : '用户已取消'
}
return {
id: item['id'] != null ? (item['id'] as string) : '',
sn: item['order_no'] != null ? item['order_no'] : '--',
typeName: '普通订单',
typeColor: 'green',
orderStatus: orderStatusNum,
cancelStatus: cancelStatus,
product: {
img: '/static/logo.png',
name: '订单详情查看'
} as UTSJSONObject,
items: null, // ml_orders_detail_view 视图未聚合 order_items详情抽屉另行处理
user: {
name: (item['customer_name'] || '未知用户') as string,
id: (item['user_id'] || '--') as string,
phone: receiverPhone != '' ? receiverPhone : (item['customer_phone'] != null ? (item['customer_phone'] as string) : '--')
} as UTSJSONObject,
isPaid: item['payment_status'] === 2,
actualPrice: item['paid_amount'] != null ? item['paid_amount'] : 0, // 已付金额(修正:原为 total_amount
paidAmount: item['paid_amount'] != null ? item['paid_amount'] : 0,
payMethod: item['payment_status_name'] != null ? (item['payment_status_name'] as string) : '--', // 支付状态ml_orders 表无支付方式字段)
payTime: formatTime(item['paid_at'] as string | null),
created_at: formatTime(item['created_at'] as string | null),
statusName: item['order_status_name'] != null ? (item['order_status_name'] as string) : '未知',
statusColor: getStatusColor(orderStatusNum),
primaryAction: orderStatusNum === 1 ? '立即支付' : '',
total_num: 0,
total_price: item['product_amount'] != null ? item['product_amount'] : 0,
coupon_price: item['discount_amount'] != null ? item['discount_amount'] : 0,
shipping_fee: item['shipping_fee'] != null ? item['shipping_fee'] : 0,
deduction_price: 0,
user_address: fullAddress,
real_name: receiverName,
user_phone: receiverPhone,
delivery_name: item['merchant_name'] != null ? (item['merchant_name'] as string) : '--',
store_name: item['shop_name'] != null ? (item['shop_name'] as string) : '--',
mark: item['remark'] != null ? (item['remark'] as string) : '-',
remark: item['merchant_memo'] != null ? (item['merchant_memo'] as string) : '-',
shipped_at: item['shipped_at'] != null ? (item['shipped_at'] as string) : '',
delivered_at: item['delivered_at'] != null ? (item['delivered_at'] as string) : '',
completed_at: item['completed_at'] != null ? (item['completed_at'] as string) : ''
} as UTSJSONObject
})
// 更新统计数据
updateTabCounts()
} else {
fetchError.value = '加载订单失败,请检查网络后重试'
console.error('Fetch orders error:', res.error)
}
} catch (e) {
fetchError.value = '加载订单时发生异常,请检查网络后重试'
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 shipOrder = async (order: UTSJSONObject) => {
uni.showModal({
title: '确认发货',
content: `确定将订单 ${order['sn']} 标记为已发货?`,
success: async (res) => {
if (res.confirm) {
try {
const result = await supabase
.from('ml_orders')
.update({ order_status: 3, shipping_status: 2, shipped_at: new Date().toISOString() } as UTSJSONObject)
.eq('order_no', order['sn'] as string)
.execute()
if (result.error == null) {
uni.showToast({ title: '发货成功' })
fetchData()
} else {
uni.showToast({ title: '操作失败', icon: 'none' })
}
} catch (e) {
console.error('shipOrder error:', e)
}
}
}
})
}
// 更多菜单操作(功能待接入)
const handleMore = (_action: string, _order: UTSJSONObject) => {
closeDropdowns()
uni.showToast({ title: '功能开发中', icon: 'none' })
}
// 删除订单 (权限占位代码)
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)
}
}
}
})
}
onMounted(() => {
fetchData()
// 点击空白处关闭下拉
// uni.onWindowClick(() => closeDropdowns())
// UVUE 不支持 window click可用 view 遮罩或在主容器加点击
})
</script>
<style scoped lang="scss">
.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: 160px; }
.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;
}
.op-link { font-size: 13px; cursor: pointer; }
.primary { color: #1890ff; padding-right: 8px; }
.divider-v {
width: 1px;
height: 12px;
background-color: #dcdfe6;
margin: 0 8px;
}
.op-dropdown-container {
position: relative;
display: flex;
flex-direction: row;
align-items: center;
cursor: pointer;
padding-left: 0px;
}
.op-link-more {
display: flex;
flex-direction: row;
align-items: center;
gap: 4px;
}
.more-text { font-size: 13px; color: #1890ff; }
.dropdown-menu {
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
width: 110px;
background-color: #ffffff;
border-radius: 4px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
margin-top: 8px;
z-index: 100000;
border: 1px solid #ebeef5;
display: flex;
flex-direction: column;
}
.dropdown-item {
height: 32px;
padding: 0 16px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
transition: background-color 0.2s;
cursor: pointer;
.item-text { font-size: 13px; color: #606266; text-align: center; }
&:hover {
background-color: #f5f7fa;
.item-text { color: #1890ff; }
}
}
.item-danger:hover {
.item-text { color: #ff4d4f !important; }
}
.text-red { color: #ff4d4f !important; }
/* 箭头 */
.arrow-down-blue {
width: 0; height: 0;
border-left: 3px solid transparent;
border-right: 3px solid transparent;
border-top: 4px solid #1890ff;
margin-top: 1px;
}
.arrow-up-blue {
width: 0; height: 0;
border-left: 3px solid transparent;
border-right: 3px solid transparent;
border-bottom: 4px solid #1890ff;
margin-bottom: 3px;
}
.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; }
.order-time {
font-size: 12px;
color: #999;
margin-top: 2px;
display: block;
}
.price-unpaid {
color: #f5222d;
}
.error-state {
padding: 40px 20px;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
}
.error-text {
font-size: 14px;
color: #f5222d;
}
.retry-btn {
height: 32px;
padding: 0 16px;
font-size: 14px;
border-radius: 4px;
background-color: #fff;
color: #1890ff;
border: 1px solid #1890ff;
margin: 0;
}
</style>