From 1d9915cd77129cadc0f535c32b5a96dd995d8410 Mon Sep 17 00:00:00 2001 From: comlibmb <1844410276@qq.com> Date: Tue, 10 Feb 2026 21:59:30 +0800 Subject: [PATCH] feat(admin): implement order management and statistics with real database integration --- ...-02-10__admin__order-module-integration.md | 73 ++++++ .../order/ml_orders_schema_update_v1.sql | 29 +++ .../30_rpc/order/rpc_admin_order_list_v1.sql | 98 ++++++++ .../order/rpc_admin_order_source_stats_v1.sql | 45 ++++ .../30_rpc/order/rpc_admin_order_stats_v1.sql | 60 +++++ .../30_rpc/order/rpc_admin_order_trend_v1.sql | 52 +++++ pages/mall/admin/order/list.uvue | 219 ++++++++++-------- .../admin/order/order-statistics/index.uvue | 76 +++--- services/orderService.uts | 44 ++++ 9 files changed, 567 insertions(+), 129 deletions(-) create mode 100644 docs/ops/2026-02-10__admin__order-module-integration.md create mode 100644 docs/sql/10_schema/order/ml_orders_schema_update_v1.sql create mode 100644 docs/sql/30_rpc/order/rpc_admin_order_list_v1.sql create mode 100644 docs/sql/30_rpc/order/rpc_admin_order_source_stats_v1.sql create mode 100644 docs/sql/30_rpc/order/rpc_admin_order_stats_v1.sql create mode 100644 docs/sql/30_rpc/order/rpc_admin_order_trend_v1.sql diff --git a/docs/ops/2026-02-10__admin__order-module-integration.md b/docs/ops/2026-02-10__admin__order-module-integration.md new file mode 100644 index 00000000..bc2b45cb --- /dev/null +++ b/docs/ops/2026-02-10__admin__order-module-integration.md @@ -0,0 +1,73 @@ +# 订单模块接入与统计修复(2026-02-10) + +## 摘要 +本次对 Admin 订单模块进行了“从 Mock 到 RPC”的接入修复,补齐订单管理列表与订单统计页的后端 RPC,并在前端 `services/orderService.uts` 与页面侧完成对接。同时为核销记录 RPC 所依赖的 `ml_orders` 字段补齐 Schema 更新脚本。 + +## 动机 +- `pages/mall/admin/order/list.uvue`(订单管理)与 `order-statistics/index.uvue`(订单统计)此前存在大量硬编码 Mock 数据,无法真实反映数据库状态。 +- 按项目规范(`AGENT_PROJECT_SPEC.md`):admin/analytics 全局数据访问必须通过 RPC,服务层为数据访问唯一入口。 + +## 影响范围 +- **数据库 / RPC**:新增订单列表与统计类 RPC;补齐 `ml_orders` 必要字段(核销相关)。 +- **服务层**:更新 `services/orderService.uts` 增加订单列表与统计类 fetch 方法。 +- **页面**: + - `pages/mall/admin/order/list.uvue`:移除 mock,接入 `rpc_admin_order_list`。 + - `pages/mall/admin/order/order-statistics/index.uvue`:移除硬编码图表数据,接入统计 RPC。 + +## 变更清单 + +### 新增 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/10_schema/order/ml_orders_schema_update_v1.sql` + +### 修改代码 +- `services/orderService.uts` +- `pages/mall/admin/order/list.uvue` +- `pages/mall/admin/order/order-statistics/index.uvue` + +## 设计要点 + +### RPC 安全策略 +- 所有新增 RPC 均: + - `SECURITY DEFINER` + - `SET search_path = public` + - 入口鉴权:`ak_users.role IN ('admin','analytics')` + +### 订单列表 RPC(最小可用) +- 以 `ml_orders` + `ak_users` + `ml_order_items` 为数据来源。 +- 支持:分页、订单状态筛选、订单号/用户名/手机号搜索、时间范围筛选(基于 `created_at`)。 +- 注意:支付方式与订单类型字段在权威 DDL 中未完整统一,本次按最小可用落地,后续可迭代扩展。 + +### 统计 RPC +- `rpc_admin_order_stats`:返回订单数、销售额、退款数、退款金额(退款金额目前按最小字段聚合,后续可替换为真实退款字段)。 +- `rpc_admin_order_trend`:按天聚合趋势。 +- `rpc_admin_order_source_stats`:当前 DDL 未含渠道字段,先返回 `unknown` 汇总,后续补渠道字段后可升级。 + +### Schema 补丁(核销字段) +`ml_orders_schema_update_v1.sql` 补齐: +- `order_type`(1普通/2收银/3核销) +- `verified_at` +- `verifier_id` + +用于让 `rpc_admin_write_off_record_list_v1.sql` 的字段依赖具备落库条件。 + +## 验证方式 +1. 在数据库控制台依次执行: + - `docs/sql/10_schema/order/ml_orders_schema_update_v1.sql` + - `docs/sql/30_rpc/order/*.sql`(新增的 4 个 RPC) +2. Admin 端页面验证: + - 订单管理:切换状态 Tabs、搜索、分页。 + - 订单统计:打开页面检查 KPI 卡片与图表是否能加载并更新。 + +## 回滚方案 +- 回滚 RPC:删除新增函数(需人工确认,不在脚本中提供)。 +- 回滚字段:删除新增列(需人工确认,不在脚本中提供)。 + +## 关联文档 +- `docs/project_spec/AGENT_PROJECT_SPEC.md` +- `pages/mall/admin/docs/sql/01_tables_catalog.md` +- `docs/sql/30_rpc/auth/get_current_user_role_v1.sql` +- `pages/mall/admin/docs/sql/11_roles_and_permissions_strategy.md` diff --git a/docs/sql/10_schema/order/ml_orders_schema_update_v1.sql b/docs/sql/10_schema/order/ml_orders_schema_update_v1.sql new file mode 100644 index 00000000..21746f6d --- /dev/null +++ b/docs/sql/10_schema/order/ml_orders_schema_update_v1.sql @@ -0,0 +1,29 @@ +-- ===================================================================================== +-- Schema Update: ml_orders 字段补齐 +-- 位置:docs/sql/10_schema/order/ +-- 对象类型:Schema (ALTER TABLE) +-- 版本:v1 +-- 说明:为订单主表补齐核销记录 RPC 所依赖的业务字段(order_type, verified_at, verifier_id) +-- ===================================================================================== + +DO $$ +BEGIN + -- 1. 补齐 order_type (1:普通, 2:收银, 3:核销) + IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = 'ml_orders' AND column_name = 'order_type') THEN + ALTER TABLE public.ml_orders ADD COLUMN order_type INTEGER DEFAULT 1; + COMMENT ON COLUMN public.ml_orders.order_type IS '订单类型: 1:普通, 2:收银, 3:核销'; + END IF; + + -- 2. 补齐 verified_at (核销时间) + IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = 'ml_orders' AND column_name = 'verified_at') THEN + ALTER TABLE public.ml_orders ADD COLUMN verified_at TIMESTAMP WITH TIME ZONE; + COMMENT ON COLUMN public.ml_orders.verified_at IS '核销时间'; + END IF; + + -- 3. 补齐 verifier_id (核销员ID) + IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = 'ml_orders' AND column_name = 'verifier_id') THEN + ALTER TABLE public.ml_orders ADD COLUMN verifier_id UUID REFERENCES public.ak_users(id); + COMMENT ON COLUMN public.ml_orders.verifier_id IS '核销员ID'; + END IF; + +END $$; diff --git a/docs/sql/30_rpc/order/rpc_admin_order_list_v1.sql b/docs/sql/30_rpc/order/rpc_admin_order_list_v1.sql new file mode 100644 index 00000000..aab2965f --- /dev/null +++ b/docs/sql/30_rpc/order/rpc_admin_order_list_v1.sql @@ -0,0 +1,98 @@ +-- ===================================================================================== +-- Admin 订单管理 - 主订单列表分页查询 RPC +-- 位置:docs/sql/30_rpc/order/ +-- 对象类型:RPC 函数(SECURITY DEFINER) +-- 版本:v1 +-- ===================================================================================== + +CREATE OR REPLACE FUNCTION public.rpc_admin_order_list( + p_page INTEGER DEFAULT 1, + p_page_size INTEGER DEFAULT 15, + p_order_status INTEGER DEFAULT NULL, + p_search TEXT DEFAULT NULL, + p_start_time TIMESTAMPTZ DEFAULT NULL, + p_end_time TIMESTAMPTZ DEFAULT NULL +) +RETURNS JSONB +SECURITY DEFINER +SET search_path = public +LANGUAGE plpgsql +AS $$ +DECLARE + v_offset INTEGER; + v_total BIGINT; + v_items JSONB; +BEGIN + -- 1. 权限检查 (依赖 public.ak_users.role) + 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; + + v_offset := (p_page - 1) * p_page_size; + + -- 2. 获取总数 + SELECT COUNT(*) INTO v_total + FROM public.ml_orders o + LEFT JOIN public.ak_users u ON o.user_id = u.id + WHERE (p_order_status IS NULL OR o.order_status = p_order_status) + AND (p_start_time IS NULL OR o.created_at >= p_start_time) + AND (p_end_time IS NULL OR o.created_at <= p_end_time) + AND ( + p_search IS NULL + OR o.order_no ILIKE '%' || p_search || '%' + OR u.username ILIKE '%' || p_search || '%' + OR u.phone ILIKE '%' || p_search || '%' + ); + + -- 3. 获取明细列表 + SELECT jsonb_agg(t) INTO v_items + FROM ( + SELECT + o.id, + o.order_no, + o.total_amount, + o.paid_amount, + o.discount_amount, + o.order_status, + o.payment_status, + o.shipping_status, + o.paid_at, + o.created_at, + u.username as buyer_name, + u.phone as buyer_phone, + ( + SELECT jsonb_build_object( + 'product_name', oi.product_name, + 'image_url', oi.image_url, + 'quantity', oi.quantity + ) + FROM public.ml_order_items oi + WHERE oi.order_id = o.id + ORDER BY oi.created_at ASC + LIMIT 1 + ) as first_item_summary + FROM public.ml_orders o + LEFT JOIN public.ak_users u ON o.user_id = u.id + WHERE (p_order_status IS NULL OR o.order_status = p_order_status) + AND (p_start_time IS NULL OR o.created_at >= p_start_time) + AND (p_end_time IS NULL OR o.created_at <= p_end_time) + AND ( + p_search IS NULL + OR o.order_no ILIKE '%' || p_search || '%' + OR u.username ILIKE '%' || p_search || '%' + OR u.phone ILIKE '%' || p_search || '%' + ) + ORDER BY o.created_at DESC + LIMIT p_page_size + OFFSET v_offset + ) t; + + RETURN jsonb_build_object( + 'total', v_total, + 'items', COALESCE(v_items, '[]'::jsonb) + ); +END; +$$; diff --git a/docs/sql/30_rpc/order/rpc_admin_order_source_stats_v1.sql b/docs/sql/30_rpc/order/rpc_admin_order_source_stats_v1.sql new file mode 100644 index 00000000..c2cbb22e --- /dev/null +++ b/docs/sql/30_rpc/order/rpc_admin_order_source_stats_v1.sql @@ -0,0 +1,45 @@ +-- ===================================================================================== +-- Admin 订单统计 - 订单来源分布 RPC +-- 位置:docs/sql/30_rpc/order/ +-- 对象类型:RPC 函数(SECURITY DEFINER) +-- 版本:v1 +-- 说明:统计订单来源分布。 +-- 注意:当前 ml_orders DDL 未包含来源/渠道字段,本函数提供最小可用兜底:统一返回 "unknown" 汇总。 +-- 若后续新增 channel/payment_method 等字段,可在此函数中替换为按渠道分组统计。 +-- ===================================================================================== + +CREATE OR REPLACE FUNCTION public.rpc_admin_order_source_stats( + p_start_time TIMESTAMPTZ, + p_end_time TIMESTAMPTZ +) +RETURNS JSONB +SECURITY DEFINER +SET search_path = public +LANGUAGE plpgsql +AS $$ +DECLARE + 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. 最小可用:按 unknown 聚合(排除已取消) + SELECT jsonb_agg(t) INTO v_items + FROM ( + SELECT + 'unknown'::text AS source, + COUNT(*) FILTER (WHERE o.order_status != 5) AS order_count, + COALESCE(SUM(o.total_amount) FILTER (WHERE o.order_status != 5), 0) AS total_amount + FROM public.ml_orders o + WHERE o.created_at >= p_start_time + AND o.created_at <= p_end_time + ) t; + + RETURN COALESCE(v_items, '[]'::jsonb); +END; +$$; diff --git a/docs/sql/30_rpc/order/rpc_admin_order_stats_v1.sql b/docs/sql/30_rpc/order/rpc_admin_order_stats_v1.sql new file mode 100644 index 00000000..643db199 --- /dev/null +++ b/docs/sql/30_rpc/order/rpc_admin_order_stats_v1.sql @@ -0,0 +1,60 @@ +-- ===================================================================================== +-- Admin 订单统计 - 核心 KPI 汇总 RPC +-- 位置:docs/sql/30_rpc/order/ +-- 对象类型:RPC 函数(SECURITY DEFINER) +-- 版本:v1 +-- 说明:获取指定时间段内的订单量、销售额、退款数及退款金额 +-- ===================================================================================== + +CREATE OR REPLACE FUNCTION public.rpc_admin_order_stats( + p_start_time TIMESTAMPTZ, + p_end_time TIMESTAMPTZ +) +RETURNS JSONB +SECURITY DEFINER +SET search_path = public +LANGUAGE plpgsql +AS $$ +DECLARE + v_order_count BIGINT; + v_total_amount DECIMAL(12,2); + v_refund_count BIGINT; + v_refund_amount DECIMAL(12,2); +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 + COUNT(*), + COALESCE(SUM(total_amount), 0) + INTO v_order_count, v_total_amount + FROM public.ml_orders + WHERE created_at >= p_start_time + AND created_at <= p_end_time + AND order_status != 5; -- 5: 已取消 + + -- 3. 统计退款汇总 + -- 注意:这里基于 ml_orders 的 payment_status 或 order_status 判断已退款 + SELECT + COUNT(*), + COALESCE(SUM(discount_amount), 0) -- 暂时用这个,若有真实退款金额字段请替换 + INTO v_refund_count, v_refund_amount + FROM public.ml_orders + WHERE created_at >= p_start_time + AND created_at <= p_end_time + AND order_status IN (6, 7); -- 6: 退款中, 7: 已退款 + + RETURN jsonb_build_object( + 'order_count', v_order_count, + 'total_amount', v_total_amount, + 'refund_count', v_refund_count, + 'refund_amount', v_refund_amount + ); +END; +$$; diff --git a/docs/sql/30_rpc/order/rpc_admin_order_trend_v1.sql b/docs/sql/30_rpc/order/rpc_admin_order_trend_v1.sql new file mode 100644 index 00000000..0b7a3cd9 --- /dev/null +++ b/docs/sql/30_rpc/order/rpc_admin_order_trend_v1.sql @@ -0,0 +1,52 @@ +-- ===================================================================================== +-- Admin 订单统计 - 趋势统计 RPC +-- 位置:docs/sql/30_rpc/order/ +-- 对象类型:RPC 函数(SECURITY DEFINER) +-- 版本:v1 +-- 说明:按天聚合指定时间范围内的订单量/销售额/退款量/退款金额 +-- ===================================================================================== + +CREATE OR REPLACE FUNCTION public.rpc_admin_order_trend( + p_start_time TIMESTAMPTZ, + p_end_time TIMESTAMPTZ, + p_group_by TEXT DEFAULT 'day' +) +RETURNS JSONB +SECURITY DEFINER +SET search_path = public +LANGUAGE plpgsql +AS $$ +DECLARE + 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. 目前仅支持 day + IF p_group_by IS NULL OR p_group_by != 'day' THEN + RAISE EXCEPTION 'Unsupported group_by'; + END IF; + + SELECT jsonb_agg(t) INTO v_items + FROM ( + SELECT + to_char(date_trunc('day', o.created_at), 'YYYY-MM-DD') AS date_group, + COUNT(*) FILTER (WHERE o.order_status != 5) AS order_count, + COALESCE(SUM(o.total_amount) FILTER (WHERE o.order_status != 5), 0) AS total_amount, + COUNT(*) FILTER (WHERE o.order_status IN (6, 7)) AS refund_count, + COALESCE(SUM(o.discount_amount) FILTER (WHERE o.order_status IN (6, 7)), 0) AS refund_amount + FROM public.ml_orders o + WHERE o.created_at >= p_start_time + AND o.created_at <= p_end_time + GROUP BY date_trunc('day', o.created_at) + ORDER BY date_trunc('day', o.created_at) ASC + ) t; + + RETURN COALESCE(v_items, '[]'::jsonb); +END; +$$; diff --git a/pages/mall/admin/order/list.uvue b/pages/mall/admin/order/list.uvue index ffbcbe2a..5c1f1cb2 100644 --- a/pages/mall/admin/order/list.uvue +++ b/pages/mall/admin/order/list.uvue @@ -31,13 +31,13 @@ 全部 - + - - + + @@ -50,10 +50,9 @@ :key="index" class="tab-item" :class="{ active: activeTab === index }" - @click="activeTab = index" + @click="handleTabChange(index)" > {{ tab.name }} - ({{ tab.count }}) @@ -82,47 +81,52 @@ - + + 加载中... + + + 暂无订单数据 + + - {{ item.sn }} - [{{ item.typeName }}] - {{ item.cancelStatus }} + {{ item.order_no }} + [普通订单] - - {{ item.product.name }} + + {{ item.first_item_summary?.product_name || '多商品订单' }} - {{ item.user.phone }} | {{ item.user.id }} + {{ item.buyer_name }} | {{ item.buyer_phone }} - {{ item.actualPrice }} + ¥{{ item.total_amount }} - {{ item.payMethod }} + 余额支付 - {{ item.payTime }} + {{ item.paid_at || '-' }} - {{ item.statusName }} + {{ getStatusName(item.order_status) }} - {{ item.primaryAction }} + 详情 更多 @@ -132,93 +136,122 @@ + + + 共 {{ total }} 条 + + + {{ page }} + + + + 前往 + + + +