admin-vue3/src/views/submit/companyReport/index.vue

512 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div class="app-container">
<!-- 搜索区域 -->
<div class="filter-container">
<el-card shadow="never">
<el-form :inline="true" :model="queryParams" class="demo-form-inline">
<el-form-item label="标题">
<el-input
v-model="queryParams.title"
placeholder="请输入标题"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="部门">
<el-select
v-model="queryParams.deptId"
placeholder="请选择部门"
clearable
style="width: 200px"
>
<el-option
v-for="dept in deptOptions"
:key="dept.id"
:label="dept.name"
:value="dept.id"
/>
</el-select>
</el-form-item>
<el-form-item label="涉事人">
<el-input
v-model="queryParams.personName"
placeholder="请输入涉事人"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="事项">
<el-select
v-model="queryParams.reasonId"
placeholder="请选择事项"
clearable
style="width: 200px"
>
<el-option
v-for="item in reportTypeOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="填报时间">
<el-date-picker
v-model="createTimeRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery" :loading="loading">
</el-button>
<el-button @click="resetQuery"></el-button>
</el-form-item>
</el-form>
</el-card>
</div>
<!-- 表格区域 -->
<div class="table-container">
<el-card shadow="never">
<!-- 表格操作区域 -->
<div class="table-header">
<el-button
@click="handleExport"
:loading="exportLoading"
type="success"
plain
>
<el-icon class="mr5"><Download /></el-icon>
导出
</el-button>
</div>
<!-- 表格 -->
<el-table
v-loading="loading"
:data="reportList"
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
stripe
style="width: 100%"
>
<el-table-column
label="标题"
prop="title"
min-width="200"
align="center"
show-overflow-tooltip
/>
<el-table-column
label="部门"
prop="deptName"
min-width="120"
align="center"
/>
<el-table-column
label="涉事人"
prop="personName"
min-width="120"
align="center"
/>
<el-table-column label="事项" min-width="120" align="center">
<template #default="{ row }">
{{ row.reportTypeName || row.reason?.name || '-' }}
</template>
</el-table-column>
<el-table-column label="分值" min-width="80" align="center">
<template #default="scope">
{{ scope.row.userIntegralRecords?.[0]?.score || '-' }}
</template>
</el-table-column>
<el-table-column
label="备注"
prop="remark"
min-width="250"
align="center"
show-overflow-tooltip
>
<template #default="scope">
{{ scope.row.remark || '-' }}
</template>
</el-table-column>
<el-table-column label="填报时间" min-width="160" align="center">
<template #default="scope">
{{ formatDisplayTime(scope.row.fillingTime) }}
</template>
</el-table-column>
<el-table-column label="状态" min-width="100" align="center">
<template #default="scope">
<el-tag :type="getStatusTagType(scope.row.status)">
{{ getStatusText(scope.row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column
label="操作"
min-width="120"
align="center"
fixed="right"
>
<template #default="scope">
<span class="table-opera-item" @click="handleView(scope.row)" :title="'查看'">
<el-icon class="table-opera-icon"><View /></el-icon>
</span>
</template>
</el-table-column>
</el-table>
<!-- 空状态 -->
<template v-if="!loading && reportList.length === 0">
<el-empty description="暂无公司通报数据" :image-size="200" />
</template>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</el-card>
</div>
<!-- 查看详情组件 -->
<DetailComponent
v-model:visible="detailDialogVisible"
:report-id="selectedReportId"
@refresh="getList"
/>
</div>
</template>
<script setup lang="ts">
import { onMounted, reactive, ref, computed, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { View, Download } from '@element-plus/icons-vue'
import { useRoute } from 'vue-router'
import Pagination from '@/components/Pagination/index.vue'
import DetailComponent from '../common/detail.vue'
import { useDeptStore } from '@/store/modules/dept'
import { useRewardsPunishReasonStore } from '@/store/modules/rewardsPunishReason'
import { enumsType } from '@/store/static'
import { getMyReportPage, exportMyReport } from '@/api/submit/myReporting'
import type { MyReportDTO, QueryParams } from '@/api/submit/myReporting/types'
// Store
const deptStore = useDeptStore()
const rewardsPunishReasonStore = useRewardsPunishReasonStore()
// Router
const route = useRoute()
//
const deptOptions = computed(() => deptStore.getDeptList)
const reportTypeOptions = computed(() => rewardsPunishReasonStore.getRewardsPunishReasonList)
// 响应式数据
const reportList = ref<MyReportDTO[]>([])
const total = ref<number>(0)
const loading = ref<boolean>(true)
const exportLoading = ref<boolean>(false)
const createTimeRange = ref<string[] | null>(null)
const detailDialogVisible = ref<boolean>(false)
const selectedReportId = ref<string>('')
// 查询参数
const queryParams = reactive<QueryParams>({
pageNo: 1,
pageSize: 10,
title: '',
deptId: '',
personName: '',
reasonId: '',
status: '',
startTime: undefined,
endTime: undefined,
isOwnReport: false,
isOwnApprove: false
})
// 工具函数
const getStatusTagType = (status: string | number) => {
const typeMap: Record<string | number, string> = {
'STAGING': 'warning', 1: 'warning',
'AUDIT': 'primary', 2: 'primary',
'APPROVED': 'success', 3: 'success',
'REVIEW_FAILED': 'danger', 4: 'danger'
}
return typeMap[status] || 'info'
}
const getStatusText = (status: string | number) => {
const statusItem = enumsType.reportStatus.find(item => item.value === status)
return statusItem ? statusItem.text : '未知状态'
}
const formatDateTime = (dateTime: string | undefined) => {
if (!dateTime) return ''
const date = new Date(dateTime)
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
})
}
const formatDisplayTime = (time: string | undefined) => {
return time ? formatDateTime(time) : '-'
}
// 初始化
onMounted(async () => {
await initData()
})
// 初始化数据
const initData = async () => {
try {
// 并行加载数据
await Promise.all([
!deptStore.getIsSetDept ? deptStore.setDeptInfoAction() : Promise.resolve(),
!rewardsPunishReasonStore.getIsSetRewardsPunishReason ? rewardsPunishReasonStore.setRewardsPunishReasonAction() : Promise.resolve()
])
await getList()
} catch (error) {
console.error('初始化数据失败:', error)
ElMessage.error('初始化数据失败')
}
}
// 获取列表
const getList = async () => {
loading.value = true
queryParams.status = 'APPROVED'
try {
const response = await getMyReportPage(queryParams)
reportList.value = response.list || []
total.value = response.total || 0
} catch (error) {
console.error('获取公司通报列表失败:', error)
ElMessage.error('获取公司通报列表失败')
} finally {
loading.value = false
}
}
// 查询
const handleQuery = () => {
queryParams.pageNo = 1
// 处理填报时间范围
if (createTimeRange.value && createTimeRange.value.length === 2) {
queryParams.startTime = createTimeRange.value[0] + 'T00:00:00'
queryParams.endTime = createTimeRange.value[1] + 'T23:59:59'
} else {
queryParams.startTime = undefined
queryParams.endTime = undefined
}
getList()
}
// 重置查询
const resetQuery = () => {
Object.assign(queryParams, {
pageNo: 1,
pageSize: 10,
title: '',
deptId: '',
personName: '',
reasonId: '',
status: '',
startTime: undefined,
endTime: undefined,
isOwnReport: false,
isOwnApprove: false
})
createTimeRange.value = null
getList()
}
// 查看详情
const handleView = (row: MyReportDTO) => {
selectedReportId.value = row.id
detailDialogVisible.value = true
}
// 监听路由变化,当路由发生变化时关闭抽屉
watch(
[() => route.path, () => route.fullPath],
() => {
detailDialogVisible.value = false
},
{ immediate: true }
)
// 导出
const handleExport = async () => {
exportLoading.value = true
try {
const response = await exportMyReport(queryParams)
// 处理文件下载
if (response instanceof Blob) {
// 创建Blob URL
const blob = new Blob([response], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8'
})
const url = window.URL.createObjectURL(blob)
// 创建下载链接
const link = document.createElement('a')
link.href = url
// 从响应头获取文件名,或者使用默认文件名
const filename = getFilenameFromResponse(response) || `公司通报数据.xlsx`
link.download = filename
// 触发下载
document.body.appendChild(link)
link.click()
// 清理
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
ElMessage.success('导出成功')
} else {
ElMessage.success('导出请求已发送')
}
} catch (error) {
console.error('导出失败:', error)
ElMessage.error('导出失败')
} finally {
exportLoading.value = false
}
}
// 从响应中获取文件名
const getFilenameFromResponse = (response: any) => {
try {
// 如果是Response对象从headers中获取
if (response.headers) {
const contentDisposition = response.headers.get('content-disposition')
if (contentDisposition) {
const filenameMatch = contentDisposition.match(/filename="?(.+?)\"?/i)
if (filenameMatch && filenameMatch[1]) {
return decodeURIComponent(filenameMatch[1])
}
}
}
} catch (e) {
console.warn('获取文件名失败:', e)
}
return null
}
</script>
<style scoped>
.app-container {
padding: 20px;
}
.filter-container {
margin-bottom: 20px;
}
.table-container {
margin-top: 20px;
}
.table-header {
margin-bottom: 15px;
display: flex;
justify-content: flex-start;
align-items: center;
}
:deep(.el-card) {
border: 1px solid #e4e7ed;
border-radius: 4px;
}
:deep(.el-card__body) {
padding: 20px;
}
:deep(.el-table) {
border: 1px solid #e4e7ed;
border-radius: 4px;
}
:deep(.el-table th) {
background-color: #f5f7fa;
font-weight: 600;
}
:deep(.el-table--striped .el-table__body tr.el-table__row--striped td) {
background-color: #fafafa;
}
:deep(.el-form-item__label) {
font-weight: 500;
color: #606266;
}
/* 操作列样式 */
.table-opera-item {
cursor: pointer;
color: #409eff;
transition: color 0.3s;
display: inline-block;
}
.table-opera-item:hover {
color: #66b1ff;
}
.table-opera-icon {
font-size: 18px;
vertical-align: middle;
}
.mr5 {
margin-right: 5px;
}
.ml10 {
margin-left: 10px;
}
.delete-icon {
color: #eb333d;
}
.delete-icon:hover {
color: #f56c6c;
}
.loading-icon {
animation: rotate 1s linear infinite;
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
</style>