Merge branch 'development'
This commit is contained in:
commit
a64d93824f
8 changed files with 146 additions and 85 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { QTableProps } from 'quasar'
|
import type { QTableProps } from 'quasar'
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import api from '@/services/HttpService'
|
import api from '@/services/HttpService'
|
||||||
|
|
||||||
|
|
@ -13,7 +13,7 @@ import useStorage from '@/stores/storage'
|
||||||
|
|
||||||
const storageStore = useStorage()
|
const storageStore = useStorage()
|
||||||
const { folder, file, currentInfo } = storeToRefs(storageStore)
|
const { folder, file, currentInfo } = storeToRefs(storageStore)
|
||||||
const { goto, deleteFolder, deleteFile, constructUrl } = storageStore
|
const { goto, deleteFolder, deleteFile } = storageStore
|
||||||
|
|
||||||
const { getFormatDate, getSize, getType, getFileInfo } = useFileInfoStore()
|
const { getFormatDate, getSize, getType, getFileInfo } = useFileInfoStore()
|
||||||
|
|
||||||
|
|
@ -33,13 +33,8 @@ const props = defineProps<{
|
||||||
mode: 'admin' | 'user'
|
mode: 'admin' | 'user'
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
interface ApiResponse {
|
|
||||||
size: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteState = ref<boolean>(false)
|
const deleteState = ref<boolean>(false)
|
||||||
const open = ref<boolean>(false)
|
const open = ref<boolean>(false)
|
||||||
const sizefolder = ref<string>()
|
|
||||||
|
|
||||||
const deletePath = ref<string>('')
|
const deletePath = ref<string>('')
|
||||||
const deleteTarget = ref<'deleteFolder' | 'deleteFile'>()
|
const deleteTarget = ref<'deleteFolder' | 'deleteFile'>()
|
||||||
|
|
@ -57,34 +52,29 @@ function triggerFileDelete(pathname: string) {
|
||||||
deleteState.value = !deleteState.value
|
deleteState.value = !deleteState.value
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSizeFolder(constructFolder: string) {
|
function getFolderSize(pathname: string) {
|
||||||
const src = constructFolder.split('/').filter(Boolean)
|
|
||||||
const path = src.join('/') + '/'
|
|
||||||
const res = async () => {
|
|
||||||
const response = await api.get(constructUrl(src, false) + '/size')
|
|
||||||
sizefolder.value = response.data.size
|
|
||||||
}
|
|
||||||
|
|
||||||
if (folder.value[currentInfo.value.path]) {
|
if (folder.value[currentInfo.value.path]) {
|
||||||
const idx = folder.value[currentInfo.value.path].findIndex(
|
const value = folder.value[currentInfo.value.path].find(
|
||||||
(v) => v.pathname === path,
|
(v) => v.pathname === pathname,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (idx !== -1) {
|
if (value && !value.size) {
|
||||||
res()
|
const arr = pathname.split('/').filter(Boolean)
|
||||||
if (folder.value[currentInfo.value.path][idx].folderSize) {
|
|
||||||
if (
|
api
|
||||||
folder.value[currentInfo.value.path][idx].folderSize !=
|
.post<{ size: number }>(
|
||||||
sizefolder.value
|
`${import.meta.env.VITE_API_ENDPOINT}storage/folder/size`,
|
||||||
) {
|
{
|
||||||
folder.value[currentInfo.value.path][idx].folderSize =
|
path: arr.slice(0, -1),
|
||||||
sizefolder.value
|
name: arr[arr.length - 1],
|
||||||
}
|
},
|
||||||
} else {
|
)
|
||||||
folder.value[currentInfo.value.path][idx].folderSize = sizefolder.value
|
.then((res) => {
|
||||||
}
|
if (res.status === 200 && res.data.size) value.size = res.data.size
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return getSize(folder.value[currentInfo.value.path][idx].folderSize)
|
|
||||||
|
return getSize(value?.size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -235,12 +225,12 @@ const onRowClick = ((_, row) => {
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
<q-tooltip
|
<q-tooltip
|
||||||
:delay="1000"
|
|
||||||
anchor="center left"
|
anchor="center left"
|
||||||
self="center right"
|
self="center right"
|
||||||
|
:delay="data.row.size ? 0 : 1000"
|
||||||
:offset="[5, 1]"
|
:offset="[5, 1]"
|
||||||
>
|
>
|
||||||
{{ getSizeFolder(data.row.pathname) }}
|
{{ getFolderSize(data.row.pathname) }}
|
||||||
</q-tooltip>
|
</q-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="props.mode === 'admin'">
|
<div v-if="props.mode === 'admin'">
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import axiosClient from '@/services/HttpService'
|
import api from '@/services/HttpService'
|
||||||
|
|
||||||
import type { StorageFile } from '@/stores/storage'
|
import type { StorageFile } from '@/stores/storage'
|
||||||
import { useFileInfoStore } from '@/stores/file-info-data'
|
import { useFileInfoStore } from '@/stores/file-info-data'
|
||||||
|
|
@ -14,22 +14,22 @@ const filePath =
|
||||||
(fileInfo.value?.pathname || '').split('/').slice(0, -1).join(' / ') + ' / '
|
(fileInfo.value?.pathname || '').split('/').slice(0, -1).join(' / ') + ' / '
|
||||||
|
|
||||||
async function downloadSubmit(path: string | undefined) {
|
async function downloadSubmit(path: string | undefined) {
|
||||||
if (path) {
|
if (!path) return
|
||||||
let formatPath: string
|
|
||||||
|
|
||||||
if (path.split('/').length - 1 === 3) {
|
const arr = path.split('/')
|
||||||
const [cabinet, drawer, folder, file] = path.split('/')
|
|
||||||
formatPath = `cabinet/${cabinet}/drawer/${drawer}/folder/${folder}/file/${file}`
|
|
||||||
} else {
|
|
||||||
const [cabinet, drawer, folder, subfolder, file] = path.split('/')
|
|
||||||
formatPath = `cabinet/${cabinet}/drawer/${drawer}/folder/${folder}/subfolder/${subfolder}/file/${file}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await axiosClient.get<StorageFile & { download: string }>(
|
if (arr.length < 3 || arr.length > 4) return
|
||||||
`${import.meta.env.VITE_API_ENDPOINT}${formatPath}`,
|
|
||||||
)
|
const file = arr.pop()
|
||||||
|
|
||||||
|
const res = await api.post<StorageFile & { downloadUrl: string }>(
|
||||||
|
`${import.meta.env.VITE_API_ENDPOINT}storage/file/download`,
|
||||||
|
{ path: arr, file },
|
||||||
|
)
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data && res.data.downloadUrl) {
|
||||||
await axios
|
await axios
|
||||||
.get(res.data.download, {
|
.get(res.data.downloadUrl, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
responseType: 'blob',
|
responseType: 'blob',
|
||||||
headers: {
|
headers: {
|
||||||
|
|
|
||||||
|
|
@ -43,15 +43,12 @@ const props = defineProps<{
|
||||||
}>()
|
}>()
|
||||||
const socket = io(import.meta.env.VITE_API_HOST)
|
const socket = io(import.meta.env.VITE_API_HOST)
|
||||||
|
|
||||||
socket.on('FileUpdate', (data: StorageFile) =>
|
socket.on('FileUpload', (data: StorageFile) => {
|
||||||
replaceSearchItem(data.pathname, data),
|
replaceSearchItem(data.pathname, data)
|
||||||
)
|
})
|
||||||
socket.on('FileUpload', (data: StorageFile) =>
|
socket.on('FileMove', (data: { from: StorageFile; to: StorageFile }) => {
|
||||||
replaceSearchItem(data.pathname, data),
|
replaceSearchItem(data.from.pathname, data.to)
|
||||||
)
|
})
|
||||||
socket.on('FileUpdateMove', (data: { from: StorageFile; to: StorageFile }) =>
|
|
||||||
replaceSearchItem(data.from.pathname, data.to),
|
|
||||||
)
|
|
||||||
|
|
||||||
function replaceSearchItem(pathname: string, data: StorageFile) {
|
function replaceSearchItem(pathname: string, data: StorageFile) {
|
||||||
const idx = foundFile.value.findIndex((v) => v.pathname === pathname)
|
const idx = foundFile.value.findIndex((v) => v.pathname === pathname)
|
||||||
|
|
|
||||||
|
|
@ -87,13 +87,13 @@ export const useFileInfoStore = defineStore('info', () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSize(size: string | undefined): string {
|
function getSize(size: number | string | undefined): string {
|
||||||
if (size === undefined) return 'ไม่ทราบขนาด'
|
if (size === undefined) return 'ไม่ทราบขนาด'
|
||||||
|
|
||||||
const units = ['B', 'KB', 'MB', 'GB', 'TB']
|
const units = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||||
|
|
||||||
let i = 0
|
let i = 0
|
||||||
let sizeNumber = parseFloat(size)
|
let sizeNumber = typeof size === 'string' ? parseFloat(size) : size
|
||||||
while (sizeNumber >= 1024 && i++ < units.length - 1) {
|
while (sizeNumber >= 1024 && i++ < units.length - 1) {
|
||||||
sizeNumber /= 1024
|
sizeNumber /= 1024
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ type Path = string
|
||||||
export interface StorageFolder {
|
export interface StorageFolder {
|
||||||
pathname: string
|
pathname: string
|
||||||
name: string
|
name: string
|
||||||
folderSize?: string
|
size?: number
|
||||||
createdAt: string
|
createdAt: string
|
||||||
createdBy: string
|
createdBy: string
|
||||||
}
|
}
|
||||||
|
|
@ -22,7 +22,7 @@ export interface StorageFile {
|
||||||
pathname: string
|
pathname: string
|
||||||
path: string
|
path: string
|
||||||
fileName: string
|
fileName: string
|
||||||
fileSize: string
|
fileSize: number
|
||||||
fileType: string
|
fileType: string
|
||||||
title: string
|
title: string
|
||||||
description: string
|
description: string
|
||||||
|
|
@ -41,30 +41,6 @@ export interface Structure extends StorageFolder {
|
||||||
|
|
||||||
type Tree = Structure[]
|
type Tree = Structure[]
|
||||||
|
|
||||||
function constructUrl(path: string | string[], append = true) {
|
|
||||||
const arr = Array.isArray(path) ? path : path.split('/').filter(Boolean)
|
|
||||||
const url =
|
|
||||||
import.meta.env.VITE_API_ENDPOINT +
|
|
||||||
arr.reduce<string>((a, v, i) => {
|
|
||||||
switch (String(i)) {
|
|
||||||
case '0':
|
|
||||||
return `cabinet/${v}`
|
|
||||||
case '1':
|
|
||||||
return `${a}/drawer/${v}`
|
|
||||||
case '2':
|
|
||||||
return `${a}/folder/${v}`
|
|
||||||
case '3':
|
|
||||||
return `${a}/subfolder/${v}`
|
|
||||||
default:
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
}, '')
|
|
||||||
|
|
||||||
return append
|
|
||||||
? url + ['cabinet', '/drawer', '/folder', '/subfolder'][arr.length]
|
|
||||||
: url
|
|
||||||
}
|
|
||||||
|
|
||||||
function consistantPath(path: string | string[]) {
|
function consistantPath(path: string | string[]) {
|
||||||
return Array.isArray(path)
|
return Array.isArray(path)
|
||||||
? path.join('/') + '/'
|
? path.join('/') + '/'
|
||||||
|
|
@ -512,7 +488,6 @@ const useStorage = defineStore('storageStore', () => {
|
||||||
createFile,
|
createFile,
|
||||||
updateFile,
|
updateFile,
|
||||||
deleteFile,
|
deleteFile,
|
||||||
constructUrl,
|
|
||||||
consistantPath,
|
consistantPath,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,24 @@ interface DownloadFileBody {
|
||||||
file: string;
|
file: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function folderSize(path: string[]) {
|
||||||
|
const size = await new Promise<number>((resolve, reject) => {
|
||||||
|
const stream = minioClient.listObjectsV2(
|
||||||
|
DEFAULT_BUCKET,
|
||||||
|
path.length === 0 ? "" : path.join("/") + "/",
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
let total: number = 0;
|
||||||
|
|
||||||
|
stream.on("data", (v) => {
|
||||||
|
if (v && v.size) total += v.size;
|
||||||
|
});
|
||||||
|
stream.on("end", () => resolve(total));
|
||||||
|
stream.on("error", () => reject(new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์")));
|
||||||
|
});
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
async function listFolder(path: string[], hidden: boolean = false) {
|
async function listFolder(path: string[], hidden: boolean = false) {
|
||||||
const list = await new Promise<{ pathname: string; name: string }[]>((resolve, reject) => {
|
const list = await new Promise<{ pathname: string; name: string }[]>((resolve, reject) => {
|
||||||
const item: { pathname: string; name: string }[] = [];
|
const item: { pathname: string; name: string }[] = [];
|
||||||
|
|
@ -379,6 +397,13 @@ export class StorageController extends Controller {
|
||||||
return this.setStatus(HttpStatusCode.NO_CONTENT);
|
return this.setStatus(HttpStatusCode.NO_CONTENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post("folder/size")
|
||||||
|
@Tags("Storage Folder")
|
||||||
|
@Security("bearerAuth", ["management-role", "admin"])
|
||||||
|
public async folderSize(@Body() body: FolderBody) {
|
||||||
|
return { size: await folderSize([...body.path, body.name]) };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ลบ Folder ออกจากระบบ
|
* ลบ Folder ออกจากระบบ
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -824,6 +824,32 @@ export function RegisterRoutes(app: Router) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
|
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
|
||||||
|
app.post('/storage/folder/size',
|
||||||
|
authenticateMiddleware([{"bearerAuth":["management-role","admin"]}]),
|
||||||
|
...(fetchMiddlewares<RequestHandler>(StorageController)),
|
||||||
|
...(fetchMiddlewares<RequestHandler>(StorageController.prototype.folderSize)),
|
||||||
|
|
||||||
|
function StorageController_folderSize(request: any, response: any, next: any) {
|
||||||
|
const args = {
|
||||||
|
body: {"in":"body","name":"body","required":true,"ref":"FolderBody"},
|
||||||
|
};
|
||||||
|
|
||||||
|
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
|
||||||
|
|
||||||
|
let validatedArgs: any[] = [];
|
||||||
|
try {
|
||||||
|
validatedArgs = getValidatedArgs(args, request, response);
|
||||||
|
|
||||||
|
const controller = new StorageController();
|
||||||
|
|
||||||
|
|
||||||
|
const promise = controller.folderSize.apply(controller, validatedArgs as any);
|
||||||
|
promiseHandler(controller, promise, response, undefined, next);
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
|
||||||
app.delete('/storage/folder',
|
app.delete('/storage/folder',
|
||||||
authenticateMiddleware([{"bearerAuth":["management-role","admin"]}]),
|
authenticateMiddleware([{"bearerAuth":["management-role","admin"]}]),
|
||||||
...(fetchMiddlewares<RequestHandler>(StorageController)),
|
...(fetchMiddlewares<RequestHandler>(StorageController)),
|
||||||
|
|
|
||||||
|
|
@ -2197,6 +2197,54 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/storage/folder/size": {
|
||||||
|
"post": {
|
||||||
|
"operationId": "FolderSize",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Ok",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"properties": {
|
||||||
|
"size": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"size"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Storage Folder"
|
||||||
|
],
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": [
|
||||||
|
"management-role",
|
||||||
|
"admin"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": [],
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/FolderBody"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/storage/file": {
|
"/storage/file": {
|
||||||
"post": {
|
"post": {
|
||||||
"operationId": "PostFile",
|
"operationId": "PostFile",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue