From 5acda051341bc00be1d8990a6793df593347b9e3 Mon Sep 17 00:00:00 2001
From: comlibmb <1844410276@qq.com>
Date: Mon, 16 Feb 2026 15:19:17 +0800
Subject: [PATCH] =?UTF-8?q?admin=E6=8E=A5=E5=85=A5=E6=95=B0=E6=8D=AE?=
=?UTF-8?q?=E5=BA=93?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../ak_distribution_agent_applications_v1.sql | 34 ++
.../ak_distribution_agents_v1.sql | 48 +--
.../ak_distribution_divisions_v1.sql | 54 ++-
.../distribution/ml_distribution_rls_v1.sql | 52 +++
.../rpc_admin_delete_agent_v1.sql | 31 ++
.../rpc_admin_delete_division_v1.sql | 38 +++
.../rpc_admin_get_agent_apply_list_v1.sql | 68 ++++
.../rpc_admin_get_agent_list_v1.sql | 62 ++++
.../rpc_admin_get_division_list_v1.sql | 59 ++++
.../rpc_admin_process_agent_apply_v1.sql | 69 ++++
.../distribution/rpc_admin_save_agent_v1.sql | 54 +++
.../rpc_admin_save_division_v1.sql | 47 +++
.../admin/distribution/division/agent.uvue | 279 +++++++++++++---
.../admin/distribution/division/apply.uvue | 214 +++++++-----
.../admin/distribution/division/list.uvue | 249 ++++++++++----
pages/mall/admin/marketing/groupbuy/list.uvue | 297 ++++++++++++++++-
.../mall/admin/marketing/recharge/record.uvue | 241 +++++++++++++-
services/admin/distributionService.uts | 310 +++++++-----------
18 files changed, 1736 insertions(+), 470 deletions(-)
create mode 100644 docs/sql/10_schema/distribution/ak_distribution_agent_applications_v1.sql
create mode 100644 docs/sql/20_rls/distribution/ml_distribution_rls_v1.sql
create mode 100644 docs/sql/30_rpc/distribution/rpc_admin_delete_agent_v1.sql
create mode 100644 docs/sql/30_rpc/distribution/rpc_admin_delete_division_v1.sql
create mode 100644 docs/sql/30_rpc/distribution/rpc_admin_get_agent_apply_list_v1.sql
create mode 100644 docs/sql/30_rpc/distribution/rpc_admin_get_agent_list_v1.sql
create mode 100644 docs/sql/30_rpc/distribution/rpc_admin_get_division_list_v1.sql
create mode 100644 docs/sql/30_rpc/distribution/rpc_admin_process_agent_apply_v1.sql
create mode 100644 docs/sql/30_rpc/distribution/rpc_admin_save_agent_v1.sql
create mode 100644 docs/sql/30_rpc/distribution/rpc_admin_save_division_v1.sql
diff --git a/docs/sql/10_schema/distribution/ak_distribution_agent_applications_v1.sql b/docs/sql/10_schema/distribution/ak_distribution_agent_applications_v1.sql
new file mode 100644
index 00000000..dcf06cba
--- /dev/null
+++ b/docs/sql/10_schema/distribution/ak_distribution_agent_applications_v1.sql
@@ -0,0 +1,34 @@
+-- =====================================================================================
+-- Schema: 分销代理商申请表
+-- 位置:docs/sql/10_schema/distribution/ak_distribution_agent_applications_v1.sql
+-- 对象类型:TABLE
+-- 版本:v1
+-- 依赖:ak_users, ak_distribution_divisions
+-- =====================================================================================
+
+CREATE TABLE IF NOT EXISTS public.ak_distribution_agent_applications (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ uid UUID NOT NULL REFERENCES public.ak_users(id) ON DELETE CASCADE,
+ division_uid UUID NOT NULL REFERENCES public.ak_distribution_divisions(uid),
+
+ agent_name TEXT NOT NULL,
+ agent_phone TEXT NULL,
+
+ proof_images JSONB NULL, -- 申请凭证图片列表
+
+ status TEXT NOT NULL DEFAULT 'pending', -- pending/approved/rejected
+ refusal_reason TEXT NULL,
+
+ approved_at TIMESTAMPTZ NULL,
+ approved_by UUID NULL REFERENCES public.ak_users(id),
+
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
+);
+
+CREATE INDEX IF NOT EXISTS idx_dist_agent_applications_uid ON public.ak_distribution_agent_applications(uid);
+CREATE INDEX IF NOT EXISTS idx_dist_agent_applications_division_uid ON public.ak_distribution_agent_applications(division_uid);
+CREATE INDEX IF NOT EXISTS idx_dist_agent_applications_status ON public.ak_distribution_agent_applications(status);
+
+COMMENT ON TABLE public.ak_distribution_agent_applications IS '分销代理商申请记录表';
+COMMENT ON COLUMN public.ak_distribution_agent_applications.proof_images IS '申请图片列表(JSON)';
diff --git a/docs/sql/10_schema/distribution/ak_distribution_agents_v1.sql b/docs/sql/10_schema/distribution/ak_distribution_agents_v1.sql
index 4757ce83..adf33ab2 100644
--- a/docs/sql/10_schema/distribution/ak_distribution_agents_v1.sql
+++ b/docs/sql/10_schema/distribution/ak_distribution_agents_v1.sql
@@ -1,41 +1,29 @@
-- =====================================================================================
-- Schema: 分销代理商管理表
-- 位置:docs/sql/10_schema/distribution/ak_distribution_agents_v1.sql
--- 说明:管理事业部旗下的代理商,按商家隔离。
+-- 对象类型:TABLE
+-- 版本:v1
+-- 依赖:ak_users, ak_distribution_divisions
-- =====================================================================================
CREATE TABLE IF NOT EXISTS public.ak_distribution_agents (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- merchant_id UUID NOT NULL REFERENCES public.ak_users(id) ON DELETE CASCADE,
- division_id UUID NOT NULL REFERENCES public.ak_distribution_divisions(id) ON DELETE CASCADE,
- uid UUID NOT NULL REFERENCES public.ak_users(id) ON DELETE CASCADE,
-
- name TEXT NOT NULL, -- 代理商名称(或备注名)
- status BOOLEAN DEFAULT true, -- 状态: true开启, false关闭
-
+ uid UUID PRIMARY KEY REFERENCES public.ak_users(id) ON DELETE CASCADE,
+ division_uid UUID NOT NULL REFERENCES public.ak_distribution_divisions(uid), -- 所属事业部
+ name TEXT NOT NULL,
+ commission_ratio NUMERIC(5,2) DEFAULT 0 CHECK (commission_ratio >= 0 AND commission_ratio <= 100),
+ is_enabled BOOLEAN DEFAULT TRUE,
+ end_time TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
-
- -- 约束:一个用户在一个商家下只能成为一个代理商
- UNIQUE(merchant_id, uid)
+ created_by UUID REFERENCES public.ak_users(id),
+ updated_by UUID REFERENCES public.ak_users(id)
);
--- 启用 RLS
-ALTER TABLE public.ak_distribution_agents ENABLE ROW LEVEL SECURITY;
-
--- 权限策略:商家仅能管理自己的代理商
-CREATE POLICY "Merchants manage their own agents"
-ON public.ak_distribution_agents FOR ALL
-TO authenticated
-USING (merchant_id = auth.uid())
-WITH CHECK (merchant_id = auth.uid());
-
--- 允许查看
-CREATE POLICY "Authenticated users view active agents"
-ON public.ak_distribution_agents FOR SELECT
-TO authenticated
-USING (status = true);
-
-- 索引
-CREATE INDEX IF NOT EXISTS idx_agents_merchant ON public.ak_distribution_agents(merchant_id);
-CREATE INDEX IF NOT EXISTS idx_agents_division ON public.ak_distribution_agents(division_id);
+CREATE INDEX IF NOT EXISTS idx_distribution_agents_division_uid ON public.ak_distribution_agents(division_uid);
+
+-- 注释
+COMMENT ON TABLE public.ak_distribution_agents IS '分销代理商信息表';
+COMMENT ON COLUMN public.ak_distribution_agents.uid IS '用户ID(关联代理商本人)';
+COMMENT ON COLUMN public.ak_distribution_agents.division_uid IS '所属事业部UID';
+COMMENT ON COLUMN public.ak_distribution_agents.commission_ratio IS '代理商固定分佣比例(%)';
diff --git a/docs/sql/10_schema/distribution/ak_distribution_divisions_v1.sql b/docs/sql/10_schema/distribution/ak_distribution_divisions_v1.sql
index e0e58126..66122446 100644
--- a/docs/sql/10_schema/distribution/ak_distribution_divisions_v1.sql
+++ b/docs/sql/10_schema/distribution/ak_distribution_divisions_v1.sql
@@ -1,46 +1,30 @@
-- =====================================================================================
-- Schema: 分销事业部管理表
-- 位置:docs/sql/10_schema/distribution/ak_distribution_divisions_v1.sql
--- 说明:管理分销体系中的事业部,支持独立分销比例、邀请码及有效期,按商家隔离。
+-- 对象类型:TABLE
+-- 版本:v1
+-- 依赖:ak_users
-- =====================================================================================
CREATE TABLE IF NOT EXISTS public.ak_distribution_divisions (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- merchant_id UUID NOT NULL REFERENCES public.ak_users(id) ON DELETE CASCADE,
-
- uid UUID NOT NULL REFERENCES public.ak_users(id), -- 事业部负责人UID
- name TEXT NOT NULL, -- 事业部名称
- invite_code TEXT UNIQUE NOT NULL, -- 事业部专属邀请码
-
- ratio DECIMAL(5,2) DEFAULT 0, -- 事业部额外分销比例 (%)
- agent_count INTEGER DEFAULT 0, -- 下属代理商数量 (由程序或触发器维护)
-
- end_time TIMESTAMPTZ, -- 协议截止时间
- status BOOLEAN DEFAULT true, -- 状态: true开启, false关闭
-
+ uid UUID PRIMARY KEY REFERENCES public.ak_users(id) ON DELETE CASCADE,
+ name TEXT NOT NULL,
+ invite_code TEXT UNIQUE NOT NULL,
+ commission_ratio NUMERIC(5,2) DEFAULT 0 CHECK (commission_ratio >= 0 AND commission_ratio <= 100),
+ is_enabled BOOLEAN DEFAULT TRUE,
+ end_time TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
-
- -- 约束:一个用户在一个商家下只能负责一个事业部
- UNIQUE(merchant_id, uid)
+ created_by UUID REFERENCES public.ak_users(id),
+ updated_by UUID REFERENCES public.ak_users(id)
);
--- 启用 RLS
-ALTER TABLE public.ak_distribution_divisions ENABLE ROW LEVEL SECURITY;
-
--- 权限策略
-CREATE POLICY "Merchants manage their own divisions"
-ON public.ak_distribution_divisions FOR ALL
-TO authenticated
-USING (merchant_id = auth.uid())
-WITH CHECK (merchant_id = auth.uid());
-
--- 允许查看
-CREATE POLICY "Authenticated users view active divisions"
-ON public.ak_distribution_divisions FOR SELECT
-TO authenticated
-USING (status = true);
-
-- 索引
-CREATE INDEX IF NOT EXISTS idx_divisions_merchant ON public.ak_distribution_divisions(merchant_id);
-CREATE INDEX IF NOT EXISTS idx_divisions_uid ON public.ak_distribution_divisions(uid);
+CREATE INDEX IF NOT EXISTS idx_distribution_divisions_invite_code ON public.ak_distribution_divisions(invite_code);
+
+-- 注释
+COMMENT ON TABLE public.ak_distribution_divisions IS '分销事业部信息表';
+COMMENT ON COLUMN public.ak_distribution_divisions.uid IS '用户ID(关联事业部负责人)';
+COMMENT ON COLUMN public.ak_distribution_divisions.invite_code IS '事业部专属邀请码';
+COMMENT ON COLUMN public.ak_distribution_divisions.commission_ratio IS '事业部固定分佣比例(%)';
+COMMENT ON COLUMN public.ak_distribution_divisions.end_time IS '事业部有效截止时间';
diff --git a/docs/sql/20_rls/distribution/ml_distribution_rls_v1.sql b/docs/sql/20_rls/distribution/ml_distribution_rls_v1.sql
new file mode 100644
index 00000000..32722f20
--- /dev/null
+++ b/docs/sql/20_rls/distribution/ml_distribution_rls_v1.sql
@@ -0,0 +1,52 @@
+-- =====================================================================================
+-- RLS: 分销模块安全策略
+-- 位置:docs/sql/20_rls/distribution/ml_distribution_rls_v1.sql
+-- 对象类型:RLS 策略
+-- 版本:v1
+-- 说明:管理端全量权限通过 SECURITY DEFINER RPC 执行;用户仅能访问个人关联数据
+-- =====================================================================================
+
+-- 启用 RLS
+ALTER TABLE public.ak_distribution_config ENABLE ROW LEVEL SECURITY;
+ALTER TABLE public.ak_distribution_level ENABLE ROW LEVEL SECURITY;
+ALTER TABLE public.ak_promoter_relations ENABLE ROW LEVEL SECURITY;
+ALTER TABLE public.ak_commission_logs ENABLE ROW LEVEL SECURITY;
+ALTER TABLE public.ak_distribution_divisions ENABLE ROW LEVEL SECURITY;
+ALTER TABLE public.ak_distribution_agents ENABLE ROW LEVEL SECURITY;
+ALTER TABLE public.ak_distribution_agent_applications ENABLE ROW LEVEL SECURITY;
+
+-- 1. 分销配置:允许所有登录用户读取(消费者端展示逻辑需要)
+DROP POLICY IF EXISTS dist_config_select_policy ON public.ak_distribution_config;
+CREATE POLICY dist_config_select_policy ON public.ak_distribution_config
+FOR SELECT TO authenticated USING (true);
+
+-- 2. 分销等级:允许所有登录用户读取可见等级
+DROP POLICY IF EXISTS dist_level_select_policy ON public.ak_distribution_level;
+CREATE POLICY dist_level_select_policy ON public.ak_distribution_level
+FOR SELECT TO authenticated USING (is_visible = true);
+
+-- 3. 推广员关系:用户仅能查看与自己相关的记录
+DROP POLICY IF EXISTS promoter_relations_select_policy ON public.ak_promoter_relations;
+CREATE POLICY promoter_relations_select_policy ON public.ak_promoter_relations
+FOR SELECT TO authenticated USING (uid = auth.uid() OR inviter_uid = auth.uid());
+
+-- 4. 佣金日志:用户仅能查看自己的佣金记录
+DROP POLICY IF EXISTS commission_logs_select_policy ON public.ak_commission_logs;
+CREATE POLICY commission_logs_select_policy ON public.ak_commission_logs
+FOR SELECT TO authenticated USING (uid = auth.uid());
+
+-- 5. 事业部与代理商:允许登录用户查看启用的记录
+DROP POLICY IF EXISTS dist_divisions_select_policy ON public.ak_distribution_divisions;
+CREATE POLICY dist_divisions_select_policy ON public.ak_distribution_divisions
+FOR SELECT TO authenticated USING (is_enabled = true);
+
+DROP POLICY IF EXISTS dist_agents_select_policy ON public.ak_distribution_agents;
+CREATE POLICY dist_agents_select_policy ON public.ak_distribution_agents
+FOR SELECT TO authenticated USING (is_enabled = true);
+
+-- 6. 代理商申请:用户仅能管理自己的申请记录
+DROP POLICY IF EXISTS dist_apply_user_policy ON public.ak_distribution_agent_applications;
+CREATE POLICY dist_apply_user_policy ON public.ak_distribution_agent_applications
+FOR ALL TO authenticated USING (uid = auth.uid()) WITH CHECK (uid = auth.uid());
+
+-- 管理端全量管理将通过 SECURITY DEFINER 的 RPC 接口执行,此处不再额外开放直接表操作
diff --git a/docs/sql/30_rpc/distribution/rpc_admin_delete_agent_v1.sql b/docs/sql/30_rpc/distribution/rpc_admin_delete_agent_v1.sql
new file mode 100644
index 00000000..13d5018b
--- /dev/null
+++ b/docs/sql/30_rpc/distribution/rpc_admin_delete_agent_v1.sql
@@ -0,0 +1,31 @@
+-- RPC: rpc_admin_delete_agent
+-- 管理端删除代理商
+
+CREATE OR REPLACE FUNCTION public.rpc_admin_delete_agent(
+ p_uid uuid
+)
+RETURNS boolean
+LANGUAGE plpgsql
+SECURITY DEFINER
+SET search_path = public
+AS $$
+DECLARE
+ v_ok boolean;
+BEGIN
+ -- 仅管理员可操作
+ IF NOT EXISTS (
+ SELECT 1 FROM public.ak_users u
+ WHERE u.id = auth.uid() AND u.role = 'admin'
+ ) THEN
+ RAISE EXCEPTION 'permission denied';
+ END IF;
+
+ DELETE FROM public.ak_distribution_agents WHERE uid = p_uid;
+
+ GET DIAGNOSTICS v_ok = ROW_COUNT;
+ RETURN v_ok;
+END;
+$$;
+
+REVOKE ALL ON FUNCTION public.rpc_admin_delete_agent(uuid) FROM PUBLIC;
+GRANT EXECUTE ON FUNCTION public.rpc_admin_delete_agent(uuid) TO authenticated;
diff --git a/docs/sql/30_rpc/distribution/rpc_admin_delete_division_v1.sql b/docs/sql/30_rpc/distribution/rpc_admin_delete_division_v1.sql
new file mode 100644
index 00000000..c7781003
--- /dev/null
+++ b/docs/sql/30_rpc/distribution/rpc_admin_delete_division_v1.sql
@@ -0,0 +1,38 @@
+-- RPC: rpc_admin_delete_division
+-- 管理端删除事业部
+
+CREATE OR REPLACE FUNCTION public.rpc_admin_delete_division(
+ p_uid uuid
+)
+RETURNS boolean
+LANGUAGE plpgsql
+SECURITY DEFINER
+SET search_path = public
+AS $$
+DECLARE
+ v_ok boolean;
+BEGIN
+ -- 仅管理员可操作
+ IF NOT EXISTS (
+ SELECT 1 FROM public.ak_users u
+ WHERE u.id = auth.uid() AND u.role = 'admin'
+ ) THEN
+ RAISE EXCEPTION 'permission denied';
+ END IF;
+
+ -- 检查是否有关联代理商
+ IF EXISTS (
+ SELECT 1 FROM public.ak_distribution_agents WHERE division_uid = p_uid
+ ) THEN
+ RAISE EXCEPTION 'cannot delete division with associated agents';
+ END IF;
+
+ DELETE FROM public.ak_distribution_divisions WHERE uid = p_uid;
+
+ GET DIAGNOSTICS v_ok = ROW_COUNT;
+ RETURN v_ok;
+END;
+$$;
+
+REVOKE ALL ON FUNCTION public.rpc_admin_delete_division(uuid) FROM PUBLIC;
+GRANT EXECUTE ON FUNCTION public.rpc_admin_delete_division(uuid) TO authenticated;
diff --git a/docs/sql/30_rpc/distribution/rpc_admin_get_agent_apply_list_v1.sql b/docs/sql/30_rpc/distribution/rpc_admin_get_agent_apply_list_v1.sql
new file mode 100644
index 00000000..ab2980f7
--- /dev/null
+++ b/docs/sql/30_rpc/distribution/rpc_admin_get_agent_apply_list_v1.sql
@@ -0,0 +1,68 @@
+-- RPC: rpc_admin_get_agent_apply_list
+-- 管理端获取代理商申请列表
+-- 支持按状态过滤:all, pending, approved, rejected
+
+CREATE OR REPLACE FUNCTION public.rpc_admin_get_agent_apply_list(
+ p_status text DEFAULT 'all',
+ p_search text DEFAULT NULL,
+ p_page integer DEFAULT 1,
+ p_page_size integer DEFAULT 20
+)
+RETURNS TABLE (
+ id uuid,
+ uid uuid,
+ name text,
+ phone text,
+ dept_uid uuid,
+ dept_name text,
+ proof_images jsonb,
+ status text,
+ refusal_reason text,
+ time timestamptz,
+ invite_code text
+)
+LANGUAGE plpgsql
+SECURITY DEFINER
+SET search_path = public
+AS $$
+DECLARE
+ v_page integer := GREATEST(1, COALESCE(p_page, 1));
+ v_page_size integer := LEAST(200, GREATEST(1, COALESCE(p_page_size, 20)));
+ v_offset integer := (v_page - 1) * v_page_size;
+BEGIN
+ -- 权限检查
+ IF NOT EXISTS (
+ SELECT 1 FROM public.ak_users u
+ WHERE u.id = auth.uid() AND u.role IN ('admin', 'analytics')
+ ) THEN
+ RAISE EXCEPTION 'permission denied';
+ END IF;
+
+ RETURN QUERY
+ SELECT
+ a.id,
+ a.uid,
+ a.agent_name AS name,
+ a.agent_phone AS phone,
+ a.division_uid AS dept_uid,
+ d.name AS dept_name,
+ a.proof_images,
+ a.status,
+ a.refusal_reason,
+ a.created_at AS time,
+ d.invite_code
+ FROM public.ak_distribution_agent_applications a
+ JOIN public.ak_distribution_divisions d ON d.uid = a.division_uid
+ WHERE (p_status = 'all' OR a.status = p_status)
+ AND (
+ p_search IS NULL OR p_search = ''
+ OR a.agent_name ILIKE ('%' || p_search || '%')
+ OR a.uid::text ILIKE ('%' || p_search || '%')
+ )
+ ORDER BY a.created_at DESC
+ LIMIT v_page_size OFFSET v_offset;
+END;
+$$;
+
+REVOKE ALL ON FUNCTION public.rpc_admin_get_agent_apply_list(text, text, integer, integer) FROM PUBLIC;
+GRANT EXECUTE ON FUNCTION public.rpc_admin_get_agent_apply_list(text, text, integer, integer) TO authenticated;
diff --git a/docs/sql/30_rpc/distribution/rpc_admin_get_agent_list_v1.sql b/docs/sql/30_rpc/distribution/rpc_admin_get_agent_list_v1.sql
new file mode 100644
index 00000000..fc73e53c
--- /dev/null
+++ b/docs/sql/30_rpc/distribution/rpc_admin_get_agent_list_v1.sql
@@ -0,0 +1,62 @@
+-- RPC: rpc_admin_get_agent_list
+-- 管理端获取代理商列表
+-- 支持搜索代理商名称或负责人UID,并关联显示所属事业部信息
+
+CREATE OR REPLACE FUNCTION public.rpc_admin_get_agent_list(
+ p_search text DEFAULT NULL,
+ p_page integer DEFAULT 1,
+ p_page_size integer DEFAULT 20
+)
+RETURNS TABLE (
+ uid uuid,
+ name text,
+ division_uid uuid,
+ division_name text,
+ commission_ratio numeric,
+ is_enabled boolean,
+ end_time timestamptz,
+ created_at timestamptz,
+ "staffCount" bigint
+)
+LANGUAGE plpgsql
+SECURITY DEFINER
+SET search_path = public
+AS $$
+DECLARE
+ v_page integer := GREATEST(1, COALESCE(p_page, 1));
+ v_page_size integer := LEAST(200, GREATEST(1, COALESCE(p_page_size, 20)));
+ v_offset integer := (v_page - 1) * v_page_size;
+BEGIN
+ -- 权限检查
+ IF NOT EXISTS (
+ SELECT 1 FROM public.ak_users u
+ WHERE u.id = auth.uid() AND u.role IN ('admin', 'analytics')
+ ) THEN
+ RAISE EXCEPTION 'permission denied';
+ END IF;
+
+ RETURN QUERY
+ SELECT
+ a.uid,
+ a.name,
+ a.division_uid,
+ d.name AS division_name,
+ a.commission_ratio,
+ a.is_enabled,
+ a.end_time,
+ a.created_at,
+ (SELECT COUNT(*) FROM public.ak_promoter_relations r WHERE r.inviter_uid = a.uid)::bigint AS "staffCount"
+ FROM public.ak_distribution_agents a
+ JOIN public.ak_distribution_divisions d ON d.uid = a.division_uid
+ WHERE (
+ p_search IS NULL OR p_search = ''
+ OR a.name ILIKE ('%' || p_search || '%')
+ OR a.uid::text ILIKE ('%' || p_search || '%')
+ )
+ ORDER BY a.created_at DESC
+ LIMIT v_page_size OFFSET v_offset;
+END;
+$$;
+
+REVOKE ALL ON FUNCTION public.rpc_admin_get_agent_list(text, integer, integer) FROM PUBLIC;
+GRANT EXECUTE ON FUNCTION public.rpc_admin_get_agent_list(text, integer, integer) TO authenticated;
diff --git a/docs/sql/30_rpc/distribution/rpc_admin_get_division_list_v1.sql b/docs/sql/30_rpc/distribution/rpc_admin_get_division_list_v1.sql
new file mode 100644
index 00000000..97383aa7
--- /dev/null
+++ b/docs/sql/30_rpc/distribution/rpc_admin_get_division_list_v1.sql
@@ -0,0 +1,59 @@
+-- RPC: rpc_admin_get_division_list
+-- 管理端获取事业部列表
+-- 支持搜索事业部名称或负责人UID
+
+CREATE OR REPLACE FUNCTION public.rpc_admin_get_division_list(
+ p_search text DEFAULT NULL,
+ p_page integer DEFAULT 1,
+ p_page_size integer DEFAULT 20
+)
+RETURNS TABLE (
+ uid uuid,
+ name text,
+ invite_code text,
+ commission_ratio numeric,
+ is_enabled boolean,
+ end_time timestamptz,
+ created_at timestamptz,
+ "agentCount" bigint
+)
+LANGUAGE plpgsql
+SECURITY DEFINER
+SET search_path = public
+AS $$
+DECLARE
+ v_page integer := GREATEST(1, COALESCE(p_page, 1));
+ v_page_size integer := LEAST(200, GREATEST(1, COALESCE(p_page_size, 20)));
+ v_offset integer := (v_page - 1) * v_page_size;
+BEGIN
+ -- 仅管理员或分析员可调用
+ IF NOT EXISTS (
+ SELECT 1 FROM public.ak_users u
+ WHERE u.id = auth.uid() AND u.role IN ('admin', 'analytics')
+ ) THEN
+ RAISE EXCEPTION 'permission denied';
+ END IF;
+
+ RETURN QUERY
+ SELECT
+ d.uid,
+ d.name,
+ d.invite_code,
+ d.commission_ratio,
+ d.is_enabled,
+ d.end_time,
+ d.created_at,
+ (SELECT COUNT(*) FROM public.ak_distribution_agents a WHERE a.division_uid = d.uid)::bigint AS "agentCount"
+ FROM public.ak_distribution_divisions d
+ WHERE (
+ p_search IS NULL OR p_search = ''
+ OR d.name ILIKE ('%' || p_search || '%')
+ OR d.uid::text ILIKE ('%' || p_search || '%')
+ )
+ ORDER BY d.created_at DESC
+ LIMIT v_page_size OFFSET v_offset;
+END;
+$$;
+
+REVOKE ALL ON FUNCTION public.rpc_admin_get_division_list(text, integer, integer) FROM PUBLIC;
+GRANT EXECUTE ON FUNCTION public.rpc_admin_get_division_list(text, integer, integer) TO authenticated;
diff --git a/docs/sql/30_rpc/distribution/rpc_admin_process_agent_apply_v1.sql b/docs/sql/30_rpc/distribution/rpc_admin_process_agent_apply_v1.sql
new file mode 100644
index 00000000..6c33a171
--- /dev/null
+++ b/docs/sql/30_rpc/distribution/rpc_admin_process_agent_apply_v1.sql
@@ -0,0 +1,69 @@
+-- RPC: rpc_admin_process_agent_apply
+-- 管理端审核代理商申请
+-- 若通过(approved),则同步在 ak_distribution_agents 中创建或更新记录
+
+CREATE OR REPLACE FUNCTION public.rpc_admin_process_agent_apply(
+ p_id uuid,
+ p_status text, -- approved / rejected
+ p_refusal_reason text DEFAULT NULL
+)
+RETURNS boolean
+LANGUAGE plpgsql
+SECURITY DEFINER
+SET search_path = public
+AS $$
+DECLARE
+ v_uid uuid;
+ v_division_uid uuid;
+ v_agent_name text;
+BEGIN
+ -- 仅管理员可审核
+ IF NOT EXISTS (
+ SELECT 1 FROM public.ak_users u
+ WHERE u.id = auth.uid() AND u.role = 'admin'
+ ) THEN
+ RAISE EXCEPTION 'permission denied';
+ END IF;
+
+ -- 1. 获取并锁定申请记录
+ SELECT uid, division_uid, agent_name
+ INTO v_uid, v_division_uid, v_agent_name
+ FROM public.ak_distribution_agent_applications
+ WHERE id = p_id;
+
+ IF v_uid IS NULL THEN
+ RAISE EXCEPTION 'application record not found';
+ END IF;
+
+ -- 2. 更新申请状态
+ UPDATE public.ak_distribution_agent_applications
+ SET
+ status = p_status,
+ refusal_reason = CASE WHEN p_status = 'rejected' THEN p_refusal_reason ELSE NULL END,
+ approved_at = now(),
+ approved_by = auth.uid(),
+ updated_at = now()
+ WHERE id = p_id;
+
+ -- 3. 如果通过,则同步到代理商正式表
+ IF p_status = 'approved' THEN
+ INSERT INTO public.ak_distribution_agents (
+ uid, division_uid, name, commission_ratio, is_enabled, updated_at, updated_by
+ )
+ VALUES (
+ v_uid, v_division_uid, v_agent_name, 0, true, now(), auth.uid()
+ )
+ ON CONFLICT (uid) DO UPDATE
+ SET
+ division_uid = EXCLUDED.division_uid,
+ name = EXCLUDED.name,
+ updated_at = now(),
+ updated_by = auth.uid();
+ END IF;
+
+ RETURN true;
+END;
+$$;
+
+REVOKE ALL ON FUNCTION public.rpc_admin_process_agent_apply(uuid, text, text) FROM PUBLIC;
+GRANT EXECUTE ON FUNCTION public.rpc_admin_process_agent_apply(uuid, text, text) TO authenticated;
diff --git a/docs/sql/30_rpc/distribution/rpc_admin_save_agent_v1.sql b/docs/sql/30_rpc/distribution/rpc_admin_save_agent_v1.sql
new file mode 100644
index 00000000..51982c5a
--- /dev/null
+++ b/docs/sql/30_rpc/distribution/rpc_admin_save_agent_v1.sql
@@ -0,0 +1,54 @@
+-- RPC: rpc_admin_save_agent
+-- 管理端新增或更新代理商
+
+CREATE OR REPLACE FUNCTION public.rpc_admin_save_agent(
+ p_uid uuid,
+ p_division_uid uuid,
+ p_name text,
+ p_commission_ratio numeric,
+ p_is_enabled boolean DEFAULT true,
+ p_end_time timestamptz DEFAULT NULL
+)
+RETURNS uuid
+LANGUAGE plpgsql
+SECURITY DEFINER
+SET search_path = public
+AS $$
+BEGIN
+ -- 仅管理员可操作
+ IF NOT EXISTS (
+ SELECT 1 FROM public.ak_users u
+ WHERE u.id = auth.uid() AND u.role = 'admin'
+ ) THEN
+ RAISE EXCEPTION 'permission denied';
+ END IF;
+
+ -- 确保事业部存在
+ IF NOT EXISTS (
+ SELECT 1 FROM public.ak_distribution_divisions WHERE uid = p_division_uid
+ ) THEN
+ RAISE EXCEPTION 'parent division not found';
+ END IF;
+
+ INSERT INTO public.ak_distribution_agents (
+ uid, division_uid, name, commission_ratio, is_enabled, end_time, updated_at, updated_by
+ )
+ VALUES (
+ p_uid, p_division_uid, p_name, p_commission_ratio, p_is_enabled, p_end_time, now(), auth.uid()
+ )
+ ON CONFLICT (uid) DO UPDATE
+ SET
+ division_uid = EXCLUDED.division_uid,
+ name = EXCLUDED.name,
+ commission_ratio = EXCLUDED.commission_ratio,
+ is_enabled = EXCLUDED.is_enabled,
+ end_time = EXCLUDED.end_time,
+ updated_at = now(),
+ updated_by = auth.uid();
+
+ RETURN p_uid;
+END;
+$$;
+
+REVOKE ALL ON FUNCTION public.rpc_admin_save_agent(uuid, uuid, text, numeric, boolean, timestamptz) FROM PUBLIC;
+GRANT EXECUTE ON FUNCTION public.rpc_admin_save_agent(uuid, uuid, text, numeric, boolean, timestamptz) TO authenticated;
diff --git a/docs/sql/30_rpc/distribution/rpc_admin_save_division_v1.sql b/docs/sql/30_rpc/distribution/rpc_admin_save_division_v1.sql
new file mode 100644
index 00000000..d874dcbc
--- /dev/null
+++ b/docs/sql/30_rpc/distribution/rpc_admin_save_division_v1.sql
@@ -0,0 +1,47 @@
+-- RPC: rpc_admin_save_division
+-- 管理端新增或更新事业部
+
+CREATE OR REPLACE FUNCTION public.rpc_admin_save_division(
+ p_uid uuid,
+ p_name text,
+ p_invite_code text,
+ p_commission_ratio numeric,
+ p_is_enabled boolean DEFAULT true,
+ p_end_time timestamptz DEFAULT NULL
+)
+RETURNS uuid
+LANGUAGE plpgsql
+SECURITY DEFINER
+SET search_path = public
+AS $$
+BEGIN
+ -- 仅管理员可操作
+ IF NOT EXISTS (
+ SELECT 1 FROM public.ak_users u
+ WHERE u.id = auth.uid() AND u.role = 'admin'
+ ) THEN
+ RAISE EXCEPTION 'permission denied';
+ END IF;
+
+ INSERT INTO public.ak_distribution_divisions (
+ uid, name, invite_code, commission_ratio, is_enabled, end_time, updated_at, updated_by
+ )
+ VALUES (
+ p_uid, p_name, p_invite_code, p_commission_ratio, p_is_enabled, p_end_time, now(), auth.uid()
+ )
+ ON CONFLICT (uid) DO UPDATE
+ SET
+ name = EXCLUDED.name,
+ invite_code = EXCLUDED.invite_code,
+ commission_ratio = EXCLUDED.commission_ratio,
+ is_enabled = EXCLUDED.is_enabled,
+ end_time = EXCLUDED.end_time,
+ updated_at = now(),
+ updated_by = auth.uid();
+
+ RETURN p_uid;
+END;
+$$;
+
+REVOKE ALL ON FUNCTION public.rpc_admin_save_division(uuid, text, text, numeric, boolean, timestamptz) FROM PUBLIC;
+GRANT EXECUTE ON FUNCTION public.rpc_admin_save_division(uuid, text, text, numeric, boolean, timestamptz) TO authenticated;
diff --git a/pages/mall/admin/distribution/division/agent.uvue b/pages/mall/admin/distribution/division/agent.uvue
index 7ccaa7ac..6e59cb40 100644
--- a/pages/mall/admin/distribution/division/agent.uvue
+++ b/pages/mall/admin/distribution/division/agent.uvue
@@ -4,32 +4,36 @@
代理商查询:
-
+
+
+
-
- 加载中...
+ 数据加载中...
+
暂无代理商数据
@@ -37,18 +41,20 @@
{{ item.uid }}
-
+
- {{ item.name || item.nickname }}
- {{ item.division_name || '-' }}
- {{ formatDateTime(item.created_at) }}
+ {{ item.name }}
+ {{ item.commission_ratio }}%
+ {{ item.division_name || '-' }}
+ {{ item.staffCount }}
+ {{ item.end_time ? item.end_time.substring(0, 10) : '-' }}
-
+ onToggleStatus(item)" />
- 详情
+ 编辑
|
- 删除
+ 删除
@@ -60,36 +66,100 @@
第 {{ page }} 页
- 共 {{ total }} 条记录
+ 共 {{ agentList.length }} 条记录
+
+
+
+
+
-
+
\ No newline at end of file
+
+.pagination { padding: 16px 24px; border-top: 1px solid #f0f0f0; display: flex; flex-direction: row; align-items: center; justify-content: space-between; }
+.pager-btns { display: flex; flex-direction: row; align-items: center; gap: 12px; }
+.page-num { font-size: 14px; color: #333; }
+.page-info { font-size: 14px; color: #999; }
+
+.loading-mask { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(255,255,255,0.7); display: flex; align-items: center; justify-content: center; z-index: 10; }
+.loading-text { color: #1890ff; font-size: 14px; }
+.empty-row { padding: 40px 0; text-align: center; color: #999; font-size: 14px; }
+
+/* 弹窗样式 */
+.popup-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; z-index: 999; }
+.popup-card { width: 500px; background-color: #fff; border-radius: 8px; display: flex; flex-direction: column; overflow: hidden; }
+.popup-header { display: flex; flex-direction: row; justify-content: space-between; align-items: center; padding: 16px 20px; border-bottom: 1px solid #f0f0f0; }
+.popup-title { font-size: 16px; font-weight: bold; color: #333; }
+.popup-close { font-size: 20px; color: #999; cursor: pointer; padding: 4px; }
+.popup-body { padding: 24px; display: flex; flex-direction: column; gap: 16px; }
+.popup-item { display: flex; flex-direction: column; gap: 8px; }
+.popup-row { flex-direction: row; align-items: center; justify-content: space-between; }
+.popup-label { font-size: 14px; color: #666; }
+.popup-input { border: 1px solid #d9d9d9; border-radius: 4px; height: 36px; padding: 0 12px; font-size: 14px; width: 100%; }
+.select-box { border: 1px solid #d9d9d9; border-radius: 4px; height: 36px; padding: 0 12px; display: flex; flex-direction: row; align-items: center; justify-content: space-between; text { font-size: 14px; color: #333; } .arrow { font-size: 10px; color: #bfbfbf; } }
+.popup-footer { padding: 16px 24px; border-top: 1px solid #f0f0f0; display: flex; flex-direction: row; justify-content: flex-end; gap: 12px; }
+
diff --git a/pages/mall/admin/distribution/division/apply.uvue b/pages/mall/admin/distribution/division/apply.uvue
index f6bd0a2a..aa573b4b 100644
--- a/pages/mall/admin/distribution/division/apply.uvue
+++ b/pages/mall/admin/distribution/division/apply.uvue
@@ -4,23 +4,24 @@
搜索:
-
+
+
-
- {{ tab }}
+
+ {{ tab.label }}
- 加载中...
+ 数据加载中...
-
+
+
-
\ No newline at end of file
diff --git a/pages/mall/admin/distribution/division/list.uvue b/pages/mall/admin/distribution/division/list.uvue
index 65917580..1a06e82b 100644
--- a/pages/mall/admin/distribution/division/list.uvue
+++ b/pages/mall/admin/distribution/division/list.uvue
@@ -1,5 +1,6 @@
+
@@ -8,9 +9,12 @@
+
+
+
@@ -18,7 +22,7 @@
- 加载中...
+ 数据加载中...
- {{ item.uid }}
+ {{ item.uid }}
-
+
- {{ item.name || item.nickname }}
+ {{ item.name }}
{{ item.invite_code }}
- {{ item.ratio }}%
- {{ item.agent_count }}
- {{ formatTime(item.end_time) }}
+ {{ item.commission_ratio }}%
+ {{ item.agentCount }}
+ {{ item.end_time ? item.end_time.substring(0, 10) : '-' }}
-
+ onToggleStatus(item)" />
- 查看代理商
+ 编辑
|
- 删除
+ 删除
+
+
+
+
+
-
+
\ No newline at end of file
+
+.pagination { padding: 16px 24px; border-top: 1px solid #f0f0f0; display: flex; flex-direction: row; align-items: center; justify-content: space-between; }
+.pager-btns { display: flex; flex-direction: row; align-items: center; gap: 12px; }
+.page-num { font-size: 14px; color: #333; }
+.page-info { font-size: 14px; color: #999; }
+
+.loading-mask { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(255,255,255,0.7); display: flex; align-items: center; justify-content: center; z-index: 10; }
+.loading-text { color: #1890ff; font-size: 14px; }
+.empty-row { padding: 40px 0; text-align: center; color: #999; font-size: 14px; }
+
+/* 弹窗样式 */
+.popup-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; z-index: 999; }
+.popup-card { width: 500px; background-color: #fff; border-radius: 8px; display: flex; flex-direction: column; overflow: hidden; }
+.popup-header { display: flex; flex-direction: row; justify-content: space-between; align-items: center; padding: 16px 20px; border-bottom: 1px solid #f0f0f0; }
+.popup-title { font-size: 16px; font-weight: bold; color: #333; }
+.popup-close { font-size: 20px; color: #999; cursor: pointer; padding: 4px; }
+.popup-body { padding: 24px; display: flex; flex-direction: column; gap: 16px; }
+.popup-item { display: flex; flex-direction: column; gap: 8px; }
+.popup-row { flex-direction: row; align-items: center; justify-content: space-between; }
+.popup-label { font-size: 14px; color: #666; }
+.popup-input { border: 1px solid #d9d9d9; border-radius: 4px; height: 36px; padding: 0 12px; font-size: 14px; width: 100%; }
+.popup-footer { padding: 16px 24px; border-top: 1px solid #f0f0f0; display: flex; flex-direction: row; justify-content: flex-end; gap: 12px; }
+
diff --git a/pages/mall/admin/marketing/groupbuy/list.uvue b/pages/mall/admin/marketing/groupbuy/list.uvue
index 59cfee04..ab4f739a 100644
--- a/pages/mall/admin/marketing/groupbuy/list.uvue
+++ b/pages/mall/admin/marketing/groupbuy/list.uvue
@@ -1,27 +1,296 @@
-
-
-
-
diff --git a/pages/mall/admin/marketing/recharge/record.uvue b/pages/mall/admin/marketing/recharge/record.uvue
index 63bd8c6b..873b651d 100644
--- a/pages/mall/admin/marketing/recharge/record.uvue
+++ b/pages/mall/admin/marketing/recharge/record.uvue
@@ -1,27 +1,240 @@
-
-
-
-
diff --git a/services/admin/distributionService.uts b/services/admin/distributionService.uts
index 7e4906a3..ab0aa154 100644
--- a/services/admin/distributionService.uts
+++ b/services/admin/distributionService.uts
@@ -1,11 +1,10 @@
import supa from '@/components/supadb/aksupainstance.uts'
/**
- * 分销配置模型 (与 ak_distribution_config 表对齐)
+ * 分销配置模型
*/
export type DistributionConfig = {
id?: string
- // 分销模式
is_enabled: boolean
extract_type: string
bind_type: string
@@ -15,8 +14,6 @@ export type DistributionConfig = {
is_area_manager: boolean
is_agent_apply: boolean
is_commission_window: boolean
-
- // 返佣设置
is_self_brokerage: boolean
is_member_brokerage: boolean
brokerage_type: string
@@ -26,17 +23,13 @@ export type DistributionConfig = {
store_brokerage_ratio: number
store_brokerage_two_ratio: number
extract_frozen_time: number
-
- // 提现设置
user_extract_min_price: number
extract_bank_list: string
extract_type_list: string[]
wechat_extract_type: string
alipay_extract_type: string
user_extract_fee: number
-
updated_at?: string
- updated_by?: string
}
/**
@@ -58,6 +51,52 @@ export type Promoter = {
unwithdrawnAmount: number
}
+/**
+ * 事业部模型
+ */
+export type Division = {
+ uid: string
+ name: string
+ invite_code: string
+ commission_ratio: number
+ is_enabled: boolean
+ end_time: string | null
+ created_at: string
+ agentCount: number
+}
+
+/**
+ * 代理商模型
+ */
+export type Agent = {
+ uid: string
+ name: string
+ division_uid: string
+ division_name: string
+ commission_ratio: number
+ is_enabled: boolean
+ end_time: string | null
+ created_at: string
+ staffCount: number
+}
+
+/**
+ * 代理商申请模型
+ */
+export type AgentApply = {
+ id: string
+ uid: string
+ name: string
+ phone: string
+ dept_uid: string
+ dept_name: string
+ proof_images: string[] | null
+ status: string
+ refusal_reason: string | null
+ time: string
+ invite_code: string
+}
+
/**
* 获取分销全局配置
*/
@@ -89,11 +128,7 @@ export async function saveDistributionConfig(config: DistributionConfig): Promis
})
.execute()
- if (error != null) {
- console.error('保存分销配置失败:', error)
- return false
- }
- return true
+ return error == null
}
/**
@@ -108,8 +143,6 @@ export type DistributionLevel = {
task_total: number
task_finish: number
is_visible: boolean
- created_at?: string
- updated_at?: string
}
/**
@@ -122,30 +155,18 @@ export async function getDistributionLevelList(): Promise {
.order('level', { ascending: true })
.execute()
- if (error != null) {
- console.error('获取分销等级列表失败:', error)
- return [] as DistributionLevel[]
- }
- return data as DistributionLevel[]
+ return (data ?? []) as DistributionLevel[]
}
/**
- * 保存/更新分销等级
+ * 保存分销等级
*/
export async function saveDistributionLevel(level: DistributionLevel): Promise {
const { error } = await supa
.from('ak_distribution_level')
- .upsert({
- ...level,
- updated_at: new Date().toISOString()
- })
+ .upsert(level)
.execute()
-
- if (error != null) {
- console.error('保存分销等级失败:', error)
- return false
- }
- return true
+ return error == null
}
/**
@@ -157,16 +178,11 @@ export async function deleteDistributionLevel(id: string): Promise {
.delete()
.eq('id', id)
.execute()
-
- if (error != null) {
- console.error('删除分销等级失败:', error)
- return false
- }
- return true
+ return error == null
}
/**
- * 获取推广员列表
+ * 推广员列表参数
*/
export type PromoterListParams = {
search?: string | null
@@ -177,185 +193,111 @@ export type PromoterListParams = {
}
/**
- * 获取推广员列表(聚合统计)
+ * 获取推广员列表
*/
export async function getPromoterList(params?: PromoterListParams): Promise {
- const payload = {
+ const { data, error } = await supa.rpc('rpc_admin_get_promoter_list', {
p_search: params?.search ?? null,
p_page: params?.page ?? 1,
p_page_size: params?.pageSize ?? 20,
p_start_time: params?.startTime ?? null,
p_end_time: params?.endTime ?? null
- } as any
+ } as any)
- const { data, error } = await supa
- .rpc('rpc_admin_get_promoter_list', payload as any)
-
- if (error != null) {
- console.error('获取推广员列表失败:', error)
- return [] as Promoter[]
- }
return (data ?? []) as Promoter[]
}
-/**
- * 事业部模型
- */
-export type DistributionDivision = {
- id?: string
- merchant_id?: string
- uid: string
- name: string
- invite_code: string
- ratio: number
- agent_count: number
- end_time: string | null
- status: boolean
- created_at?: string
- updated_at?: string
- // 关联字段
- nickname?: string
- avatar_url?: string
-}
-
-/**
- * 代理商模型
- */
-export type DistributionAgent = {
- id?: string
- merchant_id?: string
- uid: string
- division_id: string
- name: string
- status: boolean
- created_at?: string
- updated_at?: string
- // 关联字段
- nickname?: string
- avatar_url?: string
- division_name?: string
-}
-
/**
* 获取事业部列表
*/
-export async function fetchDivisions(query?: { search?: string, page?: number, pageSize?: number }): Promise<{ total: number, items: DistributionDivision[] }> {
- let q = supa.from('ak_distribution_divisions').select('*, ak_users!uid(username, avatar_url)', { count: 'exact' })
-
- if (query?.search != null && query.search !== '') {
- q = q.or(`name.ilike.%${query.search}%,ak_users.username.ilike.%${query.search}%`)
- }
-
- const p = query?.page ?? 1
- const ps = query?.pageSize ?? 20
- const from = (p - 1) * ps
- const to = from + ps - 1
-
- const { data, error, count } = await q
- .order('created_at', { ascending: false })
- .range(from, to)
- .execute()
-
- if (error != null) {
- console.error('获取事业部列表失败:', error)
- return { total: 0, items: [] as DistributionDivision[] }
- }
-
- const items = (data ?? []).map((item: any): DistributionDivision => {
- return {
- ...item,
- nickname: item.ak_users?.username,
- avatar_url: item.ak_users?.avatar_url
- } as DistributionDivision
- })
-
- return { total: count ?? 0, items }
+export async function getDivisionList(search: string | null, page: number, pageSize: number): Promise {
+ const { data, error } = await supa.rpc('rpc_admin_get_division_list', {
+ p_search: search,
+ p_page: page,
+ p_page_size: pageSize
+ } as any)
+ return (data ?? []) as Division[]
}
/**
- * 保存事业部(新增/更新)
+ * 保存事业部
*/
-export async function saveDivision(division: DistributionDivision): Promise {
- const session = supa.getSession()
- const mid = session?.user?.getString('id')
- if (mid == null) return false
-
- const { error } = await supa
- .from('ak_distribution_divisions')
- .upsert({
- ...division,
- merchant_id: mid,
- updated_at: new Date().toISOString()
- })
- .execute()
-
+export async function saveDivision(division: any): Promise {
+ const { error } = await supa.rpc('rpc_admin_save_division', {
+ p_uid: division.uid,
+ p_name: division.name,
+ p_invite_code: division.invite_code,
+ p_commission_ratio: division.commission_ratio,
+ p_is_enabled: division.is_enabled,
+ p_end_time: division.end_time
+ } as any)
return error == null
}
/**
* 删除事业部
*/
-export async function deleteDivision(id: string): Promise {
- const { error } = await supa.from('ak_distribution_divisions').delete().eq('id', id).execute()
- return error == null
+export async function deleteDivision(uid: string): Promise {
+ const { data, error } = await supa.rpc('rpc_admin_delete_division', { p_uid: uid } as any)
+ return error == null && data === true
}
/**
* 获取代理商列表
*/
-export async function fetchAgents(query?: { search?: string, divisionId?: string, page?: number, pageSize?: number }): Promise<{ total: number, items: DistributionAgent[] }> {
- let q = supa.from('ak_distribution_agents').select('*, ak_users!uid(username, avatar_url), ak_distribution_divisions!division_id(name)', { count: 'exact' })
-
- if (query?.search != null && query.search !== '') {
- q = q.or(`name.ilike.%${query.search}%,ak_users.username.ilike.%${query.search}%`)
- }
- if (query?.divisionId != null) {
- q = q.eq('division_id', query.divisionId)
- }
-
- const p = query?.page ?? 1
- const ps = query?.pageSize ?? 20
- const from = (p - 1) * ps
- const to = from + ps - 1
-
- const { data, error, count } = await q
- .order('created_at', { ascending: false })
- .range(from, to)
- .execute()
-
- if (error != null) {
- console.error('获取代理商列表失败:', error)
- return { total: 0, items: [] as DistributionAgent[] }
- }
-
- const items = (data ?? []).map((item: any): DistributionAgent => {
- return {
- ...item,
- nickname: item.ak_users?.username,
- avatar_url: item.ak_users?.avatar_url,
- division_name: item.ak_distribution_divisions?.name
- } as DistributionAgent
- })
-
- return { total: count ?? 0, items }
+export async function getAgentList(search: string | null, page: number, pageSize: number): Promise {
+ const { data, error } = await supa.rpc('rpc_admin_get_agent_list', {
+ p_search: search,
+ p_page: page,
+ p_page_size: pageSize
+ } as any)
+ return (data ?? []) as Agent[]
}
/**
* 保存代理商
*/
-export async function saveAgent(agent: DistributionAgent): Promise {
- const session = supa.getSession()
- const mid = session?.user?.getString('id')
- if (mid == null) return false
-
- const { error } = await supa
- .from('ak_distribution_agents')
- .upsert({
- ...agent,
- merchant_id: mid,
- updated_at: new Date().toISOString()
- })
- .execute()
-
+export async function saveAgent(agent: any): Promise {
+ const { error } = await supa.rpc('rpc_admin_save_agent', {
+ p_uid: agent.uid,
+ p_division_uid: agent.division_uid,
+ p_name: agent.name,
+ p_commission_ratio: agent.commission_ratio,
+ p_is_enabled: agent.is_enabled,
+ p_end_time: agent.end_time
+ } as any)
return error == null
}
+
+/**
+ * 删除代理商
+ */
+export async function deleteAgent(uid: string): Promise {
+ const { data, error } = await supa.rpc('rpc_admin_delete_agent', { p_uid: uid } as any)
+ return error == null && data === true
+}
+
+/**
+ * 获取代理商申请列表
+ */
+export async function getAgentApplyList(status: string, search: string | null, page: number, pageSize: number): Promise {
+ const { data, error } = await supa.rpc('rpc_admin_get_agent_apply_list', {
+ p_status: status,
+ p_search: search,
+ p_page: page,
+ p_page_size: pageSize
+ } as any)
+ return (data ?? []) as AgentApply[]
+}
+
+/**
+ * 审核代理商申请
+ */
+export async function processAgentApply(id: string, status: string, reason: string | null): Promise {
+ const { data, error } = await supa.rpc('rpc_admin_process_agent_apply', {
+ p_id: id,
+ p_status: status,
+ p_refusal_reason: reason
+ } as any)
+ return error == null && data === true
+}