From 2262d1bfd9c175db02977a179c2231c5b41d04c0 Mon Sep 17 00:00:00 2001 From: cyh666666 Date: Mon, 9 Mar 2026 17:20:59 +0800 Subject: [PATCH] =?UTF-8?q?consumer=E6=A8=A1=E5=9D=97=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E5=BA=A695%=EF=BC=8C=E6=A3=80=E6=9F=A5=E6=B6=88=E8=B4=B9?= =?UTF-8?q?=E8=80=85=E5=89=8D=E7=AB=AFbug=E5=B9=B6=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../consumer/sql/promotion_system_tables.sql | 402 +- .../sql/promotion_system_tables_v2.sql | 453 ++ .../consumer/sql/share_free_notifications.sql | 143 + pages.json | 6 + pages/main/cart.uvue | 5 +- pages/main/messages.uvue | 2 +- pages/main/profile.uvue | 29 +- pages/mall/consumer/address-edit.uvue | 2 - pages/mall/consumer/address-list.uvue | 1 - pages/mall/consumer/balance/index.uvue | 22 +- pages/mall/consumer/checkout.uvue | 10 +- pages/mall/consumer/doc/UTS_ANDROID_GUIDE.md | 262 +- .../mall/consumer/doc/推销模式功能需求文档.md | 425 +- pages/mall/consumer/member/index.uvue | 69 +- pages/mall/consumer/message-detail.uvue | 265 ++ pages/mall/consumer/my-reviews.uvue | 4 +- pages/mall/consumer/order-detail.uvue | 21 +- pages/mall/consumer/orders.uvue | 154 +- pages/mall/consumer/payment.uvue | 2 +- .../consumer/points/exchange-records.uvue | 58 +- pages/mall/consumer/points/exchange.uvue | 50 +- pages/mall/consumer/points/index.uvue | 6 +- pages/mall/consumer/points/signin.uvue | 4 +- pages/mall/consumer/product-reviews.uvue | 8 +- pages/mall/consumer/review.uvue | 8 +- pages/mall/consumer/share/detail.uvue | 63 +- pages/mall/consumer/share/index.uvue | 39 +- .../consumer/subscription/plan-detail.uvue | 11 +- .../mall/consumer/subscription/plan-list.uvue | 11 +- pages/mall/consumer/wallet.uvue | 2 +- pages/mall/consumer/withdraw.uvue | 2 +- uni_modules/ak-req/ak-req.uts | 62 +- .../cache/.app-android/dex/index/classes.dex | Bin 1022360 -> 0 bytes .../mall/consumer/address-edit/classes.dex | Bin 53136 -> 0 bytes .../pages/mall/consumer/checkout/classes.dex | Bin 142936 -> 0 bytes .../pages/mall/consumer/coupons/classes.dex | Bin 22100 -> 0 bytes .../pages/mall/consumer/favorites/classes.dex | Bin 51524 -> 0 bytes .../pages/mall/consumer/footprint/classes.dex | Bin 60104 -> 0 bytes .../mall/consumer/order-detail/classes.dex | Bin 85152 -> 0 bytes .../pages/mall/consumer/orders/classes.dex | Bin 115848 -> 0 bytes .../mall/consumer/payment-success/classes.dex | Bin 23048 -> 0 bytes .../pages/mall/consumer/payment/classes.dex | Bin 64744 -> 0 bytes .../mall/consumer/points/index/classes.dex | Bin 27360 -> 0 bytes .../mall/consumer/product-detail/classes.dex | Bin 118296 -> 0 bytes .../consumer/red-packets/index/classes.dex | Bin 29132 -> 0 bytes .../pages/mall/consumer/review/classes.dex | Bin 68556 -> 0 bytes .../pages/mall/consumer/search/classes.dex | Bin 99104 -> 0 bytes .../pages/mall/consumer/settings/classes.dex | Bin 52920 -> 0 bytes .../subscription/followed-shops/classes.dex | Bin 30312 -> 0 bytes .../pages/mall/consumer/wallet/classes.dex | Bin 62956 -> 0 bytes .../pages/mall/consumer/withdraw/classes.dex | Bin 36024 -> 0 bytes .../dex/pages/user/boot/classes.dex | Bin 17724 -> 0 bytes .../dex/pages/user/login/classes.dex | Bin 50492 -> 0 bytes .../cache/.app-android/sourcemap/index.kt.map | 2 +- .../sourcemap/pages/main/cart.kt.map | 1 + .../sourcemap/pages/main/category.kt.map | 1 + .../sourcemap/pages/main/index.kt.map | 1 + .../sourcemap/pages/main/messages.kt.map | 1 + .../sourcemap/pages/main/profile.kt.map | 1 + .../pages/mall/consumer/address-edit.kt.map | 2 +- .../pages/mall/consumer/balance/index.kt.map | 1 + .../pages/mall/consumer/checkout.kt.map | 2 +- .../pages/mall/consumer/coupons.kt.map | 2 +- .../pages/mall/consumer/favorites.kt.map | 2 +- .../pages/mall/consumer/footprint.kt.map | 2 +- .../pages/mall/consumer/member/index.kt.map | 1 + .../pages/mall/consumer/message-detail.kt.map | 1 + .../pages/mall/consumer/my-reviews.kt.map | 1 + .../pages/mall/consumer/order-detail.kt.map | 2 +- .../pages/mall/consumer/orders.kt.map | 2 +- .../mall/consumer/payment-success.kt.map | 2 +- .../pages/mall/consumer/payment.kt.map | 2 +- .../consumer/points/exchange-records.kt.map | 1 + .../mall/consumer/points/exchange.kt.map | 1 + .../pages/mall/consumer/points/index.kt.map | 2 +- .../pages/mall/consumer/points/signin.kt.map | 1 + .../pages/mall/consumer/product-detail.kt.map | 2 +- .../mall/consumer/product-reviews.kt.map | 1 + .../mall/consumer/red-packets/index.kt.map | 2 +- .../pages/mall/consumer/review.kt.map | 2 +- .../pages/mall/consumer/search.kt.map | 2 +- .../pages/mall/consumer/settings.kt.map | 2 +- .../pages/mall/consumer/share/detail.kt.map | 1 + .../pages/mall/consumer/share/index.kt.map | 1 + .../subscription/followed-shops.kt.map | 2 +- .../pages/mall/consumer/wallet.kt.map | 2 +- .../pages/mall/consumer/withdraw.kt.map | 2 +- .../sourcemap/pages/user/boot.kt.map | 2 +- .../sourcemap/pages/user/login.kt.map | 2 +- .../cache/.app-android/src/.manifest.json | 181 +- unpackage/cache/.app-android/src/index.kt | 4138 +++++++++++++++-- .../cache/.app-android/src/pages/main/cart.kt | 785 ++++ .../.app-android/src/pages/main/category.kt | 698 +++ .../.app-android/src/pages/main/index.kt | 858 ++++ .../.app-android/src/pages/main/messages.kt | 559 +++ .../.app-android/src/pages/main/profile.kt | 1341 ++++++ .../src/pages/mall/consumer/address-edit.kt | 2 +- .../src/pages/mall/consumer/balance/index.kt | 224 + .../src/pages/mall/consumer/checkout.kt | 4 +- .../src/pages/mall/consumer/coupons.kt | 2 +- .../src/pages/mall/consumer/favorites.kt | 2 +- .../src/pages/mall/consumer/footprint.kt | 2 +- .../src/pages/mall/consumer/member/index.kt | 319 ++ .../src/pages/mall/consumer/message-detail.kt | 190 + .../src/pages/mall/consumer/my-reviews.kt | 436 ++ .../src/pages/mall/consumer/order-detail.kt | 109 +- .../src/pages/mall/consumer/orders.kt | 155 +- .../pages/mall/consumer/payment-success.kt | 2 +- .../src/pages/mall/consumer/payment.kt | 2 +- .../mall/consumer/points/exchange-records.kt | 213 + .../pages/mall/consumer/points/exchange.kt | 413 ++ .../src/pages/mall/consumer/points/index.kt | 185 +- .../src/pages/mall/consumer/points/signin.kt | 307 ++ .../src/pages/mall/consumer/product-detail.kt | 103 +- .../pages/mall/consumer/product-reviews.kt | 443 ++ .../pages/mall/consumer/red-packets/index.kt | 2 +- .../src/pages/mall/consumer/review.kt | 2 +- .../src/pages/mall/consumer/search.kt | 2 +- .../src/pages/mall/consumer/settings.kt | 2 +- .../src/pages/mall/consumer/share/detail.kt | 296 ++ .../src/pages/mall/consumer/share/index.kt | 251 + .../consumer/subscription/followed-shops.kt | 2 +- .../src/pages/mall/consumer/wallet.kt | 2 +- .../src/pages/mall/consumer/withdraw.kt | 2 +- .../cache/.app-android/src/pages/user/boot.kt | 2 +- .../.app-android/src/pages/user/login.kt | 10 +- .../.app-android/tsc/app-android/.tsbuildInfo | 2 +- utils/supabaseService.uts | 249 +- 128 files changed, 13485 insertions(+), 1670 deletions(-) create mode 100644 doc_mall/consumer/sql/promotion_system_tables_v2.sql create mode 100644 doc_mall/consumer/sql/share_free_notifications.sql create mode 100644 pages/mall/consumer/message-detail.uvue delete mode 100644 unpackage/cache/.app-android/dex/index/classes.dex delete mode 100644 unpackage/cache/.app-android/dex/pages/mall/consumer/address-edit/classes.dex delete mode 100644 unpackage/cache/.app-android/dex/pages/mall/consumer/checkout/classes.dex delete mode 100644 unpackage/cache/.app-android/dex/pages/mall/consumer/coupons/classes.dex delete mode 100644 unpackage/cache/.app-android/dex/pages/mall/consumer/favorites/classes.dex delete mode 100644 unpackage/cache/.app-android/dex/pages/mall/consumer/footprint/classes.dex delete mode 100644 unpackage/cache/.app-android/dex/pages/mall/consumer/order-detail/classes.dex delete mode 100644 unpackage/cache/.app-android/dex/pages/mall/consumer/orders/classes.dex delete mode 100644 unpackage/cache/.app-android/dex/pages/mall/consumer/payment-success/classes.dex delete mode 100644 unpackage/cache/.app-android/dex/pages/mall/consumer/payment/classes.dex delete mode 100644 unpackage/cache/.app-android/dex/pages/mall/consumer/points/index/classes.dex delete mode 100644 unpackage/cache/.app-android/dex/pages/mall/consumer/product-detail/classes.dex delete mode 100644 unpackage/cache/.app-android/dex/pages/mall/consumer/red-packets/index/classes.dex delete mode 100644 unpackage/cache/.app-android/dex/pages/mall/consumer/review/classes.dex delete mode 100644 unpackage/cache/.app-android/dex/pages/mall/consumer/search/classes.dex delete mode 100644 unpackage/cache/.app-android/dex/pages/mall/consumer/settings/classes.dex delete mode 100644 unpackage/cache/.app-android/dex/pages/mall/consumer/subscription/followed-shops/classes.dex delete mode 100644 unpackage/cache/.app-android/dex/pages/mall/consumer/wallet/classes.dex delete mode 100644 unpackage/cache/.app-android/dex/pages/mall/consumer/withdraw/classes.dex delete mode 100644 unpackage/cache/.app-android/dex/pages/user/boot/classes.dex delete mode 100644 unpackage/cache/.app-android/dex/pages/user/login/classes.dex create mode 100644 unpackage/cache/.app-android/sourcemap/pages/main/cart.kt.map create mode 100644 unpackage/cache/.app-android/sourcemap/pages/main/category.kt.map create mode 100644 unpackage/cache/.app-android/sourcemap/pages/main/index.kt.map create mode 100644 unpackage/cache/.app-android/sourcemap/pages/main/messages.kt.map create mode 100644 unpackage/cache/.app-android/sourcemap/pages/main/profile.kt.map create mode 100644 unpackage/cache/.app-android/sourcemap/pages/mall/consumer/balance/index.kt.map create mode 100644 unpackage/cache/.app-android/sourcemap/pages/mall/consumer/member/index.kt.map create mode 100644 unpackage/cache/.app-android/sourcemap/pages/mall/consumer/message-detail.kt.map create mode 100644 unpackage/cache/.app-android/sourcemap/pages/mall/consumer/my-reviews.kt.map create mode 100644 unpackage/cache/.app-android/sourcemap/pages/mall/consumer/points/exchange-records.kt.map create mode 100644 unpackage/cache/.app-android/sourcemap/pages/mall/consumer/points/exchange.kt.map create mode 100644 unpackage/cache/.app-android/sourcemap/pages/mall/consumer/points/signin.kt.map create mode 100644 unpackage/cache/.app-android/sourcemap/pages/mall/consumer/product-reviews.kt.map create mode 100644 unpackage/cache/.app-android/sourcemap/pages/mall/consumer/share/detail.kt.map create mode 100644 unpackage/cache/.app-android/sourcemap/pages/mall/consumer/share/index.kt.map create mode 100644 unpackage/cache/.app-android/src/pages/main/cart.kt create mode 100644 unpackage/cache/.app-android/src/pages/main/category.kt create mode 100644 unpackage/cache/.app-android/src/pages/main/index.kt create mode 100644 unpackage/cache/.app-android/src/pages/main/messages.kt create mode 100644 unpackage/cache/.app-android/src/pages/main/profile.kt create mode 100644 unpackage/cache/.app-android/src/pages/mall/consumer/balance/index.kt create mode 100644 unpackage/cache/.app-android/src/pages/mall/consumer/member/index.kt create mode 100644 unpackage/cache/.app-android/src/pages/mall/consumer/message-detail.kt create mode 100644 unpackage/cache/.app-android/src/pages/mall/consumer/my-reviews.kt create mode 100644 unpackage/cache/.app-android/src/pages/mall/consumer/points/exchange-records.kt create mode 100644 unpackage/cache/.app-android/src/pages/mall/consumer/points/exchange.kt create mode 100644 unpackage/cache/.app-android/src/pages/mall/consumer/points/signin.kt create mode 100644 unpackage/cache/.app-android/src/pages/mall/consumer/product-reviews.kt create mode 100644 unpackage/cache/.app-android/src/pages/mall/consumer/share/detail.kt create mode 100644 unpackage/cache/.app-android/src/pages/mall/consumer/share/index.kt diff --git a/doc_mall/consumer/sql/promotion_system_tables.sql b/doc_mall/consumer/sql/promotion_system_tables.sql index b3c9670d..a23e3d89 100644 --- a/doc_mall/consumer/sql/promotion_system_tables.sql +++ b/doc_mall/consumer/sql/promotion_system_tables.sql @@ -2,22 +2,29 @@ -- 推销模式功能 - 数据库脚本(第一阶段) -- 包含:用户余额系统、分享免单系统、会员等级系统 -- 创建日期: 2026-03-06 +-- 注意:请在Supabase SQL编辑器中分段执行 -- ===================================================== +-- ===================================================== +-- 0. 启用必要扩展 +-- ===================================================== +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +CREATE EXTENSION IF NOT EXISTS "pgcrypto"; + -- ===================================================== -- 一、用户余额系统 -- ===================================================== -- 1. 用户余额表 -CREATE TABLE IF NOT EXISTS ml_user_balance ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - user_id UUID NOT NULL REFERENCES auth.users(id), +DROP TABLE IF EXISTS ml_user_balance CASCADE; +CREATE TABLE ml_user_balance ( + id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, + user_id UUID NOT NULL, balance DECIMAL(10,2) DEFAULT 0, frozen_balance DECIMAL(10,2) DEFAULT 0, total_earned DECIMAL(10,2) DEFAULT 0, total_withdrawn DECIMAL(10,2) DEFAULT 0, updated_at TIMESTAMPTZ DEFAULT NOW(), - UNIQUE(user_id) ); @@ -30,16 +37,17 @@ COMMENT ON COLUMN ml_user_balance.total_earned IS '累计获得'; COMMENT ON COLUMN ml_user_balance.total_withdrawn IS '累计提现'; -- 2. 余额变动记录表 -CREATE TABLE IF NOT EXISTS ml_balance_records ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - user_id UUID NOT NULL REFERENCES auth.users(id), +DROP TABLE IF EXISTS ml_balance_records CASCADE; +CREATE TABLE ml_balance_records ( + id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, + user_id UUID NOT NULL, type VARCHAR(50) NOT NULL, amount DECIMAL(10,2) NOT NULL, balance_before DECIMAL(10,2) NOT NULL DEFAULT 0, balance_after DECIMAL(10,2) NOT NULL DEFAULT 0, related_id UUID, description VARCHAR(200), - operator_id UUID REFERENCES auth.users(id), + operator_id UUID, created_at TIMESTAMPTZ DEFAULT NOW() ); @@ -55,11 +63,12 @@ COMMENT ON COLUMN ml_balance_records.type IS '类型:free_order-免单奖励 -- ===================================================== -- 1. 分享记录表 -CREATE TABLE IF NOT EXISTS ml_share_records ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - user_id UUID NOT NULL REFERENCES auth.users(id), - product_id UUID NOT NULL REFERENCES ml_products(id), - order_id UUID NOT NULL REFERENCES ml_orders(id), +DROP TABLE IF EXISTS ml_share_records CASCADE; +CREATE TABLE ml_share_records ( + id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, + user_id UUID NOT NULL, + product_id UUID NOT NULL, + order_id UUID NOT NULL, order_item_id UUID, share_code VARCHAR(20) NOT NULL UNIQUE, product_name VARCHAR(200), @@ -82,15 +91,15 @@ COMMENT ON TABLE ml_share_records IS '分享免单记录表'; COMMENT ON COLUMN ml_share_records.status IS '状态:0-进行中,1-已完成,2-已失效,3-已过期'; -- 2. 二级购买记录表 -CREATE TABLE IF NOT EXISTS ml_secondary_purchases ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - share_record_id UUID NOT NULL REFERENCES ml_share_records(id) ON DELETE CASCADE, - buyer_id UUID NOT NULL REFERENCES auth.users(id), - order_id UUID NOT NULL REFERENCES ml_orders(id), +DROP TABLE IF EXISTS ml_secondary_purchases CASCADE; +CREATE TABLE ml_secondary_purchases ( + id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, + share_record_id UUID NOT NULL, + buyer_id UUID NOT NULL, + order_id UUID NOT NULL, quantity INT DEFAULT 1, unit_price DECIMAL(10,2), created_at TIMESTAMPTZ DEFAULT NOW(), - UNIQUE(order_id, share_record_id) ); @@ -100,15 +109,16 @@ CREATE INDEX IF NOT EXISTS idx_secondary_purchases_buyer_id ON ml_secondary_purc COMMENT ON TABLE ml_secondary_purchases IS '二级用户购买记录表'; -- 3. 免单奖励记录表 -CREATE TABLE IF NOT EXISTS ml_free_order_rewards ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - user_id UUID NOT NULL REFERENCES auth.users(id), - share_record_id UUID NOT NULL REFERENCES ml_share_records(id), +DROP TABLE IF EXISTS ml_free_order_rewards CASCADE; +CREATE TABLE ml_free_order_rewards ( + id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, + user_id UUID NOT NULL, + share_record_id UUID NOT NULL, amount DECIMAL(10,2) NOT NULL, status INT DEFAULT 0, - balance_record_id UUID REFERENCES ml_balance_records(id), + balance_record_id UUID, cleared_at TIMESTAMPTZ, - cleared_by UUID REFERENCES auth.users(id), + cleared_by UUID, created_at TIMESTAMPTZ DEFAULT NOW() ); @@ -123,7 +133,8 @@ COMMENT ON COLUMN ml_free_order_rewards.status IS '状态:0-待发放,1-已 -- ===================================================== -- 1. 会员等级配置表 -CREATE TABLE IF NOT EXISTS ml_member_levels ( +DROP TABLE IF EXISTS ml_member_levels CASCADE; +CREATE TABLE ml_member_levels ( id INT PRIMARY KEY, name VARCHAR(50) NOT NULL, min_amount DECIMAL(10,2) DEFAULT 0, @@ -146,29 +157,17 @@ INSERT INTO ml_member_levels (id, name, min_amount, discount, description, sort_ (2, '银牌会员', 2000, 0.9500, '累计消费2000元升级', 2), (3, '金牌会员', 5000, 0.9200, '累计消费5000元升级', 3), (4, '钻石会员', 10000, 0.8800, '累计消费10000元升级', 4), -(5, 'VIP会员', 0, 0.8500, '商家特邀会员', 5) -ON CONFLICT (id) DO NOTHING; +(5, 'VIP会员', 0, 0.8500, '商家特邀会员', 5); --- 2. 用户会员信息扩展字段(添加到 ml_user_profiles) -ALTER TABLE ml_user_profiles ADD COLUMN IF NOT EXISTS member_level INT DEFAULT 0; -ALTER TABLE ml_user_profiles ADD COLUMN IF NOT EXISTS total_spent DECIMAL(10,2) DEFAULT 0; -ALTER TABLE ml_user_profiles ADD COLUMN IF NOT EXISTS level_updated_at TIMESTAMPTZ; -ALTER TABLE ml_user_profiles ADD COLUMN IF NOT EXISTS manual_level BOOLEAN DEFAULT FALSE; -ALTER TABLE ml_user_profiles ADD COLUMN IF NOT EXISTS manual_level_by UUID; -ALTER TABLE ml_user_profiles ADD COLUMN IF NOT EXISTS manual_level_at TIMESTAMPTZ; - -COMMENT ON COLUMN ml_user_profiles.member_level IS '当前会员等级'; -COMMENT ON COLUMN ml_user_profiles.total_spent IS '累计消费金额'; -COMMENT ON COLUMN ml_user_profiles.manual_level IS '是否手动设置等级'; - --- 3. 会员等级变更记录表 -CREATE TABLE IF NOT EXISTS ml_member_level_logs ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - user_id UUID NOT NULL REFERENCES auth.users(id), +-- 2. 会员等级变更记录表 +DROP TABLE IF EXISTS ml_member_level_logs CASCADE; +CREATE TABLE ml_member_level_logs ( + id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, + user_id UUID NOT NULL, old_level INT DEFAULT 0, new_level INT NOT NULL, reason VARCHAR(200), - operator_id UUID REFERENCES auth.users(id), + operator_id UUID, created_at TIMESTAMPTZ DEFAULT NOW() ); @@ -183,16 +182,19 @@ COMMENT ON TABLE ml_member_level_logs IS '会员等级变更记录表'; -- 用户余额表 RLS ALTER TABLE ml_user_balance ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS "Users can view own balance" ON ml_user_balance; CREATE POLICY "Users can view own balance" ON ml_user_balance FOR SELECT TO authenticated USING (auth.uid() = user_id); +DROP POLICY IF EXISTS "Users can insert own balance" ON ml_user_balance; CREATE POLICY "Users can insert own balance" ON ml_user_balance FOR INSERT TO authenticated WITH CHECK (auth.uid() = user_id); +DROP POLICY IF EXISTS "Users can update own balance" ON ml_user_balance; CREATE POLICY "Users can update own balance" ON ml_user_balance FOR UPDATE TO authenticated @@ -201,11 +203,13 @@ USING (auth.uid() = user_id); -- 余额记录表 RLS ALTER TABLE ml_balance_records ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS "Users can view own balance records" ON ml_balance_records; CREATE POLICY "Users can view own balance records" ON ml_balance_records FOR SELECT TO authenticated USING (auth.uid() = user_id); +DROP POLICY IF EXISTS "Users can insert own balance records" ON ml_balance_records; CREATE POLICY "Users can insert own balance records" ON ml_balance_records FOR INSERT TO authenticated @@ -214,22 +218,26 @@ WITH CHECK (auth.uid() = user_id); -- 分享记录表 RLS ALTER TABLE ml_share_records ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS "Users can view own share records" ON ml_share_records; CREATE POLICY "Users can view own share records" ON ml_share_records FOR SELECT TO authenticated USING (auth.uid() = user_id); +DROP POLICY IF EXISTS "Users can insert own share records" ON ml_share_records; CREATE POLICY "Users can insert own share records" ON ml_share_records FOR INSERT TO authenticated WITH CHECK (auth.uid() = user_id); +DROP POLICY IF EXISTS "Users can update own share records" ON ml_share_records; CREATE POLICY "Users can update own share records" ON ml_share_records FOR UPDATE TO authenticated USING (auth.uid() = user_id); --- 允许通过分享码查询(用于验证分享码) +-- 允许通过分享码查询 +DROP POLICY IF EXISTS "Anyone can view by share code" ON ml_share_records; CREATE POLICY "Anyone can view by share code" ON ml_share_records FOR SELECT TO authenticated, anon @@ -238,6 +246,7 @@ USING (share_code IS NOT NULL); -- 二级购买记录表 RLS ALTER TABLE ml_secondary_purchases ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS "Users can view own secondary purchases" ON ml_secondary_purchases; CREATE POLICY "Users can view own secondary purchases" ON ml_secondary_purchases FOR SELECT TO authenticated @@ -245,6 +254,7 @@ USING (buyer_id = auth.uid() OR EXISTS ( SELECT 1 FROM ml_share_records WHERE id = share_record_id AND user_id = auth.uid() )); +DROP POLICY IF EXISTS "Users can insert secondary purchases" ON ml_secondary_purchases; CREATE POLICY "Users can insert secondary purchases" ON ml_secondary_purchases FOR INSERT TO authenticated @@ -253,14 +263,16 @@ WITH CHECK (true); -- 免单奖励记录表 RLS ALTER TABLE ml_free_order_rewards ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS "Users can view own rewards" ON ml_free_order_rewards; CREATE POLICY "Users can view own rewards" ON ml_free_order_rewards FOR SELECT TO authenticated USING (auth.uid() = user_id); --- 会员等级配置表 RLS(所有人可查看) +-- 会员等级配置表 RLS ALTER TABLE ml_member_levels ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS "Anyone can view member levels" ON ml_member_levels; CREATE POLICY "Anyone can view member levels" ON ml_member_levels FOR SELECT TO authenticated, anon @@ -269,302 +281,8 @@ USING (status = 1); -- 会员等级变更记录表 RLS ALTER TABLE ml_member_level_logs ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS "Users can view own level logs" ON ml_member_level_logs; CREATE POLICY "Users can view own level logs" ON ml_member_level_logs FOR SELECT TO authenticated USING (auth.uid() = user_id); - --- ===================================================== --- 五、数据库函数 --- ===================================================== - --- 1. 生成分享码函数 -CREATE OR REPLACE FUNCTION generate_share_code() -RETURNS VARCHAR(20) AS $$ -DECLARE - chars TEXT := 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; - result VARCHAR(20) := ''; - i INT; -BEGIN - FOR i IN 1..8 LOOP - result := result || substr(chars, floor(random() * length(chars) + 1)::int, 1); - END LOOP; - RETURN result; -END; -$$ LANGUAGE plpgsql; - --- 2. 增加余额函数 -CREATE OR REPLACE FUNCTION add_user_balance( - p_user_id UUID, - p_amount DECIMAL(10,2), - p_type VARCHAR(50), - p_related_id UUID DEFAULT NULL, - p_description VARCHAR(200) DEFAULT NULL, - p_operator_id UUID DEFAULT NULL -) -RETURNS BOOLEAN AS $$ -DECLARE - v_balance_before DECIMAL(10,2); - v_balance_after DECIMAL(10,2); -BEGIN - -- 获取当前余额 - SELECT COALESCE(balance, 0) INTO v_balance_before - FROM ml_user_balance - WHERE user_id = p_user_id; - - IF v_balance_before IS NULL THEN - -- 创建余额记录 - INSERT INTO ml_user_balance (user_id, balance, total_earned) - VALUES (p_user_id, p_amount, p_amount); - v_balance_before := 0; - v_balance_after := p_amount; - ELSE - -- 更新余额 - UPDATE ml_user_balance - SET balance = balance + p_amount, - total_earned = total_earned + CASE WHEN p_amount > 0 THEN p_amount ELSE 0 END, - updated_at = NOW() - WHERE user_id = p_user_id; - v_balance_after := v_balance_before + p_amount; - END IF; - - -- 记录变动 - INSERT INTO ml_balance_records (user_id, type, amount, balance_before, balance_after, related_id, description, operator_id) - VALUES (p_user_id, p_type, p_amount, v_balance_before, v_balance_after, p_related_id, p_description, p_operator_id); - - RETURN TRUE; -END; -$$ LANGUAGE plpgsql SECURITY DEFINER; - --- 3. 清零用户余额函数 -CREATE OR REPLACE FUNCTION clear_user_balance( - p_user_id UUID, - p_operator_id UUID, - p_description VARCHAR(200) DEFAULT '商家清零余额' -) -RETURNS BOOLEAN AS $$ -DECLARE - v_balance_before DECIMAL(10,2); -BEGIN - -- 获取当前余额 - SELECT balance INTO v_balance_before - FROM ml_user_balance - WHERE user_id = p_user_id; - - IF v_balance_before IS NULL OR v_balance_before = 0 THEN - RETURN FALSE; - END IF; - - -- 清零余额 - UPDATE ml_user_balance - SET balance = 0, - updated_at = NOW() - WHERE user_id = p_user_id; - - -- 记录变动 - INSERT INTO ml_balance_records (user_id, type, amount, balance_before, balance_after, description, operator_id) - VALUES (p_user_id, 'clear', -v_balance_before, v_balance_before, 0, p_description, p_operator_id); - - RETURN TRUE; -END; -$$ LANGUAGE plpgsql SECURITY DEFINER; - --- 4. 检查并升级会员等级函数 -CREATE OR REPLACE FUNCTION check_and_upgrade_member_level(p_user_id UUID) -RETURNS INT AS $$ -DECLARE - v_total_spent DECIMAL(10,2); - v_current_level INT; - v_new_level INT; - v_manual_level BOOLEAN; -BEGIN - -- 获取用户当前信息 - SELECT member_level, total_spent, manual_level - INTO v_current_level, v_total_spent, v_manual_level - FROM ml_user_profiles - WHERE user_id = p_user_id; - - -- 如果是手动设置的等级,不自动升级 - IF v_manual_level = TRUE THEN - RETURN v_current_level; - END IF; - - -- 根据消费金额计算新等级 - SELECT id INTO v_new_level - FROM ml_member_levels - WHERE status = 1 AND min_amount <= COALESCE(v_total_spent, 0) - ORDER BY min_amount DESC - LIMIT 1; - - IF v_new_level IS NULL THEN - v_new_level := 0; - END IF; - - -- 如果等级有变化,更新并记录 - IF v_new_level > COALESCE(v_current_level, 0) THEN - UPDATE ml_user_profiles - SET member_level = v_new_level, - level_updated_at = NOW() - WHERE user_id = p_user_id; - - INSERT INTO ml_member_level_logs (user_id, old_level, new_level, reason) - VALUES (p_user_id, COALESCE(v_current_level, 0), v_new_level, '累计消费自动升级'); - - RETURN v_new_level; - END IF; - - RETURN COALESCE(v_current_level, 0); -END; -$$ LANGUAGE plpgsql SECURITY DEFINER; - --- 5. 处理分享购买函数 -CREATE OR REPLACE FUNCTION process_share_purchase( - p_share_code VARCHAR(20), - p_buyer_id UUID, - p_order_id UUID, - p_quantity INT, - p_unit_price DECIMAL(10,2) -) -RETURNS BOOLEAN AS $$ -DECLARE - v_share_record ml_share_records%ROWTYPE; - v_required_count INT; - v_reward_amount DECIMAL(10,2); -BEGIN - -- 查找分享记录 - SELECT * INTO v_share_record - FROM ml_share_records - WHERE share_code = p_share_code AND status = 0; - - IF NOT FOUND THEN - RETURN FALSE; - END IF; - - -- 不能购买自己分享的商品 - IF v_share_record.user_id = p_buyer_id THEN - RETURN FALSE; - END IF; - - -- 检查是否已购买过(同一用户对同一分享记录只算一次) - IF EXISTS (SELECT 1 FROM ml_secondary_purchases WHERE share_record_id = v_share_record.id AND buyer_id = p_buyer_id) THEN - RETURN FALSE; - END IF; - - -- 记录购买 - INSERT INTO ml_secondary_purchases (share_record_id, buyer_id, order_id, quantity, unit_price) - VALUES (v_share_record.id, p_buyer_id, p_order_id, p_quantity, p_unit_price); - - -- 更新计数 - UPDATE ml_share_records - SET current_count = current_count + 1 - WHERE id = v_share_record.id; - - -- 检查是否达标 - SELECT current_count, required_count INTO v_required_count, v_required_count - FROM ml_share_records WHERE id = v_share_record.id; - - IF v_share_record.current_count + 1 >= v_share_record.required_count THEN - -- 计算奖励金额 - v_reward_amount := v_share_record.product_price; - - -- 更新分享记录状态 - UPDATE ml_share_records - SET status = 1, completed_at = NOW(), reward_amount = v_reward_amount - WHERE id = v_share_record.id; - - -- 创建奖励记录 - INSERT INTO ml_free_order_rewards (user_id, share_record_id, amount) - VALUES (v_share_record.user_id, v_share_record.id, v_reward_amount); - - -- 增加用户余额 - PERFORM add_user_balance( - v_share_record.user_id, - v_reward_amount, - 'free_order', - v_share_record.id, - '分享免单奖励:' || v_share_record.product_name - ); - - -- 更新奖励记录状态 - UPDATE ml_free_order_rewards - SET status = 1 - WHERE share_record_id = v_share_record.id; - END IF; - - RETURN TRUE; -END; -$$ LANGUAGE plpgsql SECURITY DEFINER; - --- ===================================================== --- 六、触发器 --- ===================================================== - --- 订单完成后更新用户累计消费并检查会员等级 -CREATE OR REPLACE FUNCTION update_user_total_spent() -RETURNS TRIGGER AS $$ -BEGIN - IF NEW.status = 4 AND (OLD.status != 4 OR OLD.status IS NULL) THEN - -- 更新累计消费 - UPDATE ml_user_profiles - SET total_spent = COALESCE(total_spent, 0) + NEW.total_amount - WHERE user_id = NEW.user_id; - - -- 检查会员等级 - PERFORM check_and_upgrade_member_level(NEW.user_id); - END IF; - - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - --- 创建触发器(如果不存在) -DO $$ -BEGIN - IF NOT EXISTS ( - SELECT 1 FROM pg_trigger WHERE tgname = 'trigger_update_user_total_spent' - ) THEN - CREATE TRIGGER trigger_update_user_total_spent - AFTER UPDATE ON ml_orders - FOR EACH ROW - EXECUTE FUNCTION update_user_total_spent(); - END IF; -END $$; - --- ===================================================== --- 七、视图 --- ===================================================== - --- 用户余额概览视图 -CREATE OR REPLACE VIEW ml_user_balance_overview AS -SELECT - ub.user_id, - ub.balance, - ub.frozen_balance, - ub.total_earned, - ub.total_withdrawn, - up.member_level, - ml.name as member_level_name, - ml.discount as member_discount, - up.total_spent -FROM ml_user_balance ub -LEFT JOIN ml_user_profiles up ON ub.user_id = up.user_id -LEFT JOIN ml_member_levels ml ON up.member_level = ml.id; - --- 分享进度视图 -CREATE OR REPLACE VIEW ml_share_progress_view AS -SELECT - sr.id, - sr.user_id, - sr.product_id, - sr.product_name, - sr.product_image, - sr.product_price, - sr.share_code, - sr.required_count, - sr.current_count, - sr.status, - sr.reward_amount, - sr.created_at, - sr.completed_at, - sr.required_count - sr.current_count as remaining_count -FROM ml_share_records sr; diff --git a/doc_mall/consumer/sql/promotion_system_tables_v2.sql b/doc_mall/consumer/sql/promotion_system_tables_v2.sql new file mode 100644 index 00000000..b120f933 --- /dev/null +++ b/doc_mall/consumer/sql/promotion_system_tables_v2.sql @@ -0,0 +1,453 @@ +-- ===================================================== +-- 推销模式功能 - 数据库脚本 V2(商家级别) +-- 包含:商家推销配置、用户余额系统、分享免单系统、会员等级系统 +-- 创建日期: 2026-03-09 +-- 重要变更:推销模式改为商家级别,每个商家独立配置 +-- ===================================================== + +-- ===================================================== +-- 0. 启用必要扩展 +-- ===================================================== +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +CREATE EXTENSION IF NOT EXISTS "pgcrypto"; + +-- ===================================================== +-- 一、商家推销配置表 +-- ===================================================== + +DROP TABLE IF EXISTS ml_merchant_promotion_config CASCADE; +CREATE TABLE ml_merchant_promotion_config ( + id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, + merchant_id UUID NOT NULL UNIQUE, + promotion_enabled BOOLEAN DEFAULT FALSE, + share_free_enabled BOOLEAN DEFAULT FALSE, + distribution_enabled BOOLEAN DEFAULT FALSE, + required_count INT DEFAULT 4, + reward_type VARCHAR(20) DEFAULT 'product_price', + fixed_reward_amount DECIMAL(10,2) DEFAULT 0, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_merchant_promotion_merchant_id ON ml_merchant_promotion_config(merchant_id); + +COMMENT ON TABLE ml_merchant_promotion_config IS '商家推销模式配置表'; +COMMENT ON COLUMN ml_merchant_promotion_config.promotion_enabled IS '是否开启推销模式'; +COMMENT ON COLUMN ml_merchant_promotion_config.share_free_enabled IS '是否开启分享免单'; +COMMENT ON COLUMN ml_merchant_promotion_config.distribution_enabled IS '是否开启经销点返利'; +COMMENT ON COLUMN ml_merchant_promotion_config.required_count IS '分享免单所需购买数'; +COMMENT ON COLUMN ml_merchant_promotion_config.reward_type IS '奖励类型:product_price-商品价格,fixed-固定金额'; + +-- ===================================================== +-- 二、用户余额系统(按商家区分) +-- ===================================================== + +-- 1. 商家用户余额表 +DROP TABLE IF EXISTS ml_merchant_user_balance CASCADE; +CREATE TABLE ml_merchant_user_balance ( + id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, + merchant_id UUID NOT NULL, + user_id UUID NOT NULL, + balance DECIMAL(10,2) DEFAULT 0, + frozen_balance DECIMAL(10,2) DEFAULT 0, + total_earned DECIMAL(10,2) DEFAULT 0, + total_withdrawn DECIMAL(10,2) DEFAULT 0, + updated_at TIMESTAMPTZ DEFAULT NOW(), + UNIQUE(merchant_id, user_id) +); + +CREATE INDEX IF NOT EXISTS idx_merchant_user_balance_merchant_id ON ml_merchant_user_balance(merchant_id); +CREATE INDEX IF NOT EXISTS idx_merchant_user_balance_user_id ON ml_merchant_user_balance(user_id); + +COMMENT ON TABLE ml_merchant_user_balance IS '商家用户余额表(每个商家独立余额)'; + +-- 2. 余额变动记录表 +DROP TABLE IF EXISTS ml_balance_records CASCADE; +CREATE TABLE ml_balance_records ( + id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, + merchant_id UUID NOT NULL, + user_id UUID NOT NULL, + type VARCHAR(50) NOT NULL, + amount DECIMAL(10,2) NOT NULL, + balance_before DECIMAL(10,2) NOT NULL DEFAULT 0, + balance_after DECIMAL(10,2) NOT NULL DEFAULT 0, + related_id UUID, + description VARCHAR(200), + operator_id UUID, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_balance_records_merchant_id ON ml_balance_records(merchant_id); +CREATE INDEX IF NOT EXISTS idx_balance_records_user_id ON ml_balance_records(user_id); +CREATE INDEX IF NOT EXISTS idx_balance_records_type ON ml_balance_records(type); +CREATE INDEX IF NOT EXISTS idx_balance_records_created_at ON ml_balance_records(created_at); + +COMMENT ON TABLE ml_balance_records IS '余额变动记录表'; +COMMENT ON COLUMN ml_balance_records.type IS '类型:free_order-免单奖励,rebate-返利,withdraw-提现,clear-清零,manual-手动调整'; + +-- ===================================================== +-- 三、分享免单系统 +-- ===================================================== + +-- 1. 分享记录表 +DROP TABLE IF EXISTS ml_share_records CASCADE; +CREATE TABLE ml_share_records ( + id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, + merchant_id UUID NOT NULL, + user_id UUID NOT NULL, + product_id UUID NOT NULL, + order_id UUID NOT NULL, + order_item_id UUID, + share_code VARCHAR(20) NOT NULL UNIQUE, + product_name VARCHAR(200), + product_image VARCHAR(500), + product_price DECIMAL(10,2), + required_count INT DEFAULT 4, + current_count INT DEFAULT 0, + status INT DEFAULT 0, + reward_amount DECIMAL(10,2), + created_at TIMESTAMPTZ DEFAULT NOW(), + completed_at TIMESTAMPTZ, + expired_at TIMESTAMPTZ +); + +CREATE INDEX IF NOT EXISTS idx_share_records_merchant_id ON ml_share_records(merchant_id); +CREATE INDEX IF NOT EXISTS idx_share_records_user_id ON ml_share_records(user_id); +CREATE INDEX IF NOT EXISTS idx_share_records_share_code ON ml_share_records(share_code); +CREATE INDEX IF NOT EXISTS idx_share_records_status ON ml_share_records(status); + +COMMENT ON TABLE ml_share_records IS '分享免单记录表'; +COMMENT ON COLUMN ml_share_records.status IS '状态:0-进行中,1-已完成,2-已失效,3-已过期'; + +-- 2. 二级购买记录表 +DROP TABLE IF EXISTS ml_secondary_purchases CASCADE; +CREATE TABLE ml_secondary_purchases ( + id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, + share_record_id UUID NOT NULL, + buyer_id UUID NOT NULL, + order_id UUID NOT NULL, + quantity INT DEFAULT 1, + unit_price DECIMAL(10,2), + created_at TIMESTAMPTZ DEFAULT NOW(), + UNIQUE(order_id, share_record_id) +); + +CREATE INDEX IF NOT EXISTS idx_secondary_purchases_share_record_id ON ml_secondary_purchases(share_record_id); +CREATE INDEX IF NOT EXISTS idx_secondary_purchases_buyer_id ON ml_secondary_purchases(buyer_id); + +COMMENT ON TABLE ml_secondary_purchases IS '二级用户购买记录表'; + +-- 3. 免单奖励记录表 +DROP TABLE IF EXISTS ml_free_order_rewards CASCADE; +CREATE TABLE ml_free_order_rewards ( + id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, + merchant_id UUID NOT NULL, + user_id UUID NOT NULL, + share_record_id UUID NOT NULL, + amount DECIMAL(10,2) NOT NULL, + status INT DEFAULT 0, + balance_record_id UUID, + cleared_at TIMESTAMPTZ, + cleared_by UUID, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_free_order_rewards_merchant_id ON ml_free_order_rewards(merchant_id); +CREATE INDEX IF NOT EXISTS idx_free_order_rewards_user_id ON ml_free_order_rewards(user_id); +CREATE INDEX IF NOT EXISTS idx_free_order_rewards_status ON ml_free_order_rewards(status); + +COMMENT ON TABLE ml_free_order_rewards IS '免单奖励记录表'; +COMMENT ON COLUMN ml_free_order_rewards.status IS '状态:0-待发放,1-已发放,2-已清零'; + +-- ===================================================== +-- 四、会员等级系统(全局) +-- ===================================================== + +-- 1. 会员等级配置表 +DROP TABLE IF EXISTS ml_member_levels CASCADE; +CREATE TABLE ml_member_levels ( + id INT PRIMARY KEY, + name VARCHAR(50) NOT NULL, + min_amount DECIMAL(10,2) DEFAULT 0, + discount DECIMAL(5,4) DEFAULT 1.0000, + icon VARCHAR(200), + description TEXT, + sort_order INT DEFAULT 0, + status INT DEFAULT 1, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +COMMENT ON TABLE ml_member_levels IS '会员等级配置表'; +COMMENT ON COLUMN ml_member_levels.discount IS '折扣率,0.85表示85折'; + +-- 初始化会员等级数据 +INSERT INTO ml_member_levels (id, name, min_amount, discount, description, sort_order) VALUES +(0, '普通会员', 0, 1.0000, '注册即可成为普通会员', 0), +(1, '铜牌会员', 500, 0.9800, '累计消费500元升级', 1), +(2, '银牌会员', 2000, 0.9500, '累计消费2000元升级', 2), +(3, '金牌会员', 5000, 0.9200, '累计消费5000元升级', 3), +(4, '钻石会员', 10000, 0.8800, '累计消费10000元升级', 4), +(5, 'VIP会员', 0, 0.8500, '商家特邀会员', 5); + +-- 2. 会员等级变更记录表 +DROP TABLE IF EXISTS ml_member_level_logs CASCADE; +CREATE TABLE ml_member_level_logs ( + id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, + user_id UUID NOT NULL, + old_level INT DEFAULT 0, + new_level INT NOT NULL, + reason VARCHAR(200), + operator_id UUID, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_member_level_logs_user_id ON ml_member_level_logs(user_id); + +COMMENT ON TABLE ml_member_level_logs IS '会员等级变更记录表'; + +-- ===================================================== +-- 五、RLS 策略 +-- ===================================================== + +-- 商家推销配置表 RLS +ALTER TABLE ml_merchant_promotion_config ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "Anyone can view merchant promotion config" ON ml_merchant_promotion_config; +CREATE POLICY "Anyone can view merchant promotion config" +ON ml_merchant_promotion_config FOR SELECT +TO authenticated, anon +USING (true); + +-- 商家用户余额表 RLS +ALTER TABLE ml_merchant_user_balance ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "Users can view own merchant balance" ON ml_merchant_user_balance; +CREATE POLICY "Users can view own merchant balance" +ON ml_merchant_user_balance FOR SELECT +TO authenticated +USING (auth.uid() = user_id); + +DROP POLICY IF EXISTS "Users can insert own merchant balance" ON ml_merchant_user_balance; +CREATE POLICY "Users can insert own merchant balance" +ON ml_merchant_user_balance FOR INSERT +TO authenticated +WITH CHECK (auth.uid() = user_id); + +DROP POLICY IF EXISTS "Users can update own merchant balance" ON ml_merchant_user_balance; +CREATE POLICY "Users can update own merchant balance" +ON ml_merchant_user_balance FOR UPDATE +TO authenticated +USING (auth.uid() = user_id); + +-- 余额记录表 RLS +ALTER TABLE ml_balance_records ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "Users can view own balance records" ON ml_balance_records; +CREATE POLICY "Users can view own balance records" +ON ml_balance_records FOR SELECT +TO authenticated +USING (auth.uid() = user_id); + +DROP POLICY IF EXISTS "Users can insert own balance records" ON ml_balance_records; +CREATE POLICY "Users can insert own balance records" +ON ml_balance_records FOR INSERT +TO authenticated +WITH CHECK (auth.uid() = user_id); + +-- 分享记录表 RLS +ALTER TABLE ml_share_records ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "Users can view own share records" ON ml_share_records; +CREATE POLICY "Users can view own share records" +ON ml_share_records FOR SELECT +TO authenticated +USING (auth.uid() = user_id); + +DROP POLICY IF EXISTS "Users can insert own share records" ON ml_share_records; +CREATE POLICY "Users can insert own share records" +ON ml_share_records FOR INSERT +TO authenticated +WITH CHECK (auth.uid() = user_id); + +DROP POLICY IF EXISTS "Users can update own share records" ON ml_share_records; +CREATE POLICY "Users can update own share records" +ON ml_share_records FOR UPDATE +TO authenticated +USING (auth.uid() = user_id); + +-- 允许通过分享码查询 +DROP POLICY IF EXISTS "Anyone can view by share code" ON ml_share_records; +CREATE POLICY "Anyone can view by share code" +ON ml_share_records FOR SELECT +TO authenticated, anon +USING (share_code IS NOT NULL); + +-- 二级购买记录表 RLS +ALTER TABLE ml_secondary_purchases ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "Users can view own secondary purchases" ON ml_secondary_purchases; +CREATE POLICY "Users can view own secondary purchases" +ON ml_secondary_purchases FOR SELECT +TO authenticated +USING (buyer_id = auth.uid() OR EXISTS ( + SELECT 1 FROM ml_share_records WHERE id = share_record_id AND user_id = auth.uid() +)); + +DROP POLICY IF EXISTS "Users can insert secondary purchases" ON ml_secondary_purchases; +CREATE POLICY "Users can insert secondary purchases" +ON ml_secondary_purchases FOR INSERT +TO authenticated +WITH CHECK (true); + +-- 免单奖励记录表 RLS +ALTER TABLE ml_free_order_rewards ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "Users can view own rewards" ON ml_free_order_rewards; +CREATE POLICY "Users can view own rewards" +ON ml_free_order_rewards FOR SELECT +TO authenticated +USING (auth.uid() = user_id); + +-- 会员等级配置表 RLS +ALTER TABLE ml_member_levels ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "Anyone can view member levels" ON ml_member_levels; +CREATE POLICY "Anyone can view member levels" +ON ml_member_levels FOR SELECT +TO authenticated, anon +USING (status = 1); + +-- 会员等级变更记录表 RLS +ALTER TABLE ml_member_level_logs ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "Users can view own level logs" ON ml_member_level_logs; +CREATE POLICY "Users can view own level logs" +ON ml_member_level_logs FOR SELECT +TO authenticated +USING (auth.uid() = user_id); + +-- ===================================================== +-- 六、数据库函数 +-- ===================================================== + +-- 1. 获取商家推销配置 +CREATE OR REPLACE FUNCTION get_merchant_promotion_config(p_merchant_id UUID) +RETURNS TABLE ( + promotion_enabled BOOLEAN, + share_free_enabled BOOLEAN, + distribution_enabled BOOLEAN, + required_count INT, + reward_type VARCHAR, + fixed_reward_amount DECIMAL +) AS $$ +BEGIN + RETURN QUERY + SELECT + mpc.promotion_enabled, + mpc.share_free_enabled, + mpc.distribution_enabled, + mpc.required_count, + mpc.reward_type, + mpc.fixed_reward_amount + FROM ml_merchant_promotion_config mpc + WHERE mpc.merchant_id = p_merchant_id; + + IF NOT FOUND THEN + RETURN QUERY SELECT FALSE, FALSE, FALSE, 4, 'product_price'::VARCHAR, 0::DECIMAL; + END IF; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +-- 2. 检查商家是否开启分享免单 +CREATE OR REPLACE FUNCTION is_share_free_enabled(p_merchant_id UUID) +RETURNS BOOLEAN AS $$ +DECLARE + v_enabled BOOLEAN; +BEGIN + SELECT promotion_enabled AND share_free_enabled INTO v_enabled + FROM ml_merchant_promotion_config + WHERE merchant_id = p_merchant_id; + + RETURN COALESCE(v_enabled, FALSE); +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +-- 3. 增加商家用户余额 +CREATE OR REPLACE FUNCTION add_merchant_user_balance( + p_merchant_id UUID, + p_user_id UUID, + p_amount DECIMAL(10,2), + p_type VARCHAR(50), + p_related_id UUID DEFAULT NULL, + p_description VARCHAR(200) DEFAULT NULL, + p_operator_id UUID DEFAULT NULL +) +RETURNS BOOLEAN AS $$ +DECLARE + v_balance_before DECIMAL(10,2); + v_balance_after DECIMAL(10,2); +BEGIN + SELECT COALESCE(balance, 0) INTO v_balance_before + FROM ml_merchant_user_balance + WHERE merchant_id = p_merchant_id AND user_id = p_user_id; + + IF v_balance_before IS NULL THEN + INSERT INTO ml_merchant_user_balance (merchant_id, user_id, balance, total_earned) + VALUES (p_merchant_id, p_user_id, p_amount, p_amount); + v_balance_before := 0; + v_balance_after := p_amount; + ELSE + UPDATE ml_merchant_user_balance + SET balance = balance + p_amount, + total_earned = total_earned + CASE WHEN p_amount > 0 THEN p_amount ELSE 0 END, + updated_at = NOW() + WHERE merchant_id = p_merchant_id AND user_id = p_user_id; + v_balance_after := v_balance_before + p_amount; + END IF; + + INSERT INTO ml_balance_records (merchant_id, user_id, type, amount, balance_before, balance_after, related_id, description, operator_id) + VALUES (p_merchant_id, p_user_id, p_type, p_amount, v_balance_before, v_balance_after, p_related_id, p_description, p_operator_id); + + RETURN TRUE; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +-- 4. 清零商家用户余额 +CREATE OR REPLACE FUNCTION clear_merchant_user_balance( + p_merchant_id UUID, + p_user_id UUID, + p_operator_id UUID, + p_description VARCHAR(200) DEFAULT '商家清零余额' +) +RETURNS BOOLEAN AS $$ +DECLARE + v_balance_before DECIMAL(10,2); +BEGIN + SELECT balance INTO v_balance_before + FROM ml_merchant_user_balance + WHERE merchant_id = p_merchant_id AND user_id = p_user_id; + + IF v_balance_before IS NULL OR v_balance_before = 0 THEN + RETURN FALSE; + END IF; + + UPDATE ml_merchant_user_balance + SET balance = 0, + updated_at = NOW() + WHERE merchant_id = p_merchant_id AND user_id = p_user_id; + + INSERT INTO ml_balance_records (merchant_id, user_id, type, amount, balance_before, balance_after, description, operator_id) + VALUES (p_merchant_id, p_user_id, 'clear', -v_balance_before, v_balance_before, 0, p_description, p_operator_id); + + RETURN TRUE; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +-- ===================================================== +-- 七、测试数据(可选) +-- ===================================================== + +-- 为某个商家开启推销模式(替换YOUR_MERCHANT_ID为实际商家ID) +-- INSERT INTO ml_merchant_promotion_config (merchant_id, promotion_enabled, share_free_enabled, required_count) +-- VALUES ('YOUR_MERCHANT_ID', TRUE, TRUE, 4); diff --git a/doc_mall/consumer/sql/share_free_notifications.sql b/doc_mall/consumer/sql/share_free_notifications.sql new file mode 100644 index 00000000..8face701 --- /dev/null +++ b/doc_mall/consumer/sql/share_free_notifications.sql @@ -0,0 +1,143 @@ +-- ===================================================== +-- 分享免单通知消息 - 数据库执行语句 +-- ===================================================== + +-- 1. 添加分享免单相关的通知类型说明 +-- ml_notifications 表的 type 字段可包含以下类型: +-- 'system' - 系统通知 +-- 'promotion' - 优惠活动 +-- 'order' - 订单通知 +-- 'share_free' - 分享免单通知 (新增) + +-- 2. 创建分享免单通知的函数 +-- 当分享免单成功时,自动发送通知给用户 +CREATE OR REPLACE FUNCTION notify_share_free_success( + p_user_id UUID, + p_share_record_id UUID, + p_product_name VARCHAR, + p_reward_amount DECIMAL(10,2), + p_share_code VARCHAR +) +RETURNS VOID AS $$ +BEGIN + INSERT INTO ml_notifications ( + user_id, + type, + title, + content, + icon_url, + link_url, + extra_data, + created_at + ) VALUES ( + p_user_id, + 'share_free', + '恭喜!您已获得免单奖励', + '您分享的商品【' || p_product_name || '】已有4位好友购买,获得免单奖励 ¥' || p_reward_amount || '!分享码:' || p_share_code, + '/static/icons/gift.png', + '/pages/mall/consumer/share/detail?id=' || p_share_record_id, + jsonb_build_object( + 'share_record_id', p_share_record_id, + 'product_name', p_product_name, + 'reward_amount', p_reward_amount, + 'share_code', p_share_code + ), + NOW() + ); +END; +$$ LANGUAGE plpgsql; + +-- 3. 创建二级购买进度通知函数 +-- 当有人通过分享码购买时,发送进度通知 +CREATE OR REPLACE FUNCTION notify_share_purchase_progress( + p_user_id UUID, + p_share_record_id UUID, + p_product_name VARCHAR, + p_current_count INT, + p_required_count INT, + p_share_code VARCHAR +) +RETURNS VOID AS $$ +BEGIN + INSERT INTO ml_notifications ( + user_id, + type, + title, + content, + icon_url, + link_url, + extra_data, + created_at + ) VALUES ( + p_user_id, + 'share_free', + '分享免单进度更新', + '您分享的商品【' || p_product_name || '】已有 ' || p_current_count || '/' || p_required_count || ' 人购买,再邀请 ' || (p_required_count - p_current_count) || ' 人即可免单!', + '/static/icons/share.png', + '/pages/mall/consumer/share/detail?id=' || p_share_record_id, + jsonb_build_object( + 'share_record_id', p_share_record_id, + 'product_name', p_product_name, + 'current_count', p_current_count, + 'required_count', p_required_count, + 'share_code', p_share_code + ), + NOW() + ); +END; +$$ LANGUAGE plpgsql; + +-- 4. 创建分享创建成功通知函数 +-- 当用户创建分享时,发送确认通知 +CREATE OR REPLACE FUNCTION notify_share_created( + p_user_id UUID, + p_share_record_id UUID, + p_product_name VARCHAR, + p_share_code VARCHAR +) +RETURNS VOID AS $$ +BEGIN + INSERT INTO ml_notifications ( + user_id, + type, + title, + content, + icon_url, + link_url, + extra_data, + created_at + ) VALUES ( + p_user_id, + 'share_free', + '分享创建成功', + '您已成功分享商品【' || p_product_name || '】,分享码:' || p_share_code || '。邀请4位好友购买即可免单!', + '/static/icons/share-success.png', + '/pages/mall/consumer/share/detail?id=' || p_share_record_id, + jsonb_build_object( + 'share_record_id', p_share_record_id, + 'product_name', p_product_name, + 'share_code', p_share_code + ), + NOW() + ); +END; +$$ LANGUAGE plpgsql; + +-- 5. 示例:手动插入测试通知(可选) +-- 替换 'YOUR_USER_ID' 为实际用户ID +-- INSERT INTO ml_notifications (user_id, type, title, content, icon_url, link_url, extra_data, created_at) +-- VALUES ( +-- 'YOUR_USER_ID', +-- 'share_free', +-- '恭喜!您已获得免单奖励', +-- '您分享的商品【测试商品】已有4位好友购买,获得免单奖励 ¥99.00!分享码:ABCD1234', +-- '/static/icons/gift.png', +-- '/pages/mall/consumer/share/detail?id=test-share-id', +-- '{"share_record_id": "test-share-id", "product_name": "测试商品", "reward_amount": 99.00, "share_code": "ABCD1234"}', +-- NOW() +-- ); + +-- 6. 查询用户分享免单相关通知 +-- SELECT * FROM ml_notifications +-- WHERE type = 'share_free' +-- ORDER BY created_at DESC; diff --git a/pages.json b/pages.json index 3c298d34..1fdb756a 100644 --- a/pages.json +++ b/pages.json @@ -312,6 +312,12 @@ "navigationBarTitleText": "会员中心" } }, + { + "path": "message-detail", + "style": { + "navigationBarTitleText": "消息详情" + } + }, { "path": "red-packets/index", "style": { diff --git a/pages/main/cart.uvue b/pages/main/cart.uvue index f1ba1c97..2a031ce7 100644 --- a/pages/main/cart.uvue +++ b/pages/main/cart.uvue @@ -334,10 +334,7 @@ const updateRecommendList = (recommends: Product[]) => { const refreshRecommend = async () => { try { - // 递增页码以获取不同的商品 - recommendPage.value = recommendPage.value + 1 - - // 使用 searchProducts 获取不同页码的 6 个产品 + // 鷻加随机偏移量, const randomOffset = Math.floor(Math.random() * 1000) const hotResp = await supabaseService.searchProducts('', recommendPage.value, 6, 'sales') const recommends = hotResp.data diff --git a/pages/main/messages.uvue b/pages/main/messages.uvue index e47a0e79..4ce71d35 100644 --- a/pages/main/messages.uvue +++ b/pages/main/messages.uvue @@ -986,7 +986,7 @@ const onRefresh = () => { .message-title { font-size: 16px; color: #1a1a1a; - font-weight: 500; + font-weight: bold; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; diff --git a/pages/main/profile.uvue b/pages/main/profile.uvue index e0823881..c6462290 100644 --- a/pages/main/profile.uvue +++ b/pages/main/profile.uvue @@ -152,7 +152,7 @@ - + {{ getOrderTitle(order) }} @@ -583,11 +583,9 @@ export default { } } - // 获取积分和余额(并行获取) - const [balanceResult, points] = await Promise.all([ - supabaseService.getUserBalance(), - supabaseService.getUserPoints() - ]) + // 获取积分和余额(顺序获取,UTS不支持Promise.all数组解构) + const balanceResult = await supabaseService.getUserBalance() + const points = await supabaseService.getUserPoints() const balanceValue = balanceResult.getNumber('balance') ?? 0 @@ -1088,6 +1086,25 @@ export default { }) }, + goToProductFromOrder(order: OrderItemType) { + const itemsRaw = order.ml_order_items + if (itemsRaw == null) return + const items = itemsRaw as any[] + if (items.length > 0) { + const firstItem = items[0] + const itemStr = JSON.stringify(firstItem) + const itemParsed = JSON.parse(itemStr) + if (itemParsed == null) return + const itemObj = itemParsed as UTSJSONObject + const productId = itemObj.getString('product_id') + if (productId != null && productId !== '') { + uni.navigateTo({ + url: `/pages/mall/consumer/product-detail?id=${productId}` + }) + } + } + }, + payOrder(order: OrderItemType) { uni.navigateTo({ url: `/pages/mall/consumer/payment?orderId=${order.id}` diff --git a/pages/mall/consumer/address-edit.uvue b/pages/mall/consumer/address-edit.uvue index 2508f2d5..a25d6442 100644 --- a/pages/mall/consumer/address-edit.uvue +++ b/pages/mall/consumer/address-edit.uvue @@ -454,7 +454,6 @@ const deleteAddress = () => { padding: 0 4px; background-color: transparent; /* 确保输入框背景透明 */ border: none; /* 强制去除安卓原生边框 */ - outline: none; /* 强制去除焦点边框 */ } .textarea { @@ -466,7 +465,6 @@ const deleteAddress = () => { padding: 4px 0; background-color: transparent; border: none; /* 强制去除安卓原生边框 */ - outline: none; /* 强制去除焦点边框 */ } .placeholder { diff --git a/pages/mall/consumer/address-list.uvue b/pages/mall/consumer/address-list.uvue index d4314979..770dd9c2 100644 --- a/pages/mall/consumer/address-list.uvue +++ b/pages/mall/consumer/address-list.uvue @@ -182,7 +182,6 @@ const selectAddress = (item: Address) => { diff --git a/pages/mall/consumer/my-reviews.uvue b/pages/mall/consumer/my-reviews.uvue index 91763332..e52a3dd1 100644 --- a/pages/mall/consumer/my-reviews.uvue +++ b/pages/mall/consumer/my-reviews.uvue @@ -22,7 +22,7 @@ @@ -84,7 +84,7 @@ diff --git a/pages/mall/consumer/order-detail.uvue b/pages/mall/consumer/order-detail.uvue index 78aecdc5..f695e131 100644 --- a/pages/mall/consumer/order-detail.uvue +++ b/pages/mall/consumer/order-detail.uvue @@ -746,21 +746,16 @@ const shareForFree = async () => { // 使用 onBackPress 拦截物理返回键和系统导航栏返回 onBackPress((_): boolean => { const pages = getCurrentPages() + console.log('[order-detail onBackPress] pages count:', pages.length) + if (pages.length > 1) { - // @ts-ignore - const prevPage = pages[pages.length - 2] - // @ts-ignore - const prevRoute: string = prevPage.route ?? '' - - if (prevRoute.includes('product-detail') || prevRoute.includes('payment')) { - uni.redirectTo({ url: '/pages/mall/consumer/orders' }) - return true - } - } else { - uni.redirectTo({ url: '/pages/mall/consumer/orders' }) - return true + // 正常返回上一页 + return false } - return false + + // 如果只有当前页面,跳转到 orders + uni.redirectTo({ url: '/pages/mall/consumer/orders' }) + return true }) // 生命周期 - 在所有函数定义之后 diff --git a/pages/mall/consumer/orders.uvue b/pages/mall/consumer/orders.uvue index 7d9ce5c3..5e1b0dcc 100644 --- a/pages/mall/consumer/orders.uvue +++ b/pages/mall/consumer/orders.uvue @@ -1,4 +1,4 @@ - +