瀏覽代碼

😁增加头像裁剪上传

zuohuaijun 3 年之前
父節點
當前提交
ae44611820

+ 1 - 1
Admin.NET/Admin.NET.Application/Configuration/App.json

@@ -23,7 +23,7 @@
         "DefaultCulture": "zh-CN" // 默认语言
         "DefaultCulture": "zh-CN" // 默认语言
     },
     },
     "CorsAccessorSettings": {
     "CorsAccessorSettings": {
-        "WithExposedHeaders": [ "Content-Disposition" ], // 如果前端不代理且是axios请求
+        "WithExposedHeaders": [ "Content-Disposition", "X-Pagination", "access-token", "x-access-token" ], // 如果前端不代理且是axios请求
         "SignalRSupport": true // 启用 SignalR 跨域支持
         "SignalRSupport": true // 启用 SignalR 跨域支持
     }
     }
 }
 }

+ 6 - 6
Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj

@@ -14,23 +14,23 @@
   </ItemGroup>
   </ItemGroup>
 
 
   <ItemGroup>
   <ItemGroup>
-    <PackageReference Include="AngleSharp" Version="1.0.0-ci-228" />
+    <PackageReference Include="AngleSharp" Version="1.0.0" />
     <PackageReference Include="AspNetCoreRateLimit" Version="5.0.0" />
     <PackageReference Include="AspNetCoreRateLimit" Version="5.0.0" />
     <PackageReference Include="DotNetCore.Natasha.CSharp" Version="5.0.2" />
     <PackageReference Include="DotNetCore.Natasha.CSharp" Version="5.0.2" />
-    <PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.8.4.11" />
-    <PackageReference Include="Furion.Extras.ObjectMapper.Mapster" Version="4.8.4.11" />
-    <PackageReference Include="Furion.Pure" Version="4.8.4.11" />
+    <PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.8.4.14" />
+    <PackageReference Include="Furion.Extras.ObjectMapper.Mapster" Version="4.8.4.14" />
+    <PackageReference Include="Furion.Pure" Version="4.8.4.14" />
     <PackageReference Include="Lazy.Captcha.Core" Version="2.0.0" />
     <PackageReference Include="Lazy.Captcha.Core" Version="2.0.0" />
     <PackageReference Include="Magicodes.IE.Excel" Version="2.7.4.2" />
     <PackageReference Include="Magicodes.IE.Excel" Version="2.7.4.2" />
     <PackageReference Include="Magicodes.IE.Pdf" Version="2.7.4.2" />
     <PackageReference Include="Magicodes.IE.Pdf" Version="2.7.4.2" />
-    <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.12" />
+    <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.13" />
     <PackageReference Include="NEST" Version="7.17.5" />
     <PackageReference Include="NEST" Version="7.17.5" />
     <PackageReference Include="NETCore.MailKit" Version="2.1.0" />
     <PackageReference Include="NETCore.MailKit" Version="2.1.0" />
     <PackageReference Include="NewLife.Redis" Version="5.1.2023.106-beta1424" />
     <PackageReference Include="NewLife.Redis" Version="5.1.2023.106-beta1424" />
     <PackageReference Include="OnceMi.AspNetCore.OSS" Version="1.1.9" />
     <PackageReference Include="OnceMi.AspNetCore.OSS" Version="1.1.9" />
     <PackageReference Include="SKIT.FlurlHttpClient.Wechat.Api" Version="2.21.1" />
     <PackageReference Include="SKIT.FlurlHttpClient.Wechat.Api" Version="2.21.1" />
     <PackageReference Include="SKIT.FlurlHttpClient.Wechat.TenpayV3" Version="2.15.2" />
     <PackageReference Include="SKIT.FlurlHttpClient.Wechat.TenpayV3" Version="2.15.2" />
-    <PackageReference Include="SqlSugarCore" Version="5.1.3.44-preview03" />
+    <PackageReference Include="SqlSugarCore" Version="5.1.3.45" />
     <PackageReference Include="System.Linq.Dynamic.Core" Version="1.2.24" />
     <PackageReference Include="System.Linq.Dynamic.Core" Version="1.2.24" />
     <PackageReference Include="UAParser" Version="3.1.47" />
     <PackageReference Include="UAParser" Version="3.1.47" />
     <PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
     <PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />

+ 5 - 0
Admin.NET/Admin.NET.Core/Admin.NET.Core.xml

@@ -2572,6 +2572,11 @@
             文件超过允许大小
             文件超过允许大小
             </summary>
             </summary>
         </member>
         </member>
+        <member name="F:Admin.NET.Core.ErrorCodeEnum.D8003">
+            <summary>
+            文件后缀错误
+            </summary>
+        </member>
         <member name="F:Admin.NET.Core.ErrorCodeEnum.D9000">
         <member name="F:Admin.NET.Core.ErrorCodeEnum.D9000">
             <summary>
             <summary>
             已存在同名或同编码参数配置
             已存在同名或同编码参数配置

+ 6 - 0
Admin.NET/Admin.NET.Core/Enum/ErrorCodeEnum.cs

@@ -366,6 +366,12 @@ public enum ErrorCodeEnum
     [ErrorCodeItemMetadata("文件超过允许大小")]
     [ErrorCodeItemMetadata("文件超过允许大小")]
     D8002,
     D8002,
 
 
+    /// <summary>
+    /// 文件后缀错误
+    /// </summary>
+    [ErrorCodeItemMetadata("文件后缀错误")]
+    D8003,
+
     /// <summary>
     /// <summary>
     /// 已存在同名或同编码参数配置
     /// 已存在同名或同编码参数配置
     /// </summary>
     /// </summary>

+ 10 - 1
Admin.NET/Admin.NET.Core/Service/File/SysFileService.cs

@@ -1,3 +1,5 @@
+using Furion.VirtualFileServer;
+using Microsoft.AspNetCore.StaticFiles;
 using OnceMi.AspNetCore.OSS;
 using OnceMi.AspNetCore.OSS;
 
 
 namespace Admin.NET.Core.Service;
 namespace Admin.NET.Core.Service;
@@ -175,6 +177,13 @@ public class SysFileService : IDynamicApiController, ITransient
             throw Oops.Oh(ErrorCodeEnum.D8002);
             throw Oops.Oh(ErrorCodeEnum.D8002);
 
 
         var suffix = Path.GetExtension(file.FileName).ToLower(); // 后缀
         var suffix = Path.GetExtension(file.FileName).ToLower(); // 后缀
+        if (string.IsNullOrWhiteSpace(suffix))
+        {
+            var contentTypeProvider = FS.GetFileExtensionContentTypeProvider();
+            suffix = contentTypeProvider.Mappings.FirstOrDefault(u => u.Value == file.ContentType).Key;
+        }
+        if (string.IsNullOrWhiteSpace(suffix))
+            throw Oops.Oh(ErrorCodeEnum.D8003);
 
 
         var newFile = new SysFile
         var newFile = new SysFile
         {
         {
@@ -262,7 +271,7 @@ public class SysFileService : IDynamicApiController, ITransient
         var sysUserRep = _sysFileRep.ChangeRepository<SqlSugarRepository<SysUser>>();
         var sysUserRep = _sysFileRep.ChangeRepository<SqlSugarRepository<SysUser>>();
         var user = sysUserRep.GetFirst(u => u.Id == _userManager.UserId);
         var user = sysUserRep.GetFirst(u => u.Id == _userManager.UserId);
         // 删除当前用户已有头像
         // 删除当前用户已有头像
-        if (!string.IsNullOrWhiteSpace(user.Avatar) && user.Avatar.EndsWith(".png"))
+        if (!string.IsNullOrWhiteSpace(user.Avatar))
         {
         {
             var fileId = Path.GetFileNameWithoutExtension(user.Avatar);
             var fileId = Path.GetFileNameWithoutExtension(user.Avatar);
             await DeleteFile(new DeleteFileInput { Id = long.Parse(fileId) });
             await DeleteFile(new DeleteFileInput { Id = long.Parse(fileId) });

+ 31 - 31
Admin.NET/Admin.NET.Web.Entry/Properties/launchSettings.json

@@ -1,35 +1,35 @@
 {
 {
-  "profiles": {
-    "IIS Express": {
-      "commandName": "IISExpress",
-      "launchBrowser": true,
-      "environmentVariables": {
-        "ASPNETCORE_ENVIRONMENT": "Development"
-      }
+    "profiles": {
+        "IIS Express": {
+            "commandName": "IISExpress",
+            "launchBrowser": true,
+            "environmentVariables": {
+                "ASPNETCORE_ENVIRONMENT": "Development"
+            }
+        },
+        "Admin.NET.Web.Entry": {
+            "commandName": "Project",
+            "launchBrowser": true,
+            "environmentVariables": {
+                "ASPNETCORE_ENVIRONMENT": "Development"
+            },
+            "dotnetRunMessages": true,
+            "applicationUrl": "https://localhost:44326;http://localhost:5000"
+        },
+        "Docker": {
+            "commandName": "Docker",
+            "launchBrowser": true,
+            "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
+            "publishAllPorts": true,
+            "useSSL": true
+        }
     },
     },
-    "Admin.NET.Web.Entry": {
-      "commandName": "Project",
-      "launchBrowser": true,
-      "environmentVariables": {
-        "ASPNETCORE_ENVIRONMENT": "Development"
-      },
-      "dotnetRunMessages": true,
-      "applicationUrl": "https://localhost:44326;http://localhost:5000"
-    },
-    "Docker": {
-      "commandName": "Docker",
-      "launchBrowser": true,
-      "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
-      "publishAllPorts": true,
-      "useSSL": true
-    }
-  },
-  "iisSettings": {
-    "windowsAuthentication": false,
-    "anonymousAuthentication": true,
-    "iisExpress": {
-      "applicationUrl": "https://localhost:44326",
-      "sslPort": 44326
+    "iisSettings": {
+        "windowsAuthentication": false,
+        "anonymousAuthentication": true,
+        "iisExpress": {
+            "applicationUrl": "https://localhost:44326",
+            "sslPort": 44326
+        }
     }
     }
-  }
 }
 }

+ 47 - 10
Web/src/components/cropper/index.vue

@@ -1,6 +1,12 @@
 <template>
 <template>
 	<div>
 	<div>
-		<el-dialog title="更换头像" v-model="state.isShowDialog" width="769px">
+		<el-dialog v-model="state.isShowDialog" width="769px">
+			<template #header>
+				<div style="color: #fff">
+					<el-icon size="16" style="margin-right: 3px; display: inline; vertical-align: middle"> <ele-Edit /> </el-icon>
+					<span>{{ props.title }}</span>
+				</div>
+			</template>
 			<div class="cropper-warp">
 			<div class="cropper-warp">
 				<div class="cropper-warp-left">
 				<div class="cropper-warp-left">
 					<img :src="state.cropperImg" class="cropper-warp-left-img" />
 					<img :src="state.cropperImg" class="cropper-warp-left-img" />
@@ -23,8 +29,19 @@
 			</div>
 			</div>
 			<template #footer>
 			<template #footer>
 				<span class="dialog-footer">
 				<span class="dialog-footer">
+					<el-upload
+						ref="uploadSignRef"
+						accept=".jpg,.png"
+						:limit="1"
+						:show-file-list="false"
+						:auto-upload="false"
+						:on-change="selectPicture"
+						style="display: inline-block; position: absolute; right: 172px"
+					>
+						<el-button icon="ele-Picture">选择图片</el-button>
+					</el-upload>
 					<el-button @click="onCancel" size="default">取 消</el-button>
 					<el-button @click="onCancel" size="default">取 消</el-button>
-					<el-button type="primary" @click="onSubmit" size="default">更 换</el-button>
+					<el-button type="primary" @click="onSubmit" size="default">确 定</el-button>
 				</span>
 				</span>
 			</template>
 			</template>
 		</el-dialog>
 		</el-dialog>
@@ -33,9 +50,17 @@
 
 
 <script setup lang="ts" name="cropper">
 <script setup lang="ts" name="cropper">
 import { reactive, nextTick } from 'vue';
 import { reactive, nextTick } from 'vue';
+import mittBus from '/@/utils/mitt';
 import Cropper from 'cropperjs';
 import Cropper from 'cropperjs';
 import 'cropperjs/dist/cropper.css';
 import 'cropperjs/dist/cropper.css';
 
 
+const props = defineProps({
+	title: {
+		type: String,
+		default: () => '',
+	},
+});
+
 // 定义变量内容
 // 定义变量内容
 const state = reactive({
 const state = reactive({
 	isShowDialog: false,
 	isShowDialog: false,
@@ -54,33 +79,45 @@ const openDialog = (imgs: string) => {
 };
 };
 // 关闭弹窗
 // 关闭弹窗
 const closeDialog = () => {
 const closeDialog = () => {
+	state.cropper.destroy();
 	state.isShowDialog = false;
 	state.isShowDialog = false;
 };
 };
 // 取消
 // 取消
 const onCancel = () => {
 const onCancel = () => {
 	closeDialog();
 	closeDialog();
 };
 };
-// 更换
-const onSubmit = () => {
-	// state.cropperImgBase64 = state.cropper.getCroppedCanvas().toDataURL('image/jpeg');
+// 更换/上传
+const onSubmit = async () => {
+	state.cropperImgBase64 = state.cropper.getCroppedCanvas().toBlob(async function (img: Blob | undefined) {
+		mittBus.emit('uploadCropperImg', { img: img });
+		closeDialog();
+	});
 };
 };
 // 初始化cropperjs图片裁剪
 // 初始化cropperjs图片裁剪
 const initCropper = () => {
 const initCropper = () => {
 	const letImg = <HTMLImageElement>document.querySelector('.cropper-warp-left-img');
 	const letImg = <HTMLImageElement>document.querySelector('.cropper-warp-left-img');
+	console.log(letImg);
 	state.cropper = new Cropper(letImg, {
 	state.cropper = new Cropper(letImg, {
-		viewMode: 1,
+		viewMode: 0,
 		dragMode: 'none',
 		dragMode: 'none',
 		initialAspectRatio: 1,
 		initialAspectRatio: 1,
 		aspectRatio: 1,
 		aspectRatio: 1,
 		preview: '.before',
 		preview: '.before',
-		background: false,
-		autoCropArea: 0.6,
-		zoomOnWheel: false,
+		background: true,
+		autoCropArea: 1,
+		// zoomOnWheel: false,
+		checkCrossOrigin: false,
 		crop: () => {
 		crop: () => {
-			state.cropperImgBase64 = state.cropper.getCroppedCanvas().toDataURL('image/jpeg');
+			state.cropperImgBase64 = state.cropper.getCroppedCanvas()!.toDataURL('image/jpeg');
 		},
 		},
 	});
 	});
 };
 };
+// 选择图片
+const selectPicture = async (file: any) => {
+	let URL = window.URL || window.webkitURL;
+	state.cropperImg = URL.createObjectURL(file.raw);
+	state.cropper.replace(state.cropperImg);
+};
 
 
 // 暴露变量
 // 暴露变量
 defineExpose({
 defineExpose({

+ 2 - 0
Web/src/types/mitt.d.ts

@@ -31,6 +31,8 @@ declare type MittType<T = any> = {
 	submitRefreshColumn?: string; // 库表管理列刷新
 	submitRefreshColumn?: string; // 库表管理列刷新
 	addTableSubmitted?: string; // 库表管理增加表刷新
 	addTableSubmitted?: string; // 库表管理增加表刷新
 	submitRefreshFk?: string; // 代码生成主键刷新
 	submitRefreshFk?: string; // 代码生成主键刷新
+
+	uploadCropperImg?: any; // 上传裁剪图片
 };
 };
 
 
 // mitt 参数类型定义
 // mitt 参数类型定义

+ 26 - 6
Web/src/views/system/user/component/userCenter.vue

@@ -34,8 +34,17 @@
 						<el-image :src="userInfos.signature" fit="contain" alt="电子签名" lazy style="width: 100%; height: 100%"> </el-image>
 						<el-image :src="userInfos.signature" fit="contain" alt="电子签名" lazy style="width: 100%; height: 100%"> </el-image>
 					</div>
 					</div>
 					<el-button icon="ele-Edit" type="primary" @click="openSignDialog" v-auth="'sysUser:signature'"> 电子签名 </el-button>
 					<el-button icon="ele-Edit" type="primary" @click="openSignDialog" v-auth="'sysUser:signature'"> 电子签名 </el-button>
-					<el-upload ref="uploadSignRef" action="" accept=".png" :limit="1" :show-file-list="false" :auto-upload="false" :on-change="uploadSignFile" style="display: inline-block; margin-left: 12px">
-						<el-button icon="ele-UploadFilled" style="display: inline-block">上传手写签名</el-button>
+					<el-upload
+						ref="uploadSignRef"
+						action=""
+						accept=".png"
+						:limit="1"
+						:show-file-list="false"
+						:auto-upload="false"
+						:on-change="uploadSignFile"
+						style="display: inline-block; margin-left: 12px; position: absolute"
+					>
+						<el-button icon="ele-UploadFilled">上传手写签名</el-button>
 					</el-upload>
 					</el-upload>
 				</el-card>
 				</el-card>
 			</el-col>
 			</el-col>
@@ -145,18 +154,19 @@
 			</template>
 			</template>
 		</el-dialog>
 		</el-dialog>
 
 
-		<CropperDialog ref="cropperDialogRef" />
+		<CropperDialog ref="cropperDialogRef" :title="cropperTitle" />
 	</div>
 	</div>
 </template>
 </template>
 
 
 <script lang="ts">
 <script lang="ts">
-import { toRefs, reactive, defineComponent, ref, onMounted, watch } from 'vue';
+import { toRefs, reactive, defineComponent, ref, onMounted, watch, onUnmounted } from 'vue';
 import { storeToRefs } from 'pinia';
 import { storeToRefs } from 'pinia';
 import { ElMessageBox, UploadInstance } from 'element-plus';
 import { ElMessageBox, UploadInstance } from 'element-plus';
 import { useUserInfo } from '/@/stores/userInfo';
 import { useUserInfo } from '/@/stores/userInfo';
 import { base64ToFile } from '/@/utils/base64Conver';
 import { base64ToFile } from '/@/utils/base64Conver';
 import OrgTree from '/@/views/system/user/component/orgTree.vue';
 import OrgTree from '/@/views/system/user/component/orgTree.vue';
 import CropperDialog from '/@/components/cropper/index.vue';
 import CropperDialog from '/@/components/cropper/index.vue';
+import mittBus from '/@/utils/mitt';
 
 
 import { clearAccessTokens, getAPI } from '/@/utils/axios-utils';
 import { clearAccessTokens, getAPI } from '/@/utils/axios-utils';
 import { SysFileApi, SysUserApi } from '/@/api-services/api';
 import { SysFileApi, SysUserApi } from '/@/api-services/api';
@@ -189,12 +199,21 @@ export default defineComponent({
 			},
 			},
 			signFileList: [] as any,
 			signFileList: [] as any,
 			passwordNew2: '',
 			passwordNew2: '',
+			cropperTitle: '',
 		});
 		});
 		onMounted(async () => {
 		onMounted(async () => {
 			state.loading = true;
 			state.loading = true;
 			var res = await getAPI(SysUserApi).sysUserBaseGet();
 			var res = await getAPI(SysUserApi).sysUserBaseGet();
 			state.ruleFormBase = res.data.result ?? { account: '' };
 			state.ruleFormBase = res.data.result ?? { account: '' };
 			state.loading = false;
 			state.loading = false;
+
+			mittBus.on('uploadCropperImg', async (e) => {
+				var res = await getAPI(SysFileApi).sysFileUploadAvatarPostForm(e.img);
+				userInfos.value.avatar = res.data.result?.url + '';
+			});
+		});
+		onUnmounted(() => {
+			mittBus.off('uploadCropperImg', () => {});
 		});
 		});
 		watch(state.signOptions, () => {
 		watch(state.signOptions, () => {
 			signaturePadRef.value.signaturePad.penColor = state.signOptions.penColor;
 			signaturePadRef.value.signaturePad.penColor = state.signOptions.penColor;
@@ -218,7 +237,7 @@ export default defineComponent({
 		// 撤销电子签名
 		// 撤销电子签名
 		const unDoSign = () => {
 		const unDoSign = () => {
 			signaturePadRef.value.undoSignature();
 			signaturePadRef.value.undoSignature();
-			console.log(signaturePadRef.value.options);
+			// console.log(signaturePadRef.value.options);
 		};
 		};
 		// 清空电子签名
 		// 清空电子签名
 		const clearSign = () => {
 		const clearSign = () => {
@@ -233,7 +252,7 @@ export default defineComponent({
 		const handleChangeSignFile = (_file: any, fileList: []) => {
 		const handleChangeSignFile = (_file: any, fileList: []) => {
 			state.signFileList = fileList;
 			state.signFileList = fileList;
 		};
 		};
-		// 上传头像文件
+		// 上传头像文件回调
 		const uploadAvatarFile = async (file: any) => {
 		const uploadAvatarFile = async (file: any) => {
 			var res = await getAPI(SysFileApi).sysFileUploadAvatarPostForm(file.raw);
 			var res = await getAPI(SysFileApi).sysFileUploadAvatarPostForm(file.raw);
 			userInfos.value.avatar = res.data.result?.url + '';
 			userInfos.value.avatar = res.data.result?.url + '';
@@ -283,6 +302,7 @@ export default defineComponent({
 		};
 		};
 		// 打开裁剪弹窗
 		// 打开裁剪弹窗
 		const openCropperDialog = () => {
 		const openCropperDialog = () => {
+			state.cropperTitle = '更换头像';
 			cropperDialogRef.value.openDialog(userInfos.value.avatar);
 			cropperDialogRef.value.openDialog(userInfos.value.avatar);
 		};
 		};
 		// 鼠标进入和离开头像时
 		// 鼠标进入和离开头像时