Browse Source

wip: add cropper iamge component

Vben 4 năm trước cách đây
mục cha
commit
2e11ea677b

+ 3 - 2
package.json

@@ -37,6 +37,7 @@
     "ant-design-vue": "^2.1.2",
     "apexcharts": "^3.26.0",
     "axios": "^0.21.1",
+    "cropperjs": "^1.5.11",
     "crypto-js": "^4.0.0",
     "echarts": "^5.0.2",
     "lodash-es": "^4.17.21",
@@ -91,7 +92,7 @@
     "esno": "^0.5.0",
     "fs-extra": "^9.1.0",
     "http-server": "^0.12.3",
-    "husky": "^5.2.0",
+    "husky": "^6.0.0",
     "inquirer": "^8.0.0",
     "is-ci": "^3.0.0",
     "less": "^4.1.1",
@@ -117,7 +118,7 @@
     "vite-plugin-style-import": "^0.9.1",
     "vite-plugin-svg-icons": "^0.4.0",
     "vite-plugin-theme": "^0.5.0",
-    "vite-plugin-windicss": "0.10.4",
+    "vite-plugin-windicss": "0.11.0",
     "vue-eslint-parser": "^7.6.0"
   },
   "resolutions": {

+ 4 - 0
src/components/Cropper/index.ts

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

+ 15 - 0
src/components/Cropper/src/AvatarCropper.vue

@@ -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>

+ 100 - 0
src/components/Cropper/src/index.vue

@@ -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>

+ 16 - 12
src/components/Dropdown/src/Dropdown.vue

@@ -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 }),
       };
     },
   });

+ 10 - 4
src/components/Table/src/components/TableAction.vue

@@ -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>

+ 2 - 1
src/locales/lang/en/routes/demo/comp.ts

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

+ 2 - 1
src/locales/lang/zh_CN/routes/demo/comp.ts

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

+ 10 - 1
src/router/menus/modules/demo/comp.ts

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

+ 8 - 0
src/router/routes/modules/demo/comp.ts

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

+ 22 - 0
src/views/demo/comp/cropper/index.vue

@@ -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>

+ 1 - 1
src/views/demo/page/account/setting/BaseSetting.vue

@@ -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>

+ 4 - 1
src/views/demo/table/FixedColumn.vue

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

+ 1 - 1
vite.config.ts

@@ -85,7 +85,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
         'ant-design-vue/es/locale/en_US',
         'moment/dist/locale/eu',
       ],
-      exclude: ['vue-demi'],
+      exclude: ['vue-demi', 'consolidate'],
     },
   };
 };

+ 24 - 19
yarn.lock

@@ -2077,10 +2077,10 @@
   dependencies:
     vue-demi latest
 
-"@windicss/plugin-utils@0.10.4":
-  version "0.10.4"
-  resolved "https://registry.npmjs.org/@windicss/plugin-utils/-/plugin-utils-0.10.4.tgz#ed9163b09e030f7358cc4742b1f2b6c92d084d5d"
-  integrity sha512-jQu69qzA56Lv18OK8U4mUTDV17st4EdPawQuaRG2VNK+ZEQWYsMNnqGxhzDTl/NhWTGCcTb3D6mlFPNo0QDOFg==
+"@windicss/plugin-utils@0.11.0":
+  version "0.11.0"
+  resolved "https://registry.npmjs.org/@windicss/plugin-utils/-/plugin-utils-0.11.0.tgz#142fe810c9cf71719074518295f65bd01f0ac895"
+  integrity sha512-NIBJ7/wG8Ty///Qxktefud4OI18XjZkycW6DSkWV7++aYyZOdDgGRn493pU+5QZuXeXU8iNR6NKZDtFNcYD7kQ==
   dependencies:
     debug "^4.3.2"
     fast-glob "^3.2.5"
@@ -2088,7 +2088,7 @@
     micromatch "^4.0.2"
     pirates "^4.0.1"
     sucrase "^3.17.1"
-    windicss "^2.5.7"
+    windicss "^2.5.8"
 
 "@zxcvbn-ts/core@^0.3.0":
   version "0.3.0"
@@ -3582,6 +3582,11 @@ create-require@^1.1.0:
   resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
   integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
 
+cropperjs@^1.5.11:
+  version "1.5.11"
+  resolved "https://registry.npmjs.org/cropperjs/-/cropperjs-1.5.11.tgz#502ae6d8ca098b124de6813601cca70015879fc0"
+  integrity sha512-SJUeBBhtNBnnn+UrLKluhFRIXLJn7XFPv8QN1j49X5t+BIMwkgvDev541f96bmu8Xe0TgCx3gON22KmY/VddaA==
+
 cross-env@^7.0.3:
   version "7.0.3"
   resolved "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf"
@@ -5698,10 +5703,10 @@ husky@^5.1.3:
   resolved "https://registry.npmjs.org/husky/-/husky-5.1.3.tgz#1a0645a4fe3ffc006c4d0d8bd0bcb4c98787cc9d"
   integrity sha512-fbNJ+Gz5wx2LIBtMweJNY1D7Uc8p1XERi5KNRMccwfQA+rXlxWNSdUxswo0gT8XqxywTIw7Ywm/F4v/O35RdMg==
 
-husky@^5.2.0:
-  version "5.2.0"
-  resolved "https://registry.npmjs.org/husky/-/husky-5.2.0.tgz#fc5e1c2300d34855d47de4753607d00943fc0802"
-  integrity sha512-AM8T/auHXRBxlrfPVLKP6jt49GCM2Zz47m8G3FOMsLmTv8Dj/fKVWE0Rh2d4Qrvmy131xEsdQnb3OXRib67PGg==
+husky@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.npmjs.org/husky/-/husky-6.0.0.tgz#810f11869adf51604c32ea577edbc377d7f9319e"
+  integrity sha512-SQS2gDTB7tBN486QSoKPKQItZw97BMOd+Kdb6ghfpBc0yXyzrddI0oDV5MkDAbuB4X2mO3/nj60TRMcYxwzZeQ==
 
 iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4:
   version "0.4.24"
@@ -11005,15 +11010,15 @@ vite-plugin-theme@^0.5.0:
     tinycolor2 "^1.4.2"
     ts-jest "^26.5.3"
 
-vite-plugin-windicss@0.10.4:
-  version "0.10.4"
-  resolved "https://registry.npmjs.org/vite-plugin-windicss/-/vite-plugin-windicss-0.10.4.tgz#e93577111ea0a55befbe4e2aa2e596f55f6b74b2"
-  integrity sha512-P7alH2dGGw3OTgjs9yZG2w0i+o1HKD8PChwhm2ftP+lLCe1xDL3LReheuRil9p2xPYzrVouER2YTbIdLUEThrQ==
+vite-plugin-windicss@0.11.0:
+  version "0.11.0"
+  resolved "https://registry.npmjs.org/vite-plugin-windicss/-/vite-plugin-windicss-0.11.0.tgz#784b3b7c72d29e85d52dad705a846f8e2366a2fc"
+  integrity sha512-DSXomaGHXAXIF9zPk7T9KG8m1bMNNiIHpeoClX44qxNHT7ryI6iDI8gkWdNfYVjxjcoXSTxKqKa11MA+C7mmAA==
   dependencies:
-    "@windicss/plugin-utils" "0.10.4"
+    "@windicss/plugin-utils" "0.11.0"
     chalk "^4.1.0"
     debug "^4.3.2"
-    windicss "^2.5.7"
+    windicss "^2.5.8"
 
 vite@2.1.3:
   version "2.1.3"
@@ -11187,10 +11192,10 @@ which@^2.0.1, which@^2.0.2:
   dependencies:
     isexe "^2.0.0"
 
-windicss@^2.5.7:
-  version "2.5.7"
-  resolved "https://registry.npmjs.org/windicss/-/windicss-2.5.7.tgz#aea36568cfb412e1c673468496e920f21ef06086"
-  integrity sha512-gsWZkotmw9Hr7yZy2nJAp46pmgMO1wXFFa3rfLWm57KDM31U/AucksQnwZi7zxsKM9c6O/z/61/Uvv4J096zKA==
+windicss@^2.5.8:
+  version "2.5.8"
+  resolved "https://registry.npmjs.org/windicss/-/windicss-2.5.8.tgz#254980044de3031276062b90cfce53c13ee489bf"
+  integrity sha512-zHkozdIqv1YTIGHBOHeFGsuZVTN5yAMz6FW5Bp8im9JZxSRZLOLKdJB0K75SL13iLHKXHrC1ukwJjjL8CohrUw==
 
 wmf@~1.0.1:
   version "1.0.2"