UserAvatar.vue 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. <template>
  2. <div class="user-info-head" @click="editCropper()">
  3. <img :src="options.options.img" title="点击上传头像" class="img-circle img-lg" alt="" />
  4. </div>
  5. <el-dialog
  6. v-model="dialogVisible"
  7. title="编辑头像"
  8. width="800px"
  9. append-to-body
  10. style="padding: 30px 20px"
  11. @opened="modalOpened"
  12. >
  13. <el-row>
  14. <el-col :xs="24" :md="12" style="height: 350px">
  15. <VueCropper
  16. ref="cropper"
  17. v-if="cropperVisible"
  18. :img="options.options.img"
  19. :info="true"
  20. :autoCrop="options.options.autoCrop"
  21. :autoCropWidth="options.options.autoCropWidth"
  22. :autoCropHeight="options.options.autoCropHeight"
  23. :fixedBox="options.options.fixedBox"
  24. @real-time="realTime"
  25. />
  26. </el-col>
  27. <el-col :xs="24" :md="12" style="height: 350px">
  28. <div
  29. class="avatar-upload-preview"
  30. :style="{
  31. width: options.previews.w + 'px',
  32. height: options.previews.h + 'px',
  33. overflow: 'hidden',
  34. margin: '5px'
  35. }"
  36. >
  37. <div :style="options.previews.div">
  38. <img
  39. :src="options.previews.url"
  40. :style="options.previews.img"
  41. style="!max-width: 100%"
  42. alt=""
  43. />
  44. </div>
  45. </div>
  46. </el-col>
  47. </el-row>
  48. <template #footer>
  49. <el-row>
  50. <el-col :lg="2" :md="2">
  51. <el-upload
  52. action="#"
  53. :http-request="requestUpload"
  54. :show-file-list="false"
  55. :before-upload="beforeUpload"
  56. >
  57. <el-button size="small">
  58. <Icon icon="ep:upload-filled" class="mr-5px" />
  59. 选择
  60. </el-button>
  61. </el-upload>
  62. </el-col>
  63. <el-col :lg="{ span: 1, offset: 2 }" :md="2">
  64. <el-button size="small" @click="changeScale(1)">
  65. <Icon icon="ep:zoom-in" class="mr-5px" />
  66. </el-button>
  67. </el-col>
  68. <el-col :lg="{ span: 1, offset: 1 }" :md="2">
  69. <el-button size="small" @click="changeScale(-1)">
  70. <Icon icon="ep:zoom-out" class="mr-5px" />
  71. </el-button>
  72. </el-col>
  73. <el-col :lg="{ span: 1, offset: 1 }" :md="2">
  74. <el-button size="small" @click="rotateLeft()">
  75. <Icon icon="ep:arrow-left-bold" class="mr-5px" />
  76. </el-button>
  77. </el-col>
  78. <el-col :lg="{ span: 1, offset: 1 }" :md="2">
  79. <el-button size="small" @click="rotateRight()">
  80. <Icon icon="ep:arrow-right-bold" class="mr-5px" />
  81. </el-button>
  82. </el-col>
  83. <el-col :lg="{ span: 2, offset: 6 }" :md="2">
  84. <el-button size="small" type="primary" @click="uploadImg()">提 交</el-button>
  85. </el-col>
  86. </el-row>
  87. </template>
  88. </el-dialog>
  89. </template>
  90. <script setup lang="ts">
  91. import { ref, reactive, watch } from 'vue'
  92. import 'vue-cropper/dist/index.css'
  93. import { VueCropper } from 'vue-cropper'
  94. import { ElRow, ElCol, ElUpload, ElMessage, ElDialog } from 'element-plus'
  95. import { propTypes } from '@/utils/propTypes'
  96. import { uploadAvatarApi } from '@/api/system/user/profile'
  97. const cropper = ref()
  98. const dialogVisible = ref(false)
  99. const cropperVisible = ref(false)
  100. const props = defineProps({
  101. img: propTypes.string.def('')
  102. })
  103. const options = reactive({
  104. options: {
  105. img: props.img, //裁剪图片的地址
  106. autoCrop: true, // 是否默认生成截图框
  107. autoCropWidth: 200, // 默认生成截图框宽度
  108. autoCropHeight: 200, // 默认生成截图框高度
  109. fixedBox: true // 固定截图框大小 不允许改变
  110. },
  111. previews: {
  112. img: '',
  113. url: '',
  114. w: 0,
  115. h: 0,
  116. div: ''
  117. }
  118. })
  119. /** 编辑头像 */
  120. const editCropper = () => {
  121. dialogVisible.value = true
  122. }
  123. // 打开弹出层结束时的回调
  124. const modalOpened = () => {
  125. cropperVisible.value = true
  126. }
  127. /** 向左旋转 */
  128. const rotateLeft = () => {
  129. cropper.value.rotateLeft()
  130. }
  131. /** 向右旋转 */
  132. const rotateRight = () => {
  133. cropper.value.rotateRight()
  134. }
  135. /** 图片缩放 */
  136. const changeScale = (num: number) => {
  137. num = num || 1
  138. cropper.value.changeScale(num)
  139. }
  140. // 覆盖默认的上传行为
  141. const requestUpload: any = () => {}
  142. /** 上传预处理 */
  143. const beforeUpload = (file: Blob) => {
  144. if (file.type.indexOf('image/') == -1) {
  145. ElMessage('文件格式错误,请上传图片类型,如:JPG,PNG后缀的文件。')
  146. } else {
  147. const reader = new FileReader()
  148. reader.readAsDataURL(file)
  149. reader.onload = () => {
  150. if (reader.result) {
  151. options.options.img = reader.result as string
  152. }
  153. }
  154. }
  155. }
  156. /** 上传图片 */
  157. const uploadImg = () => {
  158. cropper.value.getCropBlob((data: any) => {
  159. let formData = new FormData()
  160. formData.append('avatarFile', data)
  161. uploadAvatarApi(formData).then((res) => {
  162. options.options.img = res
  163. })
  164. dialogVisible.value = false
  165. cropperVisible.value = false
  166. })
  167. }
  168. /** 实时预览 */
  169. const realTime = (data: any) => {
  170. options.previews = data
  171. }
  172. watch(
  173. () => props.img,
  174. () => {
  175. if (props.img) {
  176. options.options.img = props.img
  177. options.previews.img = props.img
  178. options.previews.url = props.img
  179. }
  180. }
  181. )
  182. </script>
  183. <style scoped>
  184. .user-info-head {
  185. position: relative;
  186. display: inline-block;
  187. }
  188. .img-circle {
  189. border-radius: 50%;
  190. }
  191. .img-lg {
  192. width: 120px;
  193. height: 120px;
  194. }
  195. .avatar-upload-preview {
  196. position: absolute;
  197. top: 50%;
  198. -webkit-transform: translate(50%, -50%);
  199. transform: translate(50%, -50%);
  200. width: 200px;
  201. height: 200px;
  202. border-radius: 50%;
  203. -webkit-box-shadow: 0 0 4px #ccc;
  204. box-shadow: 0 0 4px #ccc;
  205. overflow: hidden;
  206. }
  207. .user-info-head:hover:after {
  208. content: '+';
  209. position: absolute;
  210. left: 0;
  211. right: 0;
  212. top: 0;
  213. bottom: 0;
  214. color: #eee;
  215. background: rgba(0, 0, 0, 0.5);
  216. font-size: 24px;
  217. font-style: normal;
  218. -webkit-font-smoothing: antialiased;
  219. -moz-osx-font-smoothing: grayscale;
  220. cursor: pointer;
  221. line-height: 110px;
  222. border-radius: 50%;
  223. }
  224. </style>