diff --git a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/impl/MessageServiceImpl.java b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/impl/MessageServiceImpl.java
index 50eb66c1..f7fca496 100644
--- a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/impl/MessageServiceImpl.java
+++ b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/impl/MessageServiceImpl.java
@@ -61,8 +61,8 @@ public class MessageServiceImpl
     @Override
     public PageDataVO<MessageVO> page(MessageQuery query, PageQuery pageQuery) {
         QueryWrapper<MessageDO> queryWrapper = QueryHelper.build(query);
-        queryWrapper.apply(null != query.getUid(), "msgUser.user_id={0}", query.getUid());
-        queryWrapper.apply(null != query.getReadStatus(), "msgUser.read_status={0}", query.getReadStatus());
+        queryWrapper.apply(null != query.getUid(), "msgUser.user_id={0}", query.getUid())
+            .apply(null != query.getReadStatus(), "msgUser.read_status={0}", query.getReadStatus());
         IPage<MessageVO> page = baseMapper.selectVoPage(pageQuery.toPage(), queryWrapper);
         page.getRecords().forEach(this::fill);
         return PageDataVO.build(page);
@@ -71,8 +71,8 @@ public class MessageServiceImpl
     @Override
     public List<MessageVO> list(MessageQuery query, SortQuery sortQuery) {
         QueryWrapper<MessageDO> queryWrapper = QueryHelper.build(query);
-        queryWrapper.apply("msgUser.user_id={0}", LoginHelper.getUserId());
-        queryWrapper.apply(null != query.getReadStatus(), "msgUser.read_status={0}", query.getReadStatus());
+        queryWrapper.apply("msgUser.user_id={0}", LoginHelper.getUserId()).apply(null != query.getReadStatus(),
+            "msgUser.read_status={0}", query.getReadStatus());
         // 设置排序
         this.sort(queryWrapper, sortQuery);
         return baseMapper.selectVoList(queryWrapper);
@@ -99,8 +99,8 @@ public class MessageServiceImpl
         messageUserService.add(messageId, userIdList);
     }
 
-    @Transactional(rollbackFor = Exception.class)
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void delete(List<Long> ids) {
         super.delete(ids);
         messageUserService.delete(ids);
diff --git a/continew-admin-ui/package.json b/continew-admin-ui/package.json
index f1d2ec18..5626b388 100644
--- a/continew-admin-ui/package.json
+++ b/continew-admin-ui/package.json
@@ -46,6 +46,7 @@
     "query-string": "^8.1.0",
     "sortablejs": "^1.15.0",
     "vue": "^3.3.4",
+    "vue-cropper": "^1.0.9",
     "vue-echarts": "^6.6.1",
     "vue-i18n": "^9.5.0",
     "vue-json-pretty": "^2.2.4",
diff --git a/continew-admin-ui/pnpm-lock.yaml b/continew-admin-ui/pnpm-lock.yaml
index b93cddef..531aa8ed 100644
--- a/continew-admin-ui/pnpm-lock.yaml
+++ b/continew-admin-ui/pnpm-lock.yaml
@@ -58,6 +58,9 @@ dependencies:
   vue:
     specifier: ^3.3.4
     version: 3.3.4
+  vue-cropper:
+    specifier: ^1.0.9
+    version: 1.0.9
   vue-echarts:
     specifier: ^6.6.1
     version: 6.6.1(echarts@5.4.3)(vue@3.3.4)
diff --git a/continew-admin-ui/src/api/system/user-center.ts b/continew-admin-ui/src/api/system/user-center.ts
index 5b63a239..c4a5b926 100644
--- a/continew-admin-ui/src/api/system/user-center.ts
+++ b/continew-admin-ui/src/api/system/user-center.ts
@@ -12,6 +12,20 @@ export interface AvatarRes {
   avatar: string;
 }
 
+export interface cropperOptions {
+  autoCrop: boolean; // 是否默认生成截图框
+  autoCropWidth: number; // 默认生成截图框宽度
+  autoCropHeight: number; // 默认生成截图框高度
+  canMove: boolean; // 上传图片是否可以移动  (默认:true)
+  centerBox: boolean; // 截图框是否被限制在图片里面  (默认:false)
+  full: boolean; // 是否输出原图比例的截图 选true生成的图片会非常大  (默认:false)
+  fixed: boolean; // 是否开启截图框宽高固定比例  (默认:false)
+  fixedBox: boolean; // 固定截图框大小 不允许改变
+  img: string | ArrayBuffer | null; // 裁剪图片的地址
+  outputSize: number; // 裁剪生成图片的质量  (默认:1)
+  outputType: string; // 默认生成截图为PNG格式
+}
+
 export function uploadAvatar(data: FormData) {
   return axios.post<AvatarRes>(`${BASE_URL}/avatar`, data);
 }
diff --git a/continew-admin-ui/src/views/system/user/center/components/user-panel.vue b/continew-admin-ui/src/views/system/user/center/components/user-panel.vue
index b06855e4..d49c257e 100644
--- a/continew-admin-ui/src/views/system/user/center/components/user-panel.vue
+++ b/continew-admin-ui/src/views/system/user/center/components/user-panel.vue
@@ -7,8 +7,7 @@
         :show-file-list="false"
         list-type="picture-card"
         :show-upload-button="true"
-        :custom-request="handleUpload"
-        @change="handleAvatarChange"
+        :on-before-upload="handleBeforeUpload"
       >
         <template #upload-button>
           <a-avatar :size="100" class="info-avatar">
@@ -22,6 +21,56 @@
         </template>
       </a-upload>
 
+      <div class="main">
+        <a-modal
+          :visible="cropperVisible"
+          width="40%"
+          :footer="false"
+          unmount-on-close
+          render-to-body
+          @cancel="handleCropperCancel"
+        >
+          <a-row>
+            <a-col :span="14">
+              <div style="width: 370px; height: 370px">
+                <!-- 头像裁剪框 -->
+                <vue-cropper
+                  ref="cropper"
+                  :info="true"
+                  :img="options.img"
+                  :full="options.full"
+                  :fixed="options.fixed"
+                  :fixed-box="options.fixedBox"
+                  :can-move="options.canMove"
+                  :center-box="options.centerBox"
+                  :auto-crop="options.autoCrop"
+                  :auto-crop-width="options.autoCropWidth"
+                  :auto-crop-height="options.autoCropHeight"
+                  :output-type="options.outputType"
+                  :output-size="options.outputSize"
+                  @realTime="realTime"
+                />
+              </div>
+            </a-col>
+            <a-col :span="6">
+              <!-- 实时预览 -->
+              <div :style="previewStyle">
+                <div :style="previews.div">
+                  <img :src="previews.url" :style="previews.img" alt="" />
+                </div>
+              </div>
+            </a-col>
+          </a-row>
+          <br />
+          <a-space>
+            <a-button type="primary" @click="handleUpload">提交</a-button>
+            <a-button type="outline" @click="handleCropperCancel"
+              >取消</a-button
+            >
+          </a-space>
+        </a-modal>
+      </div>
+
       <a-descriptions
         :column="2"
         :label-style="{
@@ -70,15 +119,22 @@
 </template>
 
 <script lang="ts" setup>
-  import { getCurrentInstance, ref } from 'vue';
-  import { FileItem, RequestOption } from '@arco-design/web-vue';
-  import { uploadAvatar } from '@/api/system/user-center';
-  import { useUserStore } from '@/store';
+  import { reactive, ref, getCurrentInstance } from 'vue';
+  import { FileItem } from '@arco-design/web-vue';
+  import { uploadAvatar, cropperOptions } from '@/api/system/user-center';
   import getAvatar from '@/utils/avatar';
+  import { useUserStore } from '@/store';
+  import { VueCropper } from 'vue-cropper';
+  import 'vue-cropper/dist/index.css';
 
+  const fileRef = ref(reactive({ name: 'avatar.png' }));
+  const previews: any = ref({});
+  const previewStyle: any = ref({});
+  const cropperVisible = ref(false);
+  const cropper = ref();
   const { proxy } = getCurrentInstance() as any;
-
   const userStore = useUserStore();
+
   const avatar = {
     uid: '-2',
     name: 'avatar.png',
@@ -86,52 +142,75 @@
   };
   const avatarList = ref<FileItem[]>([avatar]);
 
+  const options: cropperOptions = reactive({
+    autoCrop: true,
+    autoCropWidth: 200,
+    autoCropHeight: 200,
+    canMove: true,
+    centerBox: true,
+    full: false,
+    fixed: false,
+    fixedBox: false,
+    img: '',
+    outputSize: 1,
+    outputType: 'png',
+  });
+
   /**
-   * 上传头像
+   * 上传前弹出裁剪框
    *
-   * @param options 选项
+   * @param file 头像
    */
-  const handleUpload = (options: RequestOption) => {
-    const controller = new AbortController();
-    (async function requestWrap() {
-      const {
-        onProgress,
-        onError,
-        onSuccess,
-        fileItem,
-        name = 'avatarFile',
-      } = options;
-      onProgress(20);
-      const formData = new FormData();
-      formData.append(name as string, fileItem.file as Blob);
-      uploadAvatar(formData)
-        .then((res) => {
-          onSuccess(res);
-          userStore.avatar = res.data.avatar;
-          proxy.$message.success(res.msg);
-        })
-        .catch((error) => {
-          onError(error);
-        });
-    })();
-    return {
-      abort() {
-        controller.abort();
-      },
+  const handleBeforeUpload = (file: File): boolean => {
+    fileRef.value = file;
+    const reader = new FileReader();
+    reader.readAsDataURL(file);
+    reader.onload = () => {
+      options.img = reader.result;
     };
+    cropperVisible.value = true;
+    return false;
   };
 
   /**
-   * 切换头像
-   *
-   * @param fileItemList 文件列表
-   * @param currentFile 当前文件
+   * 关闭裁剪框
    */
-  const handleAvatarChange = (
-    fileItemList: FileItem[],
-    currentFile: FileItem
-  ) => {
-    avatarList.value = [currentFile];
+  const handleCropperCancel = () => {
+    fileRef.value = { name: '' };
+    options.img = '';
+    cropperVisible.value = false;
+  };
+
+  /**
+   * 上传头像
+   */
+  const handleUpload = () => {
+    cropper.value.getCropBlob((data: string | Blob) => {
+      const formData = new FormData();
+      formData.append('avatarFile', data, fileRef.value?.name);
+      uploadAvatar(formData).then((res) => {
+        userStore.avatar = res.data.avatar;
+        avatarList.value[0].url = getAvatar(res.data.avatar, undefined);
+        proxy.$message.success(res.msg);
+        handleCropperCancel();
+      });
+    });
+  };
+
+  /**
+   * 实时预览
+   * @param data data
+   */
+  const realTime = (data: any) => {
+    previewStyle.value = {
+      width: `${data.w}px`,
+      height: `${data.h}px`,
+      overflow: 'hidden',
+      margin: '0',
+      zoom: 0.8,
+      borderRadius: '50%',
+    };
+    previews.value = data;
   };
 </script>
 
@@ -152,4 +231,8 @@
       font-size: 14px;
     }
   }
+
+  .main {
+    position: relative;
+  }
 </style>