0508修改

This commit is contained in:
lzm
2026-05-08 09:33:34 +08:00
parent 2e3a6cc1ec
commit 9ea02751de
11 changed files with 328 additions and 51 deletions

View File

@@ -14,28 +14,46 @@ export interface EmployeeOutputSummaryExportReqVO {
sortType?: string
}
export const exportProjectBudget = (projectId: number) => {
return request.download({ url: '/tjt/report/project-budget/export-excel', params: { projectId } })
export interface ProjectBudgetExportReqVO {
projectId: number
year?: number
}
export const exportProjectQuarterOutput = (planningId: number) => {
return request.download({
url: '/tjt/report/project-quarter-output/export-excel',
params: { planningId }
})
export interface ProjectQuarterOutputExportReqVO {
planningId: number
year?: number
}
export const exportProjectLeadQuarterOutput = (planningId: number) => {
return request.download({
url: '/tjt/report/project-lead-quarter-output/export-excel',
params: { planningId }
})
export interface ProjectLeadQuarterOutputExportReqVO {
planningId: number
year?: number
}
export const exportSpecialtyPersonOutput = (params: {
export interface SpecialtyPersonOutputExportReqVO {
planningId: number
specialtyCode: string
}) => {
year?: number
}
export const exportProjectBudget = (params: ProjectBudgetExportReqVO) => {
return request.download({ url: '/tjt/report/project-budget/export-excel', params })
}
export const exportProjectQuarterOutput = (params: ProjectQuarterOutputExportReqVO) => {
return request.download({
url: '/tjt/report/project-quarter-output/export-excel',
params
})
}
export const exportProjectLeadQuarterOutput = (params: ProjectLeadQuarterOutputExportReqVO) => {
return request.download({
url: '/tjt/report/project-lead-quarter-output/export-excel',
params
})
}
export const exportSpecialtyPersonOutput = (params: SpecialtyPersonOutputExportReqVO) => {
return request.download({ url: '/tjt/report/specialty-person-output/export-excel', params })
}

View File

@@ -14,6 +14,12 @@ const request = (option: any) => {
}
})
}
export interface DownloadResponseData {
data: Blob
fileName?: string
}
export default {
get: async <T = any>(option: any) => {
const res = await request({ method: 'GET', ...option })
@@ -35,9 +41,9 @@ export default {
const res = await request({ method: 'PUT', ...option })
return res.data as unknown as T
},
download: async <T = any>(option: any) => {
download: async <T = DownloadResponseData>(option: any) => {
const res = await request({ method: 'GET', responseType: 'blob', ...option })
return res as unknown as Promise<T>
return res as T
},
upload: async <T = any>(option: any) => {
option.headersType = 'multipart/form-data'

View File

@@ -20,6 +20,29 @@ import { ApiEncrypt } from '@/utils/encrypt'
const tenantEnable = import.meta.env.VITE_APP_TENANT_ENABLE
const { result_code, base_url, request_timeout } = config
const resolveDownloadFileName = (contentDisposition?: string) => {
if (!contentDisposition) {
return undefined
}
const decodeFileName = (value: string) => {
const normalizedValue = value.trim().replace(/^"(.*)"$/, '$1').replace(/\+/g, '%20')
try {
return decodeURIComponent(normalizedValue)
} catch (error) {
return normalizedValue
}
}
const filenameStarMatch = contentDisposition.match(/filename\*\s*=\s*([^;]+)/i)
if (filenameStarMatch?.[1]) {
return decodeFileName(filenameStarMatch[1].replace(/^UTF-8''/i, ''))
}
const filenameMatch = contentDisposition.match(/filename\s*=\s*([^;]+)/i)
if (filenameMatch?.[1]) {
return decodeFileName(filenameMatch[1])
}
return undefined
}
// 需要忽略的提示。忽略后,自动 Promise.reject('error')
const ignoreMsgs = [
'无效的刷新令牌', // 刷新令牌被删除时,不用提示
@@ -141,7 +164,10 @@ service.interceptors.response.use(
) {
// 注意:如果导出的响应为 json说明可能失败了不直接返回进行下载
if (response.data.type !== 'application/json') {
return response.data
return {
data: response.data,
fileName: resolveDownloadFileName(response.headers['content-disposition'])
}
}
data = await new Response(response.data).json()
}

View File

@@ -1,12 +1,38 @@
const download0 = (data: Blob, fileName: string, mineType: string) => {
export interface DownloadSource {
data: BlobPart
fileName?: string
}
const resolveDownloadPayload = (source: BlobPart | DownloadSource, fallbackFileName?: string) => {
if (
source &&
typeof source === 'object' &&
!(source instanceof Blob) &&
!(source instanceof ArrayBuffer) &&
'data' in source
) {
const resolvedSource = source as DownloadSource
return {
data: resolvedSource.data,
fileName: resolvedSource.fileName || fallbackFileName || 'download'
}
}
return {
data: source as BlobPart,
fileName: fallbackFileName || 'download'
}
}
const download0 = (source: BlobPart | DownloadSource, fileName: string | undefined, mineType: string) => {
const payload = resolveDownloadPayload(source, fileName)
// 创建 blob
const blob = new Blob([data], { type: mineType })
const blob = new Blob([payload.data], { type: mineType })
// 创建 href 超链接,点击进行下载
window.URL = window.URL || window.webkitURL
const href = URL.createObjectURL(blob)
const downA = document.createElement('a')
downA.href = href
downA.download = fileName
downA.download = payload.fileName
downA.click()
// 销毁超连接
window.URL.revokeObjectURL(href)
@@ -14,27 +40,27 @@ const download0 = (data: Blob, fileName: string, mineType: string) => {
const download = {
// 下载 Excel 方法
excel: (data: Blob, fileName: string) => {
excel: (data: BlobPart | DownloadSource, fileName?: string) => {
download0(data, fileName, 'application/vnd.ms-excel')
},
// 下载 Word 方法
word: (data: Blob, fileName: string) => {
word: (data: BlobPart | DownloadSource, fileName?: string) => {
download0(data, fileName, 'application/msword')
},
// 下载 Zip 方法
zip: (data: Blob, fileName: string) => {
zip: (data: BlobPart | DownloadSource, fileName?: string) => {
download0(data, fileName, 'application/zip')
},
// 下载 Html 方法
html: (data: Blob, fileName: string) => {
html: (data: BlobPart | DownloadSource, fileName?: string) => {
download0(data, fileName, 'text/html')
},
// 下载 Markdown 方法
markdown: (data: Blob, fileName: string) => {
markdown: (data: BlobPart | DownloadSource, fileName?: string) => {
download0(data, fileName, 'text/markdown')
},
// 下载 Json 方法
json: (data: Blob, fileName: string) => {
json: (data: BlobPart | DownloadSource, fileName?: string) => {
download0(data, fileName, 'application/json')
},
// 下载图片(允许跨域)

View File

@@ -173,7 +173,7 @@
import type { FormRules } from 'element-plus'
import * as EmployeeApi from '@/api/tjt/employee'
import * as EmployeeYearCostBudgetApi from '@/api/tjt/employeeYearCostBudget'
import { EMPLOYEE_STATUS, formatAmountText } from '@/views/tjt/shared/planning'
import { formatAmountText } from '@/views/tjt/shared/planning'
defineOptions({ name: 'TjtEmployeeYearCostBudget' })
@@ -251,7 +251,6 @@ const searchEmployees = async (keyword: string) => {
try {
employeeOptions.value = await EmployeeApi.getEmployeeSimpleList({
keyword,
status: EMPLOYEE_STATUS.active,
enabledFlag: true
})
ensureEmployeeOption()

View File

@@ -401,7 +401,6 @@ const searchEmployees = async (keyword: string) => {
try {
employeeOptions.value = await EmployeeApi.getEmployeeSimpleList({
keyword,
status: '在职',
enabledFlag: true
})
ensureEmployeeOptions(formData.value.rolePersons)

View File

@@ -208,6 +208,28 @@
</ContentWrap>
</el-col>
</el-row>
<Dialog v-model="exportDialogVisible" title="导出项目考核产值预算表" width="420">
<el-form label-width="88px">
<el-form-item label="年度">
<el-date-picker
v-model="exportYearValue"
class="!w-220px"
clearable
placeholder="请选择年度"
type="year"
value-format="YYYY"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="exportDialogVisible = false">取消</el-button>
<el-button :loading="exportLoading" type="primary" @click="submitProjectBudgetExport">
确定导出
</el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
@@ -226,10 +248,13 @@ import {
defineOptions({ name: 'TjtReportBudget' })
const message = useMessage()
const currentYear = new Date().getFullYear()
const loading = ref(false)
const planningLoading = ref(false)
const exportLoading = ref(false)
const exportDialogVisible = ref(false)
const exportYear = ref<number>()
const total = ref(0)
const projectList = ref<ProjectApi.ProjectVO[]>([])
const planningList = ref<PlanningApi.ProjectPlanningVO[]>([])
@@ -252,6 +277,13 @@ const queryProjectStartYearValue = computed({
}
})
const exportYearValue = computed({
get: () => (exportYear.value ? String(exportYear.value) : undefined),
set: (value?: string) => {
exportYear.value = value ? Number(value) : undefined
}
})
const totalPlanningAmount = computed(() =>
planningList.value.reduce((sum, item) => sum + Number(item.planningAmount || 0), 0)
)
@@ -313,11 +345,28 @@ const handleExportProjectBudget = async () => {
message.warning('请先选择项目')
return
}
exportYear.value = currentProject.value.projectStartYear || currentYear
exportDialogVisible.value = true
}
const submitProjectBudgetExport = async () => {
if (!currentProject.value?.id) {
message.warning('请先选择项目')
return
}
if (!exportYear.value) {
message.warning('请选择年度')
return
}
try {
await message.exportConfirm()
exportLoading.value = true
const data = await ReportApi.exportProjectBudget(currentProject.value.id)
download.excel(data, `${currentProject.value.projectName}_项目考核产值预算表.xlsx`)
const data = await ReportApi.exportProjectBudget({
projectId: currentProject.value.id,
year: exportYear.value
})
download.excel(data, `${currentProject.value.projectName}_${exportYear.value}_项目考核产值预算表.xlsx`)
exportDialogVisible.value = false
} finally {
exportLoading.value = false
}

View File

@@ -210,6 +210,28 @@
</ContentWrap>
</el-col>
</el-row>
<Dialog v-model="exportDialogVisible" :title="exportDialogTitle" width="420">
<el-form label-width="88px">
<el-form-item label="年度">
<el-date-picker
v-model="exportYearValue"
class="!w-220px"
clearable
placeholder="请选择年度"
type="year"
value-format="YYYY"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="exportDialogVisible = false">取消</el-button>
<el-button :loading="currentExportLoading" type="primary" @click="submitExport">
确定导出
</el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
@@ -244,6 +266,8 @@ interface QuarterYearRow {
quarters: PlanningQuarterApi.ProjectPlanningQuarterVO[]
}
type ExportDialogType = 'projectQuarter' | 'projectLeadQuarter'
const annualCategoryOptions: { label: string; value: AnnualCategoryKey }[] = [
{ label: '项目经理/项目负责人', value: 'project_lead' },
{ label: '建筑专业', value: 'arch' },
@@ -256,12 +280,16 @@ const annualCategoryOptions: { label: string; value: AnnualCategoryKey }[] = [
]
const message = useMessage()
const currentYear = new Date().getFullYear()
const loading = ref(false)
const planningLoading = ref(false)
const quarterLoading = ref(false)
const exportQuarterLoading = ref(false)
const exportLeadLoading = ref(false)
const exportDialogVisible = ref(false)
const exportDialogType = ref<ExportDialogType>('projectQuarter')
const exportYear = ref<number>()
const total = ref(0)
const projectList = ref<ProjectApi.ProjectVO[]>([])
const planningList = ref<PlanningApi.ProjectPlanningVO[]>([])
@@ -288,6 +316,23 @@ const queryProjectStartYearValue = computed({
}
})
const exportDialogTitle = computed(() =>
exportDialogType.value === 'projectQuarter' ? '导出专业间年度季度计取表' : '导出项目负责人计取表'
)
const currentExportLoading = computed(() =>
exportDialogType.value === 'projectQuarter'
? exportQuarterLoading.value
: exportLeadLoading.value
)
const exportYearValue = computed({
get: () => (exportYear.value ? String(exportYear.value) : undefined),
set: (value?: string) => {
exportYear.value = value ? Number(value) : undefined
}
})
const toNumeric = (value?: number | string | null) => {
const numericValue = Number(value ?? 0)
return Number.isNaN(numericValue) ? 0 : numericValue
@@ -552,14 +597,9 @@ const handleExportProjectQuarter = async () => {
message.warning('请先选择合约规划')
return
}
try {
await message.exportConfirm()
exportQuarterLoading.value = true
const data = await ReportApi.exportProjectQuarterOutput(currentPlanning.value.id)
download.excel(data, '专业间年度季度计取表.xlsx')
} finally {
exportQuarterLoading.value = false
}
exportDialogType.value = 'projectQuarter'
exportYear.value = currentPlanning.value.planningStartYear || currentYear
exportDialogVisible.value = true
}
const handleExportProjectLeadQuarter = async () => {
@@ -567,11 +607,66 @@ const handleExportProjectLeadQuarter = async () => {
message.warning('请先选择合约规划')
return
}
exportDialogType.value = 'projectLeadQuarter'
exportYear.value = currentPlanning.value.planningStartYear || currentYear
exportDialogVisible.value = true
}
const submitExport = async () => {
if (exportDialogType.value === 'projectQuarter') {
await submitProjectQuarterExport()
return
}
await submitProjectLeadQuarterExport()
}
const submitProjectQuarterExport = async () => {
if (!currentPlanning.value?.id) {
message.warning('请先选择合约规划')
return
}
if (!exportYear.value) {
message.warning('请选择年度')
return
}
try {
await message.exportConfirm()
exportQuarterLoading.value = true
const data = await ReportApi.exportProjectQuarterOutput({
planningId: currentPlanning.value.id,
year: exportYear.value
})
download.excel(
data,
`${currentProject.value?.projectName || '项目'}_${exportYear.value}_专业间年度季度计取表.xlsx`
)
exportDialogVisible.value = false
} finally {
exportQuarterLoading.value = false
}
}
const submitProjectLeadQuarterExport = async () => {
if (!currentPlanning.value?.id) {
message.warning('请先选择合约规划')
return
}
if (!exportYear.value) {
message.warning('请选择年度')
return
}
try {
await message.exportConfirm()
exportLeadLoading.value = true
const data = await ReportApi.exportProjectLeadQuarterOutput(currentPlanning.value.id)
download.excel(data, '项目负责人年度季度计取表.xlsx')
const data = await ReportApi.exportProjectLeadQuarterOutput({
planningId: currentPlanning.value.id,
year: exportYear.value
})
download.excel(
data,
`${currentProject.value?.projectName || '项目'}_${exportYear.value}_项目负责人年度季度计取表.xlsx`
)
exportDialogVisible.value = false
} finally {
exportLeadLoading.value = false
}

View File

@@ -172,6 +172,28 @@
</ContentWrap>
</el-col>
</el-row>
<Dialog v-model="exportDialogVisible" title="导出专业内人员计取表" width="420">
<el-form label-width="88px">
<el-form-item label="年度">
<el-date-picker
v-model="exportYearValue"
class="!w-220px"
clearable
placeholder="请选择年度"
type="year"
value-format="YYYY"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="exportDialogVisible = false">取消</el-button>
<el-button :loading="exportSpecialtyLoading" type="primary" @click="submitSpecialtyExport">
确定导出
</el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
@@ -199,10 +221,13 @@ interface SpecialtyGroup {
}
const message = useMessage()
const currentYear = new Date().getFullYear()
const loading = ref(false)
const planningLoading = ref(false)
const exportSpecialtyLoading = ref(false)
const exportDialogVisible = ref(false)
const exportYear = ref<number>()
const total = ref(0)
const projectList = ref<ProjectApi.ProjectVO[]>([])
const planningList = ref<PlanningApi.ProjectPlanningVO[]>([])
@@ -228,6 +253,13 @@ const queryProjectStartYearValue = computed({
}
})
const exportYearValue = computed({
get: () => (exportYear.value ? String(exportYear.value) : undefined),
set: (value?: string) => {
exportYear.value = value ? Number(value) : undefined
}
})
const roundRatio = (value?: number | string | null) => {
const numericValue = Number(value || 0)
return Number.isNaN(numericValue) ? 0 : Number(numericValue.toFixed(4))
@@ -393,14 +425,36 @@ const handleExportSpecialtyPerson = async () => {
message.warning('请选择具体专业后再导出')
return
}
exportYear.value = currentPlanning.value.planningStartYear || currentYear
exportDialogVisible.value = true
}
const submitSpecialtyExport = async () => {
if (!currentPlanning.value?.id) {
message.warning('请先选择合约规划')
return
}
if (!canExportCurrentGroup.value || !currentGroup.value?.specialtyCode) {
message.warning('请选择具体专业后再导出')
return
}
if (!exportYear.value) {
message.warning('请选择年度')
return
}
try {
await message.exportConfirm()
exportSpecialtyLoading.value = true
const data = await ReportApi.exportSpecialtyPersonOutput({
planningId: currentPlanning.value.id,
specialtyCode: currentGroup.value.specialtyCode
specialtyCode: currentGroup.value.specialtyCode,
year: exportYear.value
})
download.excel(data, `${currentGroup.value.specialtyName || '专业'}内人员年度季度计取表.xlsx`)
download.excel(
data,
`${currentProject.value?.projectName || '项目'}_${exportYear.value}_${currentGroup.value.specialtyName || '专业'}内人员年度季度计取表.xlsx`
)
exportDialogVisible.value = false
} finally {
exportSpecialtyLoading.value = false
}

View File

@@ -145,7 +145,6 @@
v-model="projectOverviewYearValue"
class="!w-220px"
clearable
disabled
placeholder="请选择年度"
type="year"
value-format="YYYY"
@@ -268,8 +267,10 @@ const currentOfficeName = computed(() => {
})
const projectOverviewYearValue = computed({
get: () => String(currentYear),
set: (_value?: string) => undefined
get: () => (projectOverviewForm.year ? String(projectOverviewForm.year) : undefined),
set: (value?: string) => {
projectOverviewForm.year = value ? Number(value) : undefined
}
})
const employeeSummaryYearValue = computed({
@@ -336,6 +337,9 @@ const openProjectOverviewExportDialog = () => {
message.warning('请选择专业所')
return
}
if (!projectOverviewForm.year) {
projectOverviewForm.year = currentYear
}
exportDialogType.value = 'projectOverview'
exportDialogVisible.value = true
}
@@ -353,15 +357,17 @@ const handleExportProjectOverview = async () => {
message.warning('请选择专业所')
return
}
if (!projectOverviewYearValue.value) {
if (!projectOverviewForm.year) {
message.warning('请选择年度')
return
}
try {
projectOverviewExportLoading.value = true
projectOverviewForm.year = currentYear
const data = await ReportApi.exportProjectOverview(projectOverviewForm)
download.excel(data, '项目总览表.xlsx')
download.excel(
data,
`${currentOfficeName.value || '专业所'}_${projectOverviewForm.year}_项目总览表.xlsx`
)
exportDialogVisible.value = false
} finally {
projectOverviewExportLoading.value = false
@@ -376,7 +382,7 @@ const handleExportEmployeeSummary = async () => {
try {
employeeSummaryExportLoading.value = true
const data = await ReportApi.exportEmployeeOutputSummary(employeeSummaryForm)
download.excel(data, '员工个人考核产值汇总表.xlsx')
download.excel(data, `${employeeSummaryForm.year}_员工个人考核产值汇总表.xlsx`)
exportDialogVisible.value = false
} finally {
employeeSummaryExportLoading.value = false

View File

@@ -455,7 +455,6 @@ const searchEmployees = async (keyword: string) => {
try {
employeeOptions.value = await EmployeeApi.getEmployeeSimpleList({
keyword,
status: '在职',
enabledFlag: true
})
editRoleList.value.forEach((row) => ensureEmployeeOptions(row.persons))