index.vue 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779
  1. <template>
  2. <div v-show="value" class="vue-image-crop-upload">
  3. <div class="vicp-wrap">
  4. <div class="vicp-close" @click="off">
  5. <i class="vicp-icon4" />
  6. </div>
  7. <div v-show="step == 1" class="vicp-step1">
  8. <div
  9. class="vicp-drop-area"
  10. @dragleave="preventDefault"
  11. @dragover="preventDefault"
  12. @dragenter="preventDefault"
  13. @click="handleClick"
  14. @drop="handleChange"
  15. >
  16. <i v-show="loading != 1" class="vicp-icon1">
  17. <i class="vicp-icon1-arrow" />
  18. <i class="vicp-icon1-body" />
  19. <i class="vicp-icon1-bottom" />
  20. </i>
  21. <span v-show="loading !== 1" class="vicp-hint">{{ lang.hint }}</span>
  22. <span v-show="!isSupported" class="vicp-no-supported-hint">{{ lang.noSupported }}</span>
  23. <input v-show="false" v-if="step == 1" ref="fileinput" type="file" @change="handleChange">
  24. </div>
  25. <div v-show="hasError" class="vicp-error">
  26. <i class="vicp-icon2" />
  27. {{ errorMsg }}
  28. </div>
  29. <div class="vicp-operate">
  30. <a @click="off" @mousedown="ripple">{{ lang.btn.off }}</a>
  31. </div>
  32. </div>
  33. <div v-if="step == 2" class="vicp-step2">
  34. <div class="vicp-crop">
  35. <div v-show="true" class="vicp-crop-left">
  36. <div class="vicp-img-container">
  37. <img
  38. ref="img"
  39. :src="sourceImgUrl"
  40. :style="sourceImgStyle"
  41. class="vicp-img"
  42. draggable="false"
  43. @drag="preventDefault"
  44. @dragstart="preventDefault"
  45. @dragend="preventDefault"
  46. @dragleave="preventDefault"
  47. @dragover="preventDefault"
  48. @dragenter="preventDefault"
  49. @drop="preventDefault"
  50. @touchstart="imgStartMove"
  51. @touchmove="imgMove"
  52. @touchend="createImg"
  53. @touchcancel="createImg"
  54. @mousedown="imgStartMove"
  55. @mousemove="imgMove"
  56. @mouseup="createImg"
  57. @mouseout="createImg"
  58. >
  59. <div :style="sourceImgShadeStyle" class="vicp-img-shade vicp-img-shade-1" />
  60. <div :style="sourceImgShadeStyle" class="vicp-img-shade vicp-img-shade-2" />
  61. </div>
  62. <div class="vicp-range">
  63. <input
  64. :value="scale.range"
  65. type="range"
  66. step="1"
  67. min="0"
  68. max="100"
  69. @input="zoomChange"
  70. >
  71. <i
  72. class="vicp-icon5"
  73. @mousedown="startZoomSub"
  74. @mouseout="endZoomSub"
  75. @mouseup="endZoomSub"
  76. />
  77. <i
  78. class="vicp-icon6"
  79. @mousedown="startZoomAdd"
  80. @mouseout="endZoomAdd"
  81. @mouseup="endZoomAdd"
  82. />
  83. </div>
  84. <div v-if="!noRotate" class="vicp-rotate">
  85. <i @mousedown="startRotateLeft" @mouseout="endRotate" @mouseup="endRotate">↺</i>
  86. <i @mousedown="startRotateRight" @mouseout="endRotate" @mouseup="endRotate">↻</i>
  87. </div>
  88. </div>
  89. <div v-show="true" class="vicp-crop-right">
  90. <div class="vicp-preview">
  91. <div v-if="!noSquare" class="vicp-preview-item">
  92. <img :src="createImgUrl" :style="previewStyle">
  93. <span>{{ lang.preview }}</span>
  94. </div>
  95. <div v-if="!noCircle" class="vicp-preview-item vicp-preview-item-circle">
  96. <img :src="createImgUrl" :style="previewStyle">
  97. <span>{{ lang.preview }}</span>
  98. </div>
  99. </div>
  100. </div>
  101. </div>
  102. <div class="vicp-operate">
  103. <a @click="setStep(1)" @mousedown="ripple">{{ lang.btn.back }}</a>
  104. <a class="vicp-operate-btn" @click="prepareUpload" @mousedown="ripple">{{ lang.btn.save }}</a>
  105. </div>
  106. </div>
  107. <div v-if="step == 3" class="vicp-step3">
  108. <div class="vicp-upload">
  109. <span v-show="loading === 1" class="vicp-loading">{{ lang.loading }}</span>
  110. <div class="vicp-progress-wrap">
  111. <span v-show="loading === 1" :style="progressStyle" class="vicp-progress" />
  112. </div>
  113. <div v-show="hasError" class="vicp-error">
  114. <i class="vicp-icon2" />
  115. {{ errorMsg }}
  116. </div>
  117. <div v-show="loading === 2" class="vicp-success">
  118. <i class="vicp-icon3" />
  119. {{ lang.success }}
  120. </div>
  121. </div>
  122. <div class="vicp-operate">
  123. <a @click="setStep(2)" @mousedown="ripple">{{ lang.btn.back }}</a>
  124. <a @click="off" @mousedown="ripple">{{ lang.btn.close }}</a>
  125. </div>
  126. </div>
  127. <canvas v-show="false" ref="canvas" :width="width" :height="height" />
  128. </div>
  129. </div>
  130. </template>
  131. <script>
  132. 'use strict'
  133. import request from '@/utils/request'
  134. import language from './utils/language.js'
  135. import mimes from './utils/mimes.js'
  136. import data2blob from './utils/data2blob.js'
  137. import effectRipple from './utils/effectRipple.js'
  138. export default {
  139. props: {
  140. // 域,上传文件name,触发事件会带上(如果一个页面多个图片上传控件,可以做区分
  141. field: {
  142. type: String,
  143. default: 'avatar'
  144. },
  145. // 原名key,类似于id,触发事件会带上(如果一个页面多个图片上传控件,可以做区分
  146. ki: {
  147. type: Number,
  148. default: 0
  149. },
  150. // 显示该控件与否
  151. value: {
  152. type: Boolean,
  153. default: true
  154. },
  155. // 上传地址
  156. url: {
  157. type: String,
  158. default: ''
  159. },
  160. // 其他要上传文件附带的数据,对象格式
  161. params: {
  162. type: Object,
  163. default: null
  164. },
  165. // Add custom headers
  166. headers: {
  167. type: Object,
  168. default: null
  169. },
  170. // 剪裁图片的宽
  171. width: {
  172. type: Number,
  173. default: 200
  174. },
  175. // 剪裁图片的高
  176. height: {
  177. type: Number,
  178. default: 200
  179. },
  180. // 不显示旋转功能
  181. noRotate: {
  182. type: Boolean,
  183. default: true
  184. },
  185. // 不预览圆形图片
  186. noCircle: {
  187. type: Boolean,
  188. default: false
  189. },
  190. // 不预览方形图片
  191. noSquare: {
  192. type: Boolean,
  193. default: false
  194. },
  195. // 单文件大小限制
  196. maxSize: {
  197. type: Number,
  198. default: 10240
  199. },
  200. // 语言类型
  201. langType: {
  202. type: String,
  203. default: 'zh'
  204. },
  205. // 语言包
  206. langExt: {
  207. type: Object,
  208. default: null
  209. },
  210. // 图片上传格式
  211. imgFormat: {
  212. type: String,
  213. default: 'png'
  214. },
  215. // 是否支持跨域
  216. withCredentials: {
  217. type: Boolean,
  218. default: false
  219. }
  220. },
  221. data() {
  222. const { imgFormat, langType, langExt, width, height } = this
  223. let isSupported = true
  224. const allowImgFormat = ['jpg', 'png']
  225. const tempImgFormat =
  226. allowImgFormat.indexOf(imgFormat) === -1 ? 'jpg' : imgFormat
  227. const lang = language[langType] ? language[langType] : language['en']
  228. const mime = mimes[tempImgFormat]
  229. // 规范图片格式
  230. this.imgFormat = tempImgFormat
  231. if (langExt) {
  232. Object.assign(lang, langExt)
  233. }
  234. if (typeof FormData !== 'function') {
  235. isSupported = false
  236. }
  237. return {
  238. // 图片的mime
  239. mime,
  240. // 语言包
  241. lang,
  242. // 浏览器是否支持该控件
  243. isSupported,
  244. // 浏览器是否支持触屏事件
  245. // eslint-disable-next-line no-prototype-builtins
  246. isSupportTouch: document.hasOwnProperty('ontouchstart'),
  247. // 步骤
  248. step: 1, // 1选择文件 2剪裁 3上传
  249. // 上传状态及进度
  250. loading: 0, // 0未开始 1正在 2成功 3错误
  251. progress: 0,
  252. // 是否有错误及错误信息
  253. hasError: false,
  254. errorMsg: '',
  255. // 需求图宽高比
  256. ratio: width / height,
  257. // 原图地址、生成图片地址
  258. sourceImg: null,
  259. sourceImgUrl: '',
  260. createImgUrl: '',
  261. // 原图片拖动事件初始值
  262. sourceImgMouseDown: {
  263. on: false,
  264. mX: 0, // 鼠标按下的坐标
  265. mY: 0,
  266. x: 0, // scale原图坐标
  267. y: 0
  268. },
  269. // 生成图片预览的容器大小
  270. previewContainer: {
  271. width: 100,
  272. height: 100
  273. },
  274. // 原图容器宽高
  275. sourceImgContainer: {
  276. // sic
  277. width: 240,
  278. height: 184 // 如果生成图比例与此一致会出现bug,先改成特殊的格式吧,哈哈哈
  279. },
  280. // 原图展示属性
  281. scale: {
  282. zoomAddOn: false, // 按钮缩放事件开启
  283. zoomSubOn: false, // 按钮缩放事件开启
  284. range: 1, // 最大100
  285. rotateLeft: false, // 按钮向左旋转事件开启
  286. rotateRight: false, // 按钮向右旋转事件开启
  287. degree: 0, // 旋转度数
  288. x: 0,
  289. y: 0,
  290. width: 0,
  291. height: 0,
  292. maxWidth: 0,
  293. maxHeight: 0,
  294. minWidth: 0, // 最宽
  295. minHeight: 0,
  296. naturalWidth: 0, // 原宽
  297. naturalHeight: 0
  298. }
  299. }
  300. },
  301. computed: {
  302. // 进度条样式
  303. progressStyle() {
  304. const { progress } = this
  305. return {
  306. width: progress + '%'
  307. }
  308. },
  309. // 原图样式
  310. sourceImgStyle() {
  311. const { scale, sourceImgMasking } = this
  312. const top = scale.y + sourceImgMasking.y + 'px'
  313. const left = scale.x + sourceImgMasking.x + 'px'
  314. return {
  315. top,
  316. left,
  317. width: scale.width + 'px',
  318. height: scale.height + 'px',
  319. transform: 'rotate(' + scale.degree + 'deg)', // 旋转时 左侧原始图旋转样式
  320. '-ms-transform': 'rotate(' + scale.degree + 'deg)', // 兼容IE9
  321. '-moz-transform': 'rotate(' + scale.degree + 'deg)', // 兼容FireFox
  322. '-webkit-transform': 'rotate(' + scale.degree + 'deg)', // 兼容Safari 和 chrome
  323. '-o-transform': 'rotate(' + scale.degree + 'deg)' // 兼容 Opera
  324. }
  325. },
  326. // 原图蒙版属性
  327. sourceImgMasking() {
  328. const { width, height, ratio, sourceImgContainer } = this
  329. const sic = sourceImgContainer
  330. const sicRatio = sic.width / sic.height // 原图容器宽高比
  331. let x = 0
  332. let y = 0
  333. let w = sic.width
  334. let h = sic.height
  335. let scale = 1
  336. if (ratio < sicRatio) {
  337. scale = sic.height / height
  338. w = sic.height * ratio
  339. x = (sic.width - w) / 2
  340. }
  341. if (ratio > sicRatio) {
  342. scale = sic.width / width
  343. h = sic.width / ratio
  344. y = (sic.height - h) / 2
  345. }
  346. return {
  347. scale, // 蒙版相对需求宽高的缩放
  348. x,
  349. y,
  350. width: w,
  351. height: h
  352. }
  353. },
  354. // 原图遮罩样式
  355. sourceImgShadeStyle() {
  356. const { sourceImgMasking, sourceImgContainer } = this
  357. const sic = sourceImgContainer
  358. const sim = sourceImgMasking
  359. const w =
  360. sim.width === sic.width ? sim.width : (sic.width - sim.width) / 2
  361. const h =
  362. sim.height === sic.height ? sim.height : (sic.height - sim.height) / 2
  363. return {
  364. width: w + 'px',
  365. height: h + 'px'
  366. }
  367. },
  368. previewStyle() {
  369. const { ratio, previewContainer } = this
  370. const pc = previewContainer
  371. let w = pc.width
  372. let h = pc.height
  373. const pcRatio = w / h
  374. if (ratio < pcRatio) {
  375. w = pc.height * ratio
  376. }
  377. if (ratio > pcRatio) {
  378. h = pc.width / ratio
  379. }
  380. return {
  381. width: w + 'px',
  382. height: h + 'px'
  383. }
  384. }
  385. },
  386. watch: {
  387. value(newValue) {
  388. if (newValue && this.loading !== 1) {
  389. this.reset()
  390. }
  391. }
  392. },
  393. created() {
  394. // 绑定按键esc隐藏此插件事件
  395. document.addEventListener('keyup', this.closeHandler)
  396. },
  397. destroyed() {
  398. document.removeEventListener('keyup', this.closeHandler)
  399. },
  400. methods: {
  401. // 点击波纹效果
  402. ripple(e) {
  403. effectRipple(e)
  404. },
  405. // 关闭控件
  406. off() {
  407. setTimeout(() => {
  408. this.$emit('input', false)
  409. this.$emit('close')
  410. if (this.step === 3 && this.loading === 2) {
  411. this.setStep(1)
  412. }
  413. }, 200)
  414. },
  415. // 设置步骤
  416. setStep(no) {
  417. // 延时是为了显示动画效果呢,哈哈哈
  418. setTimeout(() => {
  419. this.step = no
  420. }, 200)
  421. },
  422. /* 图片选择区域函数绑定
  423. ---------------------------------------------------------------*/
  424. preventDefault(e) {
  425. e.preventDefault()
  426. return false
  427. },
  428. handleClick(e) {
  429. if (this.loading !== 1) {
  430. if (e.target !== this.$refs.fileinput) {
  431. e.preventDefault()
  432. if (document.activeElement !== this.$refs) {
  433. this.$refs.fileinput.click()
  434. }
  435. }
  436. }
  437. },
  438. handleChange(e) {
  439. e.preventDefault()
  440. if (this.loading !== 1) {
  441. const files = e.target.files || e.dataTransfer.files
  442. this.reset()
  443. if (this.checkFile(files[0])) {
  444. this.setSourceImg(files[0])
  445. }
  446. }
  447. },
  448. /* ---------------------------------------------------------------*/
  449. // 检测选择的文件是否合适
  450. checkFile(file) {
  451. const { lang, maxSize } = this
  452. // 仅限图片
  453. if (file.type.indexOf('image') === -1) {
  454. this.hasError = true
  455. this.errorMsg = lang.error.onlyImg
  456. return false
  457. }
  458. // 超出大小
  459. if (file.size / 1024 > maxSize) {
  460. this.hasError = true
  461. this.errorMsg = lang.error.outOfSize + maxSize + 'kb'
  462. return false
  463. }
  464. return true
  465. },
  466. // 重置控件
  467. reset() {
  468. this.loading = 0
  469. this.hasError = false
  470. this.errorMsg = ''
  471. this.progress = 0
  472. },
  473. // 设置图片源
  474. setSourceImg(file) {
  475. const fr = new FileReader()
  476. fr.onload = e => {
  477. this.sourceImgUrl = fr.result
  478. this.startCrop()
  479. }
  480. fr.readAsDataURL(file)
  481. },
  482. // 剪裁前准备工作
  483. startCrop() {
  484. const {
  485. width,
  486. height,
  487. ratio,
  488. scale,
  489. sourceImgUrl,
  490. sourceImgMasking,
  491. lang
  492. } = this
  493. const sim = sourceImgMasking
  494. const img = new Image()
  495. img.src = sourceImgUrl
  496. img.onload = () => {
  497. const nWidth = img.naturalWidth
  498. const nHeight = img.naturalHeight
  499. const nRatio = nWidth / nHeight
  500. let w = sim.width
  501. let h = sim.height
  502. let x = 0
  503. let y = 0
  504. // 图片像素不达标
  505. if (nWidth < width || nHeight < height) {
  506. this.hasError = true
  507. this.errorMsg = lang.error.lowestPx + width + '*' + height
  508. return false
  509. }
  510. if (ratio > nRatio) {
  511. h = w / nRatio
  512. y = (sim.height - h) / 2
  513. }
  514. if (ratio < nRatio) {
  515. w = h * nRatio
  516. x = (sim.width - w) / 2
  517. }
  518. scale.range = 0
  519. scale.x = x
  520. scale.y = y
  521. scale.width = w
  522. scale.height = h
  523. scale.degree = 0
  524. scale.minWidth = w
  525. scale.minHeight = h
  526. scale.maxWidth = nWidth * sim.scale
  527. scale.maxHeight = nHeight * sim.scale
  528. scale.naturalWidth = nWidth
  529. scale.naturalHeight = nHeight
  530. this.sourceImg = img
  531. this.createImg()
  532. this.setStep(2)
  533. }
  534. },
  535. // 鼠标按下图片准备移动
  536. imgStartMove(e) {
  537. e.preventDefault()
  538. // 支持触摸事件,则鼠标事件无效
  539. if (this.isSupportTouch && !e.targetTouches) {
  540. return false
  541. }
  542. const et = e.targetTouches ? e.targetTouches[0] : e
  543. const { sourceImgMouseDown, scale } = this
  544. const simd = sourceImgMouseDown
  545. simd.mX = et.screenX
  546. simd.mY = et.screenY
  547. simd.x = scale.x
  548. simd.y = scale.y
  549. simd.on = true
  550. },
  551. // 鼠标按下状态下移动,图片移动
  552. imgMove(e) {
  553. e.preventDefault()
  554. // 支持触摸事件,则鼠标事件无效
  555. if (this.isSupportTouch && !e.targetTouches) {
  556. return false
  557. }
  558. const et = e.targetTouches ? e.targetTouches[0] : e
  559. const {
  560. sourceImgMouseDown: { on, mX, mY, x, y },
  561. scale,
  562. sourceImgMasking
  563. } = this
  564. const sim = sourceImgMasking
  565. const nX = et.screenX
  566. const nY = et.screenY
  567. const dX = nX - mX
  568. const dY = nY - mY
  569. let rX = x + dX
  570. let rY = y + dY
  571. if (!on) return
  572. if (rX > 0) {
  573. rX = 0
  574. }
  575. if (rY > 0) {
  576. rY = 0
  577. }
  578. if (rX < sim.width - scale.width) {
  579. rX = sim.width - scale.width
  580. }
  581. if (rY < sim.height - scale.height) {
  582. rY = sim.height - scale.height
  583. }
  584. scale.x = rX
  585. scale.y = rY
  586. },
  587. // 按钮按下开始向右旋转
  588. startRotateRight(e) {
  589. const { scale } = this
  590. scale.rotateRight = true
  591. const rotate = () => {
  592. if (scale.rotateRight) {
  593. const degree = ++scale.degree
  594. this.createImg(degree)
  595. setTimeout(function() {
  596. rotate()
  597. }, 60)
  598. }
  599. }
  600. rotate()
  601. },
  602. // 按钮按下开始向左旋转
  603. startRotateLeft(e) {
  604. const { scale } = this
  605. scale.rotateLeft = true
  606. const rotate = () => {
  607. if (scale.rotateLeft) {
  608. const degree = --scale.degree
  609. this.createImg(degree)
  610. setTimeout(function() {
  611. rotate()
  612. }, 60)
  613. }
  614. }
  615. rotate()
  616. },
  617. // 停止旋转
  618. endRotate() {
  619. const { scale } = this
  620. scale.rotateLeft = false
  621. scale.rotateRight = false
  622. },
  623. // 按钮按下开始放大
  624. startZoomAdd(e) {
  625. const { scale } = this
  626. scale.zoomAddOn = true
  627. const zoom = () => {
  628. if (scale.zoomAddOn) {
  629. const range = scale.range >= 100 ? 100 : ++scale.range
  630. this.zoomImg(range)
  631. setTimeout(function() {
  632. zoom()
  633. }, 60)
  634. }
  635. }
  636. zoom()
  637. },
  638. // 按钮松开或移开取消放大
  639. endZoomAdd(e) {
  640. this.scale.zoomAddOn = false
  641. },
  642. // 按钮按下开始缩小
  643. startZoomSub(e) {
  644. const { scale } = this
  645. scale.zoomSubOn = true
  646. const zoom = () => {
  647. if (scale.zoomSubOn) {
  648. const range = scale.range <= 0 ? 0 : --scale.range
  649. this.zoomImg(range)
  650. setTimeout(function() {
  651. zoom()
  652. }, 60)
  653. }
  654. }
  655. zoom()
  656. },
  657. // 按钮松开或移开取消缩小
  658. endZoomSub(e) {
  659. const { scale } = this
  660. scale.zoomSubOn = false
  661. },
  662. zoomChange(e) {
  663. this.zoomImg(e.target.value)
  664. },
  665. // 缩放原图
  666. zoomImg(newRange) {
  667. const { sourceImgMasking, scale } = this
  668. const {
  669. maxWidth,
  670. maxHeight,
  671. minWidth,
  672. minHeight,
  673. width,
  674. height,
  675. x,
  676. y
  677. } = scale
  678. const sim = sourceImgMasking
  679. // 蒙版宽高
  680. const sWidth = sim.width
  681. const sHeight = sim.height
  682. // 新宽高
  683. const nWidth = minWidth + ((maxWidth - minWidth) * newRange) / 100
  684. const nHeight = minHeight + ((maxHeight - minHeight) * newRange) / 100
  685. // 新坐标(根据蒙版中心点缩放)
  686. let nX = sWidth / 2 - (nWidth / width) * (sWidth / 2 - x)
  687. let nY = sHeight / 2 - (nHeight / height) * (sHeight / 2 - y)
  688. // 判断新坐标是否超过蒙版限制
  689. if (nX > 0) {
  690. nX = 0
  691. }
  692. if (nY > 0) {
  693. nY = 0
  694. }
  695. if (nX < sWidth - nWidth) {
  696. nX = sWidth - nWidth
  697. }
  698. if (nY < sHeight - nHeight) {
  699. nY = sHeight - nHeight
  700. }
  701. // 赋值处理
  702. scale.x = nX
  703. scale.y = nY
  704. scale.width = nWidth
  705. scale.height = nHeight
  706. scale.range = newRange
  707. setTimeout(() => {
  708. if (scale.range === newRange) {
  709. this.createImg()
  710. }
  711. }, 300)
  712. },
  713. // 生成需求图片
  714. createImg(e) {
  715. const {
  716. mime,
  717. sourceImg,
  718. scale: { x, y, width, height, degree },
  719. sourceImgMasking: { scale }
  720. } = this
  721. const canvas = this.$refs.canvas
  722. const ctx = canvas.getContext('2d')
  723. if (e) {
  724. // 取消鼠标按下移动状态
  725. this.sourceImgMouseDown.on = false
  726. }
  727. canvas.width = this.width
  728. canvas.height = this.height
  729. ctx.clearRect(0, 0, this.width, this.height)
  730. // 将透明区域设置为白色底边
  731. ctx.fillStyle = '#fff'
  732. ctx.fillRect(0, 0, this.width, this.height)
  733. ctx.translate(this.width * 0.5, this.height * 0.5)
  734. ctx.rotate((Math.PI * degree) / 180)
  735. ctx.translate(-this.width * 0.5, -this.height * 0.5)
  736. ctx.drawImage(
  737. sourceImg,
  738. x / scale,
  739. y / scale,
  740. width / scale,
  741. height / scale
  742. )
  743. this.createImgUrl = canvas.toDataURL(mime)
  744. },
  745. prepareUpload() {
  746. const { url, createImgUrl, field, ki } = this
  747. this.$emit('crop-success', createImgUrl, field, ki)
  748. if (typeof url === 'string' && url) {
  749. this.upload()
  750. } else {
  751. this.off()
  752. }
  753. },
  754. // 上传图片
  755. upload() {
  756. const {
  757. lang,
  758. imgFormat,
  759. mime,
  760. url,
  761. params,
  762. field,
  763. ki,
  764. createImgUrl
  765. } = this
  766. const fmData = new FormData()
  767. fmData.append(
  768. field,
  769. data2blob(createImgUrl, mime),
  770. field + '.' + imgFormat
  771. )
  772. // 添加其他参数
  773. if (typeof params === 'object' && params) {
  774. Object.keys(params).forEach(k => {
  775. fmData.append(k, params[k])
  776. })
  777. }
  778. // 监听进度回调
  779. // const uploadProgress = (event) => {
  780. // if (event.lengthComputable) {
  781. // this.progress = 100 * Math.round(event.loaded) / event.total
  782. // }
  783. // }
  784. // 上传文件
  785. this.reset()
  786. this.loading = 1
  787. this.setStep(3)
  788. request({
  789. url,
  790. method: 'post',
  791. data: fmData
  792. })
  793. .then(resData => {
  794. this.loading = 2
  795. this.$emit('crop-upload-success', resData.data)
  796. })
  797. .catch(err => {
  798. if (this.value) {
  799. this.loading = 3
  800. this.hasError = true
  801. this.errorMsg = lang.fail
  802. this.$emit('crop-upload-fail', err, field, ki)
  803. }
  804. })
  805. },
  806. closeHandler(e) {
  807. if (this.value && (e.key === 'Escape' || e.keyCode === 27)) {
  808. this.off()
  809. }
  810. }
  811. }
  812. }
  813. </script>
  814. <style lang="scss">
  815. @charset "UTF-8";
  816. @-webkit-keyframes vicp_progress {
  817. 0% {
  818. background-position-y: 0;
  819. }
  820. 100% {
  821. background-position-y: 40px;
  822. }
  823. }
  824. @keyframes vicp_progress {
  825. 0% {
  826. background-position-y: 0;
  827. }
  828. 100% {
  829. background-position-y: 40px;
  830. }
  831. }
  832. @-webkit-keyframes vicp {
  833. 0% {
  834. opacity: 0;
  835. -webkit-transform: scale(0) translatey(-60px);
  836. transform: scale(0) translatey(-60px);
  837. }
  838. 100% {
  839. opacity: 1;
  840. -webkit-transform: scale(1) translatey(0);
  841. transform: scale(1) translatey(0);
  842. }
  843. }
  844. @keyframes vicp {
  845. 0% {
  846. opacity: 0;
  847. -webkit-transform: scale(0) translatey(-60px);
  848. transform: scale(0) translatey(-60px);
  849. }
  850. 100% {
  851. opacity: 1;
  852. -webkit-transform: scale(1) translatey(0);
  853. transform: scale(1) translatey(0);
  854. }
  855. }
  856. .vue-image-crop-upload {
  857. position: fixed;
  858. display: block;
  859. -webkit-box-sizing: border-box;
  860. box-sizing: border-box;
  861. z-index: 10000;
  862. top: 0;
  863. bottom: 0;
  864. left: 0;
  865. right: 0;
  866. width: 100%;
  867. height: 100%;
  868. background-color: rgba(0, 0, 0, 0.65);
  869. -webkit-tap-highlight-color: transparent;
  870. -moz-tap-highlight-color: transparent;
  871. }
  872. .vue-image-crop-upload .vicp-wrap {
  873. -webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
  874. box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
  875. position: fixed;
  876. display: block;
  877. -webkit-box-sizing: border-box;
  878. box-sizing: border-box;
  879. z-index: 10000;
  880. top: 0;
  881. bottom: 0;
  882. left: 0;
  883. right: 0;
  884. margin: auto;
  885. width: 600px;
  886. height: 330px;
  887. padding: 25px;
  888. background-color: #fff;
  889. border-radius: 2px;
  890. -webkit-animation: vicp 0.12s ease-in;
  891. animation: vicp 0.12s ease-in;
  892. }
  893. .vue-image-crop-upload .vicp-wrap .vicp-close {
  894. position: absolute;
  895. right: -30px;
  896. top: -30px;
  897. }
  898. .vue-image-crop-upload .vicp-wrap .vicp-close .vicp-icon4 {
  899. position: relative;
  900. display: block;
  901. width: 30px;
  902. height: 30px;
  903. cursor: pointer;
  904. -webkit-transition: -webkit-transform 0.18s;
  905. transition: -webkit-transform 0.18s;
  906. transition: transform 0.18s;
  907. transition: transform 0.18s, -webkit-transform 0.18s;
  908. -webkit-transform: rotate(0);
  909. -ms-transform: rotate(0);
  910. transform: rotate(0);
  911. }
  912. .vue-image-crop-upload .vicp-wrap .vicp-close .vicp-icon4::after,
  913. .vue-image-crop-upload .vicp-wrap .vicp-close .vicp-icon4::before {
  914. -webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
  915. box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
  916. content: "";
  917. position: absolute;
  918. top: 12px;
  919. left: 4px;
  920. width: 20px;
  921. height: 3px;
  922. -webkit-transform: rotate(45deg);
  923. -ms-transform: rotate(45deg);
  924. transform: rotate(45deg);
  925. background-color: #fff;
  926. }
  927. .vue-image-crop-upload .vicp-wrap .vicp-close .vicp-icon4::after {
  928. -webkit-transform: rotate(-45deg);
  929. -ms-transform: rotate(-45deg);
  930. transform: rotate(-45deg);
  931. }
  932. .vue-image-crop-upload .vicp-wrap .vicp-close .vicp-icon4:hover {
  933. -webkit-transform: rotate(90deg);
  934. -ms-transform: rotate(90deg);
  935. transform: rotate(90deg);
  936. }
  937. .vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area {
  938. position: relative;
  939. -webkit-box-sizing: border-box;
  940. box-sizing: border-box;
  941. padding: 35px;
  942. height: 170px;
  943. background-color: rgba(0, 0, 0, 0.03);
  944. text-align: center;
  945. border: 1px dashed rgba(0, 0, 0, 0.08);
  946. overflow: hidden;
  947. }
  948. .vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area .vicp-icon1 {
  949. display: block;
  950. margin: 0 auto 6px;
  951. width: 42px;
  952. height: 42px;
  953. overflow: hidden;
  954. }
  955. .vue-image-crop-upload
  956. .vicp-wrap
  957. .vicp-step1
  958. .vicp-drop-area
  959. .vicp-icon1
  960. .vicp-icon1-arrow {
  961. display: block;
  962. margin: 0 auto;
  963. width: 0;
  964. height: 0;
  965. border-bottom: 14.7px solid rgba(0, 0, 0, 0.3);
  966. border-left: 14.7px solid transparent;
  967. border-right: 14.7px solid transparent;
  968. }
  969. .vue-image-crop-upload
  970. .vicp-wrap
  971. .vicp-step1
  972. .vicp-drop-area
  973. .vicp-icon1
  974. .vicp-icon1-body {
  975. display: block;
  976. width: 12.6px;
  977. height: 14.7px;
  978. margin: 0 auto;
  979. background-color: rgba(0, 0, 0, 0.3);
  980. }
  981. .vue-image-crop-upload
  982. .vicp-wrap
  983. .vicp-step1
  984. .vicp-drop-area
  985. .vicp-icon1
  986. .vicp-icon1-bottom {
  987. -webkit-box-sizing: border-box;
  988. box-sizing: border-box;
  989. display: block;
  990. height: 12.6px;
  991. border: 6px solid rgba(0, 0, 0, 0.3);
  992. border-top: none;
  993. }
  994. .vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area .vicp-hint {
  995. display: block;
  996. padding: 15px;
  997. font-size: 14px;
  998. color: #666;
  999. line-height: 30px;
  1000. }
  1001. .vue-image-crop-upload
  1002. .vicp-wrap
  1003. .vicp-step1
  1004. .vicp-drop-area
  1005. .vicp-no-supported-hint {
  1006. display: block;
  1007. position: absolute;
  1008. top: 0;
  1009. left: 0;
  1010. padding: 30px;
  1011. width: 100%;
  1012. height: 60px;
  1013. line-height: 30px;
  1014. background-color: #eee;
  1015. text-align: center;
  1016. color: #666;
  1017. font-size: 14px;
  1018. }
  1019. .vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area:hover {
  1020. cursor: pointer;
  1021. border-color: rgba(0, 0, 0, 0.1);
  1022. background-color: rgba(0, 0, 0, 0.05);
  1023. }
  1024. .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop {
  1025. overflow: hidden;
  1026. }
  1027. .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left {
  1028. float: left;
  1029. }
  1030. .vue-image-crop-upload
  1031. .vicp-wrap
  1032. .vicp-step2
  1033. .vicp-crop
  1034. .vicp-crop-left
  1035. .vicp-img-container {
  1036. position: relative;
  1037. display: block;
  1038. width: 240px;
  1039. height: 180px;
  1040. background-color: #e5e5e0;
  1041. overflow: hidden;
  1042. }
  1043. .vue-image-crop-upload
  1044. .vicp-wrap
  1045. .vicp-step2
  1046. .vicp-crop
  1047. .vicp-crop-left
  1048. .vicp-img-container
  1049. .vicp-img {
  1050. position: absolute;
  1051. display: block;
  1052. cursor: move;
  1053. -webkit-user-select: none;
  1054. -moz-user-select: none;
  1055. -ms-user-select: none;
  1056. user-select: none;
  1057. }
  1058. .vue-image-crop-upload
  1059. .vicp-wrap
  1060. .vicp-step2
  1061. .vicp-crop
  1062. .vicp-crop-left
  1063. .vicp-img-container
  1064. .vicp-img-shade {
  1065. -webkit-box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
  1066. box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
  1067. position: absolute;
  1068. background-color: rgba(241, 242, 243, 0.8);
  1069. }
  1070. .vue-image-crop-upload
  1071. .vicp-wrap
  1072. .vicp-step2
  1073. .vicp-crop
  1074. .vicp-crop-left
  1075. .vicp-img-container
  1076. .vicp-img-shade.vicp-img-shade-1 {
  1077. top: 0;
  1078. left: 0;
  1079. }
  1080. .vue-image-crop-upload
  1081. .vicp-wrap
  1082. .vicp-step2
  1083. .vicp-crop
  1084. .vicp-crop-left
  1085. .vicp-img-container
  1086. .vicp-img-shade.vicp-img-shade-2 {
  1087. bottom: 0;
  1088. right: 0;
  1089. }
  1090. .vue-image-crop-upload
  1091. .vicp-wrap
  1092. .vicp-step2
  1093. .vicp-crop
  1094. .vicp-crop-left
  1095. .vicp-rotate {
  1096. position: relative;
  1097. width: 240px;
  1098. height: 18px;
  1099. }
  1100. .vue-image-crop-upload
  1101. .vicp-wrap
  1102. .vicp-step2
  1103. .vicp-crop
  1104. .vicp-crop-left
  1105. .vicp-rotate
  1106. i {
  1107. display: block;
  1108. width: 18px;
  1109. height: 18px;
  1110. border-radius: 100%;
  1111. line-height: 18px;
  1112. text-align: center;
  1113. font-size: 12px;
  1114. font-weight: bold;
  1115. background-color: rgba(0, 0, 0, 0.08);
  1116. color: #fff;
  1117. overflow: hidden;
  1118. }
  1119. .vue-image-crop-upload
  1120. .vicp-wrap
  1121. .vicp-step2
  1122. .vicp-crop
  1123. .vicp-crop-left
  1124. .vicp-rotate
  1125. i:hover {
  1126. -webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
  1127. box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
  1128. cursor: pointer;
  1129. background-color: rgba(0, 0, 0, 0.14);
  1130. }
  1131. .vue-image-crop-upload
  1132. .vicp-wrap
  1133. .vicp-step2
  1134. .vicp-crop
  1135. .vicp-crop-left
  1136. .vicp-rotate
  1137. i:first-child {
  1138. float: left;
  1139. }
  1140. .vue-image-crop-upload
  1141. .vicp-wrap
  1142. .vicp-step2
  1143. .vicp-crop
  1144. .vicp-crop-left
  1145. .vicp-rotate
  1146. i:last-child {
  1147. float: right;
  1148. }
  1149. .vue-image-crop-upload
  1150. .vicp-wrap
  1151. .vicp-step2
  1152. .vicp-crop
  1153. .vicp-crop-left
  1154. .vicp-range {
  1155. position: relative;
  1156. margin: 30px 0 10px 0;
  1157. width: 240px;
  1158. height: 18px;
  1159. }
  1160. .vue-image-crop-upload
  1161. .vicp-wrap
  1162. .vicp-step2
  1163. .vicp-crop
  1164. .vicp-crop-left
  1165. .vicp-range
  1166. .vicp-icon5,
  1167. .vue-image-crop-upload
  1168. .vicp-wrap
  1169. .vicp-step2
  1170. .vicp-crop
  1171. .vicp-crop-left
  1172. .vicp-range
  1173. .vicp-icon6 {
  1174. position: absolute;
  1175. top: 0;
  1176. width: 18px;
  1177. height: 18px;
  1178. border-radius: 100%;
  1179. background-color: rgba(0, 0, 0, 0.08);
  1180. }
  1181. .vue-image-crop-upload
  1182. .vicp-wrap
  1183. .vicp-step2
  1184. .vicp-crop
  1185. .vicp-crop-left
  1186. .vicp-range
  1187. .vicp-icon5:hover,
  1188. .vue-image-crop-upload
  1189. .vicp-wrap
  1190. .vicp-step2
  1191. .vicp-crop
  1192. .vicp-crop-left
  1193. .vicp-range
  1194. .vicp-icon6:hover {
  1195. -webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
  1196. box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
  1197. cursor: pointer;
  1198. background-color: rgba(0, 0, 0, 0.14);
  1199. }
  1200. .vue-image-crop-upload
  1201. .vicp-wrap
  1202. .vicp-step2
  1203. .vicp-crop
  1204. .vicp-crop-left
  1205. .vicp-range
  1206. .vicp-icon5 {
  1207. left: 0;
  1208. }
  1209. .vue-image-crop-upload
  1210. .vicp-wrap
  1211. .vicp-step2
  1212. .vicp-crop
  1213. .vicp-crop-left
  1214. .vicp-range
  1215. .vicp-icon5::before {
  1216. position: absolute;
  1217. content: "";
  1218. display: block;
  1219. left: 3px;
  1220. top: 8px;
  1221. width: 12px;
  1222. height: 2px;
  1223. background-color: #fff;
  1224. }
  1225. .vue-image-crop-upload
  1226. .vicp-wrap
  1227. .vicp-step2
  1228. .vicp-crop
  1229. .vicp-crop-left
  1230. .vicp-range
  1231. .vicp-icon6 {
  1232. right: 0;
  1233. }
  1234. .vue-image-crop-upload
  1235. .vicp-wrap
  1236. .vicp-step2
  1237. .vicp-crop
  1238. .vicp-crop-left
  1239. .vicp-range
  1240. .vicp-icon6::before {
  1241. position: absolute;
  1242. content: "";
  1243. display: block;
  1244. left: 3px;
  1245. top: 8px;
  1246. width: 12px;
  1247. height: 2px;
  1248. background-color: #fff;
  1249. }
  1250. .vue-image-crop-upload
  1251. .vicp-wrap
  1252. .vicp-step2
  1253. .vicp-crop
  1254. .vicp-crop-left
  1255. .vicp-range
  1256. .vicp-icon6::after {
  1257. position: absolute;
  1258. content: "";
  1259. display: block;
  1260. top: 3px;
  1261. left: 8px;
  1262. width: 2px;
  1263. height: 12px;
  1264. background-color: #fff;
  1265. }
  1266. .vue-image-crop-upload
  1267. .vicp-wrap
  1268. .vicp-step2
  1269. .vicp-crop
  1270. .vicp-crop-left
  1271. .vicp-range
  1272. input[type="range"] {
  1273. display: block;
  1274. padding-top: 5px;
  1275. margin: 0 auto;
  1276. width: 180px;
  1277. height: 8px;
  1278. vertical-align: top;
  1279. background: transparent;
  1280. -webkit-appearance: none;
  1281. -moz-appearance: none;
  1282. appearance: none;
  1283. cursor: pointer;
  1284. /* 滑块
  1285. ---------------------------------------------------------------*/
  1286. /* 轨道
  1287. ---------------------------------------------------------------*/
  1288. }
  1289. .vue-image-crop-upload
  1290. .vicp-wrap
  1291. .vicp-step2
  1292. .vicp-crop
  1293. .vicp-crop-left
  1294. .vicp-range
  1295. input[type="range"]:focus {
  1296. outline: none;
  1297. }
  1298. .vue-image-crop-upload
  1299. .vicp-wrap
  1300. .vicp-step2
  1301. .vicp-crop
  1302. .vicp-crop-left
  1303. .vicp-range
  1304. input[type="range"]::-webkit-slider-thumb {
  1305. -webkit-box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
  1306. box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
  1307. -webkit-appearance: none;
  1308. appearance: none;
  1309. margin-top: -3px;
  1310. width: 12px;
  1311. height: 12px;
  1312. background-color: #61c091;
  1313. border-radius: 100%;
  1314. border: none;
  1315. -webkit-transition: 0.2s;
  1316. transition: 0.2s;
  1317. }
  1318. .vue-image-crop-upload
  1319. .vicp-wrap
  1320. .vicp-step2
  1321. .vicp-crop
  1322. .vicp-crop-left
  1323. .vicp-range
  1324. input[type="range"]::-moz-range-thumb {
  1325. box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
  1326. -moz-appearance: none;
  1327. appearance: none;
  1328. width: 12px;
  1329. height: 12px;
  1330. background-color: #61c091;
  1331. border-radius: 100%;
  1332. border: none;
  1333. -webkit-transition: 0.2s;
  1334. transition: 0.2s;
  1335. }
  1336. .vue-image-crop-upload
  1337. .vicp-wrap
  1338. .vicp-step2
  1339. .vicp-crop
  1340. .vicp-crop-left
  1341. .vicp-range
  1342. input[type="range"]::-ms-thumb {
  1343. box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
  1344. appearance: none;
  1345. width: 12px;
  1346. height: 12px;
  1347. background-color: #61c091;
  1348. border: none;
  1349. border-radius: 100%;
  1350. -webkit-transition: 0.2s;
  1351. transition: 0.2s;
  1352. }
  1353. .vue-image-crop-upload
  1354. .vicp-wrap
  1355. .vicp-step2
  1356. .vicp-crop
  1357. .vicp-crop-left
  1358. .vicp-range
  1359. input[type="range"]:active::-moz-range-thumb {
  1360. box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
  1361. width: 14px;
  1362. height: 14px;
  1363. }
  1364. .vue-image-crop-upload
  1365. .vicp-wrap
  1366. .vicp-step2
  1367. .vicp-crop
  1368. .vicp-crop-left
  1369. .vicp-range
  1370. input[type="range"]:active::-ms-thumb {
  1371. box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
  1372. width: 14px;
  1373. height: 14px;
  1374. }
  1375. .vue-image-crop-upload
  1376. .vicp-wrap
  1377. .vicp-step2
  1378. .vicp-crop
  1379. .vicp-crop-left
  1380. .vicp-range
  1381. input[type="range"]:active::-webkit-slider-thumb {
  1382. -webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
  1383. box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
  1384. margin-top: -4px;
  1385. width: 14px;
  1386. height: 14px;
  1387. }
  1388. .vue-image-crop-upload
  1389. .vicp-wrap
  1390. .vicp-step2
  1391. .vicp-crop
  1392. .vicp-crop-left
  1393. .vicp-range
  1394. input[type="range"]::-webkit-slider-runnable-track {
  1395. -webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
  1396. box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
  1397. width: 100%;
  1398. height: 6px;
  1399. cursor: pointer;
  1400. border-radius: 2px;
  1401. border: none;
  1402. background-color: rgba(68, 170, 119, 0.3);
  1403. }
  1404. .vue-image-crop-upload
  1405. .vicp-wrap
  1406. .vicp-step2
  1407. .vicp-crop
  1408. .vicp-crop-left
  1409. .vicp-range
  1410. input[type="range"]::-moz-range-track {
  1411. box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
  1412. width: 100%;
  1413. height: 6px;
  1414. cursor: pointer;
  1415. border-radius: 2px;
  1416. border: none;
  1417. background-color: rgba(68, 170, 119, 0.3);
  1418. }
  1419. .vue-image-crop-upload
  1420. .vicp-wrap
  1421. .vicp-step2
  1422. .vicp-crop
  1423. .vicp-crop-left
  1424. .vicp-range
  1425. input[type="range"]::-ms-track {
  1426. box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
  1427. width: 100%;
  1428. cursor: pointer;
  1429. background: transparent;
  1430. border-color: transparent;
  1431. color: transparent;
  1432. height: 6px;
  1433. border-radius: 2px;
  1434. border: none;
  1435. }
  1436. .vue-image-crop-upload
  1437. .vicp-wrap
  1438. .vicp-step2
  1439. .vicp-crop
  1440. .vicp-crop-left
  1441. .vicp-range
  1442. input[type="range"]::-ms-fill-lower {
  1443. background-color: rgba(68, 170, 119, 0.3);
  1444. }
  1445. .vue-image-crop-upload
  1446. .vicp-wrap
  1447. .vicp-step2
  1448. .vicp-crop
  1449. .vicp-crop-left
  1450. .vicp-range
  1451. input[type="range"]::-ms-fill-upper {
  1452. background-color: rgba(68, 170, 119, 0.15);
  1453. }
  1454. .vue-image-crop-upload
  1455. .vicp-wrap
  1456. .vicp-step2
  1457. .vicp-crop
  1458. .vicp-crop-left
  1459. .vicp-range
  1460. input[type="range"]:focus::-webkit-slider-runnable-track {
  1461. background-color: rgba(68, 170, 119, 0.5);
  1462. }
  1463. .vue-image-crop-upload
  1464. .vicp-wrap
  1465. .vicp-step2
  1466. .vicp-crop
  1467. .vicp-crop-left
  1468. .vicp-range
  1469. input[type="range"]:focus::-moz-range-track {
  1470. background-color: rgba(68, 170, 119, 0.5);
  1471. }
  1472. .vue-image-crop-upload
  1473. .vicp-wrap
  1474. .vicp-step2
  1475. .vicp-crop
  1476. .vicp-crop-left
  1477. .vicp-range
  1478. input[type="range"]:focus::-ms-fill-lower {
  1479. background-color: rgba(68, 170, 119, 0.45);
  1480. }
  1481. .vue-image-crop-upload
  1482. .vicp-wrap
  1483. .vicp-step2
  1484. .vicp-crop
  1485. .vicp-crop-left
  1486. .vicp-range
  1487. input[type="range"]:focus::-ms-fill-upper {
  1488. background-color: rgba(68, 170, 119, 0.25);
  1489. }
  1490. .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-right {
  1491. float: right;
  1492. }
  1493. .vue-image-crop-upload
  1494. .vicp-wrap
  1495. .vicp-step2
  1496. .vicp-crop
  1497. .vicp-crop-right
  1498. .vicp-preview {
  1499. height: 150px;
  1500. overflow: hidden;
  1501. }
  1502. .vue-image-crop-upload
  1503. .vicp-wrap
  1504. .vicp-step2
  1505. .vicp-crop
  1506. .vicp-crop-right
  1507. .vicp-preview
  1508. .vicp-preview-item {
  1509. position: relative;
  1510. padding: 5px;
  1511. width: 100px;
  1512. height: 100px;
  1513. float: left;
  1514. margin-right: 16px;
  1515. }
  1516. .vue-image-crop-upload
  1517. .vicp-wrap
  1518. .vicp-step2
  1519. .vicp-crop
  1520. .vicp-crop-right
  1521. .vicp-preview
  1522. .vicp-preview-item
  1523. span {
  1524. position: absolute;
  1525. bottom: -30px;
  1526. width: 100%;
  1527. font-size: 14px;
  1528. color: #bbb;
  1529. display: block;
  1530. text-align: center;
  1531. }
  1532. .vue-image-crop-upload
  1533. .vicp-wrap
  1534. .vicp-step2
  1535. .vicp-crop
  1536. .vicp-crop-right
  1537. .vicp-preview
  1538. .vicp-preview-item
  1539. img {
  1540. position: absolute;
  1541. display: block;
  1542. top: 0;
  1543. bottom: 0;
  1544. left: 0;
  1545. right: 0;
  1546. margin: auto;
  1547. padding: 3px;
  1548. background-color: #fff;
  1549. border: 1px solid rgba(0, 0, 0, 0.15);
  1550. overflow: hidden;
  1551. -webkit-user-select: none;
  1552. -moz-user-select: none;
  1553. -ms-user-select: none;
  1554. user-select: none;
  1555. }
  1556. .vue-image-crop-upload
  1557. .vicp-wrap
  1558. .vicp-step2
  1559. .vicp-crop
  1560. .vicp-crop-right
  1561. .vicp-preview
  1562. .vicp-preview-item.vicp-preview-item-circle {
  1563. margin-right: 0;
  1564. }
  1565. .vue-image-crop-upload
  1566. .vicp-wrap
  1567. .vicp-step2
  1568. .vicp-crop
  1569. .vicp-crop-right
  1570. .vicp-preview
  1571. .vicp-preview-item.vicp-preview-item-circle
  1572. img {
  1573. border-radius: 100%;
  1574. }
  1575. .vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload {
  1576. position: relative;
  1577. -webkit-box-sizing: border-box;
  1578. box-sizing: border-box;
  1579. padding: 35px;
  1580. height: 170px;
  1581. background-color: rgba(0, 0, 0, 0.03);
  1582. text-align: center;
  1583. border: 1px dashed #ddd;
  1584. }
  1585. .vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-loading {
  1586. display: block;
  1587. padding: 15px;
  1588. font-size: 16px;
  1589. color: #999;
  1590. line-height: 30px;
  1591. }
  1592. .vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-progress-wrap {
  1593. margin-top: 12px;
  1594. background-color: rgba(0, 0, 0, 0.08);
  1595. border-radius: 3px;
  1596. }
  1597. .vue-image-crop-upload
  1598. .vicp-wrap
  1599. .vicp-step3
  1600. .vicp-upload
  1601. .vicp-progress-wrap
  1602. .vicp-progress {
  1603. position: relative;
  1604. display: block;
  1605. height: 5px;
  1606. border-radius: 3px;
  1607. background-color: #4a7;
  1608. -webkit-box-shadow: 0 2px 6px 0 rgba(68, 170, 119, 0.3);
  1609. box-shadow: 0 2px 6px 0 rgba(68, 170, 119, 0.3);
  1610. -webkit-transition: width 0.15s linear;
  1611. transition: width 0.15s linear;
  1612. background-image: -webkit-linear-gradient(
  1613. 135deg,
  1614. rgba(255, 255, 255, 0.2) 25%,
  1615. transparent 25%,
  1616. transparent 50%,
  1617. rgba(255, 255, 255, 0.2) 50%,
  1618. rgba(255, 255, 255, 0.2) 75%,
  1619. transparent 75%,
  1620. transparent
  1621. );
  1622. background-image: linear-gradient(
  1623. -45deg,
  1624. rgba(255, 255, 255, 0.2) 25%,
  1625. transparent 25%,
  1626. transparent 50%,
  1627. rgba(255, 255, 255, 0.2) 50%,
  1628. rgba(255, 255, 255, 0.2) 75%,
  1629. transparent 75%,
  1630. transparent
  1631. );
  1632. background-size: 40px 40px;
  1633. -webkit-animation: vicp_progress 0.5s linear infinite;
  1634. animation: vicp_progress 0.5s linear infinite;
  1635. }
  1636. .vue-image-crop-upload
  1637. .vicp-wrap
  1638. .vicp-step3
  1639. .vicp-upload
  1640. .vicp-progress-wrap
  1641. .vicp-progress::after {
  1642. content: "";
  1643. position: absolute;
  1644. display: block;
  1645. top: -3px;
  1646. right: -3px;
  1647. width: 9px;
  1648. height: 9px;
  1649. border: 1px solid rgba(245, 246, 247, 0.7);
  1650. -webkit-box-shadow: 0 1px 4px 0 rgba(68, 170, 119, 0.7);
  1651. box-shadow: 0 1px 4px 0 rgba(68, 170, 119, 0.7);
  1652. border-radius: 100%;
  1653. background-color: #4a7;
  1654. }
  1655. .vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-error,
  1656. .vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-success {
  1657. height: 100px;
  1658. line-height: 100px;
  1659. }
  1660. .vue-image-crop-upload .vicp-wrap .vicp-operate {
  1661. position: absolute;
  1662. right: 20px;
  1663. bottom: 20px;
  1664. }
  1665. .vue-image-crop-upload .vicp-wrap .vicp-operate a {
  1666. position: relative;
  1667. float: left;
  1668. display: block;
  1669. margin-left: 10px;
  1670. width: 100px;
  1671. height: 36px;
  1672. line-height: 36px;
  1673. text-align: center;
  1674. cursor: pointer;
  1675. font-size: 14px;
  1676. color: #4a7;
  1677. border-radius: 2px;
  1678. overflow: hidden;
  1679. -webkit-user-select: none;
  1680. -moz-user-select: none;
  1681. -ms-user-select: none;
  1682. user-select: none;
  1683. }
  1684. .vue-image-crop-upload .vicp-wrap .vicp-operate a:hover {
  1685. background-color: rgba(0, 0, 0, 0.03);
  1686. }
  1687. .vue-image-crop-upload .vicp-wrap .vicp-error,
  1688. .vue-image-crop-upload .vicp-wrap .vicp-success {
  1689. display: block;
  1690. font-size: 14px;
  1691. line-height: 24px;
  1692. height: 24px;
  1693. color: #d10;
  1694. text-align: center;
  1695. vertical-align: top;
  1696. }
  1697. .vue-image-crop-upload .vicp-wrap .vicp-success {
  1698. color: #4a7;
  1699. }
  1700. .vue-image-crop-upload .vicp-wrap .vicp-icon3 {
  1701. position: relative;
  1702. display: inline-block;
  1703. width: 20px;
  1704. height: 20px;
  1705. top: 4px;
  1706. }
  1707. .vue-image-crop-upload .vicp-wrap .vicp-icon3::after {
  1708. position: absolute;
  1709. top: 3px;
  1710. left: 6px;
  1711. width: 6px;
  1712. height: 10px;
  1713. border-width: 0 2px 2px 0;
  1714. border-color: #4a7;
  1715. border-style: solid;
  1716. -webkit-transform: rotate(45deg);
  1717. -ms-transform: rotate(45deg);
  1718. transform: rotate(45deg);
  1719. content: "";
  1720. }
  1721. .vue-image-crop-upload .vicp-wrap .vicp-icon2 {
  1722. position: relative;
  1723. display: inline-block;
  1724. width: 20px;
  1725. height: 20px;
  1726. top: 4px;
  1727. }
  1728. .vue-image-crop-upload .vicp-wrap .vicp-icon2::after,
  1729. .vue-image-crop-upload .vicp-wrap .vicp-icon2::before {
  1730. content: "";
  1731. position: absolute;
  1732. top: 9px;
  1733. left: 4px;
  1734. width: 13px;
  1735. height: 2px;
  1736. background-color: #d10;
  1737. -webkit-transform: rotate(45deg);
  1738. -ms-transform: rotate(45deg);
  1739. transform: rotate(45deg);
  1740. }
  1741. .vue-image-crop-upload .vicp-wrap .vicp-icon2::after {
  1742. -webkit-transform: rotate(-45deg);
  1743. -ms-transform: rotate(-45deg);
  1744. transform: rotate(-45deg);
  1745. }
  1746. .e-ripple {
  1747. position: absolute;
  1748. border-radius: 100%;
  1749. background-color: rgba(0, 0, 0, 0.15);
  1750. background-clip: padding-box;
  1751. pointer-events: none;
  1752. -webkit-user-select: none;
  1753. -moz-user-select: none;
  1754. -ms-user-select: none;
  1755. user-select: none;
  1756. -webkit-transform: scale(0);
  1757. -ms-transform: scale(0);
  1758. transform: scale(0);
  1759. opacity: 1;
  1760. }
  1761. .e-ripple.z-active {
  1762. opacity: 0;
  1763. -webkit-transform: scale(2);
  1764. -ms-transform: scale(2);
  1765. transform: scale(2);
  1766. -webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
  1767. transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
  1768. transition: opacity 1.2s ease-out, transform 0.6s ease-out;
  1769. transition: opacity 1.2s ease-out, transform 0.6s ease-out,
  1770. -webkit-transform 0.6s ease-out;
  1771. }
  1772. </style>