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 page(MessageQuery query, PageQuery pageQuery) { QueryWrapper 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 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 list(MessageQuery query, SortQuery sortQuery) { QueryWrapper 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 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(`${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" > +
+ + + +
+ + +
+
+ + +
+
+ +
+
+
+
+
+ + 提交 + 取消 + +
+
+ - 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([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; }; @@ -152,4 +231,8 @@ font-size: 14px; } } + + .main { + position: relative; + }