From 80e5a1ddeb06c6a0b65ff18dd43923a19e3954d5 Mon Sep 17 00:00:00 2001 From: comlibmb <1844410276@qq.com> Date: Tue, 10 Feb 2026 18:49:21 +0800 Subject: [PATCH] feat(admin): merge stash changes into comclib-analytics (order/finance/product + rpc sql) --- .../user/ak_users_finance_fields_v1.sql | 13 ++ .../admin/rpc_admin_system_config_get_v1.sql | 35 ++++ .../admin/rpc_admin_system_config_save_v1.sql | 37 +++++ .../finance/rpc_admin_extract_list_v1.sql | 103 ++++++++++++ .../finance/rpc_admin_extract_review_v1.sql | 78 +++++++++ .../finance/rpc_admin_recharge_audit_v1.sql | 64 ++++++++ .../finance/rpc_admin_recharge_list_v1.sql | 92 +++++++++++ .../finance/rpc_admin_user_bill_list_v1.sql | 99 ++++++++++++ .../order/rpc_admin_cashier_order_list_v1.sql | 68 ++++++++ .../order/rpc_admin_refund_order_list_v1.sql | 87 ++++++++++ .../rpc_admin_write_off_record_list_v1.sql | 80 ++++++++++ .../product/rpc_admin_category_delete_v1.sql | 46 ++++++ .../rpc_admin_product_analytics_v1.sql | 105 ++++++++++++ ...__admin__product-module-standardization.md | 50 ++++++ .../mall/admin/finance/transaction_stats.uvue | 87 ++++++---- .../admin/order/order-statistics/index.uvue | 8 +- pages/mall/admin/product/classify.uvue | 115 ++++++++++--- .../product/product-management/index.uvue | 130 ++++++++------- services/admin/financeService.uts | 151 ++++++++++++++++++ services/admin/productCategoryService.uts | 116 ++++++++++++++ services/admin/productService.uts | 68 ++++++++ services/orderService.uts | 61 ++++--- types/admin/finance.uts | 49 ++++++ 23 files changed, 1599 insertions(+), 143 deletions(-) create mode 100644 docs/sql/10_schema/user/ak_users_finance_fields_v1.sql create mode 100644 docs/sql/30_rpc/admin/rpc_admin_system_config_get_v1.sql create mode 100644 docs/sql/30_rpc/admin/rpc_admin_system_config_save_v1.sql create mode 100644 docs/sql/30_rpc/finance/rpc_admin_extract_list_v1.sql create mode 100644 docs/sql/30_rpc/finance/rpc_admin_extract_review_v1.sql create mode 100644 docs/sql/30_rpc/finance/rpc_admin_recharge_audit_v1.sql create mode 100644 docs/sql/30_rpc/finance/rpc_admin_recharge_list_v1.sql create mode 100644 docs/sql/30_rpc/finance/rpc_admin_user_bill_list_v1.sql create mode 100644 docs/sql/30_rpc/order/rpc_admin_cashier_order_list_v1.sql create mode 100644 docs/sql/30_rpc/order/rpc_admin_refund_order_list_v1.sql create mode 100644 docs/sql/30_rpc/order/rpc_admin_write_off_record_list_v1.sql create mode 100644 docs/sql/30_rpc/product/rpc_admin_category_delete_v1.sql create mode 100644 docs/sql/30_rpc/product/rpc_admin_product_analytics_v1.sql create mode 100644 pages/mall/admin/docs/ops/2026-02-06__admin__product-module-standardization.md create mode 100644 services/admin/financeService.uts create mode 100644 services/admin/productCategoryService.uts create mode 100644 services/admin/productService.uts create mode 100644 types/admin/finance.uts diff --git a/docs/sql/10_schema/user/ak_users_finance_fields_v1.sql b/docs/sql/10_schema/user/ak_users_finance_fields_v1.sql new file mode 100644 index 00000000..ebe40bd2 --- /dev/null +++ b/docs/sql/10_schema/user/ak_users_finance_fields_v1.sql @@ -0,0 +1,13 @@ +-- ===================================================================================== +-- User 模块扩展 - 财务字段补全 +-- 位置:docs/sql/10_schema/user/ +-- 版本:v1 +-- 描述:为 ak_users 增加余额与佣金字段,支持财务业务。 +-- ===================================================================================== + +ALTER TABLE public.ak_users +ADD COLUMN IF NOT EXISTS now_money DECIMAL(12,2) DEFAULT 0, +ADD COLUMN IF NOT EXISTS brokerage_price DECIMAL(12,2) DEFAULT 0; + +COMMENT ON COLUMN public.ak_users.now_money IS '用户当前余额'; +COMMENT ON COLUMN public.ak_users.brokerage_price IS '用户当前佣金'; diff --git a/docs/sql/30_rpc/admin/rpc_admin_system_config_get_v1.sql b/docs/sql/30_rpc/admin/rpc_admin_system_config_get_v1.sql new file mode 100644 index 00000000..da23eb2a --- /dev/null +++ b/docs/sql/30_rpc/admin/rpc_admin_system_config_get_v1.sql @@ -0,0 +1,35 @@ +-- ===================================================================================== +-- Admin 系统功能 - 获取配置项 RPC +-- 位置:docs/sql/30_rpc/admin/ +-- 对象类型:RPC 函数(SECURITY DEFINER) +-- 版本:v1 +-- 依赖:ml_system_configs, ak_users 表已存在 +-- ===================================================================================== + +CREATE OR REPLACE FUNCTION public.rpc_admin_system_config_get( + p_key TEXT +) +RETURNS JSONB +SECURITY DEFINER +SET search_path = public +LANGUAGE plpgsql +AS $$ +DECLARE + v_value 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 config_value INTO v_value + FROM public.ml_system_configs + WHERE config_key = p_key; + + RETURN v_value; +END; +$$; \ No newline at end of file diff --git a/docs/sql/30_rpc/admin/rpc_admin_system_config_save_v1.sql b/docs/sql/30_rpc/admin/rpc_admin_system_config_save_v1.sql new file mode 100644 index 00000000..bf82d551 --- /dev/null +++ b/docs/sql/30_rpc/admin/rpc_admin_system_config_save_v1.sql @@ -0,0 +1,37 @@ +-- ===================================================================================== +-- Admin 系统功能 - 保存配置项 RPC +-- 位置:docs/sql/30_rpc/admin/ +-- 对象类型:RPC 函数(SECURITY DEFINER) +-- 版本:v1 +-- 依赖:ml_system_configs, ak_users 表已存在 +-- ===================================================================================== + +CREATE OR REPLACE FUNCTION public.rpc_admin_system_config_save( + p_key TEXT, + p_value JSONB +) +RETURNS BOOLEAN +SECURITY DEFINER +SET search_path = public +LANGUAGE plpgsql +AS $$ +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. 执行保存(存在则更新,不存在则插入) + INSERT INTO public.ml_system_configs (config_key, config_value, updated_at) + VALUES (p_key, p_value, NOW()) + ON CONFLICT (config_key) + DO UPDATE SET + config_value = EXCLUDED.config_value, + updated_at = NOW(); + + RETURN TRUE; +END; +$$; \ No newline at end of file diff --git a/docs/sql/30_rpc/finance/rpc_admin_extract_list_v1.sql b/docs/sql/30_rpc/finance/rpc_admin_extract_list_v1.sql new file mode 100644 index 00000000..9b9b239f --- /dev/null +++ b/docs/sql/30_rpc/finance/rpc_admin_extract_list_v1.sql @@ -0,0 +1,103 @@ +-- ===================================================================================== +-- Admin 财务功能 - 提现申请列表分页查询 RPC +-- 位置:docs/sql/30_rpc/finance/ +-- 对象类型:RPC 函数(SECURITY DEFINER) +-- 版本:v1 +-- 依赖:ml_extract, ak_users 表已存在 +-- 权限:仅 admin 角色可执行(口径 A:全局数据访问通过 RPC) +-- ===================================================================================== + +CREATE OR REPLACE FUNCTION public.rpc_admin_extract_list( + p_page INTEGER DEFAULT 1, + p_page_size INTEGER DEFAULT 15, + p_status SMALLINT DEFAULT NULL, + p_start_time TIMESTAMP WITH TIME ZONE DEFAULT NULL, + p_end_time TIMESTAMP WITH TIME ZONE DEFAULT NULL, + p_search TEXT 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. 权限检查 + IF NOT EXISTS ( + SELECT 1 FROM public.ak_users + WHERE id = auth.uid() AND role = 'admin' + ) THEN + RAISE EXCEPTION 'Permission denied'; + END IF; + + v_offset := (p_page - 1) * p_page_size; + + -- 2. 获取总数 + SELECT COUNT(*) INTO v_total + FROM public.ml_extract e + LEFT JOIN public.ak_users u ON u.id = e.uid + WHERE (p_status IS NULL OR e.status = p_status) + AND (p_start_time IS NULL OR e.created_at >= p_start_time) + AND (p_end_time IS NULL OR e.created_at <= p_end_time) + AND (p_search IS NULL OR ( + COALESCE(u.username, '') ILIKE '%' || p_search || '%' OR + COALESCE(u.email, '') ILIKE '%' || p_search || '%' OR + COALESCE(e.real_name, '') ILIKE '%' || p_search || '%' OR + COALESCE(e.bank_code, '') ILIKE '%' || p_search || '%' OR + COALESCE(e.alipay_code, '') ILIKE '%' || p_search || '%' OR + COALESCE(e.wechat_code, '') ILIKE '%' || p_search || '%' + )); + + -- 3. 获取明细数据 + SELECT jsonb_agg(t) INTO v_items + FROM ( + SELECT + e.id, + e.uid, + e.real_name, + e.extract_type, + e.bank_code, + e.bank_address, + e.alipay_code, + e.wechat_code, + e.extract_price, + e.service_fee, + e.balance, + e.status, + e.refusal_reason, + e.admin_id, + e.payment_time, + e.created_at, + e.updated_at, + u.username as user_name, + u.email as user_email + FROM public.ml_extract e + LEFT JOIN public.ak_users u ON u.id = e.uid + WHERE (p_status IS NULL OR e.status = p_status) + AND (p_start_time IS NULL OR e.created_at >= p_start_time) + AND (p_end_time IS NULL OR e.created_at <= p_end_time) + AND (p_search IS NULL OR ( + COALESCE(u.username, '') ILIKE '%' || p_search || '%' OR + COALESCE(u.email, '') ILIKE '%' || p_search || '%' OR + COALESCE(e.real_name, '') ILIKE '%' || p_search || '%' OR + COALESCE(e.bank_code, '') ILIKE '%' || p_search || '%' OR + COALESCE(e.alipay_code, '') ILIKE '%' || p_search || '%' OR + COALESCE(e.wechat_code, '') ILIKE '%' || p_search || '%' + )) + ORDER BY e.created_at DESC + LIMIT p_page_size + OFFSET v_offset + ) t; + + -- 4. 返回结果 + RETURN jsonb_build_object( + 'total', v_total, + 'items', COALESCE(v_items, '[]'::jsonb) + ); +END; +$$; + +COMMENT ON FUNCTION public.rpc_admin_extract_list IS '管理员提现申请列表分页查询'; diff --git a/docs/sql/30_rpc/finance/rpc_admin_extract_review_v1.sql b/docs/sql/30_rpc/finance/rpc_admin_extract_review_v1.sql new file mode 100644 index 00000000..35272b68 --- /dev/null +++ b/docs/sql/30_rpc/finance/rpc_admin_extract_review_v1.sql @@ -0,0 +1,78 @@ +-- ===================================================================================== +-- Admin 财务功能 - 提现审核 RPC (口径 2) +-- 位置:docs/sql/30_rpc/finance/ +-- 版本:v1 +-- 描述:提现审核通过时才扣除佣金并生成流水。 +-- 安全策略:SECURITY DEFINER, 入口鉴权, 固定 search_path +-- ===================================================================================== + +CREATE OR REPLACE FUNCTION public.rpc_admin_extract_review( + p_extract_id UUID, + p_status SMALLINT, -- 1: 通过, -1: 驳回 + p_refusal_reason TEXT DEFAULT NULL +) +RETURNS VOID +SECURITY DEFINER +SET search_path = public +LANGUAGE plpgsql +AS $$ +DECLARE + v_extract RECORD; + v_user RECORD; +BEGIN + -- 1. 鉴权:仅 admin 角色可执行 + IF NOT EXISTS ( + SELECT 1 FROM public.ak_users + WHERE id = auth.uid() AND role = 'admin' + ) THEN + RAISE EXCEPTION 'Permission denied'; + END IF; + + -- 2. 锁定并获取提现记录 + SELECT * INTO v_extract FROM public.ml_extract WHERE id = p_extract_id FOR UPDATE; + IF NOT FOUND THEN RAISE EXCEPTION 'Extract record not found'; END IF; + IF v_extract.status != 0 THEN RAISE EXCEPTION 'Record already processed'; END IF; + + -- 3. 业务处理 + IF p_status = 1 THEN + -- 审核通过:锁定并校验用户资金 + SELECT * INTO v_user FROM public.ak_users WHERE id = v_extract.uid FOR UPDATE; + IF v_user.brokerage_price < v_extract.extract_price THEN + RAISE EXCEPTION 'Insufficient brokerage balance'; + END IF; + + -- 扣除佣金 + UPDATE public.ak_users + SET brokerage_price = brokerage_price - v_extract.extract_price + WHERE id = v_extract.uid; + + -- 写入资金流水 + INSERT INTO public.ml_user_bill (uid, link_id, pm, title, category, type, number, balance) + VALUES ( + v_extract.uid, + p_extract_id::TEXT, + 0, -- 支出 + '佣金提现', + 'brokerage', + 'extract', + v_extract.extract_price, + v_user.brokerage_price - v_extract.extract_price + ); + + -- 更新提现记录 + UPDATE public.ml_extract + SET status = 1, admin_id = auth.uid(), payment_time = now() + WHERE id = p_extract_id; + + ELSIF p_status = -1 THEN + -- 审核驳回:仅更新状态 + UPDATE public.ml_extract + SET status = -1, refusal_reason = p_refusal_reason, admin_id = auth.uid() + WHERE id = p_extract_id; + ELSE + RAISE EXCEPTION 'Invalid status'; + END IF; +END; +$$; + +COMMENT ON FUNCTION public.rpc_admin_extract_review IS '管理员审核提现申请(口径 2:通过时扣款)'; diff --git a/docs/sql/30_rpc/finance/rpc_admin_recharge_audit_v1.sql b/docs/sql/30_rpc/finance/rpc_admin_recharge_audit_v1.sql new file mode 100644 index 00000000..fc6d8008 --- /dev/null +++ b/docs/sql/30_rpc/finance/rpc_admin_recharge_audit_v1.sql @@ -0,0 +1,64 @@ +-- ===================================================================================== +-- Admin 财务功能 - 充值补单/审计 RPC +-- 位置:docs/sql/30_rpc/finance/ +-- 版本:v1 +-- 描述:由管理员发起的人工充值补单或离线支付审计确认。 +-- 安全策略:SECURITY DEFINER, 入口鉴权, 固定 search_path +-- ===================================================================================== + +CREATE OR REPLACE FUNCTION public.rpc_admin_recharge_audit( + p_recharge_id UUID, + p_mark TEXT DEFAULT '管理员人工审计/补单' +) +RETURNS VOID +SECURITY DEFINER +SET search_path = public +LANGUAGE plpgsql +AS $$ +DECLARE + v_recharge RECORD; + v_user RECORD; +BEGIN + -- 1. 鉴权:仅 admin 角色可执行 + IF NOT EXISTS ( + SELECT 1 FROM public.ak_users + WHERE id = auth.uid() AND role = 'admin' + ) THEN + RAISE EXCEPTION 'Permission denied'; + END IF; + + -- 2. 锁定并获取充值记录 + SELECT * INTO v_recharge FROM public.ml_user_recharge WHERE id = p_recharge_id FOR UPDATE; + IF NOT FOUND THEN RAISE EXCEPTION 'Recharge record not found'; END IF; + IF v_recharge.paid = 1 THEN RAISE EXCEPTION 'Recharge already paid'; END IF; + + -- 3. 锁定并更新用户余额 + SELECT * INTO v_user FROM public.ak_users WHERE id = v_recharge.uid FOR UPDATE; + + UPDATE public.ak_users + SET now_money = now_money + v_recharge.price + v_recharge.give_price + WHERE id = v_recharge.uid; + + -- 4. 写入资金流水 + INSERT INTO public.ml_user_bill (uid, link_id, pm, title, category, type, number, balance, mark) + VALUES ( + v_recharge.uid, + v_recharge.order_no, + 1, -- 收入 + '用户充值', + 'now_money', + 'recharge', + v_recharge.price + v_recharge.give_price, + v_user.now_money + v_recharge.price + v_recharge.give_price, + p_mark + ); + + -- 5. 更新充值记录状态 + UPDATE public.ml_user_recharge + SET paid = 1, pay_time = now() + WHERE id = p_recharge_id; + +END; +$$; + +COMMENT ON FUNCTION public.rpc_admin_recharge_audit IS '管理员人工审计/补单(更新用户余额并生成流水)'; diff --git a/docs/sql/30_rpc/finance/rpc_admin_recharge_list_v1.sql b/docs/sql/30_rpc/finance/rpc_admin_recharge_list_v1.sql new file mode 100644 index 00000000..bad4e26c --- /dev/null +++ b/docs/sql/30_rpc/finance/rpc_admin_recharge_list_v1.sql @@ -0,0 +1,92 @@ +-- ===================================================================================== +-- Admin 财务功能 - 充值记录列表分页查询 RPC +-- 位置:docs/sql/30_rpc/finance/ +-- 对象类型:RPC 函数(SECURITY DEFINER) +-- 版本:v1 +-- 依赖:ml_user_recharge, ak_users 表已存在 +-- 权限:仅 admin 角色可执行(口径 A:全局数据访问通过 RPC) +-- ===================================================================================== + +CREATE OR REPLACE FUNCTION public.rpc_admin_recharge_list( + p_page INTEGER DEFAULT 1, + p_page_size INTEGER DEFAULT 15, + p_paid SMALLINT DEFAULT NULL, + p_start_time TIMESTAMP WITH TIME ZONE DEFAULT NULL, + p_end_time TIMESTAMP WITH TIME ZONE DEFAULT NULL, + p_search TEXT 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. 权限检查 + IF NOT EXISTS ( + SELECT 1 FROM public.ak_users + WHERE id = auth.uid() AND role = 'admin' + ) THEN + RAISE EXCEPTION 'Permission denied'; + END IF; + + v_offset := (p_page - 1) * p_page_size; + + -- 2. 获取总数 + SELECT COUNT(*) INTO v_total + FROM public.ml_user_recharge r + LEFT JOIN public.ak_users u ON u.id = r.uid + WHERE (p_paid IS NULL OR r.paid = p_paid) + AND (p_start_time IS NULL OR r.created_at >= p_start_time) + AND (p_end_time IS NULL OR r.created_at <= p_end_time) + AND (p_search IS NULL OR ( + COALESCE(r.order_no, '') ILIKE '%' || p_search || '%' OR + COALESCE(u.username, '') ILIKE '%' || p_search || '%' OR + COALESCE(u.email, '') ILIKE '%' || p_search || '%' + )); + + -- 3. 获取明细数据 + SELECT jsonb_agg(t) INTO v_items + FROM ( + SELECT + r.id, + r.uid, + r.order_no, + r.recharge_type, + r.price, + r.give_price, + r.paid, + r.pay_time, + r.channel_trade_no, + r.status, + r.created_at, + r.updated_at, + u.username as user_name, + u.email as user_email + FROM public.ml_user_recharge r + LEFT JOIN public.ak_users u ON u.id = r.uid + WHERE (p_paid IS NULL OR r.paid = p_paid) + AND (p_start_time IS NULL OR r.created_at >= p_start_time) + AND (p_end_time IS NULL OR r.created_at <= p_end_time) + AND (p_search IS NULL OR ( + COALESCE(r.order_no, '') ILIKE '%' || p_search || '%' OR + COALESCE(u.username, '') ILIKE '%' || p_search || '%' OR + COALESCE(u.email, '') ILIKE '%' || p_search || '%' + )) + ORDER BY r.created_at DESC + LIMIT p_page_size + OFFSET v_offset + ) t; + + -- 4. 返回结果 + RETURN jsonb_build_object( + 'total', v_total, + 'items', COALESCE(v_items, '[]'::jsonb) + ); +END; +$$; + +COMMENT ON FUNCTION public.rpc_admin_recharge_list IS '管理员充值记录列表分页查询'; diff --git a/docs/sql/30_rpc/finance/rpc_admin_user_bill_list_v1.sql b/docs/sql/30_rpc/finance/rpc_admin_user_bill_list_v1.sql new file mode 100644 index 00000000..92900e19 --- /dev/null +++ b/docs/sql/30_rpc/finance/rpc_admin_user_bill_list_v1.sql @@ -0,0 +1,99 @@ +-- ===================================================================================== +-- Admin 财务功能 - 资金流水列表分页查询 RPC +-- 位置:docs/sql/30_rpc/finance/ +-- 对象类型:RPC 函数(SECURITY DEFINER) +-- 版本:v1 +-- 依赖:ml_user_bill, ak_users 表已存在 +-- 权限:仅 admin 角色可执行(口径 A:全局数据访问通过 RPC) +-- ===================================================================================== + +CREATE OR REPLACE FUNCTION public.rpc_admin_user_bill_list( + p_page INTEGER DEFAULT 1, + p_page_size INTEGER DEFAULT 15, + p_category VARCHAR DEFAULT NULL, + p_type VARCHAR DEFAULT NULL, + p_pm SMALLINT DEFAULT NULL, + p_start_time TIMESTAMP WITH TIME ZONE DEFAULT NULL, + p_end_time TIMESTAMP WITH TIME ZONE DEFAULT NULL, + p_search TEXT 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. 权限检查 + IF NOT EXISTS ( + SELECT 1 FROM public.ak_users + WHERE id = auth.uid() AND role = 'admin' + ) THEN + RAISE EXCEPTION 'Permission denied'; + END IF; + + v_offset := (p_page - 1) * p_page_size; + + -- 2. 获取总数 + SELECT COUNT(*) INTO v_total + FROM public.ml_user_bill b + LEFT JOIN public.ak_users u ON u.id = b.uid + WHERE (p_category IS NULL OR b.category = p_category) + AND (p_type IS NULL OR b.type = p_type) + AND (p_pm IS NULL OR b.pm = p_pm) + AND (p_start_time IS NULL OR b.created_at >= p_start_time) + AND (p_end_time IS NULL OR b.created_at <= p_end_time) + AND (p_search IS NULL OR ( + COALESCE(b.title, '') ILIKE '%' || p_search || '%' OR + COALESCE(u.username, '') ILIKE '%' || p_search || '%' OR + COALESCE(u.email, '') ILIKE '%' || p_search || '%' + )); + + -- 3. 获取明细数据 + SELECT jsonb_agg(t) INTO v_items + FROM ( + SELECT + b.id, + b.uid, + b.link_id, + b.pm, + b.title, + b.category, + b.type, + b.number, + b.balance, + b.mark, + b.status, + b.created_at, + b.updated_at, + u.username as user_name, + u.email as user_email + FROM public.ml_user_bill b + LEFT JOIN public.ak_users u ON u.id = b.uid + WHERE (p_category IS NULL OR b.category = p_category) + AND (p_type IS NULL OR b.type = p_type) + AND (p_pm IS NULL OR b.pm = p_pm) + AND (p_start_time IS NULL OR b.created_at >= p_start_time) + AND (p_end_time IS NULL OR b.created_at <= p_end_time) + AND (p_search IS NULL OR ( + COALESCE(b.title, '') ILIKE '%' || p_search || '%' OR + COALESCE(u.username, '') ILIKE '%' || p_search || '%' OR + COALESCE(u.email, '') ILIKE '%' || p_search || '%' + )) + ORDER BY b.created_at DESC + LIMIT p_page_size + OFFSET v_offset + ) t; + + -- 4. 返回结果 + RETURN jsonb_build_object( + 'total', v_total, + 'items', COALESCE(v_items, '[]'::jsonb) + ); +END; +$$; + +COMMENT ON FUNCTION public.rpc_admin_user_bill_list IS '管理员资金流水列表分页查询'; diff --git a/docs/sql/30_rpc/order/rpc_admin_cashier_order_list_v1.sql b/docs/sql/30_rpc/order/rpc_admin_cashier_order_list_v1.sql new file mode 100644 index 00000000..337d739d --- /dev/null +++ b/docs/sql/30_rpc/order/rpc_admin_cashier_order_list_v1.sql @@ -0,0 +1,68 @@ +-- ===================================================================================== +-- Admin 订单功能 - 收银台订单列表分页查询 RPC +-- 位置:docs/sql/30_rpc/order/ +-- 对象类型:RPC 函数(SECURITY DEFINER) +-- 版本:v1 +-- ===================================================================================== + +CREATE OR REPLACE FUNCTION public.rpc_admin_cashier_order_list( + p_page INTEGER DEFAULT 1, + p_page_size INTEGER DEFAULT 15, + p_search_order_no TEXT DEFAULT NULL, + p_search_username TEXT 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. 权限检查 + 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 o.paid_at IS NOT NULL + AND (p_search_order_no IS NULL OR o.order_no ILIKE '%' || p_search_order_no || '%') + AND (p_search_username IS NULL OR u.username ILIKE '%' || p_search_username || '%'); + + -- 3. 明细 + SELECT jsonb_agg(t) INTO v_items + FROM ( + SELECT + o.id, + o.order_no, + o.total_amount, + o.discount_amount, + o.paid_at, + u.username as customer_name, + u.phone as customer_phone + FROM public.ml_orders o + LEFT JOIN public.ak_users u ON o.user_id = u.id + WHERE o.paid_at IS NOT NULL + AND (p_search_order_no IS NULL OR o.order_no ILIKE '%' || p_search_order_no || '%') + AND (p_search_username IS NULL OR u.username ILIKE '%' || p_search_username || '%') + ORDER BY o.paid_at DESC + LIMIT p_page_size + OFFSET v_offset + ) t; + + RETURN jsonb_build_object( + 'total', v_total, + 'items', COALESCE(v_items, '[]'::jsonb) + ); +END; +$$; \ No newline at end of file diff --git a/docs/sql/30_rpc/order/rpc_admin_refund_order_list_v1.sql b/docs/sql/30_rpc/order/rpc_admin_refund_order_list_v1.sql new file mode 100644 index 00000000..59584279 --- /dev/null +++ b/docs/sql/30_rpc/order/rpc_admin_refund_order_list_v1.sql @@ -0,0 +1,87 @@ +-- ===================================================================================== +-- Admin 订单功能 - 售后退款列表分页查询 RPC +-- 位置:docs/sql/30_rpc/order/ +-- 对象类型:RPC 函数(SECURITY DEFINER) +-- 版本:v1 +-- ===================================================================================== + +CREATE OR REPLACE FUNCTION public.rpc_admin_refund_order_list( + p_page INTEGER DEFAULT 1, + p_page_size INTEGER DEFAULT 15, + p_refund_status INTEGER DEFAULT NULL, + p_search TEXT 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. 权限检查 + 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_refund_orders ro + WHERE (p_refund_status IS NULL OR ro.refund_status = p_refund_status) + AND (p_search IS NULL OR ( + ro.refund_no ILIKE '%' || p_search || '%' OR + EXISTS ( + SELECT 1 FROM public.ml_orders o + WHERE o.id = ro.order_id AND o.order_no ILIKE '%' || p_search || '%' + ) + )); + + -- 3. 获取明细数据 + SELECT jsonb_agg(t) INTO v_items + FROM ( + SELECT + ro.id, + ro.refund_no, + ro.refund_amount, + ro.refund_status, + ro.refund_reason, + ro.applied_at, + o.order_no, + o.order_status, + u.username as customer_name, + u.phone as customer_phone, + ( + SELECT jsonb_build_object( + 'product_name', oi.product_name, + 'image_url', oi.image_url + ) + FROM public.ml_order_items oi + WHERE oi.order_id = ro.order_id + LIMIT 1 + ) as product_summary + FROM public.ml_refund_orders ro + LEFT JOIN public.ml_orders o ON ro.order_id = o.id + LEFT JOIN public.ak_users u ON ro.user_id = u.id + WHERE (p_refund_status IS NULL OR ro.refund_status = p_refund_status) + AND (p_search IS NULL OR ( + ro.refund_no ILIKE '%' || p_search || '%' OR + o.order_no ILIKE '%' || p_search || '%' + )) + ORDER BY ro.applied_at DESC + LIMIT p_page_size + OFFSET v_offset + ) t; + + RETURN jsonb_build_object( + 'total', v_total, + 'items', COALESCE(v_items, '[]'::jsonb) + ); +END; +$$; \ No newline at end of file diff --git a/docs/sql/30_rpc/order/rpc_admin_write_off_record_list_v1.sql b/docs/sql/30_rpc/order/rpc_admin_write_off_record_list_v1.sql new file mode 100644 index 00000000..cca64ddd --- /dev/null +++ b/docs/sql/30_rpc/order/rpc_admin_write_off_record_list_v1.sql @@ -0,0 +1,80 @@ +-- ===================================================================================== +-- Admin 订单功能 - 核销记录列表分页查询 RPC +-- 位置:docs/sql/30_rpc/order/ +-- 对象类型:RPC 函数(SECURITY DEFINER) +-- 版本:v1 +-- ===================================================================================== + +CREATE OR REPLACE FUNCTION public.rpc_admin_write_off_record_list( + p_page INTEGER DEFAULT 1, + p_page_size INTEGER DEFAULT 15, + p_search TEXT DEFAULT NULL, + p_verified_only BOOLEAN DEFAULT TRUE +) +RETURNS JSONB +SECURITY DEFINER +SET search_path = public +LANGUAGE plpgsql +AS $$ +DECLARE + v_offset INTEGER; + v_total BIGINT; + 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; + + v_offset := (p_page - 1) * p_page_size; + + -- 2. 获取总数(核销订单类型 = 3) + SELECT COUNT(*) INTO v_total + FROM public.ml_orders o + WHERE o.order_type = 3 + AND (p_verified_only = FALSE OR o.verified_at IS NOT NULL) + AND (p_search IS NULL OR o.order_no ILIKE '%' || p_search || '%'); + + -- 3. 获取明细 + SELECT jsonb_agg(t) INTO v_items + FROM ( + SELECT + o.id, + o.order_no, + o.total_amount, + o.payment_status, + o.order_status, + o.created_at, + o.verified_at, + buyer.username as customer_name, + buyer.phone as customer_phone, + verifier.username as verifier_name, + ( + SELECT jsonb_build_object( + 'product_name', oi.product_name, + 'image_url', oi.image_url + ) + FROM public.ml_order_items oi + WHERE oi.order_id = o.id + LIMIT 1 + ) as product_summary + FROM public.ml_orders o + LEFT JOIN public.ak_users buyer ON o.user_id = buyer.id + LEFT JOIN public.ak_users verifier ON o.verifier_id = verifier.id + WHERE o.order_type = 3 + AND (p_verified_only = FALSE OR o.verified_at IS NOT NULL) + AND (p_search IS NULL OR o.order_no ILIKE '%' || p_search || '%') + ORDER BY o.verified_at DESC NULLS LAST, 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; +$$; \ No newline at end of file diff --git a/docs/sql/30_rpc/product/rpc_admin_category_delete_v1.sql b/docs/sql/30_rpc/product/rpc_admin_category_delete_v1.sql new file mode 100644 index 00000000..7b3cfc9b --- /dev/null +++ b/docs/sql/30_rpc/product/rpc_admin_category_delete_v1.sql @@ -0,0 +1,46 @@ +-- ===================================================================================== +-- Admin 商品模块 - 删除分类 RPC +-- 位置:docs/sql/30_rpc/product/ +-- 对象类型:RPC 函数(SECURITY DEFINER) +-- 方案:方案 1(有子项禁止删除) +-- 版本:v1 +-- 依赖:ml_categories, ak_users 表已存在 +-- ===================================================================================== + +CREATE OR REPLACE FUNCTION public.rpc_admin_category_delete( + p_id UUID +) +RETURNS BOOLEAN +SECURITY DEFINER +SET search_path = public +LANGUAGE plpgsql +AS $$ +BEGIN + -- 1. 权限检查 + IF NOT EXISTS ( + SELECT 1 FROM public.ak_users + WHERE id = auth.uid() AND role IN ('admin', 'analytics') + ) THEN + RAISE EXCEPTION 'Permission denied'; + END IF; + + -- 2. 检查是否有子分类 (方案 1) + IF EXISTS ( + SELECT 1 FROM public.ml_categories WHERE parent_id = p_id + ) THEN + RAISE EXCEPTION '请先删除该分类下的子分类'; + END IF; + + -- 3. 检查是否有商品关联 (可选,通常作为安全保障) + IF EXISTS ( + SELECT 1 FROM public.ml_products WHERE category_id = p_id AND status != 4 + ) THEN + RAISE EXCEPTION '该分类下仍有商品,无法删除'; + END IF; + + -- 4. 执行删除 + DELETE FROM public.ml_categories WHERE id = p_id; + + RETURN FOUND; +END; +$$; diff --git a/docs/sql/30_rpc/product/rpc_admin_product_analytics_v1.sql b/docs/sql/30_rpc/product/rpc_admin_product_analytics_v1.sql new file mode 100644 index 00000000..270819a9 --- /dev/null +++ b/docs/sql/30_rpc/product/rpc_admin_product_analytics_v1.sql @@ -0,0 +1,105 @@ +-- ===================================================================================== +-- Admin 商品模块 - 商品统计概况 RPC +-- 位置:docs/sql/30_rpc/product/ +-- 对象类型:RPC 函数(SECURITY DEFINER) +-- 版本:v1 +-- 依赖:ml_products, ml_orders, ml_browse_history, ak_users +-- ===================================================================================== + +CREATE OR REPLACE FUNCTION public.rpc_admin_product_stats( + p_start_time TIMESTAMP WITH TIME ZONE, + p_end_time TIMESTAMP WITH TIME ZONE +) +RETURNS JSONB +SECURITY DEFINER +SET search_path = public +LANGUAGE plpgsql +AS $$ +DECLARE + v_stats JSONB; +BEGIN + -- 1. 权限检查 + IF NOT EXISTS ( + SELECT 1 FROM public.ak_users + WHERE id = auth.uid() AND role IN ('admin', 'analytics') + ) THEN + RAISE EXCEPTION 'Permission denied'; + END IF; + + -- 2. 统计核心指标 + -- 商品浏览量 (PV), 访客数 (UV), 支付件数, 支付金额, 退款件数, 退款金额 + WITH stats AS ( + SELECT + (SELECT COALESCE(SUM(browse_duration), 0) FROM public.ml_browse_history WHERE created_at BETWEEN p_start_time AND p_end_time) as total_views, + (SELECT COUNT(DISTINCT user_id) FROM public.ml_browse_history WHERE created_at BETWEEN p_start_time AND p_end_time) as total_visitors, + (SELECT COALESCE(SUM(quantity), 0) FROM public.ml_order_items oi JOIN public.ml_orders o ON oi.order_id = o.id + WHERE o.created_at BETWEEN p_start_time AND p_end_time AND o.order_status NOT IN (1, 5)) as pay_count, + (SELECT COALESCE(SUM(paid_amount), 0) FROM public.ml_orders + WHERE created_at BETWEEN p_start_time AND p_end_time AND order_status NOT IN (1, 5)) as pay_amount, + (SELECT COUNT(*) FROM public.ml_orders WHERE created_at BETWEEN p_start_time AND p_end_time AND order_status = 7) as refund_count, + (SELECT COALESCE(SUM(total_amount), 0) FROM public.ml_orders WHERE created_at BETWEEN p_start_time AND p_end_time AND order_status = 7) as refund_amount + ) + SELECT jsonb_build_object( + 'views', total_views, + 'visitors', total_visitors, + 'pay_count', pay_count, + 'pay_amount', pay_amount, + 'refund_count', refund_count, + 'refund_amount', refund_amount + ) INTO v_stats FROM stats; + + RETURN v_stats; +END; +$$; + +-- ===================================================================================== +-- Admin 商品模块 - 商品排行 RPC +-- ===================================================================================== +CREATE OR REPLACE FUNCTION public.rpc_admin_product_ranking( + p_start_time TIMESTAMP WITH TIME ZONE, + p_end_time TIMESTAMP WITH TIME ZONE, + p_sort_by TEXT DEFAULT 'sales', -- views, sales, amount + p_limit INTEGER DEFAULT 10 +) +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 id = auth.uid() AND role IN ('admin', 'analytics') + ) THEN + RAISE EXCEPTION 'Permission denied'; + END IF; + + -- 2. 获取排行数据 + SELECT jsonb_agg(t) INTO v_items + FROM ( + SELECT + p.id, + p.name, + p.main_image_url as image, + COALESCE(p.view_count, 0) as views, + (SELECT COUNT(DISTINCT user_id) FROM public.ml_browse_history bh WHERE bh.product_id = p.id AND bh.created_at BETWEEN p_start_time AND p_end_time) as visitors, + (SELECT COALESCE(SUM(quantity), 0) FROM public.ml_order_items oi JOIN public.ml_orders o ON oi.order_id = o.id + WHERE oi.product_id = p.id AND o.created_at BETWEEN p_start_time AND p_end_time AND o.order_status NOT IN (1, 5)) as sales, + (SELECT COALESCE(SUM(oi.total_amount), 0) FROM public.ml_order_items oi JOIN public.ml_orders o ON oi.order_id = o.id + WHERE oi.product_id = p.id AND o.created_at BETWEEN p_start_time AND p_end_time AND o.order_status NOT IN (1, 5)) as amount + FROM public.ml_products p + WHERE p.status != 4 + ORDER BY + CASE WHEN p_sort_by = 'views' THEN 4 + WHEN p_sort_by = 'sales' THEN 6 + WHEN p_sort_by = 'amount' THEN 7 + ELSE 6 END DESC + LIMIT p_limit + ) t; + + RETURN COALESCE(v_items, '[]'::jsonb); +END; +$$; diff --git a/pages/mall/admin/docs/ops/2026-02-06__admin__product-module-standardization.md b/pages/mall/admin/docs/ops/2026-02-06__admin__product-module-standardization.md new file mode 100644 index 00000000..648d546d --- /dev/null +++ b/pages/mall/admin/docs/ops/2026-02-06__admin__product-module-standardization.md @@ -0,0 +1,50 @@ +# 操作文档:Admin 商品模块标准化实施 + +- **日期**:2026-02-06 +- **作用域**:`admin` / `product` +- **实施人**:Cascade (AI Assistant) + +## 1. 摘要 +按照 `AGENT_PROJECT_SPEC.md` 规范,完成了 Admin 商品模块从数据库 RPC 到 Service 层,再到前端页面的全链路标准化改造。 + +## 2. 动机 +- 统一商品模块数据访问口径,消除页面 Mock 数据。 +- 增强数据库安全性,所有特权操作均通过 `SECURITY DEFINER` RPC 并包含角色校验。 +- 修复分类层级变动时 `path` 与 `level` 字段不同步的潜在风险。 + +## 3. 影响范围 +- **数据库**:新增/更新了 `rpc_admin_product_*` 和 `rpc_admin_category_*` 系列函数。 +- **服务层**:新增 `services/admin/productService.uts` 和 `services/admin/productCategoryService.uts`。 +- **前端页面**:重构了 `product-management/index.uvue` 和 `classification/index.uvue`。 + +## 4. 变更清单 + +### 4.1 数据库 RPC (docs/sql/30_rpc/product/) +- `rpc_admin_product_list_v1.sql`: 标准化分页查询,对齐 `JSONB` 返回结构。 +- `rpc_admin_product_update_status_v1.sql`: 统一处理上下架与回收站逻辑。 +- `rpc_admin_category_list_v1.sql`: 适配 `ml_categories` 权威字段。 +- `rpc_admin_category_create_v1.sql`: 自动维护层级路径。 +- `rpc_admin_category_update_v1.sql`: **核心增强**,支持子树 `path` 与 `level` 的级联更新,并具备递归防循环引用校验。 +- `rpc_admin_category_delete_v1.sql`: 实现“有子项禁止删除”的安全策略。 + +### 4.2 服务层 (services/admin/) +- `productService.uts`: 封装商品列表与状态变更接口。 +- `productCategoryService.uts`: 封装分类列表与 CRUD 接口。 + +### 4.3 前端重构 +- **商品管理**:接入真实数据流,支持按名称、状态搜索,支持实时上下架切换。 +- **商品分类**:接入真实树形数据,支持完整的 CRUD 操作与状态开关。 + +## 5. 安全与权限验证 +- **RPC 安全**:所有函数均声明为 `SECURITY DEFINER`,并固定 `search_path = public`。 +- **角色守卫**:函数入口显式校验 `role IN ('admin', 'analytics')`。 +- **数据隔离**:仅返回 UI 渲染必要的最小字段集。 + +## 6. 回滚方案 +- **SQL**:执行 `DROP FUNCTION IF EXISTS public.rpc_admin_...`。 +- **代码**:通过 Git 回退 `pages/mall/admin/product/` 相关目录的变更。 + +## 7. 验证方式 +1. 登录 Admin 账号,进入“商品管理”,验证列表分页与搜索是否正常。 +2. 切换商品“上架/下架”开关,刷新页面确认状态持久化。 +3. 进入“商品分类”,尝试添加子分类并移动其父级,通过数据库查询确认其 `path` 已级联修正。 diff --git a/pages/mall/admin/finance/transaction_stats.uvue b/pages/mall/admin/finance/transaction_stats.uvue index 7a9bb9c0..18ea22cf 100644 --- a/pages/mall/admin/finance/transaction_stats.uvue +++ b/pages/mall/admin/finance/transaction_stats.uvue @@ -41,7 +41,7 @@ 🕒 营业额 - 442753.70 + {{ stats.revenue }} 环比增长: 44275370% ▲ @@ -52,7 +52,7 @@ ¥ 商品支付金额 - 434693.52 + {{ stats.payAmount }} 环比增长: 43469352% ▲ @@ -63,7 +63,7 @@ 🔒 购买会员金额 - 8059.18 + {{ stats.memberAmount }} 环比增长: 805918% ▲ @@ -74,7 +74,7 @@ 💰 充值金额 - 0.00 + {{ stats.rechargeAmount }} 环比增长: 0% - @@ -85,7 +85,7 @@ 🛒 线下收银金额 - 1 + {{ stats.offlineAmount }} 环比增长: 100% ▲ @@ -100,7 +100,7 @@ 支出金额 - 442752.69 + {{ stats.expenditure }} 环比增长: 44275269% ▲ @@ -111,7 +111,7 @@ 💳 余额支付金额 - 442752.69 + {{ stats.balancePay }} 环比增长: 5293.00% ▲ @@ -122,7 +122,7 @@ 支付佣金金额 - 0.00 + {{ stats.commissionPay }} 环比增长: 0% - @@ -133,7 +133,7 @@ 📦 商品退款金额 - 0.00 + {{ stats.refundAmount }} 环比增长: 0% - @@ -281,42 +281,69 @@