feat(admin): full repair of order module including list, statistics, aftersales, cashier, and write-off records with real RPC integration
This commit is contained in:
52
docs/ops/2026-02-10__admin__order-module-repaired-full.md
Normal file
52
docs/ops/2026-02-10__admin__order-module-repaired-full.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# 订单模块全量修复与功能补全报告 (Stage A+B+C)
|
||||||
|
|
||||||
|
## 摘要
|
||||||
|
本次对 Admin 侧订单模块进行了深度的端到端修复,涵盖了核销记录、售后订单、收银订单、订单统计、订单配置及主列表。彻底解决了从 Mock 数据到真实数据库 RPC 接入的断层,并补齐了缺失的数据库字段。
|
||||||
|
|
||||||
|
## 修复范围 (Stage A+B+C)
|
||||||
|
|
||||||
|
### Stage A: 核销记录模块
|
||||||
|
- **UI 重构**:完全移除 `write-off-records/index.uvue` 的 Mock 数据。
|
||||||
|
- **功能补全**:接入 `rpc_admin_write_off_record_list`,实现真实的分页、搜索及状态展示。
|
||||||
|
|
||||||
|
### Stage B: 售后/收银/配置模块
|
||||||
|
- **售后订单**:修正了表头“实际支付”为“退款金额”的歧义,补齐了退款状态真实筛选逻辑。
|
||||||
|
- **收银订单**:重构为调用 `orderService.fetchCashierOrderPage`,对齐支付与用户信息字段。
|
||||||
|
- **订单配置**:将原本的 UI 模拟改为真实的 `get/save` 持久化逻辑,对接系统配置表。
|
||||||
|
|
||||||
|
### Stage C: 扩展字段与统计升级
|
||||||
|
- **Schema 补全**:通过 `ml_orders_schema_update_v1.sql` 补齐了 `pay_type`(支付方式)和 `channel_type`(订单渠道)字段。
|
||||||
|
- **RPC 升级**:
|
||||||
|
- `rpc_admin_order_list`:现在返回真实的支付和渠道信息。
|
||||||
|
- `rpc_admin_order_source_stats`:从 `unknown` 汇总升级为真实的按渠道分组统计。
|
||||||
|
- `rpc_admin_order_type_stats`:新增订单类型分析统计(普通/收银/核销)。
|
||||||
|
- **页面对齐**:列表页和统计页现在展示真实的“微信支付”、“小程序”等业务标签。
|
||||||
|
|
||||||
|
## 变更清单
|
||||||
|
|
||||||
|
### 数据库/RPC (SQL)
|
||||||
|
- `docs/sql/10_schema/order/ml_orders_schema_update_v1.sql` (新增)
|
||||||
|
- `docs/sql/30_rpc/order/rpc_admin_order_list_v1.sql` (升级)
|
||||||
|
- `docs/sql/30_rpc/order/rpc_admin_order_stats_v1.sql` (升级)
|
||||||
|
- `docs/sql/30_rpc/order/rpc_admin_order_trend_v1.sql` (升级)
|
||||||
|
- `docs/sql/30_rpc/order/rpc_admin_order_source_stats_v1.sql` (升级)
|
||||||
|
- `docs/sql/30_rpc/order/rpc_admin_order_type_stats_v1.sql` (新增)
|
||||||
|
|
||||||
|
### 前端代码
|
||||||
|
- `services/orderService.uts` (补全统计与列表方法)
|
||||||
|
- `pages/mall/admin/order/list.uvue` (逻辑与 UI 重构)
|
||||||
|
- `pages/mall/admin/order/order-statistics/index.uvue` (去 Mock 与 ECharts 动态驱动)
|
||||||
|
- `pages/mall/admin/order/aftersales-order/index.uvue` (标签修正与筛选逻辑)
|
||||||
|
- `pages/mall/admin/order/cashier-order/index.uvue` (RPC 接入与字段对齐)
|
||||||
|
- `pages/mall/admin/order/write-off-records/index.uvue` (完全重构接入)
|
||||||
|
- `pages/mall/admin/order/order-configuration/index.uvue` (配置持久化闭环)
|
||||||
|
|
||||||
|
## 验证说明
|
||||||
|
1. **数据库**:需依次执行 `10_schema` 下的补丁和 `30_rpc` 下的所有订单相关 SQL。
|
||||||
|
2. **功能**:
|
||||||
|
- 订单管理列表:检查 Tabs 切换、搜索、分页是否联动后端。
|
||||||
|
- 订单统计:观察趋势图和饼图是否不再是 30 天固定静态值。
|
||||||
|
- 各子模块:确保进入页面后 Loading 结束能展示真实记录。
|
||||||
|
|
||||||
|
## 风险与后续
|
||||||
|
- **支付方式映射**:当前 UI 按 1:余额, 2:微信, 3:支付宝, 4:线下 进行了硬编码映射,若后续支付方式变更,需同步更新 `list.uvue` 的映射函数。
|
||||||
@@ -26,4 +26,16 @@ BEGIN
|
|||||||
COMMENT ON COLUMN public.ml_orders.verifier_id IS '核销员ID';
|
COMMENT ON COLUMN public.ml_orders.verifier_id IS '核销员ID';
|
||||||
END IF;
|
END IF;
|
||||||
|
|
||||||
|
-- 4. 补齐 pay_type (支付方式: 1:余额, 2:微信, 3:支付宝, 4:线下支付)
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = 'ml_orders' AND column_name = 'pay_type') THEN
|
||||||
|
ALTER TABLE public.ml_orders ADD COLUMN pay_type INTEGER DEFAULT 1;
|
||||||
|
COMMENT ON COLUMN public.ml_orders.pay_type IS '支付方式: 1:余额, 2:微信, 3:支付宝, 4:线下支付';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- 5. 补齐 channel_type (订单渠道: 1:公众号, 2:小程序, 3:H5, 4:PC, 5:APP)
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = 'ml_orders' AND column_name = 'channel_type') THEN
|
||||||
|
ALTER TABLE public.ml_orders ADD COLUMN channel_type INTEGER DEFAULT 1;
|
||||||
|
COMMENT ON COLUMN public.ml_orders.channel_type IS '订单渠道: 1:公众号, 2:小程序, 3:H5, 4:PC, 5:APP';
|
||||||
|
END IF;
|
||||||
|
|
||||||
END $$;
|
END $$;
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ BEGIN
|
|||||||
o.order_status,
|
o.order_status,
|
||||||
o.payment_status,
|
o.payment_status,
|
||||||
o.shipping_status,
|
o.shipping_status,
|
||||||
|
o.pay_type,
|
||||||
|
o.channel_type,
|
||||||
o.paid_at,
|
o.paid_at,
|
||||||
o.created_at,
|
o.created_at,
|
||||||
u.username as buyer_name,
|
u.username as buyer_name,
|
||||||
|
|||||||
@@ -28,16 +28,26 @@ BEGIN
|
|||||||
RAISE EXCEPTION 'Permission denied';
|
RAISE EXCEPTION 'Permission denied';
|
||||||
END IF;
|
END IF;
|
||||||
|
|
||||||
-- 2. 最小可用:按 unknown 聚合(排除已取消)
|
-- 2. 按渠道类型聚合统计(排除已取消)
|
||||||
SELECT jsonb_agg(t) INTO v_items
|
SELECT jsonb_agg(t) INTO v_items
|
||||||
FROM (
|
FROM (
|
||||||
SELECT
|
SELECT
|
||||||
'unknown'::text AS source,
|
CASE o.channel_type
|
||||||
COUNT(*) FILTER (WHERE o.order_status != 5) AS order_count,
|
WHEN 1 THEN '公众号'
|
||||||
COALESCE(SUM(o.total_amount) FILTER (WHERE o.order_status != 5), 0) AS total_amount
|
WHEN 2 THEN '小程序'
|
||||||
|
WHEN 3 THEN 'H5'
|
||||||
|
WHEN 4 THEN 'PC'
|
||||||
|
WHEN 5 THEN 'APP'
|
||||||
|
ELSE '其他'
|
||||||
|
END AS source,
|
||||||
|
COUNT(*) AS order_count,
|
||||||
|
COALESCE(SUM(o.total_amount), 0) AS total_amount
|
||||||
FROM public.ml_orders o
|
FROM public.ml_orders o
|
||||||
WHERE o.created_at >= p_start_time
|
WHERE o.created_at >= p_start_time
|
||||||
AND o.created_at <= p_end_time
|
AND o.created_at <= p_end_time
|
||||||
|
AND o.order_status != 5
|
||||||
|
GROUP BY o.channel_type
|
||||||
|
ORDER BY order_count DESC
|
||||||
) t;
|
) t;
|
||||||
|
|
||||||
RETURN COALESCE(v_items, '[]'::jsonb);
|
RETURN COALESCE(v_items, '[]'::jsonb);
|
||||||
|
|||||||
60
docs/sql/30_rpc/order/rpc_admin_order_type_stats_v1.sql
Normal file
60
docs/sql/30_rpc/order/rpc_admin_order_type_stats_v1.sql
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
-- =====================================================================================
|
||||||
|
-- Admin 订单统计 - 订单类型分布统计 RPC
|
||||||
|
-- 位置:docs/sql/30_rpc/order/
|
||||||
|
-- 对象类型:RPC 函数(SECURITY DEFINER)
|
||||||
|
-- 版本:v1
|
||||||
|
-- 说明:按订单类型(普通、收银、核销)统计指定时间段内的销售额及其占比
|
||||||
|
-- =====================================================================================
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.rpc_admin_order_type_stats(
|
||||||
|
p_start_time TIMESTAMPTZ,
|
||||||
|
p_end_time TIMESTAMPTZ
|
||||||
|
)
|
||||||
|
RETURNS JSONB
|
||||||
|
SECURITY DEFINER
|
||||||
|
SET search_path = public
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
v_total_amount DECIMAL(12,2);
|
||||||
|
v_items JSONB;
|
||||||
|
BEGIN
|
||||||
|
-- 1. 权限检查
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM public.ak_users
|
||||||
|
WHERE auth_id = auth.uid() AND role IN ('admin', 'analytics')
|
||||||
|
) THEN
|
||||||
|
RAISE EXCEPTION 'Permission denied';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- 2. 计算总销售额(用于算占比)
|
||||||
|
SELECT COALESCE(SUM(total_amount), 0) INTO v_total_amount
|
||||||
|
FROM public.ml_orders
|
||||||
|
WHERE created_at >= p_start_time AND created_at <= p_end_time
|
||||||
|
AND order_status != 5; -- 排除已取消
|
||||||
|
|
||||||
|
-- 3. 按类型统计
|
||||||
|
SELECT jsonb_agg(t) INTO v_items
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
CASE o.order_type
|
||||||
|
WHEN 1 THEN '普通订单'
|
||||||
|
WHEN 2 THEN '收银订单'
|
||||||
|
WHEN 3 THEN '核销订单'
|
||||||
|
ELSE '其他类型'
|
||||||
|
END AS name,
|
||||||
|
COALESCE(SUM(o.total_amount), 0) AS amount,
|
||||||
|
CASE
|
||||||
|
WHEN v_total_amount > 0 THEN ROUND((COALESCE(SUM(o.total_amount), 0) / v_total_amount * 100), 2)
|
||||||
|
ELSE 0
|
||||||
|
END AS rate
|
||||||
|
FROM public.ml_orders o
|
||||||
|
WHERE o.created_at >= p_start_time AND o.created_at <= p_end_time
|
||||||
|
AND o.order_status != 5
|
||||||
|
GROUP BY o.order_type
|
||||||
|
ORDER BY amount DESC
|
||||||
|
) t;
|
||||||
|
|
||||||
|
RETURN COALESCE(v_items, '[]'::jsonb);
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
@@ -5,10 +5,12 @@
|
|||||||
<view class="filter-card border-shadow">
|
<view class="filter-card border-shadow">
|
||||||
<view class="filter-item">
|
<view class="filter-item">
|
||||||
<text class="label-txt">退款状态:</text>
|
<text class="label-txt">退款状态:</text>
|
||||||
<view class="select-mock">
|
<picker :value="statusIndex" :range="statusOptions" range-key="label" @change="onStatusChange">
|
||||||
<text class="select-val">全部</text>
|
<view class="select-mock">
|
||||||
<text class="arrow-down">▼</text>
|
<text class="select-val">{{ statusOptions[statusIndex].label }}</text>
|
||||||
</view>
|
<text class="arrow-down">▼</text>
|
||||||
|
</view>
|
||||||
|
</picker>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="filter-item">
|
<view class="filter-item">
|
||||||
@@ -39,7 +41,7 @@
|
|||||||
<view class="th" style="width: 180px;">原订单号</view>
|
<view class="th" style="width: 180px;">原订单号</view>
|
||||||
<view class="th" style="flex: 1.5;">商品信息</view>
|
<view class="th" style="flex: 1.5;">商品信息</view>
|
||||||
<view class="th" style="width: 120px;">用户信息</view>
|
<view class="th" style="width: 120px;">用户信息</view>
|
||||||
<view class="th" style="width: 100px;">实际支付</view>
|
<view class="th" style="width: 100px;">退款金额</view>
|
||||||
<view class="th" style="width: 160px;">发起退款时间</view>
|
<view class="th" style="width: 160px;">发起退款时间</view>
|
||||||
<view class="th" style="width: 100px;">退款状态</view>
|
<view class="th" style="width: 100px;">退款状态</view>
|
||||||
<view class="th" style="width: 100px;">订单状态</view>
|
<view class="th" style="width: 100px;">订单状态</view>
|
||||||
@@ -132,6 +134,20 @@ const totalPages = computed((): number => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const refundStatusFilter = ref<number | null>(null)
|
const refundStatusFilter = ref<number | null>(null)
|
||||||
|
const statusIndex = ref(0)
|
||||||
|
const statusOptions = [
|
||||||
|
{ label: '全部', value: null as number | null },
|
||||||
|
{ label: '申请中', value: 1 as number | null },
|
||||||
|
{ label: '已同意', value: 2 as number | null },
|
||||||
|
{ label: '已拒绝', value: 3 as number | null },
|
||||||
|
{ label: '已完成', value: 4 as number | null }
|
||||||
|
]
|
||||||
|
|
||||||
|
const onStatusChange = (e : any) => {
|
||||||
|
statusIndex.value = e.detail.value as number
|
||||||
|
refundStatusFilter.value = statusOptions[statusIndex.value].value
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
|
||||||
const loadRefundOrders = async () => {
|
const loadRefundOrders = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|||||||
@@ -15,12 +15,12 @@
|
|||||||
|
|
||||||
<view class="filter-item">
|
<view class="filter-item">
|
||||||
<text class="label-txt">订单号:</text>
|
<text class="label-txt">订单号:</text>
|
||||||
<input class="search-input" placeholder="请输入订单号" v-model="orderId" />
|
<input class="search-input" placeholder="请输入订单号" v-model="orderId" @confirm="handleQuery" />
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="filter-item">
|
<view class="filter-item">
|
||||||
<text class="label-txt">用户名:</text>
|
<text class="label-txt">用户名:</text>
|
||||||
<input class="search-input" placeholder="请输入用户名" v-model="username" />
|
<input class="search-input" placeholder="请输入用户名" v-model="username" @confirm="handleQuery" />
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="btn-query" @click="handleQuery">
|
<view class="btn-query" @click="handleQuery">
|
||||||
@@ -47,11 +47,17 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="table-body">
|
<view class="table-body">
|
||||||
<view v-for="(item, index) in orderList" :key="index" class="table-row">
|
<view v-if="loading" class="table-loading" style="padding: 40px; text-align: center;">
|
||||||
|
<text>加载中...</text>
|
||||||
|
</view>
|
||||||
|
<view v-else-if="orderList.length === 0" class="table-empty" style="padding: 40px; text-align: center;">
|
||||||
|
<text>暂无收银订单</text>
|
||||||
|
</view>
|
||||||
|
<view v-else v-for="(item, index) in orderList" :key="index" class="table-row">
|
||||||
<view class="td" style="flex: 1.5;"><text class="td-txt">{{ item.orderId }}</text></view>
|
<view class="td" style="flex: 1.5;"><text class="td-txt">{{ item.orderId }}</text></view>
|
||||||
<view class="td" style="flex: 1.2;"><text class="td-txt">{{ item.userInfo }}</text></view>
|
<view class="td" style="flex: 1.2;"><text class="td-txt">{{ item.userInfo }}</text></view>
|
||||||
<view class="td" style="width: 150px;"><text class="td-txt">{{ item.payPrice.toFixed(2) }}</text></view>
|
<view class="td" style="width: 150px;"><text class="td-txt">¥{{ item.payPrice.toFixed(2) }}</text></view>
|
||||||
<view class="td" style="width: 150px;"><text class="td-txt">{{ item.discountPrice.toFixed(2) }}</text></view>
|
<view class="td" style="width: 150px;"><text class="td-txt">¥{{ item.discountPrice.toFixed(2) }}</text></view>
|
||||||
<view class="td" style="width: 200px;"><text class="td-txt">{{ item.payTime }}</text></view>
|
<view class="td" style="width: 200px;"><text class="td-txt">{{ item.payTime }}</text></view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -62,17 +68,20 @@
|
|||||||
<view class="page-total">
|
<view class="page-total">
|
||||||
<text class="total-txt">共 {{ total }} 条</text>
|
<text class="total-txt">共 {{ total }} 条</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="page-select">
|
|
||||||
<text class="page-val">15条/页 ▼</text>
|
|
||||||
</view>
|
|
||||||
<view class="page-btns">
|
<view class="page-btns">
|
||||||
<text :class="['p-btn', page <= 1 ? 'disabled' : '']" @click="prevPage"><</text>
|
<view class="page-btn" :class="{ disabled: page <= 1 }" @click="prevPage">
|
||||||
<text class="p-btn active">{{ page }}</text>
|
<text><</text>
|
||||||
<text :class="['p-btn', page >= totalPages ? 'disabled' : '']" @click="nextPage">></text>
|
</view>
|
||||||
|
<view class="page-btn active">
|
||||||
|
<text>{{ page }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="page-btn" :class="{ disabled: page >= totalPages }" @click="nextPage">
|
||||||
|
<text>></text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="page-jump">
|
<view class="page-jump">
|
||||||
<text class="jump-txt">前往</text>
|
<text class="jump-txt">前往</text>
|
||||||
<input class="jump-input" placeholder="1" v-model="jumpPage" @confirm="goToJumpPage" />
|
<input class="jump-input" v-model="jumpPage" type="number" @confirm="goToJumpPage" />
|
||||||
<text class="jump-txt">页</text>
|
<text class="jump-txt">页</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -120,15 +129,13 @@ const username = ref('')
|
|||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
const orderList = ref<CashierOrder[]>([])
|
const orderList = ref<CashierOrder[]>([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
||||||
const page = ref(1)
|
const page = ref(1)
|
||||||
const pageSize = ref(15)
|
const pageSize = ref(15)
|
||||||
const jumpPage = ref('')
|
const jumpPage = ref('')
|
||||||
|
|
||||||
const totalPages = computed((): number => {
|
const totalPages = computed((): number => {
|
||||||
if (pageSize.value <= 0) return 1
|
if (pageSize.value <= 0) return 1
|
||||||
const pages = Math.ceil(total.value / pageSize.value)
|
return Math.ceil(total.value / pageSize.value)
|
||||||
return pages <= 0 ? 1 : pages
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const loadCashierOrders = async () => {
|
const loadCashierOrders = async () => {
|
||||||
@@ -143,17 +150,16 @@ const loadCashierOrders = async () => {
|
|||||||
|
|
||||||
orderList.value = res.items.map((item: any): CashierOrder => {
|
orderList.value = res.items.map((item: any): CashierOrder => {
|
||||||
return {
|
return {
|
||||||
orderId: String(item.order_no ?? '--'),
|
orderId: String(item.order_no),
|
||||||
userInfo: `${String(item.customer_name ?? '未知')} | ${String(item.customer_phone ?? '')}`,
|
userInfo: `${String(item.customer_name ?? '未知')} | ${String(item.customer_phone ?? '')}`,
|
||||||
payPrice: parseFloat(String(item.total_amount ?? item.pay_amount ?? '0')),
|
payPrice: parseFloat(String(item.total_amount ?? '0')),
|
||||||
discountPrice: parseFloat(String(item.discount_amount ?? '0')),
|
discountPrice: parseFloat(String(item.discount_amount ?? '0')),
|
||||||
payTime: String(item.paid_at ?? '--')
|
payTime: String(item.paid_at ?? '--')
|
||||||
} as CashierOrder
|
} as CashierOrder
|
||||||
})
|
})
|
||||||
|
|
||||||
total.value = res.total
|
total.value = res.total
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
uni.showToast({ title: '收银订单加载失败', icon: 'none' })
|
uni.showToast({ title: '加载收银订单失败', icon: 'none' })
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
@@ -189,7 +195,7 @@ const goToJumpPage = () => {
|
|||||||
loadCashierOrders()
|
loadCashierOrders()
|
||||||
jumpPage.value = ''
|
jumpPage.value = ''
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({ title: '请输入有效的页码', icon: 'none' })
|
uni.showToast({ title: '页码无效', icon: 'none' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,9 +351,8 @@ const closeQrModal = () => {
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
.total-txt { font-size: 14px; color: #606266; }
|
.total-txt { font-size: 14px; color: #606266; }
|
||||||
.page-val { font-size: 14px; color: #606266; border: 1px solid #dcdfe6; padding: 4px 10px; border-radius: 4px; }
|
|
||||||
.page-btns { display: flex; flex-direction: row; gap: 8px; }
|
.page-btns { display: flex; flex-direction: row; gap: 8px; }
|
||||||
.p-btn {
|
.page-btn {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
border: 1px solid #dcdfe6;
|
border: 1px solid #dcdfe6;
|
||||||
@@ -356,9 +361,10 @@ const closeQrModal = () => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.p-btn.active { background-color: #2d8cf0; border-color: #2d8cf0; color: #fff; }
|
.page-btn.active { background-color: #2d8cf0; border-color: #2d8cf0; color: #fff; }
|
||||||
.p-btn.disabled { color: #c0c4cc; background-color: #f5f7fa; }
|
.page-btn.disabled { color: #c0c4cc; background-color: #f5f7fa; cursor: not-allowed; }
|
||||||
|
|
||||||
.page-jump { display: flex; flex-direction: row; align-items: center; gap: 8px; }
|
.page-jump { display: flex; flex-direction: row; align-items: center; gap: 8px; }
|
||||||
.jump-txt { font-size: 14px; color: #606266; }
|
.jump-txt { font-size: 14px; color: #606266; }
|
||||||
@@ -453,5 +459,3 @@ const closeQrModal = () => {
|
|||||||
to { background-color: rgba(0, 0, 0, 0); }
|
to { background-color: rgba(0, 0, 0, 0); }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,7 @@
|
|||||||
<!-- 订单号|类型 -->
|
<!-- 订单号|类型 -->
|
||||||
<view class="td col-order">
|
<view class="td col-order">
|
||||||
<text class="order-sn">{{ item.order_no }}</text>
|
<text class="order-sn">{{ item.order_no }}</text>
|
||||||
<text class="order-type blue">[普通订单]</text>
|
<text class="order-type blue">[{{ getChannelName(item.channel_type) }}]</text>
|
||||||
</view>
|
</view>
|
||||||
<!-- 商品信息 -->
|
<!-- 商品信息 -->
|
||||||
<view class="td col-product">
|
<view class="td col-product">
|
||||||
@@ -113,7 +113,7 @@
|
|||||||
</view>
|
</view>
|
||||||
<!-- 支付方式 -->
|
<!-- 支付方式 -->
|
||||||
<view class="td col-pay">
|
<view class="td col-pay">
|
||||||
<text class="pay-text">余额支付</text>
|
<text class="pay-text">{{ getPayTypeName(item.pay_type) }}</text>
|
||||||
</view>
|
</view>
|
||||||
<!-- 支付时间 -->
|
<!-- 支付时间 -->
|
||||||
<view class="td col-time">
|
<view class="td col-time">
|
||||||
@@ -252,6 +252,27 @@ function getStatusName(status: number): string {
|
|||||||
const tab = statusTabs.find(t => t.value === status)
|
const tab = statusTabs.find(t => t.value === status)
|
||||||
return tab?.name ?? '未知'
|
return tab?.name ?? '未知'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPayTypeName(type: number | null): string {
|
||||||
|
switch (type) {
|
||||||
|
case 1: return '余额支付'
|
||||||
|
case 2: return '微信支付'
|
||||||
|
case 3: return '支付宝'
|
||||||
|
case 4: return '线下支付'
|
||||||
|
default: return '其他'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getChannelName(type: number | null): string {
|
||||||
|
switch (type) {
|
||||||
|
case 1: return '公众号'
|
||||||
|
case 2: return '小程序'
|
||||||
|
case 3: return 'H5'
|
||||||
|
case 4: return 'PC'
|
||||||
|
case 5: return 'APP'
|
||||||
|
default: return '普通订单'
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -249,23 +249,24 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="uts">
|
<script setup lang="uts">
|
||||||
import { ref, reactive } from 'vue'
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
|
import { getOrderSettings, saveOrderSettings } from '@/services/orderService.uts'
|
||||||
|
|
||||||
const tabs = ['包邮设置', '发票功能配置', '售后退款配置', '订单取消配置', '自动收货配置', '自动评价配置', '到店自提配置', '警戒库存配置']
|
const tabs = ['包邮设置', '发票功能配置', '售后退款配置', '订单取消配置', '自动收货配置', '自动评价配置', '到店自提配置', '警戒库存配置']
|
||||||
const currentTab = ref(0)
|
const currentTab = ref(0)
|
||||||
|
|
||||||
const config = reactive({
|
const config = reactive({
|
||||||
// 1. 包邮设置
|
// 1. 包邮设置
|
||||||
freeShippingPrice: 1000000,
|
freeShippingPrice: 0,
|
||||||
offlineFreeShipping: false,
|
offlineFreeShipping: false,
|
||||||
// 2. 发票功能配置
|
// 2. 发票功能配置
|
||||||
invoiceEnabled: true,
|
invoiceEnabled: false,
|
||||||
specialInvoiceEnabled: true,
|
specialInvoiceEnabled: false,
|
||||||
// 3. 售后退款配置
|
// 3. 售后退款配置
|
||||||
refundContactName: '',
|
refundContactName: '',
|
||||||
refundContactPhone: '',
|
refundContactPhone: '',
|
||||||
refundAddress: '',
|
refundAddress: '',
|
||||||
refundReasons: '收货地址填错了\n与描述不符\n信息填错了,重新拍\n收到商品损坏了\n未按预定时间发货\n其它原因',
|
refundReasons: '',
|
||||||
refundCoupon: true,
|
refundCoupon: true,
|
||||||
afterSalesDays: 0,
|
afterSalesDays: 0,
|
||||||
// 4. 订单取消配置
|
// 4. 订单取消配置
|
||||||
@@ -278,19 +279,48 @@ const config = reactive({
|
|||||||
autoReceiveDays: 7,
|
autoReceiveDays: 7,
|
||||||
// 6. 自动评价配置
|
// 6. 自动评价配置
|
||||||
autoCommentDays: 0,
|
autoCommentDays: 0,
|
||||||
autoCommentText: '此用户未做评价',
|
autoCommentText: '',
|
||||||
// 7. 到店自提配置
|
// 7. 到店自提配置
|
||||||
storeSelfPickup: true,
|
storeSelfPickup: false,
|
||||||
// 8. 警戒库存配置
|
// 8. 警戒库存配置
|
||||||
stockWarningCount: 10
|
stockWarningCount: 10
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleSave = () => {
|
const loadSettings = async () => {
|
||||||
|
try {
|
||||||
|
const data = await getOrderSettings()
|
||||||
|
if (data != null) {
|
||||||
|
const remoteConfig = data as UTSJSONObject
|
||||||
|
// 逐项对齐,避免结构丢失
|
||||||
|
Object.keys(config).forEach(key => {
|
||||||
|
if (remoteConfig[key] !== undefined) {
|
||||||
|
(config as any)[key] = remoteConfig[key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
uni.showToast({ title: '加载配置失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadSettings()
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
uni.showLoading({ title: '保存中...' })
|
uni.showLoading({ title: '保存中...' })
|
||||||
setTimeout(() => {
|
try {
|
||||||
|
const ok = await saveOrderSettings(config as UTSJSONObject)
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
uni.showToast({ title: '保存成功', icon: 'success' })
|
if (ok) {
|
||||||
}, 500)
|
uni.showToast({ title: '保存成功', icon: 'success' })
|
||||||
|
} else {
|
||||||
|
uni.showToast({ title: '保存失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({ title: '保存出错', icon: 'none' })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -123,7 +123,7 @@
|
|||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||||
import EChartsView from '@/uni_modules/charts/EChartsView.vue'
|
import EChartsView from '@/uni_modules/charts/EChartsView.vue'
|
||||||
import { fetchOrderStats, fetchOrderTrend, fetchOrderSourceStats } from '@/services/orderService.uts'
|
import { fetchOrderStats, fetchOrderTrend, fetchOrderSourceStats, fetchOrderTypeStats } from '@/services/orderService.uts'
|
||||||
|
|
||||||
const currentPage = ref<string>('order_statistic')
|
const currentPage = ref<string>('order_statistic')
|
||||||
|
|
||||||
@@ -155,6 +155,10 @@ async function loadAllData() {
|
|||||||
// 3. 加载来源数据
|
// 3. 加载来源数据
|
||||||
const sourceData = await fetchOrderSourceStats(startTime, endTime)
|
const sourceData = await fetchOrderSourceStats(startTime, endTime)
|
||||||
initSourceChart(sourceData)
|
initSourceChart(sourceData)
|
||||||
|
|
||||||
|
// 4. 加载订单类型数据
|
||||||
|
const typeData = await fetchOrderTypeStats(startTime, endTime)
|
||||||
|
orderTypeData.value = typeData
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
uni.showToast({ title: '加载统计数据失败', icon: 'none' })
|
uni.showToast({ title: '加载统计数据失败', icon: 'none' })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,12 +14,8 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="filter-item">
|
<view class="filter-item">
|
||||||
<text class="label-txt">筛选条件:</text>
|
<text class="label-txt">搜索订单:</text>
|
||||||
<view class="select-mock" style="width: 100px;">
|
<input class="search-input" style="width: 280px;" placeholder="请输入订单号搜索" v-model="searchQuery" @confirm="handleQuery" />
|
||||||
<text class="select-val">请选择</text>
|
|
||||||
<text class="arrow-down">▼</text>
|
|
||||||
</view>
|
|
||||||
<input class="search-input" style="width: 180px;" placeholder="请输入搜索内容" v-model="searchQuery" />
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="filter-item">
|
<view class="filter-item">
|
||||||
@@ -42,7 +38,7 @@
|
|||||||
<view class="table-header-row">
|
<view class="table-header-row">
|
||||||
<view class="th" style="width: 200px;">订单号</view>
|
<view class="th" style="width: 200px;">订单号</view>
|
||||||
<view class="th" style="width: 150px;">用户信息</view>
|
<view class="th" style="width: 150px;">用户信息</view>
|
||||||
<view class="th" style="width: 320px;">商品信息</view>
|
<view class="th" style="flex: 1;">商品信息</view>
|
||||||
<view class="th" style="width: 100px;">实际支付</view>
|
<view class="th" style="width: 100px;">实际支付</view>
|
||||||
<view class="th" style="width: 100px;">核销员</view>
|
<view class="th" style="width: 100px;">核销员</view>
|
||||||
<view class="th" style="width: 120px;">核销门店</view>
|
<view class="th" style="width: 120px;">核销门店</view>
|
||||||
@@ -52,10 +48,16 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="table-body">
|
<view class="table-body">
|
||||||
<view v-for="(item, index) in recordList" :key="index" class="table-row">
|
<view v-if="loading" class="table-loading" style="padding: 40px; text-align: center;">
|
||||||
|
<text>加载中...</text>
|
||||||
|
</view>
|
||||||
|
<view v-else-if="recordList.length === 0" class="table-empty" style="padding: 40px; text-align: center;">
|
||||||
|
<text>暂无核销记录</text>
|
||||||
|
</view>
|
||||||
|
<view v-else v-for="(item, index) in recordList" :key="index" class="table-row">
|
||||||
<view class="td" style="width: 200px;"><text class="td-txt">{{ item.orderId }}</text></view>
|
<view class="td" style="width: 200px;"><text class="td-txt">{{ item.orderId }}</text></view>
|
||||||
<view class="td" style="width: 150px;"><text class="td-txt">{{ item.userInfo }}</text></view>
|
<view class="td" style="width: 150px;"><text class="td-txt">{{ item.userInfo }}</text></view>
|
||||||
<view class="td" style="width: 320px;">
|
<view class="td" style="flex: 1;">
|
||||||
<view class="product-info">
|
<view class="product-info">
|
||||||
<image class="product-img" :src="item.productImg" mode="aspectFill"></image>
|
<image class="product-img" :src="item.productImg" mode="aspectFill"></image>
|
||||||
<view class="product-detail">
|
<view class="product-detail">
|
||||||
@@ -63,7 +65,7 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="td" style="width: 100px;"><text class="td-txt">{{ item.payPrice }}</text></view>
|
<view class="td" style="width: 100px;"><text class="td-txt">¥{{ item.payPrice.toFixed(2) }}</text></view>
|
||||||
<view class="td" style="width: 100px;"><text class="td-txt">{{ item.verifier }}</text></view>
|
<view class="td" style="width: 100px;"><text class="td-txt">{{ item.verifier }}</text></view>
|
||||||
<view class="td" style="width: 120px;"><text class="td-txt">{{ item.storeName }}</text></view>
|
<view class="td" style="width: 120px;"><text class="td-txt">{{ item.storeName }}</text></view>
|
||||||
<view class="td" style="width: 100px;"><text class="td-txt">{{ item.payStatus }}</text></view>
|
<view class="td" style="width: 100px;"><text class="td-txt">{{ item.payStatus }}</text></view>
|
||||||
@@ -78,17 +80,20 @@
|
|||||||
<view class="page-total">
|
<view class="page-total">
|
||||||
<text class="total-txt">共 {{ total }} 条</text>
|
<text class="total-txt">共 {{ total }} 条</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="page-select">
|
|
||||||
<text class="page-val">15条/页 ▼</text>
|
|
||||||
</view>
|
|
||||||
<view class="page-btns">
|
<view class="page-btns">
|
||||||
<text class="p-btn disabled"><</text>
|
<view class="page-btn" :class="{ disabled: page <= 1 }" @click="prevPage">
|
||||||
<text class="p-btn active">1</text>
|
<text><</text>
|
||||||
<text class="p-btn">></text>
|
</view>
|
||||||
|
<view class="page-btn active">
|
||||||
|
<text>{{ page }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="page-btn" :class="{ disabled: page >= totalPages }" @click="nextPage">
|
||||||
|
<text>></text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="page-jump">
|
<view class="page-jump">
|
||||||
<text class="jump-txt">前往</text>
|
<text class="jump-txt">前往</text>
|
||||||
<input class="jump-input" placeholder="1" />
|
<input class="jump-input" v-model="jumpPage" type="number" @confirm="goToJumpPage" />
|
||||||
<text class="jump-txt">页</text>
|
<text class="jump-txt">页</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -98,14 +103,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="uts">
|
<script setup lang="uts">
|
||||||
import { ref } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
|
import { fetchWriteOffRecordPage } from '@/services/orderService.uts'
|
||||||
|
|
||||||
interface WriteOffRecord {
|
interface WriteOffRecord {
|
||||||
orderId: string
|
orderId: string
|
||||||
userInfo: string
|
userInfo: string
|
||||||
productImg: string
|
productImg: string
|
||||||
productName: string
|
productName: string
|
||||||
payPrice: string
|
payPrice: number
|
||||||
verifier: string
|
verifier: string
|
||||||
storeName: string
|
storeName: string
|
||||||
payStatus: string
|
payStatus: string
|
||||||
@@ -114,71 +120,105 @@ interface WriteOffRecord {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const searchQuery = ref('')
|
const searchQuery = ref('')
|
||||||
const total = ref(10)
|
const total = ref(0)
|
||||||
const recordList = ref<WriteOffRecord[]>([
|
const recordList = ref<WriteOffRecord[]>([])
|
||||||
{
|
const loading = ref(false)
|
||||||
orderId: 'cp470547161164021760',
|
const page = ref(1)
|
||||||
userInfo: '张迪/77418',
|
const pageSize = ref(15)
|
||||||
productImg: 'https://p.demo.crmeb.net/uploads/attach/2024/09/20240905/66d87e35b7e9b.jpg',
|
const jumpPage = ref('')
|
||||||
productName: '小米家保温杯云米电热水杯杯旅行便携式烧水壶真空304不锈钢热水壶智能恒...',
|
|
||||||
payPrice: '93',
|
|
||||||
verifier: '总平台',
|
|
||||||
storeName: '提货点222',
|
|
||||||
payStatus: '余额支付',
|
|
||||||
orderStatus: '已完成',
|
|
||||||
createTime: '2025-07-22 11:06:25'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
orderId: 'cp470289876680441856',
|
|
||||||
userInfo: '130****0000/22919',
|
|
||||||
productImg: 'https://p.demo.crmeb.net/uploads/attach/2024/09/20240905/66d87e35b7e9b.jpg',
|
|
||||||
productName: '米妍 (meyarn) 刮舌苔清洁器舌苔刷清新口气成人清洁舌苔口腔2支装 粉+蓝',
|
|
||||||
payPrice: '28.4',
|
|
||||||
verifier: '总平台',
|
|
||||||
storeName: '提货点222',
|
|
||||||
payStatus: '余额支付',
|
|
||||||
orderStatus: '待评价',
|
|
||||||
createTime: '2025-07-21 18:04:04'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
orderId: 'cp462914742369910784',
|
|
||||||
userInfo: '您好亲亲/76738',
|
|
||||||
productImg: 'https://p.demo.crmeb.net/uploads/attach/2024/09/20240905/66d87e35b7e9b.jpg',
|
|
||||||
productName: '小米家保温杯云米电热水杯杯旅行便携式烧水壶真空304不锈钢热水壶智能恒...',
|
|
||||||
payPrice: '89.1',
|
|
||||||
verifier: '总平台',
|
|
||||||
storeName: '关东科技',
|
|
||||||
payStatus: '线下支付',
|
|
||||||
orderStatus: '已完成',
|
|
||||||
createTime: '2025-07-01 09:37:55'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
orderId: 'cp450327064277417984',
|
|
||||||
userInfo: 'Leo/74412',
|
|
||||||
productImg: 'https://p.demo.crmeb.net/uploads/attach/2024/09/20240905/66d87e35b7e9b.jpg',
|
|
||||||
productName: '361度运动鞋男鞋【飞羽2】夏季轻透气网面缓震回弹便捷跑步鞋 羽毛白冰河...',
|
|
||||||
payPrice: '369',
|
|
||||||
verifier: '总平台',
|
|
||||||
storeName: '提货点222',
|
|
||||||
payStatus: '线下支付',
|
|
||||||
orderStatus: '待评价',
|
|
||||||
createTime: '2025-05-27 15:58:58'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
orderId: 'cp439425186874261504',
|
|
||||||
userInfo: '白茶/73171',
|
|
||||||
productImg: 'https://p.demo.crmeb.net/uploads/attach/2024/09/20240905/66d87e35b7e9b.jpg',
|
|
||||||
productName: '【明星同款】FILA FUSION裴乐潮牌卫衣情侣老花男女宽松上衣',
|
|
||||||
payPrice: '649',
|
|
||||||
verifier: '总平台',
|
|
||||||
storeName: '关东科技',
|
|
||||||
payStatus: '余额支付',
|
|
||||||
orderStatus: '待评价',
|
|
||||||
createTime: '2025-04-27 13:58:48'
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
const handleQuery = () => { console.log('Searching...') }
|
const totalPages = computed((): number => {
|
||||||
|
if (pageSize.value <= 0) return 1
|
||||||
|
return Math.ceil(total.value / pageSize.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const loadRecords = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await fetchWriteOffRecordPage(
|
||||||
|
page.value,
|
||||||
|
pageSize.value,
|
||||||
|
searchQuery.value || null
|
||||||
|
)
|
||||||
|
|
||||||
|
recordList.value = res.items.map((item: any): WriteOffRecord => {
|
||||||
|
return {
|
||||||
|
orderId: String(item.order_no),
|
||||||
|
userInfo: `${String(item.customer_name ?? '未知')} | ${String(item.customer_phone ?? '')}`,
|
||||||
|
productImg: String(item.product_summary?.image_url ?? ''),
|
||||||
|
productName: String(item.product_summary?.product_name ?? '多商品订单'),
|
||||||
|
payPrice: parseFloat(String(item.total_amount ?? '0')),
|
||||||
|
verifier: String(item.verifier_name ?? '--'),
|
||||||
|
storeName: '--', // 目前 DDL 尚未返回门店
|
||||||
|
payStatus: getPaymentStatusName(parseInt(String(item.payment_status ?? '1'))),
|
||||||
|
orderStatus: getOrderStatusName(parseInt(String(item.order_status ?? '1'))),
|
||||||
|
createTime: String(item.created_at ?? '--')
|
||||||
|
} as WriteOffRecord
|
||||||
|
})
|
||||||
|
total.value = res.total
|
||||||
|
} catch (e) {
|
||||||
|
uni.showToast({ title: '加载核销记录失败', icon: 'none' })
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadRecords()
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleQuery = () => {
|
||||||
|
page.value = 1
|
||||||
|
loadRecords()
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevPage = () => {
|
||||||
|
if (page.value > 1) {
|
||||||
|
page.value--
|
||||||
|
loadRecords()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextPage = () => {
|
||||||
|
if (page.value < totalPages.value) {
|
||||||
|
page.value++
|
||||||
|
loadRecords()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const goToJumpPage = () => {
|
||||||
|
const targetPage = parseInt(jumpPage.value)
|
||||||
|
if (!isNaN(targetPage) && targetPage >= 1 && targetPage <= totalPages.value) {
|
||||||
|
page.value = targetPage
|
||||||
|
loadRecords()
|
||||||
|
jumpPage.value = ''
|
||||||
|
} else {
|
||||||
|
uni.showToast({ title: '页码无效', icon: 'none' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPaymentStatusName(status: number): string {
|
||||||
|
switch (status) {
|
||||||
|
case 1: return '未支付'
|
||||||
|
case 2: return '已支付'
|
||||||
|
case 3: return '部分退款'
|
||||||
|
case 4: return '全额退款'
|
||||||
|
default: return '未知'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOrderStatusName(status: number): string {
|
||||||
|
switch (status) {
|
||||||
|
case 1: return '待付款'
|
||||||
|
case 2: return '待发货'
|
||||||
|
case 3: return '待收货'
|
||||||
|
case 4: return '已完成'
|
||||||
|
case 5: return '已取消'
|
||||||
|
case 6: return '退款中'
|
||||||
|
case 7: return '已退款'
|
||||||
|
default: return '未知'
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@@ -276,6 +316,8 @@ const handleQuery = () => { console.log('Searching...') }
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table-container { padding: 0; }
|
||||||
|
|
||||||
.table-header-row {
|
.table-header-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@@ -323,6 +365,7 @@ const handleQuery = () => { console.log('Searching...') }
|
|||||||
height: 40px;
|
height: 40px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.p-name {
|
.p-name {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@@ -346,9 +389,8 @@ const handleQuery = () => { console.log('Searching...') }
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
.total-txt { font-size: 14px; color: #606266; }
|
.total-txt { font-size: 14px; color: #606266; }
|
||||||
.page-val { font-size: 14px; color: #606266; border: 1px solid #dcdfe6; padding: 4px 10px; border-radius: 4px; }
|
|
||||||
.page-btns { display: flex; flex-direction: row; gap: 8px; }
|
.page-btns { display: flex; flex-direction: row; gap: 8px; }
|
||||||
.p-btn {
|
.page-btn {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
border: 1px solid #dcdfe6;
|
border: 1px solid #dcdfe6;
|
||||||
@@ -357,13 +399,12 @@ const handleQuery = () => { console.log('Searching...') }
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.p-btn.active { background-color: #2d8cf0; border-color: #2d8cf0; color: #fff; }
|
.page-btn.active { background-color: #2d8cf0; border-color: #2d8cf0; color: #fff; }
|
||||||
.p-btn.disabled { color: #c0c4cc; background-color: #f5f7fa; }
|
.page-btn.disabled { color: #c0c4cc; background-color: #f5f7fa; cursor: not-allowed; }
|
||||||
|
|
||||||
.page-jump { display: flex; flex-direction: row; align-items: center; gap: 8px; }
|
.page-jump { display: flex; flex-direction: row; align-items: center; gap: 8px; }
|
||||||
.jump-txt { font-size: 14px; color: #606266; }
|
.jump-txt { font-size: 14px; color: #606266; }
|
||||||
.jump-input { width: 40px; height: 32px; border: 1px solid #dcdfe6; text-align: center; border-radius: 4px; font-size: 14px; }
|
.jump-input { width: 40px; height: 32px; border: 1px solid #dcdfe6; text-align: center; border-radius: 4px; font-size: 14px; }
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -121,6 +121,13 @@ export async function fetchOrderSourceStats(startTime: string, endTime: string):
|
|||||||
} as any)) as any
|
} as any)) as any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchOrderTypeStats(startTime: string, endTime: string): Promise<Array<any>> {
|
||||||
|
return (await rpcOrEmptyArray('rpc_admin_order_type_stats', {
|
||||||
|
p_start_time: startTime,
|
||||||
|
p_end_time: endTime
|
||||||
|
} as any)) as any
|
||||||
|
}
|
||||||
|
|
||||||
export async function getOrderSettings(): Promise<UTSJSONObject | null> {
|
export async function getOrderSettings(): Promise<UTSJSONObject | null> {
|
||||||
const res = await rpcOrValue('rpc_admin_system_config_get', {
|
const res = await rpcOrValue('rpc_admin_system_config_get', {
|
||||||
p_key: 'order_settings'
|
p_key: 'order_settings'
|
||||||
|
|||||||
Reference in New Issue
Block a user