Browse Source
- 新增独立「申诉台账」页 src/views/points/appeal/index.vue:列表+详情+审批,走 completeFlow(taskId 由 getProcessDetail 补全)
- 新增 src/api/points/appeal.js、src/api/points/transaction.js
- 积分管理启用「流水冲正」Tab:新建 TransactionReversePanel.vue,列表 getListByPage + 冲正 reverse/{logId}(二次确认)
- 备注:手动调整 Tab 因 balance/adjust 参数缺失暂缓(后端计划 2026-10-16 更新)
- 同步更新 PAGES_TODO / CURRENT_STATUS / DEVELOPMENT_LOG;提交前 eslint 与 build:prod 通过
master
8 changed files with 652 additions and 11 deletions
@ -0,0 +1,25 @@ |
|||||||
|
import request from '@/utils/request' |
||||||
|
|
||||||
|
// 申诉审批台账(审批人视角,待办/已办;返回 PageInfo,行内含 canApprove/isInitiator)
|
||||||
|
export function listAppealTodo(params) { |
||||||
|
return request({ url: '/business/points/appeal/todoList', method: 'get', params }) |
||||||
|
} |
||||||
|
|
||||||
|
// 申诉详情(含完整申诉字段与流程进度信息)
|
||||||
|
export function getAppealDetail(id) { |
||||||
|
return request({ url: '/business/points/appeal/detail/' + id, method: 'get' }) |
||||||
|
} |
||||||
|
|
||||||
|
// 根据流程实例 ID 获取流程详情,用于补齐当前待办任务 taskId
|
||||||
|
export function getAppealProcessDetail(flowInstanceId) { |
||||||
|
return request({ |
||||||
|
url: '/flowable/busiInfo/getProcessDetail', |
||||||
|
method: 'get', |
||||||
|
params: { id: flowInstanceId } |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
// 审批申诉流程(通过/驳回当前节点);流程结束时后端按 adjustPoints 补/扣分并推送消息
|
||||||
|
export function completeAppealFlow(data) { |
||||||
|
return request({ url: '/business/points/appeal/completeFlow', method: 'post', data }) |
||||||
|
} |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
import request from '@/utils/request' |
||||||
|
|
||||||
|
// 积分流水分页查询(管理端)
|
||||||
|
export function listTransactions(params) { |
||||||
|
return request({ url: '/business/points/transaction/getListByPage', method: 'get', params }) |
||||||
|
} |
||||||
|
|
||||||
|
// 积分冲正:按原流水 ID 冲正,原流水标记已冲正并生成反向流水
|
||||||
|
export function reverseTransaction(logId) { |
||||||
|
return request({ url: '/business/points/transaction/reverse/' + logId, method: 'post' }) |
||||||
|
} |
||||||
@ -0,0 +1,388 @@ |
|||||||
|
<template> |
||||||
|
<div class="app-container appeal-ledger"> |
||||||
|
<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: 'PointsAppeal', |
||||||
|
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) { |
||||||
|
const value = String(row.status === undefined || row.status === null ? '' : row.status) |
||||||
|
return value |
||||||
|
}, |
||||||
|
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> |
||||||
|
.appeal-ledger { |
||||||
|
min-height: calc(100vh - 52px); |
||||||
|
} |
||||||
|
|
||||||
|
.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; |
||||||
|
} |
||||||
|
|
||||||
|
.approve-summary .el-input-number { |
||||||
|
width: 100%; |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,195 @@ |
|||||||
|
<template> |
||||||
|
<data-list-panel modern-table> |
||||||
|
<template #filters> |
||||||
|
<el-form ref="queryForm" :model="queryParams" size="small" :inline="true" class="data-list-filter-form"> |
||||||
|
<el-form-item label="业务类型" prop="bizType"> |
||||||
|
<el-select v-model="queryParams.bizType" clearable placeholder="全部类型"> |
||||||
|
<el-option v-for="item in bizTypeOptions" :key="item.value" :label="item.label" :value="item.value" /> |
||||||
|
</el-select> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="状态" prop="status"> |
||||||
|
<el-select v-model="queryParams.status" clearable placeholder="全部状态"> |
||||||
|
<el-option label="有效" :value="1" /> |
||||||
|
<el-option label="已冲正" :value="2" /> |
||||||
|
</el-select> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="流水号" prop="txnNo"> |
||||||
|
<el-input v-model="queryParams.txnNo" clearable placeholder="流水号" @keyup.enter.native="handleQuery" /> |
||||||
|
</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="txnNo" min-width="160" show-overflow-tooltip /> |
||||||
|
<el-table-column label="用户ID" prop="userId" width="90" /> |
||||||
|
<el-table-column label="业务类型" width="120" align="center"> |
||||||
|
<template slot-scope="scope"> |
||||||
|
<el-tag :type="bizTypeTag(scope.row)" size="small">{{ bizTypeLabel(scope.row) }}</el-tag> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column label="变动" width="90" align="right"> |
||||||
|
<template slot-scope="scope"> |
||||||
|
<span :style="{ color: amountColor(scope.row) }">{{ amountLabel(scope.row) }}</span> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column label="变动前 → 后" width="150"> |
||||||
|
<template slot-scope="scope">{{ balanceText(scope.row) }}</template> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column label="状态" width="90" 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="90" align="center" fixed="right" class-name="data-list-operation-column"> |
||||||
|
<template slot-scope="scope"> |
||||||
|
<el-button v-if="canReverse(scope.row)" type="text" size="mini" @click="handleReverse(scope.row)">冲正</el-button> |
||||||
|
<span v-else class="txn-muted">—</span> |
||||||
|
</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> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import { listTransactions, reverseTransaction } from '@/api/points/transaction' |
||||||
|
|
||||||
|
const BIZ_TYPES = [ |
||||||
|
{ value: 'REPORT', label: '上报奖励' }, |
||||||
|
{ value: 'EXCHANGE', label: '兑换扣减' }, |
||||||
|
{ value: 'REFUND', label: '退款回滚' }, |
||||||
|
{ value: 'FREEZE', label: '冻结' }, |
||||||
|
{ value: 'UNFREEZE', label: '解冻' }, |
||||||
|
{ value: 'APPEAL_ADD', label: '申诉补发' }, |
||||||
|
{ value: 'APPEAL_DEDUCT', label: '申诉扣减' }, |
||||||
|
{ value: 'MALL_DEDUCT', label: '商城扣减' }, |
||||||
|
{ value: 'MALL_REFUND', label: '商城退款' }, |
||||||
|
{ value: 'MANUAL_ADD', label: '手动补发' }, |
||||||
|
{ value: 'MANUAL_DEDUCT', label: '手动扣减' } |
||||||
|
] |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'TransactionReversePanel', |
||||||
|
data() { |
||||||
|
return { |
||||||
|
loading: false, |
||||||
|
list: [], |
||||||
|
total: 0, |
||||||
|
queryParams: { pageNum: 1, pageSize: 10, bizType: undefined, status: undefined, txnNo: undefined }, |
||||||
|
bizTypeOptions: BIZ_TYPES |
||||||
|
} |
||||||
|
}, |
||||||
|
created() { |
||||||
|
this.getList() |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
getList() { |
||||||
|
this.loading = true |
||||||
|
listTransactions(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() |
||||||
|
}, |
||||||
|
handleReverse(row) { |
||||||
|
const label = `${row.txnNo || row.id}(${this.amountLabel(row)})` |
||||||
|
this.$confirm(`确认对流水 ${label} 执行冲正?将生成反向流水且原流水不可删除。`, '积分冲正', { type: 'warning' }) |
||||||
|
.then(() => reverseTransaction(row.id)) |
||||||
|
.then(() => { |
||||||
|
this.$modal.msgSuccess('冲正成功') |
||||||
|
this.getList() |
||||||
|
}) |
||||||
|
.catch(error => { |
||||||
|
if (error !== 'cancel') this.$modal.msgError('冲正失败,请稍后重试') |
||||||
|
}) |
||||||
|
}, |
||||||
|
canReverse(row) { |
||||||
|
return String(row.status) === '1' |
||||||
|
}, |
||||||
|
statusLabel(row) { |
||||||
|
const value = String(row.status) |
||||||
|
return ({ '1': '有效', '2': '已冲正' }[value]) || (row.status === undefined || row.status === null ? '—' : value) |
||||||
|
}, |
||||||
|
statusTagType(row) { |
||||||
|
return String(row.status) === '2' ? 'info' : 'success' |
||||||
|
}, |
||||||
|
bizTypeLabel(row) { |
||||||
|
const item = BIZ_TYPES.find(option => option.value === row.bizType) |
||||||
|
return item ? item.label : (row.bizType || '—') |
||||||
|
}, |
||||||
|
bizTypeTag(row) { |
||||||
|
const map = { REPORT: 'success', EXCHANGE: 'primary', REFUND: 'warning' } |
||||||
|
return map[row.bizType] || 'info' |
||||||
|
}, |
||||||
|
amountLabel(row) { |
||||||
|
const value = row.amount |
||||||
|
if (value === undefined || value === null || value === '') return '—' |
||||||
|
const n = Number(value) |
||||||
|
if (Number.isNaN(n)) return String(value) |
||||||
|
return `${n > 0 ? '+' : ''}${n}` |
||||||
|
}, |
||||||
|
amountColor(row) { |
||||||
|
const n = Number(row.amount) |
||||||
|
if (Number.isNaN(n) || n === 0) return 'var(--prototype-text, #1a1a2e)' |
||||||
|
return n > 0 ? '#2d8a4e' : '#d64550' |
||||||
|
}, |
||||||
|
balanceText(row) { |
||||||
|
const before = row.beforeBalance |
||||||
|
const after = row.afterBalance |
||||||
|
if ((before === undefined || before === null) && (after === undefined || after === null)) return '—' |
||||||
|
return `${before === undefined || before === null ? '—' : before} → ${after === undefined || after === null ? '—' : after}` |
||||||
|
}, |
||||||
|
formatTime(value) { |
||||||
|
if (!value) return '—' |
||||||
|
return String(value).replace('T', ' ').slice(0, 19) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style lang="scss" scoped> |
||||||
|
.txn-muted { |
||||||
|
color: var(--prototype-text-muted, #9898ae); |
||||||
|
} |
||||||
|
</style> |
||||||
Loading…
Reference in new issue