diff --git a/eslint.config.js b/eslint.config.js
index a30aa30..970d019 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -6,6 +6,7 @@ export default antfu({
}, {
ignores: [
'.github/workflows/client.yml',
+ 'packages/client/public/UPNG.js',
],
}, {
rules: {
diff --git a/packages/client/index.html b/packages/client/index.html
index b27bb3a..79240bd 100644
--- a/packages/client/index.html
+++ b/packages/client/index.html
@@ -8,8 +8,6 @@
七牛云OSS图床 | 粥里有勺糖
-
diff --git a/packages/client/package.json b/packages/client/package.json
index f294718..0afd210 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -24,6 +24,7 @@
"qiniu": "^7.11.0",
"qiniu-js": "^3.4.1",
"spark-md5": "^3.0.2",
+ "upng-js": "^2.1.0",
"vue": "^3.4.15",
"vue-router": "^4.2.5"
},
diff --git a/packages/client/src/components/ImageList.vue b/packages/client/src/components/ImageList.vue
index cd6fe62..60ff96c 100644
--- a/packages/client/src/components/ImageList.vue
+++ b/packages/client/src/components/ImageList.vue
@@ -27,6 +27,7 @@ const checkInfo = (image: IImage) => {
链接:${image.url}
上传时间:${image.date && formatDate(image.date)}
大小:${image.size ? formatSize(image.size) : '未知'}
+ ${image.originSize ? `压缩前大小:${formatSize(image.originSize)}` : ''}
`
})
@@ -56,7 +57,8 @@ const showImage = computed(() => {
- {{ formatSize(image.size) }}
+ {{ formatSize(image.size)
+ }}
🔍
url
markdown
@@ -85,7 +87,7 @@ ul.el-upload-list {
display: flex;
justify-content: space-between;
- .left{
+ .left {
flex: 1;
}
@@ -127,4 +129,5 @@ ul.el-upload-list {
li {
word-break: break-all;
}
-}
\ No newline at end of file
+}
+
\ No newline at end of file
diff --git a/packages/client/src/components/ImageUpload.vue b/packages/client/src/components/ImageUpload.vue
index 779ec60..1e20a42 100644
--- a/packages/client/src/components/ImageUpload.vue
+++ b/packages/client/src/components/ImageUpload.vue
@@ -2,11 +2,12 @@
import { UploadFilled } from '@element-plus/icons-vue'
import { computed, ref, watch } from 'vue';
import { ElMessage, type UploadInstance, type UploadProps, type UploadUserFile } from 'element-plus'
-import { compressImage, uploadFile } from '../utils/qiniu'
+import { uploadFile } from '../utils/qiniu'
import { useFocus } from '@vueuse/core';
import { useConfigStore, useImageStore } from '@/store'
import { storeToRefs } from 'pinia';
-import { formatSize } from '@/utils/stringUtil';
+import { useUploadConfig } from '@/composables';
+import { compressImage } from '@/utils/file';
const imageStore = useImageStore()
const configStore = useConfigStore()
@@ -23,6 +24,7 @@ const handleChange: UploadProps['onChange'] = (_, uploadFiles) => {
return ok
})
}
+const cacheConfig = useUploadConfig()
watch(files, async () => {
for (const file of files.value) {
@@ -33,12 +35,14 @@ watch(files, async () => {
if (!file.raw) {
continue
}
- compressImage(file.raw,{
- noCompressIfLarger: true
- }).then(v=>{
- console.log(v.dist, formatSize(v.dist.size));
- })
- uploadFile(file.raw, qiniu.value, {
+ let fileRaw = file.raw
+ if (cacheConfig.value.compressImage) {
+ // TODO: 未来开放自定义调整
+ // 采取自动压缩策略
+ fileRaw = await compressImage(file.raw) as any
+ }
+
+ uploadFile(fileRaw, qiniu.value, {
process(percent) {
file.percentage = percent
if (percent === 100) {
@@ -54,8 +58,9 @@ watch(files, async () => {
imageStore.addImage({
url: v,
name: file.name || 'image',
- file: file.raw,
- size: file.raw?.size || 0,
+ file: fileRaw,
+ size: fileRaw?.size || 0,
+ originSize: fileRaw === file.raw? 0 : file.raw?.size,
})
}).catch(err => {
ElMessage.error(err)
@@ -123,6 +128,7 @@ watch($pasteArea, () => {
const { focused } = useFocus($pasteArea)
const pasteText = computed(() => focused.value ? '现在你可以粘贴了' : '你也可以点击此处,然后粘贴你要上传的图片')
+
focused.value ? '现在你可以粘贴了' : '
+
\ No newline at end of file
diff --git a/packages/client/src/composables/index.ts b/packages/client/src/composables/index.ts
index 7b7a166..083b824 100644
--- a/packages/client/src/composables/index.ts
+++ b/packages/client/src/composables/index.ts
@@ -23,7 +23,8 @@ export function useIsMobile() {
})
}
+const defaultUploadConfig = { autoCopy: true, copyType: 'markdown', pageSize: 20, compressImage: true, compressPreview: true }
export function useUploadConfig() {
- const cacheConfig = useLocalStorage('uploadConfig', { autoCopy: true, copyType: 'markdown', pageSize: 20 })
+ const cacheConfig = useLocalStorage('uploadConfig', defaultUploadConfig)
return cacheConfig
}
diff --git a/packages/client/src/shims-vue.d.ts b/packages/client/src/shims-vue.d.ts
new file mode 100644
index 0000000..b021f5e
--- /dev/null
+++ b/packages/client/src/shims-vue.d.ts
@@ -0,0 +1,4 @@
+// 第三方库的类型定义
+// interface Window {
+// UPNG: any
+// }
diff --git a/packages/client/src/store/modules/imageStore.ts b/packages/client/src/store/modules/imageStore.ts
index 030a077..7195f09 100644
--- a/packages/client/src/store/modules/imageStore.ts
+++ b/packages/client/src/store/modules/imageStore.ts
@@ -1,6 +1,13 @@
import { defineStore } from 'pinia'
-export interface IImage { url: string, name: string, file?: File, date?: number, size: number }
+export interface IImage {
+ url: string
+ name: string
+ file?: File
+ date?: number
+ size: number
+ originSize?: number
+}
const imgStore = defineStore('imgStore', {
state: () => ({
diff --git a/packages/client/src/utils/file.ts b/packages/client/src/utils/file.ts
new file mode 100644
index 0000000..f1ba6f5
--- /dev/null
+++ b/packages/client/src/utils/file.ts
@@ -0,0 +1,94 @@
+// @ts-expect-error
+import UPNG from 'upng-js'
+interface CompressOptions {
+ /**
+ * 压缩质量(0-100)
+ * @default 80
+ */
+ quality?: number
+ /**
+ * 压缩后更大是否使用原图
+ * @default true
+ */
+ noCompressIfLarger?: boolean
+ /**
+ * 压缩后的新宽度
+ * @default 原尺寸
+ */
+ width?: number
+ /**
+ * 压缩后新高度
+ * @default 原尺寸
+ */
+ height?: number
+}
+async function compressImage(file: File, ops: CompressOptions = {}) {
+ const { width, height, quality = 80, noCompressIfLarger } = ops
+ const isPng = await isPNG(file)
+ let newFile: File | null = null
+ if (isPng) {
+ const arrayBuffer = await getBlobArrayBuffer(file)
+ const decoded = UPNG.decode(arrayBuffer)
+ const rgba8 = UPNG.toRGBA8(decoded)
+ const compressed = UPNG.encode(rgba8, width || decoded.width, height || decoded.height, convertQualityToBit(quality))
+ newFile = new File([compressed], file.name, { type: 'image/png' })
+ }
+
+ if (!newFile) {
+ return file
+ }
+
+ if (!noCompressIfLarger) {
+ return newFile
+ }
+
+ return file.size > newFile.size ? newFile : file
+}
+
+function getBlobArrayBuffer(file: Blob): Promise {
+ return file.arrayBuffer()
+}
+
+async function isPNG(file: File) {
+ const arraybuffer = await getBlobArrayBuffer(file.slice(0, 8))
+ return signatureEqual(arraybuffer, [137, 80, 78, 71, 13, 10, 26, 10])
+}
+
+function signatureEqual(source: ArrayBuffer, signature: number[]) {
+ const array = new Uint8Array(source)
+ for (let i = 0; i < signature.length; i++) {
+ if (array[i] !== signature[i]) {
+ return false
+ }
+ }
+ return true
+}
+
+function getImageDimensions(file: File): Promise<{ width: number, height: number }> {
+ return new Promise ((resolve) => {
+ const img = new Image()
+ img.onload = function () {
+ resolve({ width: img.width, height: img.height })
+ }
+ img.onerror = function () {
+ resolve({ width: 0, height: 0 })
+ }
+ img.src = URL.createObjectURL(file)
+ })
+}
+
+function convertQualityToBit(quality: number): number {
+ let bit = 0
+ if (quality > 100 || quality < 0) {
+ bit = 0
+ }
+ else {
+ bit = !quality ? 0 : quality * 256 * 0.01
+ }
+ return bit
+}
+
+export {
+ compressImage,
+ getImageDimensions,
+}
diff --git a/packages/client/src/utils/qiniu.ts b/packages/client/src/utils/qiniu.ts
index 8928ecc..4f320ce 100644
--- a/packages/client/src/utils/qiniu.ts
+++ b/packages/client/src/utils/qiniu.ts
@@ -1,5 +1,4 @@
import * as qiniu from 'qiniu-js'
-import type { CompressOptions } from 'qiniu-js/esm/utils/compress'
import { getFileMd5Hash } from './stringUtil'
import type { QiNiuConfig } from '@/store/modules/configStore'
@@ -50,11 +49,7 @@ async function generateNewFileKey(file: File, prefix = 'mdImg', scope = 'sugar')
return `${prefix}/${scope}/${md5}`
}
-async function compressImage(file: File, ops: CompressOptions) {
- return qiniu.compressImage(file, ops)
-}
export {
uploadFile,
generateNewFileKey,
- compressImage,
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6270787..0827788 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -90,6 +90,9 @@ importers:
spark-md5:
specifier: ^3.0.2
version: 3.0.2
+ upng-js:
+ specifier: ^2.1.0
+ version: 2.1.0
vue:
specifier: ^3.4.15
version: 3.4.15(typescript@5.3.3)
@@ -4431,6 +4434,10 @@ packages:
netmask: 2.0.2
dev: false
+ /pako@1.0.11:
+ resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
+ dev: false
+
/parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
@@ -5541,6 +5548,12 @@ packages:
picocolors: 1.0.0
dev: true
+ /upng-js@2.1.0:
+ resolution: {integrity: sha512-d3xzZzpMP64YkjP5pr8gNyvBt7dLk/uGI67EctzDuVp4lCZyVMo0aJO6l/VDlgbInJYDY6cnClLoBp29eKWI6g==}
+ dependencies:
+ pako: 1.0.11
+ dev: false
+
/uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
dependencies: