分配
This commit is contained in:
parent
a3f4714c09
commit
29cfc4d700
|
|
@ -229,6 +229,39 @@ public class AllocationsController : BaseApiController
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上报消耗 - 更新单个分配记录的实际完成数量
|
||||
/// 团部及以下单位可以上报自己的消耗数据
|
||||
/// </summary>
|
||||
[HttpPut("distributions/{distributionId}")]
|
||||
public async Task<IActionResult> UpdateDistribution(int distributionId, [FromBody] UpdateDistributionRequest request)
|
||||
{
|
||||
var unitId = GetCurrentUnitId();
|
||||
var userId = GetCurrentUserId();
|
||||
|
||||
if (unitId == null || userId == null)
|
||||
return Unauthorized(new { message = "无法获取用户信息" });
|
||||
|
||||
try
|
||||
{
|
||||
var distribution = await _allocationService.UpdateDistributionCompletionAsync(
|
||||
distributionId,
|
||||
unitId.Value,
|
||||
userId.Value,
|
||||
request.ActualCompletion);
|
||||
|
||||
return Ok(MapDistributionToResponse(distribution));
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
return BadRequest(new { message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除物资配额
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -162,6 +162,7 @@ public class PersonnelController : BaseApiController
|
|||
var personnel = new Personnel
|
||||
{
|
||||
Name = request.Name,
|
||||
Unit = request.Unit,
|
||||
Position = request.Position,
|
||||
Rank = request.Rank,
|
||||
Gender = "男", // 默认值
|
||||
|
|
@ -235,6 +236,7 @@ public class PersonnelController : BaseApiController
|
|||
var personnel = new Personnel
|
||||
{
|
||||
Name = request.Name,
|
||||
Unit = request.Unit,
|
||||
Position = request.Position,
|
||||
Rank = request.Rank,
|
||||
Gender = "男", // 默认值
|
||||
|
|
|
|||
|
|
@ -92,6 +92,19 @@ public class UpdateAllocationRequest
|
|||
public decimal TotalQuota { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新配额分配实际完成数量请求(上报消耗)
|
||||
/// </summary>
|
||||
public class UpdateDistributionRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 实际完成数量
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "实际完成数量为必填项")]
|
||||
[Range(0, double.MaxValue, ErrorMessage = "实际完成数量不能为负数")]
|
||||
public decimal ActualCompletion { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 物资配额响应
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@ public class CreatePersonnelRequest
|
|||
[StringLength(50, MinimumLength = 2, ErrorMessage = "姓名长度应在2-50个字符之间")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[StringLength(100, ErrorMessage = "单位长度不能超过100个字符")]
|
||||
public string? Unit { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "职位不能为空")]
|
||||
[StringLength(100, ErrorMessage = "职位长度不能超过100个字符")]
|
||||
public string Position { get; set; } = string.Empty;
|
||||
|
|
@ -176,6 +179,9 @@ public class SubmitPersonnelRequest
|
|||
[StringLength(50, MinimumLength = 2, ErrorMessage = "姓名长度应在2-50个字符之间")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[StringLength(100, ErrorMessage = "单位长度不能超过100个字符")]
|
||||
public string? Unit { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "职位不能为空")]
|
||||
[StringLength(100, ErrorMessage = "职位长度不能超过100个字符")]
|
||||
public string Position { get; set; } = string.Empty;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ public class Personnel
|
|||
public int Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string? PhotoPath { get; set; }
|
||||
public string? Unit { get; set; } // 单位
|
||||
public string Position { get; set; } = string.Empty;
|
||||
public string Rank { get; set; } = string.Empty;
|
||||
public string Gender { get; set; } = string.Empty;
|
||||
|
|
|
|||
|
|
@ -155,6 +155,25 @@ using (var scope = app.Services.CreateScope())
|
|||
{
|
||||
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
var authService = scope.ServiceProvider.GetRequiredService<IAuthenticationService>();
|
||||
var environment = scope.ServiceProvider.GetRequiredService<IWebHostEnvironment>();
|
||||
|
||||
// 确保uploads目录存在并有写入权限
|
||||
try
|
||||
{
|
||||
var uploadsPath = Path.Combine(environment.WebRootPath ?? environment.ContentRootPath, "uploads");
|
||||
var photosPath = Path.Combine(uploadsPath, "photos");
|
||||
var documentsPath = Path.Combine(uploadsPath, "documents");
|
||||
|
||||
Directory.CreateDirectory(uploadsPath);
|
||||
Directory.CreateDirectory(photosPath);
|
||||
Directory.CreateDirectory(documentsPath);
|
||||
|
||||
Console.WriteLine($"Uploads 目录已创建: {uploadsPath}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"创建 uploads 目录时出错: {ex.Message}");
|
||||
}
|
||||
|
||||
// 确保数据库已创建
|
||||
context.Database.EnsureCreated();
|
||||
|
|
@ -187,6 +206,9 @@ using (var scope = app.Services.CreateScope())
|
|||
try
|
||||
{
|
||||
context.Database.ExecuteSqlRaw(@"
|
||||
IF NOT EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID('Personnel') AND name = 'Unit')
|
||||
ALTER TABLE Personnel ADD Unit nvarchar(100) NULL;
|
||||
|
||||
IF NOT EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID('Personnel') AND name = 'Ethnicity')
|
||||
ALTER TABLE Personnel ADD Ethnicity nvarchar(50) NULL;
|
||||
|
||||
|
|
|
|||
|
|
@ -58,13 +58,25 @@ public class AllocationService : IAllocationService
|
|||
var allUnitIds = new HashSet<int>(subordinateIds) { unitId };
|
||||
|
||||
// 获取分配给这些单位的配额
|
||||
return await _context.MaterialAllocations
|
||||
var allocations = await _context.MaterialAllocations
|
||||
.Include(a => a.CreatedByUnit)
|
||||
.Include(a => a.Distributions)
|
||||
.ThenInclude(d => d.TargetUnit)
|
||||
.Include(a => a.Distributions)
|
||||
.ThenInclude(d => d.ReportedByUser)
|
||||
.Where(a => a.Distributions.Any(d => allUnitIds.Contains(d.TargetUnitId)))
|
||||
.OrderByDescending(a => a.CreatedAt)
|
||||
.ToListAsync();
|
||||
|
||||
// 过滤每个配额的分配记录,只保留分配给当前单位及其下级的记录
|
||||
foreach (var allocation in allocations)
|
||||
{
|
||||
allocation.Distributions = allocation.Distributions
|
||||
.Where(d => allUnitIds.Contains(d.TargetUnitId))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
return allocations;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<AllocationDistribution>> GetDistributionsForUnitAsync(int unitId)
|
||||
|
|
@ -229,6 +241,42 @@ public class AllocationService : IAllocationService
|
|||
return await GetByIdAsync(allocationId) ?? allocation;
|
||||
}
|
||||
|
||||
public async Task<AllocationDistribution> UpdateDistributionCompletionAsync(
|
||||
int distributionId,
|
||||
int unitId,
|
||||
int userId,
|
||||
decimal actualCompletion)
|
||||
{
|
||||
var distribution = await _context.AllocationDistributions
|
||||
.Include(d => d.Allocation)
|
||||
.Include(d => d.TargetUnit)
|
||||
.Include(d => d.ReportedByUser)
|
||||
.FirstOrDefaultAsync(d => d.Id == distributionId);
|
||||
|
||||
if (distribution == null)
|
||||
throw new ArgumentException("配额分配记录不存在");
|
||||
|
||||
// 验证权限:只能更新分配给自己单位的记录
|
||||
if (distribution.TargetUnitId != unitId)
|
||||
throw new UnauthorizedAccessException("无权更新此配额分配记录");
|
||||
|
||||
// 验证实际完成数量
|
||||
if (actualCompletion < 0)
|
||||
throw new ArgumentException("实际完成数量不能为负数");
|
||||
|
||||
if (actualCompletion > distribution.UnitQuota)
|
||||
throw new ArgumentException($"实际完成数量不能超过分配配额({distribution.UnitQuota})");
|
||||
|
||||
// 更新实际完成数量
|
||||
distribution.ActualCompletion = actualCompletion;
|
||||
distribution.ReportedAt = DateTime.UtcNow;
|
||||
distribution.ReportedByUserId = userId;
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return distribution;
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int id)
|
||||
{
|
||||
var allocation = await _context.MaterialAllocations.FindAsync(id);
|
||||
|
|
|
|||
|
|
@ -52,6 +52,11 @@ public interface IAllocationService
|
|||
/// </summary>
|
||||
Task<MaterialAllocation> UpdateDistributionsAsync(int allocationId, Dictionary<int, decimal> distributions);
|
||||
|
||||
/// <summary>
|
||||
/// 更新单个分配记录的实际完成数量(上报消耗)
|
||||
/// </summary>
|
||||
Task<AllocationDistribution> UpdateDistributionCompletionAsync(int distributionId, int unitId, int userId, decimal actualCompletion);
|
||||
|
||||
/// <summary>
|
||||
/// 删除物资配额
|
||||
/// </summary>
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 524 KiB |
|
|
@ -53,6 +53,12 @@ const routes: RouteRecordRaw[] = [
|
|||
component: () => import('@/views/allocations/AllocationForm.vue'),
|
||||
meta: { title: '编辑配额', minLevel: 1 }
|
||||
},
|
||||
{
|
||||
path: 'allocations/:id/report',
|
||||
name: 'AllocationReport',
|
||||
component: () => import('@/views/allocations/AllocationReport.vue'),
|
||||
meta: { title: '上报消耗' }
|
||||
},
|
||||
{
|
||||
path: 'reports',
|
||||
name: 'Reports',
|
||||
|
|
|
|||
|
|
@ -203,6 +203,18 @@
|
|||
<span class="time-cell">{{ row.reportedAt ? formatDate(row.reportedAt) : '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
v-if="canReportConsumption(row)"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleReportConsumption(row)"
|
||||
>
|
||||
上报消耗
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
|
@ -280,6 +292,23 @@ function getTotalDistributed(): number {
|
|||
return distributions.value.reduce((sum, d) => sum + (d.unitQuota || 0), 0)
|
||||
}
|
||||
|
||||
function canReportConsumption(distribution: AllocationDistribution): boolean {
|
||||
// 只有当前用户所属单位的分配才能上报消耗
|
||||
if (!authStore.user) return false
|
||||
return distribution.targetUnitId === authStore.user.organizationalUnitId
|
||||
}
|
||||
|
||||
function handleReportConsumption(distribution: AllocationDistribution) {
|
||||
if (!selectedAllocation.value) return
|
||||
router.push({
|
||||
path: `/allocations/${selectedAllocation.value.id}/report`,
|
||||
query: {
|
||||
distributionId: distribution.id,
|
||||
targetUnitId: distribution.targetUnitId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
pagination.pageNumber = 1
|
||||
}
|
||||
|
|
|
|||
393
src/frontend/src/views/allocations/AllocationReport.vue
Normal file
393
src/frontend/src/views/allocations/AllocationReport.vue
Normal file
|
|
@ -0,0 +1,393 @@
|
|||
<template>
|
||||
<div class="allocation-report">
|
||||
<el-card class="report-card" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<div class="header-title">
|
||||
<el-icon class="title-icon" :size="22"><DocumentAdd /></el-icon>
|
||||
<span>上报消耗</span>
|
||||
</div>
|
||||
<el-button @click="handleBack">
|
||||
<el-icon><Back /></el-icon>
|
||||
返回
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="loading" class="loading-container">
|
||||
<el-skeleton :rows="6" animated />
|
||||
</div>
|
||||
|
||||
<div v-else-if="distribution" class="report-content">
|
||||
<!-- 配额信息 -->
|
||||
<div class="info-section">
|
||||
<h3 class="section-title">配额信息</h3>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="物资名称">
|
||||
<span class="material-name">{{ allocation?.materialName }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="物资类别">
|
||||
<el-tag :type="getCategoryTagType(allocation?.category)" size="small">
|
||||
{{ allocation?.category }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="单位">
|
||||
{{ allocation?.unit }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="目标单位">
|
||||
{{ distribution.targetUnitName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="分配配额">
|
||||
<span class="quota-value">{{ formatNumber(distribution.unitQuota) }} {{ allocation?.unit }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="当前完成">
|
||||
<span :class="['completion-value', distribution.actualCompletion ? '' : 'no-data']">
|
||||
{{ distribution.actualCompletion ? formatNumber(distribution.actualCompletion) : '未上报' }}
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
<!-- 上报表单 -->
|
||||
<div class="form-section">
|
||||
<h3 class="section-title">消耗数据</h3>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="120px"
|
||||
@submit.prevent="handleSubmit"
|
||||
>
|
||||
<el-form-item label="实际完成数量" prop="actualCompletion">
|
||||
<el-input-number
|
||||
v-model="form.actualCompletion"
|
||||
:min="0"
|
||||
:max="distribution.unitQuota"
|
||||
:precision="2"
|
||||
:step="1"
|
||||
style="width: 300px"
|
||||
/>
|
||||
<span class="unit-hint">{{ allocation?.unit }}</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="完成率">
|
||||
<div class="completion-rate">
|
||||
<el-progress
|
||||
:percentage="getCompletionPercentage()"
|
||||
:status="getProgressStatus()"
|
||||
:stroke-width="20"
|
||||
style="width: 300px"
|
||||
/>
|
||||
<span class="rate-text">{{ getCompletionPercentage() }}%</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="备注" prop="remarks">
|
||||
<el-input
|
||||
v-model="form.remarks"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入备注信息(可选)"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
style="width: 500px"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="submitting" @click="handleSubmit">
|
||||
<el-icon><Check /></el-icon>
|
||||
提交上报
|
||||
</el-button>
|
||||
<el-button @click="handleBack">
|
||||
<el-icon><Close /></el-icon>
|
||||
取消
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 历史记录 -->
|
||||
<div v-if="distribution.reportedAt" class="history-section">
|
||||
<h3 class="section-title">上报历史</h3>
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
>
|
||||
<template #title>
|
||||
<span>上次上报时间: {{ formatDate(distribution.reportedAt) }}</span>
|
||||
</template>
|
||||
<div>上次上报数量: {{ formatNumber(distribution.actualCompletion || 0) }} {{ allocation?.unit }}</div>
|
||||
</el-alert>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-empty v-else description="未找到配额分配信息" />
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
|
||||
import { DocumentAdd, Back, Check, Close } from '@element-plus/icons-vue'
|
||||
import { allocationsApi } from '@/api'
|
||||
import type { MaterialAllocation, AllocationDistribution } from '@/types'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const loading = ref(false)
|
||||
const submitting = ref(false)
|
||||
const allocation = ref<MaterialAllocation | null>(null)
|
||||
const distribution = ref<AllocationDistribution | null>(null)
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
const form = reactive({
|
||||
actualCompletion: 0,
|
||||
remarks: ''
|
||||
})
|
||||
|
||||
const rules: FormRules = {
|
||||
actualCompletion: [
|
||||
{ required: true, message: '请输入实际完成数量', trigger: 'blur' },
|
||||
{
|
||||
type: 'number',
|
||||
min: 0,
|
||||
message: '数量不能小于0',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
validator: (_rule, value, callback) => {
|
||||
if (value > (distribution.value?.unitQuota || 0)) {
|
||||
callback(new Error(`数量不能超过分配配额 ${distribution.value?.unitQuota}`))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
function formatDate(dateStr: string): string {
|
||||
return new Date(dateStr).toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
function formatNumber(num: number): string {
|
||||
return num.toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
function getCategoryTagType(category?: string): string {
|
||||
switch (category) {
|
||||
case '弹药': return 'danger'
|
||||
case '装备': return 'warning'
|
||||
case '物资': return 'success'
|
||||
case '器材': return 'info'
|
||||
default: return ''
|
||||
}
|
||||
}
|
||||
|
||||
function getCompletionPercentage(): number {
|
||||
if (!distribution.value || !form.actualCompletion) return 0
|
||||
return Math.round((form.actualCompletion / distribution.value.unitQuota) * 100)
|
||||
}
|
||||
|
||||
function getProgressStatus(): string {
|
||||
const percentage = getCompletionPercentage()
|
||||
if (percentage >= 100) return 'success'
|
||||
if (percentage >= 60) return ''
|
||||
return 'warning'
|
||||
}
|
||||
|
||||
function handleBack() {
|
||||
router.back()
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!formRef.value) return
|
||||
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
|
||||
await ElMessageBox.confirm(
|
||||
`确认上报实际完成数量为 ${form.actualCompletion} ${allocation.value?.unit} 吗?`,
|
||||
'确认上报',
|
||||
{
|
||||
type: 'warning',
|
||||
confirmButtonText: '确认上报',
|
||||
cancelButtonText: '取消'
|
||||
}
|
||||
)
|
||||
|
||||
submitting.value = true
|
||||
|
||||
if (!distribution.value) return
|
||||
|
||||
await allocationsApi.updateDistribution(distribution.value.id, {
|
||||
actualCompletion: form.actualCompletion
|
||||
})
|
||||
|
||||
ElMessage.success('上报成功')
|
||||
router.back()
|
||||
} catch (error: any) {
|
||||
if (error !== 'cancel') {
|
||||
ElMessage.error(error.response?.data?.message || '上报失败')
|
||||
}
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const allocationId = Number(route.params.id)
|
||||
const distributionId = Number(route.query.distributionId)
|
||||
|
||||
if (!allocationId || !distributionId) {
|
||||
ElMessage.error('参数错误')
|
||||
router.back()
|
||||
return
|
||||
}
|
||||
|
||||
// 加载配额信息(包含分配信息)
|
||||
allocation.value = await allocationsApi.getById(allocationId)
|
||||
|
||||
// 从配额信息中获取分配信息
|
||||
distribution.value = allocation.value.distributions?.find(d => d.id === distributionId) || null
|
||||
|
||||
if (!distribution.value) {
|
||||
ElMessage.error('未找到配额分配信息')
|
||||
router.back()
|
||||
return
|
||||
}
|
||||
|
||||
// 如果已有上报数据,填充表单
|
||||
if (distribution.value.actualCompletion) {
|
||||
form.actualCompletion = distribution.value.actualCompletion
|
||||
}
|
||||
} catch {
|
||||
ElMessage.error('加载数据失败')
|
||||
router.back()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.allocation-report {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.report-card {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.title-icon {
|
||||
margin-right: 8px;
|
||||
color: #409EFF;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.report-content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.info-section,
|
||||
.form-section,
|
||||
.history-section {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid #409EFF;
|
||||
}
|
||||
|
||||
.material-name {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.quota-value {
|
||||
font-weight: 600;
|
||||
color: #409EFF;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.completion-value {
|
||||
font-weight: 500;
|
||||
color: #67C23A;
|
||||
}
|
||||
|
||||
.completion-value.no-data {
|
||||
color: #C0C4CC;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.unit-hint {
|
||||
margin-left: 12px;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.completion-rate {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.rate-text {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #409EFF;
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
:deep(.el-card__header) {
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
:deep(.el-descriptions) {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
:deep(.el-descriptions__label) {
|
||||
width: 120px;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__label) {
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -27,36 +27,38 @@
|
|||
<el-col :span="18">
|
||||
<el-descriptions :column="3" border>
|
||||
<el-descriptions-item label="姓名">{{ person.name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">{{ person.gender }}</el-descriptions-item>
|
||||
<el-descriptions-item label="年龄">{{ person.age }}</el-descriptions-item>
|
||||
<el-descriptions-item label="身份证号">{{ person.idNumber }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系方式">{{ person.contactInfo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="籍贯">{{ person.hometown }}</el-descriptions-item>
|
||||
<el-descriptions-item label="职位">{{ person.position }}</el-descriptions-item>
|
||||
<el-descriptions-item label="单位">{{ person.unit || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="部职别">{{ person.position }}</el-descriptions-item>
|
||||
<el-descriptions-item label="军衔">{{ person.rank }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业职位">{{ person.professionalTitle }}</el-descriptions-item>
|
||||
<el-descriptions-item label="文化程度">{{ person.educationLevel }}</el-descriptions-item>
|
||||
<el-descriptions-item label="身高">{{ person.height }} cm</el-descriptions-item>
|
||||
<el-descriptions-item label="士兵证号">{{ person.idNumber || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业岗位">{{ person.professionalTitle || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="政治面貌">{{ person.politicalStatus || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="文化程度">{{ person.educationLevel || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="民族">{{ person.ethnicity || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="籍贯">{{ person.hometown || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="出生年月">{{ person.birthDate || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="入伍年月">{{ person.enlistmentDate || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="特长" :span="3">{{ person.specialty || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="人员等级">
|
||||
<el-tag v-if="person.approvedLevel" :type="getLevelTagType(person.approvedLevel)">
|
||||
{{ getLevelName(person.approvedLevel) }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="提交单位" :span="2">{{ person.submittedByUnitName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="提交单位">{{ person.submittedByUnitName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="审批单位">{{ person.approvedByUnitName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="提交时间" :span="2">{{ formatDate(person.submittedAt) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="审批时间">{{ person.approvedAt ? formatDate(person.approvedAt) : '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="提交时间">{{ formatDate(person.submittedAt) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="审批时间" :span="2">{{ person.approvedAt ? formatDate(person.approvedAt) : '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-divider />
|
||||
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="参加培训情况">
|
||||
<div class="text-content">{{ person.trainingParticipation }}</div>
|
||||
<div class="text-content">{{ person.trainingParticipation || '-' }}</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="取得成绩">
|
||||
<div class="text-content">{{ person.achievements }}</div>
|
||||
<div class="text-content">{{ person.achievements || '-' }}</div>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
|
|
@ -195,4 +197,10 @@ onMounted(() => {
|
|||
margin-bottom: 12px;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
/* 缩短标签列宽度 */
|
||||
:deep(.el-descriptions__label) {
|
||||
width: 120px !important;
|
||||
min-width: 120px !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -33,32 +33,37 @@
|
|||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="单位" prop="unit">
|
||||
<el-input v-model="form.unit" placeholder="请输入单位" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="部职别" prop="position">
|
||||
<el-input v-model="form.position" placeholder="请输入部职别" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="军衔" prop="rank">
|
||||
<el-input v-model="form.rank" placeholder="请输入军衔" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="士兵证号" prop="idNumber">
|
||||
<el-input v-model="form.idNumber" placeholder="请输入士兵证号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业岗位" prop="professionalTitle">
|
||||
<el-input v-model="form.professionalTitle" placeholder="请输入专业岗位" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="政治面貌" prop="politicalStatus">
|
||||
<el-select v-model="form.politicalStatus" placeholder="请选择政治面貌" style="width: 100%">
|
||||
|
|
@ -69,6 +74,9 @@
|
|||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="文化程度" prop="educationLevel">
|
||||
<el-select v-model="form.educationLevel" placeholder="请选择文化程度" style="width: 100%">
|
||||
|
|
@ -82,22 +90,19 @@
|
|||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="民族" prop="ethnicity">
|
||||
<el-input v-model="form.ethnicity" placeholder="请输入民族" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="籍贯" prop="hometown">
|
||||
<el-input v-model="form.hometown" placeholder="请输入籍贯" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="出生年月" prop="birthDate">
|
||||
<el-date-picker
|
||||
|
|
@ -110,6 +115,9 @@
|
|||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="入伍年月" prop="enlistmentDate">
|
||||
<el-date-picker
|
||||
|
|
@ -122,12 +130,13 @@
|
|||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="特长" prop="specialty">
|
||||
<el-input v-model="form.specialty" placeholder="请输入特长" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item label="特长" prop="specialty">
|
||||
<el-input v-model="form.specialty" placeholder="请输入特长" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="参加培训、集训情况" prop="trainingParticipation">
|
||||
<el-input v-model="form.trainingParticipation" type="textarea" :rows="3" placeholder="请输入参加培训、集训情况" />
|
||||
</el-form-item>
|
||||
|
|
@ -185,6 +194,7 @@ const documentList = ref<UploadFile[]>([])
|
|||
|
||||
const form = reactive({
|
||||
name: '',
|
||||
unit: '',
|
||||
position: '',
|
||||
rank: '',
|
||||
idNumber: '',
|
||||
|
|
@ -205,6 +215,10 @@ const rules: FormRules = {
|
|||
{ required: true, message: '请输入姓名', trigger: 'blur' },
|
||||
{ min: 2, max: 50, message: '姓名长度应在2-50个字符之间', trigger: 'blur' }
|
||||
],
|
||||
unit: [
|
||||
{ required: true, message: '请输入单位', trigger: 'blur' },
|
||||
{ max: 100, message: '单位长度不能超过100个字符', trigger: 'blur' }
|
||||
],
|
||||
position: [
|
||||
{ required: true, message: '请输入部职别', trigger: 'blur' },
|
||||
{ max: 100, message: '部职别长度不能超过100个字符', trigger: 'blur' }
|
||||
|
|
@ -283,6 +297,7 @@ async function loadPersonnel() {
|
|||
try {
|
||||
const person = await personnelApi.getById(Number(route.params.id))
|
||||
form.name = person.name
|
||||
form.unit = person.unit || ''
|
||||
form.position = person.position
|
||||
form.rank = person.rank
|
||||
form.idNumber = person.idNumber || ''
|
||||
|
|
@ -338,6 +353,7 @@ async function handleSubmit() {
|
|||
formData.append('rank', form.rank)
|
||||
|
||||
// 可选字段 - 只有非空时才添加
|
||||
if (form.unit) formData.append('unit', form.unit)
|
||||
if (form.idNumber) formData.append('idNumber', form.idNumber)
|
||||
if (form.professionalTitle) formData.append('professionalTitle', form.professionalTitle)
|
||||
if (form.politicalStatus) formData.append('politicalStatus', form.politicalStatus)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user