优化文档下载
This commit is contained in:
@@ -319,7 +319,7 @@
|
||||
<span v-if="isImageMaterial(item)" class="preview-hint">
|
||||
{{ materialPreviewUrls[item.fileId] ? "点击图片可放大预览" : materialPreviewText(item) }}
|
||||
</span>
|
||||
<el-button link type="primary" icon="Download" @click="download(item.fileId, materialFileName(item))"> 下载原件 </el-button>
|
||||
<el-button link type="primary" icon="Download" @click="download(item.fileId, materialDownloadName(item))"> 下载原件 </el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -369,12 +369,7 @@
|
||||
plain
|
||||
size="small"
|
||||
icon="Download"
|
||||
@click="
|
||||
download(
|
||||
contract.generatedDocxFileId,
|
||||
`${detail?.employee?.employeeName || '员工'}_${contract.contractName || '入职合同'}.docx`
|
||||
)
|
||||
"
|
||||
@click="download(contract.generatedDocxFileId, contractDownloadName(contract, 'docx'))"
|
||||
>
|
||||
下载生成 DOCX
|
||||
</el-button>
|
||||
@@ -894,19 +889,69 @@ function contractStatusText(status?: string) {
|
||||
return map[status || ""] || status || "未知状态";
|
||||
}
|
||||
|
||||
function safeDecodeFileName(name: string): string {
|
||||
let decoded = String(name || "").trim();
|
||||
try {
|
||||
while (/%[0-9a-fA-F]{2}/.test(decoded)) {
|
||||
const next = decodeURIComponent(decoded.replace(/\+/g, "%20"));
|
||||
if (next === decoded) break;
|
||||
decoded = next;
|
||||
}
|
||||
} catch {
|
||||
// 忽略异常,保留当前值
|
||||
}
|
||||
return decoded;
|
||||
}
|
||||
|
||||
function safeDownloadName(value: string) {
|
||||
return value.replace(/[\\/:*?"<>|]/g, "_");
|
||||
return safeDecodeFileName(value)
|
||||
.replace(/[\/\\:*?"<>|]+/g, "_")
|
||||
.replace(/\s+/g, "")
|
||||
.trim();
|
||||
}
|
||||
|
||||
function getEmployeeDownloadPrefix(): string {
|
||||
const taskNo = String(
|
||||
detail.value?.task?.taskNo
|
||||
|| detail.value?.employee?.employeeNo
|
||||
|| detail.value?.employeeInfo?.employeeNo
|
||||
|| ""
|
||||
).trim();
|
||||
const employeeName = String(
|
||||
detail.value?.employee?.employeeName
|
||||
|| detail.value?.employeeInfo?.employeeName
|
||||
|| "员工"
|
||||
).trim();
|
||||
|
||||
const safeNo = safeDownloadName(taskNo);
|
||||
const safeName = safeDownloadName(employeeName);
|
||||
|
||||
if (safeNo) {
|
||||
return `${safeNo}_${safeName}`;
|
||||
}
|
||||
return safeName;
|
||||
}
|
||||
|
||||
function contractDownloadName(contract: any, extension: "pdf" | "docx") {
|
||||
const employeeName = detail.value?.employee?.employeeName || "员工";
|
||||
const contractName = contract?.contractName || "入职合同";
|
||||
return safeDownloadName(employeeName + "_" + contractName) + "." + extension;
|
||||
const prefix = getEmployeeDownloadPrefix();
|
||||
const docType = String(
|
||||
contract?.contractType
|
||||
|| contract?.contractName
|
||||
|| "入职合同"
|
||||
).trim();
|
||||
return `${prefix}_${safeDownloadName(docType)}.${extension}`;
|
||||
}
|
||||
|
||||
function materialDownloadName(item: any) {
|
||||
const prefix = getEmployeeDownloadPrefix();
|
||||
const materialType = String(item?.materialName || "资料文件").trim();
|
||||
const suffix = materialFileSuffix(item) || "png";
|
||||
return `${prefix}_${safeDownloadName(materialType)}.${suffix}`;
|
||||
}
|
||||
|
||||
function electronicSignArchiveName() {
|
||||
const employeeName = detail.value?.employee?.employeeName || "员工";
|
||||
return safeDownloadName(employeeName + "_电子签完整归档") + ".zip";
|
||||
const prefix = getEmployeeDownloadPrefix();
|
||||
return `${prefix}_电子签完整归档.zip`;
|
||||
}
|
||||
|
||||
function clearMaterialPreviews() {
|
||||
@@ -1201,18 +1246,18 @@ async function showDetail(row: OnboardingEmployee) {
|
||||
}
|
||||
|
||||
function resolveDownloadFileName(response: any, fallbackFileName?: string) {
|
||||
if (fallbackFileName) {
|
||||
return safeDecodeFileName(fallbackFileName);
|
||||
}
|
||||
|
||||
const disposition = String(response?.headers?.["content-disposition"] || "");
|
||||
const encodedName = disposition.match(/filename\*=UTF-8\'\'([^;]+)/i)?.[1] || disposition.match(/filename="?([^";]+)"?/i)?.[1];
|
||||
|
||||
if (encodedName) {
|
||||
try {
|
||||
return decodeURIComponent(encodedName.replace(/\+/g, "%20"));
|
||||
} catch {
|
||||
return encodedName;
|
||||
}
|
||||
return safeDecodeFileName(encodedName);
|
||||
}
|
||||
|
||||
return fallbackFileName || "员工资料文件";
|
||||
return "员工资料文件";
|
||||
}
|
||||
|
||||
function openHrEdit(row: OnboardingEmployee) {
|
||||
|
||||
@@ -900,7 +900,7 @@
|
||||
plain
|
||||
size="small"
|
||||
icon="Download"
|
||||
@click="download(item.generatedDocxFileId)"
|
||||
@click="download(item.generatedDocxFileId, getContractDocxDownloadName(item))"
|
||||
>
|
||||
下载已生成DOCX
|
||||
</el-button>
|
||||
@@ -2123,20 +2123,57 @@ async function loadMaterialPreviews(materials: any[]) {
|
||||
}));
|
||||
}
|
||||
|
||||
function safeDecodeFileName(name: string): string {
|
||||
let decoded = String(name || "").trim();
|
||||
try {
|
||||
// 循环解码,应对多重 URL 编码(例如 %25e5... 或 %e5...)
|
||||
while (/%[0-9a-fA-F]{2}/.test(decoded)) {
|
||||
const next = decodeURIComponent(decoded.replace(/\+/g, "%20"));
|
||||
if (next === decoded) break;
|
||||
decoded = next;
|
||||
}
|
||||
} catch {
|
||||
// 忽略异常,保留当前值
|
||||
}
|
||||
return decoded;
|
||||
}
|
||||
|
||||
function getContractDocxDownloadName(item: any) {
|
||||
const taskNo = String(detail.value?.task?.taskNo || "").trim();
|
||||
const employeeName = String(
|
||||
detail.value?.employeeInfo?.employeeName
|
||||
|| detail.value?.task?.employeeName
|
||||
|| "待填员工"
|
||||
).trim();
|
||||
const docType = String(
|
||||
item?.contractType
|
||||
|| item?.contractName
|
||||
|| "劳动合同"
|
||||
).trim();
|
||||
|
||||
const clean = (val: string) =>
|
||||
safeDecodeFileName(val)
|
||||
.replace(/[\/\\:*?"<>|]+/g, "_")
|
||||
.replace(/\s+/g, "")
|
||||
.trim();
|
||||
|
||||
return `${clean(taskNo)}_${clean(employeeName)}_${clean(docType)}.docx`;
|
||||
}
|
||||
|
||||
function resolveDownloadFileName(response: any, fallbackFileName?: string) {
|
||||
if (fallbackFileName) {
|
||||
return safeDecodeFileName(fallbackFileName);
|
||||
}
|
||||
|
||||
const disposition = String(response?.headers?.["content-disposition"] || "");
|
||||
const encodedName = disposition.match(/filename\*=UTF-8\'\'([^;]+)/i)?.[1]
|
||||
|| disposition.match(/filename="?([^";]+)"?/i)?.[1];
|
||||
|
||||
if (encodedName) {
|
||||
try {
|
||||
return decodeURIComponent(encodedName.replace(/\+/g, "%20"));
|
||||
} catch {
|
||||
return encodedName;
|
||||
}
|
||||
return safeDecodeFileName(encodedName);
|
||||
}
|
||||
|
||||
return fallbackFileName || "入职材料";
|
||||
return "入职材料";
|
||||
}
|
||||
|
||||
// 列表查询并统计
|
||||
@@ -3993,12 +4030,85 @@ onMounted(loadCompanies);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.contracts-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
margin-top: 16px;
|
||||
padding: 2px 0 12px;
|
||||
}
|
||||
|
||||
.contract-card-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
padding: 18px 24px;
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.03);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.contract-card-item:hover {
|
||||
border-color: var(--el-color-primary-light-5);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.contract-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.contract-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.contract-sub-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.confirmed-text {
|
||||
font-size: 13px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.contract-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.copy-count-row {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.contract-card-item {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 14px;
|
||||
padding: 14px 16px;
|
||||
}
|
||||
|
||||
.contract-right {
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.electronic-sign-document,
|
||||
.electronic-sign-link-card,
|
||||
.electronic-sign-footer {
|
||||
|
||||
Reference in New Issue
Block a user