refactor(sponsor/account): 重写为与 manager/Accounts.vue 完全一致
裁掉 sponsor 专属功能: - 头像上传 (avatar-circle / uploadToOss) - 修改手机号 dialog (短信验证 / phoneSmsUuid) - 所属公司 (主账号/子账号/isOwner) - Tab 切换 (改单页 form) - EditPen icon / useAsyncLock / useUserStore 保留: - breadcrumb '首页 / 账号管理' - 姓名 + 手机号 (form.nickName / phonenumber) - 原密码 + 新密码 + 确认密码 - 保存 + 取消 - '密码长度 6-20 位' 提示 - 调 /system/user/profile + /system/user/profile/updatePwd UI 100% 对齐 manager/Accounts.vue: - page-card max-width 1200px - form class='account-form' max-width 600px - filter-tip padding-left 120px 跟 el-input 对齐
This commit is contained in:
@@ -1,508 +1,83 @@
|
||||
<template>
|
||||
<!--
|
||||
1:1 抄 doctor/Account.vue 容器 + 结构
|
||||
容器: <div class="page-card"> + 面包屑 + page-title
|
||||
Tab: 个人信息 + 修改密码
|
||||
个人信息 tab 包含: 头像块 + 基本资料 (form-grid 2 列)
|
||||
修改密码 tab: 原密码 / 新密码 / 确认密码
|
||||
修改手机号: 走独立 dialog + 短信验证
|
||||
-->
|
||||
<div class="page-card">
|
||||
<!-- 面包屑 -->
|
||||
<div class="breadcrumb">
|
||||
<span @click="goHome" class="link">首页</span>
|
||||
<span class="sep">/</span>
|
||||
<span>账号信息</span>
|
||||
</div>
|
||||
<div class="page-card sponsor-account">
|
||||
<div class="breadcrumb">首页 / 账号管理</div>
|
||||
|
||||
<!-- 页面标题 -->
|
||||
<h1 class="page-title">账号信息</h1>
|
||||
<el-form :model="form" label-width="120px" :rules="rules" ref="formRef" class="account-form">
|
||||
<el-form-item label="姓名" prop="nickName"><el-input v-model="form.nickName" placeholder="请输入姓名" /></el-form-item>
|
||||
<el-form-item label="手机号" prop="phonenumber"><el-input v-model="form.phonenumber" placeholder="请输入手机号" /></el-form-item>
|
||||
|
||||
<!-- Tab 导航 -->
|
||||
<el-tabs v-model="tab" class="account-tabs">
|
||||
<!-- 个人信息 -->
|
||||
<el-tab-pane label="个人信息" name="profile">
|
||||
<div class="card">
|
||||
<!-- 头像 + 用户信息 -->
|
||||
<div class="avatar-block">
|
||||
<div class="avatar-circle" :class="{ 'has-file': avatarUrl, 'is-uploading': avatarUploading }" @click="onAvatarClick">
|
||||
<img v-if="avatarUrl" :src="avatarUrl" class="avatar-img" />
|
||||
<template v-else>{{ avatarChar }}</template>
|
||||
<span class="avatar-tip">{{ avatarUploading ? '上传中...' : '点击更换' }}</span>
|
||||
</div>
|
||||
<div class="avatar-meta">
|
||||
<div class="name">{{ store.user?.nickName || store.user?.userName || '支持方账号' }}</div>
|
||||
<div class="role">{{ roleLabel }}</div>
|
||||
<div class="phone-row">
|
||||
<span class="phone-label">联系电话:</span>
|
||||
<span class="phone-value">{{ form.phone || '未设置' }}</span>
|
||||
<el-button class="btn-edit-phone" link type="primary" @click="openPhoneDialog">
|
||||
<el-icon><EditPen /></el-icon> 修改
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 基本资料 (2 列) -->
|
||||
<div class="section-title">基本资料</div>
|
||||
<el-form :model="form" class="form-grid" label-position="right" label-width="120px">
|
||||
<el-form-item label="账号">
|
||||
<el-input :model-value="store.user?.userName || '-'" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名" :required="true">
|
||||
<el-input v-model="form.name" placeholder="请填写姓名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="邮箱">
|
||||
<el-input :model-value="store.user?.email || '-'" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属公司">
|
||||
<el-input
|
||||
v-if="myCompany.isOwner === 1"
|
||||
v-model="form.orgName"
|
||||
placeholder="请填写所属公司"
|
||||
/>
|
||||
<el-input
|
||||
v-else-if="myCompany.orgName"
|
||||
:model-value="myCompany.orgName"
|
||||
disabled
|
||||
/>
|
||||
<span v-else style="color:#909399;font-size:13px">尚未分配所属公司</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="form-error">{{ saveError }}</div>
|
||||
<div class="form-actions">
|
||||
<el-button type="primary" :loading="saving" @click="onSave">保存</el-button>
|
||||
<el-button type="default" @click="onCancel">取消</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 修改密码 -->
|
||||
<el-tab-pane label="修改密码" name="password">
|
||||
<div class="card">
|
||||
<el-form :model="pwd" label-position="right" label-width="120px" class="password-form">
|
||||
<el-form-item label="原密码" :required="true">
|
||||
<el-input v-model="pwd.old" type="password" show-password placeholder="请输入原密码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码" :required="true">
|
||||
<el-input v-model="pwd.new" type="password" show-password placeholder="请输入新密码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码" :required="true">
|
||||
<el-input v-model="pwd.confirm" type="password" show-password placeholder="请再次输入新密码" />
|
||||
</el-form-item>
|
||||
<div class="form-error">{{ pwdError }}</div>
|
||||
<div class="form-actions">
|
||||
<el-button type="primary" @click="onChangePwd">保存</el-button>
|
||||
<el-button type="default" @click="onCancelPwd">取消</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<!-- 隐藏的 avatar 上传 input -->
|
||||
<input ref="avatarInput" type="file" hidden accept="image/*" @change="onAvatarFileChange" />
|
||||
|
||||
<!-- 修改手机号 dialog -->
|
||||
<el-dialog v-model="phoneDialogVisible" title="修改联系电话" width="420px" :close-on-click-modal="false">
|
||||
<el-form :model="phoneForm" label-width="100px">
|
||||
<el-form-item label="新手机号" :required="true">
|
||||
<el-input v-model="phoneForm.phone" placeholder="请输入新手机号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="短信验证码" :required="true">
|
||||
<div class="sms-row">
|
||||
<el-input v-model="phoneForm.smsCode" placeholder="请输入验证码" />
|
||||
<el-button class="btn-sms" :disabled="phoneSmsLocked || phoneSmsCountdown > 0" @click="sendSms">
|
||||
{{ phoneSmsLocked ? '发送中...' : phoneSmsCountdown > 0 ? `${phoneSmsCountdown}s 后重试` : '发送验证码' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<div class="form-error">{{ phoneError }}</div>
|
||||
<template #footer>
|
||||
<el-button type="default" @click="phoneDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="phoneSaving" @click="onChangePhone">确认</el-button>
|
||||
</template>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
<el-form-item label="原密码" prop="oldPassword"><el-input v-model="form.oldPassword" type="password" show-password placeholder="请输入原密码" /></el-form-item>
|
||||
<el-form-item label="新密码" prop="newPassword"><el-input v-model="form.newPassword" type="password" show-password placeholder="请输入新密码" /></el-form-item>
|
||||
<el-form-item label="确认密码" prop="confirmPassword"><el-input v-model="form.confirmPassword" type="password" show-password placeholder="请再次输入新密码" /></el-form-item>
|
||||
<div class="filter-tip">* 密码长度 6-20 位,支持数字、字母、特殊字符;留空表示不修改密码</div>
|
||||
<el-form-item><el-button type="primary" :loading="saving" @click="onSave">保存</el-button><el-button @click="onCancel">取消</el-button></el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { reactive, ref, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { EditPen } from '@element-plus/icons-vue'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import request from '@/utils/request'
|
||||
import { uploadToOss } from '@/utils/oss'
|
||||
import { useAsyncLock } from '@/utils/useAsyncLock'
|
||||
|
||||
const { locked: phoneSmsLocked, run: sendPhoneSmsOnce } = useAsyncLock()
|
||||
|
||||
const store = useUserStore()
|
||||
const tab = ref('profile')
|
||||
|
||||
// 角色 label
|
||||
const roleLabel = computed(() => {
|
||||
const r = store.user?.role
|
||||
if (r === 'sponsor') return '支持方'
|
||||
if (r === 'leader') return '项目主管'
|
||||
if (r === 'expert') return '专家'
|
||||
if (r === 'executor') return '执行方'
|
||||
if (r === 'admin') return '系统管理员'
|
||||
return r || '-'
|
||||
})
|
||||
|
||||
// 短信验证码状态 (修改手机号 dialog 用)
|
||||
const phoneDialogVisible = ref(false)
|
||||
const phoneSmsCountdown = ref(0)
|
||||
let phoneSmsTimer = null
|
||||
const phoneSmsUuid = ref('')
|
||||
const phoneForm = reactive({ phone: '', smsCode: '' })
|
||||
const phoneError = ref('')
|
||||
const phoneSaving = ref(false)
|
||||
|
||||
// 个人信息 form (sponsor 字段比 doctor 少, 没有科室/职称/银行等)
|
||||
const form = reactive({
|
||||
name: '',
|
||||
phone: '',
|
||||
orgName: '',
|
||||
licenseCertUrl: '',
|
||||
idCardFrontUrl: '',
|
||||
idCardBackUrl: ''
|
||||
})
|
||||
|
||||
const pwd = reactive({ old: '', new: '', confirm: '' })
|
||||
const pwdError = ref('')
|
||||
const saveError = ref('')
|
||||
const form = reactive({ nickName: '', phonenumber: '', oldPassword: '', newPassword: '', confirmPassword: '' })
|
||||
const profile = ref({})
|
||||
const saving = ref(false)
|
||||
// 当前登录 sponsor 的所属公司 (主账号可改, 子账号只读)
|
||||
// 从 /business/org/myCompany 取: { orgId, orgName, isOwner: 1|0 }
|
||||
const myCompany = reactive({ orgId: null, orgName: '', isOwner: 0 })
|
||||
const formRef = ref(null)
|
||||
|
||||
const avatarUrl = ref(store.user?.avatar || '')
|
||||
const avatarChar = computed(() => (form.name || store.user?.userName || '支').slice(0, 1))
|
||||
const avatarUploading = ref(false)
|
||||
const avatarInput = ref(null)
|
||||
|
||||
let snapshot = ref(null)
|
||||
|
||||
function onAvatarClick() {
|
||||
if (avatarUploading.value) return
|
||||
avatarInput.value?.click()
|
||||
const rules = {
|
||||
nickName: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
|
||||
phonenumber: [{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' }],
|
||||
oldPassword: [{ required: false }],
|
||||
newPassword: [{ min: 6, max: 20, message: '密码长度 6-20 位', trigger: 'blur' }],
|
||||
confirmPassword: [{
|
||||
validator: (rule, val, cb) => {
|
||||
if (form.newPassword && val !== form.newPassword) return cb(new Error('两次输入密码不一致'))
|
||||
cb()
|
||||
}, trigger: 'blur'
|
||||
}],
|
||||
}
|
||||
|
||||
async function onAvatarFileChange(e) {
|
||||
const file = e.target.files?.[0]
|
||||
if (!file) return
|
||||
e.target.value = ''
|
||||
if (!file.type.startsWith('image/')) {
|
||||
return ElMessage.warning('请选择图片文件')
|
||||
}
|
||||
if (file.size > 5 * 1024 * 1024) {
|
||||
return ElMessage.warning('图片大小不能超过 5MB')
|
||||
}
|
||||
avatarUploading.value = true
|
||||
async function loadProfile() {
|
||||
try {
|
||||
const url = await uploadToOss(file, 'ry8080/sponsor/avatar/')
|
||||
await request({
|
||||
url: '/system/user/profile/avatarUrl',
|
||||
method: 'put',
|
||||
data: { avatarUrl: url }
|
||||
})
|
||||
avatarUrl.value = url
|
||||
if (store.user) {
|
||||
store.user.avatar = url
|
||||
localStorage.setItem('ry_user', JSON.stringify(store.user))
|
||||
}
|
||||
ElMessage.success('头像更新成功')
|
||||
} catch (err) {
|
||||
ElMessage.error(err?.msg || '头像上传失败')
|
||||
} finally {
|
||||
avatarUploading.value = false
|
||||
}
|
||||
const { data } = await request({ url: '/system/user/profile', method: 'get' })
|
||||
profile.value = data || {}
|
||||
form.nickName = profile.value.nickName || ''
|
||||
form.phonenumber = profile.value.phonenumber || ''
|
||||
} catch { ElMessage.error('获取个人信息失败') }
|
||||
}
|
||||
|
||||
function goHome() {
|
||||
if (window.parent && window.parent.document.getElementById('contentFrame')) {
|
||||
window.parent.goHome?.()
|
||||
}
|
||||
}
|
||||
|
||||
async function loadUserProfile() {
|
||||
try {
|
||||
const res = await request({ url: '/system/user/profile', method: 'get' })
|
||||
const u = res.data
|
||||
if (!u) return
|
||||
if (store.user) {
|
||||
store.user.avatar = u.avatar || store.user.avatar
|
||||
store.user.userName = u.userName || store.user.userName
|
||||
store.user.nickName = u.nickName || store.user.nickName
|
||||
store.user.phonenumber = u.phonenumber || store.user.phonenumber
|
||||
store.user.email = u.email || store.user.email
|
||||
store.user.createTime = u.createTime || store.user.createTime
|
||||
localStorage.setItem('ry_user', JSON.stringify(store.user))
|
||||
}
|
||||
avatarUrl.value = u.avatar || avatarUrl.value
|
||||
form.name = u.nickName || store.user?.userName || ''
|
||||
form.phone = u.phonenumber || ''
|
||||
form.licenseCertUrl = u.licenseCertUrl || ''
|
||||
form.idCardFrontUrl = u.idCardFrontUrl || ''
|
||||
form.idCardBackUrl = u.idCardBackUrl || ''
|
||||
// 所属公司: 走 /business/org/myCompany, 主账号可改, 子账号只读
|
||||
try {
|
||||
const r = await request({ url: '/business/org/myCompany', method: 'get' })
|
||||
const c = (r.data && (r.data.data || r.data)) || {}
|
||||
myCompany.orgId = c.orgId || null
|
||||
myCompany.orgName = c.orgName || ''
|
||||
myCompany.isOwner = Number(c.isOwner) || 0
|
||||
form.orgName = myCompany.orgName
|
||||
} catch {
|
||||
myCompany.orgId = null
|
||||
myCompany.orgName = ''
|
||||
myCompany.isOwner = 0
|
||||
form.orgName = ''
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('loadUserProfile:', e?.msg)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadUserProfile()
|
||||
snapshot = ref(JSON.parse(JSON.stringify(form)))
|
||||
})
|
||||
|
||||
async function onSave() {
|
||||
saveError.value = ''
|
||||
if (!(form.name || '').toString().trim()) {
|
||||
saveError.value = '请填写姓名'
|
||||
return ElMessage.warning(saveError.value)
|
||||
}
|
||||
const sysPayload = {
|
||||
nickName: form.name,
|
||||
licenseCertUrl: form.licenseCertUrl,
|
||||
idCardFrontUrl: form.idCardFrontUrl,
|
||||
idCardBackUrl: form.idCardBackUrl
|
||||
if (form.newPassword && form.newPassword !== form.confirmPassword) {
|
||||
ElMessage.error('两次输入密码不一致'); return
|
||||
}
|
||||
await formRef.value.validate()
|
||||
saving.value = true
|
||||
try {
|
||||
await request({ url: '/system/user/profile', method: 'put', data: sysPayload })
|
||||
// 主账号: 同步更新所属公司名称到 biz_org
|
||||
if (myCompany.isOwner === 1 && myCompany.orgId && form.orgName !== myCompany.orgName) {
|
||||
await request({
|
||||
url: '/business/org',
|
||||
method: 'put',
|
||||
data: { orgId: myCompany.orgId, orgName: form.orgName }
|
||||
})
|
||||
myCompany.orgName = form.orgName
|
||||
await request({ url: '/system/user/profile', method: 'put', data: { nickName: form.nickName, phonenumber: form.phonenumber, sex: profile.value.sex } })
|
||||
if (form.newPassword) {
|
||||
await request({ url: '/system/user/profile/updatePwd', method: 'put', data: { oldPassword: form.oldPassword, newPassword: form.newPassword } })
|
||||
}
|
||||
if (store.user) {
|
||||
store.user.nickName = form.name
|
||||
localStorage.setItem('ry_user', JSON.stringify(store.user))
|
||||
}
|
||||
snapshot.value = JSON.parse(JSON.stringify(form))
|
||||
ElMessage.success('个人信息已保存')
|
||||
ElMessage.success('保存成功')
|
||||
form.oldPassword = ''; form.newPassword = ''; form.confirmPassword = ''
|
||||
loadProfile()
|
||||
} catch (e) {
|
||||
saveError.value = e?.msg || '保存失败'
|
||||
ElMessage.error(saveError.value)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
ElMessage.error(e?.msg || '保存失败')
|
||||
} finally { saving.value = false }
|
||||
}
|
||||
|
||||
function onCancel() {
|
||||
if (snapshot.value) {
|
||||
Object.assign(form, JSON.parse(JSON.stringify(snapshot.value)))
|
||||
}
|
||||
ElMessage.info('已恢复修改前内容')
|
||||
form.nickName = profile.value.nickName || ''
|
||||
form.phonenumber = profile.value.phonenumber || ''
|
||||
form.oldPassword = ''; form.newPassword = ''; form.confirmPassword = ''
|
||||
}
|
||||
|
||||
function onChangePwd() {
|
||||
pwdError.value = ''
|
||||
if (!pwd.old) return (pwdError.value = '请先填写原密码')
|
||||
if (pwd.new.length < 6) return (pwdError.value = '新密码长度不能少于 6 位')
|
||||
if (pwd.new !== pwd.confirm) return (pwdError.value = '两次输入的新密码不一致')
|
||||
request({
|
||||
url: '/system/user/profile/updatePwd',
|
||||
method: 'put',
|
||||
data: { oldPassword: pwd.old, newPassword: pwd.new }
|
||||
}).then(() => {
|
||||
ElMessage.success('密码修改成功')
|
||||
pwd.old = pwd.new = pwd.confirm = ''
|
||||
}).catch(e => {
|
||||
pwdError.value = e?.msg || '密码修改失败'
|
||||
ElMessage.error(pwdError.value)
|
||||
})
|
||||
}
|
||||
|
||||
function onCancelPwd() {
|
||||
pwd.old = pwd.new = pwd.confirm = ''
|
||||
pwdError.value = ''
|
||||
}
|
||||
|
||||
// 手机号 dialog
|
||||
function openPhoneDialog() {
|
||||
phoneForm.phone = ''
|
||||
phoneForm.smsCode = ''
|
||||
phoneError.value = ''
|
||||
phoneDialogVisible.value = true
|
||||
}
|
||||
|
||||
async function sendSms() {
|
||||
const phone = (phoneForm.phone || '').trim()
|
||||
if (!/^(1[0-9])\d{9}$/.test(phone)) {
|
||||
return ElMessage.warning('请输入正确的手机号')
|
||||
}
|
||||
await sendPhoneSmsOnce(async () => {
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/system/user/profile/sendSmsCode',
|
||||
method: 'post',
|
||||
params: { phone }
|
||||
})
|
||||
phoneSmsUuid.value = res.uuid || ''
|
||||
ElMessage.success('验证码已发送')
|
||||
phoneSmsCountdown.value = 60
|
||||
phoneSmsTimer = setInterval(() => {
|
||||
phoneSmsCountdown.value--
|
||||
if (phoneSmsCountdown.value <= 0) {
|
||||
clearInterval(phoneSmsTimer)
|
||||
phoneSmsTimer = null
|
||||
}
|
||||
}, 1000)
|
||||
} catch (e) {
|
||||
ElMessage.error(e?.msg || '发送失败')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function onChangePhone() {
|
||||
phoneError.value = ''
|
||||
if (!phoneForm.phone || !phoneForm.smsCode || !phoneSmsUuid.value) {
|
||||
return (phoneError.value = '请填写完整信息')
|
||||
}
|
||||
phoneSaving.value = true
|
||||
try {
|
||||
await request({
|
||||
url: '/system/user/profile/changePhone',
|
||||
method: 'post',
|
||||
data: {
|
||||
phone: phoneForm.phone,
|
||||
smsCode: phoneForm.smsCode,
|
||||
uuid: phoneSmsUuid.value
|
||||
}
|
||||
})
|
||||
form.phone = phoneForm.phone
|
||||
if (store.user) store.user.phonenumber = phoneForm.phone
|
||||
ElMessage.success('手机号修改成功')
|
||||
phoneDialogVisible.value = false
|
||||
} catch (e) {
|
||||
phoneError.value = e?.msg || '修改失败'
|
||||
} finally {
|
||||
phoneSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (phoneSmsTimer) clearInterval(phoneSmsTimer)
|
||||
})
|
||||
onMounted(loadProfile)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 1:1 抄 doctor/Account.vue */
|
||||
.page-card { padding: 16px; background: #fff; border-radius: 4px; border: 1px solid #ebeef5; }
|
||||
.breadcrumb { font-size: 13px; color: #909399; margin-bottom: 8px; }
|
||||
.breadcrumb .link { color: var(--brand-primary); cursor: pointer; }
|
||||
.breadcrumb .link:hover { color: var(--brand-primary-deep, var(--brand-primary)); text-decoration: underline; }
|
||||
.breadcrumb .sep { margin: 0 6px; }
|
||||
.page-title { font-size: 20px; font-weight: 600; color: #262626; margin: 0 0 16px; }
|
||||
|
||||
.account-tabs :deep(.el-tabs__header) { margin-bottom: 0; }
|
||||
.account-tabs :deep(.el-tabs__nav-wrap::after) { height: 1px; }
|
||||
.account-tabs :deep(.el-tabs__active-bar) { background-color: var(--brand-primary); }
|
||||
|
||||
.card {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 20px 24px;
|
||||
border: 1px solid #ebeef5;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
margin: 16px 0 12px;
|
||||
padding-left: 8px;
|
||||
border-left: 3px solid var(--brand-primary);
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px 24px;
|
||||
}
|
||||
.form-grid-single {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
/* 头像块 */
|
||||
.avatar-block { display: flex; align-items: center; gap: 20px; padding-bottom: 16px; border-bottom: 1px solid #ebeef5; }
|
||||
.avatar-circle {
|
||||
width: 80px; height: 80px; border-radius: 50%;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: #fff;
|
||||
display: flex; flex-direction: column;
|
||||
align-items: center; justify-content: center;
|
||||
font-size: 28px; font-weight: 600;
|
||||
cursor: pointer; flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
transition: transform 0.15s;
|
||||
}
|
||||
.avatar-circle:hover { transform: scale(1.04); }
|
||||
.avatar-circle.is-uploading { cursor: wait; opacity: 0.75; }
|
||||
.avatar-circle .avatar-img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
||||
.avatar-tip {
|
||||
font-size: 11px; opacity: 0.85; margin-top: 4px;
|
||||
position: absolute; bottom: 4px; left: 0; right: 0;
|
||||
text-align: center;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.avatar-meta { display: flex; flex-direction: column; gap: 6px; flex: 1; }
|
||||
.avatar-meta .name { font-size: 18px; font-weight: 600; color: #262626; }
|
||||
.avatar-meta .role { font-size: 13px; color: #909399; }
|
||||
.phone-row { display: flex; align-items: center; gap: 8px; font-size: 14px; color: #606266; margin-top: 4px; }
|
||||
.phone-label { color: #909399; }
|
||||
.phone-value { color: #262626; font-weight: 500; }
|
||||
.btn-edit-phone { padding: 0 4px; }
|
||||
|
||||
/* 错误提示 + 按钮区 */
|
||||
.form-error { color: #f56c6c; font-size: 13px; margin: 4px 0 12px; min-height: 18px; }
|
||||
.form-actions { margin-top: 20px; display: flex; gap: 12px; }
|
||||
|
||||
/* 密码 form 单列 */
|
||||
.password-form { max-width: 480px; }
|
||||
.password-form :deep(.el-form-item) { margin-bottom: 16px; }
|
||||
|
||||
/* 手机号 dialog 验证码 row */
|
||||
.sms-row { display: flex; gap: 8px; align-items: center; width: 100%; }
|
||||
.sms-row .el-input { flex: 1; }
|
||||
.btn-sms { flex-shrink: 0; }
|
||||
|
||||
/* 主色统一 (跟项目 brand-primary 一致, 深海军蓝) */
|
||||
:deep(.el-button--primary:not(.is-link)) { background: var(--brand-primary); border-color: var(--brand-primary); border-radius: 4px; color: #fff; }
|
||||
:deep(.el-button--primary:not(.is-link):hover),
|
||||
:deep(.el-button--primary:not(.is-link):focus) { background: var(--brand-primary-deep, var(--brand-primary)); border-color: var(--brand-primary-deep, var(--brand-primary)); color: #fff; }
|
||||
/* link 文字按钮 + 链接色都用主色, 跟 button 主色保持一致 (不撞色) */
|
||||
:deep(.el-button--primary.is-link),
|
||||
:deep(.el-button.is-link),
|
||||
:deep(.el-link.is-link),
|
||||
:deep(.el-tabs__item.is-active) { color: var(--brand-primary); }
|
||||
:deep(.el-button--primary.is-link:hover),
|
||||
:deep(.el-button.is-link:hover) { color: var(--brand-primary-deep, var(--brand-primary)); }
|
||||
:deep(.el-button) { border-radius: 4px; }
|
||||
:deep(.el-input__wrapper), :deep(.el-textarea__inner) { border-radius: 4px; }
|
||||
/* 整页面板 (与 manager/Accounts.vue 一致: max-width 1200px) */
|
||||
.sponsor-account { padding: 16px; max-width: 1200px; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.account-form { max-width: 600px; }
|
||||
.filter-tip { color: #909399; font-size: 12px; margin-bottom: 12px; padding-left: 120px; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user