feat: 添加分析师备注字段并优化代码格式

refactor: 重构文件上传和提交逻辑

style: 调整代码缩进和格式以提高可读性
This commit is contained in:
2026-03-31 15:27:54 +08:00
parent 55695da2fd
commit eba79c71c5
2 changed files with 455 additions and 374 deletions

View File

@@ -24,7 +24,9 @@
<div class="header-right"> <div class="header-right">
<n-button secondary strong type="error" round @click="logout" class="logout-btn"> <n-button secondary strong type="error" round @click="logout" class="logout-btn">
<template #icon> <template #icon>
<n-icon><LogOutOutline /></n-icon> <n-icon>
<LogOutOutline />
</n-icon>
</template> </template>
<span>退出系统</span> <span>退出系统</span>
</n-button> </n-button>
@@ -58,7 +60,9 @@
<n-grid-item class="actions-grid"> <n-grid-item class="actions-grid">
<n-space justify="end" style="width: 100%"> <n-space justify="end" style="width: 100%">
<n-button type="primary" @click="handleSearch" :loading="loading" class="action-btn"> <n-button type="primary" @click="handleSearch" :loading="loading" class="action-btn">
<template #icon><n-icon><SearchOutline /></n-icon></template> <template #icon><n-icon>
<SearchOutline />
</n-icon></template>
查询 查询
</n-button> </n-button>
<n-button secondary @click="resetSearch">重置</n-button> <n-button secondary @click="resetSearch">重置</n-button>
@@ -79,24 +83,16 @@
</div> </div>
</div> </div>
</template> </template>
<template #header-extra> <!-- <template #header-extra>
<n-button size="small" strong secondary type="primary" class="export-btn"> <n-button size="small" strong secondary type="primary" class="export-btn">
<template #icon><n-icon><DownloadOutline /></n-icon></template> <template #icon><n-icon><DownloadOutline /></n-icon></template>
导出报表 导出报表
</n-button> </n-button>
</template> </template> -->
<div class="table-container"> <div class="table-container">
<n-data-table <n-data-table remote :columns="columns" :data="displayData" :scroll-x="1200" :bordered="false"
remote :pagination="pagination" :loading="loading" :row-class-name="() => 'table-row-animate'" />
:columns="columns"
:data="displayData"
:scroll-x="1200"
:bordered="false"
:pagination="pagination"
:loading="loading"
:row-class-name="() => 'table-row-animate'"
/>
</div> </div>
</n-card> </n-card>
</main> </main>
@@ -109,7 +105,9 @@
<!-- 分配任务卡片 --> <!-- 分配任务卡片 -->
<div class="form-section-card highlight"> <div class="form-section-card highlight">
<div class="section-header"> <div class="section-header">
<n-icon><BrushOutline /></n-icon> <n-icon>
<BrushOutline />
</n-icon>
<span>执行任务分配</span> <span>执行任务分配</span>
</div> </div>
<n-form label-placement="top" :model="editingRow"> <n-form label-placement="top" :model="editingRow">
@@ -131,7 +129,9 @@
<!-- 基础信息卡片 --> <!-- 基础信息卡片 -->
<div class="form-section-card"> <div class="form-section-card">
<div class="section-header"> <div class="section-header">
<n-icon><PersonOutline /></n-icon> <n-icon>
<PersonOutline />
</n-icon>
<span>核心业务信息</span> <span>核心业务信息</span>
</div> </div>
<n-form label-placement="left" label-width="90" size="small" :model="editingRow"> <n-form label-placement="left" label-width="90" size="small" :model="editingRow">
@@ -144,7 +144,8 @@
</n-grid-item> </n-grid-item>
<n-grid-item> <n-grid-item>
<n-form-item label="成交日期"> <n-form-item label="成交日期">
<n-date-picker v-model:formatted-value="editingRow.transaction_date" value-format="yyyy-MM-dd" type="date" style="width: 100%" /> <n-date-picker v-model:formatted-value="editingRow.transaction_date" value-format="yyyy-MM-dd"
type="date" style="width: 100%" />
</n-form-item> </n-form-item>
</n-grid-item> </n-grid-item>
<n-grid-item> <n-grid-item>
@@ -160,40 +161,33 @@
<div class="file-grid"> <div class="file-grid">
<!-- 1. 附件文档 --> <!-- 1. 附件文档 -->
<div class="file-item"> <div class="file-item">
<div class="file-label"><n-icon><FileTrayOutline /></n-icon> 附件文档</div> <div class="file-label"><n-icon>
<n-upload <FileTrayOutline />
v-model:file-list="editingRow.attachmentFileList" </n-icon> 附件文档</div>
:max="1" <n-upload v-model:file-list="editingRow.attachmentFileList" :max="1"
:custom-request="handleCustomUpload" :custom-request="handleCustomUpload">
>
<n-button dashed block>更换附件文档</n-button> <n-button dashed block>更换附件文档</n-button>
</n-upload> </n-upload>
</div> </div>
<!-- 2. 电子签名 --> <!-- 2. 电子签名 -->
<div class="file-item"> <div class="file-item">
<div class="file-label"><n-icon><CreateOutline /></n-icon> 电子签名</div> <div class="file-label"><n-icon>
<n-upload <CreateOutline />
v-model:file-list="editingRow.signatureFileList" </n-icon> 电子签名</div>
list-type="image-card" <n-upload v-model:file-list="editingRow.signatureFileList" list-type="image-card" :max="1"
:max="1" @preview="handlePreview" :custom-request="handleCustomUpload">
@preview="handlePreview"
:custom-request="handleCustomUpload"
>
点击上传 点击上传
</n-upload> </n-upload>
</div> </div>
<!-- 3. 付款截图 --> <!-- 3. 付款截图 -->
<div class="file-item payment-item"> <div class="file-item payment-item">
<div class="file-label"><n-icon><ImagesOutline /></n-icon> 付款截图凭证 (多选)</div> <div class="file-label"><n-icon>
<n-upload <ImagesOutline />
v-model:file-list="editingRow.paymentFileList" </n-icon> 付款截图凭证 (多选)</div>
list-type="image-card" <n-upload v-model:file-list="editingRow.paymentFileList" list-type="image-card" multiple
multiple @preview="handlePreview" :custom-request="handleCustomUpload" />
@preview="handlePreview"
:custom-request="handleCustomUpload"
/>
</div> </div>
</div> </div>
</n-space> </n-space>
@@ -201,7 +195,8 @@
<template #footer> <template #footer>
<div class="drawer-footer"> <div class="drawer-footer">
<n-button @click="showDrawer = false" round px-8>取消</n-button> <n-button @click="showDrawer = false" round px-8>取消</n-button>
<n-button type="primary" :loading="submitLoading" round @click="handleDrawerSubmit" class="submit-btn-glow"> <n-button type="primary" :loading="submitLoading" round @click="handleDrawerSubmit"
class="submit-btn-glow">
确认分配并同步数据 确认分配并同步数据
</n-button> </n-button>
</div> </div>
@@ -244,7 +239,8 @@ const baseInfoFields =[
{ label: '分析师', key: 'analyst_name' }, { label: '分析师', key: 'analyst_name' },
{ label: '家长姓名', key: 'parent_name' }, { label: '家长姓名', key: 'parent_name' },
{ label: '家长电话', key: 'parent_phone' }, { label: '家长电话', key: 'parent_phone' },
{ label: '身份证', key: 'parent_id_card' } { label: '身份证', key: 'parent_id_card' },
{ label: '分析师备注', key: 'analyst_remark' }
] ]
const searchParams = reactive({ const searchParams = reactive({
@@ -338,7 +334,8 @@ const fetchData = async () => {
paymentFileList, paymentFileList,
signatureFileList, signatureFileList,
attachmentFileList, attachmentFileList,
isSubmitting: false isSubmitting: false,
analyst_remark: item.analyst_remark || ''
} }
}) })
pagination.itemCount = res.pagination?.total || 0 pagination.itemCount = res.pagination?.total || 0
@@ -419,6 +416,7 @@ const submitAssignment = async (row) => {
const attachment_object_name = row.attachmentFileList[0]?.name || row.attachmentFileList[0]?.object_name || row.attachmentFileList[0]?.name const attachment_object_name = row.attachmentFileList[0]?.name || row.attachmentFileList[0]?.object_name || row.attachmentFileList[0]?.name
const payload = { const payload = {
analyst_remark: row.analyst_remark,
wecom_id: String(row.wecom_id), wecom_id: String(row.wecom_id),
payment_object_names, payment_object_names,
signature_object_name, signature_object_name,
@@ -601,8 +599,19 @@ const logout = () => {
filter: blur(40px); filter: blur(40px);
} }
.circle-1 { width: 300px; height: 300px; top: -100px; right: -50px; } .circle-1 {
.circle-2 { width: 200px; height: 200px; bottom: -50px; left: 10%; } width: 300px;
height: 300px;
top: -100px;
right: -50px;
}
.circle-2 {
width: 200px;
height: 200px;
bottom: -50px;
left: 10%;
}
/* 内容包裹器 */ /* 内容包裹器 */
.content-wrapper { .content-wrapper {
@@ -779,11 +788,25 @@ const logout = () => {
/* 响应式适配 */ /* 响应式适配 */
@media (max-width: 768px) { @media (max-width: 768px) {
.header-banner { height: 180px; } .header-banner {
.title-meta h1 { font-size: 18px; } height: 180px;
.title-meta p { display: none; } }
.header-right span { display: none; }
.logout-btn { padding: 8px !important; } .title-meta h1 {
font-size: 18px;
}
.title-meta p {
display: none;
}
.header-right span {
display: none;
}
.logout-btn {
padding: 8px !important;
}
.file-grid { .file-grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;

View File

@@ -18,7 +18,9 @@
</div> </div>
<n-button class="logout-btn" secondary type="error" round @click="logout"> <n-button class="logout-btn" secondary type="error" round @click="logout">
<template #icon> <template #icon>
<n-icon><LogOutOutline /></n-icon> <n-icon>
<LogOutOutline />
</n-icon>
</template> </template>
退出系统 退出系统
</n-button> </n-button>
@@ -33,19 +35,23 @@
<div class="glass-card section-card"> <div class="glass-card section-card">
<div class="card-header"> <div class="card-header">
<div class="header-indicator"></div> <div class="header-indicator"></div>
<n-icon size="22"><IdCardOutline /></n-icon> <n-icon size="22">
<IdCardOutline />
</n-icon>
<h3>1. 客户及成交信息</h3> <h3>1. 客户及成交信息</h3>
</div> </div>
<div class="card-body"> <div class="card-body">
<n-grid :x-gap="24" :y-gap="0" cols="1 s:2 m:3" responsive="screen"> <n-grid :x-gap="24" :y-gap="0" cols="1 s:2 m:3" responsive="screen">
<n-grid-item> <n-grid-item>
<n-form-item label="分析师主管" path="analystSupervisor"> <n-form-item label="分析师主管" path="analystSupervisor">
<n-input v-model:value="formData.analystSupervisor" placeholder="输入姓名" clearable /> <n-input v-model:value="formData.analystSupervisor" placeholder="输入姓名"
clearable />
</n-form-item> </n-form-item>
</n-grid-item> </n-grid-item>
<n-grid-item> <n-grid-item>
<n-form-item label="分析师部门" path="analystDepartment"> <n-form-item label="分析师部门" path="analystDepartment">
<n-input v-model:value="formData.analystDepartment" placeholder="例如:市场一部" clearable /> <n-input v-model:value="formData.analystDepartment" placeholder="例如:市场一部"
clearable />
</n-form-item> </n-form-item>
</n-grid-item> </n-grid-item>
<n-grid-item> <n-grid-item>
@@ -65,29 +71,34 @@
</n-grid-item> </n-grid-item>
<n-grid-item> <n-grid-item>
<n-form-item label="身份证号码" path="parentIdCard"> <n-form-item label="身份证号码" path="parentIdCard">
<n-input v-model:value="formData.parentIdCard" placeholder="18位身份证号" clearable /> <n-input v-model:value="formData.parentIdCard" placeholder="18位身份证号"
clearable />
</n-form-item> </n-form-item>
</n-grid-item> </n-grid-item>
<n-grid-item> <n-grid-item>
<n-form-item label="成交日期" path="transactionDate"> <n-form-item label="成交日期" path="transactionDate">
<n-date-picker v-model:value="formData.transactionDate" type="date" clearable style="width: 100%" /> <n-date-picker v-model:value="formData.transactionDate" type="date" clearable
style="width: 100%" />
</n-form-item> </n-form-item>
</n-grid-item> </n-grid-item>
<n-grid-item> <n-grid-item>
<n-form-item label="成交金额" path="transactionAmount"> <n-form-item label="成交金额" path="transactionAmount">
<n-input-number v-model:value="formData.transactionAmount" placeholder="0.00" clearable style="width: 100%"> <n-input-number v-model:value="formData.transactionAmount" placeholder="0.00"
clearable style="width: 100%">
<template #prefix>¥</template> <template #prefix>¥</template>
</n-input-number> </n-input-number>
</n-form-item> </n-form-item>
</n-grid-item> </n-grid-item>
<n-grid-item> <n-grid-item>
<n-form-item label="指导周期" path="guidancePeriod"> <n-form-item label="指导周期" path="guidancePeriod">
<n-input v-model:value="formData.guidancePeriod" placeholder="例如3个月" clearable /> <n-input v-model:value="formData.guidancePeriod" placeholder="例如3个月"
clearable />
</n-form-item> </n-form-item>
</n-grid-item> </n-grid-item>
</n-grid> </n-grid>
<n-form-item label="分析师备注" class="mt-8"> <n-form-item label="分析师备注" class="mt-8">
<n-input v-model:value="formData.analystNotes" type="textarea" placeholder="填写备注信息(选填)" :autosize="{ minRows: 2, maxRows: 4 }" /> <n-input v-model:value="formData.analystNotes" type="textarea" placeholder="填写备注信息(选填)"
:autosize="{ minRows: 2, maxRows: 4 }" />
</n-form-item> </n-form-item>
</div> </div>
</div> </div>
@@ -98,7 +109,9 @@
<div class="glass-card section-card full-h"> <div class="glass-card section-card full-h">
<div class="card-header"> <div class="card-header">
<div class="header-indicator"></div> <div class="header-indicator"></div>
<n-icon size="22"><DocumentAttachOutline /></n-icon> <n-icon size="22">
<DocumentAttachOutline />
</n-icon>
<h3>2. 附件文档 (限1份)</h3> <h3>2. 附件文档 (限1份)</h3>
</div> </div>
<div class="card-body"> <div class="card-body">
@@ -107,7 +120,9 @@
:custom-request="({ file, onFinish, onError, onProgress }) => handleCustomUpload({ file, onFinish, onError, onProgress }, 'documentFileList')"> :custom-request="({ file, onFinish, onError, onProgress }) => handleCustomUpload({ file, onFinish, onError, onProgress }, 'documentFileList')">
<n-upload-dragger class="modern-dragger"> <n-upload-dragger class="modern-dragger">
<div class="dragger-icon"> <div class="dragger-icon">
<n-icon size="44" depth="3"><CloudUploadOutline /></n-icon> <n-icon size="44" depth="3">
<CloudUploadOutline />
</n-icon>
</div> </div>
<n-text class="dragger-text">点击或拖拽上传</n-text> <n-text class="dragger-text">点击或拖拽上传</n-text>
<n-p depth="3" class="dragger-subtext">支持 PDF, DOCX, XLSX (Max 50MB)</n-p> <n-p depth="3" class="dragger-subtext">支持 PDF, DOCX, XLSX (Max 50MB)</n-p>
@@ -120,7 +135,9 @@
<div class="glass-card section-card full-h"> <div class="glass-card section-card full-h">
<div class="card-header"> <div class="card-header">
<div class="header-indicator"></div> <div class="header-indicator"></div>
<n-icon size="22"><BrushOutline /></n-icon> <n-icon size="22">
<BrushOutline />
</n-icon>
<h3>4. 电子签名 (限1张)</h3> <h3>4. 电子签名 (限1张)</h3>
</div> </div>
<div class="card-body"> <div class="card-body">
@@ -129,7 +146,9 @@
@preview="handlePreview" @preview="handlePreview"
:custom-request="({ file, onFinish, onError, onProgress }) => handleCustomUpload({ file, onFinish, onError, onProgress }, 'signatureFileList')"> :custom-request="({ file, onFinish, onError, onProgress }) => handleCustomUpload({ file, onFinish, onError, onProgress }, 'signatureFileList')">
<div class="signature-placeholder"> <div class="signature-placeholder">
<n-icon size="28" depth="3"><CreateOutline /></n-icon> <n-icon size="28" depth="3">
<CreateOutline />
</n-icon>
<span>上传手写签名</span> <span>上传手写签名</span>
</div> </div>
</n-upload> </n-upload>
@@ -142,7 +161,9 @@
<div class="glass-card section-card mt-20"> <div class="glass-card section-card mt-20">
<div class="card-header"> <div class="card-header">
<div class="header-indicator"></div> <div class="header-indicator"></div>
<n-icon size="22"><ImagesOutline /></n-icon> <n-icon size="22">
<ImagesOutline />
</n-icon>
<h3>3. 付款截图凭证 (可多张)</h3> <h3>3. 付款截图凭证 (可多张)</h3>
</div> </div>
<div class="card-body"> <div class="card-body">
@@ -150,7 +171,9 @@
v-model:file-list="paymentFileListLast" @preview="handlePreview" v-model:file-list="paymentFileListLast" @preview="handlePreview"
:custom-request="({ file, onFinish, onError, onProgress }) => handleCustomUpload({ file, onFinish, onError, onProgress }, 'paymentFileList')"> :custom-request="({ file, onFinish, onError, onProgress }) => handleCustomUpload({ file, onFinish, onError, onProgress }, 'paymentFileList')">
<div class="payment-placeholder"> <div class="payment-placeholder">
<n-icon size="32"><CameraOutline /></n-icon> <n-icon size="32">
<CameraOutline />
</n-icon>
<span>添加凭证</span> <span>添加凭证</span>
</div> </div>
</n-upload> </n-upload>
@@ -167,7 +190,9 @@
</n-button> </n-button>
<n-button size="large" type="primary" round @click="handleSubmit" class="btn-submit"> <n-button size="large" type="primary" round @click="handleSubmit" class="btn-submit">
<template #icon> <template #icon>
<n-icon><CheckmarkCircleOutline /></n-icon> <n-icon>
<CheckmarkCircleOutline />
</n-icon>
</template> </template>
提交材料至中心 提交材料至中心
</n-button> </n-button>
@@ -445,6 +470,7 @@ const handleSubmit = async () => {
transaction_date: formatDate(formData.transactionDate), transaction_date: formatDate(formData.transactionDate),
transaction_amount: String(formData.transactionAmount || ''), transaction_amount: String(formData.transactionAmount || ''),
guidance_period: formData.guidancePeriod, guidance_period: formData.guidancePeriod,
analyst_remark: formData.analystNotes,
payment_object_names: formData.paymentFileList.map(f => f.rawResponse?.data?.object_name).filter(Boolean), payment_object_names: formData.paymentFileList.map(f => f.rawResponse?.data?.object_name).filter(Boolean),
signature_object_name: formData.signatureFileList[0]?.rawResponse?.data?.object_name || null, signature_object_name: formData.signatureFileList[0]?.rawResponse?.data?.object_name || null,
attachment_object_name: formData.documentFileList[0]?.rawResponse?.data?.object_name || null, attachment_object_name: formData.documentFileList[0]?.rawResponse?.data?.object_name || null,
@@ -471,7 +497,8 @@ const handleCancel = () => router.back()
.page-wrapper { .page-wrapper {
min-height: 100vh; min-height: 100vh;
background-color: #f5f7fa; background-color: #f5f7fa;
padding-bottom: 120px; /* 为底部悬浮栏留出空间 */ padding-bottom: 120px;
/* 为底部悬浮栏留出空间 */
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
} }
@@ -580,9 +607,19 @@ const handleCancel = () => router.back()
} }
/* 布局辅助 */ /* 布局辅助 */
.mt-8 { margin-top: 8px; } .mt-8 {
.mt-20 { margin-top: 20px; } margin-top: 8px;
.full-h { height: 100%; display: flex; flex-direction: column; } }
.mt-20 {
margin-top: 20px;
}
.full-h {
height: 100%;
display: flex;
flex-direction: column;
}
/* 上传组件现代风格 */ /* 上传组件现代风格 */
.modern-dragger { .modern-dragger {
@@ -641,6 +678,7 @@ const handleCancel = () => router.back()
height: 120px; height: 120px;
border-radius: 12px; border-radius: 12px;
} }
.payment-modern-upload :deep(.n-upload-file.n-upload-file--image-card) { .payment-modern-upload :deep(.n-upload-file.n-upload-file--image-card) {
width: 120px; width: 120px;
height: 120px; height: 120px;
@@ -706,16 +744,26 @@ const handleCancel = () => router.back()
padding: 0 20px; padding: 0 20px;
} }
.text-group h1 { font-size: 20px; } .text-group h1 {
.text-group p { font-size: 11px; } font-size: 20px;
.logout-btn { scale: 0.9; } }
.text-group p {
font-size: 11px;
}
.logout-btn {
scale: 0.9;
}
.main-content { .main-content {
margin-top: -40px; margin-top: -40px;
padding: 0 12px; padding: 0 12px;
} }
.card-body { padding: 16px; } .card-body {
padding: 16px;
}
.footer-actions-wrapper { .footer-actions-wrapper {
bottom: 0; bottom: 0;
@@ -724,8 +772,18 @@ const handleCancel = () => router.back()
height: 80px; height: 80px;
} }
.footer-blur-bg { border-radius: 20px 20px 0 0; } .footer-blur-bg {
.footer-content { justify-content: space-around; padding: 0 16px; } border-radius: 20px 20px 0 0;
.btn-cancel, .btn-submit { flex: 1; } }
.footer-content {
justify-content: space-around;
padding: 0 16px;
}
.btn-cancel,
.btn-submit {
flex: 1;
}
} }
</style> </style>