Files
lingji-work-fe/src/views/components/management/person/index.vue

392 lines
11 KiB
Vue
Raw Normal View History

<template>
2025-09-11 18:16:05 +08:00
<div class="bg-#fff rounded-16px w-100% p-36px person-wrap">
2025-09-10 14:29:18 +08:00
<p class="title mb-32px">个人信息</p>
2025-09-11 18:16:05 +08:00
<div class="flex items-center">
<div class="mr-60px relative cursor-pointer" @click="openEditInfoModal">
<Avatar :src="dataSource.head_image" :size="100" />
<img :src="icon1" width="20" height="20" class="absolute right-5px bottom-4px" />
</div>
<div class="flex-1 flex h-68px">
<div class="flex flex-1">
<span class="cts mr-4p">昵称</span>
<span class="cts !color-#211F24 bold !font-500 mr-12px">{{ dataSource.name || '-' }}</span>
<Button type="text" size="small" class="!p-0 !h-22px m-0" @click="openChangeNameModal">编辑</Button>
</div>
<div class="flex-1">
<div class="flex items-center mb-24px">
<span class="cts mr-4px w-56px">手机号</span>
<span class="cts !color-#211F24 bold !font-500 mr-12px">{{ mobile }}</span>
<Button type="text" size="small" class="!p-0 !h-22px m-0" @click="openChangeMobileModal">更改</Button>
2025-09-04 18:05:16 +08:00
</div>
2025-09-11 18:16:05 +08:00
<div class="flex items-center">
<span class="cts mr-4px w-56px">密码</span>
<span
class="!bg-#211F24 bold !font-500 w-6px h-6px rounded-50% mr-2px"
v-for="(item, index) in new Array(10).fill(0)"
:key="index"
></span>
<Button type="text" size="small" class="!p-0 !h-22px ml-10px">更改</Button>
</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>-->
2025-09-03 12:04:15 +08:00
<Modal v-model:open="infoVisible" centered title="修改用户信息" @ok="handleSubmitUserInfo">
2025-09-03 16:28:19 +08:00
<Form
class="form"
2025-09-04 11:07:21 +08:00
:rules="rules"
:model="userInfoForm"
:label-col-props="{ span: 3, offset: 0 }"
:wrapper-col-props="{ span: 21, offset: 0 }"
>
2025-09-03 16:28:19 +08:00
<FormItem name="head_image" label="头像">
2025-07-16 17:34:27 +08:00
<div class="flex items-center">
2025-09-05 16:41:50 +08:00
<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"
/>
2025-09-03 11:15:37 +08:00
<Button><icon-upload />上传新头像</Button>
</span>
</div>
2025-09-03 16:28:19 +08:00
</FormItem>
<FormItem name="name" label="昵称">
2025-09-04 11:07:21 +08:00
<Input v-model:value="userInfoForm.name" placeholder="请输入昵称" />
2025-09-03 16:28:19 +08:00
</FormItem>
</Form>
</Modal>
2025-09-03 12:04:15 +08:00
<Modal v-model:open="imageVisible" centered title="头像裁剪">
<VueCropper></VueCropper>
</Modal>
2025-09-03 12:04:15 +08:00
<Modal v-model:open="mobileVisible" centered title="修改手机号" @ok="handleUpdateMobile">
2025-09-03 16:28:19 +08:00
<Form
ref="formRef"
:model="form"
class="form"
:rules="formRules"
2025-09-04 11:07:21 +08:00
labelAlign="right"
:labelCol="{ span: 4 }"
:wrapperCol="{ span: 20 }"
>
2025-09-03 16:28:19 +08:00
<FormItem required name="mobile" label="新手机号">
2025-09-04 11:07:21 +08:00
<Input v-model:value="form.mobile" size="small" placeholder="请输入新的手机号" />
2025-09-03 16:28:19 +08:00
</FormItem>
<FormItem required name="captcha" label="获取验证码">
2025-09-04 11:07:21 +08:00
<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>
2025-09-04 11:07:21 +08:00
</Input>
2025-09-03 16:28:19 +08:00
</FormItem>
</Form>
<PuzzleVerification
:show="verificationVisible"
@submit="handleVerificationSubmit"
@cancel="verificationVisible = false"
/>
</Modal>
2025-09-11 18:16:05 +08:00
<SafetyVerificationModal
ref="safetyVerificationModalRef"
@success="handleVerifySuccess"
@cancel="handleVerifyCancel"
/>
<ChangeNameModal ref="changeNameModalRef" />
<ChangeMobileModal ref="changeMobileModalRef" />
2025-07-01 17:28:18 +08:00
</div>
</template>
<script setup lang="ts">
2025-09-11 18:16:05 +08:00
import { Avatar, Button, Form, FormItem, Input, message } from 'ant-design-vue';
import Modal from '@/components/modal.vue';
2025-09-10 09:09:06 +08:00
import PuzzleVerification from '@/views/login/components/PuzzleVerification.vue';
2025-09-11 18:16:05 +08:00
import SafetyVerificationModal from './components/safety-verification-modal/index.vue';
import ChangeNameModal from './components/change-name-modal/index.vue';
import ChangeMobileModal from './components/change-mobile-modal/index.vue';
import { reactive, ref } from 'vue';
import 'vue-cropper/dist/index.css';
import { VueCropper } from 'vue-cropper';
2025-09-11 18:16:05 +08:00
import { fetchImageUploadFile, sendUpdateMobileCaptcha, updateMobile, updateMyInfo } from '@/api/all';
import axios from 'axios';
import { useUserStore } from '@/stores';
2025-09-11 18:16:05 +08:00
import icon1 from './img/icon1.png';
const store = useUserStore();
2025-09-11 18:16:05 +08:00
const safetyVerificationModalRef = ref(null);
const changeNameModalRef = ref(null);
const changeMobileModalRef = ref(null);
2025-09-11 11:26:51 +08:00
// const userInfo = computed(() => {
// return store.userInfo ?? {};
// });
const columns = [
{
title: '用户信息',
slotName: 'info',
},
{
title: '手机号',
slotName: 'mobile',
},
];
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 isSendCaptcha = ref(false);
const uploadInputRef = ref();
const dataSource = computed(() => {
2025-09-11 18:16:05 +08:00
return store.userInfo;
});
const mobile = computed(() => {
const _mobile = dataSource.value.mobile;
if (!_mobile) return '-';
return _mobile.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
});
2025-09-11 18:16:05 +08:00
console.log(store.userInfo);
2025-09-11 11:26:51 +08:00
// 表单校验规则
const formRules = {
mobile: [
{
2025-09-04 11:07:21 +08:00
validator: (_rule: any, value: string) => {
if (!value) {
return Promise.reject('请填写手机号');
}
if (!/^1[3-9]\d{9}$/.test(value)) {
2025-09-04 11:07:21 +08:00
return Promise.reject('手机号格式不正确');
}
2025-09-04 11:07:21 +08:00
return Promise.resolve();
},
2025-09-04 11:07:21 +08:00
required: true,
trigger: ['blur', 'change'],
},
],
captcha: [
{
required: true,
2025-09-04 11:07:21 +08:00
validator: (rule, value) => {
if (!value) {
return Promise.reject('请填写验证码');
}
if (!/^\d{6}$/.test(value)) {
2025-09-04 11:07:21 +08:00
return Promise.reject('验证码必须是6位数字');
}
2025-09-04 11:07:21 +08:00
return Promise.resolve();
},
2025-09-04 11:07:21 +08:00
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;
}
}
2025-09-11 18:16:05 +08:00
const openChangeNameModal = () => {
changeNameModalRef.value.open(dataSource.name);
};
const openChangeMobileModal = () => {
changeMobileModalRef.value.open();
};
async function handleSubmitUserInfo() {
await updateMyInfo(userInfoForm);
2025-09-05 11:30:31 +08:00
message.success('修改成功!');
}
async function sendCaptcha() {
try {
const result = await formRef.value.validateField('mobile');
if (result === true || result === undefined) {
verificationVisible.value = true;
isSendCaptcha.value = true;
}
2025-09-05 11:30:31 +08:00
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 });
2025-09-05 11:30:31 +08:00
message.success('发送成功');
verificationVisible.value = false;
countdown.value = 60;
beginCountdown();
}
async function handleUpdateMobile() {
if (!isSendCaptcha.value) {
2025-09-05 11:30:31 +08:00
message.error('请先获取验证码!');
return false;
}
const res = await formRef.value.validate();
if (res === true || res === undefined) {
await updateMobile(form);
2025-09-05 11:30:31 +08:00
message.success('修改成功!');
}
}
function getFileExtension(filename: string): string {
const match = filename.match(/\.([^.]+)$/);
return match ? match[1].toLowerCase() : '';
}
</script>
2025-07-01 17:28:18 +08:00
<style scoped lang="scss">
2025-09-11 18:16:05 +08:00
@import './style.scss';
</style>
<style scoped lang="scss">
.form {
margin-top: 13px;
2025-09-11 18:16:05 +08:00
:deep(.arco-row) {
align-items: center;
}
2025-09-11 18:16:05 +08:00
:deep(.arco-form-item-label) {
font-family: $font-family-medium;
font-weight: 400;
font-size: 14px;
margin: 0;
}
2025-09-11 18:16:05 +08:00
: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;
2025-09-11 18:16:05 +08:00
input::placeholder {
font-family: $font-family-medium;
font-weight: 400;
font-size: 14px;
color: var(--Text-4, rgba(147, 148, 153, 1));
}
}
2025-09-11 18:16:05 +08:00
:deep(.arco-input-disabled) {
background: var(--BG-200, rgba(242, 243, 245, 1));
border: 1px solid var(--BG-400, rgba(215, 215, 217, 1));
2025-09-11 18:16:05 +08:00
.arco-input:disabled {
font-family: $font-family-medium;
font-weight: 400;
font-size: 14px;
}
}
2025-09-11 18:16:05 +08:00
: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);
}
}
2025-09-11 18:16:05 +08:00
.upload-button {
width: 104px;
height: 24px;
margin-left: 12px;
2025-09-11 18:16:05 +08:00
: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));
}
}
2025-09-11 18:16:05 +08:00
2025-09-10 14:29:18 +08:00
.title {
color: var(--Text-1, #211f24);
font-family: $font-family-medium;
font-size: 18px;
font-style: normal;
font-weight: 500;
line-height: 26px; /* 150% */
2025-07-01 17:28:18 +08:00
}
</style>