shinnosuke
2026-08-28 88c94d51fb9f82e856245908696ea4a970541a33
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getParkingPlateSubmissionDetail, updateParkingPlateSubmissionStatus } from '../api/admin'
 
const route = useRoute()
const loading = ref(false)
const processing = ref(false)
const detail = ref<Record<string, any>>({})
 
function text(value: unknown) {
  return value === undefined || value === null || value === '' ? '-' : String(value)
}
 
function pick(group: Record<string, any> | undefined, ...keys: string[]) {
  if (!group) return ''
  for (const key of keys) {
    if (group[key] !== undefined && group[key] !== null && group[key] !== '') return group[key]
  }
  return ''
}
 
function formatDate(value: unknown) {
  return text(value).replace('T', ' ')
}
 
function statusName(value: unknown, fallback = '') {
  const raw = String(value ?? '').trim().toUpperCase().replace('-', '_')
  const label = fallback.trim()
  if (label === '已拒绝') return '已驳回'
  if (label) return label
  if (raw === 'ACTIVE' || raw === 'PROCESSED' || raw === 'PASSED' || raw === 'APPROVED') return '已处理'
  if (raw === 'REJECTED' || raw === 'REFUSED' || raw === 'DENIED') return '已驳回'
  if (raw === 'CANCELLED' || raw === 'CANCELED') return '已取消'
  return '待处理'
}
 
const talentInfo = computed(() => detail.value.talentInfo || {})
const submissionInfo = computed(() => detail.value.submissionInfo || {})
const statusInfo = computed(() => detail.value.statusInfo || {})
 
const cardholderItems = computed(() => [
  { label: '持卡人姓名:', value: text(pick(talentInfo.value, 'talentName') || pick(detail.value, 'talentName')) },
  { label: '手机号:', value: text(pick(talentInfo.value, 'talentMobile') || pick(detail.value, 'talentMobile')) },
  { label: '人才等级:', value: text(pick(talentInfo.value, 'cardLevel') || pick(detail.value, 'cardLevel', 'talentLevel')) },
])
 
const submissionItems = computed(() => [
  { label: '车牌号:', value: text(pick(submissionInfo.value, 'plateNumber', 'plateNo') || pick(detail.value, 'plateNumber', 'plateNo')) },
  { label: '停车场:', value: text(pick(submissionInfo.value, 'parkingLotName', 'parkingLot', 'serviceName') || pick(detail.value, 'parkingLotName', 'parkingLot', 'serviceName')) },
  { label: '提交时间:', value: formatDate(pick(submissionInfo.value, 'submittedAt', 'createdAt') || pick(detail.value, 'submittedAt', 'createdAt')) },
])
 
const currentStatus = computed(() => statusName(
  pick(statusInfo.value, 'status') || pick(detail.value, 'status'),
  String(pick(statusInfo.value, 'statusName') || pick(detail.value, 'statusName') || ''),
))
 
const statusItems = computed(() => [
  { label: '状态:', value: currentStatus.value },
  { label: '处理时间:', value: formatDate(pick(statusInfo.value, 'handledAt', 'processedAt') || pick(detail.value, 'handledAt', 'processedAt')) },
  { label: '处理人:', value: text(pick(statusInfo.value, 'handledByName', 'handlerName', 'processedBy') || pick(detail.value, 'handledByName', 'handlerName', 'processedBy')) },
])
 
const canProcess = computed(() => !['已处理', '已驳回', '已取消'].includes(currentStatus.value))
 
async function loadData() {
  loading.value = true
  try {
    detail.value = await getParkingPlateSubmissionDetail(String(route.params.id)) || {}
  } finally {
    loading.value = false
  }
}
 
async function markHandled() {
  const id = detail.value.id || detail.value.registrationId
  if (!id || processing.value || !canProcess.value) return
  try {
    await ElMessageBox.confirm('确认将该停车车牌申请标记为已处理吗?', '确认处理', {
      type: 'warning',
      confirmButtonText: '确认处理',
      cancelButtonText: '取消',
    })
  } catch {
    return
  }
  processing.value = true
  try {
    detail.value = await updateParkingPlateSubmissionStatus(id, '已处理')
    ElMessage.success('已处理该车牌申请')
  } finally {
    processing.value = false
  }
}
 
onMounted(loadData)
</script>
 
<template>
  <div v-loading="loading" class="parking-plate-detail-page">
    <section class="parking-plate-detail-panel">
      <div class="reservation-detail-section">
        <h3>持卡人信息</h3>
        <div class="reservation-detail-grid parking-plate-cardholder-grid">
          <div v-for="item in cardholderItems" :key="item.label" class="reservation-detail-item">
            <span>{{ item.label }}</span><strong>{{ item.value }}</strong>
          </div>
        </div>
      </div>
 
      <div class="reservation-detail-section">
        <h3>提交信息</h3>
        <div class="reservation-detail-grid parking-plate-submit-grid">
          <div v-for="item in submissionItems" :key="item.label" class="reservation-detail-item">
            <span>{{ item.label }}</span><strong>{{ item.value }}</strong>
          </div>
        </div>
      </div>
 
      <div class="reservation-detail-section">
        <h3>状态信息</h3>
        <div class="reservation-detail-grid parking-plate-status-grid">
          <div v-for="item in statusItems" :key="item.label" class="reservation-detail-item">
            <span>{{ item.label }}</span><strong>{{ item.value }}</strong>
          </div>
        </div>
      </div>
    </section>
 
    <el-button v-if="canProcess" type="primary" class="reservation-detail-action" :loading="processing" @click="markHandled">处理</el-button>
  </div>
</template>
 
<style scoped>
.parking-plate-detail-page {
  display: grid;
  gap: 20px;
}
 
.parking-plate-detail-panel {
  min-height: 0;
  padding: 20px 18px 26px;
  background: #ffffff;
}
 
.parking-plate-detail-panel .reservation-detail-section {
  margin-bottom: 24px;
}
 
.parking-plate-detail-panel .reservation-detail-section:last-child {
  margin-bottom: 0;
}
 
.parking-plate-detail-panel .reservation-detail-grid {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  min-height: 74px;
  padding: 22px 16px;
  background: #f1f3f5;
}
 
.parking-plate-detail-panel .parking-plate-submit-grid {
  min-height: 92px;
  align-content: start;
}
 
@media (max-width: 900px) {
  .parking-plate-detail-panel .reservation-detail-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
</style>