Commit b96ea0753bfd769693a368cf1e3d8316688c0dcb
1 parent
6cbac4b7
feat(avatar-cropper): more props added
为头像剪裁组件添加value属性、onChange事件以及用于自定义上传按钮的属性;更新个人设置页的头像设置
Showing
4 changed files
with
104 additions
and
22 deletions
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 | }, |