|
@@ -38,12 +38,11 @@
|
|
|
</van-form>
|
|
</van-form>
|
|
|
<van-form ref="personInfoRef" v-show="state.active == 1" required="auto">
|
|
<van-form ref="personInfoRef" v-show="state.active == 1" required="auto">
|
|
|
<van-cell-group inset>
|
|
<van-cell-group inset>
|
|
|
- <van-field name="avatar" label="头像" :rules="[{ required: true, message: '请上传头像' }]">
|
|
|
|
|
- <template #input>
|
|
|
|
|
- <van-uploader v-model="state.form.fileList" multiple :max-count="1" :before-read="beforeRead"
|
|
|
|
|
- :after-read="afterRead" />
|
|
|
|
|
|
|
+ <van-cell class="flex" is-link size="normal" title="头像" @click="editAvatar" required>
|
|
|
|
|
+ <template #value>
|
|
|
|
|
+ <van-image width="30px" height="30px" :src="state.form.avatar || '/src/assets/img/avatar.png'" />
|
|
|
</template>
|
|
</template>
|
|
|
- </van-field>
|
|
|
|
|
|
|
+ </van-cell>
|
|
|
|
|
|
|
|
<van-field v-model="state.form.nickName" label="姓名" placeholder="姓名"
|
|
<van-field v-model="state.form.nickName" label="姓名" placeholder="姓名"
|
|
|
:rules="[{ required: true, message: '请填写姓名' }]" />
|
|
:rules="[{ required: true, message: '请填写姓名' }]" />
|
|
@@ -214,6 +213,22 @@
|
|
|
<van-picker :columns="pjtTypeList" :columns-field-names="{ text: 'dictLabel', value: 'dictValue' }"
|
|
<van-picker :columns="pjtTypeList" :columns-field-names="{ text: 'dictLabel', value: 'dictValue' }"
|
|
|
@confirm="onPjtTypePicker" @cancel="showPjtTypePicker = false" />
|
|
@confirm="onPjtTypePicker" @cancel="showPjtTypePicker = false" />
|
|
|
</van-popup>
|
|
</van-popup>
|
|
|
|
|
+ <!-- 剪裁图片组件 -->
|
|
|
|
|
+ <van-popup class="bg-tran" v-model:show="cropperState.isShowDialog" closeable @close="closeDialog" position="bottom"
|
|
|
|
|
+ :style="{ padding: '10px' }">
|
|
|
|
|
+ <div class="cropper-warp">
|
|
|
|
|
+ <div class="cropper-warp-left">
|
|
|
|
|
+ <img :src="typeof cropperState.cropperImg === 'string' ? cropperState.cropperImg : ''"
|
|
|
|
|
+ class="cropper-warp-left-img" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <van-row align="center" justify="end" class="mt10">
|
|
|
|
|
+ <van-uploader :after-read="afterRead">
|
|
|
|
|
+ <van-button icon="plus">上传头像</van-button>
|
|
|
|
|
+ </van-uploader>
|
|
|
|
|
+ <van-button type="primary" @click="onAvatarSubmit" class="ml10">确 定</van-button>
|
|
|
|
|
+ </van-row>
|
|
|
|
|
+ </van-popup>
|
|
|
<van-notify v-model:show="show" type="danger">
|
|
<van-notify v-model:show="show" type="danger">
|
|
|
<span>操作失败</span>
|
|
<span>操作失败</span>
|
|
|
</van-notify>
|
|
</van-notify>
|
|
@@ -221,18 +236,23 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script name="register" lang="ts" setup>
|
|
<script name="register" lang="ts" setup>
|
|
|
- import { onMounted, reactive, ref, watch } from 'vue'
|
|
|
|
|
- import to from 'await-to-js'
|
|
|
|
|
- import { useLoginApi } from '/@/api/login/index'
|
|
|
|
|
- import crypto from 'sm-crypto'
|
|
|
|
|
- import { useDictApi } from '/@/api/system/dict'
|
|
|
|
|
- import { useProApi } from '/@/api/project'
|
|
|
|
|
- import { useDeptApi } from '/@/api/system/dept'
|
|
|
|
|
- import { useRouter, useRoute } from 'vue-router'
|
|
|
|
|
- import { showNotify } from 'vant'
|
|
|
|
|
- import { enhancedEncrypt, decryptLoginData, encryptWithBackendConfig } from '/@/utils/aesCrypto';
|
|
|
|
|
- import { UserTypeTooltip } from '/@/constants/pageConstants'
|
|
|
|
|
- import { isPasswordValid } from '/@/utils/stringUtils'
|
|
|
|
|
|
|
+ import { onMounted, reactive, ref, watch, nextTick } from 'vue'
|
|
|
|
|
+import to from 'await-to-js'
|
|
|
|
|
+import { useLoginApi } from '/@/api/login/index'
|
|
|
|
|
+import crypto from 'sm-crypto'
|
|
|
|
|
+import { useDictApi } from '/@/api/system/dict'
|
|
|
|
|
+import { useProApi } from '/@/api/project'
|
|
|
|
|
+import { useDeptApi } from '/@/api/system/dept'
|
|
|
|
|
+import { useRouter, useRoute } from 'vue-router'
|
|
|
|
|
+import { showNotify } from 'vant'
|
|
|
|
|
+import { enhancedEncrypt, decryptLoginData, encryptWithBackendConfig } from '/@/utils/aesCrypto';
|
|
|
|
|
+import { UserTypeTooltip } from '/@/constants/pageConstants'
|
|
|
|
|
+import { isPasswordValid } from '/@/utils/stringUtils'
|
|
|
|
|
+import Cropper from 'cropperjs'
|
|
|
|
|
+import 'cropperjs/dist/cropper.css'
|
|
|
|
|
+import axios from 'axios'
|
|
|
|
|
+import { userImgSize } from '/@/constants/pageConstants'
|
|
|
|
|
+import { getResourceUrl } from '/@/utils/url'
|
|
|
|
|
|
|
|
const uploadUrl = import.meta.env.VITE_UPLOAD;
|
|
const uploadUrl = import.meta.env.VITE_UPLOAD;
|
|
|
const sm3 = crypto.sm3
|
|
const sm3 = crypto.sm3
|
|
@@ -316,6 +336,14 @@
|
|
|
})
|
|
})
|
|
|
const deptDataBackup = ref(<any[]>[])
|
|
const deptDataBackup = ref(<any[]>[])
|
|
|
|
|
|
|
|
|
|
+ // 定义裁剪相关状态
|
|
|
|
|
+ const cropperState = reactive({
|
|
|
|
|
+ isShowDialog: false,
|
|
|
|
|
+ cropperImg: '' as ArrayBuffer | string,
|
|
|
|
|
+ cropperImgBase64: '',
|
|
|
|
|
+ cropper: '' as any
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
const getDicts = () => {
|
|
const getDicts = () => {
|
|
|
Promise.all([
|
|
Promise.all([
|
|
|
dictApi.getDictDataByType('sys_user_type'),
|
|
dictApi.getDictDataByType('sys_user_type'),
|
|
@@ -609,6 +637,150 @@
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // 打开头像编辑弹窗
|
|
|
|
|
+ const editAvatar = () => {
|
|
|
|
|
+ const src = state.form.avatar || ''
|
|
|
|
|
+ const fullSrc = getResourceUrl(src)
|
|
|
|
|
+ openDialog(fullSrc)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 打开弹窗
|
|
|
|
|
+ const openDialog = (imgs: string) => {
|
|
|
|
|
+ cropperState.cropperImg = imgs
|
|
|
|
|
+ cropperState.isShowDialog = true
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ initCropper()
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 初始化cropperjs图片裁剪
|
|
|
|
|
+ const initCropper = () => {
|
|
|
|
|
+ const letImg = <HTMLImageElement>document.querySelector('.cropper-warp-left-img')
|
|
|
|
|
+ cropperState.cropper = new Cropper(letImg, {
|
|
|
|
|
+ viewMode: 1,
|
|
|
|
|
+ dragMode: 'move',
|
|
|
|
|
+ initialAspectRatio: 1,
|
|
|
|
|
+ aspectRatio: 1,
|
|
|
|
|
+ preview: '.before',
|
|
|
|
|
+ background: false,
|
|
|
|
|
+ autoCropArea: 1,
|
|
|
|
|
+ cropBoxMovable: true, // 是否允许剪裁框拖动
|
|
|
|
|
+ cropBoxResizable: false, // 是否允许剪裁框缩放
|
|
|
|
|
+ zoomable: true,
|
|
|
|
|
+ ready() {
|
|
|
|
|
+ cropperState.cropper.setCropBoxData({
|
|
|
|
|
+ width: 500, // 宽度
|
|
|
|
|
+ height: 500 // 高度
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ crop: () => {
|
|
|
|
|
+ cropperState.cropperImgBase64 = cropperState.cropper
|
|
|
|
|
+ .getCroppedCanvas({ width: 500, height: 500 })
|
|
|
|
|
+ .toDataURL('image/jpeg')
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 上传头像处理
|
|
|
|
|
+ const afterRead = (res: any) => {
|
|
|
|
|
+ const rawFile = res.file
|
|
|
|
|
+ if (
|
|
|
|
|
+ rawFile.type !== 'image/jpeg' &&
|
|
|
|
|
+ rawFile.type !== 'image/jpg' &&
|
|
|
|
|
+ rawFile.type !== 'image/png' &&
|
|
|
|
|
+ rawFile.type !== 'image/bmp' &&
|
|
|
|
|
+ rawFile.type !== 'image/gif'
|
|
|
|
|
+ ) {
|
|
|
|
|
+ showNotify({
|
|
|
|
|
+ message: '上传图片必须是JPG/PNG/BMP/GIF类型!',
|
|
|
|
|
+ type: 'warning'
|
|
|
|
|
+ })
|
|
|
|
|
+ return false
|
|
|
|
|
+ } else if (rawFile.size / 1024 / 1024 > userImgSize) {
|
|
|
|
|
+ showNotify({
|
|
|
|
|
+ message: `图片大小不能超过${userImgSize}MB!`,
|
|
|
|
|
+ type: 'warning'
|
|
|
|
|
+ })
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+ const reader = new FileReader()
|
|
|
|
|
+ reader.readAsDataURL(rawFile)
|
|
|
|
|
+ reader.onload = () => {
|
|
|
|
|
+ cropperState.cropperImg = reader.result
|
|
|
|
|
+ cropperState.cropper.destroy()
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ initCropper()
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 提交头像裁剪
|
|
|
|
|
+ const onAvatarSubmit = () => {
|
|
|
|
|
+ const base64Url = cropperState.cropper.getCroppedCanvas({ width: 500, height: 500 }).toDataURL('image/jpeg')
|
|
|
|
|
+ let file = dataURLtoFile(base64Url, 'avatar.png')
|
|
|
|
|
+ uploadBaseFunc(file)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // base64转file
|
|
|
|
|
+ const dataURLtoFile = (dataurl: string, filename: string) => {
|
|
|
|
|
+ // 获取到base64编码
|
|
|
|
|
+ const arr = dataurl.split(',')
|
|
|
|
|
+ // 将base64编码转为字符串
|
|
|
|
|
+ const bstr = window.atob(arr[1])
|
|
|
|
|
+ let n = bstr.length
|
|
|
|
|
+ const u8arr = new Uint8Array(n) // 创建初始化为0的,包含length个元素的无符号整型数组
|
|
|
|
|
+ while (n--) {
|
|
|
|
|
+ u8arr[n] = bstr.charCodeAt(n)
|
|
|
|
|
+ }
|
|
|
|
|
+ return new File([u8arr], filename, {
|
|
|
|
|
+ type: 'image/png'
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 上传图片获取地址
|
|
|
|
|
+ const uploadBaseFunc = (file: File) => {
|
|
|
|
|
+ const formData = new FormData()
|
|
|
|
|
+ formData.append('file', file)
|
|
|
|
|
+ axios
|
|
|
|
|
+ .post(uploadUrl, formData, {
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ 'Content-Type': 'multipart/form-data'
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ .then(async (res) => {
|
|
|
|
|
+ if (res) {
|
|
|
|
|
+ if (res.data.Code == 200) {
|
|
|
|
|
+ // 图片上传成功,直接修改
|
|
|
|
|
+ state.form.avatar = res?.data.Data || '' // 更新表单中的头像字段
|
|
|
|
|
+ showNotify({
|
|
|
|
|
+ type: 'success',
|
|
|
|
|
+ message: '头像上传成功'
|
|
|
|
|
+ })
|
|
|
|
|
+ closeDialog()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch(() => {
|
|
|
|
|
+ showNotify({
|
|
|
|
|
+ type: 'warning',
|
|
|
|
|
+ message: '上传失败'
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 关闭弹窗
|
|
|
|
|
+ const closeDialog = () => {
|
|
|
|
|
+ if (cropperState.cropper) {
|
|
|
|
|
+ cropperState.cropper.destroy()
|
|
|
|
|
+ cropperState.isShowDialog = false
|
|
|
|
|
+ cropperState.cropperImg = ''
|
|
|
|
|
+ cropperState.cropperImgBase64 = ''
|
|
|
|
|
+ cropperState.cropper = ''
|
|
|
|
|
+ cropperState.isShowDialog = false
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
const beforeRead = (file: any) => {
|
|
const beforeRead = (file: any) => {
|
|
|
if (
|
|
if (
|
|
|
file.type !== 'image/jpeg' &&
|
|
file.type !== 'image/jpeg' &&
|
|
@@ -632,118 +804,6 @@
|
|
|
return true;
|
|
return true;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- // 图片压缩函数(与用户信息修改页面保持一致)
|
|
|
|
|
- const compressImage = (file: File): Promise<File> => {
|
|
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
|
|
- const img = new Image();
|
|
|
|
|
- const reader = new FileReader();
|
|
|
|
|
-
|
|
|
|
|
- reader.onload = (e: any) => {
|
|
|
|
|
- img.src = e.target.result;
|
|
|
|
|
- img.onload = () => {
|
|
|
|
|
- const canvas = document.createElement('canvas');
|
|
|
|
|
- const ctx = canvas.getContext('2d');
|
|
|
|
|
-
|
|
|
|
|
- // 设置最大尺寸为500px(与用户信息修改页面保持一致)
|
|
|
|
|
- const MAX_SIZE = 500;
|
|
|
|
|
- let width = img.width;
|
|
|
|
|
- let height = img.height;
|
|
|
|
|
-
|
|
|
|
|
- if (width > height) {
|
|
|
|
|
- if (width > MAX_SIZE) {
|
|
|
|
|
- height *= MAX_SIZE / width;
|
|
|
|
|
- width = MAX_SIZE;
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- if (height > MAX_SIZE) {
|
|
|
|
|
- width *= MAX_SIZE / height;
|
|
|
|
|
- height = MAX_SIZE;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- canvas.width = width;
|
|
|
|
|
- canvas.height = height;
|
|
|
|
|
- ctx!.drawImage(img, 0, 0, width, height);
|
|
|
|
|
-
|
|
|
|
|
- // 转换为JPEG格式并压缩(质量0.8)
|
|
|
|
|
- canvas.toBlob((blob) => {
|
|
|
|
|
- if (blob) {
|
|
|
|
|
- resolve(new File([blob], file.name, { type: 'image/jpeg' }));
|
|
|
|
|
- } else {
|
|
|
|
|
- reject(new Error('图片压缩失败'));
|
|
|
|
|
- }
|
|
|
|
|
- }, 'image/jpeg', 0.8);
|
|
|
|
|
- };
|
|
|
|
|
- img.onerror = reject;
|
|
|
|
|
- };
|
|
|
|
|
- reader.onerror = reject;
|
|
|
|
|
- reader.readAsDataURL(file);
|
|
|
|
|
- });
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- // 上传头像方法(添加压缩处理)
|
|
|
|
|
- const afterRead = async (file: any) => {
|
|
|
|
|
- try {
|
|
|
|
|
- // 先压缩图片(与用户信息修改页面保持一致)
|
|
|
|
|
- const compressedFile = await compressImage(file.file);
|
|
|
|
|
-
|
|
|
|
|
- // 创建FormData对象
|
|
|
|
|
- const formData = new FormData();
|
|
|
|
|
- formData.append('file', compressedFile);
|
|
|
|
|
-
|
|
|
|
|
- // 使用fetch上传文件
|
|
|
|
|
- const response = await fetch(uploadUrl, {
|
|
|
|
|
- method: 'POST',
|
|
|
|
|
- body: formData
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- if (response.ok) {
|
|
|
|
|
- const result = await response.json();
|
|
|
|
|
- console.log(result);
|
|
|
|
|
- // 假设服务器返回的格式为 { code: 200, data: { url: "文件URL" } }
|
|
|
|
|
- if (result.Code === 200 && result.Data) {
|
|
|
|
|
- // 清空之前的头像,只保留最新上传的
|
|
|
|
|
- state.form.avatar = result.Data;
|
|
|
|
|
- state.form.fileList = [{
|
|
|
|
|
- url: result.Data,
|
|
|
|
|
- isImage: true
|
|
|
|
|
- }]
|
|
|
|
|
- // 触发表单验证,更新头像字段的验证状态
|
|
|
|
|
- personInfoRef.value?.validate('avatar').catch(() => { });
|
|
|
|
|
- showNotify({
|
|
|
|
|
- type: 'success',
|
|
|
|
|
- message: '头像上传成功'
|
|
|
|
|
- });
|
|
|
|
|
- } else {
|
|
|
|
|
- // 上传失败,清空头像
|
|
|
|
|
- state.form.avatar = '';
|
|
|
|
|
- state.form.fileList = [];
|
|
|
|
|
- showNotify({
|
|
|
|
|
- type: 'danger',
|
|
|
|
|
- message: '头像上传失败:' + (result.message || '未知错误')
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- // 上传失败,清空头像
|
|
|
|
|
- state.form.avatar = '';
|
|
|
|
|
- state.form.fileList = [];
|
|
|
|
|
- showNotify({
|
|
|
|
|
- type: 'danger',
|
|
|
|
|
- message: '头像上传失败:服务器错误'
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error('头像上传错误:', error);
|
|
|
|
|
- // 上传失败,清空头像
|
|
|
|
|
- state.form.avatar = '';
|
|
|
|
|
- state.form.fileList = [];
|
|
|
|
|
- showNotify({
|
|
|
|
|
- type: 'danger',
|
|
|
|
|
- message: '头像上传失败:网络错误'
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
const onRegister = async () => {
|
|
const onRegister = async () => {
|
|
|
const form = state.form.registerType == '10' ? personInfoRef.value : personInfoRef.value
|
|
const form = state.form.registerType == '10' ? personInfoRef.value : personInfoRef.value
|
|
|
const [error] = await to(form.validate())
|
|
const [error] = await to(form.validate())
|
|
@@ -967,4 +1027,17 @@
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+.cropper-warp {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ background-color: #eee;
|
|
|
|
|
+
|
|
|
|
|
+ .cropper-warp-left {
|
|
|
|
|
+ height: 500px;
|
|
|
|
|
+ width: 500px;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
</style>
|
|
</style>
|