Commit b96ea0753bfd769693a368cf1e3d8316688c0dcb

Authored by 无木
1 parent 6cbac4b7

feat(avatar-cropper): more props added

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