20260203
This commit is contained in:
@@ -25,6 +25,7 @@
|
||||
|
||||
<script lang="uts">
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import { IS_TEST_MODE } from '@/ak/config.uts'
|
||||
|
||||
export default {
|
||||
onLoad() {
|
||||
@@ -37,37 +38,23 @@
|
||||
methods: {
|
||||
checkAndRedirect() {
|
||||
console.log('boot: start redirect check')
|
||||
try {
|
||||
const sessionInfo = supa.getSession();
|
||||
if (sessionInfo != null && sessionInfo.user != null) {
|
||||
// 已登录 -> 直接进入消费者端首页
|
||||
console.log('boot: found session, go consumer/index')
|
||||
uni.reLaunch({
|
||||
url: '/pages/mall/consumer/index',
|
||||
success: () => {
|
||||
console.log('boot: redirect to consumer/index success')
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('boot: redirect to consumer/index failed', err)
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('boot: error checking session', e);
|
||||
|
||||
if (IS_TEST_MODE) {
|
||||
// 测试阶段:不做强制重定向,保留你手动输入的 URL / 目标页面
|
||||
return
|
||||
}
|
||||
|
||||
console.log('boot: no session, go login')
|
||||
// 未登录 -> 登录页
|
||||
uni.reLaunch({
|
||||
url: '/pages/user/login',
|
||||
success: () => {
|
||||
console.log('boot: redirect to login success')
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('boot: redirect to login failed', err)
|
||||
try {
|
||||
const sessionInfo = supa.getSession()
|
||||
if (sessionInfo != null && sessionInfo.user != null) {
|
||||
uni.reLaunch({ url: '/pages/mall/consumer/index' })
|
||||
return
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('boot: error checking session', e)
|
||||
}
|
||||
|
||||
uni.reLaunch({ url: '/pages/user/login' })
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -141,8 +141,8 @@
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import { getCurrentUser, logout, setIsLoggedIn, setUserProfile } from '@/utils/store.uts'
|
||||
import type { UserProfile } from '@/pages/user/types.uts'
|
||||
import { IS_TEST_MODE } from '@/ak/config.uts'
|
||||
import { getCurrentUser, logout } from '@/utils/store.uts'
|
||||
|
||||
const cssVars = {
|
||||
'--bg': '#f5f6f8',
|
||||
@@ -172,11 +172,18 @@ const codeCountdown = ref<number>(0)
|
||||
|
||||
onMounted(() => {
|
||||
try {
|
||||
if (IS_TEST_MODE) return
|
||||
const sessionInfo = supa.getSession()
|
||||
// 只有当用户确实存在,且 ID 有效时才自动跳转
|
||||
if (sessionInfo != null && sessionInfo.user != null && sessionInfo.user?.getString('id') != null) {
|
||||
console.log('检测到有效会话,自动跳转首页')
|
||||
uni.switchTab({ url: '/pages/mall/consumer/index' })
|
||||
if (sessionInfo != null && sessionInfo.user != null) {
|
||||
const pages = getCurrentPages() as any[]
|
||||
const currentPage = pages.length > 0 ? pages[pages.length - 1] : null
|
||||
const opts = currentPage?.options as any
|
||||
const redirect = opts?.redirect as string | null
|
||||
if (redirect != null && redirect.length > 0) {
|
||||
uni.redirectTo({ url: decodeURIComponent(redirect) })
|
||||
} else {
|
||||
uni.switchTab({ url: '/pages/mall/consumer/index' })
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('检查登录状态失败:', e)
|
||||
@@ -340,9 +347,19 @@ const handleLogin = async () => {
|
||||
}
|
||||
|
||||
uni.showToast({ title: '登录成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
uni.switchTab({ url: '/pages/mall/consumer/index' })
|
||||
}, 500)
|
||||
if (!IS_TEST_MODE) {
|
||||
setTimeout(() => {
|
||||
const pages = getCurrentPages() as any[]
|
||||
const currentPage = pages.length > 0 ? pages[pages.length - 1] : null
|
||||
const opts = currentPage?.options as any
|
||||
const redirect = opts?.redirect as string | null
|
||||
if (redirect != null && redirect.length > 0) {
|
||||
uni.redirectTo({ url: decodeURIComponent(redirect) })
|
||||
} else {
|
||||
uni.switchTab({ url: '/pages/mall/consumer/index' })
|
||||
}
|
||||
}, 500)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('登录错误:', err)
|
||||
let msg = '登录失败,请重试'
|
||||
@@ -357,7 +374,20 @@ const handleLogin = async () => {
|
||||
}
|
||||
|
||||
const navigateToRegister = () => {
|
||||
uni.navigateTo({ url: '/pages/user/register' })
|
||||
const pages = getCurrentPages() as any[]
|
||||
const currentPage = pages.length > 0 ? pages[pages.length - 1] : null
|
||||
const opts = currentPage?.options as any
|
||||
const redirect = opts?.redirect as string | null
|
||||
|
||||
if (redirect != null && redirect.length > 0) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/user/register?redirect=${redirect}`
|
||||
})
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url: '/pages/user/register'
|
||||
})
|
||||
}
|
||||
}
|
||||
const handleTutorial = () => uni.showToast({ title: '扫码教程开发中', icon: 'none' })
|
||||
const handleForgotPassword = () => uni.showToast({ title: '忘记密码开发中', icon: 'none' })
|
||||
|
||||
@@ -330,9 +330,20 @@
|
||||
|
||||
// 跳转到登录页
|
||||
const navigateToLogin = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/user/login'
|
||||
})
|
||||
const pages = getCurrentPages() as any[]
|
||||
const currentPage = pages.length > 0 ? pages[pages.length - 1] : null
|
||||
const opts = currentPage?.options as any
|
||||
const redirect = opts?.redirect as string | null
|
||||
|
||||
if (redirect != null && redirect.length > 0) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/user/login?redirect=${redirect}`
|
||||
})
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url: '/pages/user/login'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 跳转到协议页面
|
||||
|
||||
145
pages/user/test/USER_AUTH_SCHEMA.sql
Normal file
145
pages/user/test/USER_AUTH_SCHEMA.sql
Normal file
@@ -0,0 +1,145 @@
|
||||
-- ============================================
|
||||
-- 用户登录 / 注册 - 核心用户资料表结构(创建版 / Create-only)
|
||||
-- ============================================
|
||||
-- 用途:创建核心业务用户资料表(ak_users)及其相关函数和 RLS 策略。
|
||||
-- 特点:
|
||||
-- 1. 不做 DROP/DELETE/TRUNCATE(不清空数据)
|
||||
-- 2. 通过 IF NOT EXISTS + 系统表判断,实现可重复执行
|
||||
-- 3. 职责单一:只负责 ak_users,不涉及其他基础表
|
||||
-- 4. 依赖:应在基础表(01_create_tables.sql)之后执行
|
||||
-- ============================================
|
||||
|
||||
-- ============================================
|
||||
-- 1. 业务用户资料表 ak_users
|
||||
-- ============================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.ak_users (
|
||||
id uuid primary key,
|
||||
username text,
|
||||
email text,
|
||||
gender text,
|
||||
birthday date,
|
||||
height_cm numeric,
|
||||
weight_kg numeric,
|
||||
bio text,
|
||||
avatar_url text,
|
||||
preferred_language text,
|
||||
role text,
|
||||
school_id text,
|
||||
grade_id text,
|
||||
class_id text,
|
||||
created_at timestamptz default now(),
|
||||
updated_at timestamptz default now()
|
||||
);
|
||||
|
||||
-- 中文注释
|
||||
COMMENT ON TABLE public.ak_users IS '业务用户资料表(与 auth.users 一一对应)';
|
||||
COMMENT ON COLUMN public.ak_users.id IS '用户ID(等于 auth.users.id)';
|
||||
COMMENT ON COLUMN public.ak_users.username IS '用户名/昵称';
|
||||
COMMENT ON COLUMN public.ak_users.email IS '邮箱';
|
||||
COMMENT ON COLUMN public.ak_users.gender IS '性别';
|
||||
COMMENT ON COLUMN public.ak_users.birthday IS '生日';
|
||||
COMMENT ON COLUMN public.ak_users.height_cm IS '身高(厘米)';
|
||||
COMMENT ON COLUMN public.ak_users.weight_kg IS '体重(公斤)';
|
||||
COMMENT ON COLUMN public.ak_users.bio IS '个人简介';
|
||||
COMMENT ON COLUMN public.ak_users.avatar_url IS '头像地址';
|
||||
COMMENT ON COLUMN public.ak_users.preferred_language IS '偏好语言';
|
||||
COMMENT ON COLUMN public.ak_users.role IS '角色(如 customer/merchant/admin 等)';
|
||||
COMMENT ON COLUMN public.ak_users.school_id IS '学校ID(可选)';
|
||||
COMMENT ON COLUMN public.ak_users.grade_id IS '年级ID(可选)';
|
||||
COMMENT ON COLUMN public.ak_users.class_id IS '班级ID(可选)';
|
||||
COMMENT ON COLUMN public.ak_users.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN public.ak_users.updated_at IS '更新时间';
|
||||
|
||||
-- 为 ak_users 添加 updated_at 触发器
|
||||
-- 注意:通用函数 update_updated_at_column() 在 01_create_tables.sql 中创建
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'update_ak_users_updated_at') THEN
|
||||
EXECUTE 'CREATE TRIGGER update_ak_users_updated_at BEFORE UPDATE ON public.ak_users FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column()';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ============================================
|
||||
-- 2. 行级安全策略(RLS)
|
||||
-- ============================================
|
||||
|
||||
-- 启用 RLS
|
||||
ALTER TABLE public.ak_users ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- 仅允许本人读写自己的资料
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename='ak_users' AND policyname='ak_users_self_select') THEN
|
||||
EXECUTE 'CREATE POLICY "ak_users_self_select" ON public.ak_users FOR SELECT USING (auth.uid() = id)';
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename='ak_users' AND policyname='ak_users_self_insert') THEN
|
||||
EXECUTE 'CREATE POLICY "ak_users_self_insert" ON public.ak_users FOR INSERT WITH CHECK (auth.uid() = id)';
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename='ak_users' AND policyname='ak_users_self_update') THEN
|
||||
EXECUTE 'CREATE POLICY "ak_users_self_update" ON public.ak_users FOR UPDATE USING (auth.uid() = id)';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ============================================
|
||||
-- 3. 相关函数
|
||||
-- ============================================
|
||||
|
||||
-- 函数1:手动初始化/更新用户资料(可选,供前端调用)
|
||||
CREATE OR REPLACE FUNCTION public.upsert_user_profile(
|
||||
p_user_id uuid,
|
||||
p_email text,
|
||||
p_username text default null
|
||||
)
|
||||
RETURNS public.ak_users
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
DECLARE
|
||||
v_username text := coalesce(p_username, split_part(p_email, '@', 1), 'user');
|
||||
v_result public.ak_users;
|
||||
BEGIN
|
||||
-- 插入或更新用户资料
|
||||
INSERT INTO public.ak_users (id, email, username)
|
||||
VALUES (p_user_id, p_email, v_username)
|
||||
ON CONFLICT (id) DO UPDATE
|
||||
SET
|
||||
email = excluded.email,
|
||||
username = coalesce(excluded.username, ak_users.username)
|
||||
RETURNING * INTO v_result;
|
||||
|
||||
RETURN v_result;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- 函数2:供 auth.users 触发器使用,自动创建用户资料
|
||||
CREATE OR REPLACE FUNCTION public.handle_new_user()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
BEGIN
|
||||
BEGIN
|
||||
INSERT INTO public.ak_users (id, email, username)
|
||||
VALUES (NEW.id, NEW.email, COALESCE(SPLIT_PART(NEW.email, '@', 1), 'user'))
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
-- 重要:不要因为业务表写入失败而阻断 auth.users 的注册事务
|
||||
RAISE WARNING 'handle_new_user failed: %', SQLERRM;
|
||||
END;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- ============================================
|
||||
-- 4. 函数授权
|
||||
-- ============================================
|
||||
|
||||
-- upsert_user_profile 只允许已登录用户调用
|
||||
REVOKE ALL ON FUNCTION public.upsert_user_profile(uuid, text, text) FROM PUBLIC;
|
||||
GRANT EXECUTE ON FUNCTION public.upsert_user_profile(uuid, text, text) TO authenticated;
|
||||
|
||||
-- handle_new_user 是触发器函数,由系统内部调用,无需对任何角色授权
|
||||
35
pages/user/test/USER_AUTH_TEST_DATA.sql
Normal file
35
pages/user/test/USER_AUTH_TEST_DATA.sql
Normal file
@@ -0,0 +1,35 @@
|
||||
-- ============================================
|
||||
-- 用户登录 / 注册 测试数据
|
||||
-- 位置:pages/user/test/USER_AUTH_TEST_DATA.sql
|
||||
-- 用途:
|
||||
-- - 准备少量业务侧测试数据,方便前端联调登录/个人中心
|
||||
-- - 注意:Supabase 的 auth.users 建议通过实际“注册”流程生成,这里不直接插入
|
||||
-- ============================================
|
||||
|
||||
-- 1. 如果你已经通过注册页创建了账号(auth.users 中有记录),
|
||||
-- 可以在 ak_users 中补充一条测试资料:
|
||||
-- 把 '00000000-0000-0000-0000-000000000001' 替换成自己 auth.users 里的 id。
|
||||
|
||||
insert into public.ak_users (id, username, email, role)
|
||||
values
|
||||
('00000000-0000-0000-0000-000000000001', 'demo_user', 'demo@example.com', 'analyst')
|
||||
on conflict (id) do update
|
||||
set
|
||||
username = excluded.username,
|
||||
email = excluded.email,
|
||||
role = excluded.role;
|
||||
|
||||
-- 2. 可选:补充 users / user_sessions 基础数据,方便分析模块统计演示
|
||||
|
||||
insert into public.users (id, email, nickname)
|
||||
values
|
||||
('00000000-0000-0000-0000-000000000001', 'demo@example.com', 'Demo 分析师')
|
||||
on conflict (id) do update
|
||||
set
|
||||
email = excluded.email,
|
||||
nickname = excluded.nickname;
|
||||
|
||||
insert into public.user_sessions (user_id, session_token, is_active)
|
||||
values
|
||||
('00000000-0000-0000-0000-000000000001', 'demo-session-token', true);
|
||||
|
||||
89
pages/user/test/USER_AUTH_TRIGGER.sql
Normal file
89
pages/user/test/USER_AUTH_TRIGGER.sql
Normal file
@@ -0,0 +1,89 @@
|
||||
-- ============================================
|
||||
-- 自动创建 ak_users 记录的触发器
|
||||
-- ============================================
|
||||
-- 位置:pages/user/test/USER_AUTH_TRIGGER.sql
|
||||
-- 用途:当 auth.users 表中创建新用户时,自动在 ak_users 表中创建对应的业务资料记录
|
||||
--
|
||||
-- 执行方式:
|
||||
-- 在 Supabase Dashboard 的 SQL Editor 中执行此文件
|
||||
-- 需要 superuser 权限(Dashboard 默认有)
|
||||
-- ============================================
|
||||
|
||||
-- 触发器函数 `public.handle_new_user()` 的定义在 `USER_AUTH_SCHEMA.sql` 中完成。
|
||||
-- 这里仅负责在 auth.users 上创建触发器(避免重复定义函数导致版本不一致)。
|
||||
|
||||
-- 兼容处理:如果 `public.handle_new_user()` 尚未创建(例如先执行了本文件),则在此处补齐创建。
|
||||
-- 说明:这里采用“存在则跳过”的创建方式,不会覆盖已有实现。
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_proc p
|
||||
JOIN pg_namespace n ON n.oid = p.pronamespace
|
||||
WHERE n.nspname = 'public'
|
||||
AND p.proname = 'handle_new_user'
|
||||
) THEN
|
||||
EXECUTE $fn$
|
||||
CREATE FUNCTION public.handle_new_user()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $body$
|
||||
BEGIN
|
||||
BEGIN
|
||||
INSERT INTO public.ak_users (id, email, username)
|
||||
VALUES (
|
||||
NEW.id,
|
||||
NEW.email,
|
||||
COALESCE(SPLIT_PART(NEW.email, '@', 1), 'user')
|
||||
)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
RAISE WARNING 'handle_new_user failed: %', SQLERRM;
|
||||
END;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$body$;
|
||||
$fn$;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- 在 auth.users 表上创建触发器
|
||||
-- 注意:这个触发器会在每次 auth.users 插入新记录时自动执行
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'on_auth_user_created') THEN
|
||||
EXECUTE 'CREATE TRIGGER on_auth_user_created AFTER INSERT ON auth.users FOR EACH ROW EXECUTE FUNCTION public.handle_new_user()';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ============================================
|
||||
-- 验证触发器
|
||||
-- ============================================
|
||||
-- 执行以下查询验证触发器是否创建成功:
|
||||
-- SELECT * FROM pg_trigger WHERE tgname = 'on_auth_user_created';
|
||||
-- SELECT * FROM pg_proc WHERE proname = 'handle_new_user';
|
||||
|
||||
-- ============================================
|
||||
-- 测试触发器(可选)
|
||||
-- ============================================
|
||||
-- 注意:以下测试代码会创建一个测试用户,执行前请确认
|
||||
--
|
||||
-- 1. 通过 Supabase Auth API 注册一个新用户
|
||||
-- 2. 检查 ak_users 表中是否自动创建了对应的记录
|
||||
--
|
||||
-- 或者手动测试(需要 admin 权限):
|
||||
-- INSERT INTO auth.users (id, email, encrypted_password, email_confirmed_at, created_at, updated_at)
|
||||
-- VALUES (
|
||||
-- gen_random_uuid(),
|
||||
-- 'test@example.com',
|
||||
-- crypt('password', gen_salt('bf')),
|
||||
-- NOW(),
|
||||
-- NOW(),
|
||||
-- NOW()
|
||||
-- );
|
||||
--
|
||||
-- 然后检查 ak_users 表:
|
||||
-- SELECT * FROM ak_users WHERE email = 'test@example.com';
|
||||
Reference in New Issue
Block a user