Browse Source

🚀 优化图片

master
wangxiang 2 years ago
parent
commit
0bdd284f0e
  1. 29
      src/layouts/default/header/components/user-dropdown/index.vue
  2. 3
      src/locales/lang/en/layout.ts
  3. 3
      src/locales/lang/zh-CN/layout.ts
  4. 9
      src/store/modules/user.ts
  5. 2
      src/views/system/user/account/center/Project.vue
  6. 4
      src/views/system/user/account/center/index.vue
  7. 20
      src/views/system/user/account/setting/UserInfo.vue
  8. 361
      src/views/system/user/userInfo/index.vue
  9. 96
      src/views/system/user/userInfo/userInfo.data.ts

29
src/layouts/default/header/components/user-dropdown/index.vue

@ -1,7 +1,7 @@
<template> <template>
<Dropdown placement="bottomLeft" :trigger="['click', 'hover']" :overlayClassName="`${prefixCls}-dropdown-overlay`"> <Dropdown placement="bottomLeft" :trigger="['click', 'hover']" :overlayClassName="`${prefixCls}-dropdown-overlay`">
<span :class="[prefixCls, `${prefixCls}--${theme}`]" class="flex"> <span :class="[prefixCls, `${prefixCls}--${theme}`]" class="flex">
<img :class="`${prefixCls}__header`" :src="getAvatarUrl"> <img :class="`${prefixCls}__header`" :src="getUserInfo.avatar">
<span :class="`${prefixCls}__info hidden md:block`"> <span :class="`${prefixCls}__info hidden md:block`">
<span :class="`${prefixCls}__name`" class="truncate"> <span :class="`${prefixCls}__name`" class="truncate">
{{ getUserInfo.nickName }} {{ getUserInfo.nickName }}
@ -12,8 +12,13 @@
<template #overlay> <template #overlay>
<Menu @click="handleMenuClick"> <Menu @click="handleMenuClick">
<MenuItem <MenuItem
key="userInfo" key="accountSettings"
:text="t('layout.header.dropdownItemUserInfo')" :text="t('layout.header.dropdownItemAccountSettings')"
icon="ant-design:user-outlined"
/>
<MenuItem
key="userCenter"
:text="t('layout.header.dropdownItemUserCenter')"
icon="ant-design:user-outlined" icon="ant-design:user-outlined"
/> />
<MenuItem <MenuItem
@ -44,8 +49,7 @@
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { isUrl, isBase64image } from '/@/utils/is'; import { isUrl, isBase64image } from '/@/utils/is';
import { useGlobSetting } from '/@/hooks/setting'; type MenuEvent = 'logout' | 'doc' | 'lock' | 'accountSettings' | 'userCenter';
type MenuEvent = 'logout' | 'doc' | 'lock' | 'userInfo';
import type { User } from '/@/api/platform/core/entity/user'; import type { User } from '/@/api/platform/core/entity/user';
export default defineComponent({ export default defineComponent({
@ -64,7 +68,6 @@
const { t } = useI18n(); const { t } = useI18n();
const { getUseLockPage } = useHeaderSetting(); const { getUseLockPage } = useHeaderSetting();
const userStore = useUserStore(); const userStore = useUserStore();
const { apiUrl } = useGlobSetting();
const getUserInfo = computed((): User => userStore.getUserInfo || {}); const getUserInfo = computed((): User => userStore.getUserInfo || {});
const { push } = useRouter(); const { push } = useRouter();
@ -79,17 +82,14 @@
userStore.confirmLoginOut(); userStore.confirmLoginOut();
} }
const getAvatarUrl = computed((): string => {
return (isUrl(unref(getUserInfo).avatar) || isBase64image(unref(getUserInfo).avatar))
? unref(getUserInfo).avatar
: apiUrl + unref(getUserInfo).avatar;
});
function handleMenuClick(e: { key: MenuEvent }) { function handleMenuClick(e: { key: MenuEvent }) {
switch (e.key) { switch (e.key) {
case 'userInfo': case 'accountSettings':
push('/system/accountSetting'); push('/system/accountSetting');
break; break;
case 'userCenter':
push('/system/userCenter');
break;
case 'logout': case 'logout':
handleLoginOut(); handleLoginOut();
break; break;
@ -105,8 +105,7 @@
getUserInfo, getUserInfo,
handleMenuClick, handleMenuClick,
register, register,
getUseLockPage, getUseLockPage
getAvatarUrl,
}; };
}, },
}); });

3
src/locales/lang/en/layout.ts

@ -3,7 +3,8 @@ export default {
header: { header: {
// 用户下拉 // 用户下拉
dropdownItemLoginOut: 'Login Out', dropdownItemLoginOut: 'Login Out',
dropdownItemUserInfo: 'User Settings', dropdownItemAccountSettings: 'Account Settings',
dropdownItemUserCenter: 'User Center',
tooltipErrorLog: 'Error log', tooltipErrorLog: 'Error log',
tooltipLock: 'Lock screen', tooltipLock: 'Lock screen',

3
src/locales/lang/zh-CN/layout.ts

@ -3,7 +3,8 @@ export default {
header: { header: {
// 用户下拉 // 用户下拉
dropdownItemLoginOut: '退出系统', dropdownItemLoginOut: '退出系统',
dropdownItemUserInfo: '个人设置', dropdownItemAccountSettings: '账户设置',
dropdownItemUserCenter: '用户中心',
// 提示信息 // 提示信息
tooltipErrorLog: '错误日志', tooltipErrorLog: '错误日志',

9
src/store/modules/user.ts

@ -19,6 +19,8 @@ import { usePermissionStore } from '/@/store/modules/permission';
import { RouteRecordRaw } from 'vue-router'; import { RouteRecordRaw } from 'vue-router';
import defaultAvatar from '/@/assets/images/defaultAvatar.svg'; import defaultAvatar from '/@/assets/images/defaultAvatar.svg';
import { urlToBase64 } from '/@/utils/file/base64Conver'; import { urlToBase64 } from '/@/utils/file/base64Conver';
import { useGlobSetting } from '/@/hooks/setting';
import { isUrl } from '/@/utils/is';
interface UserState { interface UserState {
userInfo: Nullable<User>; userInfo: Nullable<User>;
@ -134,8 +136,13 @@ export const useUserStore = defineStore({
}, },
/** 获取用户信息 */ /** 获取用户信息 */
async getUserInfoAction(): Promise<User> { async getUserInfoAction(): Promise<User> {
const { apiUrl } = useGlobSetting();
const userInfo = await getUserInfo().catch(()=> this.resetState()); const userInfo = await getUserInfo().catch(()=> this.resetState());
userInfo.avatar || (userInfo.avatar = await urlToBase64(defaultAvatar)); userInfo.avatar = userInfo.avatar
? isUrl(userInfo.avatar)
? userInfo.avatar
: apiUrl + userInfo.avatar
: await urlToBase64(defaultAvatar);
userInfo.tenantIds = String(userInfo.tenantId).split(','); userInfo.tenantIds = String(userInfo.tenantId).split(',');
// 存储用户扩展信息,便于鉴权 // 存储用户扩展信息,便于鉴权
this.setUserInfo(userInfo); this.setUserInfo(userInfo);

2
src/views/system/user/account/center/Project.vue

@ -5,7 +5,7 @@
<a-col :span="6"> <a-col :span="6">
<ListItem> <ListItem>
<Card :hoverable="true" :class="`${prefixCls}__card`"> <Card :hoverable="true" :class="`${prefixCls}__card`">
<img :src="demoImg" /> <img :src="demoImg">
<div :class="`${prefixCls}__card-title`"> <div :class="`${prefixCls}__card-title`">
{{ item.title }} {{ item.title }}
</div> </div>

4
src/views/system/user/account/center/index.vue

@ -60,8 +60,6 @@
import Article from './Article.vue'; import Article from './Article.vue';
import Application from './Application.vue'; import Application from './Application.vue';
import Project from './Project.vue'; import Project from './Project.vue';
import headerImg from '/@/assets/images/header.jpg';
import { tags, teams, details, achieveList } from './data'; import { tags, teams, details, achieveList } from './data';
import { useUserStore } from '/@/store/modules/user'; import { useUserStore } from '/@/store/modules/user';
@ -80,7 +78,7 @@
}, },
setup() { setup() {
const userStore = useUserStore(); const userStore = useUserStore();
const avatar = computed(() => userStore.getUserInfo.avatar || headerImg); const avatar = computed(() => userStore.getUserInfo.avatar);
return { return {
prefixCls: 'account-center', prefixCls: 'account-center',
avatar, avatar,

20
src/views/system/user/account/setting/UserInfo.vue

@ -11,7 +11,7 @@
<div class="change-avatar"> <div class="change-avatar">
<CropperAvatar <CropperAvatar
:uploadApi="commonUpload" :uploadApi="commonUpload"
:value="getAvatarUrl" :value="getUserInfo.avatar"
btnText="更换头像" btnText="更换头像"
:btnProps="{ preIcon: 'ant-design:cloud-upload-outlined' }" :btnProps="{ preIcon: 'ant-design:cloud-upload-outlined' }"
:width="150" :width="150"
@ -34,10 +34,8 @@
import { commonUpload } from '/@/api/platform/core/controller/upload'; import { commonUpload } from '/@/api/platform/core/controller/upload';
import { useContentHeight } from '/@/hooks/web/useContentHeight'; import { useContentHeight } from '/@/hooks/web/useContentHeight';
import { User } from '/@/api/platform/core/entity/user'; import { User } from '/@/api/platform/core/entity/user';
import { isEmpty, isUrl } from '/@/utils/is'; import {isBase64image, isEmpty, isUrl} from '/@/utils/is';
import { assignWith } from 'lodash-es'; import { assignWith } from 'lodash-es';
import { useGlobSetting } from '/@/hooks/setting';
const { apiUrl } = useGlobSetting();
interface State { interface State {
baseInfoBtnLoading: boolean; baseInfoBtnLoading: boolean;
@ -93,7 +91,11 @@
async function handleSubmit() { async function handleSubmit() {
try { try {
const formData = await validate(); const formData = await validate();
formData.avatar = state.userInfo.avatar; //
!isBase64image(state.userInfo.avatar) &&
(formData.avatar = isUrl(state.userInfo.avatar)
? state.userInfo.avatar
: state.userInfo.avatar.replace(/\/[^\/]+/, ''));
state.baseInfoBtnLoading = true; state.baseInfoBtnLoading = true;
await editUser(formData); await editUser(formData);
createMessage.success('保存成功!'); createMessage.success('保存成功!');
@ -109,12 +111,7 @@
} }
} }
const getUserInfo = computed((): User & any => state.userInfo || {}); const getUserInfo = computed((): User => state.userInfo || {});
const getAvatarUrl = computed((): string => {
if (!unref(getUserInfo).avatar) return userInfoStore.avatar;
return isUrl(unref(getUserInfo).avatar) ? unref(getUserInfo).avatar : apiUrl + unref(getUserInfo).avatar;
});
function handleAvatarChange({ src, data }) { function handleAvatarChange({ src, data }) {
state.userInfo.avatar = data.url; state.userInfo.avatar = data.url;
@ -127,7 +124,6 @@
commonUpload, commonUpload,
handleAvatarChange, handleAvatarChange,
getContentStyle, getContentStyle,
getAvatarUrl,
handleSubmit handleSubmit
}; };
} }

361
src/views/system/user/userInfo/index.vue

@ -1,361 +0,0 @@
<template>
<div>
<PageWrapper dense
fixedHeight
contentFullHeight
contentClass="flex"
>
<ACard class="base-info w-2/5 xl:w-2/5">
<template #title>
<UserOutlined/> 基本信息
</template>
<div class="user-info-top">
<AAvatar draggable
width="32px"
:src="getAvatarUrl"
:size="80"
/>
<h2>{{ getUserInfo.nickName }}</h2>
<p>{{ getUserInfo.remarks }}</p>
<a-button preIcon="fa6-solid:id-badge"
type="primary"
shape="round"
>{{ getUserInfo.userName }}</a-button>
</div>
<ADivider/>
<div class="user-info-main">
<ADescriptions :labelStyle="getLabelStyle"
:contentStyle="getDescriptionsStyle"
:colon="false"
>
<ADescriptionsItem span="24">
<template #label>
<Icon icon="fa6-solid:building"/>
</template>
{{ getUserInfo.deptName }}
</ADescriptionsItem>
<ADescriptionsItem span="24">
<template #label>
<Icon icon="fa6-solid:envelope"/>
</template>
{{ getUserInfo.email }}
</ADescriptionsItem>
<ADescriptionsItem span="24">
<template #label>
<Icon icon="fa6-solid:mobile"/>
</template>
{{ getUserInfo.phone }}
</ADescriptionsItem>
<ADescriptionsItem span="24">
<template #label>
<Icon icon="fa6-solid:venus-double"/>
</template>
{{ ~~getUserInfo.sex === 0 ? '男' : '女' }}
</ADescriptionsItem>
<ADescriptionsItem span="24">
<template #label>
<Icon icon="fa6-solid:city"/>
</template>
长沙康来公司多租户
</ADescriptionsItem>
<ADescriptionsItem span="24">
<template #label>
<Icon icon="fa6-solid:robot"/>
</template>
<h1 style="margin-right: 5px">企业认证:</h1>
<a v-if="getPushThirdParty.status == '0'">申请中</a>
<a v-else-if="getPushThirdParty.status == '1'">已认证</a>
<a v-else @click="handleCertification">申请</a>
</ADescriptionsItem>
</ADescriptions>
</div>
</ACard>
<ACard class="info-modify w-3/5 xl:w-3/5">
<template #title>
<CreditCardOutlined/> 资料修改
</template>
<ACard :bordered="false"
:tab-list="state.tabList"
:active-tab-key="state.currentCardKey"
@tabChange="key => state.currentCardKey = key"
>
<BasicForm v-show="state.currentCardKey === 'baseInfo'" @register="registerForm"/>
<AForm v-show="state.currentCardKey === 'uploadAvatar'"
ref="formElRef"
class="upload-avatar-form"
:scrollToFirstError="true"
@keypress.enter="handleSubmit"
>
<AFormItem>
<AUpload list-type="picture-card"
class="avatar-uploader"
accept="image/*"
:multiple="false"
:show-upload-list="false"
:customRequest="handleUploadAvatar"
:before-upload="handleBeforeUpload"
>
<img v-if="getImageUrl" :src="getImageUrl">
<div v-else>
<LoadingOutlined v-if="state.uploadAvatarLoading"/>
<PlusOutlined v-else/>
<div class="ant-upload-text">上传头像</div>
</div>
</AUpload>
</AFormItem>
<AFormItem :wrapperCol="{ style: { 'text-align': 'left' }, class: 'avatar-submit-form-item' }">
<a-button preIcon="fa-regular:save"
:loading="state.uploadAvatarBtnLoading"
type="primary"
@click="handleAvatarSubmit"
>保存</a-button>
<a-button preIcon="ant-design:delete-twotone"
danger
type="primary"
:disabled="!state.imageUrl"
@click="state.imageUrl = ''"
>删除</a-button>
</AFormItem>
</AForm>
</ACard>
</ACard>
</PageWrapper>
<ThirdPartyModal @register="registerModal" @success="handleRefreshPushThirdParty"/>
</div>
</template>
<script lang="ts" setup>
import {Avatar, Card, Descriptions, Divider, Form, Upload} from 'ant-design-vue';
import {PageWrapper} from '/@/components/Page';
import {CreditCardOutlined, LoadingOutlined, PlusOutlined, UserOutlined} from '@ant-design/icons-vue';
import type {CSSProperties} from 'vue';
import {computed, reactive, unref} from 'vue';
import {Icon} from '/@/components/Icon';
import {BasicForm, useForm} from '/@/components/Form/index';
import {userFormSchema} from './userInfo.data';
import {useMessage} from '/@/hooks/web/useMessage';
import {editUser, getUser} from '/@/api/platform/system/controller/user';
import type {User} from '/@/api/platform/core/entity/user';
import type {PushThirdParty} from '/@/api/platform/common/entity/pushThirdParty';
import {useUserStore} from '/@/store/modules/user';
import {commonUpload} from '/@/api/platform/core/controller/upload';
import {useGlobSetting} from '/@/hooks/setting';
import {isUrl} from '/@/utils/is';
import {getPushThirdPartyByUserId} from '/@/api/platform/common/controller/pushThirdParty';
import {useModal} from '/@/components/Modal';
import ThirdPartyModal from '/@/views/common/push/pushThirdParty/ThirdPartyModal.vue';
interface InfoState {
currentCardKey: string;
baseInfoBtnLoading: boolean;
uploadAvatarBtnLoading: boolean;
uploadAvatarLoading: boolean;
userInfo: User | any;
pushThirdParty: PushThirdParty | any;
imageUrl: string;
tabList: Recordable[];
}
const ACard = Card;
const AAvatar = Avatar;
const ADescriptions = Descriptions;
const ADescriptionsItem = Descriptions.Item;
const ADivider = Divider;
const AForm = Form;
const AFormItem = Form.Item;
const AUpload = Upload;
const { createMessage } = useMessage();
const userStore = useUserStore();
const userInfo = userStore.getUserInfo;
const { apiUrl } = useGlobSetting();
const state = reactive<InfoState>({
currentCardKey: 'baseInfo',
baseInfoBtnLoading: false,
uploadAvatarLoading: false,
uploadAvatarBtnLoading: false,
userInfo: undefined,
pushThirdParty: undefined,
imageUrl: '',
tabList: [
{
key: 'baseInfo',
tab: '基本信息',
},
{
key: 'uploadAvatar',
tab: '上传头像',
}
]
});
const [registerModal, { openModal }] = useModal();
const defaultAvatarUrl = 'https://godolphinx.org/dolphin1024x1024.png';
const [registerForm, { resetFields, setFieldsValue, updateSchema, validate, clearValidate }] = useForm({
labelWidth: 100,
schemas: userFormSchema,
showSubmitButton: true,
showResetButton: false,
showAdvancedButton: false,
submitButtonOptions: {
text: '保存',
preIcon: 'fa-regular:save',
loading: state.baseInfoBtnLoading,
onClick: handleSubmit
},
actionColOptions: { span: 24 }
});
getUser(userInfo.id).then(result => {
state.userInfo = result.result;
setFieldsValue(result.result);
handleRefreshPushThirdParty();
});
function handleRefreshPushThirdParty() {
getPushThirdPartyByUserId(userInfo.id).then(pushThirdParty => state.pushThirdParty = pushThirdParty);
}
async function handleSubmit() {
try {
const formData = await validate();
state.baseInfoBtnLoading = true;
await editUser(formData);
createMessage.success('保存成功!');
const result = await getUser(userInfo.id);
state.userInfo = result.result;
await setFieldsValue(result.result);
} finally {
state.baseInfoBtnLoading = false;
}
}
async function handleAvatarSubmit() {
try {
state.uploadAvatarBtnLoading = true;
await editUser({
id: userInfo.id,
avatar: state.imageUrl
});
createMessage.success('保存成功!');
const result = await getUser(userInfo.id);
state.userInfo = result.result;
await setFieldsValue(result.result);
} finally {
state.uploadAvatarBtnLoading = false;
}
}
function handleCertification() {
openModal(true,{ _tag: 'add' });
}
const getUserInfo = computed((): User & any => {
let userInfo = state.userInfo || {};
userInfo.avatar || (userInfo.avatar = defaultAvatarUrl);
state!.imageUrl = userInfo.avatar;
return userInfo;
});
const getPushThirdParty = computed((): PushThirdParty & any => state.pushThirdParty || {});
const getAvatarUrl = computed((): string => isUrl(unref(getUserInfo).avatar) ? unref(getUserInfo).avatar : apiUrl + unref(getUserInfo).avatar);
const getImageUrl = computed((): string => (!state.imageUrl || isUrl(state.imageUrl)) ? state.imageUrl : apiUrl + state.imageUrl);
const getLabelStyle = computed((): CSSProperties => ({
fontSize: '14px',
display: 'inline',
width: '40px'
}));
const getDescriptionsStyle = computed((): CSSProperties => ({
fontSize: '20px',
color: '#606266',
'text-align': 'left',
'font-weight': 400,
'line-height': '23px',
'font-size': '14px'
}));
function handleBeforeUpload (file: File) {
const isJpgOrPng = file.type?.includes('image/'),
isLt2M = file.size / 1024 / 1024 < 10;
!isJpgOrPng && createMessage.error('您只能上传 image/* 格式的文件!');
!isLt2M && createMessage.error('图片必须小于 10MB!');
return isJpgOrPng && isLt2M;
}
async function handleUploadAvatar(file) {
try {
state.uploadAvatarLoading = true;
const { data } = await commonUpload({
file: file.file,
name: 'file',
}, () => {});
state.imageUrl = data.url;
} finally {
state.uploadAvatarLoading = false;
}
}
</script>
<style lang="less" scoped>
.base-info {
overflow-y: scroll;
overflow-x: hidden;
margin: 10px 15px 10px 10px;
scrollbar-width: none;
.user-info-top {
padding: 20px 0px;
text-align: center;
h2 {
color: #515a6e;
margin-top: 10px;
font-size: 24px;
}
p {
color: #999;
margin-top: 5px;
font-size: 12px;
font-weight: 0;
}
}
.user-info-main {
padding: 20px 100px;
}
}
.base-info::-webkit-scrollbar {
display: none;
}
.info-modify {
margin: 10px 10px 10px 0px;
::v-deep(.avatar-uploader) > .ant-upload {
width: 180px;
height: 180px;
}
::v-deep(.ant-upload-select-picture-card .ant-upload-text) {
margin-top: 8px;
color: #666;
}
::v-deep(.upload-avatar-form) > .ant-row {
margin-bottom: 0px;
}
::v-deep(.avatar-submit-form-item) button {
margin-left: 6px;
}
}
</style>

96
src/views/system/user/userInfo/userInfo.data.ts

@ -1,96 +0,0 @@
import { FormSchema } from '/@/components/Table';
export const userFormSchema: FormSchema[] = [
{
field: 'id',
label: 'ID',
component: 'Input',
show: false
},
{
field: 'nickName',
label: '用户昵称',
component: 'Input',
required: true,
colProps: {
span: 12
}
},
{
field: 'deptName',
label: '归属机构',
component: 'Input',
required: true,
componentProps: {
disabled: true
},
colProps: {
span: 12
}
},
{
field: 'phone',
label: '手机号',
component: 'Input',
rules: [
{
required: true,
message: '请输入手机号!',
},
{
pattern: new RegExp('^1[3|4|5|6|7|8|9][0-9]\\d{8}$'),
message: '请输入正确的手机号码!',
validateTrigger: 'blur'
}
],
colProps: {
span: 12
}
},
{
field: 'email',
label: '邮箱',
component: 'Input',
rules: [
{
required: true,
message: '请输入邮箱!',
},
{
type: 'email',
message: '请输入正确的邮箱地址!',
validateTrigger: ['blur', 'change']
}
],
colProps: {
span: 12
}
},
{
field: 'sex',
label: '性别',
component: 'RadioGroup',
defaultValue: '0',
required: true,
componentProps: {
options: [
{ label: '男', value: '0' },
{ label: '女', value: '1' }
]
},
colProps: {
span: 12
}
},
{
label: '备注',
field: 'remarks',
component: 'InputTextArea',
componentProps: {
rows: 6
},
colProps: {
span: 24
}
}
];
Loading…
Cancel
Save