mirror of https://github.com/halo-dev/halo
feat: add image editor feature for attachment upload component (#5585)
#### What type of PR is this? /area ui /kind feature /milestone 2.14.x #### What this PR does / why we need it: 为上传附件的组件添加基本的图片编辑功能。 <img width="747" alt="image" src="https://github.com/halo-dev/halo/assets/21301288/6816e045-ae4a-4d26-b3a4-23494fb39d5f"> #### Which issue(s) this PR fixes: Fixes #5583 #### Does this PR introduce a user-facing change? ```release-note 为上传附件的组件添加基本的图片编辑功能。 ```pull/5590/head
parent
499b9eabb5
commit
2af92396d4
|
@ -1,10 +1,10 @@
|
|||
<script lang="ts" setup>
|
||||
import {
|
||||
VModal,
|
||||
IconAddCircle,
|
||||
VAlert,
|
||||
VDropdown,
|
||||
VDropdownItem,
|
||||
VModal,
|
||||
} from "@halo-dev/components";
|
||||
import { ref, watch } from "vue";
|
||||
import type { Policy, PolicyTemplate } from "@halo-dev/api-client";
|
||||
|
@ -119,6 +119,7 @@ watch(
|
|||
:body-class="['!p-0']"
|
||||
:visible="visible"
|
||||
:width="650"
|
||||
:centered="false"
|
||||
:title="$t('core.attachment.upload_modal.title')"
|
||||
@update:visible="onVisibleChange"
|
||||
>
|
||||
|
|
|
@ -64,6 +64,7 @@
|
|||
"@uppy/dashboard": "^3.7.1",
|
||||
"@uppy/drag-drop": "^3.0.3",
|
||||
"@uppy/file-input": "^3.0.4",
|
||||
"@uppy/image-editor": "^2.4.4",
|
||||
"@uppy/locales": "^3.5.0",
|
||||
"@uppy/progress-bar": "^3.0.4",
|
||||
"@uppy/status-bar": "^3.2.5",
|
||||
|
|
|
@ -89,6 +89,9 @@ importers:
|
|||
'@uppy/file-input':
|
||||
specifier: ^3.0.4
|
||||
version: 3.0.4(@uppy/core@3.8.0)
|
||||
'@uppy/image-editor':
|
||||
specifier: ^2.4.4
|
||||
version: 2.4.4(@uppy/core@3.8.0)
|
||||
'@uppy/locales':
|
||||
specifier: ^3.5.0
|
||||
version: 3.5.0
|
||||
|
@ -5618,7 +5621,7 @@ packages:
|
|||
ts-dedent: 2.2.0
|
||||
type-fest: 2.19.0
|
||||
vue: 3.4.19(typescript@5.3.3)
|
||||
vue-component-type-helpers: 2.0.6
|
||||
vue-component-type-helpers: 2.0.7
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
|
@ -6698,6 +6701,17 @@ packages:
|
|||
preact: 10.11.2
|
||||
dev: false
|
||||
|
||||
/@uppy/image-editor@2.4.4(@uppy/core@3.8.0):
|
||||
resolution: {integrity: sha512-ASjv2Mh4mhlhkh1isDvIj3tlbERG77jo/ETwtajRSERGV6Ga9w1QGG7vL19bmNW9McHPFMZvOKNojAgv4SAtPg==}
|
||||
peerDependencies:
|
||||
'@uppy/core': ^3.9.3
|
||||
dependencies:
|
||||
'@uppy/core': 3.8.0
|
||||
'@uppy/utils': 5.7.4
|
||||
cropperjs: 1.5.7
|
||||
preact: 10.11.2
|
||||
dev: false
|
||||
|
||||
/@uppy/informer@3.0.4(@uppy/core@3.8.0):
|
||||
resolution: {integrity: sha512-gzocdxn8qAFsW2EryehwjghladaBgv6Isjte53FTBV7o/vjaHPP6huKGbYpljyuQi8i9V+KrmvNGslofssgJ4g==}
|
||||
peerDependencies:
|
||||
|
@ -6777,6 +6791,13 @@ packages:
|
|||
preact: 10.11.2
|
||||
dev: false
|
||||
|
||||
/@uppy/utils@5.7.4:
|
||||
resolution: {integrity: sha512-0Xsr7Xqdrb9mgfY3hi0YdhIaAxUw6qJasAflNMwNsyLGt3kH4pLfQHucolBKfWglVGtk1vfb49hZYvJGpcpzYA==}
|
||||
dependencies:
|
||||
lodash: 4.17.21
|
||||
preact: 10.11.2
|
||||
dev: false
|
||||
|
||||
/@uppy/vue@1.1.0(@uppy/core@3.8.0)(@uppy/dashboard@3.7.1)(@uppy/drag-drop@3.0.3)(@uppy/file-input@3.0.4)(@uppy/progress-bar@3.0.4)(@uppy/status-bar@3.2.5)(vue@3.4.19):
|
||||
resolution: {integrity: sha512-xUKjLq8R0VBQHPDBb/f/bCL8+f51qSrTqYS6JXA4oFQ3QXICQN72E3rAPOyEwkiy+45JJMHA6uKeC+v6H9dcVg==}
|
||||
peerDependencies:
|
||||
|
@ -8763,6 +8784,10 @@ packages:
|
|||
resolution: {integrity: sha512-by7jKAo73y5/Do0K6sxdTKHgndY0NMjG2bEdgeJxycbcmHuCiMXqw8sxy5C5Y5WTOTcDGmbT7Sr5CgKOXR06OA==}
|
||||
dev: false
|
||||
|
||||
/cropperjs@1.5.7:
|
||||
resolution: {integrity: sha512-sGj+G/ofKh+f6A4BtXLJwtcKJgMUsXYVUubfTo9grERiDGXncttefmue/fyQFvn8wfdyoD1KhDRYLfjkJFl0yw==}
|
||||
dev: false
|
||||
|
||||
/cross-spawn@5.1.0:
|
||||
resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==}
|
||||
dependencies:
|
||||
|
@ -17566,8 +17591,8 @@ packages:
|
|||
resolution: {integrity: sha512-0vOfAtI67UjeO1G6UiX5Kd76CqaQ67wrRZiOe7UAb9Jm6GzlUr/fC7CV90XfwapJRjpCMaZFhv1V0ajWRmE9Dg==}
|
||||
dev: true
|
||||
|
||||
/vue-component-type-helpers@2.0.6:
|
||||
resolution: {integrity: sha512-qdGXCtoBrwqk1BT6r2+1Wcvl583ZVkuSZ3or7Y1O2w5AvWtlvvxwjGhmz5DdPJS9xqRdDlgTJ/38ehWnEi0tFA==}
|
||||
/vue-component-type-helpers@2.0.7:
|
||||
resolution: {integrity: sha512-7e12Evdll7JcTIocojgnCgwocX4WzIYStGClBQ+QuWPinZo/vQolv2EMq4a3lg16TKfwWafLimG77bxb56UauA==}
|
||||
dev: true
|
||||
|
||||
/vue-demi@0.13.11(vue@3.4.19):
|
||||
|
|
|
@ -2,9 +2,11 @@
|
|||
import { Dashboard } from "@uppy/vue";
|
||||
import "@uppy/core/dist/style.css";
|
||||
import "@uppy/dashboard/dist/style.css";
|
||||
import Uppy, { type SuccessResponse } from "@uppy/core";
|
||||
import type { Restrictions } from "@uppy/core";
|
||||
import Uppy, { type SuccessResponse } from "@uppy/core";
|
||||
import XHRUpload from "@uppy/xhr-upload";
|
||||
import ImageEditor from "@uppy/image-editor";
|
||||
import "@uppy/image-editor/dist/style.min.css";
|
||||
import zh_CN from "@uppy/locales/lib/zh_CN";
|
||||
import zh_TW from "@uppy/locales/lib/zh_TW";
|
||||
import en_US from "@uppy/locales/lib/en_US";
|
||||
|
@ -62,38 +64,61 @@ const uppy = computed(() => {
|
|||
meta: props.meta,
|
||||
restrictions: props.restrictions,
|
||||
autoProceed: props.autoProceed,
|
||||
}).use(XHRUpload, {
|
||||
endpoint: `${import.meta.env.VITE_API_URL}${props.endpoint}`,
|
||||
allowedMetaFields: props.allowedMetaFields,
|
||||
withCredentials: true,
|
||||
formData: true,
|
||||
fieldName: props.name,
|
||||
method: props.method,
|
||||
limit: 5,
|
||||
timeout: 0,
|
||||
getResponseError: (responseText: string, response: unknown) => {
|
||||
try {
|
||||
const response = JSON.parse(responseText);
|
||||
if (typeof response === "object" && response && response) {
|
||||
const { title, detail } = (response || {}) as ProblemDetail;
|
||||
const message = [title, detail].filter(Boolean).join(": ");
|
||||
})
|
||||
.use(XHRUpload, {
|
||||
endpoint: `${import.meta.env.VITE_API_URL}${props.endpoint}`,
|
||||
allowedMetaFields: props.allowedMetaFields,
|
||||
withCredentials: true,
|
||||
formData: true,
|
||||
fieldName: props.name,
|
||||
method: props.method,
|
||||
limit: 5,
|
||||
timeout: 0,
|
||||
getResponseError: (responseText: string, response: unknown) => {
|
||||
try {
|
||||
const response = JSON.parse(responseText);
|
||||
if (typeof response === "object" && response && response) {
|
||||
const { title, detail } = (response || {}) as ProblemDetail;
|
||||
const message = [title, detail].filter(Boolean).join(": ");
|
||||
|
||||
if (message) {
|
||||
Toast.error(message, { duration: 5000 });
|
||||
if (message) {
|
||||
Toast.error(message, { duration: 5000 });
|
||||
|
||||
return new Error(message);
|
||||
return new Error(message);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
const responseBody = response as XMLHttpRequest;
|
||||
const { status, statusText } = responseBody;
|
||||
const defaultMessage = [status, statusText].join(": ");
|
||||
Toast.error(defaultMessage, { duration: 5000 });
|
||||
return new Error(defaultMessage);
|
||||
}
|
||||
} catch (e) {
|
||||
const responseBody = response as XMLHttpRequest;
|
||||
const { status, statusText } = responseBody;
|
||||
const defaultMessage = [status, statusText].join(": ");
|
||||
Toast.error(defaultMessage, { duration: 5000 });
|
||||
return new Error(defaultMessage);
|
||||
}
|
||||
return new Error("Internal Server Error");
|
||||
},
|
||||
});
|
||||
return new Error("Internal Server Error");
|
||||
},
|
||||
})
|
||||
.use(ImageEditor, {
|
||||
locale: {
|
||||
strings: {
|
||||
revert: i18n.global.t("core.components.uppy.image_editor.revert"),
|
||||
rotate: i18n.global.t("core.components.uppy.image_editor.rotate"),
|
||||
zoomIn: i18n.global.t("core.components.uppy.image_editor.zoom_in"),
|
||||
zoomOut: i18n.global.t("core.components.uppy.image_editor.zoom_out"),
|
||||
flipHorizontal: i18n.global.t(
|
||||
"core.components.uppy.image_editor.flip_horizontal"
|
||||
),
|
||||
aspectRatioSquare: i18n.global.t(
|
||||
"core.components.uppy.image_editor.aspect_ratio_square"
|
||||
),
|
||||
aspectRatioLandscape: i18n.global.t(
|
||||
"core.components.uppy.image_editor.aspect_ratio_landscape"
|
||||
),
|
||||
aspectRatioPortrait: i18n.global.t(
|
||||
"core.components.uppy.image_editor.aspect_ratio_portrait"
|
||||
),
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
uppy.value.on("upload-success", (_, response: SuccessResponse) => {
|
||||
|
|
|
@ -1462,6 +1462,16 @@ core:
|
|||
editor_provider_selector:
|
||||
tooltips:
|
||||
disallow: The content format is different and cannot be switched
|
||||
uppy:
|
||||
image_editor:
|
||||
revert: "Revert"
|
||||
rotate: "Rotate"
|
||||
zoom_in: "Zoom in"
|
||||
zoom_out: "Zoom out"
|
||||
flip_horizontal: "Flip horizontal"
|
||||
aspect_ratio_square: "Crop square"
|
||||
aspect_ratio_landscape: "Crop landscape (16:9)"
|
||||
aspect_ratio_portrait: "Crop portrait (9:16)"
|
||||
composables:
|
||||
content_cache:
|
||||
toast_recovered: Recovered unsaved content from cache
|
||||
|
|
|
@ -1408,6 +1408,16 @@ core:
|
|||
editor_provider_selector:
|
||||
tooltips:
|
||||
disallow: 内容格式不同,无法切换
|
||||
uppy:
|
||||
image_editor:
|
||||
revert: "恢复"
|
||||
rotate: "旋转"
|
||||
zoom_in: "放大"
|
||||
zoom_out: "缩小"
|
||||
flip_horizontal: "水平翻转"
|
||||
aspect_ratio_square: "裁剪为正方形"
|
||||
aspect_ratio_landscape: "裁剪为横向 (16:9)"
|
||||
aspect_ratio_portrait: "裁剪为纵向 (9:16)"
|
||||
composables:
|
||||
content_cache:
|
||||
toast_recovered: 已从缓存中恢复未保存的内容
|
||||
|
|
|
@ -1374,6 +1374,16 @@ core:
|
|||
editor_provider_selector:
|
||||
tooltips:
|
||||
disallow: 內容格式不同,無法切換
|
||||
uppy:
|
||||
image_editor:
|
||||
revert: "還原"
|
||||
rotate: "旋轉"
|
||||
zoom_in: "放大"
|
||||
zoom_out: "縮小"
|
||||
flip_horizontal: "水平翻轉"
|
||||
aspect_ratio_square: "裁剪為正方形"
|
||||
aspect_ratio_landscape: "裁剪為橫向 (16:9)"
|
||||
aspect_ratio_portrait: "裁剪為縱向 (9:16)"
|
||||
composables:
|
||||
content_cache:
|
||||
toast_recovered: 已從緩存中恢復未保存的內容
|
||||
|
|
Loading…
Reference in New Issue