物资类别
-
+
{{ selectedAllocation?.category }}
@@ -325,29 +325,35 @@
-
+
-
-
+
分配配额
{{ formatNumber(selectedDistribution?.unitQuota || 0) }}
-
+
累计消耗
{{ formatNumber(selectedDistribution?.actualCompletion || 0) }}
-
+
+
+
上报单位数
+
{{ unitSummaries.length }}
+
+
+
上报次数
{{ consumptionReports.length }}
@@ -355,40 +361,87 @@
-
-
-
-
-
- +{{ formatNumber(row.reportedAmount) }}
+
+
+
+
+
+
+
+
+ {{ row.unitName }}
+
+
+
+
+
+
+ {{ getUnitLevelText(row.unitLevel) }}
+
+
+
+
+
+ {{ formatNumber(row.totalReported) }}
+
+
+
+
+ {{ row.reportCount }} 次
+
+
+
+
+ {{ row.lastReportedAt ? formatDate(row.lastReportedAt) : '-' }}
+
+
+
+
+
-
-
-
- {{ formatNumber(row.cumulativeAmount) }}
+
+
+
+
+
+
+
+ +{{ formatNumber(row.reportedAmount) }}
+
+
+
+
+ {{ formatNumber(row.cumulativeAmount) }}
+
+
+
+
+ {{ row.reportedByUserName || '-' }}
+
+
+
+
+ {{ formatDate(row.reportedAt) }}
+
+
+
+
+
-
-
-
- {{ row.reportedByUserName || '-' }}
-
-
-
-
- {{ formatDate(row.reportedAt) }}
-
-
-
-
-
-
-
+
+
@@ -399,7 +452,7 @@ import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Search, View, Edit, Delete, Box, Setting, OfficeBuilding, Clock } from '@element-plus/icons-vue'
import { useAuthStore } from '@/stores/auth'
-import { allocationsApi } from '@/api'
+import { allocationsApi, type UnitReportSummary } from '@/api'
import type { MaterialAllocation, AllocationDistribution } from '@/types'
const router = useRouter()
@@ -408,6 +461,8 @@ const authStore = useAuthStore()
const allocations = ref([])
const distributions = ref([])
const consumptionReports = ref([])
+const unitSummaries = ref([])
+const reportsTabActive = ref('summary')
const loading = ref(false)
const loadingReports = ref(false)
const showDistributionDialog = ref(false)
@@ -458,7 +513,7 @@ function getCategoryTagType(category: string): string {
case '装备': return 'warning'
case '物资': return 'success'
case '器材': return 'info'
- default: return ''
+ default: return 'info'
}
}
@@ -468,6 +523,26 @@ function getProgressStatus(rate: number): string {
return 'warning'
}
+function getUnitLevelTagType(level: string): string {
+ switch (level) {
+ case 'Division': return 'danger'
+ case 'Regiment': return 'warning'
+ case 'Battalion': return 'success'
+ case 'Company': return 'info'
+ default: return 'info'
+ }
+}
+
+function getUnitLevelText(level: string): string {
+ switch (level) {
+ case 'Division': return '师团'
+ case 'Regiment': return '团'
+ case 'Battalion': return '营'
+ case 'Company': return '连'
+ default: return level
+ }
+}
+
function getDistributionPercentage(allocation: MaterialAllocation): number {
if (!allocation.distributions || allocation.distributions.length === 0) return 0
const distributed = allocation.distributions.reduce((sum, d) => sum + (d.unitQuota || 0), 0)
@@ -556,13 +631,21 @@ function handleReportFromSummary() {
async function handleViewReports(distribution: AllocationDistribution) {
selectedDistribution.value = distribution
showReportsDialog.value = true
+ reportsTabActive.value = 'summary'
loadingReports.value = true
try {
- const reports = await allocationsApi.getConsumptionReports(distribution.id)
+ // 同时加载汇总数据和明细数据
+ const [reports, summaries] = await Promise.all([
+ allocationsApi.getConsumptionReports(distribution.id),
+ allocationsApi.getReportSummaryByUnit(distribution.id)
+ ])
consumptionReports.value = reports
- } catch {
+ unitSummaries.value = summaries
+ } catch (error) {
+ console.error('加载上报记录失败', error)
ElMessage.error('加载上报记录失败')
consumptionReports.value = []
+ unitSummaries.value = []
} finally {
loadingReports.value = false
}
diff --git a/src/frontend/src/views/allocations/AllocationReport.vue b/src/frontend/src/views/allocations/AllocationReport.vue
index 91a3f06..31f54ea 100644
--- a/src/frontend/src/views/allocations/AllocationReport.vue
+++ b/src/frontend/src/views/allocations/AllocationReport.vue
@@ -75,6 +75,20 @@
label-width="120px"
@submit.prevent="handleSubmit"
>
+
+
+
+
+ 营部
+ 上报到所属营部
+
+
+ 团部
+ 直接上报到团部
+
+
+
+
上报历史
@@ -160,7 +174,7 @@
- {{ formatNumber(row.cumulativeAmount) }}
+ {{ formatNumber(row.calculatedCumulativeAmount) }}
@@ -205,7 +219,13 @@ const formRef = ref()
const form = reactive({
actualCompletion: 0,
- remarks: ''
+ remarks: '',
+ reportToLevel: '' as string // 上报目标级别:Battalion(营部)或 Regiment(团部)
+})
+
+// 判断是否为连部级别(Company = 4)
+const isCompanyLevel = computed(() => {
+ return authStore.organizationalLevelNum === 4
})
// 判断是否为营部及以下级别(Battalion = 3, Company = 4)
@@ -220,6 +240,24 @@ const totalReportedAmount = computed(() => {
return consumptionReports.value.reduce((sum, report) => sum + report.reportedAmount, 0)
})
+// 重新计算累计数量(基于可见范围内的上报记录)
+// 因为数据库中的cumulativeAmount是所有单位的累计,需要根据过滤后的记录重新计算
+const reportsWithRecalculatedCumulative = computed(() => {
+ // 按时间正序排列计算累计
+ const sortedByTimeAsc = [...consumptionReports.value].sort(
+ (a, b) => new Date(a.reportedAt).getTime() - new Date(b.reportedAt).getTime()
+ )
+
+ let cumulative = 0
+ const reportsWithCumulative = sortedByTimeAsc.map(report => {
+ cumulative += report.reportedAmount
+ return { ...report, calculatedCumulativeAmount: cumulative }
+ })
+
+ // 按时间倒序返回(与原始顺序一致)
+ return reportsWithCumulative.reverse()
+})
+
// 根据用户级别动态生成验证规则
const formRules = computed(() => {
const baseRules: FormRules = {
@@ -234,6 +272,13 @@ const formRules = computed(() => {
]
}
+ // 连部需要选择上报目标
+ if (isCompanyLevel.value) {
+ baseRules.reportToLevel = [
+ { required: true, message: '请选择上报目标', trigger: 'change' }
+ ]
+ }
+
// 只有师团/团级才需要验证不超过剩余配额
if (!isBattalionOrBelow.value) {
baseRules.actualCompletion.push({
@@ -266,7 +311,7 @@ function getCategoryTagType(category?: string): string {
case '装备': return 'warning'
case '物资': return 'success'
case '器材': return 'info'
- default: return ''
+ default: return 'info'
}
}
@@ -295,10 +340,16 @@ async function handleSubmit() {
const newTotal = (distribution.value?.actualCompletion || 0) + form.actualCompletion
- // 营部及以下级别的确认信息不显示总数,使用"消耗"文字
- const confirmMessage = isBattalionOrBelow.value
- ? `本次消耗数量:${form.actualCompletion} ${allocation.value?.unit}\n\n确认提交吗?`
- : `本次上报数量:${form.actualCompletion} ${allocation.value?.unit}\n上报后总数:${newTotal} ${allocation.value?.unit}\n\n确认提交吗?`
+ // 构建确认信息
+ let confirmMessage = ''
+ if (isCompanyLevel.value) {
+ const targetName = form.reportToLevel === 'Battalion' ? '营部' : '团部'
+ confirmMessage = `本次消耗数量:${form.actualCompletion} ${allocation.value?.unit}\n上报目标:${targetName}\n\n确认提交吗?`
+ } else if (isBattalionOrBelow.value) {
+ confirmMessage = `本次消耗数量:${form.actualCompletion} ${allocation.value?.unit}\n\n确认提交吗?`
+ } else {
+ confirmMessage = `本次上报数量:${form.actualCompletion} ${allocation.value?.unit}\n上报后总数:${newTotal} ${allocation.value?.unit}\n\n确认提交吗?`
+ }
await ElMessageBox.confirm(
confirmMessage,
@@ -314,9 +365,17 @@ async function handleSubmit() {
if (!distribution.value) return
+ // 构建备注信息(包含上报目标)
+ let remarks = form.remarks || ''
+ if (isCompanyLevel.value && form.reportToLevel) {
+ const targetName = form.reportToLevel === 'Battalion' ? '营部' : '团部'
+ remarks = remarks ? `[上报到${targetName}] ${remarks}` : `[上报到${targetName}]`
+ }
+
// 累加到已有数量
await allocationsApi.updateDistribution(distribution.value.id, {
- actualCompletion: newTotal
+ actualCompletion: newTotal,
+ remarks: remarks
})
ElMessage.success(isBattalionOrBelow.value ? '消耗提交成功' : '上报成功')
@@ -560,4 +619,23 @@ onMounted(() => {
.history-section :deep(.el-table) {
margin-top: 16px;
}
+
+.radio-desc {
+ margin-left: 8px;
+ color: #909399;
+ font-size: 13px;
+}
+
+:deep(.el-radio-group) {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+:deep(.el-radio) {
+ display: flex;
+ align-items: center;
+ height: auto;
+ padding: 8px 0;
+}
diff --git a/src/frontend/src/views/approvals/ApprovalDetail.vue b/src/frontend/src/views/approvals/ApprovalDetail.vue
index 0ba6286..d29e83f 100644
--- a/src/frontend/src/views/approvals/ApprovalDetail.vue
+++ b/src/frontend/src/views/approvals/ApprovalDetail.vue
@@ -95,7 +95,7 @@ function getTypeTagType(type: ApprovalRequestType): string {
case ApprovalRequestType.AllocationModification: return 'primary'
case ApprovalRequestType.ReportModification: return 'success'
case ApprovalRequestType.PersonnelModification: return 'warning'
- default: return ''
+ default: return 'info'
}
}
@@ -113,7 +113,7 @@ function getStatusTagType(status: ApprovalStatus): string {
case ApprovalStatus.Pending: return 'warning'
case ApprovalStatus.Approved: return 'success'
case ApprovalStatus.Rejected: return 'danger'
- default: return ''
+ default: return 'info'
}
}
diff --git a/src/frontend/src/views/approvals/ApprovalList.vue b/src/frontend/src/views/approvals/ApprovalList.vue
index d9c8af5..8e2e555 100644
--- a/src/frontend/src/views/approvals/ApprovalList.vue
+++ b/src/frontend/src/views/approvals/ApprovalList.vue
@@ -138,7 +138,7 @@ function getTypeTagType(type: ApprovalRequestType): string {
case ApprovalRequestType.AllocationModification: return 'primary'
case ApprovalRequestType.ReportModification: return 'success'
case ApprovalRequestType.PersonnelModification: return 'warning'
- default: return ''
+ default: return 'info'
}
}
@@ -156,7 +156,7 @@ function getStatusTagType(status: ApprovalStatus): string {
case ApprovalStatus.Pending: return 'warning'
case ApprovalStatus.Approved: return 'success'
case ApprovalStatus.Rejected: return 'danger'
- default: return ''
+ default: return 'info'
}
}
diff --git a/src/frontend/src/views/organizations/OrganizationList.vue b/src/frontend/src/views/organizations/OrganizationList.vue
index 926bb3f..93f4b06 100644
--- a/src/frontend/src/views/organizations/OrganizationList.vue
+++ b/src/frontend/src/views/organizations/OrganizationList.vue
@@ -201,7 +201,7 @@ function getLevelTagType(level: OrganizationalLevel): string {
case OrganizationalLevel.Regiment: return 'warning'
case OrganizationalLevel.Battalion: return 'success'
case OrganizationalLevel.Company: return 'info'
- default: return ''
+ default: return 'info'
}
}
diff --git a/src/frontend/src/views/personnel/PersonnelDetail.vue b/src/frontend/src/views/personnel/PersonnelDetail.vue
index eac469f..a1c647d 100644
--- a/src/frontend/src/views/personnel/PersonnelDetail.vue
+++ b/src/frontend/src/views/personnel/PersonnelDetail.vue
@@ -104,7 +104,7 @@ function getStatusTagType(status: PersonnelStatus): string {
case PersonnelStatus.Pending: return 'warning'
case PersonnelStatus.Approved: return 'success'
case PersonnelStatus.Rejected: return 'danger'
- default: return ''
+ default: return 'info'
}
}
@@ -124,7 +124,7 @@ function getLevelTagType(level: PersonnelLevel): string {
case PersonnelLevel.Regiment: return 'warning'
case PersonnelLevel.Battalion: return 'success'
case PersonnelLevel.Company: return 'info'
- default: return ''
+ default: return 'info'
}
}
diff --git a/src/frontend/src/views/personnel/PersonnelList.vue b/src/frontend/src/views/personnel/PersonnelList.vue
index 37f3674..6fb87e8 100644
--- a/src/frontend/src/views/personnel/PersonnelList.vue
+++ b/src/frontend/src/views/personnel/PersonnelList.vue
@@ -54,7 +54,7 @@
-
+
{{ row.gender }}
@@ -202,7 +202,7 @@ function getStatusTagType(status: PersonnelStatus): string {
case PersonnelStatus.Pending: return 'warning'
case PersonnelStatus.Approved: return 'success'
case PersonnelStatus.Rejected: return 'danger'
- default: return ''
+ default: return 'info'
}
}
@@ -222,7 +222,7 @@ function getLevelTagType(level: PersonnelLevel): string {
case PersonnelLevel.Regiment: return 'warning'
case PersonnelLevel.Battalion: return 'success'
case PersonnelLevel.Company: return 'info'
- default: return ''
+ default: return 'info'
}
}
diff --git a/src/frontend/src/views/reports/ReportList.vue b/src/frontend/src/views/reports/ReportList.vue
index 0748431..b2295ad 100644
--- a/src/frontend/src/views/reports/ReportList.vue
+++ b/src/frontend/src/views/reports/ReportList.vue
@@ -1,77 +1,196 @@
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
{{ pagination.total }}
+
总配额数
+
+
+
+
+
+
+
+
{{ reportedCount }}
+
已上报
+
+
+
+
+
+
+
+
{{ pendingCount }}
+
待上报
+
+
+
+
+
+
+
+
{{ averageCompletionRate }}%
+
平均完成率
+
+
+
+
+
+
- {{ row.actualCompletion }}
- 未上报
+
+ {{ row.category }}
+
-
+
-
+ {{ row.materialName }}
-
-
+
+
- {{ row.reportedAt ? formatDate(row.reportedAt) : '-' }}
+ {{ formatNumber(row.unitQuota) }}
-
+
+
+
+ {{ formatNumber(row.actualCompletion) }}
+
+ 未上报
+
+
+
+
+
+
+
+
+
+
+
+ {{ row.targetUnitName }}
+
+
+
+
+
+
+ {{ formatDate(row.reportedAt) }}
+
+ -
+
+
+
+
{{ row.actualCompletion !== null ? '修改' : '上报' }}
-
-
+
+
-
+
-
+
+
+
+ {{ selectedReport?.category }}
+
+
-
+ {{ selectedReport?.materialName }}
-
+ {{ formatNumber(selectedReport?.unitQuota || 0) }} {{ selectedReport?.unit }}
+
+ {{ formatNumber(selectedReport?.actualCompletion || 0) }} {{ selectedReport?.unit }}
+
+
-
+
+ {{ selectedReport?.unit }}
+
+
+
取消
- 提交
+
+
+ 确认提交
+
@@ -80,6 +199,7 @@
diff --git a/src/frontend/src/views/reports/ReportSummary.vue b/src/frontend/src/views/reports/ReportSummary.vue
index f9dd3eb..9eb8d26 100644
--- a/src/frontend/src/views/reports/ReportSummary.vue
+++ b/src/frontend/src/views/reports/ReportSummary.vue
@@ -120,7 +120,7 @@ function getLevelTagType(level: OrganizationalLevel): string {
case OrganizationalLevel.Regiment: return 'warning'
case OrganizationalLevel.Battalion: return 'success'
case OrganizationalLevel.Company: return 'info'
- default: return ''
+ default: return 'info'
}
}