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

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

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

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

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

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

@ -3,17 +3,159 @@ import { Button } from './Button';
import { Input } from './Input';
import {
// Need
Affix,
Anchor,
AutoComplete,
Alert,
Avatar,
BackTop,
Badge,
Button as AntButton,
Breadcrumb,
Calendar,
Card,
Collapse,
Carousel,
Cascader,
Checkbox,
Col,
Comment,
DatePicker,
Descriptions,
Divider,
Dropdown,
Drawer,
Empty,
Form,
Image,
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,
InputNumber as AInputNumber,
} 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) {
compList.forEach((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
import 'virtual:svg-icons-register';
import App from './App.vue';
import Antd from 'ant-design-vue';
import VueCookies from 'vue-cookies';
import { createApp } from 'vue';
import { initAppConfigStore, initAbpConfigStore } from '/@/logics/initAppConfig';
@ -53,8 +52,6 @@ async function bootstrap() {
app.use(VueCookies);
app.use(Antd);
app.mount('#app');
}

Loading…
Cancel
Save