index.vue 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. <template>
  2. <div class="user-info-head" @click="editCropper()">
  3. <el-avatar :size="100" :src="options.img" />
  4. <el-dialog :title="title" v-model="dialogVisiable" width="600px" append-to-body @opened="modalOpened" @close="closeDialog">
  5. <el-row>
  6. <el-col class="flex justify-center">
  7. <vue-cropper
  8. ref="cropper"
  9. :img="options.img"
  10. :info="true"
  11. :autoCrop="options.autoCrop"
  12. :autoCropWidth="options.autoCropWidth"
  13. :autoCropHeight="options.autoCropHeight"
  14. :fixedBox="options.fixedBox"
  15. :outputType="options.outputType"
  16. @realTime="realTime"
  17. :centerBox="true"
  18. v-if="visible"
  19. class="cropper"
  20. />
  21. </el-col>
  22. </el-row>
  23. <br />
  24. <el-row class="flex justify-center">
  25. <el-col :lg="2" :md="2">
  26. <el-upload action="#" :http-request="requestUpload" :show-file-list="false" :before-upload="beforeUpload">
  27. <el-button type="success">
  28. 选择
  29. <el-icon class="el-icon--right"><Plus /></el-icon>
  30. </el-button>
  31. </el-upload>
  32. </el-col>
  33. <el-col :lg="{ span: 1, offset: 2 }" :md="2">
  34. <el-button icon="RefreshLeft" @click="rotateLeft()"></el-button>
  35. </el-col>
  36. <el-col :lg="{ span: 1, offset: 2 }" :md="2">
  37. <el-button icon="RefreshRight" @click="rotateRight()"></el-button>
  38. </el-col>
  39. <el-col :lg="{ span: 2, offset: 2 }" :md="2">
  40. <el-button type="primary" @click="uploadImg()">更新头像</el-button>
  41. </el-col>
  42. </el-row>
  43. </el-dialog>
  44. </div>
  45. </template>
  46. <script setup>
  47. import 'vue-cropper/dist/index.css';
  48. import { VueCropper } from 'vue-cropper';
  49. import { useUserInfo } from '/@/stores/userInfo';
  50. import { getCurrentInstance, nextTick, reactive, ref, computed, onMounted, defineExpose } from 'vue';
  51. import { base64ToFile } from '/@/utils/tools';
  52. const userStore = useUserInfo();
  53. const { proxy } = getCurrentInstance();
  54. const open = ref(false);
  55. const visible = ref(false);
  56. const title = ref('修改头像');
  57. const emit = defineEmits(['uploadImg']);
  58. const props = defineProps({
  59. modelValue: {
  60. type: Boolean,
  61. default: false,
  62. required: true,
  63. },
  64. });
  65. const dialogVisiable = computed({
  66. get() {
  67. return props.modelValue;
  68. },
  69. set(newVal) {
  70. emit('update:modelValue', newVal);
  71. },
  72. });
  73. //图片裁剪数据
  74. const options = reactive({
  75. img: userStore.userInfos.avatar, // 裁剪图片的地址
  76. fileName: '',
  77. autoCrop: true, // 是否默认生成截图框
  78. autoCropWidth: 200, // 默认生成截图框宽度
  79. autoCropHeight: 200, // 默认生成截图框高度
  80. fixedBox: true, // 固定截图框大小 不允许改变
  81. outputType: 'png', // 默认生成截图为PNG格式
  82. });
  83. /** 编辑头像 */
  84. function editCropper() {
  85. dialogVisiable.value = true;
  86. }
  87. /** 打开弹出层结束时的回调 */
  88. function modalOpened() {
  89. nextTick(() => {
  90. visible.value = true;
  91. });
  92. }
  93. /** 覆盖默认上传行为 */
  94. function requestUpload() {}
  95. /** 向左旋转 */
  96. function rotateLeft() {
  97. proxy.$refs.cropper.rotateLeft();
  98. }
  99. /** 向右旋转 */
  100. function rotateRight() {
  101. proxy.$refs.cropper.rotateRight();
  102. }
  103. /** 图片缩放 */
  104. function changeScale(num) {
  105. num = num || 1;
  106. proxy.$refs.cropper.changeScale(num);
  107. }
  108. /** 上传预处理 */
  109. function beforeUpload(file) {
  110. if (file.type.indexOf('image/') == -1) {
  111. proxy.$modal.msgError('文件格式错误,请上传图片类型,如:JPG,PNG后缀的文件。');
  112. } else {
  113. const reader = new FileReader();
  114. reader.readAsDataURL(file);
  115. reader.onload = () => {
  116. options.img = reader.result;
  117. options.fileName = file.name;
  118. };
  119. }
  120. }
  121. /** 上传图片 */
  122. function uploadImg() {
  123. // 获取截图的 base64 数据
  124. proxy.$refs.cropper.getCropData((data) => {
  125. let img = new Image();
  126. img.src = data;
  127. img.onload = async () => {
  128. let _data = compress(img);
  129. const imgFile = base64ToFile(_data, options.fileName);
  130. emit('uploadImg', imgFile);
  131. };
  132. });
  133. }
  134. // 压缩图片
  135. function compress(img) {
  136. let canvas = document.createElement('canvas');
  137. let ctx = canvas.getContext('2d');
  138. // let initSize = img.src.length;
  139. let width = img.width;
  140. let height = img.height;
  141. canvas.width = width;
  142. canvas.height = height;
  143. // 铺底色
  144. ctx.fillStyle = '#fff';
  145. ctx.fillRect(0, 0, canvas.width, canvas.height);
  146. ctx.drawImage(img, 0, 0, width, height);
  147. // 进行压缩
  148. let ndata = canvas.toDataURL('image/jpeg', 0.8);
  149. return ndata;
  150. }
  151. /** 关闭窗口 */
  152. function closeDialog() {
  153. options.visible = false;
  154. options.img = userStore.userInfos.avatar;
  155. }
  156. const updateAvatar = (img) => {
  157. options.img = img;
  158. };
  159. defineExpose({
  160. updateAvatar,
  161. });
  162. </script>
  163. <style lang="scss" scoped>
  164. .user-info-head {
  165. position: relative;
  166. display: inline-block;
  167. height: 120px;
  168. }
  169. .user-info-head:hover:after {
  170. content: '修改头像';
  171. position: absolute;
  172. text-align: center;
  173. left: 0;
  174. right: 0;
  175. top: 0;
  176. bottom: 0;
  177. color: #000000;
  178. font-size: 20px;
  179. font-style: normal;
  180. cursor: pointer;
  181. line-height: 110px;
  182. }
  183. .cropper {
  184. height: 400px;
  185. width: 400px;
  186. }
  187. </style>