Merge branch 'main' into fix

This commit is contained in:
xingyu
2026-01-19 10:54:43 +08:00
committed by GitHub
22 changed files with 1587 additions and 119 deletions

View File

@@ -0,0 +1,138 @@
<script lang="ts" setup>
import type { UploadChangeParam } from 'ant-design-vue';
import { ref } from 'vue';
import { Page, VCropper } from '@vben/common-ui';
import { Button, Card, Select, Upload } from 'ant-design-vue';
const options = [
{ label: '1:1', value: '1:1' },
{ label: '16:9', value: '16:9' },
{ label: '不限制', value: '' },
];
const cropperRef = ref<InstanceType<typeof VCropper>>();
const cropLoading = ref(false);
const validAspectRatio = ref<string | undefined>('1:1');
const imgUrl = ref('');
const cropperImg = ref();
const selectImgFile = (event: UploadChangeParam) => {
const file = event.fileList[0]?.originFileObj;
if (!file) return;
if (!file.type.startsWith('image/')) {
console.error('请上传图片文件');
return;
}
const reader = new FileReader();
reader.addEventListener('load', (e) => {
imgUrl.value = e.target?.result as string;
});
reader.addEventListener('error', () => {
console.error('Failed to read file');
});
reader.readAsDataURL(file);
};
const cropImage = async () => {
if (!cropperRef.value) return;
cropLoading.value = true;
try {
cropperImg.value = await cropperRef.value.getCropImage();
} catch (error) {
console.error('图片裁剪失败:', error);
} finally {
cropLoading.value = false;
}
};
/**
* 下载图片
*/
const downloadImage = () => {
if (!cropperImg.value) return;
const link = document.createElement('a');
link.download = `cropped-image-${Date.now()}.png`;
link.href = cropperImg.value;
link.click();
};
</script>
<template>
<Page
title="VCropper 图片裁剪"
description="VCropper是一个图片裁剪组件提供基础的图片裁剪功能。"
>
<Card>
<div class="image-cropper-container">
<div class="cropper-ratio-display">
<label class="ratio-label">当前裁剪比例</label>
<Select
class="w-24"
v-model:value="validAspectRatio"
:options="options"
/>
<Upload
:max-count="1"
:show-upload-list="false"
:before-upload="() => false"
@change="selectImgFile"
>
<Button>上传图片</Button>
</Upload>
</div>
<div v-if="imgUrl" class="cropper-main-wrapper">
<VCropper
ref="cropperRef"
:img="imgUrl"
:aspect-ratio="validAspectRatio"
:width="600"
:height="600"
/>
<!-- 操作按钮组 -->
<div class="cropper-btn-group">
<Button :loading="cropLoading" @click="cropImage" type="primary">
裁剪
</Button>
<Button v-if="cropperImg" @click="downloadImage" danger>
下载图片
</Button>
</div>
<!-- 裁剪预览 -->
<img
v-if="cropperImg"
class="h-full w-80"
:src="cropperImg"
alt="裁剪预览"
/>
</div>
</div>
</Card>
</Page>
</template>
<style scoped>
/* 比例展示区域 */
.cropper-ratio-display {
@apply my-2.5 flex items-center justify-start gap-4;
}
.ratio-label {
@apply text-sm font-medium;
}
/* 主裁剪区域 */
.cropper-main-wrapper {
@apply flex items-center gap-4;
}
.cropper-btn-group {
@apply flex flex-col gap-2;
}
</style>

View File

@@ -348,6 +348,15 @@ const [BaseForm, baseFormApi] = useVbenForm({
showUploadList: true,
// 上传列表的内建样式,支持四种基本样式 text, picture, picture-card 和 picture-circle
listType: 'picture-card',
// onChange事件已被重写如需自定义请在此基础上扩展
handleChange: ({ file }: { file: UploadFile }) => {
const { name, status } = file;
if (status === 'done') {
message.success(`${name} ${$t('examples.form.upload-success')}`);
} else if (status === 'error') {
message.error(`${name} ${$t('examples.form.upload-fail')}`);
}
},
},
fieldName: 'files',
label: $t('examples.form.file'),
@@ -358,6 +367,28 @@ const [BaseForm, baseFormApi] = useVbenForm({
},
rules: 'selectRequired',
},
{
component: 'Upload',
componentProps: {
accept: '.png,.jpg,.jpeg',
customRequest: upload_file,
maxCount: 1,
maxSize: 2,
listType: 'picture-card',
// 是否启用图片裁剪(多选或者非图片不唤起裁剪框)
crop: true,
// 裁剪比例
aspectRatio: '1:1',
},
fieldName: 'cropImage',
label: $t('examples.form.crop-image'),
renderComponentContent: () => {
return {
default: () => $t('examples.form.upload-image'),
};
},
rules: 'selectRequired',
},
],
// 大屏一行显示3个中屏一行显示2个小屏一行显示1个
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
@@ -365,13 +396,20 @@ const [BaseForm, baseFormApi] = useVbenForm({
function onSubmit(values: Record<string, any>) {
const files = toRaw(values.files) as UploadFile[];
const cropImage = (toRaw(values.cropImage) ?? []) as UploadFile[];
const doneFiles = files.filter((file) => file.status === 'done');
const failedFiles = files.filter((file) => file.status !== 'done');
const doneCrop = cropImage.filter((file) => file.status === 'done');
const failedCrop = cropImage.filter((file) => file.status !== 'done');
const msg = [
...doneFiles.map((file) => file.response?.url || file.url),
...failedFiles.map((file) => file.name),
].join(', ');
const msgCrop = [
...doneCrop.map((file) => file.response?.url || file.url),
...failedCrop.map((file) => file.name),
].join(', ');
if (failedFiles.length === 0) {
message.success({
@@ -383,8 +421,19 @@ function onSubmit(values: Record<string, any>) {
});
return;
}
if (doneCrop.length > 0 && failedCrop.length === 0) {
message.success({
content: `${$t('examples.form.upload-urls')}: ${msgCrop}`,
});
} else if (failedCrop.length > 0) {
message.error({
content: `${$t('examples.form.upload-error')}: ${msgCrop}`,
});
return;
}
// 如果需要可提交前替换为需要的urls
values.files = doneFiles.map((file) => file.response?.url || file.url);
values.cropImage = doneCrop.map((file) => file.response?.url || file.url);
message.success({
content: `form values: ${JSON.stringify(values)}`,
});