Commit 27cb958c2ef4ef74bb204a46b49af9fbd9b7eeff

Authored by Li Kui
Committed by GitHub
1 parent 155ad458

fix: Cropper typo (#2891)

src/components/Cropper/src/CropperAvatar.vue
... ... @@ -20,7 +20,7 @@
20 20 {{ btnText ? btnText : t('component.cropper.selectImage') }}
21 21 </a-button>
22 22  
23   - <CopperModal
  23 + <CropperModal
24 24 @register="register"
25 25 @upload-success="handleUploadSuccess"
26 26 :uploadApi="uploadApi"
... ... @@ -39,7 +39,7 @@
39 39 watch,
40 40 PropType,
41 41 } from 'vue';
42   - import CopperModal from './CopperModal.vue';
  42 + import CropperModal from './CropperModal.vue';
43 43 import { useDesign } from '/@/hooks/web/useDesign';
44 44 import { useModal } from '/@/components/Modal';
45 45 import { useMessage } from '/@/hooks/web/useMessage';
... ... @@ -58,7 +58,7 @@
58 58  
59 59 export default defineComponent({
60 60 name: 'CropperAvatar',
61   - components: { CopperModal, Icon },
  61 + components: { CropperModal, Icon },
62 62 props,
63 63 emits: ['update:value', 'change'],
64 64 setup(props, { emit, expose }) {
... ...
src/components/Cropper/src/CopperModal.vue renamed to src/components/Cropper/src/CropperModal.vue
1   -<template>
2   - <BasicModal
3   - v-bind="$attrs"
4   - @register="register"
5   - :title="t('component.cropper.modalTitle')"
6   - width="800px"
7   - :canFullscreen="false"
8   - @ok="handleOk"
9   - :okText="t('component.cropper.okText')"
10   - >
11   - <div :class="prefixCls">
12   - <div :class="`${prefixCls}-left`">
13   - <div :class="`${prefixCls}-cropper`">
14   - <CropperImage
15   - v-if="src"
16   - :src="src"
17   - height="300px"
18   - :circled="circled"
19   - @cropend="handleCropend"
20   - @ready="handleReady"
21   - />
22   - </div>
23   -
24   - <div :class="`${prefixCls}-toolbar`">
25   - <Upload :fileList="[]" accept="image/*" :beforeUpload="handleBeforeUpload">
26   - <Tooltip :title="t('component.cropper.selectImage')" placement="bottom">
27   - <a-button size="small" preIcon="ant-design:upload-outlined" type="primary" />
28   - </Tooltip>
29   - </Upload>
30   - <Space>
31   - <Tooltip :title="t('component.cropper.btn_reset')" placement="bottom">
32   - <a-button
33   - type="primary"
34   - preIcon="ant-design:reload-outlined"
35   - size="small"
36   - :disabled="!src"
37   - @click="handlerToolbar('reset')"
38   - />
39   - </Tooltip>
40   - <Tooltip :title="t('component.cropper.btn_rotate_left')" placement="bottom">
41   - <a-button
42   - type="primary"
43   - preIcon="ant-design:rotate-left-outlined"
44   - size="small"
45   - :disabled="!src"
46   - @click="handlerToolbar('rotate', -45)"
47   - />
48   - </Tooltip>
49   - <Tooltip :title="t('component.cropper.btn_rotate_right')" placement="bottom">
50   - <a-button
51   - type="primary"
52   - preIcon="ant-design:rotate-right-outlined"
53   - size="small"
54   - :disabled="!src"
55   - @click="handlerToolbar('rotate', 45)"
56   - />
57   - </Tooltip>
58   - <Tooltip :title="t('component.cropper.btn_scale_x')" placement="bottom">
59   - <a-button
60   - type="primary"
61   - preIcon="vaadin:arrows-long-h"
62   - size="small"
63   - :disabled="!src"
64   - @click="handlerToolbar('scaleX')"
65   - />
66   - </Tooltip>
67   - <Tooltip :title="t('component.cropper.btn_scale_y')" placement="bottom">
68   - <a-button
69   - type="primary"
70   - preIcon="vaadin:arrows-long-v"
71   - size="small"
72   - :disabled="!src"
73   - @click="handlerToolbar('scaleY')"
74   - />
75   - </Tooltip>
76   - <Tooltip :title="t('component.cropper.btn_zoom_in')" placement="bottom">
77   - <a-button
78   - type="primary"
79   - preIcon="ant-design:zoom-in-outlined"
80   - size="small"
81   - :disabled="!src"
82   - @click="handlerToolbar('zoom', 0.1)"
83   - />
84   - </Tooltip>
85   - <Tooltip :title="t('component.cropper.btn_zoom_out')" placement="bottom">
86   - <a-button
87   - type="primary"
88   - preIcon="ant-design:zoom-out-outlined"
89   - size="small"
90   - :disabled="!src"
91   - @click="handlerToolbar('zoom', -0.1)"
92   - />
93   - </Tooltip>
94   - </Space>
95   - </div>
96   - </div>
97   - <div :class="`${prefixCls}-right`">
98   - <div :class="`${prefixCls}-preview`">
99   - <img :src="previewSource" v-if="previewSource" :alt="t('component.cropper.preview')" />
100   - </div>
101   - <template v-if="previewSource">
102   - <div :class="`${prefixCls}-group`">
103   - <Avatar :src="previewSource" size="large" />
104   - <Avatar :src="previewSource" :size="48" />
105   - <Avatar :src="previewSource" :size="64" />
106   - <Avatar :src="previewSource" :size="80" />
107   - </div>
108   - </template>
109   - </div>
110   - </div>
111   - </BasicModal>
112   -</template>
113   -<script lang="ts">
114   - import type { CropendResult, Cropper } from './typing';
115   -
116   - import { defineComponent, ref, PropType } from 'vue';
117   - import CropperImage from './Cropper.vue';
118   - import { Space, Upload, Avatar, Tooltip } from 'ant-design-vue';
119   - import { useDesign } from '/@/hooks/web/useDesign';
120   - import { BasicModal, useModalInner } from '/@/components/Modal';
121   - import { dataURLtoBlob } from '/@/utils/file/base64Conver';
122   - import { isFunction } from '/@/utils/is';
123   - import { useI18n } from '/@/hooks/web/useI18n';
124   -
125   - type apiFunParams = { file: Blob; name: string; filename: string };
126   -
127   - const props = {
128   - circled: { type: Boolean, default: true },
129   - uploadApi: {
130   - type: Function as PropType<(params: apiFunParams) => Promise<any>>,
131   - },
132   - src: { type: String },
133   - };
134   -
135   - export default defineComponent({
136   - name: 'CropperModal',
137   - components: { BasicModal, Space, CropperImage, Upload, Avatar, Tooltip },
138   - props,
139   - emits: ['uploadSuccess', 'register'],
140   - setup(props, { emit }) {
141   - let filename = '';
142   - const src = ref(props.src || '');
143   - const previewSource = ref('');
144   - const cropper = ref<Cropper>();
145   - let scaleX = 1;
146   - let scaleY = 1;
147   -
148   - const { prefixCls } = useDesign('cropper-am');
149   - const [register, { closeModal, setModalProps }] = useModalInner();
150   - const { t } = useI18n();
151   -
152   - // Block upload
153   - function handleBeforeUpload(file: File) {
154   - const reader = new FileReader();
155   - reader.readAsDataURL(file);
156   - src.value = '';
157   - previewSource.value = '';
158   - reader.onload = function (e) {
159   - src.value = (e.target?.result as string) ?? '';
160   - filename = file.name;
161   - };
162   - return false;
163   - }
164   -
165   - function handleCropend({ imgBase64 }: CropendResult) {
166   - previewSource.value = imgBase64;
167   - }
168   -
169   - function handleReady(cropperInstance: Cropper) {
170   - cropper.value = cropperInstance;
171   - }
172   -
173   - function handlerToolbar(event: string, arg?: number) {
174   - if (event === 'scaleX') {
175   - scaleX = arg = scaleX === -1 ? 1 : -1;
176   - }
177   - if (event === 'scaleY') {
178   - scaleY = arg = scaleY === -1 ? 1 : -1;
179   - }
180   - cropper?.value?.[event]?.(arg);
181   - }
182   -
183   - async function handleOk() {
184   - const uploadApi = props.uploadApi;
185   - if (uploadApi && isFunction(uploadApi)) {
186   - const blob = dataURLtoBlob(previewSource.value);
187   - try {
188   - setModalProps({ confirmLoading: true });
189   - const result = await uploadApi({ name: 'file', file: blob, filename });
190   - emit('uploadSuccess', { source: previewSource.value, data: result.url });
191   - closeModal();
192   - } finally {
193   - setModalProps({ confirmLoading: false });
194   - }
195   - }
196   - }
197   -
198   - return {
199   - t,
200   - prefixCls,
201   - src,
202   - register,
203   - previewSource,
204   - handleBeforeUpload,
205   - handleCropend,
206   - handleReady,
207   - handlerToolbar,
208   - handleOk,
209   - };
210   - },
211   - });
212   -</script>
213   -
214   -<style lang="less">
215   - @prefix-cls: ~'@{namespace}-cropper-am';
216   -
217   - .@{prefix-cls} {
218   - display: flex;
219   -
220   - &-left,
221   - &-right {
222   - height: 340px;
223   - }
224   -
225   - &-left {
226   - width: 55%;
227   - }
228   -
229   - &-right {
230   - width: 45%;
231   - }
232   -
233   - &-cropper {
234   - height: 300px;
235   - background: #eee;
236   - background-image: linear-gradient(
237   - 45deg,
238   - rgb(0 0 0 / 25%) 25%,
239   - transparent 0,
240   - transparent 75%,
241   - rgb(0 0 0 / 25%) 0
242   - ),
243   - linear-gradient(
244   - 45deg,
245   - rgb(0 0 0 / 25%) 25%,
246   - transparent 0,
247   - transparent 75%,
248   - rgb(0 0 0 / 25%) 0
249   - );
250   - background-position: 0 0, 12px 12px;
251   - background-size: 24px 24px;
252   - }
253   -
254   - &-toolbar {
255   - display: flex;
256   - align-items: center;
257   - justify-content: space-between;
258   - margin-top: 10px;
259   - }
260   -
261   - &-preview {
262   - width: 220px;
263   - height: 220px;
264   - margin: 0 auto;
265   - overflow: hidden;
266   - border: 1px solid @border-color-base;
267   - border-radius: 50%;
268   -
269   - img {
270   - width: 100%;
271   - height: 100%;
272   - }
273   - }
274   -
275   - &-group {
276   - display: flex;
277   - align-items: center;
278   - justify-content: space-around;
279   - margin-top: 8px;
280   - padding-top: 8px;
281   - border-top: 1px solid @border-color-base;
282   - }
283   - }
284   -</style>
  1 +<template>
  2 + <BasicModal
  3 + v-bind="$attrs"
  4 + @register="register"
  5 + :title="t('component.cropper.modalTitle')"
  6 + width="800px"
  7 + :canFullscreen="false"
  8 + @ok="handleOk"
  9 + :okText="t('component.cropper.okText')"
  10 + >
  11 + <div :class="prefixCls">
  12 + <div :class="`${prefixCls}-left`">
  13 + <div :class="`${prefixCls}-cropper`">
  14 + <CropperImage
  15 + v-if="src"
  16 + :src="src"
  17 + height="300px"
  18 + :circled="circled"
  19 + @cropend="handleCropend"
  20 + @ready="handleReady"
  21 + />
  22 + </div>
  23 +
  24 + <div :class="`${prefixCls}-toolbar`">
  25 + <Upload :fileList="[]" accept="image/*" :beforeUpload="handleBeforeUpload">
  26 + <Tooltip :title="t('component.cropper.selectImage')" placement="bottom">
  27 + <a-button size="small" preIcon="ant-design:upload-outlined" type="primary" />
  28 + </Tooltip>
  29 + </Upload>
  30 + <Space>
  31 + <Tooltip :title="t('component.cropper.btn_reset')" placement="bottom">
  32 + <a-button
  33 + type="primary"
  34 + preIcon="ant-design:reload-outlined"
  35 + size="small"
  36 + :disabled="!src"
  37 + @click="handlerToolbar('reset')"
  38 + />
  39 + </Tooltip>
  40 + <Tooltip :title="t('component.cropper.btn_rotate_left')" placement="bottom">
  41 + <a-button
  42 + type="primary"
  43 + preIcon="ant-design:rotate-left-outlined"
  44 + size="small"
  45 + :disabled="!src"
  46 + @click="handlerToolbar('rotate', -45)"
  47 + />
  48 + </Tooltip>
  49 + <Tooltip :title="t('component.cropper.btn_rotate_right')" placement="bottom">
  50 + <a-button
  51 + type="primary"
  52 + preIcon="ant-design:rotate-right-outlined"
  53 + size="small"
  54 + :disabled="!src"
  55 + @click="handlerToolbar('rotate', 45)"
  56 + />
  57 + </Tooltip>
  58 + <Tooltip :title="t('component.cropper.btn_scale_x')" placement="bottom">
  59 + <a-button
  60 + type="primary"
  61 + preIcon="vaadin:arrows-long-h"
  62 + size="small"
  63 + :disabled="!src"
  64 + @click="handlerToolbar('scaleX')"
  65 + />
  66 + </Tooltip>
  67 + <Tooltip :title="t('component.cropper.btn_scale_y')" placement="bottom">
  68 + <a-button
  69 + type="primary"
  70 + preIcon="vaadin:arrows-long-v"
  71 + size="small"
  72 + :disabled="!src"
  73 + @click="handlerToolbar('scaleY')"
  74 + />
  75 + </Tooltip>
  76 + <Tooltip :title="t('component.cropper.btn_zoom_in')" placement="bottom">
  77 + <a-button
  78 + type="primary"
  79 + preIcon="ant-design:zoom-in-outlined"
  80 + size="small"
  81 + :disabled="!src"
  82 + @click="handlerToolbar('zoom', 0.1)"
  83 + />
  84 + </Tooltip>
  85 + <Tooltip :title="t('component.cropper.btn_zoom_out')" placement="bottom">
  86 + <a-button
  87 + type="primary"
  88 + preIcon="ant-design:zoom-out-outlined"
  89 + size="small"
  90 + :disabled="!src"
  91 + @click="handlerToolbar('zoom', -0.1)"
  92 + />
  93 + </Tooltip>
  94 + </Space>
  95 + </div>
  96 + </div>
  97 + <div :class="`${prefixCls}-right`">
  98 + <div :class="`${prefixCls}-preview`">
  99 + <img :src="previewSource" v-if="previewSource" :alt="t('component.cropper.preview')" />
  100 + </div>
  101 + <template v-if="previewSource">
  102 + <div :class="`${prefixCls}-group`">
  103 + <Avatar :src="previewSource" size="large" />
  104 + <Avatar :src="previewSource" :size="48" />
  105 + <Avatar :src="previewSource" :size="64" />
  106 + <Avatar :src="previewSource" :size="80" />
  107 + </div>
  108 + </template>
  109 + </div>
  110 + </div>
  111 + </BasicModal>
  112 +</template>
  113 +<script lang="ts">
  114 + import type { CropendResult, Cropper } from './typing';
  115 +
  116 + import { defineComponent, ref, PropType } from 'vue';
  117 + import CropperImage from './Cropper.vue';
  118 + import { Space, Upload, Avatar, Tooltip } from 'ant-design-vue';
  119 + import { useDesign } from '/@/hooks/web/useDesign';
  120 + import { BasicModal, useModalInner } from '/@/components/Modal';
  121 + import { dataURLtoBlob } from '/@/utils/file/base64Conver';
  122 + import { isFunction } from '/@/utils/is';
  123 + import { useI18n } from '/@/hooks/web/useI18n';
  124 +
  125 + type apiFunParams = { file: Blob; name: string; filename: string };
  126 +
  127 + const props = {
  128 + circled: { type: Boolean, default: true },
  129 + uploadApi: {
  130 + type: Function as PropType<(params: apiFunParams) => Promise<any>>,
  131 + },
  132 + src: { type: String },
  133 + };
  134 +
  135 + export default defineComponent({
  136 + name: 'CropperModal',
  137 + components: { BasicModal, Space, CropperImage, Upload, Avatar, Tooltip },
  138 + props,
  139 + emits: ['uploadSuccess', 'register'],
  140 + setup(props, { emit }) {
  141 + let filename = '';
  142 + const src = ref(props.src || '');
  143 + const previewSource = ref('');
  144 + const cropper = ref<Cropper>();
  145 + let scaleX = 1;
  146 + let scaleY = 1;
  147 +
  148 + const { prefixCls } = useDesign('cropper-am');
  149 + const [register, { closeModal, setModalProps }] = useModalInner();
  150 + const { t } = useI18n();
  151 +
  152 + // Block upload
  153 + function handleBeforeUpload(file: File) {
  154 + const reader = new FileReader();
  155 + reader.readAsDataURL(file);
  156 + src.value = '';
  157 + previewSource.value = '';
  158 + reader.onload = function (e) {
  159 + src.value = (e.target?.result as string) ?? '';
  160 + filename = file.name;
  161 + };
  162 + return false;
  163 + }
  164 +
  165 + function handleCropend({ imgBase64 }: CropendResult) {
  166 + previewSource.value = imgBase64;
  167 + }
  168 +
  169 + function handleReady(cropperInstance: Cropper) {
  170 + cropper.value = cropperInstance;
  171 + }
  172 +
  173 + function handlerToolbar(event: string, arg?: number) {
  174 + if (event === 'scaleX') {
  175 + scaleX = arg = scaleX === -1 ? 1 : -1;
  176 + }
  177 + if (event === 'scaleY') {
  178 + scaleY = arg = scaleY === -1 ? 1 : -1;
  179 + }
  180 + cropper?.value?.[event]?.(arg);
  181 + }
  182 +
  183 + async function handleOk() {
  184 + const uploadApi = props.uploadApi;
  185 + if (uploadApi && isFunction(uploadApi)) {
  186 + const blob = dataURLtoBlob(previewSource.value);
  187 + try {
  188 + setModalProps({ confirmLoading: true });
  189 + const result = await uploadApi({ name: 'file', file: blob, filename });
  190 + emit('uploadSuccess', { source: previewSource.value, data: result.url });
  191 + closeModal();
  192 + } finally {
  193 + setModalProps({ confirmLoading: false });
  194 + }
  195 + }
  196 + }
  197 +
  198 + return {
  199 + t,
  200 + prefixCls,
  201 + src,
  202 + register,
  203 + previewSource,
  204 + handleBeforeUpload,
  205 + handleCropend,
  206 + handleReady,
  207 + handlerToolbar,
  208 + handleOk,
  209 + };
  210 + },
  211 + });
  212 +</script>
  213 +
  214 +<style lang="less">
  215 + @prefix-cls: ~'@{namespace}-cropper-am';
  216 +
  217 + .@{prefix-cls} {
  218 + display: flex;
  219 +
  220 + &-left,
  221 + &-right {
  222 + height: 340px;
  223 + }
  224 +
  225 + &-left {
  226 + width: 55%;
  227 + }
  228 +
  229 + &-right {
  230 + width: 45%;
  231 + }
  232 +
  233 + &-cropper {
  234 + height: 300px;
  235 + background: #eee;
  236 + background-image: linear-gradient(
  237 + 45deg,
  238 + rgb(0 0 0 / 25%) 25%,
  239 + transparent 0,
  240 + transparent 75%,
  241 + rgb(0 0 0 / 25%) 0
  242 + ),
  243 + linear-gradient(
  244 + 45deg,
  245 + rgb(0 0 0 / 25%) 25%,
  246 + transparent 0,
  247 + transparent 75%,
  248 + rgb(0 0 0 / 25%) 0
  249 + );
  250 + background-position: 0 0, 12px 12px;
  251 + background-size: 24px 24px;
  252 + }
  253 +
  254 + &-toolbar {
  255 + display: flex;
  256 + align-items: center;
  257 + justify-content: space-between;
  258 + margin-top: 10px;
  259 + }
  260 +
  261 + &-preview {
  262 + width: 220px;
  263 + height: 220px;
  264 + margin: 0 auto;
  265 + overflow: hidden;
  266 + border: 1px solid @border-color-base;
  267 + border-radius: 50%;
  268 +
  269 + img {
  270 + width: 100%;
  271 + height: 100%;
  272 + }
  273 + }
  274 +
  275 + &-group {
  276 + display: flex;
  277 + align-items: center;
  278 + justify-content: space-around;
  279 + margin-top: 8px;
  280 + padding-top: 8px;
  281 + border-top: 1px solid @border-color-base;
  282 + }
  283 + }
  284 +</style>
... ...