병합에러 업로드

This commit is contained in:
Yesol Choi
2025-05-28 09:27:18 +09:00
parent 8ed89d386e
commit 348bba0413

View File

@@ -1,142 +1,252 @@
<script setup lang="ts"> <script setup lang="ts">
import {getDetailPrcs, updatePrcsNo, putSurveyPrcsNo} from '/src/service/priceApi'
import { type Person } from '/@src/utils/types'
import {formatDatefromString} from "/@src/utils/common/comfunc.ts";
const notyf = useNotyf()
const loading = ref(false)
const router = useRouter()
const props = defineProps<{
prcsNo: iPrcs
}>()
import axios from 'axios'
import { getContractDetail, updateContract, saveContract } from "/@src/service/contract.ts";
import { getDetailPrcs } from "/@src/service/priceApi.ts";
onBeforeMount(async ()=>{ onBeforeMount(async ()=>{
let result; const result = await getContractDetail(history.state.key)
if(history.state.key === undefined) {
result = await getDetailPrcs(props.prcsNo)
} else {
result = await getDetailPrcs(history.state.key)
}
getDetailList(result) getDetailList(result)
}) })
const detailActionsOpen = ref(false) const registerFormOpen = ref(false)
const apprLine = defineModel<Person[]>() const loading = ref(false)
const notyf = useNotyf()
const generalParams = reactive({ const router = useRouter()
title: "",
content: "",
regSdat: "",
regEdat: "",
prvYn: true,
prvRsn : "",
prvPwd : "",
aiYn: true,
})
const params = reactive({ const params = reactive({
cateSelect: '', cateCd: '',
prcsNo: '', // 키값 contNo: '',
stCd:'',//결재상태 코드{ 등록중:0100[회수버튼], title: '',
// 회수: 0300, 반려: 0400, 등록 완료: 0200 } compNm: '',
prcsAttsColumn:[ //첨부파일 입력 signDt: '',
{ key: 'logiFnm', label: '구분'}, contAmt: '',
{ key: 'data', label: '데이터'} contStatCd: '',
], contStat: '',
prcsAtts: [], //첨부파일 데이터 regsabun: '',
felxColumn: [ regNm: '',
{ key: 'gubunNm', label: '구분'}, regDt: '',
{ key: 'deptNm', label: '부서' }, reason: '',
{ key: 'sabun', label: '사번' }, page: 1,
{ key: 'name', label: '이름' }, row: 10,
{ key: 'attendNm', label: '비고' }, flexColumn: [],
{ key: 'apprStat', label: '결재상태'}, modalColumn: [],
{ key: 'date', label: '승인일자'}, })
],
priceData:[], const completedPriceDataParams = reactive({
prcsBizsColumn: [ //견적사 입력 prcsNo:'',
{ key: 'num', label: '구분', width: '10%' }, cateCd:'',
{ key: 'email', label: '이메일', editable: true, width: '50px' }, cateNm:'',
{ key: 'bizNo', label: '사업자번호', editable: true, width: '50px'}, bizNo:'',
], compNm:'',
prcsBizs: [], //견적사 입력 데이터 title:'',
dtlSpecsColumn: [ content:'',
{ key: 'num', label: '번호', editable: false }, regSdat:'',
{ key: 'itemNm', label: '품명', editable: true }, regEdat:'',
{ key: 'spec', label: '규격', editable: true }, regSabun:'',
{ key: 'unit', label: '단위', editable: true }, regNm:'',
{ key: 'qty', label: '수량', editable: true }, regDt:'',
{ key: '', label: '단가(VAT별도)', editable: false }, contAmt:'',
{ key: '', label: '금액(VAT별도)', editable: false }, stCd:'',
], stNm:'',
dtlSpecs: [], //상세 규격 입력 데이터 svyDt:'',
reason:'',
estimates: [],
page: 1,
row: 5,
})
params.modalColumn = [
{ key: 'cateNm', label: '분야' },
{ key: 'title', label: '제목' },
{ key: 'regNm', label: '담당자' },
{ key: 'stNm', label: '등록상태' },
{ key: 'title', label: '비고' },
{ key: 'regNm', label: '선택' },
]
const selectedCode = ref()
const priceSearchCheckBoxStatus = ref(false)
const data = reactive({
contractData: [],
completedPriceSearchData: [],
})
const isLoading = ref(false)
watch(registerFormOpen, async (isOpen) => {
if (isOpen) {
isLoading.value = true
// error.value = null
try {
const priceSearchDataRespone = await axios.get('/api/cont/prcs')
data.completedPriceSearchData = Array.isArray(priceSearchDataRespone.data) ? priceSearchDataRespone.data : []
}
catch (error) {
console.log(error)
data.priceData = []
}
}
}) })
function getDetailList(arg){ function getDetailList(arg){
params.prcsNo = arg.prcsNo console.log("arg",arg)
params.stCd = arg.stCd completedPriceDataParams.prcsNo = arg.prcsNo
params.cateSelect = arg.cateNm selectedCode.value = arg.cateCd
generalParams.title = arg.title completedPriceDataParams.bizNo = arg.bizNo
generalParams.content = arg.content completedPriceDataParams.compNm = arg.compNm
params.prcsBizs = arg.prcsBizs.map(req => ({ completedPriceDataParams.title = arg.title
bizNo: req.bizNo, completedPriceDataParams.regSdat = arg.contSdat // 계약기간
email: req.email, completedPriceDataParams.regEdat = arg.contEdat
})) completedPriceDataParams.contAmt = arg.contAmt
params.dtlSpecs = arg.dtlSpecs // completedPriceDataParams.svyDt = arg.signDt 계약체결일 todo
generalParams.regSdat = formatDatefromString(arg.regSdat) completedPriceDataParams.reason = arg.reason
generalParams.regEdat = formatDatefromString(arg.regEdat)
apprLine.value = arg.apprMst.apprReqs.map(req => ({
gubunNm: req.gubunNm,
deptCd: req.deptCd,
deptNm: req.deptNm,
sabun: req.sabun,
name: req.name,
apprNo: req.apprNo,
apprOrd: req.apprOrd,
apprStat: req.apprStat,
attendNm: req.attendNm
})) //비고 데이터 없음, 승인일자 없음 todo
console.log(" apprLine.value", apprLine.value)
} }
const updateState = async () => {
let res = null function formatMonthDate(dateStr) {
try { if (!dateStr) return ''
loading.value = true const date = new Date(dateStr)
const paramsPrice = { return date.toLocaleDateString('ko-KR', {
prcsNo : params.prcsNo year: 'numeric',
} month: '2-digit',
res = await updatePrcsNo(paramsPrice.prcsNo) day: '2-digit',
notyf.dismissAll() }).replace(/\./g, '-').replace(/\s/g, '').replace(/-$/, '')
if (res.request.status === 200) { }
notyf.primary('회수 되었습니다.')
router.push({path: '/app/priceManagement'}) function getDateDiff(start, end) {
} if (!start || !end) return null
} catch (e) { const startDate = new Date(start)
notyf.error(e.message) const endDate = new Date(end)
} finally { const diff = Math.ceil((endDate - startDate) / (1000 * 60 * 60 * 24))
loading.value = false return diff >= 0 ? diff + 1 : null
}
const contractPeriod = computed(() => {
const start = completedPriceDataParams.regSdat
const end = completedPriceDataParams.regEdat
const startStr = formatMonthDate(start)
const endStr = formatMonthDate(end)
const diff = getDateDiff(start, end)
if (startStr && endStr && diff) {
return `${startStr} ~ ${endStr} (${diff}일)`
}
else if (startStr && endStr) {
return `${startStr} ~ ${endStr}`
}
else if (startStr) {
return `${startStr} ~`
}
else if (endStr) {
return `~ ${endStr}`
}
else {
return ''
}
})
function handlePriceRowClick(row) {
completedPriceDataParams.prcsNo = row.prcsNo || ''
completedPriceDataParams.cateCd = row.cateCd || ''
completedPriceDataParams.cateNm = row.cateNm || ''
completedPriceDataParams.title = row.title || ''
completedPriceDataParams.content = row.content || ''
completedPriceDataParams.regSdat = row.regSdat || ''
completedPriceDataParams.regEdat = row.regEdat || ''
completedPriceDataParams.regSabun = row.regSabun || ''
completedPriceDataParams.regNm = row.regNm || ''
completedPriceDataParams.regDt = row.regDt || ''
completedPriceDataParams.stCd = row.stCd || ''
completedPriceDataParams.stNm = row.stNm || ''
completedPriceDataParams.svyDt = row.svyDt || ''
completedPriceDataParams.reason = row.reason || ''
selectedCode.value = row.cateCd || ''
completedPriceDataParams.estimates = row.estimates || []
if (row.estimates && row.estimates.length > 0) {
const minEstimate = row.estimates.reduce((min, curr) => curr.amt < min.amt ? curr : min, row.estimates[0])
completedPriceDataParams.bizNo = minEstimate.bizNo || ''
completedPriceDataParams.compNm = minEstimate.compNm || ''
completedPriceDataParams.contAmt = minEstimate.amt || ''
} else {
completedPriceDataParams.bizNo = ''
completedPriceDataParams.compNm = ''
completedPriceDataParams.contAmt = ''
notyf.error("견적서가 없습니다.")
}
registerFormOpen.value = false
priceSearchCheckBoxStatus.value = !!row
console.log(row)
console.log(completedPriceDataParams.contAmt)
}
const showFileInputs = ref(false)
const fileInputs = ref([
{ file: null, description: '시행 품의문' },
{ file: null, description: '소액수의계약서' },
{ file: null, description: '수의계약 체결 제한 여부 확인서' },
{ file: null, description: '퇴직자 재직여부 확인서' },
{ file: null, description: '인지세 납부확인서' },
{ file: null, description: '정부권장정책 이행 구매 검토서' },
{ file: null, description: '기타' }
])
function handleFileChange(e, idx) {
const files = e.target.files
if (files && files.length > 0) {
fileInputs.value[idx].file = files[0]
} else {
fileInputs.value[idx].file = null
} }
} }
const onChangeFinal = async () => { const saveContOne = async () => {
let res = null let res = null
try { try{
loading.value = true loading.value = true
res = await putSurveyPrcsNo(params.prcsNo) // if (!validation()) {
notyf.dismissAll() // return;
if (res.request.status === 200) { // }
notyf.primary('가격조사 완료 되었습니다.') const paramsCont ={
prcsNo: completedPriceDataParams.prcsNo,
bizNo: completedPriceDataParams.bizNo,
compNm: completedPriceDataParams.compNm,
title: completedPriceDataParams.title,
regSdat: formatMonthDate(completedPriceDataParams.regSdat),
regEdat: formatMonthDate(completedPriceDataParams.regEdat),
contAmt: completedPriceDataParams.contAmt,
signDt: formatMonthDate(completedPriceDataParams.svyDt),
reason: completedPriceDataParams.reason,
exeYn: true, // 가격조사 예외여부 확인필요
// cateCd: completedPriceDataParams.cateCd,
// cateNm: completedPriceDataParams.cateNm,
// content: completedPriceDataParams.content,
// regSabun: completedPriceDataParams.regSabun,
// regNm: completedPriceDataParams.regNm,
// regDt: formatMonthDate(completedPriceDataParams.regDt),
// stCd: completedPriceDataParams.stCd,
// stNm: completedPriceDataParams.stNm,
}
res = await updateContract(paramsCont)
if(res.request.status == '200'){
notyf.primary('등록 되었습니다.')
router.push({path: '/app/contractManagement'}) router.push({path: '/app/contractManagement'})
} }
} catch (e) { }catch(e){
notyf.error(e.message) notyf.error(e.message)
} finally { }finally {
loading.value = false loading.value = false
} }
} }
const moveList = () => {
router.push('/app/contractManagement')
}
</script> </script>
<template> <template>
@@ -146,36 +256,74 @@ const onChangeFinal = async () => {
<table class="table datatable-table is-fullwidth"> <table class="table datatable-table is-fullwidth">
<colgroup> <colgroup>
<col style="width: 10%;"> <col style="width: 10%;">
<col style="width: 20%;">
<col style="width: 20%;">
<col style="width: 10%;"> <col style="width: 10%;">
<col style="width: 10%;"> <col style="width: 10%;">
<col style="width: 10%;"> <col style="width: 20%;">
<col style="width: 10%;">
<col style="width: 10%;">
<col style="width: 10%;"> <col style="width: 10%;">
</colgroup> </colgroup>
<tbody> <tbody>
<tr> <tr>
<td>분야</td> <td>분야</td>
<td colspan="2"> <td>
<span class="column is-7"> <span class="colum">
<input <VField class="">
:readonly=true <VCodeSelect
v-model="params.cateSelect" v-model="selectedCode"
class="input custom-text-filter" cd_grp="5"
placeholder="제목" disabled
> /></VField>
</span> </span>
</td> </td>
<td>제목</td> <td>
<VButton
color="primary"
@click="registerFormOpen = true"
>
가격조사 가져오기
</VButton>
<VModal
is="form"
:open="registerFormOpen"
title="계약관리 등록"
size="contract-big"
actions="right"
>
<template #content>
<div class="modal-form">
<ComVFlexTable
:data="data.completedPriceSearchData"
:columns="params.modalColumn"
:compact="true"
:separators="true"
@row-click="handlePriceRowClick"
clickable
/>
</div>
</template>
</VModal>
</td>
<td>
<VField class="is-flex">
<VControl raw subcontrol>
<VCheckbox
label="가격조사여부"
color="info"
v-model="priceSeachCheckBoxStatus"
/>
</VControl>
</VField>
</td>
<td colspan="3"> <td colspan="3">
<div class="column is-fullhd"> <div class="column is-fullhd">
<VField class="pr-2"> <VField>
<VControl> <VControl>
<input <input
:readonly=true v-model="completedPriceDataParams.reason"
v-model="generalParams.title"
class="input custom-text-filter" class="input custom-text-filter"
placeholder="제목" placeholder="가격조사 안했을 시 예외 사유 입력(필수)"
disabled
> >
</VControl> </VControl>
</VField> </VField>
@@ -183,234 +331,225 @@ const onChangeFinal = async () => {
</td> </td>
</tr> </tr>
<tr> <tr>
<td>내용</td> <td>계약명</td>
<td colspan="6"> <td colspan="6">
<div class="column is-fullhd"> <VField>
<VField class="pr-2"> <VControl>
<VControl>
<input
:readonly=true
v-model="generalParams.content"
class="input custom-text-filter"
placeholder="내용"
/>
</VControl>
</VField>
</div>
</td>
</tr>
<tr>
<td>규격입력</td>
<td colspan="2">
<VButton
color='success'
icon="fas"
elevated
@click="detailActionsOpen = true"
>
<span> 상세 규격 등록 완료</span>
</VButton>
<VModal
:open="detailActionsOpen"
actions="center"
title="상세 규격 입력"
size="contract-big"
cancelLabel="닫기"
@close="detailActionsOpen = false"
>
<template #content>
<ComVFlexTable
:key="params.dtlSpecs.length"
:data="params.dtlSpecs"
:columns="params.dtlSpecsColumn"
:separators="true"
:clickable="true"
>
<template #body-cell="{ row, column, index, value }">
<!-- : 특정 컬럼이면 input, 아니면 그냥 출력 -->
<div>
<input
:readonly=true
v-if="column.editable"
v-model="row[column.key]"
class="editable-input"
/>
<span v-else-if="column.key=='num'">{{index+1}}</span>
<span v-else>{{ value }}</span>
</div>
</template>
</ComVFlexTable>
</template>
<template #cancel>
</template>
</VModal>
</td>
<td>등록기간</td>
<td colspan="5">
<div class="columns">
<div class="column is-5">
<!-- <VDatePicker-->
<!-- v-model="generalParams.regSdat"-->
<!-- color="green"-->
<!-- disabled-->
<!-- trim-weeks-->
<!-- style="pointer-events: none;"-->
<!-- >-->
<!-- <template #default="{ inputValue, inputEvents }">-->
<!-- <VField>-->
<!-- <VControl icon="lucide:calendar">-->
<!-- <input-->
<!-- :readonly=true-->
<!-- class="input v-input"-->
<!-- type="text"-->
<!-- :value="inputValue"-->
<!-- v-on="inputEvents"-->
<!-- placeholder="시작일"-->
<!-- >-->
<!-- </VControl>-->
<!-- </VField>-->
<!-- </template>-->
<!-- </VDatePicker>-->
<input <input
:readonly=true v-model="completedPriceDataParams.title"
v-model="generalParams.regSdat"
class="input custom-text-filter" class="input custom-text-filter"
placeholder="제목" placeholder="계약명"
disabled
> >
</div> </VControl>
<div style="transform: translateY(15px)">~</div>
<div class="column is-5">
<!-- <VDatePicker-->
<!-- v-model="generalParams.regEdat"-->
<!-- color="green"-->
<!-- trim-weeks-->
<!-- disabledDates=""-->
<!-- >-->
<!-- <template #default="{ inputValue, inputEvents }">-->
<!-- <VField>-->
<!-- <VControl icon="lucide:calendar">-->
<!-- <input-->
<!-- :readonly=true-->
<!-- class="input v-input"-->
<!-- type="text"-->
<!-- :value="inputValue"-->
<!-- v-on="inputEvents"-->
<!-- placeholder="종료일"-->
<!-- >-->
<!-- </VControl>-->
<!-- </VField>-->
<!-- </template>-->
<!-- </VDatePicker>-->
<input
:readonly=true
v-model="generalParams.regEdat"
class="input custom-text-filter"
placeholder="제목"
>
</div>
</div>
</td>
</tr>
<tr>
<td>첨부파일</td>
<td colspan="3">
<VField class="file has-name is-right">
<div class="file-label">
<input
class="file-input"
type="file"
name="resume">
<span class="file-cta">
<span class="file-icon">
<i class="fas fa-cloud-upload-alt"/>
</span>
<span class="file-label">첨부파일</span>
</span>
<span class="file-name light-text">2022.xls</span>
</div>
</VField> </VField>
</td> </td>
</tr> </tr>
<tr> <tr>
<td>견적요청</td> <td>계약상대자</td>
<td colspan="6"> <td>
<div style="display: flex; justify-content: flex-end; gap: 8px;"> <VField>
<!-- <VButton--> <VControl>
<!-- color="primary"--> <input
<!-- icon="fas"--> v-model="completedPriceDataParams.bizNo"
<!-- elevated--> class="input custom-text-filter"
<!-- @click="showTable = !showTable"--> placeholder="사업자번호"
<!-- >--> disabled
<!-- 견적사 확인--> >
<!-- </VButton>--> </VControl>
</VField>
</td>
<td>
<VField>
<VControl>
<input
v-model="completedPriceDataParams.compNm"
class="input custom-text-filter"
placeholder="업체명"
disabled
>
</VControl>
</VField>
</td>
<td>
<VButton color="warning">
부정당 확인
</VButton>
</td>
<td>
<VButton color="success">
정상
</VButton>
</td>
<td>
<VButton color="warning">
분할계약 확인
</VButton>
</td>
<td>
<VButton color="success">
정상
</VButton>
</td>
</tr>
<tr>
<td>계약체결일</td>
<td>
<VField>
<VControl>
<input
:value="formatMonthDate(completedPriceDataParams.regSdat)"
class="input custom-text-filter"
placeholder="계약체결일"
disabled
>
</VControl>
</VField>
</td>
<td>
<VButton <VButton
color="primary" color="primary"
icon="fas fa-circle"
elevated
@click="onChangeFinal"
> >
가격조사 완료
</VButton> </VButton>
<VModal
actions="center"
title="계약금액"
>
<template #content>
<VPlaceholderSection
title="Go Premium"
subtitle="Unlock more features and business tools by going premium"
/>
</template>
<template #action>
<VButton color="primary" raised>
등록
</VButton>
</template>
</VModal>
</td>
<td colspan="2">
<VField>
<VControl>
<input
v-model="completedPriceDataParams.contAmt"
class="input custom-text-filter"
placeholder="금액"
disabled
>
</VControl>
</VField>
</td>
<td colspan="1">
<span class="colum">
<VField>
<VSelect disabled>
<VOption value="">
수의계약 사유
</VOption>
</VSelect>
</VField>
</span>
</td>
<td>
<VButton>근거</VButton>
</td>
</tr>
<tr>
<td>계약기간</td>
<td colspan="1">
<div>
<div>
<VDatePicker
v-model="completedPriceDataParams.regSdat"
color="green"
trim-weeks
>
<template #default="{ inputValue, inputEvents }">
<VField>
<VControl icon="lucide:calendar">
<input
class="input v-input"
type="text"
:value="inputValue"
placeholder="시작일"
v-on="inputEvents"
disabled
>
</VControl>
</VField>
</template>
</VDatePicker>
</div>
</div> </div>
<div class="mt-2"> </td>
<ComVFlexTable <td colspan="1">
:key="params.prcsBizs.length" <div class="">
:data="params.prcsBizs" <div>
:columns="params.prcsBizsColumn" <VDatePicker
:compact="true" v-model="completedPriceDataParams.regEdat"
:separators="true" color="green"
:clickable="true" trim-weeks
>
<template #default="{ inputValue, inputEvents }">
<VField>
<VControl icon="lucide:calendar">
<input
class="input v-input"
type="text"
:value="inputValue"
placeholder="종료일"
v-on="inputEvents"
disabled
>
</VControl>
</VField>
</template>
</VDatePicker>
</div>
</div>
</td>
<td colspan="5">
계약기간 : {{ contractPeriod }}
</td>
</tr>
<tr>
<td>첨부파일</td>
<td colspan="1">
<VButton color="info" @click="showFileInputs = !showFileInputs">
등록
</VButton>
</td>
<td colspan="5">
<!-- 첨부파일 입력영역: 등록 버튼 클릭 토글 -->
<div v-if="showFileInputs" class="file-upload-list" style="margin-top:10px;">
<div
v-for="(input, idx) in fileInputs"
:key="idx"
style="display: flex; align-items: center; margin-bottom: 8px;"
> >
<template #body-cell="{ column, index, value }"> <!-- 파일선택 -->
<div> <label class="file-label" style="margin-right: 10px;">
<span v-if="column.key=='num'">{{index + 1}}</span> <input
<!-- readonly 출력 --> type="file"
<span v-else>{{ value }}</span> class="file-input"
</div> @change="e => handleFileChange(e, idx)"
</template> />
</ComVFlexTable> <span class="file-cta">파일선택</span>
</label>
<span style="flex:1; margin-right: 10px;">{{ input.file ? input.file.name : '첨부된 파일 없음' }}</span>
<span style="flex:2; color: #666;">{{ input.description }}</span>
</div>
</div> </div>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> <div class="bottom-button">
<div class="column is-12"> <VButton @click="saveContOne"> </VButton>
<VField class="pr-2"> <VButton @click="moveList"> </VButton>
<VLabel class="has-fullwidth"> </div>
결재선
</VLabel>
</VField>
<ComVFlexTable
:data="apprLine"
:columns="params.felxColumn"
:separators="true"
:clickable="true"
:compact="true">
<template #body-cell="{ row, column, index, value }">
<!-- : 특정 컬럼이면 input, 아니면 그냥 출력 -->
<div>
<span v-if="column.key=='attendNm' && !value">{{'재중'}}</span>
<span v-else>{{value}}</span>
</div>
</template>
</ComVFlexTable>
</div>
<div>
<VButton
v-if="params.stCd === '0100'"
color="warning"
@click.stop="updateState"
>
회수
</VButton>
<VButton
to="/app/priceManagement"
v-if="params.stCd === '0100' || '0200' || '0300' || '0400'"
color="info"
>
닫기
</VButton>
</div> </div>
</div> </div>
</div> </div>
@@ -420,16 +559,61 @@ const onChangeFinal = async () => {
.table tbody td { .table tbody td {
color: var(--smoke-white); color: var(--smoke-white);
} }
.datatable-table { .datatable-table {
padding: 12px 12px; td{
font-family: var(--font),serif;
vertical-align: middle;
padding: 4px 12px;
border-bottom: 1px solid var(--fade-grey);
}
td:nth-child(1) { td:nth-child(1) {
background-color: var(--primary); background-color: var(--primary);
text-align: center;
} }
td:nth-child(3) {
background-color: var(--primary); tr:nth-child(3) {
text-align: center;
}
tr td button{
width: 100%;
}
tr:nth-child(5) > td:nth-child(4) {
color: black;
} }
} }
</style> .bottom-button {
text-align: center;
button {
background-color: cornflowerblue;
margin: 10px;
font-weight: bold;
border-color: var(--primary);
color: white;
}
}
button:nth-child(2) {
background-color: #AB9A6c;
}
button:nth-child(3) {
background-color: silver;
}
.field {
margin-bottom: 0;
}
.file-label {
cursor: pointer;
}
.file-input {
display: none;
}
.file-cta {
background: #eee;
padding: 4px 12px;
border-radius: 4px;
}
</style>