Appearance
BizUserProfile 个人中心
个人中心一页式容器:左侧板块导航 + 右侧面板,四个板块按需渲染(sections 控制且顺序随传入数组)。基础资料:横幅(cover 插槽可自定义背景)+ 头像 + 角色标签 + 部门 / 账号 / 入职日期,「编辑资料」弹窗表单(姓名必填,邮箱 / 手机格式校验),头像点击可裁剪替换(内嵌 BizAvatarCropper,本地选图 → 裁剪 → avatar-change 外抛并即时预览);安全设置:改密表单(旧密码 + 新密码 + 确认一致性),内嵌 BizPasswordMeter(biz-password-input 子导出组件)实时强度评估与规则清单,passwordRule 注入业务合规规则;账号绑定:手机 / 邮箱 / 微信 / 钉钉绑定状态与操作;登录设备:当前设备标记与异地设备下线,空态 ElEmpty。组件不含业务接口,保存资料、改密、绑定、下线均以事件交给业务侧落库。适用于系统「个人中心 / 账号设置」页面。
基础用法

王小明
管理员前端开发
研发部 / 前端组xiaoming.wang2021-03-18 入职
专注前端工程化与组件库建设,热爱开源。
安全设置
账号绑定
- 手机号码138****6688已绑定
- 邮箱xia****ng@example.com已绑定
- 微信微信昵称 *明未绑定
- 钉钉未绑定
登录设备
- MacBook Pro · Chrome 120当前设备上海 · 静安区 · 在线中
- iPhone 15 · 企业 App上海 · 静安区 · 2026-10-19 21:04
- Windows 11 · Edge北京 · 朝阳区 · 2026-10-15 09:32
左侧导航切换板块:基础资料(头像裁剪 / 编辑弹窗)、安全设置(改密 + 强度评估)、账号绑定、登录设备; 写操作均事件外抛,本页模拟落库回写。
四个板块完整渲染:编辑资料、修改密码、绑定操作、设备下线均事件外抛(demo 内模拟落库回写)。
<script setup lang="ts">
/** 场景:基础——四个板块完整渲染,编辑 / 改密 / 绑定 / 下线事件模拟落库回写 */
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { BizUserProfile } from 'vue3-ep-biz-lib'
import type {
BizProfileBindActionPayload,
BizProfileBinding,
BizProfileDevice,
BizUserProfileUser,
} from 'vue3-ep-biz-lib'
const user = ref<BizUserProfileUser>({
avatar: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png',
name: '王小明',
account: 'xiaoming.wang',
dept: '研发部 / 前端组',
roles: ['管理员', '前端开发'],
email: 'xiaoming.wang@example.com',
phone: '13800006688',
bio: '专注前端工程化与组件库建设,热爱开源。',
joinDate: '2021-03-18 入职',
})
const bindings = ref<BizProfileBinding[]>([
{ type: 'phone', account: '138****6688', bound: true },
{ type: 'email', account: 'xia****ng@example.com', bound: true },
{ type: 'wechat', account: '微信昵称 *明', bound: false },
{ type: 'dingtalk', bound: false },
])
const devices = ref<BizProfileDevice[]>([
{ id: 1, name: 'MacBook Pro · Chrome 120', location: '上海 · 静安区', lastTime: '在线中', current: true },
{ id: 2, name: 'iPhone 15 · 企业 App', location: '上海 · 静安区', lastTime: '2026-10-19 21:04' },
{ id: 3, name: 'Windows 11 · Edge', location: '北京 · 朝阳区', lastTime: '2026-10-15 09:32' },
])
const onAvatarChange = (url: string): void => {
user.value = { ...user.value, avatar: url }
}
const onSaveProfile = (next: BizUserProfileUser): void => {
user.value = next
ElMessage.success('资料已保存(实际项目此处调用接口)')
}
const onChangePassword = (): void => {
ElMessage.success('密码修改成功,请使用新密码重新登录')
}
const onBindAction = (payload: BizProfileBindActionPayload): void => {
const target = bindings.value.find((item) => item.type === payload.type)
if (target !== undefined) target.bound = payload.action === 'bind'
ElMessage.success(`${payload.type} ${payload.action === 'bind' ? '绑定' : '解绑'}成功`)
}
const onDeviceRemove = (device: BizProfileDevice): void => {
devices.value = devices.value.filter((item) => item.id !== device.id)
ElMessage.success(`已下线:${device.name}`)
}
</script>
<template>
<div>
<BizUserProfile
:user="user"
:bindings="bindings"
:devices="devices"
@avatar-change="onAvatarChange"
@save-profile="onSaveProfile"
@change-password="onChangePassword"
@bind-action="onBindAction"
@device-remove="onDeviceRemove"
/>
<p style="margin: 10px 0 0; font-size: 12px; color: var(--el-text-color-secondary)">
左侧导航切换板块:基础资料(头像裁剪 / 编辑弹窗)、安全设置(改密 + 强度评估)、账号绑定、登录设备;
写操作均事件外抛,本页模拟落库回写。
</p>
</div>
</template>
显示代码
按需渲染板块

欧阳大明
架构师
技术委员会daming.ouyang2019-06-03 入职
负责平台架构与开源治理。
安全设置
账号绑定
- 手机号码137****2233已绑定
- 邮箱dami****@example.com已绑定
- 微信未绑定
- 钉钉钉钉昵称 *明已绑定
登录设备
暂无登录设备
sections=['base', 'binding']:左侧导航仅剩两项,安全设置与登录设备不渲染; 数组顺序即渲染顺序(如 ['binding', 'base'] 则绑定在前)。
sections 只渲染 base + binding,导航与面板同步裁剪,渲染顺序随传入数组。<script setup lang="ts">
/** 场景:按需渲染板块——sections 只渲染 base + binding,导航与面板同步裁剪 */
import { ref } from 'vue'
import { BizUserProfile } from 'vue3-ep-biz-lib'
import type { BizProfileBinding, BizUserProfileUser } from 'vue3-ep-biz-lib'
const user = ref<BizUserProfileUser>({
avatar: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png',
name: '欧阳大明',
account: 'daming.ouyang',
dept: '技术委员会',
roles: ['架构师'],
bio: '负责平台架构与开源治理。',
joinDate: '2019-06-03 入职',
})
const bindings = ref<BizProfileBinding[]>([
{ type: 'phone', account: '137****2233', bound: true },
{ type: 'email', account: 'dami****@example.com', bound: true },
{ type: 'wechat', bound: false },
{ type: 'dingtalk', account: '钉钉昵称 *明', bound: true },
])
</script>
<template>
<div>
<BizUserProfile :user="user" :bindings="bindings" :sections="['base', 'binding']" />
<p style="margin: 10px 0 0; font-size: 12px; color: var(--el-text-color-secondary)">
<code>sections=['base', 'binding']</code>:左侧导航仅剩两项,安全设置与登录设备不渲染;
数组顺序即渲染顺序(如 <code>['binding', 'base']</code> 则绑定在前)。
</p>
</div>
</template>
显示代码
头像裁剪替换

李思思
设计师
设计中心 / 体验设计sisi.li
让复杂系统的交互变得自然。
安全设置
账号绑定
暂无绑定项
登录设备
暂无登录设备
点击头像选择本地图片 → 拖拽 / 滚轮缩放裁剪(1:1 圆形预览)→ 确认后组件内即时预览并外抛 avatar-change(base64,尚未裁剪)。
点击头像 → 本地选图 → 裁剪弹窗(内嵌 BizAvatarCropper,拖拽 / 滚轮缩放)→ 确认后
avatar-change 外抛 base64 并即时预览。<script setup lang="ts">
/** 场景:头像裁剪替换——点击头像 → 本地选图 → 裁剪弹窗 → avatar-change 外抛并即时预览 */
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { BizUserProfile } from 'vue3-ep-biz-lib'
import type { BizUserProfileUser } from 'vue3-ep-biz-lib'
const user = ref<BizUserProfileUser>({
avatar: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png',
name: '李思思',
account: 'sisi.li',
dept: '设计中心 / 体验设计',
roles: ['设计师'],
bio: '让复杂系统的交互变得自然。',
})
const lastLength = ref(0)
const onAvatarChange = (url: string): void => {
user.value = { ...user.value, avatar: url }
lastLength.value = url.length
ElMessage.success('头像已更新(实际项目此处上传 base64 / blob 到服务端)')
}
</script>
<template>
<div>
<BizUserProfile :user="user" :sections="['base']" @avatar-change="onAvatarChange" />
<p style="margin: 10px 0 0; font-size: 12px; color: var(--el-text-color-secondary)">
点击头像选择本地图片 → 拖拽 / 滚轮缩放裁剪(1:1 圆形预览)→ 确认后组件内即时预览并外抛
<code>avatar-change</code>(base64,{{ lastLength > 0 ? `最近一次长度 ${lastLength} 字符` : '尚未裁剪' }})。
</p>
</div>
</template>
显示代码
修改密码
王小明
研发部 / 前端组xiaoming.wang
安全设置
账号绑定
暂无绑定项
登录设备
暂无登录设备
提交参数:尚未提交
新密码输入时实时强度分档与规则清单(BizPasswordMeter);失焦触发 passwordRule 业务校验 (可试输入 xiaoming123 触发「禁含账号名」);两次输入不一致时确认框报错。
新密码实时强度评估 + 规则清单;
passwordRule 注入业务规则(长度 / 大小写数字组合 / 禁含账号名),两次输入一致性校验。<script setup lang="ts">
/** 场景:修改密码——新密码强度评估 + passwordRule 业务规则 + 一致性校验,提交参数回显 */
import { ref } from 'vue'
import { ElButton, ElMessage } from 'element-plus'
import { BizUserProfile } from 'vue3-ep-biz-lib'
import type { BizProfileChangePasswordPayload, BizUserProfileInstance, BizUserProfileUser } from 'vue3-ep-biz-lib'
const user = ref<BizUserProfileUser>({
name: '王小明',
account: 'xiaoming.wang',
dept: '研发部 / 前端组',
})
/** 业务密码规则:长度 / 大小写数字组合 / 禁含账号名 */
const passwordRule = (pwd: string): string | true => {
if (pwd.length < 8) return '密码长度至少 8 位'
if (!/[a-z]/.test(pwd) || !/[A-Z]/.test(pwd) || !/\d/.test(pwd)) return '需同时包含大写、小写字母和数字'
if (pwd.toLowerCase().includes('xiaoming')) return '密码不能包含账号名'
return true
}
const profileRef = ref<BizUserProfileInstance>()
const submitted = ref('尚未提交')
const onChangePassword = (payload: BizProfileChangePasswordPayload): void => {
submitted.value = `旧密码:${payload.oldPassword},新密码:${payload.newPassword}`
ElMessage.success('密码校验通过并外抛 change-password(提交后表单已自动重置)')
}
</script>
<template>
<div>
<BizUserProfile
ref="profileRef"
:user="user"
:sections="['security']"
:password-rule="passwordRule"
@change-password="onChangePassword"
/>
<div style="margin-top: 12px; display: flex; align-items: center; gap: 12px">
<ElButton size="small" @click="profileRef?.resetPasswordForm()">外部重置表单(Expose)</ElButton>
<span style="font-size: 12px; color: var(--el-text-color-secondary)">提交参数:{{ submitted }}</span>
</div>
<p style="margin: 10px 0 0; font-size: 12px; color: var(--el-text-color-secondary)">
新密码输入时实时强度分档与规则清单(BizPasswordMeter);失焦触发 passwordRule 业务校验
(可试输入 <code>xiaoming123</code> 触发「禁含账号名」);两次输入不一致时确认框报错。
</p>
</div>
</template>
显示代码
账号绑定
赵倩
运营
运营部 / 用户增长qian.zhao
安全设置
账号绑定
- 手机号码186****9911已绑定
- 邮箱qian****@example.com未绑定
- 微信微信昵称 *倩已绑定
- 钉钉钉钉昵称 *倩已绑定
登录设备
暂无登录设备
已绑定项操作按钮为 danger plain「解绑」,未绑定项为 primary plain「绑定」;最近一次事件参数:(尚未操作)。
绑定 / 解绑操作外抛
bind-action,demo 内模拟切换 bound 状态并回显事件参数。<script setup lang="ts">
/** 场景:账号绑定——绑定 / 解绑操作外抛 bind-action,模拟切换 bound 状态 */
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { BizUserProfile } from 'vue3-ep-biz-lib'
import type { BizProfileBindActionPayload, BizProfileBinding, BizUserProfileUser } from 'vue3-ep-biz-lib'
const user = ref<BizUserProfileUser>({
name: '赵倩',
account: 'qian.zhao',
dept: '运营部 / 用户增长',
roles: ['运营'],
})
const bindings = ref<BizProfileBinding[]>([
{ type: 'phone', account: '186****9911', bound: true },
{ type: 'email', account: 'qian****@example.com', bound: false },
{ type: 'wechat', account: '微信昵称 *倩', bound: true },
{ type: 'dingtalk', account: '钉钉昵称 *倩', bound: true },
])
const lastAction = ref('(尚未操作)')
const onBindAction = (payload: BizProfileBindActionPayload): void => {
const target = bindings.value.find((item) => item.type === payload.type)
if (target !== undefined) target.bound = payload.action === 'bind'
lastAction.value = `{ type: '${payload.type}', action: '${payload.action}' }`
ElMessage.success(`模拟${payload.action === 'bind' ? '绑定' : '解绑'}成功(实际项目此处调用接口)`)
}
</script>
<template>
<div>
<BizUserProfile :user="user" :bindings="bindings" :sections="['binding']" @bind-action="onBindAction" />
<p style="margin: 10px 0 0; font-size: 12px; color: var(--el-text-color-secondary)">
已绑定项操作按钮为 danger plain「解绑」,未绑定项为 primary plain「绑定」;最近一次事件参数:{{ lastAction }}。
</p>
</div>
</template>
显示代码
登录设备

王小明
xiaoming.wang
安全设置
账号绑定
暂无绑定项
登录设备
- MacBook Pro · Chrome 120当前设备上海 · 静安区 · 在线中
- iPhone 15 · 企业 App上海 · 静安区 · 2026-10-19 21:04
- Windows 11 · Edge北京 · 朝阳区 · 2026-10-15 09:32
- iPad Air · Safari杭州 · 西湖区 · 2026-09-30 18:11
当前设备带 primary 标记且无下线按钮;全部历史设备下线后列表展示空态(ElEmpty)。剩余设备:4 台。
当前设备高亮标记且不可下线,其余设备
device-remove 下线;列表清空后展示空态。<script setup lang="ts">
/** 场景:登录设备——当前设备标记不可下线,其余设备 device-remove 下线,清空后展示空态 */
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { BizUserProfile } from 'vue3-ep-biz-lib'
import type { BizProfileDevice, BizUserProfileUser } from 'vue3-ep-biz-lib'
const user = ref<BizUserProfileUser>({
avatar: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png',
name: '王小明',
account: 'xiaoming.wang',
})
const devices = ref<BizProfileDevice[]>([
{ id: 1, name: 'MacBook Pro · Chrome 120', location: '上海 · 静安区', lastTime: '在线中', current: true },
{ id: 2, name: 'iPhone 15 · 企业 App', location: '上海 · 静安区', lastTime: '2026-10-19 21:04' },
{ id: 3, name: 'Windows 11 · Edge', location: '北京 · 朝阳区', lastTime: '2026-10-15 09:32' },
{ id: 4, name: 'iPad Air · Safari', location: '杭州 · 西湖区', lastTime: '2026-09-30 18:11' },
])
const onDeviceRemove = (device: BizProfileDevice): void => {
devices.value = devices.value.filter((item) => item.id !== device.id)
ElMessage.success(`已下线:${device.name}(实际项目此处调用接口)`)
}
</script>
<template>
<div>
<BizUserProfile :user="user" :devices="devices" :sections="['devices']" @device-remove="onDeviceRemove" />
<p style="margin: 10px 0 0; font-size: 12px; color: var(--el-text-color-secondary)">
当前设备带 primary 标记且无下线按钮;全部历史设备下线后列表展示空态(ElEmpty)。剩余设备:{{ devices.length }} 台。
</p>
</div>
</template>
显示代码
自定义插槽
cover 插槽:自定义横幅背景2026 Q4 · 安全面检已通过

孙悦
数据工程师值班专员
数据平台 / 实时计算yue.sun2022-11-11 入职
与流数据为伴,SLO 守夜人。
86%
资料完整度
42 次
本月登录
A 级
安全评分
安全设置
security-extra:建议每 90 天更换一次密码,且不与历史密码重复。
账号绑定
暂无绑定项
binding-extra:绑定手机后可用于登录验证与密码找回。
登录设备
- MacBook Pro · Chrome 120当前设备深圳 · 南山区 · 在线中
- Ubuntu 22.04 · Firefox深圳 · 南山区 · 2026-10-18 14:02
cover 插槽首个子元素自动铺满横幅区(覆盖默认渐变);各板块 extra 插槽追加在对应内容之后。
cover 自定义横幅背景,各板块 *-extra 插槽追加说明内容。<script setup lang="ts">
/** 场景:自定义插槽——cover 自定义横幅背景,各板块 *-extra 追加内容 */
import { ref } from 'vue'
import { ElButton, ElMessage } from 'element-plus'
import { BizUserProfile } from 'vue3-ep-biz-lib'
import type { BizProfileDevice, BizUserProfileUser } from 'vue3-ep-biz-lib'
const user = ref<BizUserProfileUser>({
avatar: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png',
name: '孙悦',
account: 'yue.sun',
dept: '数据平台 / 实时计算',
roles: ['数据工程师', '值班专员'],
bio: '与流数据为伴,SLO 守夜人。',
joinDate: '2022-11-11 入职',
})
const devices = ref<BizProfileDevice[]>([
{ id: 1, name: 'MacBook Pro · Chrome 120', location: '深圳 · 南山区', lastTime: '在线中', current: true },
{ id: 2, name: 'Ubuntu 22.04 · Firefox', location: '深圳 · 南山区', lastTime: '2026-10-18 14:02' },
])
const stats = [
{ label: '资料完整度', value: '86%' },
{ label: '本月登录', value: '42 次' },
{ label: '安全评分', value: 'A 级' },
]
const onRemoveOthers = (): void => {
const count = devices.value.filter((item) => !item.current).length
devices.value = devices.value.filter((item) => item.current)
ElMessage.success(`已下线 ${count} 台历史设备(devices-extra 插槽按钮)`)
}
</script>
<template>
<div>
<BizUserProfile :user="user" :devices="devices">
<template #cover>
<div
style="
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
background: linear-gradient(120deg, var(--el-color-success-light-8), var(--el-color-primary-light-5));
color: var(--el-text-color-primary);
font-size: 13px;
"
>
<span>cover 插槽:自定义横幅背景</span>
<span>2026 Q4 · 安全面检已通过</span>
</div>
</template>
<template #base-extra>
<div style="display: flex; gap: 32px; padding-top: 12px; border-top: 1px solid var(--el-border-color-lighter)">
<div v-for="stat in stats" :key="stat.label">
<div style="font-size: 18px; font-weight: 600; color: var(--el-text-color-primary)">{{ stat.value }}</div>
<div style="font-size: 12px; color: var(--el-text-color-secondary)">{{ stat.label }}</div>
</div>
</div>
</template>
<template #security-extra>
<p style="margin: 0; font-size: 12px; color: var(--el-text-color-secondary)">
security-extra:建议每 90 天更换一次密码,且不与历史密码重复。
</p>
</template>
<template #binding-extra>
<p style="margin: 0; font-size: 12px; color: var(--el-text-color-secondary)">
binding-extra:绑定手机后可用于登录验证与密码找回。
</p>
</template>
<template #devices-extra>
<ElButton size="small" type="danger" plain @click="onRemoveOthers">一键下线历史设备</ElButton>
</template>
</BizUserProfile>
<p style="margin: 10px 0 0; font-size: 12px; color: var(--el-text-color-secondary)">
cover 插槽首个子元素自动铺满横幅区(覆盖默认渐变);各板块 extra 插槽追加在对应内容之后。
</p>
</div>
</template>
显示代码
事件回显

王小明
管理员
研发部 / 前端组xiaoming.wang
安全设置
账号绑定
- 手机号码138****6688已绑定
- 微信未绑定
登录设备
- MacBook Pro · Chrome 120当前设备上海 · 在线中
- Windows 11 · Edge北京 · 2026-10-15 09:32
当前激活板块(v-model:activeSection):base
事件流(最近 8 条):
暂无事件:切换导航 / 裁剪头像 / 保存资料 / 提交改密 / 绑定操作 / 设备下线试试
全部事件(update:activeSection / avatar-change / save-profile / change-password / bind-action / device-remove)触发记录实时回显。
<script setup lang="ts">
/** 场景:事件回显——update:activeSection / avatar-change / save-profile / change-password / bind-action / device-remove 全事件记录 */
import { ref } from 'vue'
import { BizUserProfile } from 'vue3-ep-biz-lib'
import type {
BizProfileBindActionPayload,
BizProfileDevice,
BizUserProfileUser,
} from 'vue3-ep-biz-lib'
interface EventLog {
time: string
name: string
detail: string
}
const user = ref<BizUserProfileUser>({
avatar: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png',
name: '王小明',
account: 'xiaoming.wang',
dept: '研发部 / 前端组',
roles: ['管理员'],
})
const bindings = ref([
{ type: 'phone' as const, account: '138****6688', bound: true },
{ type: 'wechat' as const, bound: false },
])
const devices = ref<BizProfileDevice[]>([
{ id: 1, name: 'MacBook Pro · Chrome 120', location: '上海', lastTime: '在线中', current: true },
{ id: 2, name: 'Windows 11 · Edge', location: '北京', lastTime: '2026-10-15 09:32' },
])
const active = ref('base')
const logs = ref<EventLog[]>([])
const pushLog = (name: string, detail: unknown): void => {
const time = new Date().toLocaleTimeString('zh-CN', { hour12: false })
logs.value = [{ time, name, detail: JSON.stringify(detail) }, ...logs.value].slice(0, 8)
}
const onAvatarChange = (url: string): void => {
user.value = { ...user.value, avatar: url }
pushLog('avatar-change', `base64(${url.length} 字符)`)
}
const onSaveProfile = (next: BizUserProfileUser): void => {
user.value = next
pushLog('save-profile', next)
}
const onBindAction = (payload: BizProfileBindActionPayload): void => {
const target = bindings.value.find((item) => item.type === payload.type)
if (target !== undefined) target.bound = payload.action === 'bind'
pushLog('bind-action', payload)
}
const onDeviceRemove = (device: BizProfileDevice): void => {
devices.value = devices.value.filter((item) => item.id !== device.id)
pushLog('device-remove', device)
}
</script>
<template>
<div>
<BizUserProfile
v-model:activeSection="active"
:user="user"
:bindings="bindings"
:devices="devices"
@avatar-change="onAvatarChange"
@save-profile="onSaveProfile"
@change-password="(payload) => pushLog('change-password', payload)"
@bind-action="onBindAction"
@device-remove="onDeviceRemove"
/>
<div style="margin-top: 12px">
<div style="font-size: 13px; color: var(--el-text-color-primary); margin-bottom: 6px">
当前激活板块(v-model:activeSection):{{ active }}
</div>
<div style="font-size: 13px; color: var(--el-text-color-primary); margin-bottom: 6px">事件流(最近 8 条):</div>
<div
v-for="(log, index) in logs"
:key="`${log.time}-${log.name}-${index}`"
style="padding: 4px 0; font-size: 12px; color: var(--el-text-color-secondary); border-bottom: 1px dashed var(--el-border-color-lighter)"
>
[{{ log.time }}] <span style="color: var(--el-color-primary)">{{ log.name }}</span> → {{ log.detail }}
</div>
<div v-if="logs.length === 0" style="font-size: 12px; color: var(--el-text-color-placeholder)">
暂无事件:切换导航 / 裁剪头像 / 保存资料 / 提交改密 / 绑定操作 / 设备下线试试
</div>
</div>
</div>
</template>
显示代码
只读浏览
editable = false(切换对比编辑入口显隐)

周正
审计员
合规部 / 内审组zheng.zhou2020-08-01 入职
只读视角供管理员查看他人资料快照。
安全设置
账号绑定
- 手机号码155****0022已绑定
- 邮箱zheng****@example.com已绑定
登录设备
- MacBook Pro · Chrome 120当前设备成都 · 高新区 · 在线中
- Windows 11 · Chrome成都 · 高新区 · 2026-10-12 10:05
editable=false:头像无角标且不可点击、「编辑资料」按钮隐藏、绑定 / 解绑按钮隐藏、设备下线按钮隐藏,仅浏览。
editable=false 隐藏全部编辑入口(头像裁剪、编辑资料、绑定操作、设备下线),仅浏览。<script setup lang="ts">
/** 场景:只读浏览——editable=false 隐藏全部编辑入口(头像裁剪 / 编辑资料 / 绑定操作 / 设备下线) */
import { ref } from 'vue'
import { ElSwitch } from 'element-plus'
import { BizUserProfile } from 'vue3-ep-biz-lib'
import type { BizProfileBinding, BizProfileDevice, BizUserProfileUser } from 'vue3-ep-biz-lib'
const editable = ref(false)
const user = ref<BizUserProfileUser>({
avatar: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png',
name: '周正',
account: 'zheng.zhou',
dept: '合规部 / 内审组',
roles: ['审计员'],
email: 'zheng****@example.com',
bio: '只读视角供管理员查看他人资料快照。',
joinDate: '2020-08-01 入职',
})
const bindings = ref<BizProfileBinding[]>([
{ type: 'phone', account: '155****0022', bound: true },
{ type: 'email', account: 'zheng****@example.com', bound: true },
])
const devices = ref<BizProfileDevice[]>([
{ id: 1, name: 'MacBook Pro · Chrome 120', location: '成都 · 高新区', lastTime: '在线中', current: true },
{ id: 2, name: 'Windows 11 · Chrome', location: '成都 · 高新区', lastTime: '2026-10-12 10:05' },
])
</script>
<template>
<div>
<div style="margin-bottom: 10px; display: flex; align-items: center; gap: 8px; font-size: 13px">
<ElSwitch v-model="editable" />
<span>editable = {{ editable }}(切换对比编辑入口显隐)</span>
</div>
<BizUserProfile :user="user" :bindings="bindings" :devices="devices" :editable="editable" />
<p style="margin: 10px 0 0; font-size: 12px; color: var(--el-text-color-secondary)">
editable=false:头像无角标且不可点击、「编辑资料」按钮隐藏、绑定 / 解绑按钮隐藏、设备下线按钮隐藏,仅浏览。
</p>
</div>
</template>
显示代码
Props
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| user | BizUserProfileUser | — | 用户资料(name 必填,含 avatar / account / dept / roles / email / phone / bio / joinDate) |
| devices | BizProfileDevice[] | [] | 登录设备列表(current 标记当前设备,不可下线) |
| bindings | BizProfileBinding[] | [] | 账号绑定列表(account 为脱敏后字符串) |
| sections | Array<'base' | 'security' | 'binding' | 'devices'> | 全部(按序) | 渲染板块,按数组顺序展示;传空数组回落为全部 |
| editable | boolean | true | 显示编辑入口(false 时只读浏览) |
| activeSection | string | 'base' | 激活板块(v-model:activeSection) |
| avatarEditable | boolean | true | 头像可裁剪替换(需 editable 同为 true) |
| passwordRule | (pwd: string) => string | true | — | 业务密码校验:返回 true 通过,返回 string 为错误文案 |
Events
| 事件名 | 参数 | 说明 |
|---|---|---|
| update:activeSection | (value: string) | 激活板块变化(v-model:activeSection) |
| avatar-change | (url: string) | 头像裁剪确认(base64),组件内已即时预览 |
| save-profile | (user: BizUserProfileUser) | 编辑资料保存(已合并原 user 未编辑字段,清空项置 undefined) |
| change-password | (payload: { oldPassword: string; newPassword: string }) | 改密提交(校验通过后表单自动重置) |
| bind-action | (payload: { type: 'phone' | 'email' | 'wechat' | 'dingtalk'; action: 'bind' | 'unbind' }) | 绑定 / 解绑操作 |
| device-remove | (device: BizProfileDevice) | 非当前设备下线 |
Slots
| 插槽名 | 说明 |
|---|---|
| cover | 横幅背景区(首个子元素自动铺满,覆盖默认渐变) |
| base-extra | 基础资料板块追加内容 |
| security-extra | 安全设置板块追加内容 |
| binding-extra | 账号绑定板块追加内容 |
| devices-extra | 登录设备板块追加内容 |
Expose
| 名称 | 类型 | 说明 |
|---|---|---|
| getSection | () => string | 获取当前激活板块 key(base / security / binding / devices) |
| setSection | (key: string) => void | 切换到指定板块(不在 sections 渲染列表内的 key 会被忽略) |
| resetPasswordForm | () => void | 重置修改密码表单(清空输入与校验状态) |
TIP
头像裁剪链路:点击头像 → 隐藏 input 本地选图 → FileReader 转 dataURL → BizAvatarCropper 裁剪(1:1 圆形预览)→ 确认后组件内即时预览并外抛 avatar-change;父级上传落库后回写 user.avatar 会同步覆盖预览。