更新
This commit is contained in:
1 parent
8fc52c574f
commit
fb04d66e61
7 files changed
+284
-9
No files matched your search
@@ -130,6 +130,67 @@ func (c *AppAuthController) GetCurrentUser() {
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateCurrentUserProfile 修改当前用户资料(姓名 / 邮箱)。
|
||||
// 手机号由微信验证并作为登录账号,不开放修改;同步统一身份与当前企业绑定表,
|
||||
// 避免下次登录或切换企业后又读到旧的姓名。
|
||||
func (c *AppAuthController) UpdateCurrentUserProfile() {
|
||||
claims, err := identity.Claims(c.Ctx.Request.Header.Get("Authorization"), identity.EndBackend, identity.EndApp)
|
||||
if err != nil {
|
||||
code := identity.Status(err)
|
||||
c.serveJSON(map[string]interface{}{"code": code, "msg": err.Error()})
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
body := c.Ctx.Input.RequestBody
|
||||
if len(body) == 0 {
|
||||
body, _ = io.ReadAll(c.Ctx.Request.Body)
|
||||
}
|
||||
if err := json.Unmarshal(body, &req); err != nil {
|
||||
c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"})
|
||||
return
|
||||
}
|
||||
name := strings.TrimSpace(req.Name)
|
||||
email := strings.TrimSpace(req.Email)
|
||||
if name == "" {
|
||||
c.serveJSON(map[string]interface{}{"code": 400, "msg": "请填写姓名"})
|
||||
return
|
||||
}
|
||||
if len([]rune(name)) > 30 {
|
||||
c.serveJSON(map[string]interface{}{"code": 400, "msg": "姓名不能超过 30 个字符"})
|
||||
return
|
||||
}
|
||||
if email != "" && (!strings.Contains(email, "@") || len(email) > 128) {
|
||||
c.serveJSON(map[string]interface{}{"code": 400, "msg": "邮箱格式不正确"})
|
||||
return
|
||||
}
|
||||
|
||||
uid := services.ResolveTenantUID(uint64(claims.TenantId), uint64(claims.UserID))
|
||||
num, err := models.Orm.QueryTable(new(models.SystemTenantUser)).
|
||||
Filter("uid", uid).
|
||||
Filter("tid", claims.TenantId).
|
||||
Update(map[string]interface{}{"name": name, "email": email})
|
||||
if err != nil || num == 0 {
|
||||
c.serveJSON(map[string]interface{}{"code": 400, "msg": "未找到当前企业下的用户资料"})
|
||||
return
|
||||
}
|
||||
_, _ = models.Orm.QueryTable(new(models.AuthIdentity)).
|
||||
Filter("id", uint64(claims.UserID)).
|
||||
Update(map[string]interface{}{"nickname": name, "email": email})
|
||||
_, _ = models.Orm.QueryTable(new(models.AuthTenantUser)).
|
||||
Filter("identity_id", uint64(claims.UserID)).
|
||||
Filter("tid", claims.TenantId).
|
||||
Update(map[string]interface{}{"name": name, "email": email})
|
||||
|
||||
c.serveJSON(map[string]interface{}{
|
||||
"code": 200,
|
||||
"msg": "资料已更新",
|
||||
"data": map[string]interface{}{"name": name, "email": email},
|
||||
})
|
||||
}
|
||||
|
||||
// SendLoginCode 发送App端登录验证码
|
||||
func (c *AppAuthController) SendLoginCode() {
|
||||
var req struct {
|
||||
|
||||
@@ -54,6 +54,8 @@ func Register() {
|
||||
|
||||
// 当前用户信息
|
||||
beego.Router("/api/currentUser", &controllers.AppAuthController{}, "get:GetCurrentUser")
|
||||
// 修改当前用户资料(姓名 / 邮箱;手机号由微信验证,不开放修改)
|
||||
beego.Router("/api/currentUser/profile", &controllers.AppAuthController{}, "post:UpdateCurrentUserProfile")
|
||||
|
||||
// 极验与登录验证配置
|
||||
beego.Router("/api/login/getGeetest3Infos", &controllers.AppAuthController{}, "get:GetGeetest3Infos")
|
||||
|
||||
@@ -208,6 +208,16 @@ export function getCurrentUser() {
|
||||
})
|
||||
}
|
||||
|
||||
/** 修改当前用户资料:{ name, email };手机号由微信验证,只读不传 */
|
||||
export function updateCurrentUserProfile(data) {
|
||||
return request({
|
||||
url: '/api/currentUser/profile',
|
||||
method: 'POST',
|
||||
data,
|
||||
silent: true
|
||||
})
|
||||
}
|
||||
|
||||
/** 是否开启登录人机/验证码校验 */
|
||||
export function getOpenVerify() {
|
||||
return request({
|
||||
|
||||
@@ -56,12 +56,19 @@ const DEFAULT_TABS = [
|
||||
{ name: 'home', text: '概览', icon: 'gauge' },
|
||||
{ name: 'functions', text: '功能', icon: 'table-cells' },
|
||||
{ name: 'todo', text: '待办', icon: 'circle-check' },
|
||||
{ name: 'mine', text: '我的', icon: 'user' }
|
||||
// 末位不再是「我的」,而是回平台(模块选择页);name 保留 mine 以兼容 current="mine"
|
||||
{ name: 'mine', text: '回平台', icon: 'arrow-left' }
|
||||
]
|
||||
|
||||
const list = computed(() => (props.tabs && props.tabs.length ? props.tabs : DEFAULT_TABS))
|
||||
|
||||
function onTap(name) {
|
||||
// 「回平台」:清空页面栈回到模块选择页。必须放在 current 判重之前,
|
||||
// 否则停在原「我的」页时点击会被判重拦下。
|
||||
if (name === 'mine' || name === 'platform') {
|
||||
uni.reLaunch({ url: '/pages/nav/nav' })
|
||||
return
|
||||
}
|
||||
if (name === props.current) return
|
||||
if (props.variant === 'switch') {
|
||||
emit('change', name)
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
<text class="head-eyebrow">WENXIN WORKSPACE</text>
|
||||
<text class="head-title">工作台</text>
|
||||
</view>
|
||||
<view v-if="!guest" class="account-chip"><FaIcon name="user" color="#3478f6" :size="15" /><text>{{ user.account || '我的账号' }}</text></view>
|
||||
<view v-else class="account-chip"><FaIcon name="user" color="#3478f6" :size="15" /><text>游客</text></view>
|
||||
<view v-if="!guest" class="account-chip" @tap="goProfile"><FaIcon name="user" color="#3478f6" :size="15" /><text>{{ user.account || '我的账号' }}</text></view>
|
||||
<view v-else class="account-chip" @tap="goProfile"><FaIcon name="user" color="#3478f6" :size="15" /><text>游客</text></view>
|
||||
</view>
|
||||
<view v-if="!guest" class="tenant-switch" @tap="chooseTenant">
|
||||
<view class="tenant-mark"><FaIcon name="building" color="#ffffff" :size="19" /></view>
|
||||
@@ -471,6 +471,15 @@ async function load(force = false) {
|
||||
|
||||
/* ==================== 交互 ==================== */
|
||||
|
||||
/** 点击右上角用户名 → 进入个人中心修改资料;未授权时提示先点模块授权手机号 */
|
||||
function goProfile() {
|
||||
if (guest.value) {
|
||||
uni.showToast({ title: '请先点击模块授权手机号', icon: 'none' })
|
||||
return
|
||||
}
|
||||
uni.navigateTo({ url: '/pages/profile/profile' })
|
||||
}
|
||||
|
||||
function chooseTenant() {
|
||||
const list = tenants.value
|
||||
if (list.length <= 1) { uni.showToast({ title: list.length ? '当前账号仅绑定一个企业' : '暂无可切换的企业', icon: 'none' })
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="profile-header" :style="headPad">
|
||||
<view class="header-top">
|
||||
<view class="back-platform" @tap="goBackPlatform">
|
||||
<FaIcon name="chevron-left" color="#ffffff" :size="14" />
|
||||
<text>回平台</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="profile-info">
|
||||
<view class="profile-avatar">
|
||||
<text class="avatar-text">{{ avatarText }}</text>
|
||||
@@ -58,16 +64,45 @@
|
||||
</view>
|
||||
|
||||
<AppTabbar current="profile" />
|
||||
|
||||
<!-- 编辑资料:姓名 / 邮箱可改,手机号由微信验证只读 -->
|
||||
<view v-if="editVisible" class="edit-mask" @tap="closeEdit">
|
||||
<view class="edit-sheet" @tap.stop>
|
||||
<view class="edit-head">
|
||||
<text class="edit-title">编辑资料</text>
|
||||
<text class="edit-close" @tap="closeEdit">取消</text>
|
||||
</view>
|
||||
<view class="edit-field">
|
||||
<text class="edit-label">姓名</text>
|
||||
<input v-model="editForm.name" class="edit-input" placeholder="请输入姓名" :maxlength="30" />
|
||||
</view>
|
||||
<view class="edit-field">
|
||||
<text class="edit-label">邮箱</text>
|
||||
<input v-model="editForm.email" class="edit-input" placeholder="选填" />
|
||||
</view>
|
||||
<view class="edit-field">
|
||||
<text class="edit-label">手机号</text>
|
||||
<view class="edit-readonly">
|
||||
<text>{{ phoneText }}</text>
|
||||
<text class="readonly-tag">微信验证,不可修改</text>
|
||||
</view>
|
||||
</view>
|
||||
<text v-if="editError" class="edit-error">{{ editError }}</text>
|
||||
<button class="edit-submit" :disabled="saving" @tap="saveProfile">
|
||||
{{ saving ? '保存中…' : '保存' }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { mpHeadPad } from '@/utils/mpHead.js'
|
||||
|
||||
const headPad = mpHeadPad()
|
||||
import { getUser, setUser, logout, isLoggedIn, isSSOEnabled, getToken } from '@/utils/auth.js'
|
||||
import { getCurrentUser, logoutApi, logoutUAC } from '@/api/auth.js'
|
||||
import { getCurrentUser, updateCurrentUserProfile, logoutApi, logoutUAC } from '@/api/auth.js'
|
||||
import { requestSubscribeNotice } from '@/utils/subscribe.js'
|
||||
import AppTabbar from '@/components/MainTabbar.vue'
|
||||
|
||||
@@ -133,14 +168,74 @@ async function loadUser() {
|
||||
|
||||
onMounted(() => {
|
||||
if (!isLoggedIn()) {
|
||||
uni.reLaunch({ url: '/pages/login/login' })
|
||||
// 新流程没有独立登录页:回平台点击模块即可重新授权手机号
|
||||
uni.reLaunch({ url: '/pages/nav/nav' })
|
||||
return
|
||||
}
|
||||
loadUser()
|
||||
})
|
||||
|
||||
/* ==================== 编辑资料 ==================== */
|
||||
const editVisible = ref(false)
|
||||
const saving = ref(false)
|
||||
const editError = ref('')
|
||||
const editForm = reactive({ name: '', email: '' })
|
||||
|
||||
// 手机号来自微信验证,只读展示(掩码),不随表单提交
|
||||
const phoneText = computed(() => {
|
||||
const u = user.value || {}
|
||||
const p = u.phone || (u.account && /^1\d{10}$/.test(String(u.account)) ? String(u.account) : '')
|
||||
return p ? String(p).replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') : '微信已验证'
|
||||
})
|
||||
|
||||
function onEdit() {
|
||||
uni.showToast({ title: '编辑资料', icon: 'none' })
|
||||
editForm.name = user.value?.nickname || user.value?.name || ''
|
||||
editForm.email = user.value?.email || ''
|
||||
editError.value = ''
|
||||
editVisible.value = true
|
||||
}
|
||||
|
||||
function closeEdit() {
|
||||
if (!saving.value) editVisible.value = false
|
||||
}
|
||||
|
||||
async function saveProfile() {
|
||||
if (saving.value) return
|
||||
const name = String(editForm.name || '').trim()
|
||||
const email = String(editForm.email || '').trim()
|
||||
if (!name) {
|
||||
editError.value = '请填写姓名'
|
||||
return
|
||||
}
|
||||
if (email && !email.includes('@')) {
|
||||
editError.value = '邮箱格式不正确'
|
||||
return
|
||||
}
|
||||
saving.value = true
|
||||
editError.value = ''
|
||||
try {
|
||||
const res = await updateCurrentUserProfile({ name, email })
|
||||
const next = {
|
||||
...(getUser() || {}),
|
||||
...(user.value || {}),
|
||||
...(res || {}),
|
||||
name,
|
||||
nickname: name,
|
||||
email
|
||||
}
|
||||
setUser(next)
|
||||
user.value = next
|
||||
editVisible.value = false
|
||||
uni.showToast({ title: '资料已更新', icon: 'success' })
|
||||
} catch (e) {
|
||||
editError.value = (e && e.message) || '保存失败,请重试'
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function goBackPlatform() {
|
||||
uni.reLaunch({ url: '/pages/nav/nav' })
|
||||
}
|
||||
|
||||
function onMenuTap(item) {
|
||||
@@ -172,7 +267,7 @@ function handleLogout() {
|
||||
}
|
||||
// 再清本地并跳转,避免接口超时/失败导致“退出无效”
|
||||
logout()
|
||||
uni.reLaunch({ url: '/pages/login/login' })
|
||||
uni.reLaunch({ url: '/pages/nav/nav' })
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -357,4 +452,94 @@ function handleLogout() {
|
||||
.bottom-space {
|
||||
height: 48rpx;
|
||||
}
|
||||
/* ============ 回平台 ============ */
|
||||
.header-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 24rpx;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.back-platform {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
padding: 10rpx 20rpx;
|
||||
border-radius: $radius-full;
|
||||
background: rgba(255, 255, 255, .18);
|
||||
color: #ffffff;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
/* ============ 编辑资料弹层 ============ */
|
||||
.edit-mask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
background: rgba(15, 23, 42, .48);
|
||||
@include mask-responsive;
|
||||
}
|
||||
.edit-sheet {
|
||||
width: 100%;
|
||||
padding: 28rpx $page-padding-x 40rpx;
|
||||
padding-bottom: calc(40rpx + env(safe-area-inset-bottom));
|
||||
box-sizing: border-box;
|
||||
border-radius: 32rpx 32rpx 0 0;
|
||||
background: #ffffff;
|
||||
@include sheet-responsive;
|
||||
}
|
||||
.edit-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
.edit-title { font-size: 32rpx; font-weight: 700; color: $color-text; }
|
||||
.edit-close { font-size: 26rpx; color: $color-text-muted; }
|
||||
.edit-field { margin-bottom: 26rpx; }
|
||||
.edit-label { display: block; margin-bottom: 12rpx; font-size: 25rpx; font-weight: 600; color: #334155; }
|
||||
.edit-input {
|
||||
height: 88rpx;
|
||||
padding: 0 22rpx;
|
||||
box-sizing: border-box;
|
||||
border: 1rpx solid #dce7f7;
|
||||
border-radius: $radius-md;
|
||||
background: #fbfdff;
|
||||
font-size: 28rpx;
|
||||
color: $color-text;
|
||||
}
|
||||
.edit-readonly {
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 22rpx;
|
||||
box-sizing: border-box;
|
||||
border: 1rpx solid #e2e8f0;
|
||||
border-radius: $radius-md;
|
||||
background: #f1f5f9;
|
||||
font-size: 27rpx;
|
||||
color: #475569;
|
||||
}
|
||||
.readonly-tag { font-size: 21rpx; color: #94a3b8; }
|
||||
.edit-error { display: block; margin: 4rpx 0 16rpx; font-size: 24rpx; color: $color-danger; }
|
||||
.edit-submit {
|
||||
height: 92rpx;
|
||||
line-height: 92rpx;
|
||||
margin-top: 12rpx;
|
||||
border: none;
|
||||
border-radius: $radius-full;
|
||||
background: linear-gradient(135deg, #60a5fa, #2563eb);
|
||||
color: #ffffff;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
|
||||
&::after { border: none; }
|
||||
&[disabled] { opacity: .6; }
|
||||
}
|
||||
</style>
|
||||
@@ -55,7 +55,8 @@ const TAB_ORDER = ['home', 'functions', 'mine']
|
||||
const tabs = [
|
||||
{ name: 'home', text: '首页', icon: 'house' },
|
||||
{ name: 'functions', text: '功能', icon: 'table-cells' },
|
||||
{ name: 'mine', text: '我的', icon: 'user' }
|
||||
// 末位为「回平台」:点击由 AppTabbar 统一 reLaunch 到 /pages/nav/nav
|
||||
{ name: 'mine', text: '回平台', icon: 'arrow-left' }
|
||||
]
|
||||
|
||||
const slug = ref('')
|
||||
|
||||
Reference in new issue
Block a user