feat(admin): implement user level, group and label modules with database, rpc and ui

This commit is contained in:
comlibmb
2026-02-10 20:34:45 +08:00
parent 80e5a1ddeb
commit 47968565a5
28 changed files with 1896 additions and 140 deletions

View File

@@ -0,0 +1,36 @@
-- =====================================================================================
-- RPC: rpc_admin_user_group_delete
-- 位置docs/sql/30_rpc/user/
-- 对象类型RPC 函数 (SECURITY DEFINER)
-- 版本v1
-- 说明:逻辑删除用户分组(设置 deleted_at
-- =====================================================================================
CREATE OR REPLACE FUNCTION public.rpc_admin_user_group_delete(
p_id UUID
)
RETURNS BOOLEAN
SECURITY DEFINER
SET search_path = public
LANGUAGE plpgsql
AS $$
DECLARE
v_ok BOOLEAN;
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. 逻辑删除
UPDATE public.ak_user_groups
SET deleted_at = now(), updated_at = now()
WHERE id = p_id AND deleted_at IS NULL;
GET DIAGNOSTICS v_ok = ROW_COUNT;
RETURN v_ok;
END;
$$;

View File

@@ -0,0 +1,60 @@
-- =====================================================================================
-- RPC: rpc_admin_user_group_list
-- 位置docs/sql/30_rpc/user/
-- 对象类型RPC 函数 (SECURITY DEFINER)
-- 版本v1
-- 说明:管理端分页获取用户分组列表,支持搜索、状态筛选及逻辑删除过滤
-- =====================================================================================
CREATE OR REPLACE FUNCTION public.rpc_admin_user_group_list(
p_page INT,
p_page_size INT,
p_search TEXT DEFAULT NULL,
p_status INT DEFAULT NULL,
p_include_deleted BOOLEAN DEFAULT FALSE
)
RETURNS JSONB
SECURITY DEFINER
SET search_path = public
LANGUAGE plpgsql
AS $$
DECLARE
v_total INT;
v_items JSONB;
BEGIN
-- 1. 权限检查
IF NOT EXISTS (
SELECT 1 FROM public.ak_users
WHERE auth_id = auth.uid() AND role IN ('admin', 'analytics')
) THEN
RAISE EXCEPTION 'Permission denied';
END IF;
-- 2. 获取总数
SELECT COUNT(*) INTO v_total
FROM public.ak_user_groups
WHERE (p_include_deleted OR deleted_at IS NULL)
AND (p_search IS NULL OR name ILIKE '%' || p_search || '%' OR remark ILIKE '%' || p_search || '%')
AND (p_status IS NULL OR status = p_status);
-- 3. 分页获取数据
SELECT jsonb_agg(t) INTO v_items
FROM (
SELECT
id, name, remark, status,
created_at, updated_at, deleted_at
FROM public.ak_user_groups
WHERE (p_include_deleted OR deleted_at IS NULL)
AND (p_search IS NULL OR name ILIKE '%' || p_search || '%' OR remark ILIKE '%' || p_search || '%')
AND (p_status IS NULL OR status = p_status)
ORDER BY created_at DESC
LIMIT p_page_size
OFFSET (p_page - 1) * p_page_size
) t;
RETURN jsonb_build_object(
'total', v_total,
'items', COALESCE(v_items, '[]'::jsonb)
);
END;
$$;

View File

@@ -0,0 +1,66 @@
-- =====================================================================================
-- RPC: rpc_admin_user_group_save
-- 位置docs/sql/30_rpc/user/
-- 对象类型RPC 函数 (SECURITY DEFINER)
-- 版本v1
-- 说明:新增/更新用户分组(逻辑删除记录默认不允许更新)
-- =====================================================================================
CREATE OR REPLACE FUNCTION public.rpc_admin_user_group_save(
p_id UUID DEFAULT NULL,
p_name TEXT,
p_remark TEXT DEFAULT NULL,
p_status INT DEFAULT 1
)
RETURNS UUID
SECURITY DEFINER
SET search_path = public
LANGUAGE plpgsql
AS $$
DECLARE
v_id UUID;
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. 参数校验
IF p_name IS NULL OR length(trim(p_name)) = 0 THEN
RAISE EXCEPTION 'Invalid name';
END IF;
-- 3. 新增
IF p_id IS NULL THEN
INSERT INTO public.ak_user_groups(
name, remark, status,
created_at, updated_at, deleted_at
) VALUES (
p_name, p_remark, COALESCE(p_status, 1),
now(), now(), NULL
)
RETURNING id INTO v_id;
RETURN v_id;
END IF;
-- 4. 更新(不允许更新已删除记录)
UPDATE public.ak_user_groups
SET
name = p_name,
remark = p_remark,
status = COALESCE(p_status, status),
updated_at = now()
WHERE id = p_id AND deleted_at IS NULL
RETURNING id INTO v_id;
IF v_id IS NULL THEN
RAISE EXCEPTION 'Not found or deleted';
END IF;
RETURN v_id;
END;
$$;

View File

@@ -0,0 +1,37 @@
-- =====================================================================================
-- RPC: rpc_admin_user_group_set_status
-- 位置docs/sql/30_rpc/user/
-- 对象类型RPC 函数 (SECURITY DEFINER)
-- 版本v1
-- 说明:设置用户分组状态(启用/禁用)
-- =====================================================================================
CREATE OR REPLACE FUNCTION public.rpc_admin_user_group_set_status(
p_id UUID,
p_status INT
)
RETURNS BOOLEAN
SECURITY DEFINER
SET search_path = public
LANGUAGE plpgsql
AS $$
DECLARE
v_ok BOOLEAN;
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;
UPDATE public.ak_user_groups
SET status = p_status,
updated_at = now()
WHERE id = p_id AND deleted_at IS NULL;
GET DIAGNOSTICS v_ok = ROW_COUNT;
RETURN v_ok;
END;
$$;

View File

@@ -0,0 +1,36 @@
-- =====================================================================================
-- RPC: rpc_admin_user_label_delete
-- 位置docs/sql/30_rpc/user/
-- 对象类型RPC 函数 (SECURITY DEFINER)
-- 版本v1
-- 说明:逻辑删除用户标签(设置 deleted_at
-- =====================================================================================
CREATE OR REPLACE FUNCTION public.rpc_admin_user_label_delete(
p_id UUID
)
RETURNS BOOLEAN
SECURITY DEFINER
SET search_path = public
LANGUAGE plpgsql
AS $$
DECLARE
v_ok BOOLEAN;
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. 逻辑删除
UPDATE public.ak_user_labels
SET deleted_at = now(), updated_at = now()
WHERE id = p_id AND deleted_at IS NULL;
GET DIAGNOSTICS v_ok = ROW_COUNT;
RETURN v_ok;
END;
$$;

View File

@@ -0,0 +1,60 @@
-- =====================================================================================
-- RPC: rpc_admin_user_label_list
-- 位置docs/sql/30_rpc/user/
-- 对象类型RPC 函数 (SECURITY DEFINER)
-- 版本v1
-- 说明:管理端分页获取用户标签列表,支持搜索、状态筛选及逻辑删除过滤
-- =====================================================================================
CREATE OR REPLACE FUNCTION public.rpc_admin_user_label_list(
p_page INT,
p_page_size INT,
p_search TEXT DEFAULT NULL,
p_status INT DEFAULT NULL,
p_include_deleted BOOLEAN DEFAULT FALSE
)
RETURNS JSONB
SECURITY DEFINER
SET search_path = public
LANGUAGE plpgsql
AS $$
DECLARE
v_total INT;
v_items JSONB;
BEGIN
-- 1. 权限检查
IF NOT EXISTS (
SELECT 1 FROM public.ak_users
WHERE auth_id = auth.uid() AND role IN ('admin', 'analytics')
) THEN
RAISE EXCEPTION 'Permission denied';
END IF;
-- 2. 获取总数
SELECT COUNT(*) INTO v_total
FROM public.ak_user_labels
WHERE (p_include_deleted OR deleted_at IS NULL)
AND (p_search IS NULL OR name ILIKE '%' || p_search || '%' OR remark ILIKE '%' || p_search || '%')
AND (p_status IS NULL OR status = p_status);
-- 3. 分页获取数据
SELECT jsonb_agg(t) INTO v_items
FROM (
SELECT
id, name, color, remark, status,
created_at, updated_at, deleted_at
FROM public.ak_user_labels
WHERE (p_include_deleted OR deleted_at IS NULL)
AND (p_search IS NULL OR name ILIKE '%' || p_search || '%' OR remark ILIKE '%' || p_search || '%')
AND (p_status IS NULL OR status = p_status)
ORDER BY created_at DESC
LIMIT p_page_size
OFFSET (p_page - 1) * p_page_size
) t;
RETURN jsonb_build_object(
'total', v_total,
'items', COALESCE(v_items, '[]'::jsonb)
);
END;
$$;

View File

@@ -0,0 +1,68 @@
-- =====================================================================================
-- RPC: rpc_admin_user_label_save
-- 位置docs/sql/30_rpc/user/
-- 对象类型RPC 函数 (SECURITY DEFINER)
-- 版本v1
-- 说明:新增/更新用户标签(逻辑删除记录默认不允许更新)
-- =====================================================================================
CREATE OR REPLACE FUNCTION public.rpc_admin_user_label_save(
p_id UUID DEFAULT NULL,
p_name TEXT,
p_color TEXT DEFAULT NULL,
p_remark TEXT DEFAULT NULL,
p_status INT DEFAULT 1
)
RETURNS UUID
SECURITY DEFINER
SET search_path = public
LANGUAGE plpgsql
AS $$
DECLARE
v_id UUID;
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. 参数校验
IF p_name IS NULL OR length(trim(p_name)) = 0 THEN
RAISE EXCEPTION 'Invalid name';
END IF;
-- 3. 新增
IF p_id IS NULL THEN
INSERT INTO public.ak_user_labels(
name, color, remark, status,
created_at, updated_at, deleted_at
) VALUES (
p_name, p_color, p_remark, COALESCE(p_status, 1),
now(), now(), NULL
)
RETURNING id INTO v_id;
RETURN v_id;
END IF;
-- 4. 更新(不允许更新已删除记录)
UPDATE public.ak_user_labels
SET
name = p_name,
color = p_color,
remark = p_remark,
status = COALESCE(p_status, status),
updated_at = now()
WHERE id = p_id AND deleted_at IS NULL
RETURNING id INTO v_id;
IF v_id IS NULL THEN
RAISE EXCEPTION 'Not found or deleted';
END IF;
RETURN v_id;
END;
$$;

View File

@@ -0,0 +1,37 @@
-- =====================================================================================
-- RPC: rpc_admin_user_label_set_status
-- 位置docs/sql/30_rpc/user/
-- 对象类型RPC 函数 (SECURITY DEFINER)
-- 版本v1
-- 说明:设置用户标签状态(启用/禁用)
-- =====================================================================================
CREATE OR REPLACE FUNCTION public.rpc_admin_user_label_set_status(
p_id UUID,
p_status INT
)
RETURNS BOOLEAN
SECURITY DEFINER
SET search_path = public
LANGUAGE plpgsql
AS $$
DECLARE
v_ok BOOLEAN;
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;
UPDATE public.ak_user_labels
SET status = p_status,
updated_at = now()
WHERE id = p_id AND deleted_at IS NULL;
GET DIAGNOSTICS v_ok = ROW_COUNT;
RETURN v_ok;
END;
$$;

View File

@@ -0,0 +1,36 @@
-- =====================================================================================
-- RPC: rpc_admin_user_level_delete
-- 位置docs/sql/30_rpc/user/
-- 对象类型RPC 函数 (SECURITY DEFINER)
-- 版本v1
-- 说明:逻辑删除用户等级(设置 deleted_at
-- =====================================================================================
CREATE OR REPLACE FUNCTION public.rpc_admin_user_level_delete(
p_id UUID
)
RETURNS BOOLEAN
SECURITY DEFINER
SET search_path = public
LANGUAGE plpgsql
AS $$
DECLARE
v_ok BOOLEAN;
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. 逻辑删除
UPDATE public.ak_user_levels
SET deleted_at = now(), updated_at = now()
WHERE id = p_id AND deleted_at IS NULL;
GET DIAGNOSTICS v_ok = ROW_COUNT;
RETURN v_ok;
END;
$$;

View File

@@ -0,0 +1,64 @@
-- =====================================================================================
-- RPC: rpc_admin_user_level_list
-- 位置docs/sql/30_rpc/user/
-- 对象类型RPC 函数 (SECURITY DEFINER)
-- 版本v1
-- 说明:管理端分页获取用户等级列表,支持搜索、状态筛选及逻辑删除过滤
-- =====================================================================================
CREATE OR REPLACE FUNCTION public.rpc_admin_user_level_list(
p_page INT,
p_page_size INT,
p_search TEXT DEFAULT NULL,
p_status INT DEFAULT NULL,
p_is_visible BOOLEAN DEFAULT NULL,
p_include_deleted BOOLEAN DEFAULT FALSE
)
RETURNS JSONB
SECURITY DEFINER
SET search_path = public
LANGUAGE plpgsql
AS $$
DECLARE
v_total INT;
v_items JSONB;
BEGIN
-- 1. 权限检查 (依赖 public.get_current_user_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;
-- 2. 获取总数
SELECT COUNT(*) INTO v_total
FROM public.ak_user_levels
WHERE (p_include_deleted OR deleted_at IS NULL)
AND (p_search IS NULL OR name ILIKE '%' || p_search || '%')
AND (p_status IS NULL OR status = p_status)
AND (p_is_visible IS NULL OR is_visible = p_is_visible);
-- 3. 分页获取数据
SELECT jsonb_agg(t) INTO v_items
FROM (
SELECT
id, name, level_weight, min_experience, discount_percent,
is_visible, status, icon_url, bg_image_url, bg_style_json,
remark, created_at, updated_at, deleted_at
FROM public.ak_user_levels
WHERE (p_include_deleted OR deleted_at IS NULL)
AND (p_search IS NULL OR name ILIKE '%' || p_search || '%')
AND (p_status IS NULL OR status = p_status)
AND (p_is_visible IS NULL OR is_visible = p_is_visible)
ORDER BY level_weight ASC
LIMIT p_page_size
OFFSET (p_page - 1) * p_page_size
) t;
RETURN jsonb_build_object(
'total', v_total,
'items', COALESCE(v_items, '[]'::jsonb)
);
END;
$$;

View File

@@ -0,0 +1,94 @@
-- =====================================================================================
-- RPC: rpc_admin_user_level_save
-- 位置docs/sql/30_rpc/user/
-- 对象类型RPC 函数 (SECURITY DEFINER)
-- 版本v1
-- 说明:新增/更新用户等级(逻辑删除记录默认不允许更新)
-- =====================================================================================
CREATE OR REPLACE FUNCTION public.rpc_admin_user_level_save(
p_id UUID DEFAULT NULL,
p_name TEXT,
p_level_weight INT,
p_min_experience INT,
p_discount_percent INT,
p_is_visible BOOLEAN,
p_status INT,
p_icon_url TEXT DEFAULT NULL,
p_bg_image_url TEXT DEFAULT NULL,
p_bg_style_json JSONB DEFAULT NULL,
p_remark TEXT DEFAULT NULL
)
RETURNS UUID
SECURITY DEFINER
SET search_path = public
LANGUAGE plpgsql
AS $$
DECLARE
v_id UUID;
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. 参数校验(最小化)
IF p_name IS NULL OR length(trim(p_name)) = 0 THEN
RAISE EXCEPTION 'Invalid name';
END IF;
IF p_level_weight < 0 OR p_min_experience < 0 THEN
RAISE EXCEPTION 'Invalid level_weight or min_experience';
END IF;
IF p_discount_percent < 1 OR p_discount_percent > 100 THEN
RAISE EXCEPTION 'Invalid discount_percent';
END IF;
-- 3. 新增
IF p_id IS NULL THEN
INSERT INTO public.ak_user_levels(
name, level_weight, min_experience, discount_percent,
is_visible, status,
icon_url, bg_image_url, bg_style_json,
remark,
created_at, updated_at, deleted_at
) VALUES (
p_name, p_level_weight, p_min_experience, p_discount_percent,
p_is_visible, p_status,
p_icon_url, p_bg_image_url, p_bg_style_json,
p_remark,
now(), now(), NULL
)
RETURNING id INTO v_id;
RETURN v_id;
END IF;
-- 4. 更新(不允许更新已删除记录)
UPDATE public.ak_user_levels
SET
name = p_name,
level_weight = p_level_weight,
min_experience = p_min_experience,
discount_percent = p_discount_percent,
is_visible = p_is_visible,
status = p_status,
icon_url = p_icon_url,
bg_image_url = p_bg_image_url,
bg_style_json = p_bg_style_json,
remark = p_remark,
updated_at = now()
WHERE id = p_id AND deleted_at IS NULL
RETURNING id INTO v_id;
IF v_id IS NULL THEN
RAISE EXCEPTION 'Not found or deleted';
END IF;
RETURN v_id;
END;
$$;

View File

@@ -0,0 +1,37 @@
-- =====================================================================================
-- RPC: rpc_admin_user_level_set_status
-- 位置docs/sql/30_rpc/user/
-- 对象类型RPC 函数 (SECURITY DEFINER)
-- 版本v1
-- 说明:设置用户等级状态(启用/禁用)
-- =====================================================================================
CREATE OR REPLACE FUNCTION public.rpc_admin_user_level_set_status(
p_id UUID,
p_status INT
)
RETURNS BOOLEAN
SECURITY DEFINER
SET search_path = public
LANGUAGE plpgsql
AS $$
DECLARE
v_ok BOOLEAN;
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;
UPDATE public.ak_user_levels
SET status = p_status,
updated_at = now()
WHERE id = p_id AND deleted_at IS NULL;
GET DIAGNOSTICS v_ok = ROW_COUNT;
RETURN v_ok;
END;
$$;

View File

@@ -0,0 +1,37 @@
-- =====================================================================================
-- RPC: rpc_admin_user_level_set_visible
-- 位置docs/sql/30_rpc/user/
-- 对象类型RPC 函数 (SECURITY DEFINER)
-- 版本v1
-- 说明:设置用户等级是否展示
-- =====================================================================================
CREATE OR REPLACE FUNCTION public.rpc_admin_user_level_set_visible(
p_id UUID,
p_is_visible BOOLEAN
)
RETURNS BOOLEAN
SECURITY DEFINER
SET search_path = public
LANGUAGE plpgsql
AS $$
DECLARE
v_ok BOOLEAN;
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;
UPDATE public.ak_user_levels
SET is_visible = p_is_visible,
updated_at = now()
WHERE id = p_id AND deleted_at IS NULL;
GET DIAGNOSTICS v_ok = ROW_COUNT;
RETURN v_ok;
END;
$$;