Commit b96ea0753bfd769693a368cf1e3d8316688c0dcb

Authored by 无木
1 parent 6cbac4b7

feat(avatar-cropper): more props added

为头像剪裁组件添加value属性、onChange事件以及用于自定义上传按钮的属性;更新个人设置页的头像设置
CHANGELOG.zh_CN.md
@@ -7,6 +7,11 @@ @@ -7,6 +7,11 @@
7 - 增加`ignoreRoute`用于在`ROUTE_MAPPING`或`BACK`权限模式下仅生成菜单 7 - 增加`ignoreRoute`用于在`ROUTE_MAPPING`或`BACK`权限模式下仅生成菜单
8 - 增加`hidePathForChildren`配置,标识为子项目生成菜单时忽略本级`path` 8 - 增加`hidePathForChildren`配置,标识为子项目生成菜单时忽略本级`path`
9 - **TableAction** 新增`tooltip`配置,可以为按钮增加 tooltip 提示 9 - **TableAction** 新增`tooltip`配置,可以为按钮增加 tooltip 提示
  10 +- **CropperAvatar**
  11 + - 新增`value`用于设置当前头像
  12 + - 新增`onChange`用于接受头像剪裁并上传成功事件
  13 + - 新增`btnText`、`btnProps` 用于自定义上传按钮文案和属性
  14 + - 为剪裁`Modal`内的操作按钮添加工具提示
10 15
11 ### 🐛 Bug Fixes 16 ### 🐛 Bug Fixes
12 17
src/components/Cropper/src/CropperAvatar.vue
1 <template> 1 <template>
2 <div :class="getClass" :style="getStyle"> 2 <div :class="getClass" :style="getStyle">
3 <div :class="`${prefixCls}-image-wrapper`" :style="getImageWrapperStyle" @click="openModal"> 3 <div :class="`${prefixCls}-image-wrapper`" :style="getImageWrapperStyle" @click="openModal">
  4 + <div :class="`${prefixCls}-image-mask`" :style="getImageWrapperStyle">
  5 + <Icon
  6 + icon="ant-design:cloud-upload-outlined"
  7 + :size="getIconWidth"
  8 + :style="getImageWrapperStyle"
  9 + color="#d6d6d6"
  10 + />
  11 + </div>
4 <img :src="sourceValue" v-if="sourceValue" alt="avatar" /> 12 <img :src="sourceValue" v-if="sourceValue" alt="avatar" />
5 </div> 13 </div>
6 - <a-button :class="`${prefixCls}-upload-btn`" @click="openModal">  
7 - {{ t('component.cropper.selectImage') }} 14 + <a-button
  15 + :class="`${prefixCls}-upload-btn`"
  16 + @click="openModal"
  17 + v-if="showBtn"
  18 + v-bind="btnProps"
  19 + >
  20 + {{ btnText ? btnText : t('component.cropper.selectImage') }}
8 </a-button> 21 </a-button>
9 - <CopperModal @register="register" @uploadSuccess="handleUploadSuccess" :uploadApi="uploadApi" /> 22 +
  23 + <CopperModal
  24 + @register="register"
  25 + @uploadSuccess="handleUploadSuccess"
  26 + :uploadApi="uploadApi"
  27 + :src="sourceValue"
  28 + />
10 </div> 29 </div>
11 </template> 30 </template>
12 <script lang="ts"> 31 <script lang="ts">
13 - import { defineComponent, computed, CSSProperties, unref, ref } from 'vue'; 32 + import { defineComponent, computed, CSSProperties, unref, ref, watchEffect, watch } from 'vue';
14 import CopperModal from './CopperModal.vue'; 33 import CopperModal from './CopperModal.vue';
15 import { useDesign } from '/@/hooks/web/useDesign'; 34 import { useDesign } from '/@/hooks/web/useDesign';
16 import { useModal } from '/@/components/Modal'; 35 import { useModal } from '/@/components/Modal';
17 import { useMessage } from '/@/hooks/web/useMessage'; 36 import { useMessage } from '/@/hooks/web/useMessage';
18 import { useI18n } from '/@/hooks/web/useI18n'; 37 import { useI18n } from '/@/hooks/web/useI18n';
  38 + import type { ButtonProps } from '/@/components/Button';
  39 + import Icon from '/@/components/Icon';
19 40
20 const props = { 41 const props = {
21 width: { type: [String, Number], default: '200px' }, 42 width: { type: [String, Number], default: '200px' },
  43 + value: { type: String },
  44 + showBtn: { type: Boolean, default: true },
  45 + btnProps: { type: Object as ButtonProps },
  46 + btnText: { type: String, default: '' },
22 uploadApi: { type: Function as PropType<({ file: Blob, name: string }) => Promise<void>> }, 47 uploadApi: { type: Function as PropType<({ file: Blob, name: string }) => Promise<void>> },
23 }; 48 };
24 49
25 export default defineComponent({ 50 export default defineComponent({
26 name: 'CropperAvatar', 51 name: 'CropperAvatar',
27 - components: { CopperModal }, 52 + components: { CopperModal, Icon },
28 props, 53 props,
29 - setup(props) {  
30 - const sourceValue = ref(''); 54 + emits: ['update:value', 'change'],
  55 + setup(props, { emit }) {
  56 + const sourceValue = ref(props.value || '');
31 const { prefixCls } = useDesign('cropper-avatar'); 57 const { prefixCls } = useDesign('cropper-avatar');
32 const [register, { openModal }] = useModal(); 58 const [register, { openModal }] = useModal();
33 const { createMessage } = useMessage(); 59 const { createMessage } = useMessage();
@@ -37,14 +63,28 @@ @@ -37,14 +63,28 @@
37 63
38 const getWidth = computed(() => `${props.width}`.replace(/px/, '') + 'px'); 64 const getWidth = computed(() => `${props.width}`.replace(/px/, '') + 'px');
39 65
  66 + const getIconWidth = computed(() => parseInt(`${props.width}`.replace(/px/, '')) / 2 + 'px');
  67 +
40 const getStyle = computed((): CSSProperties => ({ width: unref(getWidth) })); 68 const getStyle = computed((): CSSProperties => ({ width: unref(getWidth) }));
41 69
42 const getImageWrapperStyle = computed( 70 const getImageWrapperStyle = computed(
43 (): CSSProperties => ({ width: unref(getWidth), height: unref(getWidth) }) 71 (): CSSProperties => ({ width: unref(getWidth), height: unref(getWidth) })
44 ); 72 );
45 73
  74 + watchEffect(() => {
  75 + sourceValue.value = props.value;
  76 + });
  77 +
  78 + watch(
  79 + () => sourceValue.value,
  80 + (v: string) => {
  81 + emit('update:value', v);
  82 + }
  83 + );
  84 +
46 function handleUploadSuccess({ source }) { 85 function handleUploadSuccess({ source }) {
47 sourceValue.value = source; 86 sourceValue.value = source;
  87 + emit('change', source);
48 createMessage.success(t('component.cropper.uploadSuccess')); 88 createMessage.success(t('component.cropper.uploadSuccess'));
49 } 89 }
50 90
@@ -53,6 +93,7 @@ @@ -53,6 +93,7 @@
53 prefixCls, 93 prefixCls,
54 register, 94 register,
55 openModal, 95 openModal,
  96 + getIconWidth,
56 sourceValue, 97 sourceValue,
57 getClass, 98 getClass,
58 getImageWrapperStyle, 99 getImageWrapperStyle,
@@ -82,6 +123,27 @@ @@ -82,6 +123,27 @@
82 } 123 }
83 } 124 }
84 125
  126 + &-image-mask {
  127 + opacity: 0;
  128 + position: absolute;
  129 + width: inherit;
  130 + height: inherit;
  131 + border-radius: inherit;
  132 + border: inherit;
  133 + background: rgba(0, 0, 0, 0.4);
  134 + cursor: pointer;
  135 + -webkit-transition: opacity 0.4s;
  136 + transition: opacity 0.4s;
  137 +
  138 + ::v-deep(svg) {
  139 + margin: auto;
  140 + }
  141 + }
  142 +
  143 + &-image-mask:hover {
  144 + opacity: 40;
  145 + }
  146 +
85 &-upload-btn { 147 &-upload-btn {
86 margin: 10px auto; 148 margin: 10px auto;
87 } 149 }
src/views/demo/comp/cropper/index.vue
1 <template> 1 <template>
2 <PageWrapper title="图片裁剪示例" content="需要开启测试接口服务才能进行上传测试!"> 2 <PageWrapper title="图片裁剪示例" content="需要开启测试接口服务才能进行上传测试!">
3 <CollapseContainer title="头像裁剪"> 3 <CollapseContainer title="头像裁剪">
4 - <CropperAvatar :uploadApi="uploadApi" /> 4 + <CropperAvatar :uploadApi="uploadApi" :value="avatar" />
5 </CollapseContainer> 5 </CollapseContainer>
6 6
7 <CollapseContainer title="矩形裁剪" class="my-4"> 7 <CollapseContainer title="矩形裁剪" class="my-4">
@@ -9,7 +9,7 @@ @@ -9,7 +9,7 @@
9 <div class="cropper-container mr-10"> 9 <div class="cropper-container mr-10">
10 <CropperImage ref="refCropper" :src="img" @cropend="handleCropend" style="width: 40vw" /> 10 <CropperImage ref="refCropper" :src="img" @cropend="handleCropend" style="width: 40vw" />
11 </div> 11 </div>
12 - <img :src="cropperImg" class="croppered" v-if="cropperImg" /> 12 + <img :src="cropperImg" class="croppered" v-if="cropperImg" alt="" />
13 </div> 13 </div>
14 <p v-if="cropperImg">裁剪后图片信息:{{ info }}</p> 14 <p v-if="cropperImg">裁剪后图片信息:{{ info }}</p>
15 </CollapseContainer> 15 </CollapseContainer>
@@ -34,10 +34,11 @@ @@ -34,10 +34,11 @@
34 <script lang="ts"> 34 <script lang="ts">
35 import { defineComponent, ref } from 'vue'; 35 import { defineComponent, ref } from 'vue';
36 import { PageWrapper } from '/@/components/Page'; 36 import { PageWrapper } from '/@/components/Page';
37 - import { CollapseContainer } from '/@/components/Container/index'; 37 + import { CollapseContainer } from '/@/components/Container';
38 import { CropperImage, CropperAvatar } from '/@/components/Cropper'; 38 import { CropperImage, CropperAvatar } from '/@/components/Cropper';
39 import { uploadApi } from '/@/api/sys/upload'; 39 import { uploadApi } from '/@/api/sys/upload';
40 import img from '/@/assets/images/header.jpg'; 40 import img from '/@/assets/images/header.jpg';
  41 + import { useUserStore } from '/@/store/modules/user';
41 42
42 export default defineComponent({ 43 export default defineComponent({
43 components: { 44 components: {
@@ -51,7 +52,8 @@ @@ -51,7 +52,8 @@
51 const cropperImg = ref(''); 52 const cropperImg = ref('');
52 const circleInfo = ref(''); 53 const circleInfo = ref('');
53 const circleImg = ref(''); 54 const circleImg = ref('');
54 - 55 + const userStore = useUserStore();
  56 + const avatar = ref(userStore.getUserInfo?.avatar || '');
55 function handleCropend({ imgBase64, imgInfo }) { 57 function handleCropend({ imgBase64, imgInfo }) {
56 info.value = imgInfo; 58 info.value = imgInfo;
57 cropperImg.value = imgBase64; 59 cropperImg.value = imgBase64;
@@ -70,6 +72,7 @@ @@ -70,6 +72,7 @@
70 circleImg, 72 circleImg,
71 handleCropend, 73 handleCropend,
72 handleCircleCropend, 74 handleCircleCropend,
  75 + avatar,
73 uploadApi, 76 uploadApi,
74 }; 77 };
75 }, 78 },
src/views/demo/page/account/setting/BaseSetting.vue
@@ -7,10 +7,14 @@ @@ -7,10 +7,14 @@
7 <a-col :span="10"> 7 <a-col :span="10">
8 <div class="change-avatar"> 8 <div class="change-avatar">
9 <div class="mb-2"> 头像 </div> 9 <div class="mb-2"> 头像 </div>
10 - <img width="140" :src="avatar" />  
11 - <Upload :showUploadList="false">  
12 - <Button class="ml-5"> <Icon icon="feather:upload" />更换头像 </Button>  
13 - </Upload> 10 + <CropperAvatar
  11 + :uploadApi="uploadApi"
  12 + :value="avatar"
  13 + btnText="更换头像"
  14 + :btnProps="{ preIcon: 'ant-design:cloud-upload-outlined' }"
  15 + @change="updateAvatar"
  16 + width="150"
  17 + />
14 </div> 18 </div>
15 </a-col> 19 </a-col>
16 </a-row> 20 </a-row>
@@ -18,11 +22,11 @@ @@ -18,11 +22,11 @@
18 </CollapseContainer> 22 </CollapseContainer>
19 </template> 23 </template>
20 <script lang="ts"> 24 <script lang="ts">
21 - import { Button, Upload, Row, Col } from 'ant-design-vue'; 25 + import { Button, Row, Col } from 'ant-design-vue';
22 import { computed, defineComponent, onMounted } from 'vue'; 26 import { computed, defineComponent, onMounted } from 'vue';
23 import { BasicForm, useForm } from '/@/components/Form/index'; 27 import { BasicForm, useForm } from '/@/components/Form/index';
24 - import { CollapseContainer } from '/@/components/Container/index';  
25 - import Icon from '/@/components/Icon/index'; 28 + import { CollapseContainer } from '/@/components/Container';
  29 + import { CropperAvatar } from '/@/components/Cropper';
26 30
27 import { useMessage } from '/@/hooks/web/useMessage'; 31 import { useMessage } from '/@/hooks/web/useMessage';
28 32
@@ -30,16 +34,16 @@ @@ -30,16 +34,16 @@
30 import { accountInfoApi } from '/@/api/demo/account'; 34 import { accountInfoApi } from '/@/api/demo/account';
31 import { baseSetschemas } from './data'; 35 import { baseSetschemas } from './data';
32 import { useUserStore } from '/@/store/modules/user'; 36 import { useUserStore } from '/@/store/modules/user';
  37 + import { uploadApi } from '/@/api/sys/upload';
33 38
34 export default defineComponent({ 39 export default defineComponent({
35 components: { 40 components: {
36 BasicForm, 41 BasicForm,
37 CollapseContainer, 42 CollapseContainer,
38 Button, 43 Button,
39 - Upload,  
40 - Icon,  
41 - [Row.name]: Row,  
42 - [Col.name]: Col, 44 + ARow: Row,
  45 + ACol: Col,
  46 + CropperAvatar,
43 }, 47 },
44 setup() { 48 setup() {
45 const { createMessage } = useMessage(); 49 const { createMessage } = useMessage();
@@ -61,9 +65,17 @@ @@ -61,9 +65,17 @@
61 return avatar || headerImg; 65 return avatar || headerImg;
62 }); 66 });
63 67
  68 + function updateAvatar(src: string) {
  69 + const userinfo = userStore.getUserInfo;
  70 + userinfo.avatar = src;
  71 + userStore.setUserInfo(userinfo);
  72 + }
  73 +
64 return { 74 return {
65 avatar, 75 avatar,
66 register, 76 register,
  77 + uploadApi,
  78 + updateAvatar,
67 handleSubmit: () => { 79 handleSubmit: () => {
68 createMessage.success('更新成功!'); 80 createMessage.success('更新成功!');
69 }, 81 },