fix : 파일 업로드, 파일 다운로드 기능 추가 => 가격조사 등록, 가격조사 수정

This commit is contained in:
Yesol Choi
2025-05-26 17:41:33 +09:00
parent 92896faa2c
commit fbe349be24
5 changed files with 218 additions and 68 deletions

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { savePrice } from '/src/service/priceApi' import {deletePrcsFile, getPrcsFileDown, savePrice} from '/src/service/priceApi'
import { Person } from '/@src/utils/types' import {type iPbAtt, Person} from '/@src/utils/types'
import {formatBizNum,formatDate} from "/@src/utils/common/comfunc.ts"; import {formatBizNum,formatDate} from "/@src/utils/common/comfunc.ts";
const notyf = useNotyf() const notyf = useNotyf()
@@ -131,7 +131,7 @@ const savePriceOne = async () => {
} }
} }
const pbAtts = ref<iPbAtt[]>(params.prcsAtts)
const addNewEstimateRow = () => { const addNewEstimateRow = () => {
const newRow = {} const newRow = {}
@@ -173,8 +173,6 @@ const onDetailDelete = (index: number) => {
} }
const onPayDelete = (index: number) => { const onPayDelete = (index: number) => {
console.log("params.felxColumn.length",params.felxColumn.length)
console.log("params.felxColumn.length",apprLine.value.length)
if(apprLine.value.length-1 !== params.felxColumn.length if(apprLine.value.length-1 !== params.felxColumn.length
|| (params.felxColumn.length == 6 && apprLine.value.length-1 == params.felxColumn.length)) || (params.felxColumn.length == 6 && apprLine.value.length-1 == params.felxColumn.length))
{ {
@@ -183,19 +181,32 @@ const onPayDelete = (index: number) => {
} }
const fileInput = ref<HTMLInputElement | null>(null) const onFileClick = () => {
const fileName = ref('') const input = <any> document.querySelector('.file-input')
input.click()
function openFileDialog() {
fileInput.value?.click()
} }
function onFileChange(event: Event) { const onFileChange = (e: any) => {
const target = event.target as HTMLInputElement Array.from(e.target.files).forEach((file: any) => {
if (target.files && target.files.length > 0) { const reader = new FileReader()
fileName.value = target.files[0].name reader.onload = () => {
// 여기서 파일 업로드 로직을 추가할 수 있습니다. const result = <string>reader.result
// 예: emit('file-selected', target.files[0]) const pbAtt = <iPbAtt>{
logiFnm: file.name,
size: file.size,
data: result.split(',')[1],
}
pbAtts.value.push(pbAtt)
}
reader.readAsDataURL(file)
})
}
const onFilDelete = async ( index: number, fileOrd: number) => {
notyf.dismissAll()
if (index) {
pbAtts.value.splice(index, 1)
} }
} }
@@ -431,20 +442,42 @@ function onInput(row, column){
</td> </td>
</tr> </tr>
<tr> <tr>
<td>첨부파일</td> <td>첨부파일</td>
<td colspan="10"> <td colspan="3">
<VField class="file has-name is-left"> <VField>
<VButton @click="openFileDialog"> <div class="column is-6">
파일 첨부 <div class="file has-name is-fullwidth">
</VButton> <input
<input class="file-input hide"
ref="fileInput" type="file"
type="file" multiple
style="display: none" @change="(e) => onFileChange(e)"
@change="onFileChange" >
/> <VLabel>
<span v-if="fileName" class="file-name">{{ fileName }}</span> <VButton icon="fas fa-plus" color="info" class="file-trigger" @click="onFileClick" >
</VField> <span>파일업로드</span>
</VButton>
</VLabel>
<VControl>
<div
v-for="(f, i) in pbAtts"
:key="f.logiFnm"
class="content estimate-file-wrapper"
>
<div class="estimate-file-name">
{{ f.logiFnm }}
</div>
<div class="estimate-file-size">
{{ Math.ceil(f.size / 1024) }}kb
</div>
<div>
<i class="fa fa-trash estimate-file-delete" @click="onFilDelete( i, f.fileOrd)" />
</div>
</div>
</VControl>
</div>
</div>
</VField>
</td> </td>
</tr> </tr>
</tbody> </tbody>

View File

@@ -1,8 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import {getDetailPrcs, updatePrice} from '/src/service/priceApi' import {getDetailPrcs, updatePrice, getPrcsFileDown, deletePrcsFile} from '/src/service/priceApi'
import { type Person } from '/@src/utils/types' import {type iPbAtt, type Person} from '/@src/utils/types'
import {formatBizNum, formatDate} from "/@src/utils/common/comfunc.ts"; import {formatBizNum, formatDate} from "/@src/utils/common/comfunc.ts";
const notyf = useNotyf() const notyf = useNotyf()
const loading = ref(false) const loading = ref(false)
const router = useRouter() const router = useRouter()
@@ -73,7 +72,10 @@ const params = reactive({
btnChangeFlag: false btnChangeFlag: false
}) })
const pbAtts = ref<iPbAtt[]>(params.prcsAtts)
function getDetailList(arg){ function getDetailList(arg){
console.log("arg",arg)
params.prcsNo = arg.prcsNo params.prcsNo = arg.prcsNo
params.stCd = arg.stCd params.stCd = arg.stCd
params.cateSelect = arg.cateCd params.cateSelect = arg.cateCd
@@ -98,6 +100,7 @@ function getDetailList(arg){
apprDt: req.apprDt, apprDt: req.apprDt,
attendCd: req.attendCd attendCd: req.attendCd
})) //비고 데이터 없음, 승인일자 없음 todo })) //비고 데이터 없음, 승인일자 없음 todo
params.prcsAtts = arg.prcsAtts
} }
const changeButton = () => { const changeButton = () => {
@@ -138,9 +141,12 @@ const updatePriceOne = async () => {
prcsBizs: params.prcsBizs.map(({ num, ...rest }) => rest), //견적사 입력 데이터 prcsBizs: params.prcsBizs.map(({ num, ...rest }) => rest), //견적사 입력 데이터
dtlSpecs: [], //todo dtlSpecs: [], //todo
// params.dtlSpecs.map(({ num, ...rest }) => rest), //상세 규격 입력 데이터 // params.dtlSpecs.map(({ num, ...rest }) => rest), //상세 규격 입력 데이터
prcsAtts: params.prcsAtts, //첨부파일 데이터 prcsAtts: pbAtts.value.map(req => ({
logiFnm: req.logiFnm,
data: req.data})),//첨부파일 데이터,
apprReqs: apprLine.value.map(({ deptNm, ...rest }) => rest), //결재선 데이터 apprReqs: apprLine.value.map(({ deptNm, ...rest }) => rest), //결재선 데이터
} }
console.log("pbAtts.value",pbAtts.value)
console.log("paramsPrice",paramsPrice) console.log("paramsPrice",paramsPrice)
res = await updatePrice(paramsPrice) res = await updatePrice(paramsPrice)
notyf.dismissAll() notyf.dismissAll()
@@ -193,29 +199,60 @@ const onDetailDelete = (index: number) => {
} }
} }
const fileInput = ref<HTMLInputElement | null>(null)
const fileName = ref('')
function openFileDialog() {
fileInput.value?.click()
}
function onFileChange(event: Event) {
const target = event.target as HTMLInputElement
if (target.files && target.files.length > 0) {
fileName.value = target.files[0].name
// 여기서 파일 업로드 로직을 추가할 수 있습니다.
// 예: emit('file-selected', target.files[0])
}
}
function onInput(row, column){ function onInput(row, column){
if (column.key === 'bizNo') { if (column.key === 'bizNo') {
const raw =row[column.key].replace(/\D/g, '') const raw =row[column.key].replace(/\D/g, '')
row[column.key] = formatBizNum(raw) // 실시간 포맷 적용 row[column.key] = formatBizNum(raw)
} }
} }
const onFileClick = () => {
const input = <any> document.querySelector('.file-input')
input.click()
}
const onFileChange = (e: any) => {
Array.from(e.target.files).forEach((file: any) => {
const reader = new FileReader()
reader.onload = () => {
const result = <string>reader.result
const pbAtt = <iPbAtt>{
prcsNo: params.prcsNo,
logiFnm: file.name,
size: file.size,
data: result.split(',')[1],
}
pbAtts.value.push(pbAtt)
}
reader.readAsDataURL(file)
})
}
const onFilDelete = async (prcsNo: string, index: number, fileOrd: number) => {
notyf.dismissAll()
console.log(prcsNo)
if (prcsNo === undefined || prcsNo === null) {
pbAtts.value.splice(index, 1)
}
else {
await deletePrcsFile(params.prcsNo, fileOrd).then((res: string) => {
notyf.success(res)
pbAtts.value.splice(index, 1)
})
}
}
const onPrcsFileDownload = async (prcsNo: string, fileOrd: number, logiFnm: string) => {
const link = document.createElement('a')
link.href = `https://svcm.hmsn.ink/api/prcs/${prcsNo}/${fileOrd}` //todo
link.setAttribute('download', logiFnm)
link.setAttribute('target', '_blank')
document.body.appendChild(link)
link.click()
}
</script> </script>
<template> <template>
@@ -439,20 +476,62 @@ function onInput(row, column){
</tr> </tr>
<tr> <tr>
<td>첨부파일</td> <td>첨부파일</td>
<td colspan="3"> <td colspan="4">
<VField class="file has-name is-left"> <VField>
<VButton @click="openFileDialog">
파일 첨부
</VButton>
<input <input
ref="fileInput" class="file-input hide"
type="file" type="file"
style="display: none" multiple
@change="onFileChange" @change="(e) => onFileChange(e)"
/> >
<VLabel>
<span v-if="fileName" class="file-name">{{ fileName }}</span> <VButton icon="fas fa-plus" color="info" class="file-trigger" @click="onFileClick" >
<span>파일업로드</span>
</VButton>
</VLabel>
<VControl>
<div
v-for="(f, i) in pbAtts"
:key="f.logiFnm"
class="content estimate-file-wrapper"
>
<div class="estimate-file-name">
{{ f.logiFnm }}
</div>
<div class="estimate-file-size">
{{ Math.ceil(f.size / 1024) }}kb
</div>
<div>
<i class="fa fa-trash estimate-file-delete" @click="onFilDelete(f.bizNo, i, f.fileOrd)" />
</div>
</div>
</VControl>
</VField> </VField>
<div class="column is-12">
<VField>
<VLabel>첨부파일</VLabel>
<VControl>
<div
v-for="f in params.prcsAtts"
:key="f.logiFnm"
class="content estimate-file-wrapper"
>
<div>
<i class="fa fa-file-pdf estimate-file-img" style="font-size:40px;" />
</div>
<div class="estimate-file-name">
{{ f.logiFnm }}
</div>
<div class="estimate-file-size">
{{ Math.ceil(f.size / 1024) }}kb
</div>
<div>
<i class="fa fa-download estimate-file-download" @click="onPrcsFileDownload(f.prcsNo, f.fileOrd, f.logiFnm)" />
</div>
</div>
</VControl>
</VField>
</div>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@@ -532,5 +611,4 @@ function onInput(row, column){
background-color: var(--primary); background-color: var(--primary);
} }
} }
</style> </style>

View File

@@ -0,0 +1,38 @@
.estimate-file-wrapper {
display:flex;
justify-content: space-between;
border:1px solid #ccc;
border-radius:5px;
padding:10px 20px;
margin-bottom:10px;
.estimate-file-download, .estimate-file-delete {
font-size: 15px;
line-height: 40px;
padding-left: 30px;
cursor: pointer;
}
.estimate-file-img {
font-size: 40px;
padding-right: 50px;
}
.estimate-file-name {
width: 400px;
line-height: 40px;
}
.estimate-file-size {
width: 200px;
line-height: 40px;
}
}
.estimate-file-download, .estimate-file-delete, .fa-plus-circle {
&:hover {
color: var(--primary);
}
}
.file-trigger {
margin-left: 10px;
height:15px;
padding:10px 10px;
}

View File

@@ -1,2 +1,3 @@
@import './vuerosearch'; @import './vuerosearch';
@import './vuerotable'; @import './vuerotable';
@import './vuerofile';

View File

@@ -70,15 +70,15 @@ export async function getDetailPrcs(params) {
} }
/** /**
* 가격조사서 다운로드 Todo * 가격조사서 다운로드
* @param {object} params * @param {object} params
* @property {string} params.prcsNo - 가격조사번호 * @property {string} params.prcsNo - 가격조사번호
* @property {string} params.fileOrd - 파일순서 * @property {string} params.fileOrd - 파일순서
* @returns * @returns
*/ */
export async function getPrcsFileDown(params) { export async function getPrcsFileDown(prcsNo, fileOrd) {
try { try {
const result = await axios.get(`/api/prcs/${params}`) const result = await axios.get(`/api/prcs/${prcsNo}/${fileOrd}`)
return result.data return result.data
} catch (e) { } catch (e) {
throw new Error(e) throw new Error(e)
@@ -86,15 +86,15 @@ export async function getPrcsFileDown(params) {
} }
/** /**
* 가격조사서 첨부파일 삭제 Todo * 가격조사서 첨부파일 삭제
* @param {object} params * @param {object} params
* @property {string} params.prcsNo - 가격조사번호 * @property {string} params.prcsNo - 가격조사번호
* @property {string} params.fileOrd - 파일순서 * @property {string} params.fileOrd - 파일순서
* @returns * @returns
*/ */
export async function deletePrcsFile(params) { export async function deletePrcsFile(prcsNo, fileOrd) {
try { try {
const result = await axios.delete(`/api/prcs/${params}`) const result = await axios.delete(`/api/prcs/${prcsNo}/${fileOrd}`)
return result.data return result.data
} catch (e) { } catch (e) {
throw new Error(e) throw new Error(e)