index.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. // import { ElImage, ElMessage, ElUpload } from 'element-plus'
  2. import { defineComponent } from 'vue'
  3. import styles from './index.module.less'
  4. import iconUpload from '../col-upload/images/icon_upload.png'
  5. // import Cropper from './cropper'
  6. import { VueCropper } from 'vue-cropper'
  7. import umiRequest from 'umi-request'
  8. import 'vue-cropper/dist/index.css'
  9. import {
  10. ElButton,
  11. ElCol,
  12. ElDialog,
  13. ElIcon,
  14. ElImage,
  15. ElMessage,
  16. ElUpload,
  17. ElRow
  18. } from 'element-plus'
  19. import { CirclePlus, Remove } from '@element-plus/icons-vue'
  20. import iconRate from '../col-upload/images/icon_rate.png'
  21. import request from '@/helpers/request'
  22. export default defineComponent({
  23. name: 'col-cropper',
  24. props: {
  25. modelValue: {
  26. type: String,
  27. default: ''
  28. },
  29. options: {
  30. // 裁切需要参数
  31. type: Object,
  32. default: {
  33. autoCrop: true, //是否默认生成截图框
  34. enlarge: 1, // 图片放大倍数
  35. autoCropWidth: 200, //默认生成截图框宽度
  36. autoCropHeight: 200, //默认生成截图框高度
  37. fixedBox: true, //是否固定截图框大小 不允许改变
  38. previewsCircle: true, //预览图是否是原圆形
  39. title: '上传图片'
  40. }
  41. },
  42. // 显示图片原始图片
  43. showSize: {
  44. type: Boolean,
  45. default: false
  46. },
  47. disabled: {
  48. type: Boolean,
  49. default: false
  50. },
  51. bucket: {
  52. type: String,
  53. default: 'daya'
  54. },
  55. size: {
  56. type: Number,
  57. default: 5 // 默认5M
  58. },
  59. accept: {
  60. type: String,
  61. default: 'images/*'
  62. },
  63. tips: {
  64. type: String,
  65. default: '请上传图片'
  66. },
  67. extraTips: {
  68. type: String,
  69. default: '图片最大不能超过5MB'
  70. },
  71. cropUploadSuccess: {
  72. type: Function,
  73. default: (data: string) => {}
  74. }
  75. },
  76. data() {
  77. return {
  78. isStopRun: false,
  79. loading: false,
  80. ossUploadUrl: 'https://ks3-cn-beijing.ksyuncs.com/' + this.bucket,
  81. dataObj: {
  82. policy: '',
  83. signature: '',
  84. key: '',
  85. KSSAccessKeyId: '',
  86. acl: 'public-read',
  87. name: ''
  88. },
  89. visible: false,
  90. img: null,
  91. optionsList: {
  92. img: '', //裁剪图片的地址
  93. autoCrop: true, //是否默认生成截图框
  94. autoCropWidth: 180, //默认生成截图框宽度
  95. autoCropHeight: 180, //默认生成截图框高度
  96. fixedBox: false, //是否固定截图框大小 不允许改变
  97. full: false,
  98. enlarge: 1, // 是否按照截图框比例输出 默认为1
  99. previewsCircle: true, //预览图是否是原圆形
  100. centerBox: true,
  101. outputType: 'png',
  102. title: '修改头像',
  103. name: null // 文件名称
  104. },
  105. previews: {} as any,
  106. url: {
  107. upload: '/sys/common/saveToImgByStr'
  108. },
  109. submitLoading: false
  110. }
  111. },
  112. methods: {
  113. onDelete() {
  114. // 删除图片
  115. this.$emit('update:modelValue', '')
  116. },
  117. //从本地选择文件
  118. async handleChange(info: any) {
  119. if (this.isStopRun) {
  120. return
  121. }
  122. this.loading = true
  123. const options = this.options
  124. this.getBase64(info.file, (imageUrl: any) => {
  125. const target = Object.assign({}, options, {
  126. img: imageUrl,
  127. name: info.file.name // 上传文件名
  128. })
  129. // ;(this as any).$refs.CropperModal.edit(target)
  130. this.edit(target)
  131. })
  132. },
  133. // 上传之前 格式与大小校验
  134. beforeUpload(file) {
  135. this.isStopRun = false
  136. var fileType = file.type
  137. if (fileType.indexOf('image') < 0) {
  138. ElMessage.warning('请上传图片')
  139. this.isStopRun = true
  140. return false
  141. }
  142. // const isJpgOrPng = this.acceptArray.includes(file.type)
  143. // if (!isJpgOrPng) {
  144. // ElMessage.error('你上传图片格式不正确!')
  145. // this.isStopRun = true
  146. // }
  147. console.log(this.size)
  148. const size = this.size || 0
  149. const isLtSize = file.size < size * 1024 * 1024
  150. if (!isLtSize) {
  151. ElMessage.error('图片大小不能超过' + this.size + 'MB!')
  152. this.isStopRun = true
  153. }
  154. return isLtSize
  155. },
  156. error() {
  157. this.remove()
  158. this.loading = false
  159. },
  160. remove() {
  161. this.onDelete()
  162. },
  163. //获取服务器返回的地址
  164. handleCropperSuccess(data: any) {
  165. //将返回的数据回显
  166. this.loading = false
  167. console.log(data, 'success')
  168. this.$emit('update:modelValue', data)
  169. // this.cropUploadSuccess(data)
  170. // this.$emit('cropUploadSuccess', data)
  171. // console.log(this.modelValue, 'modelValue')
  172. },
  173. // 取消上传
  174. handleCropperClose() {
  175. this.loading = false
  176. this.remove()
  177. },
  178. getBase64(img, callback) {
  179. const reader = new FileReader()
  180. reader.addEventListener('load', () => callback(reader.result))
  181. reader.readAsDataURL(img)
  182. },
  183. edit(record: any) {
  184. const { options } = this
  185. this.visible = true
  186. this.optionsList = Object.assign({}, options, record)
  187. console.log(this.options)
  188. },
  189. /**
  190. * 取消截图
  191. */
  192. cancelHandel() {
  193. this.visible = false
  194. // this.cropperNo()
  195. this.loading = false
  196. this.remove()
  197. },
  198. /**
  199. * 确认截图
  200. */
  201. okHandel() {
  202. ;(this as any).$refs.cropperRef.getCropBlob(async data => {
  203. this.submitLoading = true
  204. const options: any = this.options
  205. const fileName =
  206. (options.name ? options.name.split('.')[0] : +new Date()) + '.png'
  207. try {
  208. let key = new Date().getTime() + fileName
  209. let obj = {
  210. filename: fileName,
  211. bucketName: this.bucket,
  212. postData: {
  213. filename: fileName,
  214. acl: 'public-read',
  215. key: key,
  216. unknowValueField: []
  217. }
  218. }
  219. const res = await request.post('/api-website/getUploadSign', {
  220. data: obj
  221. })
  222. this.dataObj = {
  223. policy: res.data.policy,
  224. signature: res.data.signature,
  225. key: key,
  226. KSSAccessKeyId: res.data.kssAccessKeyId,
  227. acl: 'public-read',
  228. name: fileName
  229. }
  230. let formData = new FormData()
  231. for (let key in this.dataObj) {
  232. formData.append(key, this.dataObj[key])
  233. }
  234. formData.append('file', this.blobToFile(data, fileName), fileName)
  235. await umiRequest(this.ossUploadUrl, {
  236. method: 'POST',
  237. data: formData
  238. })
  239. console.log(this.ossUploadUrl + '/' + key)
  240. const uploadUrl = this.ossUploadUrl + '/' + key
  241. // this.cropperOk(uploadUrl)
  242. this.$emit('update:modelValue', uploadUrl)
  243. } catch (err: any) {
  244. ElMessage.error(err)
  245. } finally {
  246. this.submitLoading = false
  247. this.cancelHandel()
  248. }
  249. })
  250. },
  251. //转成blob
  252. blobToFile(Blob: any, fileName: any) {
  253. //兼容IE
  254. Blob.lastModifiedDate = new Date()
  255. Blob.name = fileName
  256. return Blob
  257. },
  258. base64ToFile(urlData: any, fileName: any) {
  259. let arr = urlData.split(',')
  260. let mime = arr[0].match(/:(.*?);/)[1]
  261. let bytes = atob(arr[1]) // 解码base64
  262. let n = bytes.length
  263. let ia = new Uint8Array(n)
  264. while (n--) {
  265. ia[n] = bytes.charCodeAt(n)
  266. }
  267. return new File([ia], fileName, { type: mime })
  268. },
  269. //移动框的事件
  270. realTime(data: any) {
  271. this.previews = data
  272. },
  273. //图片缩放
  274. changeScale(num: number) {
  275. num = num || 1
  276. ;(this as any).$refs.cropperRef.changeScale(num)
  277. },
  278. //向左旋转
  279. rotateLeft() {
  280. ;(this as any).$refs.cropperRef.rotateLeft()
  281. },
  282. //向右旋转
  283. rotateRight() {
  284. ;(this as any).$refs.cropperRef.rotateRight()
  285. }
  286. },
  287. render() {
  288. return (
  289. <div class={[styles.colUpload, 'w-full']}>
  290. <ElUpload
  291. disabled={this.disabled}
  292. showFileList={false}
  293. accept={this.accept}
  294. beforeUpload={this.beforeUpload}
  295. // @ts-ignore
  296. httpRequest={this.handleChange}
  297. // limit={1}
  298. ref="uploadRef"
  299. >
  300. <div
  301. ref="uploadDom"
  302. class={[styles.uploadClass, 'w-full']}
  303. style={{ height: '106px' }}
  304. >
  305. {this.modelValue ? (
  306. <ElImage
  307. src={this.modelValue}
  308. fit="cover"
  309. class={styles.uploadSection}
  310. />
  311. ) : (
  312. <div
  313. class={[
  314. styles.uploadSection,
  315. 'flex items-center flex-col justify-center'
  316. ]}
  317. >
  318. <img src={iconUpload} class="w-8 h-7 mb-3" />
  319. <p>{this.tips}</p>
  320. </div>
  321. )}
  322. </div>
  323. </ElUpload>
  324. <p class="text-3 text-[#999999] leading-6 pt-1">{this.extraTips}</p>
  325. <ElDialog
  326. modelValue={this.visible}
  327. onUpdate:modelValue={val => (this.visible = val)}
  328. appendToBody
  329. title={this.options.title}
  330. closeOnClickModal={false}
  331. width={'800px'}
  332. v-slots={{
  333. footer: () => (
  334. <span class="dialog-footer !text-center block">
  335. <ElButton
  336. onClick={this.cancelHandel}
  337. disabled={this.submitLoading}
  338. >
  339. 取消
  340. </ElButton>
  341. <ElButton
  342. type="primary"
  343. onClick={this.okHandel}
  344. loading={this.submitLoading}
  345. >
  346. 保 存
  347. </ElButton>
  348. </span>
  349. )
  350. }}
  351. >
  352. <ElRow>
  353. <ElCol xs={24} md={12} style={{ width: '350px' }}>
  354. <VueCropper
  355. ref="cropperRef"
  356. img={this.optionsList.img}
  357. info={true}
  358. autoCrop={this.optionsList.autoCrop}
  359. autoCropWidth={this.optionsList.autoCropWidth}
  360. full={this.optionsList.full}
  361. outputType={this.optionsList.outputType}
  362. autoCropHeight={this.optionsList.autoCropHeight}
  363. fixedBox={this.optionsList.fixedBox}
  364. enlarge={this.optionsList.enlarge}
  365. onRealTime={this.realTime}
  366. style={{ height: '350px' }}
  367. />
  368. <div class="flex pt-2">
  369. <div
  370. onClick={() => {
  371. this.changeScale(1)
  372. }}
  373. class="mr-2 cursor-pointer"
  374. title="放大"
  375. >
  376. <ElIcon size={30} color="#333">
  377. <CirclePlus />
  378. </ElIcon>
  379. </div>
  380. <div
  381. onClick={() => {
  382. this.changeScale(-1)
  383. }}
  384. class="mr-2 cursor-pointer"
  385. title="缩小"
  386. >
  387. <ElIcon size={30} color="#333">
  388. <Remove />
  389. </ElIcon>
  390. </div>
  391. <div
  392. onClick={this.rotateRight}
  393. title="向右旋转"
  394. class="cursor-pointer"
  395. >
  396. <img src={iconRate} class="w-[30px] h-[30px]" />
  397. </div>
  398. </div>
  399. </ElCol>
  400. <ElCol xs={24} md={12} style={{ height: '350px' }}>
  401. <div class={styles.previewImg}>
  402. <span>预览图片</span>
  403. <div
  404. class={
  405. this.optionsList.previewsCircle
  406. ? styles['avatar-upload-preview']
  407. : styles['avatar-upload-preview_range']
  408. }
  409. style={{
  410. width: this.optionsList.autoCropWidth + 'px',
  411. height: this.optionsList.autoCropHeight + 'px'
  412. }}
  413. >
  414. <ElImage src={this.previews.url} style={this.previews.img} />
  415. </div>
  416. </div>
  417. </ElCol>
  418. </ElRow>
  419. </ElDialog>
  420. </div>
  421. )
  422. }
  423. })