feat: 头像裁剪
This commit is contained in:
@ -20,7 +20,12 @@
|
|||||||
|
|
||||||
<!-- 头像设置 -->
|
<!-- 头像设置 -->
|
||||||
<Dropdown trigger="click" overlayClassName="layout-avatar-dropdown">
|
<Dropdown trigger="click" overlayClassName="layout-avatar-dropdown">
|
||||||
<img alt="avatar" src="@/assets/avatar.svg" class="cursor-pointer w-32px h-32px rounded-50%" />
|
<div class="cursor-pointer">
|
||||||
|
<Avatar :src="userInfo.head_image" :size="32" v-if="userInfo.head_image" />
|
||||||
|
<div v-else class="w-32px h-32px rounded-50% bg-#6D4CFE flex items-center justify-center">
|
||||||
|
<span class="color-#FFF text-14px font-400 lh-22px">{{ userInfo.mobile?.slice(-3) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<template #overlay>
|
<template #overlay>
|
||||||
<Menu>
|
<Menu>
|
||||||
<MenuItem>
|
<MenuItem>
|
||||||
@ -126,7 +131,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Dropdown, Menu, MenuItem, SubMenu } from 'ant-design-vue';
|
import { Dropdown, Menu, MenuItem, SubMenu, Avatar } from 'ant-design-vue';
|
||||||
import { useRouter } from 'vue-router'; // import router from '@/router';
|
import { useRouter } from 'vue-router'; // import router from '@/router';
|
||||||
import { useEnterpriseStore } from '@/stores/modules/enterprise';
|
import { useEnterpriseStore } from '@/stores/modules/enterprise';
|
||||||
import { useSidebarStore } from '@/stores/modules/side-bar';
|
import { useSidebarStore } from '@/stores/modules/side-bar';
|
||||||
@ -169,6 +174,7 @@ const primary_enterprise = computed(() => userStore.userInfo?.primary_enterprise
|
|||||||
const enterprises = computed(() => {
|
const enterprises = computed(() => {
|
||||||
return userStore.userInfo?.enterprises ?? [];
|
return userStore.userInfo?.enterprises ?? [];
|
||||||
});
|
});
|
||||||
|
const userInfo = computed(() => userStore.userInfo);
|
||||||
const enterpriseInfo = computed(() => {
|
const enterpriseInfo = computed(() => {
|
||||||
return enterpriseStore?.enterpriseInfo ?? {};
|
return enterpriseStore?.enterpriseInfo ?? {};
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,10 +2,20 @@
|
|||||||
<div class="bg-#fff rounded-16px w-100% p-36px person-wrap">
|
<div class="bg-#fff rounded-16px w-100% p-36px person-wrap">
|
||||||
<p class="title mb-32px">个人信息</p>
|
<p class="title mb-32px">个人信息</p>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="mr-60px relative cursor-pointer" @click="openEditInfoModal">
|
<Upload
|
||||||
<Avatar :src="dataSource.head_image" :size="100" />
|
action="/"
|
||||||
|
:showUploadList="false"
|
||||||
|
accept="image/*"
|
||||||
|
v-if="dataSource"
|
||||||
|
:customRequest="handleUpload"
|
||||||
|
class="mr-60px relative cursor-pointer"
|
||||||
|
>
|
||||||
|
<Avatar :src="dataSource.head_image" :size="100" v-if="dataSource.head_image" />
|
||||||
|
<div v-else class="w-100 h-100 rounded-50% bg-#6D4CFE flex items-center justify-center">
|
||||||
|
<span class="color-#FFF text-46px font-400">{{ dataSource.mobile?.slice(-3) }}</span>
|
||||||
|
</div>
|
||||||
<img :src="icon1" width="20" height="20" class="absolute right-5px bottom-4px" />
|
<img :src="icon1" width="20" height="20" class="absolute right-5px bottom-4px" />
|
||||||
</div>
|
</Upload>
|
||||||
<div class="flex-1 flex h-68px">
|
<div class="flex-1 flex h-68px">
|
||||||
<div class="flex flex-1">
|
<div class="flex flex-1">
|
||||||
<span class="cts mr-4p">昵称:</span>
|
<span class="cts mr-4p">昵称:</span>
|
||||||
@ -15,7 +25,7 @@
|
|||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="flex items-center mb-24px">
|
<div class="flex items-center mb-24px">
|
||||||
<span class="cts mr-4px w-56px">手机号:</span>
|
<span class="cts mr-4px w-56px">手机号:</span>
|
||||||
<span class="cts !color-#211F24 bold !font-500 mr-12px">{{ mobile }}</span>
|
<span class="cts !color-#211F24 bold !font-500 mr-12px">{{ mobileLabel }}</span>
|
||||||
<Button type="text" size="small" class="!p-0 !h-22px m-0" @click="openChangeMobileModal">更改</Button>
|
<Button type="text" size="small" class="!p-0 !h-22px m-0" @click="openChangeMobileModal">更改</Button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
@ -30,85 +40,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- <Table :dataSource="dataSource" :pagination="false" :showSorterTooltip="false" class="mt-8px">-->
|
|
||||||
<!-- <Table.Column title="用户信息" dataIndex="info">-->
|
|
||||||
<!-- <template #customRender="{ record }">-->
|
|
||||||
<!-- <div class="pt-3px pb-3px">-->
|
|
||||||
<!-- <Avatar :src="record.head_image" :size="32" />-->
|
|
||||||
<!-- {{ record.name || '-' }}-->
|
|
||||||
<!-- <icon-edit size="13" class="ml-8px" @click="openEditInfoModal" />-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- </Table.Column>-->
|
|
||||||
<!-- <Table.Column title="手机号" dataIndex="mobile">-->
|
|
||||||
<!-- <template #customRender="{ record }">-->
|
|
||||||
<!-- {{ record.mobile.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') }}-->
|
|
||||||
<!-- <icon-edit size="13" class="ml-8px" @click="openEditMobileModal" />-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- </Table.Column>-->
|
|
||||||
<!-- <template #emptyText>-->
|
|
||||||
<!-- <NoData />-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- </Table>-->
|
|
||||||
<Modal v-model:open="infoVisible" centered title="修改用户信息" @ok="handleSubmitUserInfo">
|
|
||||||
<Form
|
|
||||||
class="form"
|
|
||||||
:rules="rules"
|
|
||||||
:model="userInfoForm"
|
|
||||||
:label-col-props="{ span: 3, offset: 0 }"
|
|
||||||
:wrapper-col-props="{ span: 21, offset: 0 }"
|
|
||||||
>
|
|
||||||
<FormItem name="head_image" label="头像">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<Avatar :src="userInfoForm.file_url" :size="48" />
|
|
||||||
<span class="upload-button" @click="triggerFileInput">
|
|
||||||
<input
|
|
||||||
ref="uploadInputRef"
|
|
||||||
accept="image/*"
|
|
||||||
type="file"
|
|
||||||
style="display: none"
|
|
||||||
@change="handleFileChange"
|
|
||||||
/>
|
|
||||||
<Button><icon-upload />上传新头像</Button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</FormItem>
|
|
||||||
<FormItem name="name" label="昵称">
|
|
||||||
<Input v-model:value="userInfoForm.name" placeholder="请输入昵称" />
|
|
||||||
</FormItem>
|
|
||||||
</Form>
|
|
||||||
</Modal>
|
|
||||||
<Modal v-model:open="imageVisible" centered title="头像裁剪">
|
|
||||||
<VueCropper></VueCropper>
|
|
||||||
</Modal>
|
|
||||||
<Modal v-model:open="mobileVisible" centered title="修改手机号" @ok="handleUpdateMobile">
|
|
||||||
<Form
|
|
||||||
ref="formRef"
|
|
||||||
:model="form"
|
|
||||||
class="form"
|
|
||||||
:rules="formRules"
|
|
||||||
labelAlign="right"
|
|
||||||
:labelCol="{ span: 4 }"
|
|
||||||
:wrapperCol="{ span: 20 }"
|
|
||||||
>
|
|
||||||
<FormItem required name="mobile" label="新手机号">
|
|
||||||
<Input v-model:value="form.mobile" size="small" placeholder="请输入新的手机号" />
|
|
||||||
</FormItem>
|
|
||||||
<FormItem required name="captcha" label="获取验证码">
|
|
||||||
<Input v-model:value="form.captcha" size="small" placeholder="请输入验证码">
|
|
||||||
<template #suffix>
|
|
||||||
<span v-if="countdown <= 0" @click="sendCaptcha">发送验证码</span>
|
|
||||||
<span v-else>{{ countdown }}s</span>
|
|
||||||
</template>
|
|
||||||
</Input>
|
|
||||||
</FormItem>
|
|
||||||
</Form>
|
|
||||||
<PuzzleVerification
|
|
||||||
:show="verificationVisible"
|
|
||||||
@submit="handleVerificationSubmit"
|
|
||||||
@cancel="verificationVisible = false"
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
<ChangePasswordModal ref="changePasswordModalRef" />
|
<ChangePasswordModal ref="changePasswordModalRef" />
|
||||||
<ChangeNameModal ref="changeNameModalRef" />
|
<ChangeNameModal ref="changeNameModalRef" />
|
||||||
@ -116,18 +47,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Avatar, Button, Form, FormItem, Input, message } from 'ant-design-vue';
|
import { Avatar, Button, Upload, message } from 'ant-design-vue';
|
||||||
import Modal from '@/components/modal.vue';
|
|
||||||
import PuzzleVerification from '@/views/login/components/PuzzleVerification.vue';
|
|
||||||
import ChangePasswordModal from './components/change-password-modal/index.vue';
|
import ChangePasswordModal from './components/change-password-modal/index.vue';
|
||||||
import ChangeNameModal from './components/change-name-modal/index.vue';
|
import ChangeNameModal from './components/change-name-modal/index.vue';
|
||||||
import ChangeMobileModal from './components/change-mobile-modal/index.vue';
|
import ChangeMobileModal from './components/change-mobile-modal/index.vue';
|
||||||
|
|
||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
import 'vue-cropper/dist/index.css';
|
|
||||||
import { VueCropper } from 'vue-cropper';
|
|
||||||
import { fetchImageUploadFile, sendUpdateMobileCaptcha, updateMobile, updateMyInfo } from '@/api/all';
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import { fetchImageUploadFile, updateMyInfo } from '@/api/all';
|
||||||
import { useUserStore } from '@/stores';
|
import { useUserStore } from '@/stores';
|
||||||
|
|
||||||
import icon1 from './img/icon1.png';
|
import icon1 from './img/icon1.png';
|
||||||
@ -136,98 +63,21 @@ const store = useUserStore();
|
|||||||
const changePasswordModalRef = ref(null);
|
const changePasswordModalRef = ref(null);
|
||||||
const changeNameModalRef = ref(null);
|
const changeNameModalRef = ref(null);
|
||||||
const changeMobileModalRef = ref(null);
|
const changeMobileModalRef = ref(null);
|
||||||
|
|
||||||
const infoVisible = ref(false);
|
|
||||||
const imageVisible = ref(false);
|
|
||||||
const mobileVisible = ref(false);
|
|
||||||
const verificationVisible = ref(false);
|
|
||||||
const timer = ref();
|
|
||||||
const countdown = ref(0);
|
|
||||||
const formRef = ref();
|
const formRef = ref();
|
||||||
const isSendCaptcha = ref(false);
|
|
||||||
const uploadInputRef = ref();
|
|
||||||
|
|
||||||
const dataSource = computed(() => {
|
const dataSource = computed(() => {
|
||||||
return store.userInfo;
|
return store.userInfo;
|
||||||
});
|
});
|
||||||
const mobile = computed(() => {
|
const mobileLabel = computed(() => {
|
||||||
const _mobile = dataSource.value.mobile;
|
const _mobile = dataSource.value.mobile;
|
||||||
if (!_mobile) return '-';
|
if (!_mobile) return '-';
|
||||||
return _mobile.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
|
return _mobile.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
|
||||||
});
|
});
|
||||||
|
|
||||||
// 表单校验规则
|
// const form = reactive({
|
||||||
const formRules = {
|
// mobile: '',
|
||||||
mobile: [
|
// captcha: '',
|
||||||
{
|
// });
|
||||||
validator: (_rule: any, value: string) => {
|
|
||||||
if (!value) {
|
|
||||||
return Promise.reject('请填写手机号');
|
|
||||||
}
|
|
||||||
if (!/^1[3-9]\d{9}$/.test(value)) {
|
|
||||||
return Promise.reject('手机号格式不正确');
|
|
||||||
}
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
required: true,
|
|
||||||
trigger: ['blur', 'change'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
captcha: [
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
validator: (rule, value) => {
|
|
||||||
if (!value) {
|
|
||||||
return Promise.reject('请填写验证码');
|
|
||||||
}
|
|
||||||
if (!/^\d{6}$/.test(value)) {
|
|
||||||
return Promise.reject('验证码必须是6位数字');
|
|
||||||
}
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
|
|
||||||
trigger: ['blur', 'change'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const form = reactive({
|
|
||||||
mobile: '',
|
|
||||||
captcha: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
const userInfoForm = reactive({
|
|
||||||
name: '',
|
|
||||||
head_image: '',
|
|
||||||
file_url: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
function triggerFileInput() {
|
|
||||||
uploadInputRef.value.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
function openEditInfoModal() {
|
|
||||||
infoVisible.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleFileChange(event: Event) {
|
|
||||||
const target = event.target as HTMLInputElement;
|
|
||||||
const file = target.files?.[0];
|
|
||||||
if (file) {
|
|
||||||
const fileExtension = getFileExtension(file.name);
|
|
||||||
const { data } = await fetchImageUploadFile({
|
|
||||||
suffix: fileExtension,
|
|
||||||
});
|
|
||||||
const { upload_url, file_name, file_url } = data;
|
|
||||||
|
|
||||||
const blob = new Blob([file], { type: file.type });
|
|
||||||
await axios.put(upload_url, blob, {
|
|
||||||
headers: { 'Content-Type': file.type },
|
|
||||||
});
|
|
||||||
userInfoForm.head_image = file_name;
|
|
||||||
userInfoForm.file_url = file_url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const openChangeNameModal = () => {
|
const openChangeNameModal = () => {
|
||||||
changeNameModalRef.value.open(dataSource.name);
|
changeNameModalRef.value.open(dataSource.name);
|
||||||
@ -239,123 +89,38 @@ const openChangePasswordModal = () => {
|
|||||||
changePasswordModalRef.value.open(dataSource.value.mobile);
|
changePasswordModalRef.value.open(dataSource.value.mobile);
|
||||||
};
|
};
|
||||||
|
|
||||||
async function handleSubmitUserInfo() {
|
const getFileExtension: string = (filename: string) => {
|
||||||
await updateMyInfo(userInfoForm);
|
|
||||||
message.success('修改成功!');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sendCaptcha() {
|
|
||||||
try {
|
|
||||||
const result = await formRef.value.validateField('mobile');
|
|
||||||
if (result === true || result === undefined) {
|
|
||||||
verificationVisible.value = true;
|
|
||||||
isSendCaptcha.value = true;
|
|
||||||
}
|
|
||||||
message.error('请填写正确的手机号!');
|
|
||||||
} catch (error) {
|
|
||||||
console.log('手机号验证失败:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function beginCountdown() {
|
|
||||||
timer.value = setInterval(() => {
|
|
||||||
countdown.value--;
|
|
||||||
if (countdown.value <= 0) {
|
|
||||||
clearInterval(timer.value);
|
|
||||||
timer.value = null;
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleVerificationSubmit() {
|
|
||||||
await sendUpdateMobileCaptcha({ mobile: form.mobile });
|
|
||||||
message.success('发送成功');
|
|
||||||
verificationVisible.value = false;
|
|
||||||
countdown.value = 60;
|
|
||||||
beginCountdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFileExtension(filename: string): string {
|
|
||||||
const match = filename.match(/\.([^.]+)$/);
|
const match = filename.match(/\.([^.]+)$/);
|
||||||
return match ? match[1].toLowerCase() : '';
|
return match ? match[1].toLowerCase() : '';
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const handleUpload = async (option) => {
|
||||||
|
const { file } = option;
|
||||||
|
try {
|
||||||
|
if (file) {
|
||||||
|
const fileExtension = getFileExtension(file.name);
|
||||||
|
const { data } = await fetchImageUploadFile({
|
||||||
|
suffix: fileExtension,
|
||||||
|
});
|
||||||
|
const { upload_url, file_url } = data;
|
||||||
|
|
||||||
|
const blob = new Blob([file], { type: file.type });
|
||||||
|
await axios.put(upload_url, blob, {
|
||||||
|
headers: { 'Content-Type': file.type },
|
||||||
|
});
|
||||||
|
|
||||||
|
const { code } = await updateMyInfo({ head_image: file_url });
|
||||||
|
if (code === 200) {
|
||||||
|
message.success('修改成功');
|
||||||
|
store.userInfo.head_image = file_url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
message.error('上传失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@import './style.scss';
|
@import './style.scss';
|
||||||
</style>
|
</style>
|
||||||
<style scoped lang="scss">
|
|
||||||
.form {
|
|
||||||
margin-top: 13px;
|
|
||||||
|
|
||||||
:deep(.arco-row) {
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.arco-form-item-label) {
|
|
||||||
font-family: $font-family-medium;
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 14px;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.arco-input-wrapper) {
|
|
||||||
background: white;
|
|
||||||
border: 1px solid var(--BG-400, rgba(215, 215, 217, 1));
|
|
||||||
font-family: $font-family-medium;
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 4px 12px;
|
|
||||||
|
|
||||||
input::placeholder {
|
|
||||||
font-family: $font-family-medium;
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--Text-4, rgba(147, 148, 153, 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.arco-input-disabled) {
|
|
||||||
background: var(--BG-200, rgba(242, 243, 245, 1));
|
|
||||||
border: 1px solid var(--BG-400, rgba(215, 215, 217, 1));
|
|
||||||
|
|
||||||
.arco-input:disabled {
|
|
||||||
font-family: $font-family-medium;
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.arco-input-focus) {
|
|
||||||
border: 1px solid var(--Brand-Brand-6, rgba(109, 76, 254, 1));
|
|
||||||
box-shadow: 0 2px 4px 0 rgba(109, 76, 254, 0.2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-button {
|
|
||||||
width: 104px;
|
|
||||||
height: 24px;
|
|
||||||
margin-left: 12px;
|
|
||||||
|
|
||||||
:deep(.arco-btn) {
|
|
||||||
width: 104px;
|
|
||||||
height: 24px;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 2px 12px;
|
|
||||||
border: 1px solid var(--BG-500, rgba(177, 178, 181, 1));
|
|
||||||
font-family: $font-family-medium;
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 12px;
|
|
||||||
vertical-align: middle;
|
|
||||||
color: var(--Text-2, rgba(60, 64, 67, 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
color: var(--Text-1, #211f24);
|
|
||||||
font-family: $font-family-medium;
|
|
||||||
font-size: 18px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 26px; /* 150% */
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@ -1,4 +1,12 @@
|
|||||||
.person-wrap {
|
.person-wrap {
|
||||||
|
.title {
|
||||||
|
color: var(--Text-1, #211f24);
|
||||||
|
font-family: $font-family-medium;
|
||||||
|
font-size: 18px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 26px; /* 150% */
|
||||||
|
}
|
||||||
.cts {
|
.cts {
|
||||||
color: var(--Text-4, #939499);
|
color: var(--Text-4, #939499);
|
||||||
font-family: $font-family-regular;
|
font-family: $font-family-regular;
|
||||||
@ -10,4 +18,5 @@
|
|||||||
font-family: $font-family-medium;
|
font-family: $font-family-medium;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user