Browse Source
- 抽出可复用组件 src/views/points/appeal/components/AppealLedgerPanel.vue(列表/详情/审批) - 独立页 src/views/points/appeal/index.vue 改为包裹该组件 - 积分管理启用第 2 个 Tab「申诉处理」,复用同一组件 - 同步更新 PAGES_TODO / CURRENT_STATUS / DEVELOPMENT_LOGmaster
6 changed files with 396 additions and 375 deletions
@ -0,0 +1,379 @@
@@ -0,0 +1,379 @@
|
||||
<template> |
||||
<div class="appeal-ledger-panel"> |
||||
<data-list-panel modern-table> |
||||
<template #filters> |
||||
<el-form v-show="showSearch" ref="queryForm" :model="queryParams" size="small" :inline="true" class="data-list-filter-form"> |
||||
<el-form-item label="状态" prop="status"> |
||||
<el-select v-model="queryParams.status" clearable placeholder="全部状态"> |
||||
<el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" /> |
||||
</el-select> |
||||
</el-form-item> |
||||
<el-form-item label="申诉类型" prop="appealType"> |
||||
<el-select v-model="queryParams.appealType" clearable placeholder="全部类型"> |
||||
<el-option v-for="item in typeOptions" :key="item.value" :label="item.label" :value="item.value" /> |
||||
</el-select> |
||||
</el-form-item> |
||||
<el-form-item> |
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> |
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> |
||||
</el-form-item> |
||||
</el-form> |
||||
</template> |
||||
|
||||
<template #actions> |
||||
<el-button icon="el-icon-refresh" size="mini" @click="getList">刷新</el-button> |
||||
</template> |
||||
|
||||
<el-table v-loading="loading" :data="list" size="mini" class="data-list-table"> |
||||
<el-table-column label="申诉编号" prop="appealNo" min-width="150" show-overflow-tooltip /> |
||||
<el-table-column label="申诉人" prop="userName" width="100" show-overflow-tooltip /> |
||||
<el-table-column label="关联隐患" prop="reportNo" width="130" show-overflow-tooltip /> |
||||
<el-table-column label="申诉类型" width="130"> |
||||
<template slot-scope="scope">{{ typeLabel(scope.row) }}</template> |
||||
</el-table-column> |
||||
<el-table-column label="申诉理由" prop="appealReason" min-width="220" show-overflow-tooltip /> |
||||
<el-table-column label="状态" width="110" align="center"> |
||||
<template slot-scope="scope"> |
||||
<el-tag :type="statusTagType(scope.row)" size="small">{{ statusLabel(scope.row) }}</el-tag> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="提交时间" width="160"> |
||||
<template slot-scope="scope">{{ formatTime(scope.row.createTime) }}</template> |
||||
</el-table-column> |
||||
<el-table-column label="操作" width="140" align="center" fixed="right" class-name="data-list-operation-column"> |
||||
<template slot-scope="scope"> |
||||
<el-button type="text" size="mini" @click="openDetail(scope.row)">详情</el-button> |
||||
<el-button v-if="canApprove(scope.row)" type="text" size="mini" @click="openApprove(scope.row)">去审批</el-button> |
||||
</template> |
||||
</el-table-column> |
||||
</el-table> |
||||
|
||||
<template #footer> |
||||
<el-pagination |
||||
background |
||||
layout="total, sizes, prev, pager, next, jumper" |
||||
:total="total" |
||||
:current-page.sync="queryParams.pageNum" |
||||
:page-size.sync="queryParams.pageSize" |
||||
:page-sizes="[10, 20, 50]" |
||||
@size-change="getList" |
||||
@current-change="getList" |
||||
/> |
||||
</template> |
||||
</data-list-panel> |
||||
|
||||
<!-- 详情 --> |
||||
<el-dialog title="申诉详情" :visible.sync="detailVisible" width="640px" append-to-body> |
||||
<div v-loading="detailLoading" class="appeal-detail"> |
||||
<el-descriptions v-if="detail" :column="2" border size="small"> |
||||
<el-descriptions-item label="申诉编号">{{ detail.appealNo || '—' }}</el-descriptions-item> |
||||
<el-descriptions-item label="申诉类型">{{ typeLabel(detail) }}</el-descriptions-item> |
||||
<el-descriptions-item label="申诉人">{{ detail.userName || '—' }}</el-descriptions-item> |
||||
<el-descriptions-item label="所属厂区">{{ detail.factoryDeptName || '—' }}</el-descriptions-item> |
||||
<el-descriptions-item label="关联隐患">{{ detail.reportNo || '—' }}</el-descriptions-item> |
||||
<el-descriptions-item label="当前状态">{{ statusLabel(detail) }}</el-descriptions-item> |
||||
<el-descriptions-item label="提交时间">{{ formatTime(detail.createTime) }}</el-descriptions-item> |
||||
<el-descriptions-item label="处理时间">{{ formatTime(detail.handleTime) }}</el-descriptions-item> |
||||
<el-descriptions-item label="处理人">{{ detail.handleUserName || '—' }}</el-descriptions-item> |
||||
<el-descriptions-item label="调整积分">{{ formatAdjust(detail.adjustPoints) }}</el-descriptions-item> |
||||
<el-descriptions-item label="申诉理由" :span="2">{{ detail.appealReason || '—' }}</el-descriptions-item> |
||||
<el-descriptions-item label="处理意见" :span="2">{{ detail.handleRemark || '—' }}</el-descriptions-item> |
||||
</el-descriptions> |
||||
<div v-if="attachmentList.length" class="appeal-attachments"> |
||||
<div class="appeal-attachments__title">附件</div> |
||||
<a v-for="(url, index) in attachmentList" :key="index" class="appeal-attachments__item" :href="url" target="_blank" rel="noopener">{{ url }}</a> |
||||
</div> |
||||
<el-empty v-if="!detailLoading && !detail" description="暂无详情" /> |
||||
</div> |
||||
</el-dialog> |
||||
|
||||
<!-- 审批 --> |
||||
<el-dialog title="审批申诉" :visible.sync="approveVisible" width="560px" append-to-body> |
||||
<div class="approve-summary"> |
||||
<div><span class="label">申诉编号:</span>{{ approveForm.appealNo || '—' }}</div> |
||||
<div><span class="label">申诉人:</span>{{ approveForm.userName || '—' }}</div> |
||||
<div><span class="label">关联隐患:</span>{{ approveForm.reportNo || '—' }}</div> |
||||
<div><span class="label">申诉理由:</span>{{ approveForm.appealReason || '—' }}</div> |
||||
</div> |
||||
<el-alert v-if="approveTaskMissing" title="未获取到审批任务 ID,暂无法提交审批。" type="warning" :closable="false" show-icon style="margin-bottom:12px" /> |
||||
<el-form ref="approveForm" :model="approveForm" :rules="approveRules" label-width="90px" size="small"> |
||||
<el-form-item label="审批结果" prop="result"> |
||||
<el-radio-group v-model="approveForm.result"> |
||||
<el-radio label="1">通过</el-radio> |
||||
<el-radio label="2">驳回</el-radio> |
||||
</el-radio-group> |
||||
</el-form-item> |
||||
<el-form-item v-if="approveForm.result === '1'" label="调整积分"> |
||||
<el-input-number v-model="approveForm.adjustPoints" :controls="false" placeholder="正数补发/负数扣减,可选" /> |
||||
<span class="field-tip">正数补发、负数扣减;不填则按原积分处理</span> |
||||
</el-form-item> |
||||
<el-form-item label="审批意见" prop="comment"> |
||||
<el-input v-model="approveForm.comment" type="textarea" :rows="3" maxlength="500" show-word-limit placeholder="请填写审批意见" /> |
||||
</el-form-item> |
||||
</el-form> |
||||
<div slot="footer" class="dialog-footer"> |
||||
<el-button @click="approveVisible = false">取消</el-button> |
||||
<el-button type="primary" :loading="approveSubmitting" @click="submitApprove">提交审批</el-button> |
||||
</div> |
||||
</el-dialog> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import { listAppealTodo, getAppealDetail, getAppealProcessDetail, completeAppealFlow } from '@/api/points/appeal' |
||||
|
||||
export default { |
||||
name: 'AppealLedgerPanel', |
||||
data() { |
||||
return { |
||||
loading: false, |
||||
showSearch: true, |
||||
list: [], |
||||
total: 0, |
||||
queryParams: { pageNum: 1, pageSize: 10, status: undefined, appealType: undefined }, |
||||
statusOptions: [ |
||||
{ label: '待受理', value: '0' }, |
||||
{ label: '处理中', value: '1' }, |
||||
{ label: '已通过', value: '2' }, |
||||
{ label: '已驳回', value: '3' }, |
||||
{ label: '已撤回', value: '4' } |
||||
], |
||||
typeOptions: [ |
||||
{ label: '审核驳回申诉', value: 'REJECT' }, |
||||
{ label: '积分异议', value: 'POINTS' }, |
||||
{ label: '其他', value: 'OTHER' } |
||||
], |
||||
detailVisible: false, |
||||
detailLoading: false, |
||||
detail: null, |
||||
approveVisible: false, |
||||
approveSubmitting: false, |
||||
approveTaskMissing: false, |
||||
approveRow: null, |
||||
approveForm: { appealNo: '', userName: '', reportNo: '', appealReason: '', result: '1', adjustPoints: undefined, comment: '' }, |
||||
approveRules: { |
||||
result: [{ required: true, message: '请选择审批结果', trigger: 'change' }], |
||||
comment: [{ required: true, message: '请填写审批意见', trigger: 'blur' }] |
||||
} |
||||
} |
||||
}, |
||||
computed: { |
||||
attachmentList() { |
||||
const raw = this.detail && this.detail.attachmentUrls |
||||
if (!raw) return [] |
||||
try { |
||||
const parsed = JSON.parse(raw) |
||||
return Array.isArray(parsed) ? parsed.map(item => (typeof item === 'string' ? item : item.url)).filter(Boolean) : [] |
||||
} catch (error) { |
||||
return String(raw).split(',').map(item => item.trim()).filter(Boolean) |
||||
} |
||||
} |
||||
}, |
||||
created() { |
||||
this.getList() |
||||
}, |
||||
methods: { |
||||
getList() { |
||||
this.loading = true |
||||
listAppealTodo(this.queryParams) |
||||
.then(response => { |
||||
const page = response.data || response |
||||
this.list = page.list || page.rows || page.records || [] |
||||
this.total = Number(page.total || page.totalCount || this.list.length) |
||||
}) |
||||
.catch(() => { |
||||
this.list = [] |
||||
this.total = 0 |
||||
this.$modal.msgError('申诉台账加载失败,请检查后端服务和登录状态') |
||||
}) |
||||
.finally(() => { |
||||
this.loading = false |
||||
}) |
||||
}, |
||||
handleQuery() { |
||||
this.queryParams.pageNum = 1 |
||||
this.getList() |
||||
}, |
||||
resetQuery() { |
||||
this.$refs.queryForm.resetFields() |
||||
this.queryParams.pageNum = 1 |
||||
this.getList() |
||||
}, |
||||
openDetail(row) { |
||||
this.detailVisible = true |
||||
this.detail = null |
||||
this.detailLoading = true |
||||
getAppealDetail(row.id) |
||||
.then(response => { |
||||
this.detail = response.data || response |
||||
}) |
||||
.catch(() => { |
||||
this.$modal.msgError('申诉详情加载失败') |
||||
}) |
||||
.finally(() => { |
||||
this.detailLoading = false |
||||
}) |
||||
}, |
||||
openApprove(row) { |
||||
this.approveRow = row |
||||
this.approveTaskMissing = false |
||||
this.approveForm = { |
||||
appealNo: row.appealNo, |
||||
userName: row.userName, |
||||
reportNo: row.reportNo, |
||||
appealReason: row.appealReason, |
||||
result: '1', |
||||
adjustPoints: undefined, |
||||
comment: '' |
||||
} |
||||
this.approveVisible = true |
||||
this.resolveTaskId(row) |
||||
}, |
||||
// 通过流程实例补全当前待办 taskId(与隐患审核一致) |
||||
async resolveTaskId(row) { |
||||
if (row.canApprove !== true && String(row.canApprove) !== 'true') return |
||||
if (!row.flowInstanceId) { |
||||
this.approveTaskMissing = true |
||||
return |
||||
} |
||||
try { |
||||
const response = await getAppealProcessDetail(row.flowInstanceId) |
||||
const task = this.findCurrentTask(response.data) |
||||
this.approveRow = Object.assign({}, row, { taskId: task ? String(task.taskId || '') : '' }) |
||||
this.approveTaskMissing = !this.approveRow.taskId |
||||
} catch (error) { |
||||
this.approveTaskMissing = true |
||||
} |
||||
}, |
||||
findCurrentTask(data) { |
||||
const tasks = this.normalizeTaskList(data) |
||||
if (!tasks.length) return null |
||||
return tasks.find(task => task.taskId && !task.endTime && !task.finishTime) || tasks.find(task => task.taskId) || null |
||||
}, |
||||
normalizeTaskList(data) { |
||||
const tasks = [] |
||||
const visit = value => { |
||||
if (Array.isArray(value)) { |
||||
value.forEach(visit) |
||||
return |
||||
} |
||||
if (!value || typeof value !== 'object') return |
||||
if (Object.prototype.hasOwnProperty.call(value, 'taskId')) tasks.push(value) |
||||
Object.keys(value).forEach(key => { |
||||
if (key !== 'taskId') visit(value[key]) |
||||
}) |
||||
} |
||||
visit(data) |
||||
return tasks |
||||
}, |
||||
submitApprove() { |
||||
this.$refs.approveForm.validate(valid => { |
||||
if (!valid) return |
||||
const row = this.approveRow || {} |
||||
if (!row.taskId) { |
||||
this.$modal.msgWarning('未获取到审批任务 ID,无法提交审批') |
||||
return |
||||
} |
||||
const payload = { |
||||
taskId: row.taskId, |
||||
instanceId: row.flowInstanceId, |
||||
type: 'appeal', |
||||
result: this.approveForm.result, |
||||
comment: this.approveForm.comment |
||||
} |
||||
// 改判补分:adjustPoints 随流程变量下发(联调确认后端读取方式) |
||||
if (this.approveForm.result === '1' && this.approveForm.adjustPoints !== undefined && this.approveForm.adjustPoints !== null) { |
||||
payload.variables = { adjustPoints: this.approveForm.adjustPoints } |
||||
} |
||||
this.approveSubmitting = true |
||||
completeAppealFlow(payload) |
||||
.then(() => { |
||||
this.$modal.msgSuccess(this.approveForm.result === '1' ? '申诉已通过' : '申诉已驳回') |
||||
this.approveVisible = false |
||||
this.getList() |
||||
}) |
||||
.catch(() => { |
||||
this.$modal.msgError('审批提交失败,请稍后重试') |
||||
}) |
||||
.finally(() => { |
||||
this.approveSubmitting = false |
||||
}) |
||||
}) |
||||
}, |
||||
canApprove(row) { |
||||
const can = row.canApprove === true || String(row.canApprove) === 'true' |
||||
const status = this.statusKey(row) |
||||
return can && (status === '0' || status === '1') |
||||
}, |
||||
statusKey(row) { |
||||
return String(row.status === undefined || row.status === null ? '' : row.status) |
||||
}, |
||||
statusLabel(row) { |
||||
const name = row.statusName |
||||
if (name) return name |
||||
return ({ '0': '待受理', '1': '处理中', '2': '已通过', '3': '已驳回', '4': '已撤回' }[this.statusKey(row)]) || '—' |
||||
}, |
||||
statusTagType(row) { |
||||
const key = this.statusKey(row) |
||||
if (key === '2') return 'success' |
||||
if (key === '3') return 'danger' |
||||
if (key === '1') return 'primary' |
||||
if (key === '4') return 'info' |
||||
return 'warning' |
||||
}, |
||||
typeLabel(row) { |
||||
return ({ REJECT: '审核驳回申诉', POINTS: '积分异议', OTHER: '其他' }[row.appealType]) || row.appealType || '—' |
||||
}, |
||||
formatAdjust(value) { |
||||
if (value === undefined || value === null || value === '') return '—' |
||||
const n = Number(value) |
||||
return Number.isNaN(n) ? String(value) : `${n > 0 ? '+' : ''}${n}` |
||||
}, |
||||
formatTime(value) { |
||||
if (!value) return '—' |
||||
return String(value).replace('T', ' ').slice(0, 19) |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
.approve-summary { |
||||
margin-bottom: 14px; |
||||
padding: 12px 14px; |
||||
border-radius: 8px; |
||||
background: var(--prototype-panel-soft, #fafbfc); |
||||
color: var(--prototype-text-secondary, #5a5a72); |
||||
font-size: 13px; |
||||
line-height: 22px; |
||||
} |
||||
|
||||
.approve-summary .label { |
||||
color: var(--prototype-text-muted, #9898ae); |
||||
} |
||||
|
||||
.field-tip { |
||||
margin-left: 10px; |
||||
color: var(--prototype-text-muted, #9898ae); |
||||
font-size: 12px; |
||||
} |
||||
|
||||
.appeal-attachments { |
||||
margin-top: 14px; |
||||
} |
||||
|
||||
.appeal-attachments__title { |
||||
margin-bottom: 6px; |
||||
color: var(--prototype-text-secondary, #5a5a72); |
||||
font-size: 13px; |
||||
font-weight: 600; |
||||
} |
||||
|
||||
.appeal-attachments__item { |
||||
display: block; |
||||
margin-bottom: 4px; |
||||
color: #1890ff; |
||||
font-size: 12px; |
||||
word-break: break-all; |
||||
} |
||||
</style> |
||||
Loading…
Reference in new issue