优化文档下载
This commit is contained in:
@@ -319,7 +319,7 @@
|
|||||||
<span v-if="isImageMaterial(item)" class="preview-hint">
|
<span v-if="isImageMaterial(item)" class="preview-hint">
|
||||||
{{ materialPreviewUrls[item.fileId] ? "点击图片可放大预览" : materialPreviewText(item) }}
|
{{ materialPreviewUrls[item.fileId] ? "点击图片可放大预览" : materialPreviewText(item) }}
|
||||||
</span>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -369,12 +369,7 @@
|
|||||||
plain
|
plain
|
||||||
size="small"
|
size="small"
|
||||||
icon="Download"
|
icon="Download"
|
||||||
@click="
|
@click="download(contract.generatedDocxFileId, contractDownloadName(contract, 'docx'))"
|
||||||
download(
|
|
||||||
contract.generatedDocxFileId,
|
|
||||||
`${detail?.employee?.employeeName || '员工'}_${contract.contractName || '入职合同'}.docx`
|
|
||||||
)
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
下载生成 DOCX
|
下载生成 DOCX
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -894,19 +889,69 @@ function contractStatusText(status?: string) {
|
|||||||
return map[status || ""] || status || "未知状态";
|
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) {
|
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") {
|
function contractDownloadName(contract: any, extension: "pdf" | "docx") {
|
||||||
const employeeName = detail.value?.employee?.employeeName || "员工";
|
const prefix = getEmployeeDownloadPrefix();
|
||||||
const contractName = contract?.contractName || "入职合同";
|
const docType = String(
|
||||||
return safeDownloadName(employeeName + "_" + contractName) + "." + extension;
|
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() {
|
function electronicSignArchiveName() {
|
||||||
const employeeName = detail.value?.employee?.employeeName || "员工";
|
const prefix = getEmployeeDownloadPrefix();
|
||||||
return safeDownloadName(employeeName + "_电子签完整归档") + ".zip";
|
return `${prefix}_电子签完整归档.zip`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearMaterialPreviews() {
|
function clearMaterialPreviews() {
|
||||||
@@ -1201,18 +1246,18 @@ async function showDetail(row: OnboardingEmployee) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resolveDownloadFileName(response: any, fallbackFileName?: string) {
|
function resolveDownloadFileName(response: any, fallbackFileName?: string) {
|
||||||
|
if (fallbackFileName) {
|
||||||
|
return safeDecodeFileName(fallbackFileName);
|
||||||
|
}
|
||||||
|
|
||||||
const disposition = String(response?.headers?.["content-disposition"] || "");
|
const disposition = String(response?.headers?.["content-disposition"] || "");
|
||||||
const encodedName = disposition.match(/filename\*=UTF-8\'\'([^;]+)/i)?.[1] || disposition.match(/filename="?([^";]+)"?/i)?.[1];
|
const encodedName = disposition.match(/filename\*=UTF-8\'\'([^;]+)/i)?.[1] || disposition.match(/filename="?([^";]+)"?/i)?.[1];
|
||||||
|
|
||||||
if (encodedName) {
|
if (encodedName) {
|
||||||
try {
|
return safeDecodeFileName(encodedName);
|
||||||
return decodeURIComponent(encodedName.replace(/\+/g, "%20"));
|
|
||||||
} catch {
|
|
||||||
return encodedName;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return fallbackFileName || "员工资料文件";
|
return "员工资料文件";
|
||||||
}
|
}
|
||||||
|
|
||||||
function openHrEdit(row: OnboardingEmployee) {
|
function openHrEdit(row: OnboardingEmployee) {
|
||||||
|
|||||||
@@ -900,7 +900,7 @@
|
|||||||
plain
|
plain
|
||||||
size="small"
|
size="small"
|
||||||
icon="Download"
|
icon="Download"
|
||||||
@click="download(item.generatedDocxFileId)"
|
@click="download(item.generatedDocxFileId, getContractDocxDownloadName(item))"
|
||||||
>
|
>
|
||||||
下载已生成DOCX
|
下载已生成DOCX
|
||||||
</el-button>
|
</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) {
|
function resolveDownloadFileName(response: any, fallbackFileName?: string) {
|
||||||
|
if (fallbackFileName) {
|
||||||
|
return safeDecodeFileName(fallbackFileName);
|
||||||
|
}
|
||||||
|
|
||||||
const disposition = String(response?.headers?.["content-disposition"] || "");
|
const disposition = String(response?.headers?.["content-disposition"] || "");
|
||||||
const encodedName = disposition.match(/filename\*=UTF-8\'\'([^;]+)/i)?.[1]
|
const encodedName = disposition.match(/filename\*=UTF-8\'\'([^;]+)/i)?.[1]
|
||||||
|| disposition.match(/filename="?([^";]+)"?/i)?.[1];
|
|| disposition.match(/filename="?([^";]+)"?/i)?.[1];
|
||||||
|
|
||||||
if (encodedName) {
|
if (encodedName) {
|
||||||
try {
|
return safeDecodeFileName(encodedName);
|
||||||
return decodeURIComponent(encodedName.replace(/\+/g, "%20"));
|
|
||||||
} catch {
|
|
||||||
return encodedName;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return fallbackFileName || "入职材料";
|
return "入职材料";
|
||||||
}
|
}
|
||||||
|
|
||||||
// 列表查询并统计
|
// 列表查询并统计
|
||||||
@@ -3993,12 +4030,85 @@ onMounted(loadCompanies);
|
|||||||
gap: 16px;
|
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) {
|
@media (max-width: 768px) {
|
||||||
.copy-count-row {
|
.copy-count-row {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
flex-direction: column;
|
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-document,
|
||||||
.electronic-sign-link-card,
|
.electronic-sign-link-card,
|
||||||
.electronic-sign-footer {
|
.electronic-sign-footer {
|
||||||
|
|||||||
Reference in New Issue
Block a user