完成修改密码功能
This commit is contained in:
@@ -658,6 +658,73 @@ export class AkSupa {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 模拟 supabase-js 的 auth 属性,提供认证相关方法
|
||||
*/
|
||||
get auth() : AkSupa {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验密码或登录(别名,兼容 supabase-js 命名)
|
||||
*/
|
||||
async signInWithPassword(credentials : UTSJSONObject) : Promise<AkSupaSignInResult> {
|
||||
const email = credentials.getString('email');
|
||||
const password = credentials.getString('password');
|
||||
if (email == null || password == null) {
|
||||
throw new Error('Email and password are required');
|
||||
}
|
||||
return await this.signIn(email, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户信息(如修改密码、修改元数据等)
|
||||
* 对应 Supabase Auth API: PUT /auth/v1/user (部分版本或Kong配置可能只支持PUT)
|
||||
*/
|
||||
async updateUser(attributes : UTSJSONObject) : Promise<AkReqResponse<any>> {
|
||||
const url = this.baseUrl + '/auth/v1/user';
|
||||
const token = AkReq.getToken();
|
||||
if (token == null || token == '') {
|
||||
throw new Error('未登录,无法更新用户信息');
|
||||
}
|
||||
|
||||
const headers = {
|
||||
apikey: this.apikey,
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
} as UTSJSONObject;
|
||||
|
||||
// 尝试先用 PUT 方法,因为部分环境 PATCH 报 405 Method Not Allowed
|
||||
const reqOptions : AkReqOptions = {
|
||||
url,
|
||||
method: 'PUT',
|
||||
headers,
|
||||
data: attributes,
|
||||
contentType: 'application/json'
|
||||
};
|
||||
|
||||
// updateUser 后,Supabase 会返回更新后的用户对象
|
||||
let res = await AkReq.request(reqOptions, false);
|
||||
|
||||
// 如果 PUT 也是 405,则尝试 PATCH
|
||||
if (res.status == 405) {
|
||||
reqOptions.method = 'PATCH';
|
||||
res = await AkReq.request(reqOptions, false);
|
||||
}
|
||||
|
||||
if (res.status >= 200 && res.status < 300 && res.data != null) {
|
||||
// 如果返回了新的 user 对象,更新本地缓存
|
||||
try {
|
||||
const newUser = new UTSJSONObject(res.data);
|
||||
this.user = newUser;
|
||||
if (this.session != null) {
|
||||
this.session!!.user = newUser;
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// [CHANGE][2026-01-30] hydrate user from /auth/v1/user when token exists in storage
|
||||
async hydrateSessionFromStorage() : Promise<boolean> {
|
||||
try {
|
||||
@@ -1052,7 +1119,7 @@ async delete(table : string, filter : string | null) : Promise<AkReqResponse<any
|
||||
} as UTSJSONObject,
|
||||
data: { refresh_token: this.session?.refresh_token } as UTSJSONObject,
|
||||
contentType: 'application/json'
|
||||
}, false);
|
||||
}, true);
|
||||
if (res.status == 200 && (res.data != null)) {
|
||||
const data = res.data as UTSJSONObject;
|
||||
const access_token = data.getString('access_token') ?? '';
|
||||
@@ -1187,4 +1254,15 @@ export function createClient(url : string, key : string) : AkSupa {
|
||||
return new AkSupa(url, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个临时 Supabase 客户端实例,用于校验密码等操作,不会污染全局 Token
|
||||
*/
|
||||
export function createTempClient(url : string, key : string) : AkSupa {
|
||||
const supa = new AkSupa(url, key);
|
||||
// 临时客户端不执行持久化逻辑,直接清空可能已加载的 session
|
||||
supa.session = null;
|
||||
supa.user = null;
|
||||
return supa;
|
||||
}
|
||||
|
||||
export default AkSupa;
|
||||
|
||||
112
pages/mall/admin/docs/多 Tab 环境下身份隔离方案与架构重构事故复盘.md
Normal file
112
pages/mall/admin/docs/多 Tab 环境下身份隔离方案与架构重构事故复盘.md
Normal file
@@ -0,0 +1,112 @@
|
||||
多 Tab 环境下身份隔离方案与架构重构事故复盘
|
||||
文档状态: 最终闭环归档
|
||||
涉及模块: 认证持久化基座、响应式全局状态树、UI 挂载生命周期、路由权限与菜单控制
|
||||
系统特性: uni-app x / Vue 3 / Supabase
|
||||
|
||||
1. 问题背景与最初验证方式
|
||||
本项目作为一个多角色的电商/管理后台(包含超级管理员 admin、商户 merchant),在日常开发和商务运营中,用户往往期望在同一浏览器内开启多个标签页(Tab)来验证不同角色的权限和数据。由于最近收到大量关于“账号串号、菜单错乱、UI 闪烁”的反馈,我们进行了系统的场景验证:
|
||||
|
||||
1.1 原始验证场景与表现
|
||||
多重登录覆盖:
|
||||
打开 Tab A 登录 admin,操作正常。
|
||||
打开 Tab B 登录 merchant,操作正常。
|
||||
异常: 切回 Tab A 并刷新页面,Tab A 的身份立刻倒戈,变成了 Tab B 的 merchant 账号。
|
||||
退出连坐效应:
|
||||
在 Tab A 点击退出登录,结果 Tab B 的请求也会随即报 Token 失败,双双被踢下线。
|
||||
视觉逻辑撕裂(缝合怪现象):
|
||||
某次同时开两个账号测试中发现,左侧菜单渲染的是 merchant 特有的那五个选项,但顶部 Header 上显示的仍是原来 admin 的用户名,右上角个人中心的下拉框又是第三个状态;页面呈现出极度的割裂感。
|
||||
刷新时的残影与突变:
|
||||
任何角色在按 F5 刷新时,页面总是先短暂显示为“未知用户”或硬编码的“admin”,左侧菜单只显示一个“Dashboard”,经过零点几秒的闪烁后,才猛然渲染出正确的菜单和用户名。
|
||||
以上异常现象之间具有高度联动性,这指向的并不是一个单纯的 UI 的 Bug,而是从 Token 凭证基座、缓存读写策略、到全局状态流转以及 UI 生命周期控制的一连串系统性设计缺陷。
|
||||
|
||||
2. 原始实现逻辑是什么
|
||||
在重修之前,我们需要剖析清楚旧系统中的“虚假身份链路树”,才能理解为什么这棵树结出了毒果:
|
||||
|
||||
登录写缓存(Token & 身份旁路):
|
||||
用户登录后,底层 HTTP 服务 (uni_modules/ak-req/ak-req.uts) 会调用 uni.setStorageSync 将 access_token 和 refresh_token 等写入缓存。
|
||||
业务层 login.uvue 怕以后拿不到数据,自作主张又通过 uni.setStorageSync('user_id', uid) 偷偷写了一份 ID;同时部分代码还保留了 uni.setStorageSync('merchant_id', 'admin') 的远古 mock。
|
||||
角色获取的投机取巧:
|
||||
role.uts 提供菜单与权限计算。当内存状态 (state.userProfile) 没有到位时,它立刻回退去读 uni.getStorageSync('adminRole') 或者旧版本的 admin_role。
|
||||
UI 数据的分裂抓取:
|
||||
左侧菜单:调用 getCurrentAdminRole()(受上面那条缓存影响)。
|
||||
顶栏 Header (AdminHeader.uvue):部分信息读取了 state.userProfile.username,但在为空时,代码里直接硬塞了一个 'admin' 字符串兜底。
|
||||
业务接口:比如商品管理 edit.uvue,发请求前不在意当前的系统 Session,而是直接通过 uni.getStorageSync('merchant_id') 去拼数据库条件。
|
||||
挂载生命周期:
|
||||
AdminLayout.uvue 无视了 aksupainstance 从远程获取并补齐 Session 的百毫秒级网络/IO时延。框架 onMounted 后 Vue 立刻画出 DOM,拿着空的数据源或错误的高优缓存“抢跑”渲染了页面。3. 原来为什么会出错:根因拆解
|
||||
通过追踪以上数据流向,我将系统的错误拆分为四个不同层次的叠加灾难。
|
||||
|
||||
3.1 共享存储导致的跨 Tab 串号 (Persistence Layer)
|
||||
H5 环境下,uni-app 的 uni.setStorageSync 会被编译为调用原生 Web 的 window.localStorage。这是一个同源域(跨 Tab)全面共享的持久化存储。
|
||||
|
||||
覆盖: 无论是 Tab A 还是 Tab B 操作,只要发网络请求产生的新 Token、新 role,全都会写入同一个全局大池子中。“后发者通吃”,全站缓存均反映最后一个登录的人。
|
||||
连坐: A 退出时清空 localStorage,B 在随后的验证或新请求中自然也就拿不到 Token 从而崩溃。
|
||||
3.2 Header、菜单、个人中心不是同一数据源 (State Layer)
|
||||
数据未能做到一元化集散管理。Vue 的响应式并没有覆盖整条身份获取通道。
|
||||
|
||||
菜单侧信赖了 localStorage 的快捷读取(读到了 B 的角色)。
|
||||
Header 侧信赖了尚未挂载完成的 state,由于请求未回,显示“未知用户”。
|
||||
结果导致同一时刻,一个页面上有三个不同渠道供出的不同身份残影。
|
||||
3.3 异步时序错位导致的抢跑渲染 (Runtime Layer)
|
||||
页面组件在试图绘制时,单例 aksupainstance 触发的 hydrateSessionFromStorage() 可能还没走完,state.userProfile 尚为空。
|
||||
Vue 在这短暂的“数据真空期”画完了错误的页面,等网络回调到来,再用 state 的更新硬性把画面抖回去,视觉上就是难看的闪烁。
|
||||
|
||||
3.4 共享 identity 旁路残留问题 (Business Context)
|
||||
就算底层修好了,页面业务里(比如商户端拉取商品列表的 index.uvue 或 supabaseService.uts 后备逻辑)依然散落着向 localStorage 直接索要 user_id 和 merchant_id 的不良习惯。一旦不铲除,在发业务请求时权限依然会被串号所污染。
|
||||
|
||||
4. 每一轮修复分别做了什么
|
||||
为了解决这四个层面的交织问题,我们实施了由表及里、从 UI 深入底层的五轮演进修复:
|
||||
|
||||
第一阶段:修显示层异常
|
||||
动作:尝试优化 AdminHeader.uvue 使得其响应更加自然。移除了硬编码的 'admin'。
|
||||
局限:只是治标不治本,缓解了文案假死的情况,但跨 Tab 强刷还是会变成别人的账号。这告诉我们,问题的根本源头在更下游。
|
||||
第二阶段:修角色缓存与 Header 错误兜底
|
||||
动作:我们在 store.uts 中扩建了响应式存储,不仅维护 userProfile,并列新增了底层的授权原身 authUser(直接映射 session 的基础 user 数据)。我们改造 AdminHeader.uvue 的 computed 属性,使其有了严密的瀑布降级:userProfile.username -> authUser.email -> authUser.phone -> truncate(id)。
|
||||
效果:UI 的渲染此时有了唯一根据。但一旦按下 F5 刷新依然会闪烁错位,并且跨 Tab 的数据霸权问题未解。
|
||||
第三阶段:修页面刷新时的阻断与身份恢复时序
|
||||
动作:为彻底消灭渲染抢跑和左侧菜单的“一帧残影”,我们在 AdminLayout.uvue 强力引入了阻断变量 isAuthReady = ref(false)。
|
||||
实现:单例 aksupainstance.uts 导出 export const supaReady = supaInstance.hydrateSessionFromStorage() 这个关键的顶层 await。只有当它从磁盘恢复甚至向后端校验完凭证后,isAuthReady 才会变成 True 放行子组件(包含菜单组件)挂载。
|
||||
效果:页面变得顺滑不再闪动,但核心发现爆出——A 的数据被验证仍然读取到了缓存池中 B 的身份。
|
||||
第四阶段:修多 Tab 会话隔离 (本役关键转折点)
|
||||
动作:剖开底层基石,拦截 ak-req.uts 所有对 Token 的操作,使用条件编译为 H5 环境单独开辟特区。
|
||||
实现:
|
||||
效果:Token 不再流入跨 Tab 共享的 localStorage,而是被封存入完全按浏览器标签页物理隔离的 sessionStorage!至此,“连坐”与“身份覆盖”奇迹般停止了,基础认证的主链路完成了隔离闭环。
|
||||
第五阶段:清理共享 identity 旁路
|
||||
动作:利用正则在全局排查出所有 getStorageSync('user_id')、merchant_id 的老旧黑魔法并肃清。
|
||||
执行细节:
|
||||
删除 login.uvue 里私自存放 user_id 的操作。
|
||||
修复 edit.uvue 中原有的越权隐患代码:
|
||||
const mId = uni.getStorageSync('merchant_id')
|
||||
--> 坚决替换为:const mId = supa.getSession().user?.id
|
||||
删除 aksupa.uts 中退出登录时清理全局变量的指令,保护了其他 Tab 的安危。
|
||||
效果:彻底拔除了所有潜伏在业务深处的侧漏点。user_id/merchant_id 现在只通过当前 Tab 的运行时计算得来。5. 最终闭环逻辑是什么
|
||||
此时整套前端系统的状态机和请求流向,已凝结为绝对干净的单向闭环。
|
||||
|
||||
【最终身份闭环链路图】
|
||||
|
||||
因果解答:
|
||||
|
||||
为什么始终是同一个账号? 因为 View 层的渲染全盘接驳到了 store.uts 这个单例枢纽。绝不会有组件私自“偷吃”外部缓存。
|
||||
为什么刷新不串号? Tab 刷新加载的第一秒,框架在 sessionStorage 摸到的全是属于自己这个窗口原教旨级的鉴权 Token,从而去 Supabase 换来了自身的资料。它哪怕想认识别的 Tab 也跨越不来。
|
||||
为什么 A 退出不影响 B? 移除底部的黑魔法连坐缓存后,A 退出仅仅清空其拥有的 sessionStorage 和当前内存;B 安然无恙。6. 为什么这种方式能够实现真正闭环
|
||||
从宏观建构看,这次并不是用“if/else”补坑或者临时写一套脚本洗数据造成的“表面修复”。它的彻底生效依赖四大架构调整:
|
||||
|
||||
共享状态源的强制切断机制(持久化隔离):这不单是将 localStorage 改为 sessionStorage,而是从最底盘网络客户端驱动发起底层劫持(条件编译拦截),确保了跨平台包容性不折损的同时完成了 H5 下环境级别的强制网闸。
|
||||
重塑出唯一真实数据源(SSOT - Single Source Of Truth):剥夺了之前 role.uts 中那些诸如 admin_role 等眼花缭乱却错误百出的缓存优先读取权利,改由 user_metadata 与远程 ak_users 库做绝对断言。从根本收缩了多数据源带来的状态裂变。
|
||||
引入 UI 的防抢跑闸门(同步阻断设计):通过 isAuthReady 这一全局屏障的引入,我们将应用初始化时异步加载造成的并发渲染冲突消灭。这类似于后端的资源自检网关(Health Check)。
|
||||
抹除缓存旁路的零容忍合规化(业务重耦合):清查了业务内残存直接对接 Storage 获取凭据的调用(如商品 edit.uvue)。强制业务层取身份只能向上游状态池发出“请示”,而非自己瞎去找“私房钱”;这是在业务流闭环。7. 最终验证结果与结论
|
||||
严格重走原始所有异常验证用例:
|
||||
|
||||
并行共存: Tab A 在 admin 权限全开操作,Tab B 在 merchant 权限受限下操作。各自跑通。
|
||||
单独或交叉强刷测试: 狂按 F5。A 的屏幕显示阻断遮罩 -> 解锁 -> 依然是完美的 Admin 环境;B 屏幕显示遮罩 -> 解锁 -> 依然是严谨的 Merchant 环境。无“串号重置”现象,无“未知用户”闪烁,无菜单“退化”现象。
|
||||
退登独立测试: 在 A 主动注销,系统清理当前 Tab 无任何抛错跳转回页;切回 B,点任何需要验证的请求,依然成功拉取属于 B 的数据。无“拉断电闸式连坐”。
|
||||
唯余一项边界规范注意:
|
||||
现代浏览器存在“复制当前标签页(Duplicate Tab)”功能。使用该原生的操作会附带将原封不动的 sessionStorage 克隆给新的克隆 Tab。这是浏览器的底层特性,也是合乎常理的双开机制(相当于镜像);普通“新打开一个空白标签访问系统”,将仍是未经登录的纯净环境,不会继承。
|
||||
|
||||
结论: 跨 Tab 身份冲突缺陷已经从根源和机制两端收口。
|
||||
|
||||
8. 最终总结
|
||||
回顾本次排查修复之旅,它极具警示意义:前端多重状态混乱与生命周期视觉闪烁,往往不是在组件内调调判断条件或给几个 timeout 能绕过去的,而是“设计架构中的状态流转通道出现了裂隙”。
|
||||
|
||||
问题本质:是系统缺少对环境隔离上下文(Web Context Scope)与 Vue 内存生命周期一致性的尊重。任由各个模块向大池子(localStorage)不负责任地伸手索取。
|
||||
机制收益:通过一套稳固的:“物理层面按 Tab 剥离” + “生命周期按重获取阻断” + “业务使用按单一中枢获取” + “清理法外旁路缓存” 组合拳,使得前端对登录信息的掌控进入了真正的自治安全期。
|
||||
长效架构思考:这套解法相比只在界面写个逻辑来挡一挡或者仅仅修复由于 undefined 导致的文案显示,它的层级更加高维。未来我们无论新增多少种中等角色抑或新增几十个需要拉取 user_id 的特殊业务,只要严格依从这条收拢唯一化的高速链路去调用接口去获取 Token/ID,在底层框架机制不变的前提下,类似数据倒流与覆盖的噩梦将再无将在工程中宣告绝迹。
|
||||
@@ -56,9 +56,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { reactive, computed, onMounted } from 'vue'
|
||||
import { reactive, computed, onMounted, ref } from 'vue'
|
||||
import { state, logout } from '@/utils/store.uts'
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import { createTempClient } from '@/components/supadb/aksupa.uts'
|
||||
import { SUPA_URL, SUPA_KEY } from '@/ak/config.uts'
|
||||
|
||||
const userAccount = computed((): string => state.userProfile.email || 'demo@example.com')
|
||||
const avatarUrl = computed((): string => state.userProfile.avatar_url || '/static/logo.png')
|
||||
@@ -70,119 +72,123 @@ const formData = reactive({
|
||||
confirmPassword: ''
|
||||
})
|
||||
|
||||
const isSubmitting = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
formData.name = state.userProfile.username || ''
|
||||
})
|
||||
|
||||
const onSubmit = async () => {
|
||||
if (formData.name.trim() == '') {
|
||||
if (isSubmitting.value) return
|
||||
|
||||
const nameTrimmed = formData.name.trim()
|
||||
if (nameTrimmed == '') {
|
||||
uni.showToast({ title: '姓名不能为空', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
// 修改密码逻辑
|
||||
if (formData.oldPassword != '' || formData.newPassword != '' || formData.confirmPassword != '') {
|
||||
if (formData.oldPassword == '') {
|
||||
uni.showToast({ title: '需要输入原始密码', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (formData.newPassword == '') {
|
||||
uni.showToast({ title: '新密码不能为空', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (formData.newPassword !== formData.confirmPassword) {
|
||||
uni.showToast({ title: '两次输入的新密码不一致', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (formData.newPassword === formData.oldPassword) {
|
||||
uni.showToast({ title: '新密码不能与原始密码相同', icon: 'none' })
|
||||
return
|
||||
isSubmitting.value = true
|
||||
uni.showLoading({ title: '正在处理...', mask: true })
|
||||
|
||||
try {
|
||||
// 1. 如果姓名有变化,更新姓名
|
||||
if (nameTrimmed !== state.userProfile.username) {
|
||||
const resName = await supa.from('ak_users').update({
|
||||
username: nameTrimmed
|
||||
}).eq('id', state.userProfile.id).execute()
|
||||
|
||||
if (resName.error != null) {
|
||||
throw new Error('更新姓名失败: ' + (resName.error?.message ?? '网络异常'))
|
||||
}
|
||||
// 更新本地状态
|
||||
state.userProfile.username = nameTrimmed
|
||||
}
|
||||
|
||||
uni.showLoading({ title: '验证并提交中...' })
|
||||
// 2. 处理密码修改
|
||||
const wantsChangePassword = formData.oldPassword != '' || formData.newPassword != '' || formData.confirmPassword != ''
|
||||
if (wantsChangePassword) {
|
||||
if (formData.oldPassword == '') {
|
||||
throw new Error('需要输入原始密码')
|
||||
}
|
||||
if (formData.newPassword == '') {
|
||||
throw new Error('新密码不能为空')
|
||||
}
|
||||
// 关键修复:确保比较前去除可能的多余空格,或至少确保值确实一致
|
||||
const newPwd = formData.newPassword
|
||||
const confPwd = formData.confirmPassword
|
||||
|
||||
if (newPwd !== confPwd) {
|
||||
throw new Error('两次输入的新密码不一致')
|
||||
}
|
||||
if (newPwd === formData.oldPassword) {
|
||||
throw new Error('新密码不能与原始密码相同')
|
||||
}
|
||||
|
||||
try {
|
||||
const email = state.userProfile.email
|
||||
if (email == '') {
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '账号缺失,无法验证', icon: 'none' })
|
||||
return
|
||||
throw new Error('账号信息缺失,请重新登录后再试')
|
||||
}
|
||||
|
||||
// 1. 验证原密码
|
||||
const resSignIn = await supa.auth.signInWithPassword({
|
||||
email: email,
|
||||
password: formData.oldPassword
|
||||
})
|
||||
// 使用临时客户端校验密码,不影响当前会话
|
||||
const tempSupa = createTempClient(SUPA_URL, SUPA_KEY)
|
||||
|
||||
if (resSignIn.error != null) {
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '原密码错误', icon: 'none' })
|
||||
return
|
||||
try {
|
||||
await tempSupa.auth.signInWithPassword({
|
||||
email: email,
|
||||
password: formData.oldPassword,
|
||||
options: { persistSession: false }
|
||||
} as UTSJSONObject)
|
||||
} catch (e : any) {
|
||||
const errMsg = e.message || '原始密码校验失败'
|
||||
if (errMsg.toLowerCase().includes('invalid login credentials')) {
|
||||
throw new Error('原始密码错误')
|
||||
}
|
||||
throw new Error(errMsg)
|
||||
}
|
||||
|
||||
// 2. 更新新密码
|
||||
// 密码更新(使用主客户端)
|
||||
const resUpdate = await supa.auth.updateUser({
|
||||
password: formData.newPassword
|
||||
})
|
||||
} as UTSJSONObject)
|
||||
|
||||
if (resUpdate.error != null) {
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '密码更新失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
// 3. 同时更新姓名
|
||||
if (formData.name !== state.userProfile.username) {
|
||||
await supa.from('ak_users').update({
|
||||
username: formData.name
|
||||
}).eq('id', state.userProfile.id)
|
||||
throw new Error('密码修改失败: ' + (resUpdate.error?.message ?? '网络异常'))
|
||||
}
|
||||
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '修改成功, 请重新登录', icon: 'success' })
|
||||
|
||||
// 退出登录
|
||||
// 退出登录并强制跳转
|
||||
setTimeout(() => {
|
||||
logout()
|
||||
uni.removeStorageSync('adminRole')
|
||||
// 同时清理管理端特定的角色缓存和状态
|
||||
try {
|
||||
const { clearAdminRoleCache } = require('@/layouts/admin/utils/role.uts')
|
||||
clearAdminRoleCache()
|
||||
} catch (e) {}
|
||||
|
||||
uni.removeStorageSync('adminRole') // 确保清理旧的缓存标识
|
||||
uni.removeStorageSync('token')
|
||||
uni.reLaunch({
|
||||
url: '/pages/user/login'
|
||||
})
|
||||
uni.reLaunch({ url: '/pages/user/login' })
|
||||
}, 1500)
|
||||
|
||||
return
|
||||
} catch (e) {
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '网络异常', icon: 'none' })
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 仅修改基本信息
|
||||
if (formData.name !== state.userProfile.username) {
|
||||
uni.showLoading({ title: '保存中...' })
|
||||
try {
|
||||
const res = await supa.from('ak_users').update({
|
||||
username: formData.name
|
||||
}).eq('id', state.userProfile.id)
|
||||
// 如果只是更新了姓名
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '基本信息已更新', icon: 'success' })
|
||||
isSubmitting.value = false
|
||||
|
||||
if (res.error != null) {
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '保存失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
state.userProfile.username = formData.name
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '保存成功', icon: 'success' })
|
||||
} catch (e) {
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '网络异常', icon: 'none' })
|
||||
}
|
||||
} else {
|
||||
uni.showToast({ title: '无修改内容', icon: 'none' })
|
||||
} catch (err : any) {
|
||||
uni.hideLoading()
|
||||
isSubmitting.value = false
|
||||
const msg = err.message || '操作失败,请重试'
|
||||
console.error('Submit user update failed:', err)
|
||||
uni.showToast({
|
||||
title: msg,
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user