Commit 2e11ea677b0bb8fc1a5d8d3bc5a9a664195adb7a

Authored by Vben
1 parent 39d629a0

wip: add cropper iamge component

package.json
... ... @@ -37,6 +37,7 @@
37 37 "ant-design-vue": "^2.1.2",
38 38 "apexcharts": "^3.26.0",
39 39 "axios": "^0.21.1",
  40 + "cropperjs": "^1.5.11",
40 41 "crypto-js": "^4.0.0",
41 42 "echarts": "^5.0.2",
42 43 "lodash-es": "^4.17.21",
... ... @@ -91,7 +92,7 @@
91 92 "esno": "^0.5.0",
92 93 "fs-extra": "^9.1.0",
93 94 "http-server": "^0.12.3",
94   - "husky": "^5.2.0",
  95 + "husky": "^6.0.0",
95 96 "inquirer": "^8.0.0",
96 97 "is-ci": "^3.0.0",
97 98 "less": "^4.1.1",
... ... @@ -117,7 +118,7 @@
117 118 "vite-plugin-style-import": "^0.9.1",
118 119 "vite-plugin-svg-icons": "^0.4.0",
119 120 "vite-plugin-theme": "^0.5.0",
120   - "vite-plugin-windicss": "0.10.4",
  121 + "vite-plugin-windicss": "0.11.0",
121 122 "vue-eslint-parser": "^7.6.0"
122 123 },
123 124 "resolutions": {
... ...
src/components/Cropper/index.ts 0 → 100644
  1 +import type Cropper from 'cropperjs';
  2 +
  3 +export type { Cropper };
  4 +export { default as CropperImage } from './src/index.vue';
... ...
src/components/Cropper/src/AvatarCropper.vue 0 → 100644
  1 +<template>
  2 + <div :class="$attrs.class" :style="$attrs.style"> </div>
  3 +</template>
  4 +<script lang="ts">
  5 + // TODO
  6 + import { defineComponent } from 'vue';
  7 +
  8 + export default defineComponent({
  9 + name: 'AvatarCropper',
  10 + props: {},
  11 + setup() {
  12 + return {};
  13 + },
  14 + });
  15 +</script>
... ...
src/components/Cropper/src/index.vue 0 → 100644
  1 +<template>
  2 + <div :class="$attrs.class" :style="$attrs.style">
  3 + <img ref="imgElRef" :src="src" :alt="alt" :crossorigin="crossorigin" :style="getImageStyle" />
  4 + </div>
  5 +</template>
  6 +<script lang="ts">
  7 + import type { CSSProperties } from 'vue';
  8 +
  9 + import { defineComponent, onMounted, ref, unref, computed } from 'vue';
  10 +
  11 + import Cropper from 'cropperjs';
  12 + import 'cropperjs/dist/cropper.css';
  13 +
  14 + type Options = Cropper.Options;
  15 +
  16 + const defaultOptions: Cropper.Options = {
  17 + aspectRatio: 16 / 9,
  18 + zoomable: true,
  19 + zoomOnTouch: true,
  20 + zoomOnWheel: true,
  21 + cropBoxMovable: true,
  22 + cropBoxResizable: true,
  23 + toggleDragModeOnDblclick: true,
  24 + autoCrop: true,
  25 + background: true,
  26 + highlight: true,
  27 + center: true,
  28 + responsive: true,
  29 + restore: true,
  30 + checkCrossOrigin: true,
  31 + checkOrientation: true,
  32 + scalable: true,
  33 + modal: true,
  34 + guides: true,
  35 + movable: true,
  36 + rotatable: true,
  37 + };
  38 + export default defineComponent({
  39 + name: 'CropperImage',
  40 + props: {
  41 + src: {
  42 + type: String,
  43 + required: true,
  44 + },
  45 + alt: {
  46 + type: String,
  47 + },
  48 + height: {
  49 + type: String,
  50 + default: '500px',
  51 + },
  52 + crossorigin: {
  53 + type: String,
  54 + default: undefined,
  55 + },
  56 + imageStyle: {
  57 + type: Object as PropType<CSSProperties>,
  58 + default: {},
  59 + },
  60 + options: {
  61 + type: Object as PropType<Options>,
  62 + default: {},
  63 + },
  64 + },
  65 + setup(props) {
  66 + const imgElRef = ref<ElRef<HTMLImageElement>>(null);
  67 + const cropper = ref<Nullable<Cropper>>(null);
  68 +
  69 + const isReady = ref(false);
  70 +
  71 + const getImageStyle = computed(
  72 + (): CSSProperties => {
  73 + return {
  74 + height: props.height,
  75 + maxWidth: '100%',
  76 + ...props.imageStyle,
  77 + };
  78 + }
  79 + );
  80 +
  81 + async function init() {
  82 + const imgEl = unref(imgElRef);
  83 + if (!imgEl) {
  84 + return;
  85 + }
  86 + cropper.value = new Cropper(imgEl, {
  87 + ...defaultOptions,
  88 + ready: () => {
  89 + isReady.value = true;
  90 + },
  91 + ...props.options,
  92 + });
  93 + }
  94 +
  95 + onMounted(init);
  96 +
  97 + return { imgElRef, getImageStyle, isReady };
  98 + },
  99 + });
  100 +</script>
... ...
src/components/Dropdown/src/Dropdown.vue
... ... @@ -5,14 +5,20 @@
5 5 </span>
6 6 <template #overlay>
7 7 <a-menu :selectedKeys="selectedKeys">
8   - <template v-for="item in getMenuList" :key="`${item.event}`">
  8 + <template v-for="item in dropMenuList" :key="`${item.event}`">
9 9 <a-menu-item
10 10 v-bind="getAttr(item.event)"
11 11 @click="handleClickMenu(item)"
12 12 :disabled="item.disabled"
13 13 >
14   - <Icon :icon="item.icon" v-if="item.icon" />
15   - <span class="ml-1">{{ item.text }}</span>
  14 + <Popconfirm v-if="popconfirm" v-bind="item">
  15 + <Icon :icon="item.icon" v-if="item.icon" />
  16 + <span class="ml-1">{{ item.text }}</span>
  17 + </Popconfirm>
  18 + <template v-else>
  19 + <Icon :icon="item.icon" v-if="item.icon" />
  20 + <span class="ml-1">{{ item.text }}</span>
  21 + </template>
16 22 </a-menu-item>
17 23 <a-menu-divider v-if="item.divider" :key="`d-${item.event}`" />
18 24 </template>
... ... @@ -25,9 +31,9 @@
25 31 import type { PropType } from 'vue';
26 32 import type { DropMenu } from './types';
27 33  
28   - import { defineComponent, computed, unref } from 'vue';
29   - import { Dropdown, Menu } from 'ant-design-vue';
30   - import Icon from '/@/components/Icon/index';
  34 + import { defineComponent } from 'vue';
  35 + import { Dropdown, Menu, Popconfirm } from 'ant-design-vue';
  36 + import { Icon } from '/@/components/Icon';
31 37  
32 38 export default defineComponent({
33 39 name: 'BasicDropdown',
... ... @@ -37,8 +43,10 @@
37 43 [Menu.Item.name]: Menu.Item,
38 44 [Menu.Divider.name]: Menu.Divider,
39 45 Icon,
  46 + Popconfirm,
40 47 },
41 48 props: {
  49 + popconfirm: Boolean,
42 50 /**
43 51 * the trigger mode which executes the drop-down action
44 52 * @default ['hover']
... ... @@ -61,19 +69,15 @@
61 69 },
62 70 emits: ['menuEvent'],
63 71 setup(props, { emit }) {
64   - const getMenuList = computed(() => props.dropMenuList);
65   -
66 72 function handleClickMenu(item: DropMenu) {
67 73 const { event } = item;
68   - const menu = unref(getMenuList).find((item) => `${item.event}` === `${event}`);
  74 + const menu = props.dropMenuList.find((item) => `${item.event}` === `${event}`);
69 75 emit('menuEvent', menu);
70 76 item.onClick?.();
71 77 }
72   -
73 78 return {
74 79 handleClickMenu,
75   - getMenuList,
76   - getAttr: (key: string) => ({ key }),
  80 + getAttr: (key: string | number) => ({ key }),
77 81 };
78 82 },
79 83 });
... ...
src/components/Table/src/components/TableAction.vue
... ... @@ -10,7 +10,12 @@
10 10 v-if="divider && index < getActions.length - (dropDownActions ? 0 : 1)"
11 11 />
12 12 </template>
13   - <Dropdown :trigger="['hover']" :dropMenuList="getDropList" v-if="dropDownActions">
  13 + <Dropdown
  14 + :trigger="['hover']"
  15 + :dropMenuList="getDropdownList"
  16 + popconfirm
  17 + v-if="dropDownActions"
  18 + >
14 19 <slot name="more"></slot>
15 20 <a-button type="link" size="small" v-if="!$slots.more">
16 21 <MoreOutlined class="icon-more" />
... ... @@ -71,11 +76,12 @@
71 76 });
72 77 });
73 78  
74   - const getDropList = computed(() => {
  79 + const getDropdownList = computed(() => {
75 80 return (toRaw(props.dropDownActions) || []).map((action, index) => {
76   - const { label } = action;
  81 + const { label, popConfirm } = action;
77 82 return {
78 83 ...action,
  84 + ...popConfirm,
79 85 text: label,
80 86 divider: index < props.dropDownActions.length - 1 ? props.divider : false,
81 87 };
... ... @@ -88,7 +94,7 @@
88 94 return actionColumn?.align ?? 'left';
89 95 });
90 96  
91   - return { prefixCls, getActions, getDropList, getAlign };
  97 + return { prefixCls, getActions, getDropdownList, getAlign };
92 98 },
93 99 });
94 100 </script>
... ...
src/locales/lang/en/routes/demo/comp.ts
... ... @@ -33,5 +33,6 @@ export default {
33 33  
34 34 loading: 'Loading',
35 35  
36   - time: 'Time',
  36 + time: 'Relative Time',
  37 + cropperImage: 'Cropper Image',
37 38 };
... ...
src/locales/lang/zh_CN/routes/demo/comp.ts
... ... @@ -32,5 +32,6 @@ export default {
32 32  
33 33 loading: 'Loading',
34 34  
35   - time: '时间组件',
  35 + time: '相对时间',
  36 + cropperImage: '图片裁剪',
36 37 };
... ...
src/router/menus/modules/demo/comp.ts
... ... @@ -6,7 +6,9 @@ const menu: MenuModule = {
6 6 menu: {
7 7 name: t('routes.demo.comp.comp'),
8 8 path: '/comp',
9   -
  9 + tag: {
  10 + dot: true,
  11 + },
10 12 children: [
11 13 {
12 14 path: 'basic',
... ... @@ -115,6 +117,13 @@ const menu: MenuModule = {
115 117 ],
116 118 },
117 119 {
  120 + path: 'cropper',
  121 + name: t('routes.demo.comp.cropperImage'),
  122 + tag: {
  123 + content: 'new',
  124 + },
  125 + },
  126 + {
118 127 path: 'countTo',
119 128 name: t('routes.demo.comp.countTo'),
120 129 },
... ...
src/router/routes/modules/demo/comp.ts
... ... @@ -233,6 +233,14 @@ const comp: AppRouteModule = {
233 233 },
234 234 },
235 235 {
  236 + path: 'cropper',
  237 + name: 'CropperDemo',
  238 + component: () => import('/@/views/demo/comp/cropper/index.vue'),
  239 + meta: {
  240 + title: t('routes.demo.comp.cropperImage'),
  241 + },
  242 + },
  243 + {
236 244 path: 'timestamp',
237 245 name: 'TimeDemo',
238 246 component: () => import('/@/views/demo/comp/time/index.vue'),
... ...
src/views/demo/comp/cropper/index.vue 0 → 100644
  1 +<template>
  2 + <PageWrapper title="图片裁剪示例" contentBackground>
  3 + <CropperImage src="https://fengyuanchen.github.io/cropperjs/images/picture.jpg"></CropperImage>
  4 + </PageWrapper>
  5 +</template>
  6 +<script lang="ts">
  7 + import { defineComponent } from 'vue';
  8 + import { PageWrapper } from '/@/components/Page';
  9 +
  10 + import { CropperImage } from '/@/components/Cropper';
  11 +
  12 + import img from '/@/assets/images/header.jpg';
  13 + export default defineComponent({
  14 + components: {
  15 + PageWrapper,
  16 + CropperImage,
  17 + },
  18 + setup() {
  19 + return { img };
  20 + },
  21 + });
  22 +</script>
... ...
src/views/demo/page/account/setting/BaseSetting.vue
... ... @@ -9,7 +9,7 @@
9 9 <div class="mb-2"> 头像 </div>
10 10 <img width="140" :src="headerImg" />
11 11 <Upload :showUploadList="false">
12   - <Button type="ghost" class="ml-5"> <Icon icon="feather:upload" />更换头像 </Button>
  12 + <Button class="ml-5"> <Icon icon="feather:upload" />更换头像 </Button>
13 13 </Upload>
14 14 </div>
15 15 </a-col>
... ...
src/views/demo/table/FixedColumn.vue
... ... @@ -13,7 +13,10 @@
13 13 :dropDownActions="[
14 14 {
15 15 label: '启用',
16   - onClick: handleOpen.bind(null, record),
  16 + popConfirm: {
  17 + title: '是否启用?',
  18 + confirm: handleOpen.bind(null, record),
  19 + },
17 20 },
18 21 ]"
19 22 />
... ...
vite.config.ts
... ... @@ -85,7 +85,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig =&gt; {
85 85 'ant-design-vue/es/locale/en_US',
86 86 'moment/dist/locale/eu',
87 87 ],
88   - exclude: ['vue-demi'],
  88 + exclude: ['vue-demi', 'consolidate'],
89 89 },
90 90 };
91 91 };
... ...
yarn.lock
... ... @@ -2077,10 +2077,10 @@
2077 2077 dependencies:
2078 2078 vue-demi latest
2079 2079  
2080   -"@windicss/plugin-utils@0.10.4":
2081   - version "0.10.4"
2082   - resolved "https://registry.npmjs.org/@windicss/plugin-utils/-/plugin-utils-0.10.4.tgz#ed9163b09e030f7358cc4742b1f2b6c92d084d5d"
2083   - integrity sha512-jQu69qzA56Lv18OK8U4mUTDV17st4EdPawQuaRG2VNK+ZEQWYsMNnqGxhzDTl/NhWTGCcTb3D6mlFPNo0QDOFg==
  2080 +"@windicss/plugin-utils@0.11.0":
  2081 + version "0.11.0"
  2082 + resolved "https://registry.npmjs.org/@windicss/plugin-utils/-/plugin-utils-0.11.0.tgz#142fe810c9cf71719074518295f65bd01f0ac895"
  2083 + integrity sha512-NIBJ7/wG8Ty///Qxktefud4OI18XjZkycW6DSkWV7++aYyZOdDgGRn493pU+5QZuXeXU8iNR6NKZDtFNcYD7kQ==
2084 2084 dependencies:
2085 2085 debug "^4.3.2"
2086 2086 fast-glob "^3.2.5"
... ... @@ -2088,7 +2088,7 @@
2088 2088 micromatch "^4.0.2"
2089 2089 pirates "^4.0.1"
2090 2090 sucrase "^3.17.1"
2091   - windicss "^2.5.7"
  2091 + windicss "^2.5.8"
2092 2092  
2093 2093 "@zxcvbn-ts/core@^0.3.0":
2094 2094 version "0.3.0"
... ... @@ -3582,6 +3582,11 @@ create-require@^1.1.0:
3582 3582 resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
3583 3583 integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
3584 3584  
  3585 +cropperjs@^1.5.11:
  3586 + version "1.5.11"
  3587 + resolved "https://registry.npmjs.org/cropperjs/-/cropperjs-1.5.11.tgz#502ae6d8ca098b124de6813601cca70015879fc0"
  3588 + integrity sha512-SJUeBBhtNBnnn+UrLKluhFRIXLJn7XFPv8QN1j49X5t+BIMwkgvDev541f96bmu8Xe0TgCx3gON22KmY/VddaA==
  3589 +
3585 3590 cross-env@^7.0.3:
3586 3591 version "7.0.3"
3587 3592 resolved "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf"
... ... @@ -5698,10 +5703,10 @@ husky@^5.1.3:
5698 5703 resolved "https://registry.npmjs.org/husky/-/husky-5.1.3.tgz#1a0645a4fe3ffc006c4d0d8bd0bcb4c98787cc9d"
5699 5704 integrity sha512-fbNJ+Gz5wx2LIBtMweJNY1D7Uc8p1XERi5KNRMccwfQA+rXlxWNSdUxswo0gT8XqxywTIw7Ywm/F4v/O35RdMg==
5700 5705  
5701   -husky@^5.2.0:
5702   - version "5.2.0"
5703   - resolved "https://registry.npmjs.org/husky/-/husky-5.2.0.tgz#fc5e1c2300d34855d47de4753607d00943fc0802"
5704   - integrity sha512-AM8T/auHXRBxlrfPVLKP6jt49GCM2Zz47m8G3FOMsLmTv8Dj/fKVWE0Rh2d4Qrvmy131xEsdQnb3OXRib67PGg==
  5706 +husky@^6.0.0:
  5707 + version "6.0.0"
  5708 + resolved "https://registry.npmjs.org/husky/-/husky-6.0.0.tgz#810f11869adf51604c32ea577edbc377d7f9319e"
  5709 + integrity sha512-SQS2gDTB7tBN486QSoKPKQItZw97BMOd+Kdb6ghfpBc0yXyzrddI0oDV5MkDAbuB4X2mO3/nj60TRMcYxwzZeQ==
5705 5710  
5706 5711 iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4:
5707 5712 version "0.4.24"
... ... @@ -11005,15 +11010,15 @@ vite-plugin-theme@^0.5.0:
11005 11010 tinycolor2 "^1.4.2"
11006 11011 ts-jest "^26.5.3"
11007 11012  
11008   -vite-plugin-windicss@0.10.4:
11009   - version "0.10.4"
11010   - resolved "https://registry.npmjs.org/vite-plugin-windicss/-/vite-plugin-windicss-0.10.4.tgz#e93577111ea0a55befbe4e2aa2e596f55f6b74b2"
11011   - integrity sha512-P7alH2dGGw3OTgjs9yZG2w0i+o1HKD8PChwhm2ftP+lLCe1xDL3LReheuRil9p2xPYzrVouER2YTbIdLUEThrQ==
  11013 +vite-plugin-windicss@0.11.0:
  11014 + version "0.11.0"
  11015 + resolved "https://registry.npmjs.org/vite-plugin-windicss/-/vite-plugin-windicss-0.11.0.tgz#784b3b7c72d29e85d52dad705a846f8e2366a2fc"
  11016 + integrity sha512-DSXomaGHXAXIF9zPk7T9KG8m1bMNNiIHpeoClX44qxNHT7ryI6iDI8gkWdNfYVjxjcoXSTxKqKa11MA+C7mmAA==
11012 11017 dependencies:
11013   - "@windicss/plugin-utils" "0.10.4"
  11018 + "@windicss/plugin-utils" "0.11.0"
11014 11019 chalk "^4.1.0"
11015 11020 debug "^4.3.2"
11016   - windicss "^2.5.7"
  11021 + windicss "^2.5.8"
11017 11022  
11018 11023 vite@2.1.3:
11019 11024 version "2.1.3"
... ... @@ -11187,10 +11192,10 @@ which@^2.0.1, which@^2.0.2:
11187 11192 dependencies:
11188 11193 isexe "^2.0.0"
11189 11194  
11190   -windicss@^2.5.7:
11191   - version "2.5.7"
11192   - resolved "https://registry.npmjs.org/windicss/-/windicss-2.5.7.tgz#aea36568cfb412e1c673468496e920f21ef06086"
11193   - integrity sha512-gsWZkotmw9Hr7yZy2nJAp46pmgMO1wXFFa3rfLWm57KDM31U/AucksQnwZi7zxsKM9c6O/z/61/Uvv4J096zKA==
  11195 +windicss@^2.5.8:
  11196 + version "2.5.8"
  11197 + resolved "https://registry.npmjs.org/windicss/-/windicss-2.5.8.tgz#254980044de3031276062b90cfce53c13ee489bf"
  11198 + integrity sha512-zHkozdIqv1YTIGHBOHeFGsuZVTN5yAMz6FW5Bp8im9JZxSRZLOLKdJB0K75SL13iLHKXHrC1ukwJjjL8CohrUw==
11194 11199  
11195 11200 wmf@~1.0.1:
11196 11201 version "1.0.2"
... ...