refactor(05): upload slip

This commit is contained in:
puriphatt 2024-10-22 16:00:34 +07:00
parent 4bf0e22bc2
commit 9991bab995
2 changed files with 200 additions and 57 deletions

View file

@ -0,0 +1,93 @@
<script lang="ts" setup>
import axios from 'axios';
import { getAttachmentHead, convertFileSize } from 'stores/utils';
import { onMounted, ref } from 'vue';
const api = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL });
const size = ref<number>(0);
const props = withDefaults(
defineProps<{
icon?: string;
color?: string;
name?: string;
url?: string;
progress?: number;
uploading?: { loaded: number; total: number };
clickable?: boolean;
closeable?: boolean;
}>(),
{
icon: 'mdi-file-outline',
color: 'var(--brand-1)',
name: '-',
url: '-',
progress: 0,
uploading: () => ({ loaded: 0, total: 0 }),
clickable: false,
closeable: true,
},
);
defineEmits<{
(e: 'close'): void;
(e: 'click'): void;
}>();
async function getSize() {
if (props.progress !== 1) return;
const res = await getAttachmentHead(api, props.url);
if (res && res['content-length']) size.value = Number(res['content-length']);
}
onMounted(() => {
getSize();
});
</script>
<template>
<div
class="bordered rounded row items-center q-pa-md"
:class="{ 'cursor-pointer': clickable }"
@click="$emit('click')"
>
<q-icon :name="icon" size="lg" :style="`color: ${color}`" />
<article class="col column q-pl-md">
<span>{{ name }}</span>
<span class="text-caption app-text-muted-2">
{{
uploading.loaded
? `${convertFileSize(uploading.loaded)} of ${convertFileSize(uploading.total)}`
: `${convertFileSize(size)} of ${convertFileSize(size)}`
}}
<q-spinner-ios
v-if="progress !== 1"
class="q-mx-xs"
color="primary"
size="1.5em"
/>
<q-icon v-else name="mdi-check-circle" color="positive" size="1rem" />
{{ progress !== 1 ? `Uploading...` : 'Completed' }}
</span>
</article>
<q-btn
v-if="closeable"
icon="mdi-close"
flat
rounded
size="sm"
padding="0"
class="q-ml-auto self-start"
style="color: hsl(var(--text-mute))"
@click.stop="$emit('close')"
/>
<q-linear-progress
v-if="progress !== 1"
:value="progress"
class="q-mt-sm rounded"
color="info"
/>
</div>
</template>

View file

@ -1,18 +1,15 @@
<script setup lang="ts">
import { baseUrl } from 'stores/utils';
import { storeToRefs } from 'pinia';
import { useConfigStore } from 'stores/config';
import {
formatNumberDecimal,
commaInput,
deleteItem,
convertFileSize,
} from 'stores/utils';
import { formatNumberDecimal } from 'stores/utils';
import DialogForm from 'src/components/DialogForm.vue';
import { reactive, ref, watch } from 'vue';
import { useQuotationPayment } from 'src/stores/quotations';
import { Quotation, QuotationPaymentData } from 'src/stores/quotations/types';
import { dateFormat } from 'src/utils/datetime';
import { QFile } from 'quasar';
import UploadFileCard from 'src/components/upload-file/UploadFileCard.vue';
const configStore = useConfigStore();
const quotationPayment = useQuotationPayment();
@ -25,6 +22,8 @@ defineEmits<{
const model = defineModel<boolean>({ default: false, required: true });
const data = defineModel<Quotation | undefined>('data', { required: true });
const fileManager =
ref<ReturnType<typeof quotationPayment.createPaymentFileManager>>();
const refQFile = ref<InstanceType<typeof QFile>[]>([]);
const paymentData = ref<QuotationPaymentData[]>([]);
const payAll = ref<boolean>(false);
@ -32,7 +31,8 @@ const slipFile = ref<
{
quotationId: string;
paymentId: string;
file: File[];
data: { name: string; progress: number; loaded: number; total: number }[];
file?: File;
}[]
>([]);
@ -51,24 +51,100 @@ function pickFile(index: number) {
refQFile.value[index].pickFiles();
}
async function getSlipList(payment: QuotationPaymentData, index: number) {
if (!fileManager.value) return;
const slipList = await fileManager.value.listAttachment({
parentId: payment.id,
});
if (slipList && slipList.length > 0) {
slipFile.value[index].data = slipFile.value[index].data.filter((item) =>
slipList.includes(item.name),
);
slipList.forEach((slip) => {
const exists = slipFile.value[index].data.some(
(item) => item.name === slip,
);
if (!exists) {
slipFile.value[index].data.push({
name: slip,
progress: 1,
loaded: 0,
total: 0,
});
}
});
} else slipFile.value[index].data = [];
}
async function triggerUpload(
payment: QuotationPaymentData,
index: number,
file?: File,
) {
if (!data.value || !fileManager.value || !file) return;
slipFile.value[index].data.push({
name: file.name,
progress: 0,
loaded: 0,
total: 0,
});
const ret = await fileManager.value.putAttachment({
parentId: payment.id,
name: file.name,
file: file,
onUploadProgress: (e) => {
console.log(e);
slipFile.value[index].data[slipFile.value[index].data.length - 1] = {
name: file.name,
progress: e.progress || 0,
loaded: e.loaded,
total: e.total || 0,
};
},
});
if (ret) await getSlipList(payment, index);
slipFile.value[index].file = undefined;
}
async function triggerDelete(
payment: QuotationPaymentData,
name: string,
index: number,
) {
if (!data.value || !fileManager.value) return;
await fileManager.value.delAttachment({ parentId: payment.id, name });
await getSlipList(payment, index);
}
async function triggerViewSlip(payment: QuotationPaymentData, name: string) {
if (!data.value || !fileManager.value) return;
const url = `${baseUrl}/quotation/${data.value.id}/payment/${payment.id}/attachment/${name}`;
window.open(url, '_blank');
}
watch(
() => model.value,
async (open) => {
if (!data.value) return;
if (!open) {
paymentData.value = [];
state.payExpansion = [];
slipFile.value = [];
} else {
fileManager.value = quotationPayment.createPaymentFileManager(
data.value.id,
);
const ret = await quotationPayment.getQuotationPayment(data.value.id);
if (ret) {
paymentData.value = ret.quotationPaymentData;
slipFile.value = paymentData.value.map((v) => ({
quotationId: ret.id,
paymentId: v.id,
file: [],
data: [],
}));
// console.log(ret);
// console.log(paymentData.value);
// console.log(slipFile.value);
}
}
},
@ -79,6 +155,7 @@ watch(
:title="$t('quotation.receipt')"
v-model:modal="model"
width="65%"
hideFooter
>
<div
v-if="data"
@ -358,6 +435,7 @@ watch(
hide-expand-icon
v-model="state.payExpansion[i]"
header-style="padding:0px"
@before-show="getSlipList(p, i)"
>
<template v-slot:header>
<div class="column full-width">
@ -453,60 +531,32 @@ watch(
ref="refQFile"
v-show="false"
v-model="slipFile[i].file"
label="Pick files"
filled
multiple
append
style="max-width: 300px"
@update:model-value="triggerUpload(p, i, slipFile[i].file)"
/>
</div>
</div>
<section class="surface-2 row q-px-md">
<!-- upload card -->
<div
v-for="(file, j) in slipFile[i].file"
v-for="(d, j) in slipFile[i].data"
:key="j"
class="col-12 q-pa-md bordered rounded row items-center"
:class="{ 'q-mb-md': i === 5, 'q-mb-sm': i < 5 }"
class="col-12"
:class="{
'q-pb-md': j === slipFile[i].data.length - 1,
'q-pb-sm': j < slipFile[i].data.length,
}"
>
<q-icon
name="mdi-file-image-outline"
size="lg"
class="app-text-muted"
/>
<article class="col column q-pl-md">
<span>{{ file.name }}</span>
<span class="text-caption app-text-muted-2">
{{ convertFileSize(file.size) }}
<q-spinner-ios
v-if="false"
class="q-mx-xs"
color="primary"
size="1.5em"
/>
<q-icon
v-else
name="mdi-check-circle"
color="positive"
size="1rem"
/>
{{ false ? `Uploading...` : 'Completed' }}
</span>
</article>
<q-btn
icon="mdi-close"
flat
rounded
size="sm"
padding="0"
class="q-ml-auto self-start"
style="color: hsl(var(--text-mute))"
@click="deleteItem(slipFile[i].file, j)"
/>
<q-linear-progress
:value="40"
class="q-mt-sm rounded"
color="info"
<UploadFileCard
:name="d.name"
:progress="d.progress"
:uploading="{ loaded: d.loaded, total: d.total }"
:url="`/quotation/${data.id}/payment/${p.id}/attachment/${d.name}`"
icon="mdi-file-image-outline"
color="hsl(var(--text-mute))"
clickable
@click="triggerViewSlip(p, d.name)"
@close="triggerDelete(p, d.name, i)"
/>
</div>
</section>