UserAvatar.vue 5.4 KB

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