diff --git a/README.md b/README.md index fbbf086..2e6c68e 100644 --- a/README.md +++ b/README.md @@ -29,26 +29,53 @@ * 管理页面支持分页加载图片 ### 使用教程 -* 1.fork项目到自己的github -* 2.注册CloudFlare并开通R2服务 -![Upload](https://oss.tuqu.me/roim/blog/cf/r2.png) -* 3.找到Pages选项并且创建项目 -![Upload](https://oss.tuqu.me/roim/blog/cf/pages1.png) -* 4.选择项目创建方式 -![Upload](https://oss.tuqu.me/roim/blog/cf/pages2.png) -* 4.链接Github或GitLab并选需要构建的项目 - ![Upload](https://oss.tuqu.me/roim/blog/cf/pages3.png) - ![Upload](https://oss.tuqu.me/roim/blog/cf/pages4.png) -* 5.设置环境变量 -> 因为cloudflare默认的node版本较低需要手动指定版本,否在会导致构建失败. - ![Upload](https://oss.tuqu.me/roim/blog/cf/pages5.png) -* 6.设置项目的函数信息绑定R2和KV服务 -![Upload](https://oss.tuqu.me/roim/blog/cf/pages6.png) -![Upload](https://oss.tuqu.me/roim/blog/cf/pages7.png) -* 7.构建项目,提示成功即可访问 - ![Upload](https://oss.tuqu.me/roim/blog/cf/pages8.png) - -> 注意:Pages的函数变量名称需要于项目的变量名称一致,如果需要修改functions里面的Env名空间,对应的文件是`[[path]].ts` +1. fork项目到自己的github + +2. 注册CloudFlare并开通R2服务 + ![Upload](https://oss.tuqu.me/roim/blog/cf/r2.png) + +3. 找到Pages选项并且创建项目 + ![Upload](https://oss.tuqu.me/roim/blog/cf/pages1.png) + +4. 选择项目创建方式 + ![Upload](https://oss.tuqu.me/roim/blog/cf/pages2.png) + +5. 链接Github或GitLab并选需要构建的项目 + ![Upload](https://oss.tuqu.me/roim/blog/cf/pages3.png) + ![Upload](https://oss.tuqu.me/roim/blog/cf/pages4.png) + + 链接到仓库后,,选择`Vue框架预设`,,保存并部署 + + image-20240625125904324 + +6. 设置环境变量`BASE_URL` + + image-20240625125627118 + +7. 设置项目的函数信息,绑定R2和KV服务 + image-20240625125529675 + + ```typescript + // 请确保变量名与`functions/rest/[[path]].ts`中一致 + // ./functions/rest/[[path]].ts + + export interface Env { + BASE_URL: string + XK: KVNamespace + PICX: R2Bucket + } + ``` + + > 注意:Pages的函数变量名称需要于项目的变量名称一致,如果需要修改functions里面的Env名空间,对应的文件是`[[path]].ts` + +8. 设置`PICX_AUTH_TOKEN` + + 前往绑定的KV中,添加`PICX_AUTH_TOKEN`条目,填写Token + + image-20240625130106257 + +9. 重试部署,提示成功即可使用 + ### 图床截图 ![Upload](https://oss.tuqu.me/roim/blog/5.png) diff --git a/functions/rest/[[path]].ts b/functions/rest/[[path]].ts index 557e39a..6a3cfd2 100644 --- a/functions/rest/[[path]].ts +++ b/functions/rest/[[path]].ts @@ -14,6 +14,7 @@ export const onRequest: PagesFunction = async (context : EventContext) => { try { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const response: Response | undefined = await router.handle(context.request, context.env); + response?.headers.set('Access-Control-Allow-Origin', '*'); return response ?? error(404, 'not found'); } catch (err) { return error(500, (err as Error).message); diff --git a/functions/rest/routes/index.ts b/functions/rest/routes/index.ts index 9cec42f..7aa752b 100644 --- a/functions/rest/routes/index.ts +++ b/functions/rest/routes/index.ts @@ -22,7 +22,9 @@ const auth = async (request : Request, env : Env) => { return json(Fail("system not auth setting")) } if (authKey != token) { - return json(FailCode("auth fail", StatusCode.NotAuth)) + if(!request.url.includes("list")){ + return json(FailCode("auth fail"+request.url, StatusCode.NotAuth)) + } } // return new Response('Not Authenticated', { status: 401 }) } @@ -46,7 +48,73 @@ router.post('/checkToken', async (req : Request, env : Env) => { // list image router.post('/list', auth, async (req : Request, env : Env) => { - const data = await req.json() as ImgReq + var data; + + try{ + data= await req.json() as ImgReq + + }catch(e){ + let url=new URL(req.url) + data={ + limit:url.searchParams.get("limit"), + delimiter:url.searchParams.get("delimiter"), + } + } + + if (!data.limit) { + data.limit = 10 + } + if (data.limit > 100) { + data.limit = 100 + } + if (!data.delimiter) { + data.delimiter = "/" + } + let include = undefined + if (data.delimiter != "/") { + include = data.delimiter + } + // console.log(include) + const options = { + limit: data.limit, + cursor: data.cursor, + delimiter: data.delimiter, + prefix: include + } + const list = await env.PICX.list(options) + console.log(list) + const truncated = list.truncated ? list.truncated : false + const cursor = list.cursor + const objs = list.objects + const urls = objs.map(it => { + return { + url: `${env.BASE_URL}/rest/${it.key}`, + key: it.key, + size: it.size + } + }) + return json(Ok({ + list: urls, + next: truncated, + cursor: cursor, + prefixes: list.delimitedPrefixes + })) +}) +// list image +router.post('/listdir', auth, async (req : Request, env : Env) => { + var data; + + try{ + data= await req.json() as ImgReq + + }catch(e){ + let url=new URL(req.url) + data={ + limit:url.searchParams.get("limit"), + delimiter:url.searchParams.get("delimiter"), + } + } + if (!data.limit) { data.limit = 10 } @@ -68,7 +136,7 @@ router.post('/list', auth, async (req : Request, env : Env) => { prefix: include } const list = await env.PICX.list(options) - // console.log(list) + console.log(list) const truncated = list.truncated ? list.truncated : false const cursor = list.cursor const objs = list.objects @@ -91,6 +159,9 @@ router.post('/list', auth, async (req : Request, env : Env) => { router.post('/upload', auth, async (req: Request, env : Env) => { const files = await req.formData() const images = files.getAll("files") + let q = files.get("prefix") + const prefix= q?q:"" + console.log(prefix) const errs = [] const urls = Array() for (let item of images) { @@ -104,21 +175,23 @@ router.post('/upload', auth, async (req: Request, env : Env) => { const header = new Headers() header.set("content-type", fileType) header.set("content-length", `${item.size}`) - const object = await env.PICX.put(filename, item.stream(), { + const object = await env.PICX.put(prefix+filename, item.stream(), { httpMetadata: header, }) as R2Object if (object || object.key) { urls.push({ - key: object.key, + key: "123"+object.key, size: object.size, url: `${env.BASE_URL}/rest/${object.key}`, - filename: item.name + filename: "456"+item.name }) } } return json(Build(urls, errs.toString())) }) + + // 创建目录 router.post("/folder", auth, async (req: Request, env: Env) => { try { @@ -127,9 +200,25 @@ router.post("/folder", auth, async (req: Request, env: Env) => { if (!regx.test(data.name)) { return json(Fail("Folder name error")) } - await env.PICX.put(data.name + '/', null) + + let file=data.prehold; + + + const header = new Headers() + header.set("content-type", file.type) + header.set("content-length", `${file.size}`) + + console.log(file) + return json(file) + + console.log("ragbshfnj") + await env.PICX.put((data.name + '/').replace("//","/")+file.name, file.stream(), { + httpMetadata: header, + }) + return json(Ok("Success")) } catch (e) { + console.log(e) return json(Fail("Create folder fail")) } }) diff --git a/functions/rest/type.ts b/functions/rest/type.ts index 2428dc8..990b18b 100644 --- a/functions/rest/type.ts +++ b/functions/rest/type.ts @@ -26,7 +26,8 @@ export interface ImgReq { // 文件夹名称 export interface Folder { - name: string + name: string, + prehold:File } export function NotAuth() : Result { diff --git a/package.json b/package.json index 6e79c3d..b77e726 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "postcss": "^8.4.5", "tailwindcss": "^3.2.4", "typescript": "^4.4.4", - "vite": "^3.2.5", + "vite": "^3.2.10", "wrangler": "^2.9.0" } } diff --git a/src/assets/prehold.png b/src/assets/prehold.png new file mode 100644 index 0000000..39954c7 Binary files /dev/null and b/src/assets/prehold.png differ diff --git a/src/plugins/router.ts b/src/plugins/router.ts index 104f19a..ffe8e7a 100644 --- a/src/plugins/router.ts +++ b/src/plugins/router.ts @@ -14,6 +14,10 @@ const router = createRouter({ { path: '/auth', component: () => import('../views/auth.vue') + // component: () => import('../views/UploadImages.vue') + // component: () => import('../views/ManageImages.vue') + + }, { path: '/:path(.*)', diff --git a/src/utils/request.ts b/src/utils/request.ts index 260c8c0..dea536c 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -50,6 +50,7 @@ request.interceptors.response.use( ) export const requestListImages = (data : ImgReq): Promise => request.post('/rest/list', data) +export const requestListDir = (data : ImgReq): Promise => request.post('/rest/listdir', data) export const requestUploadImages = (data: FormData) : Promise => request.post('/rest/upload', data) export const createFolder = (data: Folder) => request.post('/rest/folder', data) export const checkToken = (data: AuthToken) => request.post('/rest/checkToken', data) diff --git a/src/utils/types.ts b/src/utils/types.ts index 6deaa77..5e46a6e 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -40,5 +40,6 @@ export interface ImgReq { limit: Number } export interface Folder { - name: string + name: string, + prehold:File } diff --git a/src/views/ManageImages.vue b/src/views/ManageImages.vue index fff7e35..77d3770 100644 --- a/src/views/ManageImages.vue +++ b/src/views/ManageImages.vue @@ -9,8 +9,8 @@ 已上传 {{ uploadedImages.length }} 张图片,共 {{ formatBytes(imagesTotalSize) }} -
- +
+ { delimiter.value = path listImages() } + const addFolder = () => { ElMessageBox.prompt('请输入目录名称,仅支持英文名称', '新增目录', { confirmButtonText: '创建', @@ -76,17 +77,37 @@ const addFolder = () => { inputErrorMessage: '无效的目录名称', }).then(({ value }) => { loading.value = true - createFolder( { - name: value - }).then((res) => { - console.log(res) - ElMessage.success('文件见创建成功') - listImages() - }).catch(() => { - ElMessage.error('文件见创建失败') - }).finally(() => { - loading.value = false - }) + + const imagePath = new URL('../assets/prehold.png', import.meta.url).href; + + + const xhr = new XMLHttpRequest(); + xhr.open('GET', imagePath, true); + xhr.responseType = 'blob'; + + xhr.onload = () => { + if (xhr.status === 200) { + const blob = xhr.response; + let file = new File([blob], 'yourimage.jpg', { type: blob.type }); + + console.log(file) + createFolder( { + name: value, + prehold:file + }).then((res) => { + console.log(res) + ElMessage.success('文件夹创建成功') + listImages() + }).catch(() => { + ElMessage.error('文件夹创建失败') + }).finally(() => { + loading.value = false + }) + } + }; + xhr.send(); + + }).catch(() => {}) } const listImages = () => { diff --git a/src/views/UploadImages.vue b/src/views/UploadImages.vue index 031dd79..43d791f 100644 --- a/src/views/UploadImages.vue +++ b/src/views/UploadImages.vue @@ -40,7 +40,7 @@
-
+
- +
+ +
已选择 {{ convertedImages.length }} 张,共 {{ formatBytes(imagesTotalSize) }} @@ -105,12 +114,15 @@ import { faUpload } from '@fortawesome/free-solid-svg-icons' import { computed, onMounted, onUnmounted, ref } from 'vue' import LoadingOverlay from '../components/LoadingOverlay.vue' import formatBytes from '../utils/format-bytes' -import {ElNotification as elNotify } from 'element-plus' -import { requestUploadImages } from '../utils/request' +import {ElAutocomplete,ElNotification as elNotify } from 'element-plus' +import { requestUploadImages,requestListDir } from '../utils/request' import { useRouter } from 'vue-router' import ImageBox from '../components/ImageBox.vue' import ResultList from '../components/ResultList.vue' import type { ConvertedImage, ImgItem } from '../utils/types' +import { requestListImages, requestDeleteImage, createFolder } from '../utils/request' +import type { ImgReq, Folder } from '../utils/types' + const convertedImages = ref([]) const imgResultList = ref([]) const imagesTotalSize = computed(() => @@ -121,6 +133,28 @@ const imageSizeLimit = 20 * 1024 * 1024 const input = ref() const loading = ref(false) const router = useRouter() +const prefix = ref('') + +interface RestaurantItem { + value: string +} +const createFilter = (queryString: string) => { + return (restaurant: RestaurantItem) => { + return ( + restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0 + ) + } +} + + +const restaurants = ref([]) +const querySearch = (queryString: string, cb: any) => { + const results = queryString + ? restaurants.value.filter(createFilter(queryString)) + : restaurants.value + // call callback function to return suggestions + cb(results) +} const onInputChange = () => { appendConvertedImages(input.value?.files) @@ -134,6 +168,7 @@ const onPaste = (e: ClipboardEvent) => { onMounted(() => { document.onpaste = onPaste + updateDir() }) onUnmounted(() => { @@ -179,7 +214,36 @@ const appendConvertedImages = async (files: FileList | null | undefined) => { } loading.value = false } +const updateDir = () => { + +// let dirs= Array( [ +// "o/", +// "runoilbus/", +// "ss/" +// ]) +// console.log(dirs) +// for (let index = 0; index < dirs.length; index++) { +// const element = dirs[index]; +// dirs[index]={value:element} +// console.log({value:element}) + +// } +// console.log(dirs) +// restaurants.value=dirs + requestListImages( { + limit: 100, + }).then((data) => { + console.log(data) + let dirs= (data.prefixes) + for (let index = 0; index < dirs.length; index++) { + let element = dirs[index]; + dirs[index]={value:element} + } + + restaurants.value=dirs + }).catch((e) => {console.log(e)}) +} const removeImage = (tmpSrc: string) => { convertedImages.value = convertedImages.value.filter((item) => item.tmpSrc !== tmpSrc) URL.revokeObjectURL(tmpSrc) @@ -192,6 +256,9 @@ const uploadImages = () => { for (let item of convertedImages.value) { formData.append('files', item.file) } + let p=prefix.value+'/' + p=p.replace('//','/') + formData.append("prefix",p) requestUploadImages(formData) .then((res) => {