refactor(05): upload slip
This commit is contained in:
parent
4bf0e22bc2
commit
9991bab995
2 changed files with 200 additions and 57 deletions
93
src/components/upload-file/UploadFileCard.vue
Normal file
93
src/components/upload-file/UploadFileCard.vue
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue