wip: add cropper iamge component

This commit is contained in:
Vben
2021-03-29 22:48:13 +08:00
parent 39d629a029
commit 2e11ea677b
15 changed files with 222 additions and 43 deletions

View File

@@ -0,0 +1,4 @@
import type Cropper from 'cropperjs';
export type { Cropper };
export { default as CropperImage } from './src/index.vue';

View File

@@ -0,0 +1,15 @@
<template>
<div :class="$attrs.class" :style="$attrs.style"> </div>
</template>
<script lang="ts">
// TODO
import { defineComponent } from 'vue';
export default defineComponent({
name: 'AvatarCropper',
props: {},
setup() {
return {};
},
});
</script>

View File

@@ -0,0 +1,100 @@
<template>
<div :class="$attrs.class" :style="$attrs.style">
<img ref="imgElRef" :src="src" :alt="alt" :crossorigin="crossorigin" :style="getImageStyle" />
</div>
</template>
<script lang="ts">
import type { CSSProperties } from 'vue';
import { defineComponent, onMounted, ref, unref, computed } from 'vue';
import Cropper from 'cropperjs';
import 'cropperjs/dist/cropper.css';
type Options = Cropper.Options;
const defaultOptions: Cropper.Options = {
aspectRatio: 16 / 9,
zoomable: true,
zoomOnTouch: true,
zoomOnWheel: true,
cropBoxMovable: true,
cropBoxResizable: true,
toggleDragModeOnDblclick: true,
autoCrop: true,
background: true,
highlight: true,
center: true,
responsive: true,
restore: true,
checkCrossOrigin: true,
checkOrientation: true,
scalable: true,
modal: true,
guides: true,
movable: true,
rotatable: true,
};
export default defineComponent({
name: 'CropperImage',
props: {
src: {
type: String,
required: true,
},
alt: {
type: String,
},
height: {
type: String,
default: '500px',
},
crossorigin: {
type: String,
default: undefined,
},
imageStyle: {
type: Object as PropType<CSSProperties>,
default: {},
},
options: {
type: Object as PropType<Options>,
default: {},
},
},
setup(props) {
const imgElRef = ref<ElRef<HTMLImageElement>>(null);
const cropper = ref<Nullable<Cropper>>(null);
const isReady = ref(false);
const getImageStyle = computed(
(): CSSProperties => {
return {
height: props.height,
maxWidth: '100%',
...props.imageStyle,
};
}
);
async function init() {
const imgEl = unref(imgElRef);
if (!imgEl) {
return;
}
cropper.value = new Cropper(imgEl, {
...defaultOptions,
ready: () => {
isReady.value = true;
},
...props.options,
});
}
onMounted(init);
return { imgElRef, getImageStyle, isReady };
},
});
</script>

View File

@@ -5,14 +5,20 @@
</span>
<template #overlay>
<a-menu :selectedKeys="selectedKeys">
<template v-for="item in getMenuList" :key="`${item.event}`">
<template v-for="item in dropMenuList" :key="`${item.event}`">
<a-menu-item
v-bind="getAttr(item.event)"
@click="handleClickMenu(item)"
:disabled="item.disabled"
>
<Icon :icon="item.icon" v-if="item.icon" />
<span class="ml-1">{{ item.text }}</span>
<Popconfirm v-if="popconfirm" v-bind="item">
<Icon :icon="item.icon" v-if="item.icon" />
<span class="ml-1">{{ item.text }}</span>
</Popconfirm>
<template v-else>
<Icon :icon="item.icon" v-if="item.icon" />
<span class="ml-1">{{ item.text }}</span>
</template>
</a-menu-item>
<a-menu-divider v-if="item.divider" :key="`d-${item.event}`" />
</template>
@@ -25,9 +31,9 @@
import type { PropType } from 'vue';
import type { DropMenu } from './types';
import { defineComponent, computed, unref } from 'vue';
import { Dropdown, Menu } from 'ant-design-vue';
import Icon from '/@/components/Icon/index';
import { defineComponent } from 'vue';
import { Dropdown, Menu, Popconfirm } from 'ant-design-vue';
import { Icon } from '/@/components/Icon';
export default defineComponent({
name: 'BasicDropdown',
@@ -37,8 +43,10 @@
[Menu.Item.name]: Menu.Item,
[Menu.Divider.name]: Menu.Divider,
Icon,
Popconfirm,
},
props: {
popconfirm: Boolean,
/**
* the trigger mode which executes the drop-down action
* @default ['hover']
@@ -61,19 +69,15 @@
},
emits: ['menuEvent'],
setup(props, { emit }) {
const getMenuList = computed(() => props.dropMenuList);
function handleClickMenu(item: DropMenu) {
const { event } = item;
const menu = unref(getMenuList).find((item) => `${item.event}` === `${event}`);
const menu = props.dropMenuList.find((item) => `${item.event}` === `${event}`);
emit('menuEvent', menu);
item.onClick?.();
}
return {
handleClickMenu,
getMenuList,
getAttr: (key: string) => ({ key }),
getAttr: (key: string | number) => ({ key }),
};
},
});

View File

@@ -10,7 +10,12 @@
v-if="divider && index < getActions.length - (dropDownActions ? 0 : 1)"
/>
</template>
<Dropdown :trigger="['hover']" :dropMenuList="getDropList" v-if="dropDownActions">
<Dropdown
:trigger="['hover']"
:dropMenuList="getDropdownList"
popconfirm
v-if="dropDownActions"
>
<slot name="more"></slot>
<a-button type="link" size="small" v-if="!$slots.more">
<MoreOutlined class="icon-more" />
@@ -71,11 +76,12 @@
});
});
const getDropList = computed(() => {
const getDropdownList = computed(() => {
return (toRaw(props.dropDownActions) || []).map((action, index) => {
const { label } = action;
const { label, popConfirm } = action;
return {
...action,
...popConfirm,
text: label,
divider: index < props.dropDownActions.length - 1 ? props.divider : false,
};
@@ -88,7 +94,7 @@
return actionColumn?.align ?? 'left';
});
return { prefixCls, getActions, getDropList, getAlign };
return { prefixCls, getActions, getDropdownList, getAlign };
},
});
</script>

View File

@@ -33,5 +33,6 @@ export default {
loading: 'Loading',
time: 'Time',
time: 'Relative Time',
cropperImage: 'Cropper Image',
};

View File

@@ -32,5 +32,6 @@ export default {
loading: 'Loading',
time: '时间组件',
time: '相对时间',
cropperImage: '图片裁剪',
};

View File

@@ -6,7 +6,9 @@ const menu: MenuModule = {
menu: {
name: t('routes.demo.comp.comp'),
path: '/comp',
tag: {
dot: true,
},
children: [
{
path: 'basic',
@@ -114,6 +116,13 @@ const menu: MenuModule = {
},
],
},
{
path: 'cropper',
name: t('routes.demo.comp.cropperImage'),
tag: {
content: 'new',
},
},
{
path: 'countTo',
name: t('routes.demo.comp.countTo'),

View File

@@ -232,6 +232,14 @@ const comp: AppRouteModule = {
title: t('routes.demo.comp.transition'),
},
},
{
path: 'cropper',
name: 'CropperDemo',
component: () => import('/@/views/demo/comp/cropper/index.vue'),
meta: {
title: t('routes.demo.comp.cropperImage'),
},
},
{
path: 'timestamp',
name: 'TimeDemo',

View File

@@ -0,0 +1,22 @@
<template>
<PageWrapper title="图片裁剪示例" contentBackground>
<CropperImage src="https://fengyuanchen.github.io/cropperjs/images/picture.jpg"></CropperImage>
</PageWrapper>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { PageWrapper } from '/@/components/Page';
import { CropperImage } from '/@/components/Cropper';
import img from '/@/assets/images/header.jpg';
export default defineComponent({
components: {
PageWrapper,
CropperImage,
},
setup() {
return { img };
},
});
</script>

View File

@@ -9,7 +9,7 @@
<div class="mb-2"> 头像 </div>
<img width="140" :src="headerImg" />
<Upload :showUploadList="false">
<Button type="ghost" class="ml-5"> <Icon icon="feather:upload" />更换头像 </Button>
<Button class="ml-5"> <Icon icon="feather:upload" />更换头像 </Button>
</Upload>
</div>
</a-col>

View File

@@ -13,7 +13,10 @@
:dropDownActions="[
{
label: '启用',
onClick: handleOpen.bind(null, record),
popConfirm: {
title: '是否启用?',
confirm: handleOpen.bind(null, record),
},
},
]"
/>