Browse Source

Merge pull request #709 from colinin/fix-cropper

fixed cropper
pull/651/head
yx lin 3 years ago
committed by GitHub
parent
commit
0d6df63427
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 54
      apps/vue/src/components/Cropper/src/CopperModal.vue
  2. 26
      apps/vue/src/components/Cropper/src/Cropper.vue
  3. 50
      apps/vue/src/components/Cropper/src/CropperAvatar.vue
  4. 146
      apps/vue/src/components/registerGlobComp.ts
  5. 3
      apps/vue/src/main.ts

54
apps/vue/src/components/Cropper/src/CopperModal.vue

@ -22,14 +22,14 @@
</div> </div>
<div :class="`${prefixCls}-toolbar`"> <div :class="`${prefixCls}-toolbar`">
<Upload :fileList="[]" accept="image/*" :beforeUpload="handleBeforeUpload"> <Upload :fileList="fileList" accept="image/*" :beforeUpload="handleBeforeUpload">
<Tooltip :title="t('component.cropper.selectImage')" placement="bottom"> <Tooltip :title="t('component.cropper.selectImage')" placement="bottom">
<a-button size="small" preIcon="ant-design:upload-outlined" type="primary" /> <Button size="small" preIcon="ant-design:upload-outlined" type="primary" />
</Tooltip> </Tooltip>
</Upload> </Upload>
<Space> <Space>
<Tooltip :title="t('component.cropper.btn_reset')" placement="bottom"> <Tooltip :title="t('component.cropper.btn_reset')" placement="bottom">
<a-button <Button
type="primary" type="primary"
preIcon="ant-design:reload-outlined" preIcon="ant-design:reload-outlined"
size="small" size="small"
@ -38,7 +38,7 @@
/> />
</Tooltip> </Tooltip>
<Tooltip :title="t('component.cropper.btn_rotate_left')" placement="bottom"> <Tooltip :title="t('component.cropper.btn_rotate_left')" placement="bottom">
<a-button <Button
type="primary" type="primary"
preIcon="ant-design:rotate-left-outlined" preIcon="ant-design:rotate-left-outlined"
size="small" size="small"
@ -47,7 +47,7 @@
/> />
</Tooltip> </Tooltip>
<Tooltip :title="t('component.cropper.btn_rotate_right')" placement="bottom"> <Tooltip :title="t('component.cropper.btn_rotate_right')" placement="bottom">
<a-button <Button
type="primary" type="primary"
preIcon="ant-design:rotate-right-outlined" preIcon="ant-design:rotate-right-outlined"
size="small" size="small"
@ -56,7 +56,7 @@
/> />
</Tooltip> </Tooltip>
<Tooltip :title="t('component.cropper.btn_scale_x')" placement="bottom"> <Tooltip :title="t('component.cropper.btn_scale_x')" placement="bottom">
<a-button <Button
type="primary" type="primary"
preIcon="vaadin:arrows-long-h" preIcon="vaadin:arrows-long-h"
size="small" size="small"
@ -65,7 +65,7 @@
/> />
</Tooltip> </Tooltip>
<Tooltip :title="t('component.cropper.btn_scale_y')" placement="bottom"> <Tooltip :title="t('component.cropper.btn_scale_y')" placement="bottom">
<a-button <Button
type="primary" type="primary"
preIcon="vaadin:arrows-long-v" preIcon="vaadin:arrows-long-v"
size="small" size="small"
@ -74,7 +74,7 @@
/> />
</Tooltip> </Tooltip>
<Tooltip :title="t('component.cropper.btn_zoom_in')" placement="bottom"> <Tooltip :title="t('component.cropper.btn_zoom_in')" placement="bottom">
<a-button <Button
type="primary" type="primary"
preIcon="ant-design:zoom-in-outlined" preIcon="ant-design:zoom-in-outlined"
size="small" size="small"
@ -83,7 +83,7 @@
/> />
</Tooltip> </Tooltip>
<Tooltip :title="t('component.cropper.btn_zoom_out')" placement="bottom"> <Tooltip :title="t('component.cropper.btn_zoom_out')" placement="bottom">
<a-button <Button
type="primary" type="primary"
preIcon="ant-design:zoom-out-outlined" preIcon="ant-design:zoom-out-outlined"
size="small" size="small"
@ -110,13 +110,15 @@
</div> </div>
</BasicModal> </BasicModal>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import type { CropendResult, Cropper } from './typing'; import type { CropendResult, Cropper } from './typing';
import type { UploadProps } from 'ant-design-vue';
import { defineComponent, ref } from 'vue'; import { ref } from 'vue';
import CropperImage from './Cropper.vue'; import CropperImage from './Cropper.vue';
import { Space, Upload, Avatar, Tooltip } from 'ant-design-vue'; import { Space, Upload, Avatar, Tooltip } from 'ant-design-vue';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
import { Button } from '/@/components/Button';
import { BasicModal, useModalInner } from '/@/components/Modal'; import { BasicModal, useModalInner } from '/@/components/Modal';
import { dataURLtoBlob } from '/@/utils/file/base64Conver'; import { dataURLtoBlob } from '/@/utils/file/base64Conver';
import { isFunction } from '/@/utils/is'; import { isFunction } from '/@/utils/is';
@ -124,23 +126,18 @@
type apiFunParams = { file: Blob; name: string; filename: string }; type apiFunParams = { file: Blob; name: string; filename: string };
const props = { const emits = defineEmits(['uploadSuccess', 'register']);
const props = defineProps({
circled: { type: Boolean, default: true }, circled: { type: Boolean, default: true },
uploadApi: { uploadApi: {
type: Function as PropType<(params: apiFunParams) => Promise<any>>, type: Function as PropType<(params: apiFunParams) => Promise<any>>,
}, },
}; });
export default defineComponent({
name: 'CropperModal',
components: { BasicModal, Space, CropperImage, Upload, Avatar, Tooltip },
props,
emits: ['uploadSuccess', 'register'],
setup(props, { emit }) {
let filename = ''; let filename = '';
const src = ref(''); const src = ref('');
const previewSource = ref(''); const previewSource = ref('');
const cropper = ref<Cropper>(); const cropper = ref<Cropper>();
const fileList = ref<UploadProps['fileList']>([]);
let scaleX = 1; let scaleX = 1;
let scaleY = 1; let scaleY = 1;
@ -186,28 +183,13 @@
try { try {
setModalProps({ confirmLoading: true }); setModalProps({ confirmLoading: true });
const result = await uploadApi({ name: 'file', file: blob, filename }); const result = await uploadApi({ name: 'file', file: blob, filename });
emit('uploadSuccess', { source: previewSource.value, data: result.data }); emits('uploadSuccess', { source: previewSource.value, data: result.data });
closeModal(); closeModal();
} finally { } finally {
setModalProps({ confirmLoading: false }); setModalProps({ confirmLoading: false });
} }
} }
} }
return {
t,
prefixCls,
src,
register,
previewSource,
handleBeforeUpload,
handleCropend,
handleReady,
handlerToolbar,
handleOk,
};
},
});
</script> </script>
<style lang="less"> <style lang="less">

26
apps/vue/src/components/Cropper/src/Cropper.vue

@ -10,9 +10,9 @@
/> />
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import type { CSSProperties } from 'vue'; import type { CSSProperties } from 'vue';
import { defineComponent, onMounted, ref, unref, computed, onUnmounted } from 'vue'; import { onMounted, ref, unref, computed, onUnmounted, useAttrs } from 'vue';
import Cropper from 'cropperjs'; import Cropper from 'cropperjs';
import 'cropperjs/dist/cropper.css'; import 'cropperjs/dist/cropper.css';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
@ -20,6 +20,7 @@
type Options = Cropper.Options; type Options = Cropper.Options;
const emits = defineEmits(['cropend', 'ready', 'cropendError']);
const defaultOptions: Options = { const defaultOptions: Options = {
aspectRatio: 1, aspectRatio: 1,
zoomable: true, zoomable: true,
@ -43,7 +44,7 @@
rotatable: true, rotatable: true,
}; };
const props = { const props = defineProps({
src: { type: String, required: true }, src: { type: String, required: true },
alt: { type: String }, alt: { type: String },
circled: { type: Boolean, default: false }, circled: { type: Boolean, default: false },
@ -55,13 +56,10 @@
}, },
imageStyle: { type: Object as PropType<CSSProperties>, default: () => ({}) }, imageStyle: { type: Object as PropType<CSSProperties>, default: () => ({}) },
options: { type: Object as PropType<Options>, default: () => ({}) }, options: { type: Object as PropType<Options>, default: () => ({}) },
}; });
const attrs = useAttrs();
export default defineComponent({
name: 'CropperImage',
props,
emits: ['cropend', 'ready', 'cropendError'],
setup(props, { attrs, emit }) {
const imgElRef = ref<ElRef<HTMLImageElement>>(); const imgElRef = ref<ElRef<HTMLImageElement>>();
const cropper = ref<Nullable<Cropper>>(); const cropper = ref<Nullable<Cropper>>();
const isReady = ref(false); const isReady = ref(false);
@ -107,7 +105,7 @@
ready: () => { ready: () => {
isReady.value = true; isReady.value = true;
realTimeCroppered(); realTimeCroppered();
emit('ready', cropper.value); emits('ready', cropper.value);
}, },
crop() { crop() {
debounceRealTimeCroppered(); debounceRealTimeCroppered();
@ -141,13 +139,13 @@
let fileReader: FileReader = new FileReader(); let fileReader: FileReader = new FileReader();
fileReader.readAsDataURL(blob); fileReader.readAsDataURL(blob);
fileReader.onloadend = (e) => { fileReader.onloadend = (e) => {
emit('cropend', { emits('cropend', {
imgBase64: e.target?.result ?? '', imgBase64: e.target?.result ?? '',
imgInfo, imgInfo,
}); });
}; };
fileReader.onerror = () => { fileReader.onerror = () => {
emit('cropendError'); emits('cropendError');
}; };
}, 'image/png'); }, 'image/png');
} }
@ -169,10 +167,6 @@
context.fill(); context.fill();
return canvas; return canvas;
} }
return { getClass, imgElRef, getWrapperStyle, getImageStyle, isReady, croppered };
},
});
</script> </script>
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-cropper-image'; @prefix-cls: ~'@{namespace}-cropper-image';

50
apps/vue/src/components/Cropper/src/CropperAvatar.vue

@ -11,14 +11,14 @@
</div> </div>
<img :src="sourceValue" v-if="sourceValue" alt="avatar" /> <img :src="sourceValue" v-if="sourceValue" alt="avatar" />
</div> </div>
<a-button <Button
:class="`${prefixCls}-upload-btn`" :class="`${prefixCls}-upload-btn`"
@click="openModal" @click="openModal"
v-if="showBtn" v-if="showBtn"
v-bind="btnProps" v-bind="btnProps"
> >
{{ btnText ? btnText : t('component.cropper.selectImage') }} {{ btnText ? btnText : t('component.cropper.selectImage') }}
</a-button> </Button>
<CopperModal <CopperModal
@register="register" @register="register"
@ -28,9 +28,8 @@
/> />
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { import {
defineComponent,
computed, computed,
CSSProperties, CSSProperties,
unref, unref,
@ -39,6 +38,7 @@
watch, watch,
PropType, PropType,
} from 'vue'; } from 'vue';
import { Button } from '/@/components/Button';
import CopperModal from './CopperModal.vue'; import CopperModal from './CopperModal.vue';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
import { useModal } from '/@/components/Modal'; import { useModal } from '/@/components/Modal';
@ -47,21 +47,22 @@
import type { ButtonProps } from '/@/components/Button'; import type { ButtonProps } from '/@/components/Button';
import Icon from '/@/components/Icon'; import Icon from '/@/components/Icon';
const props = { interface File {
file: Blob;
name: string;
fileName?: string;
}
const emits = defineEmits(['update:value', 'change']);
const props = defineProps({
width: { type: [String, Number], default: '200px' }, width: { type: [String, Number], default: '200px' },
value: { type: String }, value: { type: String },
showBtn: { type: Boolean, default: true }, showBtn: { type: Boolean, default: true },
btnProps: { type: Object as PropType<ButtonProps> }, btnProps: { type: Object as PropType<ButtonProps> },
btnText: { type: String, default: '' }, btnText: { type: String, default: '' },
uploadApi: { type: Function as PropType<({ file: Blob, name: string }) => Promise<void>> }, uploadApi: { type: Function as PropType<(file: File) => Promise<void>> },
}; });
export default defineComponent({
name: 'CropperAvatar',
components: { CopperModal, Icon },
props,
emits: ['update:value', 'change'],
setup(props, { emit, expose }) {
const sourceValue = ref(props.value || ''); const sourceValue = ref(props.value || '');
const { prefixCls } = useDesign('cropper-avatar'); const { prefixCls } = useDesign('cropper-avatar');
const [register, { openModal, closeModal }] = useModal(); const [register, { openModal, closeModal }] = useModal();
@ -87,32 +88,17 @@
watch( watch(
() => sourceValue.value, () => sourceValue.value,
(v: string) => { (v: string) => {
emit('update:value', v); emits('update:value', v);
}, },
); );
function handleUploadSuccess({ source }) { function handleUploadSuccess({ source }) {
sourceValue.value = source; sourceValue.value = source;
emit('change', source); emits('change', source);
createMessage.success(t('component.cropper.uploadSuccess')); createMessage.success(t('component.cropper.uploadSuccess'));
} }
expose({ openModal: openModal.bind(null, true), closeModal }); defineExpose({ openModal: openModal.bind(null, true), closeModal });
return {
t,
prefixCls,
register,
openModal: openModal as any,
getIconWidth,
sourceValue,
getClass,
getImageWrapperStyle,
getStyle,
handleUploadSuccess,
};
},
});
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

146
apps/vue/src/components/registerGlobComp.ts

@ -3,17 +3,159 @@ import { Button } from './Button';
import { Input } from './Input'; import { Input } from './Input';
import { import {
// Need // Need
Affix,
Anchor,
AutoComplete,
Alert,
Avatar,
BackTop,
Badge,
Button as AntButton, Button as AntButton,
Breadcrumb,
Calendar,
Card,
Collapse,
Carousel,
Cascader,
Checkbox,
Col,
Comment,
DatePicker,
Descriptions,
Divider,
Dropdown,
Drawer,
Empty,
Form,
Image,
Layout, Layout,
List,
Mentions,
Statistic,
Pagination,
Popover,
Progress,
Radio,
Rate,
Result,
Row,
Select,
Skeleton,
Slider,
Space,
Spin,
Steps,
Switch,
Transfer,
Tabs,
Tag,
Timeline,
Tooltip,
Typography,
Upload,
Input as AInput, Input as AInput,
InputNumber as AInputNumber,
} from 'ant-design-vue'; } from 'ant-design-vue';
const compList = [AntButton.Group]; const compList = [
Affix,
Anchor,
Anchor.Link,
AntButton.Group,
AutoComplete,
Alert,
Avatar,
Avatar.Group,
BackTop,
Badge,
Badge.Ribbon,
Breadcrumb,
Breadcrumb.Item,
Breadcrumb.Separator,
Calendar,
Card,
Card.Grid,
Card.Meta,
Collapse,
Collapse.Panel,
Carousel,
Cascader,
Checkbox,
Checkbox.Group,
Col,
Comment,
DatePicker,
DatePicker.MonthPicker,
DatePicker.QuarterPicker,
DatePicker.RangePicker,
DatePicker.TimePicker,
DatePicker.WeekPicker,
DatePicker.YearPicker,
Descriptions,
Descriptions.Item,
Divider,
Dropdown,
Dropdown.Button,
Drawer,
Empty,
Form,
Form.Item,
Form.ItemRest,
Image,
AInput,
AInput.Group,
AInput.Password,
AInput.Search,
AInput.TextArea,
AInputNumber,
List,
Mentions,
Statistic,
Statistic.Countdown,
Pagination,
Popover,
Progress,
Radio,
Radio.Button,
Radio.Group,
Rate,
Result,
Row,
Select,
Select.OptGroup,
Select.Option,
Skeleton,
Skeleton.Button,
Skeleton.Avatar,
Skeleton.Image,
Skeleton.Input,
Slider,
Space,
Spin,
Steps,
Steps.Step,
Switch,
Transfer,
Tabs,
Tabs.TabPane,
Tag,
Tag.CheckableTag,
Timeline,
Timeline.Item,
Tooltip,
Typography,
Typography.Link,
Typography.Paragraph,
Typography.Text,
Typography.Title,
Upload,
Upload.Dragger,
];
export function registerGlobComp(app: App) { export function registerGlobComp(app: App) {
compList.forEach((comp) => { compList.forEach((comp) => {
app.component(comp.name || comp.displayName, comp); app.component(comp.name || comp.displayName, comp);
}); });
app.use(Input).use(AInput).use(Button).use(Layout); app.use(Input).use(Button).use(Layout);
} }

3
apps/vue/src/main.ts

@ -5,7 +5,6 @@ import 'virtual:windi-utilities.css';
// Register icon sprite // Register icon sprite
import 'virtual:svg-icons-register'; import 'virtual:svg-icons-register';
import App from './App.vue'; import App from './App.vue';
import Antd from 'ant-design-vue';
import VueCookies from 'vue-cookies'; import VueCookies from 'vue-cookies';
import { createApp } from 'vue'; import { createApp } from 'vue';
import { initAppConfigStore, initAbpConfigStore } from '/@/logics/initAppConfig'; import { initAppConfigStore, initAbpConfigStore } from '/@/logics/initAppConfig';
@ -53,8 +52,6 @@ async function bootstrap() {
app.use(VueCookies); app.use(VueCookies);
app.use(Antd);
app.mount('#app'); app.mount('#app');
} }

Loading…
Cancel
Save