| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516 |
- <template>
- <el-container class="dashboard-container">
- <el-main class="dashboard-main">
- <div class="top-blocks-row">
- <div class="section-block flex-item">
- <h3 class="section-title">我的角色</h3>
- <div class="btn-group">
- <el-button
- v-for="item in roles"
- :key="item"
- icon="el-icon-user-solid"
- size="small"
- class="custom-btn"
- @click="goToRole(item)"
- >
- {{ item }}
- </el-button>
- </div>
- </div>
- <div class="section-block flex-item">
- <h3 class="section-title">快捷工作台</h3>
- <div class="btn-group">
- <el-button icon="el-icon-files" size="small" class="custom-btn" @click="navTo('/disk')">Disk</el-button>
- <el-button icon="el-icon-chat-dot-square" size="small" class="custom-btn" @click="navTo('/chat')">Chat</el-button>
- <el-button icon="el-icon-film" size="small" class="custom-btn" @click="navTo('/')">VOD</el-button>
- <el-button icon="el-icon-document" size="small" class="custom-btn" @click="navTo('/blog')">Blog</el-button>
- </div>
- </div>
- </div>
- <el-card class="box-card profile-card" shadow="hover">
- <div slot="header" class="card-header-custom">
- <i class="el-icon-postcard" />
- <span>个人档案资料</span>
- </div>
- <div class="profile-card-content">
- <div class="avatar-section">
- <div class="avatar-wrapper">
- <el-image :src="loginUser.avatarUrl" class="user-avatar" fit="cover">
- <div slot="error" class="avatar-error-slot">
- <i class="el-icon-user" />
- </div>
- </el-image>
- </div>
- <el-button
- type="primary"
- size="mini"
- icon="el-icon-camera"
- class="update-avatar-btn"
- @click="openAvatarDialog"
- >
- 更换头像
- </el-button>
- </div>
- <el-form ref="profileForm" :model="loginUser" label-width="80px" size="small" class="profile-form">
- <div class="form-grid">
- <el-form-item label="用户 ID">
- <el-input v-model="loginUser.userId" readonly class="is-readonly" />
- </el-form-item>
- <el-form-item label="用户名">
- <el-input v-model="loginUser.username" readonly class="is-readonly" />
- </el-form-item>
- <el-form-item label="显示名">
- <div class="input-with-action">
- <el-input v-model="loginUser.screenName" readonly />
- <el-button type="text" icon="el-icon-edit" @click="openEditDialog('screenName', '显示名', loginUser.screenName)">修改</el-button>
- </div>
- </el-form-item>
- <el-form-item label="电子邮箱">
- <div class="input-with-action">
- <el-input v-model="loginUser.email" readonly />
- <el-button type="text" icon="el-icon-edit" @click="openEditDialog('email', '电子邮箱', loginUser.email)">修改</el-button>
- </div>
- </el-form-item>
- <el-form-item label="手机号码">
- <div class="input-with-action">
- <el-input v-model="loginUser.mobile" readonly />
- <el-button type="text" icon="el-icon-edit" @click="openEditDialog('mobile', '手机号码', loginUser.mobile)">修改</el-button>
- </div>
- </el-form-item>
- <el-form-item label="安全密码">
- <div class="input-with-action">
- <el-input value="******" type="password" readonly show-password />
- <el-button type="text" icon="el-icon-key" @click="passwordDialogVisible = true">重置</el-button>
- </div>
- </el-form-item>
- </div>
- <el-form-item label="个性签名" class="full-width-item signature-item">
- <el-input
- ref="signatureInput"
- v-model="loginUser.signature"
- type="textarea"
- :rows="3"
- :readonly="!isEditingSignature"
- :placeholder="isEditingSignature ? '请输入您的个性签名...' : '这个人很懒,什么都没有留下~'"
- resize="none"
- />
- <div class="signature-actions">
- <el-button v-if="!isEditingSignature" type="text" icon="el-icon-edit" @click="startEditSignature">修改签名</el-button>
- <template v-else>
- <el-button size="mini" type="primary" round @click="saveSignature">保存</el-button>
- <el-button size="mini" round @click="cancelEditSignature">取消</el-button>
- </template>
- </div>
- </el-form-item>
- </el-form>
- </div>
- </el-card>
- </el-main>
- <el-dialog title="更新个人头像" :visible.sync="avatarDialogVisible" width="400px" append-to-body center custom-class="custom-dialog">
- <div class="avatar-upload-box">
- <el-upload
- class="avatar-uploader"
- :action="imgOssUrl"
- :headers="imgHeaders"
- :data="imgData"
- :with-credentials="false"
- :show-file-list="false"
- :before-upload="beforeAvatarUpload"
- :on-success="handleAvatarSuccess"
- >
- <div v-if="previewUrl" class="upload-preview-wrapper">
- <img :src="previewUrl" class="upload-preview" alt="preview">
- <div class="upload-mask"><i class="el-icon-edit" /><span>更换图片</span></div>
- </div>
- <div v-else-if="loginUser.avatarUrl" class="upload-preview-wrapper">
- <img :src="loginUser.avatarUrl" class="upload-preview" alt="current">
- <div class="upload-mask"><i class="el-icon-plus" /><span>上传新头像</span></div>
- </div>
- <div v-else class="upload-placeholder">
- <i class="el-icon-plus" /><p>点击选择新头像</p>
- </div>
- </el-upload>
- </div>
- <div class="upload-tip-text">仅支持 JPG 格式,大小不超过 2MB</div>
- <span slot="footer" class="dialog-footer">
- <el-button size="small" round @click="cancelAvatarUpdate">取 消</el-button>
- <el-button size="small" type="primary" round :loading="submitLoading" @click="confirmAvatarUpdate">确 认</el-button>
- </span>
- </el-dialog>
- <el-dialog :title="'修改' + editModel.title" :visible.sync="editDialogVisible" width="420px" append-to-body custom-class="custom-dialog">
- <el-form label-position="top" size="small" style="padding: 0 10px;">
- <el-form-item :label="'新的' + editModel.title">
- <el-input v-model="editModel.value" autocomplete="off" clearable :placeholder="'请输入新的' + editModel.title" />
- </el-form-item>
- </el-form>
- <span slot="footer" class="dialog-footer">
- <el-button size="small" round @click="editDialogVisible = false">取 消</el-button>
- <el-button size="small" type="primary" round :loading="editSubmitLoading" @click="submitSingleField">确 认</el-button>
- </span>
- </el-dialog>
- <el-dialog title="重置安全密码" :visible.sync="passwordDialogVisible" width="420px" append-to-body custom-class="custom-dialog">
- <el-form ref="passwordForm" :model="passwordForm" label-position="top" size="small" style="padding: 0 10px;">
- <el-form-item label="当前旧密码">
- <el-input v-model="passwordForm.oldPassword" type="password" show-password placeholder="请输入当前密码验证身份" />
- </el-form-item>
- <el-form-item label="输入新密码">
- <el-input v-model="passwordForm.newPassword" type="password" show-password placeholder="新密码长度建议大于6位" />
- </el-form-item>
- </el-form>
- <span slot="footer" class="dialog-footer">
- <el-button size="small" round @click="closePasswordDialog">取 消</el-button>
- <el-button size="small" type="primary" round :loading="passwordSubmitLoading" @click="submitPassword">确 认</el-button>
- </span>
- </el-dialog>
- </el-container>
- </template>
- <script>
- import { userMixin } from 'assets/js/mixin'
- import { getAuthedUser, updateAuthedUser } from '@/utils/auth'
- import { updateAvatar } from '@/api/account' // 假设你的其它更新 API 也在 account 或对应模块里
- import { getAvatarChannelInfo } from '@/api/file'
- import { addApprovalRequest } from '@/api/user'
- export default {
- name: 'Dashboard',
- mixins: [userMixin],
- data() {
- return {
- loginUser: {
- avatarUrl: '',
- gender: '',
- signature: '',
- userId: '',
- username: '',
- screenName: '',
- mobile: '',
- email: '',
- roles: []
- },
- roles: [],
- // 头像上传状态
- avatarDialogVisible: false,
- submitLoading: false,
- imgOssUrl: '',
- imgHeaders: { Authorization: '' },
- imgData: { channelCode: 0 },
- previewUrl: '',
- uploadSuccessData: null,
- // 常规单字段修改弹窗状态
- editDialogVisible: false,
- editSubmitLoading: false,
- editModel: {
- field: '',
- title: '',
- value: ''
- },
- // 签名行内修改状态
- isEditingSignature: false,
- cachedSignature: '',
- // 密码修改弹窗状态
- passwordDialogVisible: false,
- passwordSubmitLoading: false,
- passwordForm: {
- oldPassword: '',
- newPassword: ''
- }
- }
- },
- created() {
- this.initUserData()
- },
- methods: {
- initUserData() {
- document.title = 'Dashboard'
- const user = getAuthedUser()
- if (user) {
- this.loginUser = user
- this.roles = user.roles || []
- }
- },
- navTo(path) {
- if (this.$route.path === path) {
- this.$router.go(0)
- return
- }
- this.$router.push(path)
- },
- requireRole() {
- const requestData = {
- bizType: 'applyRole',
- bizPayload: JSON.stringify({ amount: 5 })
- }
- addApprovalRequest(requestData).then(resp => {
- if (resp.code === 0) {
- this.$message.info('角色申请请求已提交')
- } else {
- this.$message.warning(resp.msg)
- }
- }).catch(error => {
- this.$message.error('角色申请失败: ' + error.message)
- })
- },
- // ==================== 1. 常规单字段(显示名、邮箱、手机)修改业务 ====================
- openEditDialog(field, title, currentVal) {
- this.editModel.field = field
- this.editModel.title = title
- this.editModel.value = currentVal
- this.editDialogVisible = true
- },
- submitSingleField() {
- if (!this.editModel.value.trim()) {
- return this.$message.warning(`请输入有效的${this.editModel.title}`)
- }
- this.editSubmitLoading = true
- // 模拟或者组装你的 API 请求参数,例如:
- // const updateData = { [this.editModel.field]: this.editModel.value }
- // updateUserInfo(updateData).then(res => { ... })
- setTimeout(() => {
- // 1. 动态同步到本地模型
- this.loginUser[this.editModel.field] = this.editModel.value
- // 2. 持久化到 localStorage/cookie 缓存
- updateAuthedUser(this.loginUser)
- this.$message.success(`${this.editModel.title}已成功修改`)
- this.editDialogVisible = false
- this.editSubmitLoading = false
- }, 600)
- },
- // ==================== 2. 签名行内即时修改业务 ====================
- startEditSignature() {
- this.cachedSignature = this.loginUser.signature // 缓存旧签名以便取消
- this.isEditingSignature = true
- this.$nextTick(() => {
- this.$refs.signatureInput.focus() // 自动聚焦到输入框
- })
- },
- saveSignature() {
- // 触发后端 API 保存签名逻辑
- this.$message.success('个性签名已同步更新')
- updateAuthedUser(this.loginUser)
- this.isEditingSignature = false
- },
- cancelEditSignature() {
- this.loginUser.signature = this.cachedSignature // 恢复原状
- this.isEditingSignature = false
- },
- // ==================== 3. 独立密码修改业务 ====================
- submitPassword() {
- if (!this.passwordForm.oldPassword || !this.passwordForm.newPassword) {
- return this.$message.warning('请完整填写当前密码与新密码')
- }
- if (this.passwordForm.newPassword.length < 6) {
- return this.$message.warning('为了您的账户安全,新密码不得少于6位')
- }
- this.passwordSubmitLoading = true
- // 调用你的密码更新 API 接口
- // changePassword(this.passwordForm).then(res => { ... })
- setTimeout(() => {
- this.$message.success('安全密码修改成功,请妥善保管')
- this.closePasswordDialog()
- }, 800)
- },
- closePasswordDialog() {
- this.passwordDialogVisible = false
- this.passwordForm.oldPassword = ''
- this.passwordForm.newPassword = ''
- this.passwordSubmitLoading = false
- },
- // ==================== 4. 头像模块云存储控制(保持不变) ====================
- openAvatarDialog() {
- const pageLoading = this.$loading({
- lock: true,
- text: '正在开辟安全云传输通道...',
- spinner: 'el-icon-loading',
- background: 'rgba(255, 255, 255, 0.7)'
- })
- getAvatarChannelInfo().then(res => {
- if (res.code === 0) {
- this.imgData.channelCode = res.data.channelCode
- this.imgOssUrl = res.data.ossUrl
- this.imgHeaders.Authorization = 'Bearer ' + res.data.token
- this.avatarDialogVisible = true
- } else {
- this.$message.error(res.msg)
- }
- }).catch(error => {
- this.$message.error(error.message)
- }).finally(() => {
- pageLoading.close()
- })
- },
- beforeAvatarUpload(file) {
- if (!this.imgOssUrl) {
- this.$message.error('未检测到可用的云端存储节点!')
- return false
- }
- const isJPG = file.type === 'image/jpeg'
- const isLt2M = file.size / 1024 / 1024 < 2
- if (!isJPG) this.$message.error('仅允许上传 JPG 格式的图片!')
- if (!isLt2M) this.$message.error('图片体积不能超过 2MB!')
- return isJPG && isLt2M
- },
- handleAvatarSuccess(res, file) {
- if (res.code === 0) {
- this.previewUrl = URL.createObjectURL(file.raw)
- this.uploadSuccessData = res.data
- this.$message.success('新头像本地预载完成,请点击确认进行同步')
- } else {
- this.$message.error('文件服务器解析异常: ' + res.msg)
- }
- },
- confirmAvatarUpdate() {
- if (!this.uploadSuccessData) {
- return this.$message.warning('尚未检测到新上传的图像资源')
- }
- this.submitLoading = true
- const postParam = {
- channelCode: this.imgData.channelCode,
- uploadId: this.uploadSuccessData.uploadId
- }
- updateAvatar(postParam).then(resp => {
- if (resp.code === 0) {
- this.loginUser.avatarUrl = resp.data.avatarUrl
- updateAuthedUser(this.loginUser)
- this.$message.success('个人头像已成功同步至云端')
- this.avatarDialogVisible = false
- this.clearUploadStates()
- } else {
- this.$message.warning('配置档案同步失败,请稍后重试')
- }
- }).finally(() => {
- this.submitLoading = false
- })
- },
- cancelAvatarUpdate() {
- this.avatarDialogVisible = false
- this.clearUploadStates()
- },
- clearUploadStates() {
- if (this.previewUrl && this.previewUrl.startsWith('blob:')) {
- URL.revokeObjectURL(this.previewUrl)
- }
- this.previewUrl = ''
- this.uploadSuccessData = null
- },
- goToRole(role) { this.$message.info(`当前操作角色: ${role}`) }
- }
- }
- </script>
- <style scoped>
- /* 基础布局样式保持不变 */
- .dashboard-container { background-color: #f6f8f9; min-height: 100vh; }
- .dashboard-main { padding: 24px; }
- .top-blocks-row { display: flex; gap: 20px; margin-bottom: 20px; flex-wrap: wrap; }
- .flex-item { flex: 1; min-width: 320px; margin-bottom: 0 !important; }
- .section-block { background: #ffffff; padding: 20px; border-radius: 8px; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.02); border: 1px solid #eef1f5; }
- .section-title { margin-top: 0; margin-bottom: 18px; color: #1f2d3d; font-size: 15px; font-weight: 600; border-left: 4px solid #1890ff; padding-left: 12px; }
- .btn-group { display: flex; flex-wrap: wrap; gap: 10px; }
- .custom-btn { border-radius: 6px; transition: all 0.2s ease; background: #f8fafc; border-color: #e2e8f0; color: #475569; }
- .custom-btn:hover { background: #fff; border-color: #1890ff; color: #1890ff; transform: translateY(-1px); box-shadow: 0 2px 6px rgba(24, 144, 255, 0.15); }
- /* 资料档案卡片 */
- .profile-card { border-radius: 8px; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.02) !important; border: 1px solid #eef1f5; }
- .card-header-custom { display: flex; align-items: center; gap: 8px; font-weight: 600; color: #334155; font-size: 15px; }
- .card-header-custom i { color: #1890ff; font-size: 17px; }
- .profile-card-content { display: flex; gap: 40px; padding: 10px 0; flex-wrap: wrap; }
- /* 左侧头像展示区 */
- .avatar-section { display: flex; flex-direction: column; align-items: center; flex-shrink: 0; width: 140px; }
- .avatar-wrapper { padding: 4px; border: 1px solid #e2e8f0; border-radius: 50%; background: #fff; box-shadow: 0 4px 10px rgba(0,0,0,0.03); }
- .user-avatar { width: 110px; height: 110px; border-radius: 50%; background-color: #f1f5f9; }
- .avatar-error-slot { display: flex; justify-content: center; align-items: center; width: 100%; height: 100%; background: #f8fafc; color: #94a3b8; font-size: 36px; }
- .update-avatar-btn { margin-top: 16px !important; width: 100%; border-radius: 20px; box-shadow: 0 4px 10px rgba(24, 144, 255, 0.15); }
- /* 右侧多列网格表单及动作按钮整合 */
- .profile-form { flex: 1; min-width: 280px; }
- .form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 4px 32px; }
- /* 核心:带操作按钮的复合型 Input */
- .input-with-action {
- display: flex;
- align-items: center;
- gap: 10px;
- }
- .input-with-action .el-input {
- flex: 1;
- }
- .input-with-action .el-button--text {
- padding: 0;
- font-weight: 500;
- color: #1890ff;
- }
- /* 独立修饰绝对不可编辑的项 */
- .is-readonly ::v-deep .el-input__inner {
- background-color: #f1f5f9 !important;
- color: #94a3b8 !important;
- }
- /* 签名大文本定制 */
- .full-width-item { grid-column: 1 / -1; margin-top: 12px; }
- .signature-item { position: relative; }
- .signature-actions {
- display: flex;
- justify-content: flex-end;
- gap: 10px;
- margin-top: 6px;
- }
- /* 全局统一下降级 Input 的只读底色质感 */
- ::v-deep .el-input__inner,
- ::v-deep .el-textarea__inner {
- border-color: #e2e8f0 !important;
- color: #334155 !important;
- background-color: #ffffff; /* 允许编辑的框默认亮白 */
- }
- ::v-deep .el-input__inner[readonly],
- ::v-deep .el-textarea__inner[readonly] {
- background-color: #f8fafc !important; /* 处于只读状态时柔和变灰 */
- }
- ::v-deep .el-form-item__label { color: #64748b; font-weight: 500; }
- /* 弹窗及云上传通用 */
- .avatar-upload-box { display: flex; justify-content: center; padding: 10px 0 5px 0; }
- .avatar-uploader ::v-deep .el-upload { border: 2px dashed #cbd5e1; border-radius: 50%; cursor: pointer; position: relative; overflow: hidden; width: 140px; height: 140px; background-color: #f8fafc; transition: all 0.25s ease; }
- .avatar-uploader ::v-deep .el-upload:hover { border-color: #1890ff; background-color: #f0f7ff; }
- .upload-placeholder { display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100%; color: #64748b; }
- .upload-placeholder i { font-size: 28px; margin-bottom: 6px; color: #94a3b8;}
- .upload-placeholder p { margin: 0; font-size: 13px; }
- .upload-preview-wrapper { position: relative; width: 140px; height: 140px; }
- .upload-preview { width: 140px; height: 140px; object-fit: cover; border-radius: 50%; }
- .upload-mask { position: absolute; inset: 0; background: rgba(15, 23, 42, 0.6); color: #fff; display: flex; flex-direction: column; gap: 6px; justify-content: center; align-items: center; font-size: 12px; border-radius: 50%; opacity: 0; transition: opacity 0.25s ease; }
- .upload-preview-wrapper:hover .upload-mask { opacity: 1; }
- .upload-tip-text { text-align: center; font-size: 12px; color: #64748b; margin-top: 12px; }
- /* 统一高级圆角弹窗 */
- ::v-deep .custom-dialog { border-radius: 12px; overflow: hidden; }
- </style>
|