初步完成merchant和admin不同role的展示内容逻辑
This commit is contained in:
36
fix_login.py
Normal file
36
fix_login.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import codecs
|
||||||
|
import re
|
||||||
|
|
||||||
|
path = r'd:\骅锋\mall\pages\user\login.uvue'
|
||||||
|
with codecs.open(path, 'r', 'utf-8') as f:
|
||||||
|
text = f.read()
|
||||||
|
|
||||||
|
# Define the old block using regex to handle variable whitespace/tabs
|
||||||
|
old_pattern = r"const merchantId = await checkMerchantAccess\(sessionUid,\s*account\.value\)\s*if\s*\(merchantId == null\)\s*\{\s*await supa\.signOut\(\)\s*logout\(\)\s*throw new Error\('您还没有注册商家端账户,快去注册一个'\)\s*\}\s*// 存入商家ID\s*uni\.setStorageSync\('merchant_id', merchantId\)"
|
||||||
|
|
||||||
|
new_code = '''const accessData = await checkAdminOrMerchantAccess(sessionUid, account.value)
|
||||||
|
if (accessData == null) {
|
||||||
|
await supa.signOut()
|
||||||
|
logout()
|
||||||
|
throw new Error('该账户无后台或商家端权限')
|
||||||
|
}
|
||||||
|
|
||||||
|
const currRole = accessData.getString('role')
|
||||||
|
const currId = accessData.getString('id')
|
||||||
|
uni.setStorageSync('adminRole', currRole)
|
||||||
|
|
||||||
|
if (currRole === 'merchant') {
|
||||||
|
uni.setStorageSync('merchant_id', currId)
|
||||||
|
} else {
|
||||||
|
uni.removeStorageSync('merchant_id')
|
||||||
|
}'''
|
||||||
|
|
||||||
|
# Replace it
|
||||||
|
if 'checkMerchantAccess' in text:
|
||||||
|
new_text = re.sub(old_pattern, new_code, text)
|
||||||
|
with codecs.open(path, 'w', 'utf-8') as f:
|
||||||
|
f.write(new_text)
|
||||||
|
print("Fixed checkMerchantAccess reference.")
|
||||||
|
else:
|
||||||
|
print("Not found.")
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ import {
|
|||||||
import type { TabItem } from '@/layouts/admin/store/adminNavStore.uts'
|
import type { TabItem } from '@/layouts/admin/store/adminNavStore.uts'
|
||||||
|
|
||||||
import { getComponent } from '@/layouts/admin/router/adminComponentMap.uts'
|
import { getComponent } from '@/layouts/admin/router/adminComponentMap.uts'
|
||||||
import { hasAdminModuleAccess } from '@/layouts/admin/utils/role.uts'
|
import { hasAdminModuleAccess, refreshAdminRole } from '@/layouts/admin/utils/role.uts'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
currentPage: {
|
currentPage: {
|
||||||
@@ -389,7 +389,8 @@ function onNotify(): void {
|
|||||||
|
|
||||||
let resizeTid: any = null
|
let resizeTid: any = null
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
|
await refreshAdminRole()
|
||||||
initNavState()
|
initNavState()
|
||||||
if (props.currentPage != '') {
|
if (props.currentPage != '') {
|
||||||
openRoute(props.currentPage as string)
|
openRoute(props.currentPage as string)
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ import {
|
|||||||
openRoute
|
openRoute
|
||||||
} from '@/layouts/admin/store/adminNavStore.uts'
|
} from '@/layouts/admin/store/adminNavStore.uts'
|
||||||
import { state, logout } from '@/utils/store.uts'
|
import { state, logout } from '@/utils/store.uts'
|
||||||
|
import { clearAdminRoleCache } from '@/layouts/admin/utils/role.uts'
|
||||||
|
|
||||||
const showUserMenu = ref(false)
|
const showUserMenu = ref(false)
|
||||||
const userName = computed((): string => state.userProfile.username || state.userProfile.email || 'admin')
|
const userName = computed((): string => state.userProfile.username || state.userProfile.email || 'admin')
|
||||||
@@ -116,7 +117,7 @@ function handleLogout(e: any) {
|
|||||||
success: (res) => {
|
success: (res) => {
|
||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
logout()
|
logout()
|
||||||
uni.removeStorageSync('adminRole')
|
clearAdminRoleCache()
|
||||||
uni.removeStorageSync('token')
|
uni.removeStorageSync('token')
|
||||||
// Force the layout cleanup to wait for the dialog to disappear
|
// Force the layout cleanup to wait for the dialog to disappear
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@@ -1,49 +1,136 @@
|
|||||||
// Admin role-based access control
|
import { state, getCurrentUser } from '@/utils/store.uts'
|
||||||
export function getCurrentAdminRole(): string {
|
import supa from '@/components/supadb/aksupainstance.uts'
|
||||||
// 从本地存储获取当前 role。为不影响全局,优先读 admin_role,默认降级到读全局 role/userInfo,再默认
|
|
||||||
// 根据实际系统中的 token/userInfo 结构可灵活调整
|
/**
|
||||||
const roleType = uni.getStorageSync('admin_role') as string
|
* 将任意角色类型的原始值格式化为标准化的应用角色
|
||||||
if (roleType && typeof roleType === 'string' && roleType.length > 0) {
|
*/
|
||||||
return roleType
|
export function normalizeRole(rawRole: any | null): string {
|
||||||
}
|
if (rawRole == null || rawRole === undefined) return 'unknown'
|
||||||
|
const roleStr = String(rawRole).trim().toLowerCase()
|
||||||
// 检查是否有关联 merchant_id 或者是存了全局 role
|
if (roleStr === 'admin') return 'admin'
|
||||||
const merchantId = uni.getStorageSync('merchant_id')
|
if (roleStr === 'merchant') return 'merchant'
|
||||||
if (merchantId) return 'merchant'
|
return 'unknown'
|
||||||
|
|
||||||
const globalRole = uni.getStorageSync('role') as string
|
|
||||||
if (globalRole && typeof globalRole === 'string' && globalRole.length > 0) {
|
|
||||||
return globalRole
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'admin' // 默认返回 admin 作为兜底(兼容原有全部显示逻辑)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取不同 role 允许访问的顶级模块 ID (主侧边栏的顶层菜单 id)
|
/**
|
||||||
export function getVisibleTopMenuIds(role: string): string[] {
|
* 判断是否为纯后台管理员
|
||||||
if (role === 'admin') {
|
*/
|
||||||
return ['home', 'user', 'order', 'product', 'marketing', 'distribution', 'kefu', 'finance', 'cms', 'decoration', 'app', 'setting', 'maintain']
|
export function isAdminRole(role: string): boolean {
|
||||||
|
return normalizeRole(role) === 'admin'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为商户角色
|
||||||
|
*/
|
||||||
|
export function isMerchantRole(role: string): boolean {
|
||||||
|
return normalizeRole(role) === 'merchant'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前的标准化角色 (同步方法)
|
||||||
|
*/
|
||||||
|
export function getCurrentAdminRole(): string {
|
||||||
|
// 1. 最高优先级:当前响应式内存 userProfile(已查数据库)
|
||||||
|
if (state.userProfile != null && state.userProfile!.role != null) {
|
||||||
|
const memRole = normalizeRole(state.userProfile!.role)
|
||||||
|
if (memRole === 'admin' || memRole === 'merchant') {
|
||||||
|
return memRole
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 缓存兜底:为了刷新页面时立刻渲染,读取最新的 adminRole 缓存
|
||||||
|
const cachedRole = uni.getStorageSync('adminRole')
|
||||||
|
if (cachedRole != null && cachedRole != '') {
|
||||||
|
const normCached = normalizeRole(cachedRole)
|
||||||
|
if (normCached === 'admin' || normCached === 'merchant') {
|
||||||
|
return normCached
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role === 'merchant') {
|
// 兼容旧的缓存字段以防万一
|
||||||
// merchant: 只能看到 主页、订单、商品、营销、财务
|
const oldCached = uni.getStorageSync('admin_role')
|
||||||
|
if (oldCached != null && oldCached != '') {
|
||||||
|
const normOld = normalizeRole(oldCached)
|
||||||
|
if (normOld === 'admin' || normOld === 'merchant') {
|
||||||
|
return normOld
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn('[AdminRole] 未能获取到有效的管理端角色,准备安全降级...')
|
||||||
|
return 'unknown'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理本地相关角色和管理端缓存 (登出时调用)
|
||||||
|
*/
|
||||||
|
export function clearAdminRoleCache(): void {
|
||||||
|
// 清理 admin 专属
|
||||||
|
uni.removeStorageSync('adminRole')
|
||||||
|
uni.removeStorageSync('admin_role')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验并写入最新的 adminRole (用于 Login 后或者 Layout 挂载时强制刷新)
|
||||||
|
*/
|
||||||
|
export async function refreshAdminRole(): Promise<string> {
|
||||||
|
const userStrProfile = await getCurrentUser()
|
||||||
|
let finalRole = 'unknown'
|
||||||
|
|
||||||
|
if (userStrProfile != null && userStrProfile.role != null) {
|
||||||
|
finalRole = normalizeRole(userStrProfile.role)
|
||||||
|
console.log('[AdminRole] 从 ak_users 读取真实身份成功:', finalRole)
|
||||||
|
} else {
|
||||||
|
// metadata fallback
|
||||||
|
const sessionInfo = supa.getSession()
|
||||||
|
if (sessionInfo.user != null) {
|
||||||
|
const meta = sessionInfo.user?.get("user_metadata") as UTSJSONObject | null
|
||||||
|
if (meta != null && meta.getString('role') != null) {
|
||||||
|
finalRole = normalizeRole(meta.getString('role'))
|
||||||
|
console.log('[AdminRole] 从 Auth Metadata 读取兜底身份:', finalRole)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finalRole !== 'unknown') {
|
||||||
|
uni.setStorageSync('adminRole', finalRole)
|
||||||
|
if (state.userProfile != null) {
|
||||||
|
state.userProfile!.role = finalRole
|
||||||
|
}
|
||||||
|
console.log('[AdminRole] 最新角色已写入状态和缓存:', finalRole)
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalRole
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getVisibleTopMenuIds(role: string): string[] {
|
||||||
|
const normRole = normalizeRole(role)
|
||||||
|
if (normRole === 'admin') {
|
||||||
|
return ['home', 'user', 'order', 'product', 'marketing', 'distribution', 'kefu', 'finance', 'cms', 'decoration', 'app', 'setting', 'maintain']
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normRole === 'merchant') {
|
||||||
return ['home', 'order', 'product', 'marketing', 'finance']
|
return ['home', 'order', 'product', 'marketing', 'finance']
|
||||||
}
|
}
|
||||||
|
|
||||||
// 其他 role: 安全兜底
|
|
||||||
return ['home']
|
return ['home']
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断是否有权限访问某模块或路由
|
export function hasAdminModuleAccess(moduleId: string | undefined): boolean {
|
||||||
export function hasAdminModuleAccess(moduleId: string | undefined): boolean {
|
if (!moduleId) return true
|
||||||
if (!moduleId) return true // 没有指定的通常认为是公共的,比如一些通用组件
|
|
||||||
|
|
||||||
const role = getCurrentAdminRole()
|
const role = getCurrentAdminRole()
|
||||||
if (role === 'admin') return true
|
const normRole = normalizeRole(role)
|
||||||
|
|
||||||
|
if (normRole === 'unknown') {
|
||||||
|
return moduleId === 'home'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normRole === 'admin') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// 对于 merchant 角色,允许其访问的顶级 menu id 及其所属路由
|
if (normRole === 'merchant') {
|
||||||
if (role === 'merchant') {
|
const allowed = ['home', 'order', 'product', 'marketing', 'finance']
|
||||||
const allowed = ['home', 'order', 'product', 'marketing', 'finance']
|
|
||||||
return allowed.includes(moduleId)
|
return allowed.includes(moduleId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,43 @@
|
|||||||
ak-req.uts:144
|
|
||||||
POST http://119.146.131.237:9126/auth/v1/signup 500 (Internal Server Error)
|
list.uvue:526 Coupon list initializing and fetching data...
|
||||||
register.uvue:221 📝 注册返回结果:
|
13
|
||||||
UTSJSONObject2 {code: '23505', message: 'duplicate key value violates unique constraint "ak_users_email_key"', detail: 'Key (email)=(admin@163.com) already exists.', _resolveKeyPath: ƒ, _getValue: ƒ, …}
|
role.uts:59 [AdminRole] 未能获取到有效的管理端角色,准备安全降级...
|
||||||
code
|
role.uts:59 [AdminRole] 未能获取到有效的管理端角色,准备安全降级...
|
||||||
|
login.uvue:373 signIn result:
|
||||||
|
AkSupaSignInResult {access_token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmO…xzZX0.YFcXQloKqsalFsOktCsDQUWPwvP8d_B58ss_SznxwZs', refresh_token: 'gdsl27rjhn62', expires_at: 1773284440, user: UTSJSONObject2, token_type: 'bearer', …}
|
||||||
|
access_token
|
||||||
:
|
:
|
||||||
"23505"
|
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmOTkyZGZmYS1hOGZkLTQ1YmItODY3MC02ZmVlNWE1YWU4NGQiLCJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNzczMjg0NDQwLCJpYXQiOjE3NzMyODA4NDAsImVtYWlsIjoiYWRtaW5AMTYzLmNvbSIsInBob25lIjoiIiwiYXBwX21ldGFkYXRhIjp7InByb3ZpZGVyIjoiZW1haWwiLCJwcm92aWRlcnMiOlsiZW1haWwiXX0sInVzZXJfbWV0YWRhdGEiOnsiZW1haWwiOiJhZG1pbkAxNjMuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInBob25lX3ZlcmlmaWVkIjpmYWxzZSwic3ViIjoiZjk5MmRmZmEtYThmZC00NWJiLTg2NzAtNmZlZTVhNWFlODRkIiwidXNlcl9yb2xlIjoibWVyY2hhbnQifSwicm9sZSI6ImF1dGhlbnRpY2F0ZWQiLCJhYWwiOiJhYWwxIiwiYW1yIjpbeyJtZXRob2QiOiJwYXNzd29yZCIsInRpbWVzdGFtcCI6MTc3MzI4MDg0MH1dLCJzZXNzaW9uX2lkIjoiMTJhMmEyZjgtZWU1ZC00OWZjLWIwOTAtOTBlNmIzNWMxZGJhIiwiaXNfYW5vbnltb3VzIjpmYWxzZX0.YFcXQloKqsalFsOktCsDQUWPwvP8d_B58ss_SznxwZs"
|
||||||
detail
|
expires_at
|
||||||
:
|
:
|
||||||
"Key (email)=(admin@163.com) already exists."
|
1773284440
|
||||||
message
|
expires_in
|
||||||
:
|
:
|
||||||
"duplicate key value violates unique constraint \"ak_users_email_key\""
|
3600
|
||||||
|
raw
|
||||||
|
:
|
||||||
|
UTSJSONObject2
|
||||||
|
access_token
|
||||||
|
:
|
||||||
|
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmOTkyZGZmYS1hOGZkLTQ1YmItODY3MC02ZmVlNWE1YWU4NGQiLCJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNzczMjg0NDQwLCJpYXQiOjE3NzMyODA4NDAsImVtYWlsIjoiYWRtaW5AMTYzLmNvbSIsInBob25lIjoiIiwiYXBwX21ldGFkYXRhIjp7InByb3ZpZGVyIjoiZW1haWwiLCJwcm92aWRlcnMiOlsiZW1haWwiXX0sInVzZXJfbWV0YWRhdGEiOnsiZW1haWwiOiJhZG1pbkAxNjMuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInBob25lX3ZlcmlmaWVkIjpmYWxzZSwic3ViIjoiZjk5MmRmZmEtYThmZC00NWJiLTg2NzAtNmZlZTVhNWFlODRkIiwidXNlcl9yb2xlIjoibWVyY2hhbnQifSwicm9sZSI6ImF1dGhlbnRpY2F0ZWQiLCJhYWwiOiJhYWwxIiwiYW1yIjpbeyJtZXRob2QiOiJwYXNzd29yZCIsInRpbWVzdGFtcCI6MTc3MzI4MDg0MH1dLCJzZXNzaW9uX2lkIjoiMTJhMmEyZjgtZWU1ZC00OWZjLWIwOTAtOTBlNmIzNWMxZGJhIiwiaXNfYW5vbnltb3VzIjpmYWxzZX0.YFcXQloKqsalFsOktCsDQUWPwvP8d_B58ss_SznxwZs"
|
||||||
|
expires_at
|
||||||
|
:
|
||||||
|
1773284440
|
||||||
|
expires_in
|
||||||
|
:
|
||||||
|
3600
|
||||||
|
refresh_token
|
||||||
|
:
|
||||||
|
"gdsl27rjhn62"
|
||||||
|
token_type
|
||||||
|
:
|
||||||
|
"bearer"
|
||||||
|
user
|
||||||
|
:
|
||||||
|
UTSJSONObject2 {id: 'f992dffa-a8fd-45bb-8670-6fee5a5ae84d', aud: 'authenticated', role: 'authenticated', email: 'admin@163.com', email_confirmed_at: '2026-03-12T01:25:56.424096Z', …}
|
||||||
|
weak_password
|
||||||
|
:
|
||||||
|
null
|
||||||
forEach
|
forEach
|
||||||
:
|
:
|
||||||
ƒ forEach(callback)
|
ƒ forEach(callback)
|
||||||
@@ -52,4 +79,205 @@ _resolveKeyPath
|
|||||||
ƒ _resolveKeyPath(keyPath)
|
ƒ _resolveKeyPath(keyPath)
|
||||||
[[Prototype]]
|
[[Prototype]]
|
||||||
:
|
:
|
||||||
Object
|
Object
|
||||||
|
refresh_token
|
||||||
|
:
|
||||||
|
"gdsl27rjhn62"
|
||||||
|
token_type
|
||||||
|
:
|
||||||
|
"bearer"
|
||||||
|
user
|
||||||
|
:
|
||||||
|
UTSJSONObject2 {id: 'f992dffa-a8fd-45bb-8670-6fee5a5ae84d', aud: 'authenticated', role: 'authenticated', email: 'admin@163.com', email_confirmed_at: '2026-03-12T01:25:56.424096Z', …}
|
||||||
|
$UTSMetadata$
|
||||||
|
:
|
||||||
|
(...)
|
||||||
|
[[Prototype]]
|
||||||
|
:
|
||||||
|
UTSType
|
||||||
|
login.uvue:176 🔍 开始校验商家端角色 -> UID: f992dffa-a8fd-45bb-8670-6fee5a5ae84d, Email: admin@163.com
|
||||||
|
login.uvue:186 ✅ 按 auth_id 匹配成功,role: admin
|
||||||
|
login.uvue:449 登录错误: Error: 您还没有注册商家端账户,快去注册一个
|
||||||
|
at login.uvue:403:10
|
||||||
|
at Generator.next (<anonymous>)
|
||||||
|
login.uvue:373 signIn result:
|
||||||
|
AkSupaSignInResult {access_token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmO…xzZX0.0RArWcn148XkRpW9C0vwboAcvpem4KRz6-OO0vAE4RU', refresh_token: 'kgzfaeokz4r2', expires_at: 1773284456, user: UTSJSONObject2, token_type: 'bearer', …}
|
||||||
|
access_token
|
||||||
|
:
|
||||||
|
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmOTkyZGZmYS1hOGZkLTQ1YmItODY3MC02ZmVlNWE1YWU4NGQiLCJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNzczMjg0NDU2LCJpYXQiOjE3NzMyODA4NTYsImVtYWlsIjoiYWRtaW5AMTYzLmNvbSIsInBob25lIjoiIiwiYXBwX21ldGFkYXRhIjp7InByb3ZpZGVyIjoiZW1haWwiLCJwcm92aWRlcnMiOlsiZW1haWwiXX0sInVzZXJfbWV0YWRhdGEiOnsiZW1haWwiOiJhZG1pbkAxNjMuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInBob25lX3ZlcmlmaWVkIjpmYWxzZSwic3ViIjoiZjk5MmRmZmEtYThmZC00NWJiLTg2NzAtNmZlZTVhNWFlODRkIiwidXNlcl9yb2xlIjoibWVyY2hhbnQifSwicm9sZSI6ImF1dGhlbnRpY2F0ZWQiLCJhYWwiOiJhYWwxIiwiYW1yIjpbeyJtZXRob2QiOiJwYXNzd29yZCIsInRpbWVzdGFtcCI6MTc3MzI4MDg1Nn1dLCJzZXNzaW9uX2lkIjoiN2NlZWYwZjQtNmNlOS00ZDE5LWJjYmItNzFhNDVmOTRiZTI2IiwiaXNfYW5vbnltb3VzIjpmYWxzZX0.0RArWcn148XkRpW9C0vwboAcvpem4KRz6-OO0vAE4RU"
|
||||||
|
expires_at
|
||||||
|
:
|
||||||
|
1773284456
|
||||||
|
expires_in
|
||||||
|
:
|
||||||
|
3600
|
||||||
|
raw
|
||||||
|
:
|
||||||
|
UTSJSONObject2
|
||||||
|
access_token
|
||||||
|
:
|
||||||
|
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmOTkyZGZmYS1hOGZkLTQ1YmItODY3MC02ZmVlNWE1YWU4NGQiLCJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNzczMjg0NDU2LCJpYXQiOjE3NzMyODA4NTYsImVtYWlsIjoiYWRtaW5AMTYzLmNvbSIsInBob25lIjoiIiwiYXBwX21ldGFkYXRhIjp7InByb3ZpZGVyIjoiZW1haWwiLCJwcm92aWRlcnMiOlsiZW1haWwiXX0sInVzZXJfbWV0YWRhdGEiOnsiZW1haWwiOiJhZG1pbkAxNjMuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInBob25lX3ZlcmlmaWVkIjpmYWxzZSwic3ViIjoiZjk5MmRmZmEtYThmZC00NWJiLTg2NzAtNmZlZTVhNWFlODRkIiwidXNlcl9yb2xlIjoibWVyY2hhbnQifSwicm9sZSI6ImF1dGhlbnRpY2F0ZWQiLCJhYWwiOiJhYWwxIiwiYW1yIjpbeyJtZXRob2QiOiJwYXNzd29yZCIsInRpbWVzdGFtcCI6MTc3MzI4MDg1Nn1dLCJzZXNzaW9uX2lkIjoiN2NlZWYwZjQtNmNlOS00ZDE5LWJjYmItNzFhNDVmOTRiZTI2IiwiaXNfYW5vbnltb3VzIjpmYWxzZX0.0RArWcn148XkRpW9C0vwboAcvpem4KRz6-OO0vAE4RU"
|
||||||
|
expires_at
|
||||||
|
:
|
||||||
|
1773284456
|
||||||
|
expires_in
|
||||||
|
:
|
||||||
|
3600
|
||||||
|
refresh_token
|
||||||
|
:
|
||||||
|
"kgzfaeokz4r2"
|
||||||
|
token_type
|
||||||
|
:
|
||||||
|
"bearer"
|
||||||
|
user
|
||||||
|
:
|
||||||
|
UTSJSONObject2 {id: 'f992dffa-a8fd-45bb-8670-6fee5a5ae84d', aud: 'authenticated', role: 'authenticated', email: 'admin@163.com', email_confirmed_at: '2026-03-12T01:25:56.424096Z', …}
|
||||||
|
weak_password
|
||||||
|
:
|
||||||
|
null
|
||||||
|
forEach
|
||||||
|
:
|
||||||
|
ƒ forEach(callback)
|
||||||
|
get
|
||||||
|
:
|
||||||
|
ƒ get(key)
|
||||||
|
getAny
|
||||||
|
:
|
||||||
|
ƒ getAny(key, defaultValue)
|
||||||
|
getArray
|
||||||
|
:
|
||||||
|
ƒ getArray(key, defaultValue)
|
||||||
|
getBoolean
|
||||||
|
:
|
||||||
|
ƒ getBoolean(key, defaultValue)
|
||||||
|
getJSON
|
||||||
|
:
|
||||||
|
ƒ getJSON(key, defaultValue)
|
||||||
|
getNumber
|
||||||
|
:
|
||||||
|
ƒ getNumber(key, defaultValue)
|
||||||
|
getString
|
||||||
|
:
|
||||||
|
ƒ getString(key, defaultValue)
|
||||||
|
set
|
||||||
|
:
|
||||||
|
ƒ set(key, value)
|
||||||
|
toJSON
|
||||||
|
:
|
||||||
|
undefined
|
||||||
|
toMap
|
||||||
|
:
|
||||||
|
ƒ toMap()
|
||||||
|
_getValue
|
||||||
|
:
|
||||||
|
ƒ _getValue(keyPath, defaultValue)
|
||||||
|
_resolveKeyPath
|
||||||
|
:
|
||||||
|
ƒ _resolveKeyPath(keyPath)
|
||||||
|
[[Prototype]]
|
||||||
|
:
|
||||||
|
Object
|
||||||
|
refresh_token
|
||||||
|
:
|
||||||
|
"kgzfaeokz4r2"
|
||||||
|
token_type
|
||||||
|
:
|
||||||
|
"bearer"
|
||||||
|
user
|
||||||
|
:
|
||||||
|
UTSJSONObject2
|
||||||
|
app_metadata
|
||||||
|
:
|
||||||
|
UTSJSONObject2 {provider: 'email', providers: Array(1), toJSON: undefined, _resolveKeyPath: ƒ, _getValue: ƒ, …}
|
||||||
|
aud
|
||||||
|
:
|
||||||
|
"authenticated"
|
||||||
|
confirmed_at
|
||||||
|
:
|
||||||
|
"2026-03-12T01:25:56.424096Z"
|
||||||
|
created_at
|
||||||
|
:
|
||||||
|
"2026-03-12T01:25:56.397092Z"
|
||||||
|
email
|
||||||
|
:
|
||||||
|
"admin@163.com"
|
||||||
|
email_confirmed_at
|
||||||
|
:
|
||||||
|
"2026-03-12T01:25:56.424096Z"
|
||||||
|
id
|
||||||
|
:
|
||||||
|
"f992dffa-a8fd-45bb-8670-6fee5a5ae84d"
|
||||||
|
identities
|
||||||
|
:
|
||||||
|
[UTSJSONObject2]
|
||||||
|
is_anonymous
|
||||||
|
:
|
||||||
|
false
|
||||||
|
last_sign_in_at
|
||||||
|
:
|
||||||
|
"2026-03-12T02:00:56.659679275Z"
|
||||||
|
phone
|
||||||
|
:
|
||||||
|
""
|
||||||
|
role
|
||||||
|
:
|
||||||
|
"authenticated"
|
||||||
|
updated_at
|
||||||
|
:
|
||||||
|
"2026-03-12T02:00:56.667158Z"
|
||||||
|
user_metadata
|
||||||
|
:
|
||||||
|
UTSJSONObject2 {email: 'admin@163.com', email_verified: true, phone_verified: false, sub: 'f992dffa-a8fd-45bb-8670-6fee5a5ae84d', user_role: 'merchant', …}
|
||||||
|
forEach
|
||||||
|
:
|
||||||
|
ƒ forEach(callback)
|
||||||
|
get
|
||||||
|
:
|
||||||
|
ƒ get(key)
|
||||||
|
getAny
|
||||||
|
:
|
||||||
|
ƒ getAny(key, defaultValue)
|
||||||
|
getArray
|
||||||
|
:
|
||||||
|
ƒ getArray(key, defaultValue)
|
||||||
|
getBoolean
|
||||||
|
:
|
||||||
|
ƒ getBoolean(key, defaultValue)
|
||||||
|
getJSON
|
||||||
|
:
|
||||||
|
ƒ getJSON(key, defaultValue)
|
||||||
|
getNumber
|
||||||
|
:
|
||||||
|
ƒ getNumber(key, defaultValue)
|
||||||
|
getString
|
||||||
|
:
|
||||||
|
ƒ getString(key, defaultValue)
|
||||||
|
set
|
||||||
|
:
|
||||||
|
ƒ set(key, value)
|
||||||
|
toJSON
|
||||||
|
:
|
||||||
|
undefined
|
||||||
|
toMap
|
||||||
|
:
|
||||||
|
ƒ toMap()
|
||||||
|
_getValue
|
||||||
|
:
|
||||||
|
ƒ _getValue(keyPath, defaultValue)
|
||||||
|
_resolveKeyPath
|
||||||
|
:
|
||||||
|
ƒ _resolveKeyPath(keyPath)
|
||||||
|
[[Prototype]]
|
||||||
|
:
|
||||||
|
Object
|
||||||
|
$UTSMetadata$
|
||||||
|
:
|
||||||
|
(...)
|
||||||
|
[[Prototype]]
|
||||||
|
:
|
||||||
|
UTSType
|
||||||
|
login.uvue:176 🔍 开始校验商家端角色 -> UID: f992dffa-a8fd-45bb-8670-6fee5a5ae84d, Email: admin@163.com
|
||||||
|
login.uvue:186 ✅ 按 auth_id 匹配成功,role: admin
|
||||||
|
login.uvue:449 登录错误: Error: 您还没有注册商家端账户,快去注册一个
|
||||||
|
at login.uvue:403:10
|
||||||
|
at Generator.next (<anonymous>)
|
||||||
|
|||||||
@@ -175,65 +175,54 @@ const isLoading = ref<boolean>(false)
|
|||||||
* 【核心函数】:登录成功后,多条件校验是否为商家角色
|
* 【核心函数】:登录成功后,多条件校验是否为商家角色
|
||||||
* 优先级: session_uid (auth_id) -> id -> normalized email
|
* 优先级: session_uid (auth_id) -> id -> normalized email
|
||||||
*/
|
*/
|
||||||
const checkMerchantAccess = async (uid: string, rawEmail: string) : Promise<string | null> => {
|
const checkAdminOrMerchantAccess = async (uid: string, rawEmail: string) : Promise<UTSJSONObject | null> => {
|
||||||
const email = rawEmail.trim().toLowerCase()
|
const email = rawEmail.trim().toLowerCase()
|
||||||
console.log(`🔍 开始校验商家端角色 -> UID: ${uid}, Email: ${email}`)
|
console.log(`🔍 开始校验后台或商家端角色 -> UID: ${uid}, Email: ${email}`)
|
||||||
|
|
||||||
try {
|
const parseRoleData = (dataArray: any | null): UTSJSONObject | null => {
|
||||||
// 1. 尝试按 auth_id 查询
|
if (Array.isArray(dataArray) && dataArray.length > 0) {
|
||||||
let res = await supa.from('ak_users').select('id, role').eq('auth_id', uid).execute()
|
const obj = dataArray[0] as UTSJSONObject
|
||||||
let dataArray = res.data
|
const role = obj.getString('role')
|
||||||
if (Array.isArray(dataArray) && dataArray.length > 0) {
|
const id = obj.getString('id')
|
||||||
const obj = dataArray[0] as UTSJSONObject
|
console.log('✅ 匹配成功,role:', role)
|
||||||
const role = obj.getString('role')
|
if ((role === 'merchant' || role === 'admin') && id != null) {
|
||||||
const id = obj.getString('id')
|
return { id, role } as UTSJSONObject
|
||||||
console.log('✅ 按 auth_id 匹配成功,role:', role)
|
}
|
||||||
if (role === 'merchant' && id != null) return id
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 尝试按 id 查询 (兼容老数据)
|
try {
|
||||||
res = await supa.from('ak_users').select('id, role').eq('id', uid).execute()
|
// 1. 尝试按 auth_id 查询
|
||||||
dataArray = res.data
|
let res = await supa.from('ak_users').select('id, role').eq('auth_id', uid).execute()
|
||||||
if (Array.isArray(dataArray) && dataArray.length > 0) {
|
let parsed = parseRoleData(res.data)
|
||||||
const obj = dataArray[0] as UTSJSONObject
|
if (parsed != null) return parsed
|
||||||
const role = obj.getString('role')
|
|
||||||
const id = obj.getString('id')
|
|
||||||
console.log('✅ 按 id 匹配成功,role:', role)
|
|
||||||
if (role === 'merchant' && id != null) return id
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 尝试按 email 兜底查询
|
// 2. 尝试按 id 查询 (兼容老数据)
|
||||||
if (email !== '') {
|
res = await supa.from('ak_users').select('id, role').eq('id', uid).execute()
|
||||||
res = await supa.from('ak_users').select('id, role').eq('email', email).execute()
|
parsed = parseRoleData(res.data)
|
||||||
dataArray = res.data
|
if (parsed != null) return parsed
|
||||||
|
|
||||||
if (Array.isArray(dataArray) && dataArray.length > 0) {
|
|
||||||
// 如果按邮箱查出来多条,可能存在脏数据,只取第一条并记录日志
|
|
||||||
if (dataArray.length > 1) {
|
|
||||||
console.error('⚠️ 警告: 按 email 查到多条 ak_users 记录,取第一条校验。Email:', email)
|
|
||||||
}
|
|
||||||
const obj = dataArray[0] as UTSJSONObject
|
|
||||||
const role = obj.getString('role')
|
|
||||||
const id = obj.getString('id')
|
|
||||||
console.log('✅ 按 email 匹配成功,role:', role)
|
|
||||||
if (role === 'merchant' && id != null) return id
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.error('❌ 未能在 ak_users 中找到该用户的任何记录')
|
// 3. 尝试按 email 兜底查询
|
||||||
// 查无此人,跑出自定义错误以与普通系统报错区分
|
if (email !== '') {
|
||||||
throw new Error('NOT_REGISTERED')
|
res = await supa.from('ak_users').select('id, role').eq('email', email).execute()
|
||||||
} catch (e) {
|
const dataArray = res.data
|
||||||
console.error('❌ 查询角色过程异常:', e)
|
if (Array.isArray(dataArray) && dataArray.length > 1) {
|
||||||
if (e instanceof Error && e.message === 'NOT_REGISTERED') {
|
console.error('⚠️ 警告: 按 email 查到多条 ak_users 记录,取第一条校验。Email:', email)
|
||||||
throw new Error('您还没有注册商家端账户,快去注册一个')
|
}
|
||||||
}
|
parsed = parseRoleData(dataArray)
|
||||||
// 真实的查询异常/RLS异常抛出,防止误会为"未注册"
|
if (parsed != null) return parsed
|
||||||
throw new Error('商家身份校验失败,请联系管理员检查用户数据')
|
}
|
||||||
}
|
|
||||||
|
console.error('❌ 未能在 ak_users 中找到该用户的有效角色记录')
|
||||||
|
throw new Error('NOT_REGISTERED')
|
||||||
|
} catch (e) {
|
||||||
|
console.error('❌ 查询角色过程异常:', e)
|
||||||
|
if (e instanceof Error && e.message === 'NOT_REGISTERED') {
|
||||||
|
throw new Error('该账户无后台或商家端权限,请联系管理员核对')
|
||||||
|
}
|
||||||
|
throw new Error('后台身份校验失败,请联系管理员检查用户数据')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const codeDisabled = ref<boolean>(false)
|
const codeDisabled = ref<boolean>(false)
|
||||||
@@ -400,15 +389,22 @@ const handleLogin = async () => {
|
|||||||
const sessionUser = result.user
|
const sessionUser = result.user
|
||||||
let sessionUid = sessionUser?.getString('id') ?? ''
|
let sessionUid = sessionUser?.getString('id') ?? ''
|
||||||
|
|
||||||
const merchantId = await checkMerchantAccess(sessionUid, account.value)
|
const accessData = await checkAdminOrMerchantAccess(sessionUid, account.value)
|
||||||
if (merchantId == null) {
|
if (accessData == null) {
|
||||||
await supa.signOut()
|
await supa.signOut()
|
||||||
logout()
|
logout()
|
||||||
throw new Error('您还没有注册商家端账户,快去注册一个')
|
throw new Error('该账户无后台或商家端权限')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 存入商家ID
|
const currRole = accessData.getString('role')
|
||||||
uni.setStorageSync('merchant_id', merchantId)
|
const currId = accessData.getString('id')
|
||||||
|
uni.setStorageSync('adminRole', currRole)
|
||||||
|
|
||||||
|
if (currRole === 'merchant') {
|
||||||
|
uni.setStorageSync('merchant_id', currId)
|
||||||
|
} else {
|
||||||
|
uni.removeStorageSync('merchant_id')
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({ title: '手机号密码登录功能开发中', icon: 'none' })
|
uni.showToast({ title: '手机号密码登录功能开发中', icon: 'none' })
|
||||||
return
|
return
|
||||||
|
|||||||
64
update_header.py
Normal file
64
update_header.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import codecs
|
||||||
|
|
||||||
|
path = r'd:\骅锋\mall\layouts\admin\components\AdminHeader.uvue'
|
||||||
|
with codecs.open(path, 'r', 'utf-8') as f:
|
||||||
|
text = f.read()
|
||||||
|
|
||||||
|
# Replace logout logic
|
||||||
|
old_logout = '''function handleLogout(e: any) {
|
||||||
|
if (e && typeof e.stopPropagation === 'function') {
|
||||||
|
e.stopPropagation()
|
||||||
|
}
|
||||||
|
showUserMenu.value = false
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '确定要退出登录吗?',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
logout()
|
||||||
|
uni.removeStorageSync('adminRole')
|
||||||
|
uni.removeStorageSync('token')
|
||||||
|
// Force the layout cleanup to wait for the dialog to disappear
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.reLaunch({
|
||||||
|
url: '/pages/user/login'
|
||||||
|
})
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}'''
|
||||||
|
|
||||||
|
new_logout = '''import { clearAdminRoleCache } from '@/layouts/admin/utils/role.uts'
|
||||||
|
|
||||||
|
function handleLogout(e: any) {
|
||||||
|
if (e && typeof e.stopPropagation === 'function') {
|
||||||
|
e.stopPropagation()
|
||||||
|
}
|
||||||
|
showUserMenu.value = false
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '确定要退出登录吗?',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
logout()
|
||||||
|
clearAdminRoleCache()
|
||||||
|
uni.removeStorageSync('token')
|
||||||
|
// Force the layout cleanup to wait for the dialog to disappear
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.reLaunch({
|
||||||
|
url: '/pages/user/login'
|
||||||
|
})
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}'''
|
||||||
|
|
||||||
|
if old_logout in text:
|
||||||
|
text = text.replace(old_logout, new_logout)
|
||||||
|
with codecs.open(path, 'w', 'utf-8') as f:
|
||||||
|
f.write(text)
|
||||||
|
print("AdminHeader logout logic updated.")
|
||||||
|
else:
|
||||||
|
print("AdminHeader target old snippet not found.")
|
||||||
17
update_header2.py
Normal file
17
update_header2.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import codecs
|
||||||
|
import re
|
||||||
|
|
||||||
|
path = r'd:\骅锋\mall\layouts\admin\components\AdminHeader.uvue'
|
||||||
|
with codecs.open(path, 'r', 'utf-8') as f:
|
||||||
|
text = f.read()
|
||||||
|
|
||||||
|
# Replace uni.removeStorageSync('adminRole') with clearAdminRoleCache()
|
||||||
|
# Add import if missing
|
||||||
|
if 'clearAdminRoleCache' not in text:
|
||||||
|
text = text.replace("import { state, logout } from '@/utils/store.uts'", "import { state, logout } from '@/utils/store.uts'\nimport { clearAdminRoleCache } from '@/layouts/admin/utils/role.uts'")
|
||||||
|
|
||||||
|
text = text.replace("uni.removeStorageSync('adminRole')", "clearAdminRoleCache()")
|
||||||
|
|
||||||
|
with codecs.open(path, 'w', 'utf-8') as f:
|
||||||
|
f.write(text)
|
||||||
|
print("Updated via direct string replace")
|
||||||
22
update_layout.py
Normal file
22
update_layout.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import codecs
|
||||||
|
|
||||||
|
path = r'd:\骅锋\mall\layouts\admin\AdminLayout.uvue'
|
||||||
|
with codecs.open(path, 'r', 'utf-8') as f:
|
||||||
|
text = f.read()
|
||||||
|
|
||||||
|
if 'refreshAdminRole' not in text:
|
||||||
|
text = text.replace("import { hasAdminModuleAccess } from '@/layouts/admin/utils/role.uts'", "import { hasAdminModuleAccess, refreshAdminRole } from '@/layouts/admin/utils/role.uts'")
|
||||||
|
|
||||||
|
insert_code = '''onMounted(async () => {
|
||||||
|
// 核心修复:挂载时强制刷新一下角色(异步获取 ak_users/metadata 并覆盖缓存)
|
||||||
|
await refreshAdminRole()
|
||||||
|
|
||||||
|
initNavState()'''
|
||||||
|
|
||||||
|
text = text.replace("onMounted(() => {\n initNavState()", insert_code)
|
||||||
|
|
||||||
|
with codecs.open(path, 'w', 'utf-8') as f:
|
||||||
|
f.write(text)
|
||||||
|
print("AdminLayout updated with refreshAdminRole.")
|
||||||
|
else:
|
||||||
|
print("AdminLayout already updated.")
|
||||||
20
update_layout2.py
Normal file
20
update_layout2.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import codecs
|
||||||
|
import re
|
||||||
|
|
||||||
|
path = r'd:\骅锋\mall\layouts\admin\AdminLayout.uvue'
|
||||||
|
with codecs.open(path, 'r', 'utf-8') as f:
|
||||||
|
text = f.read()
|
||||||
|
|
||||||
|
if 'refreshAdminRole' not in text:
|
||||||
|
text = text.replace("import { hasAdminModuleAccess", "import { hasAdminModuleAccess, refreshAdminRole")
|
||||||
|
|
||||||
|
# regex replace onMounted(() => { \s* initNavState()
|
||||||
|
text = re.sub(r'onMounted\(\(\) => \{\s+initNavState\(\)',
|
||||||
|
"onMounted(async () => {\n await refreshAdminRole()\n initNavState()",
|
||||||
|
text)
|
||||||
|
|
||||||
|
with codecs.open(path, 'w', 'utf-8') as f:
|
||||||
|
f.write(text)
|
||||||
|
print("AdminLayout regex updated.")
|
||||||
|
else:
|
||||||
|
print("Already there.")
|
||||||
19
update_layout3.py
Normal file
19
update_layout3.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import codecs
|
||||||
|
import re
|
||||||
|
|
||||||
|
path = r'd:\骅锋\mall\layouts\admin\AdminLayout.uvue'
|
||||||
|
with codecs.open(path, 'r', 'utf-8') as f:
|
||||||
|
text = f.read()
|
||||||
|
|
||||||
|
# Make sure we add the await
|
||||||
|
if 'await refreshAdminRole()' not in text:
|
||||||
|
text = re.sub(r'onMounted\(\(\) => \{\s+initNavState\(\)',
|
||||||
|
"onMounted(async () => {\n await refreshAdminRole()\n initNavState()",
|
||||||
|
text)
|
||||||
|
|
||||||
|
with codecs.open(path, 'w', 'utf-8') as f:
|
||||||
|
f.write(text)
|
||||||
|
print("Injected await refreshAdminRole()")
|
||||||
|
else:
|
||||||
|
print("Await already present.")
|
||||||
|
|
||||||
106
update_login.py
Normal file
106
update_login.py
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import codecs
|
||||||
|
import re
|
||||||
|
|
||||||
|
path = r'd:\骅锋\mall\pages\user\login.uvue'
|
||||||
|
with codecs.open(path, 'r', 'utf-8') as f:
|
||||||
|
text = f.read()
|
||||||
|
|
||||||
|
# Replace the checkMerchantAccess function and related logic
|
||||||
|
|
||||||
|
old_func_start = "const checkMerchantAccess = async (uid: string, rawEmail: string) : Promise<string | null> => {"
|
||||||
|
old_func_end_marker = "const codeDisabled = ref<boolean>(false)"
|
||||||
|
|
||||||
|
func_start_idx = text.find(old_func_start)
|
||||||
|
func_end_idx = text.find(old_func_end_marker)
|
||||||
|
|
||||||
|
if func_start_idx != -1 and func_end_idx != -1:
|
||||||
|
new_func = '''const checkAdminOrMerchantAccess = async (uid: string, rawEmail: string) : Promise<UTSJSONObject | null> => {
|
||||||
|
const email = rawEmail.trim().toLowerCase()
|
||||||
|
console.log(🔍 开始校验后台或商家端角色 -> UID: , Email: )
|
||||||
|
|
||||||
|
const parseRoleData = (dataArray: any | null): UTSJSONObject | null => {
|
||||||
|
if (Array.isArray(dataArray) && dataArray.length > 0) {
|
||||||
|
const obj = dataArray[0] as UTSJSONObject
|
||||||
|
const role = obj.getString('role')
|
||||||
|
const id = obj.getString('id')
|
||||||
|
console.log('✅ 匹配成功,role:', role)
|
||||||
|
if ((role === 'merchant' || role === 'admin') && id != null) {
|
||||||
|
return { id, role } as UTSJSONObject
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. 尝试按 auth_id 查询
|
||||||
|
let res = await supa.from('ak_users').select('id, role').eq('auth_id', uid).execute()
|
||||||
|
let parsed = parseRoleData(res.data)
|
||||||
|
if (parsed != null) return parsed
|
||||||
|
|
||||||
|
// 2. 尝试按 id 查询 (兼容老数据)
|
||||||
|
res = await supa.from('ak_users').select('id, role').eq('id', uid).execute()
|
||||||
|
parsed = parseRoleData(res.data)
|
||||||
|
if (parsed != null) return parsed
|
||||||
|
|
||||||
|
// 3. 尝试按 email 兜底查询
|
||||||
|
if (email !== '') {
|
||||||
|
res = await supa.from('ak_users').select('id, role').eq('email', email).execute()
|
||||||
|
const dataArray = res.data
|
||||||
|
if (Array.isArray(dataArray) && dataArray.length > 1) {
|
||||||
|
console.error('⚠️ 警告: 按 email 查到多条 ak_users 记录,取第一条校验。Email:', email)
|
||||||
|
}
|
||||||
|
parsed = parseRoleData(dataArray)
|
||||||
|
if (parsed != null) return parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error('❌ 未能在 ak_users 中找到该用户的有效角色记录')
|
||||||
|
throw new Error('NOT_REGISTERED')
|
||||||
|
} catch (e) {
|
||||||
|
console.error('❌ 查询角色过程异常:', e)
|
||||||
|
if (e instanceof Error && e.message === 'NOT_REGISTERED') {
|
||||||
|
throw new Error('该账户无后台或商家端权限,请联系管理员核对')
|
||||||
|
}
|
||||||
|
throw new Error('后台身份校验失败,请联系管理员检查用户数据')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
text = text[:func_start_idx] + new_func + text[func_end_idx:]
|
||||||
|
|
||||||
|
# Replace the usage in handleLogin
|
||||||
|
old_usage = ''' const merchantId = await checkMerchantAccess(sessionUid, account.value)
|
||||||
|
if (merchantId == null) {
|
||||||
|
await supa.signOut()
|
||||||
|
logout()
|
||||||
|
throw new Error('您还没有注册商家端账户,快去注册一个')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 存入商家ID
|
||||||
|
uni.setStorageSync('merchant_id', merchantId)'''
|
||||||
|
|
||||||
|
new_usage = ''' const accessData = await checkAdminOrMerchantAccess(sessionUid, account.value)
|
||||||
|
if (accessData == null) {
|
||||||
|
await supa.signOut()
|
||||||
|
logout()
|
||||||
|
throw new Error('该账户无后台或商家端权限')
|
||||||
|
}
|
||||||
|
|
||||||
|
const currRole = accessData.getString('role')
|
||||||
|
const currId = accessData.getString('id')
|
||||||
|
uni.setStorageSync('adminRole', currRole)
|
||||||
|
|
||||||
|
if (currRole === 'merchant') {
|
||||||
|
uni.setStorageSync('merchant_id', currId)
|
||||||
|
} else {
|
||||||
|
uni.removeStorageSync('merchant_id')
|
||||||
|
}'''
|
||||||
|
|
||||||
|
if 'checkMerchantAccess' in text:
|
||||||
|
print("WARNING: checkMerchantAccess still found after replace, let's substitute directly")
|
||||||
|
text = text.replace(old_usage, new_usage)
|
||||||
|
|
||||||
|
with codecs.open(path, 'w', 'utf-8') as f:
|
||||||
|
f.write(text)
|
||||||
|
|
||||||
|
print("Updated login.uvue successfully.")
|
||||||
146
update_role.py
Normal file
146
update_role.py
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
import codecs
|
||||||
|
|
||||||
|
content = '''import { state, getCurrentUser } from '@/utils/store.uts'
|
||||||
|
import supa from '@/components/supadb/aksupainstance.uts'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将任意角色类型的原始值格式化为标准化的应用角色
|
||||||
|
*/
|
||||||
|
export function normalizeRole(rawRole: any | null): string {
|
||||||
|
if (rawRole == null || rawRole === undefined) return 'unknown'
|
||||||
|
const roleStr = String(rawRole).trim().toLowerCase()
|
||||||
|
if (roleStr === 'admin') return 'admin'
|
||||||
|
if (roleStr === 'merchant') return 'merchant'
|
||||||
|
return 'unknown'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为纯后台管理员
|
||||||
|
*/
|
||||||
|
export function isAdminRole(role: string): boolean {
|
||||||
|
return normalizeRole(role) === 'admin'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为商户角色
|
||||||
|
*/
|
||||||
|
export function isMerchantRole(role: string): boolean {
|
||||||
|
return normalizeRole(role) === 'merchant'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前的标准化角色 (同步方法)
|
||||||
|
*/
|
||||||
|
export function getCurrentAdminRole(): string {
|
||||||
|
// 1. 最高优先级:当前响应式内存 userProfile(已查数据库)
|
||||||
|
if (state.userProfile != null && state.userProfile!.role != null) {
|
||||||
|
const memRole = normalizeRole(state.userProfile!.role)
|
||||||
|
if (memRole === 'admin' || memRole === 'merchant') {
|
||||||
|
return memRole
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 缓存兜底:为了刷新页面时立刻渲染,读取最新的 adminRole 缓存
|
||||||
|
const cachedRole = uni.getStorageSync('adminRole')
|
||||||
|
if (cachedRole != null && cachedRole != '') {
|
||||||
|
const normCached = normalizeRole(cachedRole)
|
||||||
|
if (normCached === 'admin' || normCached === 'merchant') {
|
||||||
|
return normCached
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容旧的缓存字段以防万一
|
||||||
|
const oldCached = uni.getStorageSync('admin_role')
|
||||||
|
if (oldCached != null && oldCached != '') {
|
||||||
|
const normOld = normalizeRole(oldCached)
|
||||||
|
if (normOld === 'admin' || normOld === 'merchant') {
|
||||||
|
return normOld
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn('[AdminRole] 未能获取到有效的管理端角色,准备安全降级...')
|
||||||
|
return 'unknown'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理本地相关角色和管理端缓存 (登出时调用)
|
||||||
|
*/
|
||||||
|
export function clearAdminRoleCache(): void {
|
||||||
|
// 清理 admin 专属
|
||||||
|
uni.removeStorageSync('adminRole')
|
||||||
|
uni.removeStorageSync('admin_role')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验并写入最新的 adminRole (用于 Login 后或者 Layout 挂载时强制刷新)
|
||||||
|
*/
|
||||||
|
export async function refreshAdminRole(): Promise<string> {
|
||||||
|
const userStrProfile = await getCurrentUser()
|
||||||
|
let finalRole = 'unknown'
|
||||||
|
|
||||||
|
if (userStrProfile != null && userStrProfile.role != null) {
|
||||||
|
finalRole = normalizeRole(userStrProfile.role)
|
||||||
|
console.log('[AdminRole] 从 ak_users 读取真实身份成功:', finalRole)
|
||||||
|
} else {
|
||||||
|
// metadata fallback
|
||||||
|
const sessionInfo = supa.getSession()
|
||||||
|
if (sessionInfo.user != null) {
|
||||||
|
const meta = sessionInfo.user?.get("user_metadata") as UTSJSONObject | null
|
||||||
|
if (meta != null && meta.getString('role') != null) {
|
||||||
|
finalRole = normalizeRole(meta.getString('role'))
|
||||||
|
console.log('[AdminRole] 从 Auth Metadata 读取兜底身份:', finalRole)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finalRole !== 'unknown') {
|
||||||
|
uni.setStorageSync('adminRole', finalRole)
|
||||||
|
if (state.userProfile != null) {
|
||||||
|
state.userProfile!.role = finalRole
|
||||||
|
}
|
||||||
|
console.log('[AdminRole] 最新角色已写入状态和缓存:', finalRole)
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalRole
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getVisibleTopMenuIds(role: string): string[] {
|
||||||
|
const normRole = normalizeRole(role)
|
||||||
|
if (normRole === 'admin') {
|
||||||
|
return ['home', 'user', 'order', 'product', 'marketing', 'distribution', 'kefu', 'finance', 'cms', 'decoration', 'app', 'setting', 'maintain']
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normRole === 'merchant') {
|
||||||
|
return ['home', 'order', 'product', 'marketing', 'finance']
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['home']
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasAdminModuleAccess(moduleId: string | undefined): boolean {
|
||||||
|
if (!moduleId) return true
|
||||||
|
|
||||||
|
const role = getCurrentAdminRole()
|
||||||
|
const normRole = normalizeRole(role)
|
||||||
|
|
||||||
|
if (normRole === 'unknown') {
|
||||||
|
return moduleId === 'home'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normRole === 'admin') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normRole === 'merchant') {
|
||||||
|
const allowed = ['home', 'order', 'product', 'marketing', 'finance']
|
||||||
|
return allowed.includes(moduleId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
|
||||||
|
with codecs.open(r'd:\骅锋\mall\layouts\admin\utils\role.uts', 'w', 'utf-8') as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
print("success!")
|
||||||
Reference in New Issue
Block a user