|
|
<template> <el-card class="card1" style="margin-bottom: 0.5vh;"> <el-col style="margin-bottom: 1vh"> <div class="select"> <div class="selectRow"> <el-text class="text" size="large">{{ $t('common.jwcode') }}:</el-text> <el-input class="selectContent" v-model="searchForm.jwcode" :placeholder="t('common.jwcodePlaceholder')" style="width: 12vw;margin-right:1vw" clearable /> </div> <div class="selectRow"> <el-text class="text" size="large">{{ $t('common.market') }}:</el-text> <el-cascader v-model="selectedMarkets" :options="marketOptions" :placeholder="$t('common.marketPlaceholder')" clearable style="width: 12vw" @change="handleMarketChange" /> </div> </div> </el-col> <el-col> <div class="select"> <div class="selectRow" style="width: 36vw;"> <el-text class="text" size="large" v-show="checkTab === 'pending'">{{ $t('common.submitTime') }}:</el-text> <el-text class="text" size="large" v-show="checkTab === 'reject' || checkTab === 'pass'">{{ $t('common.auditTime') }}:</el-text> <el-date-picker v-model="dateRange" type="datetimerange" :range-separator="t('common.to')" :start-placeholder="$t('common.startTime')" :end-placeholder="$t('common.endTime')" class="selectContent" style="width: 25vw;margin-right:1vw" @change="handleDatePickerChange" :default-time="defaultTime" /> <!-- <el-button @click="getToday()" :type="activeTimeRange === 'today' ? 'primary' : ''">今</el-button>--> <!-- <el-button @click="getYesterday()" :type="activeTimeRange === 'yesterday' ? 'primary' : ''">昨</el-button>--> <!-- <el-button @click="get7Days()" :type="activeTimeRange === '7days' ? 'primary' : ''">近7天</el-button>--> </div> <div class="selectRow" style="justify-content: flex-start;"> <el-button type="primary" @click="handleSearch">{{ t('common.search') }}</el-button> <el-button type="success" @click="resetSearch">{{ t('common.reset') }}</el-button>
</div> </div> </el-col> </el-card>
<el-card class="card2"> <!-- 将el-tabs替换为按钮组 --> <div class="custom-button-group"> <el-button v-if="hasbeanWait && hasbeanWaitShow" :type="checkTab === 'pending' ? 'primary' : 'default'" class="custom-tab-button" @click="adminWait"> {{ $t('audit.waitAudit') }} </el-button> <el-button v-if="hasbeanThrough" :type="checkTab === 'pass' ? 'primary' : 'default'" class="custom-tab-button" @click="adminPass"> {{ $t('audit.passed') }} </el-button> <el-button v-if="hasbeanReject" :type="checkTab === 'reject' ? 'primary' : 'default'" class="custom-tab-button" @click="adminReject"> {{ $t('audit.rejected') }} </el-button> </div>
<div class="goldStatistics"> {{ $t('audit.totalNum') }}:{{ format3(stats.num) }}{{ $t('common.条') }} {{ $t('audit.totalBean') }}:{{ format3(stats.beanNum) }}{{ $t('common.goldBean') }} {{ $t('audit.permanentBean') }}:{{ format3(stats.permanentBean) }}{{ $t('common.goldBean') }} {{ $t('audit.freeBean') }}:{{ format3(stats.freeBean) }}{{ $t('common.goldBean') }} </div>
<el-table :data="tableData" height="65vh" @sort-change="handleSortChange" :row-style="{ height: '50px' }"> <el-table-column type="index" :label="$t('audit.id')" width="80"> <template #default="scope"> {{ scope.$index + 1 + (pagination.pageNum - 1) * pagination.pageSize }} </template> </el-table-column> <el-table-column prop="name" :label="$t('audit.name')" width="120" show-overflow-tooltip /> <el-table-column prop="jwcode" :label="$t('audit.jwcode')" width="120" /> <el-table-column prop="market" :label="$t('audit.market')" width="120" /> <el-table-column prop="permanentBean" :label="$t('audit.permanentBean')" width="120" sortable="custom" /> <el-table-column prop="freeBean" :label="$t('audit.freeBean')" width="120" sortable="custom" /> <el-table-column prop="remark" :label="$t('audit.note')" width="150" show-overflow-tooltip /> <el-table-column prop="submitName" :label="$t('audit.submitter')" width="120" /> <el-table-column v-if="checkTab === 'reject'" prop="reason" :label="$t('audit.rejectReason')" width="120" show-overflow-tooltip /> <el-table-column v-if="checkTab !== 'pending'" prop="auditName" :label="$t('audit.auditor')" width="120" /> <el-table-column prop="createTime" :label="$t('audit.submitTime')" width="180" sortable="custom"> <template #default="{ row }"> {{ moment(row.createTime).format('YYYY-MM-DD HH:mm:ss') }} </template> </el-table-column> <el-table-column v-if="checkTab !== 'pending'" prop="auditTime" :label="$t('audit.auditTime')" width="240" sortable="custom"> <template #default="{ row }"> {{ row.auditTime ? moment(row.auditTime).format('YYYY-MM-DD HH:mm:ss') : '--' }} </template> </el-table-column> <el-table-column v-if="checkTab === 'pending' && (hasbeanWaitThough || hasbeanWaitReject)" fixed="right" prop="operation" :label="$t('audit.operation')" width="200px"> <template #default="scope"> <div class="operation">
<el-link :underline="false" class="pass-btn" v-if="hasbeanWaitThough" :disabled="clicked || cancelClicked" type="primary" @click="showApproveDialog(scope.row)"> {{ $t('common.pass') }} </el-link> <el-link :underline="false" class="reject-btn" v-if="hasbeanWaitReject" :disabled="clicked || cancelClicked" type="primary" @click="showRejectDialog(scope.row)"> {{ $t('common.reject') }} </el-link> </div> </template> </el-table-column> </el-table> <div class="pagination"> <el-pagination background :current-page="pagination.pageNum" :page-size="pagination.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="stats.num" @size-change="handlePageSizeChange" @current-change="handleCurrentChange"></el-pagination> </div> </el-card>
<!-- 驳回理由对话框 --> <el-dialog v-model="rejectReasonDialogVisible" :title="$t('audit.rejectReason')" width="500px" @close="handleRejectReasonCancel"> <el-form> <el-form-item :label="$t('audit.rejectReason')" required> <el-input v-model="reason" type="textarea" :rows="4" :placeholder="$t('audit.rejectReasonPlaceholder')" maxlength="200" show-word-limit /> </el-form-item> </el-form> <template #footer> <span class="dialog-footer"> <el-button @click="handleRejectReasonCancel">{{ $t('common.cancel') }}</el-button> <el-button :disabled="cancelClicked" type="primary" @click="handleReject">{{ $t('common.confirm') }}</el-button> </span> </template> </el-dialog>
<ConfirmDialog v-model="rejectDialogVisible" :message="$t('audit.rejectRecord')" @confirm="showRejectReasonInput" @cancel="handleRejectCancel" @close="handleRejectClose" />
<ConfirmDialog v-model="approveDialogVisible" :message="$t('audit.passRecord')" @confirm="throttledHandleApprove" @cancel="handleApproveCancel" @close="handleApproveClose" />
</template><script setup>import { onMounted, ref, watch } from 'vue'import { ElMessage } from 'element-plus'import API from '@/util/http.js'import moment from 'moment'import { useAdminStore } from "@/store/index.js";import { storeToRefs } from "pinia";import _ from 'lodash'import { permissionMapping, hasMenuPermission } from "@/utils/menuTreePermission.js"import ConfirmDialog from '@/components/dialogs/ConfirmDialog.vue';// i18n国际化包
import { useI18n } from 'vue-i18n'const { t } = useI18n()
const defaultTime = [ new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59),]const adminStore = useAdminStore();const { adminData, menuTree,flag } = storeToRefs(adminStore);
// 监听全局flag状态变化
watch(flag, (newFlag, oldFlag) => { // 当flag状态改变时,重新发送请求
if (newFlag !== oldFlag) { // console.log('员工数据flag状态改变,重新加载数据', newFlag)
get() getStats() // console.log('flag2',flag)
}})
import dayjs from "dayjs";import {reverseMarketMapping} from "@/utils/marketMap.js";
const tableData = ref([])const marketOptions = ref("")const dateRange = ref([])const activeTimeRange = ref('')const sortField = ref('')const sortOrder = ref('')const checkTab = ref('pending')const rejectDialogVisible = ref(false)const rejectReasonDialogVisible = ref(false) // 驳回理由输入框显示状态
const reason = ref('')const rejectRow = ref({ id: null})// 驳回行数据
const passRow = ref({ id: null})// 通过行数据
const popconfirmRef = ref(null)
// 操作权限(金豆审核相关,与充值权限格式对齐)
const hasbeanWait = ref(false) // 金豆审核待审核(对应beanWait:42)
const hasbeanThrough = ref(false) // 金豆审核已通过(对应beanThrough:43)
const hasbeanReject = ref(false) // 金豆审核已驳回(对应beanReject:44)
const hasbeanWaitShow = ref(false) // 金豆审核待审核查看(对应beanWaitShow:45)
const hasbeanWaitThough = ref(false) // 金豆审核通过(对应beanWaitThough:46)
const hasbeanWaitReject = ref(false) // 金豆审核驳回(对应beanWaitReject:47)
// 初始化权限状态(补充金豆审核权限,与充值权限初始化逻辑并列)
const initPermissions = async () => { if (!menuTree.value || !menuTree.value.length) return; hasbeanWait.value = hasMenuPermission(menuTree.value, permissionMapping.gold_bean_audit_pending); hasbeanThrough.value = hasMenuPermission(menuTree.value, permissionMapping.pass_gold_bean_audit); hasbeanReject.value = hasMenuPermission(menuTree.value, permissionMapping.reject_gold_bean_audit); hasbeanWaitShow.value = hasMenuPermission(menuTree.value, permissionMapping.view_gold_bean_pending); hasbeanWaitThough.value = hasMenuPermission(menuTree.value, permissionMapping.gold_bean_audit_approved); hasbeanWaitReject.value = hasMenuPermission(menuTree.value, permissionMapping.gold_bean_audit_rejected);};
// 设置对话框可见性
const approveDialogVisible = ref(false)const clicked = ref(false)// 状态常量
const STATUS = { PENDING: 0, // 待审核
APPROVED: 1, // 通过
REJECTED: 2 // 驳回
}const cancelClicked = ref(false)// 存储地区选择变化
const selectedMarkets = ref("")const searchForm = ref({ jwcode: '', market: '', createStartTime: '', createEndTime: '', status: STATUS.PENDING, auditStartTime: '', auditEndTime: ''})const pagination = ref({ pageNum: 1, pageSize: 50})// 合计数
const stats = ref({ num: 0, beanNum: 0, permanentBean: 0, freeBean: 0})const handleSortChange = (column) => { if (column.prop === 'permanentBean') { sortField.value = 'permanentBean' } else if (column.prop === 'freeBean') { sortField.value = 'freeBean' } else if (column.prop === 'createTime') { sortField.value = 'createTime' } else if (column.prop === 'auditTime') { sortField.value = 'auditTime' } else { sortField.value = '' } sortOrder.value = column.order === 'ascending' ? 'ASC' : 'DESC' console.log('排序字段:', sortField.value) console.log('排序方式:', sortOrder.value) get()}const handleSearch = async function () { trimJwCode() if (searchForm.value.jwcode) { const numRef = /^\d{1,9}$/; if (!numRef.test(searchForm.value.jwcode)) { ElMessage.error(t('elmessage.checkJwcodeFormat')) return } } await get() await getStats()}const get = async function () { if (!hasbeanWaitShow) { ElMessage.error(t('elmessage.noPermission')) return } try { if (dateRange.value && dateRange.value.length === 2) { if (checkTab.value === 'pending') { searchForm.value.createStartTime = moment(dateRange.value[0]).format('YYYY-MM-DD HH:mm:ss') searchForm.value.createEndTime = moment(dateRange.value[1]).format('YYYY-MM-DD HH:mm:ss') } else { searchForm.value.auditStartTime = moment(dateRange.value[0]).format('YYYY-MM-DD HH:mm:ss') searchForm.value.auditEndTime = moment(dateRange.value[1]).format('YYYY-MM-DD HH:mm:ss') } } else { searchForm.value.createStartTime = '' searchForm.value.createEndTime = '' } if (checkTab.value === 'pending') { sortField.value = 'createTime' sortOrder.value = 'desc' } else { sortField.value = 'auditTime' sortOrder.value = 'desc' } const params = { pageNum: pagination.value.pageNum,//页码
pageSize: pagination.value.pageSize,//页面大小
beanAuditInfo: { jwcode: searchForm.value.jwcode, status: searchForm.value.status, market: searchForm.value.market, createStartTime: searchForm.value.createStartTime, createEndTime: searchForm.value.createEndTime, auditStartTime: searchForm.value.auditStartTime, auditEndTime: searchForm.value.auditEndTime, sortField: sortField.value, sortOrder: sortOrder.value, flag: flag.value } } console.log('看看传给后端的参数:', params) const res = await API({ url: '/beanAudit/selectBy', data: params }) tableData.value = res.data.list || [] } catch (error) { console.error('获取数据失败', error) }}const getStats = async () => { if (!hasbeanWaitShow) { ElMessage.error(t('elmessage.noPermission')) return } try { const params = { jwcode: searchForm.value.jwcode, status: searchForm.value.status, market: searchForm.value.market, createStartTime: searchForm.value.createStartTime, createEndTime: searchForm.value.createEndTime, auditStartTime: searchForm.value.auditStartTime, auditEndTime: searchForm.value.auditEndTime } const res = await API({ url: '/beanAudit/statsBean', data: { ...params, flag: flag.value } })
stats.value.num = res.data.num stats.value.permanentBean = res.data.permanentBean stats.value.freeBean = res.data.freeBean stats.value.beanNum = res.data.beanNum console.log('see see stats和搜索对象', stats.value, params) } catch (error) { console.log('请求失败', error) }}// 显示通过对话框
const showApproveDialog = (row) => { if (!hasbeanWaitThough) { ElMessage.error(t('elmessage.noPermission')) return } passRow.value.id = row.id approveDialogVisible.value = true}
// 处理通过操作
// 为什么使用handleApproveConfirm页面就加载不出来,使用handleApprove就可以,
// (发现问题:加了防抖,名称为handleApprove,修改名称一致即可)
const handleApproveConfirm = async function() {// 不要再传row了!哪有row!!!
if (!hasbeanWaitThough) { ElMessage.error(t('elmessage.noPermission')) return } clicked.value = true try { const params = { id: passRow.value.id, auditName: adminData.value.adminName } await API({ url: '/beanAudit/status1', data: params }) ElMessage.success(t('elmessage.approveSuccess')) approveDialogVisible.value = false await get() clicked.value = false await getStats() } catch (error) { console.error(t('elmessage.approveFailed'), error) ElMessage.error(t('elmessage.operationFailed')) }}// 处理通过取消操作
const handleApproveCancel = () => { approveDialogVisible.value = false}// 处理通过关闭操作
const handleApproveClose = () => { approveDialogVisible.value = false}// 处理驳回操作
const handleReject = async () => { if (!hasbeanWaitReject) { ElMessage.error(t('elmessage.noPermission')) return } cancelClicked.value = true if (!reason.value.trim()) { ElMessage.warning(t('elmessage.rejectReasonPlaceholder')) cancelClicked.value = false return } try { const params = { id: rejectRow.value.id, auditName: adminData.value.adminName, reason: reason.value } await API({ url: '/beanAudit/status2', data: params }) ElMessage.success(t('elmessage.rejectSuccess')) rejectReasonDialogVisible.value = false await get() cancelClicked.value = false await getStats() } catch (error) { console.error(t('elmessage.rejectFailed'), error) ElMessage.error(t('elmessage.operationFailed')) }}// 确认驳回后显示理由输入框
const showRejectReasonInput = () => { rejectDialogVisible.value = false // 关闭确认对话框
rejectReasonDialogVisible.value = true // 打开驳回理由输入框
}// 处理驳回取消操作
const handleRejectCancel = () => { rejectDialogVisible.value = false}// 处理驳回关闭操作
const handleRejectClose = () => { rejectDialogVisible.value = false}// 处理驳回理由对话框关闭和取消操作
const handleRejectReasonCancel = () => { rejectReasonDialogVisible.value = false cancelClicked.value = false // 重置禁用状态
reason.value = '' // 清空驳回理由
}// 这个防抖好像还不能放在定义之前(handleApproveConfirm)
const throttledHandleApprove = _.throttle(handleApproveConfirm, 5000, { trailing: false})// 显示驳回对话框
const showRejectDialog = (row) => { if (!hasbeanWaitReject) { ElMessage.error(t('elmessage.noPermission')) return } rejectRow.value.id = row.id reason.value = '' rejectDialogVisible.value = true}
const getToday = async function () { const today = dayjs() const startTime = today.startOf('day').format('YYYY-MM-DD HH:mm:ss') const endTime = today.endOf('day').format('YYYY-MM-DD HH:mm:ss') dateRange.value = [startTime, endTime] console.log('dateRange', dateRange.value) activeTimeRange.value = 'today' await get() await getStats()}const getYesterday = async function () {
const today = dayjs() const startTime = today.subtract(1, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss') const endTime = today.subtract(1, 'day').endOf('day').format('YYYY-MM-DD HH:mm:ss') dateRange.value = [startTime, endTime] console.log('dateRange', dateRange.value) activeTimeRange.value = 'yesterday' await get() await getStats()}const get7Days = async function () { const today = dayjs() const startTime = today.subtract(6, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss') const endTime = today.endOf('day').format('YYYY-MM-DD HH:mm:ss') dateRange.value = [startTime, endTime] console.log('dateRange', dateRange.value) activeTimeRange.value = '7days' await get() await getStats()}const resetSearch = async function () { const status = searchForm.value.status; searchForm.value = { jwcode: '', market: '', createStartTime: '', createEndTime: '', status: status, auditStartTime: '', auditEndTime: '' } // 重置页码
pagination.value.pageNum = 1 selectedMarkets.value = [] dateRange.value = [] activeTimeRange.value = ''
await get() await getStats()}const handleClick = async function (tab) { checkTab.value = tab.props.name if (tab.props.name === 'pending') { if (!hasbeanWait) { ElMessage.error(t('elmessage.noPermission')) return } adminWait() } else if (tab.props.name === 'pass') { if (!hasbeanThrough) { ElMessage.error(t('elmessage.noPermission')) return } adminPass() } else if (tab.props.name === 'reject') { if (!hasbeanReject) { ElMessage.error(t('elmessage.noPermission')) return } adminReject() }}// 待审核
const adminWait = async function () { checkTab.value = 'pending' searchForm.value.status = STATUS.PENDING await get() await getStats() console.log('切换页面后:', checkTab.value, sortField.value, sortOrder.value)}
// 已通过
const adminPass = async function () { checkTab.value = 'pass' searchForm.value.status = STATUS.APPROVED await get() await getStats() console.log('切换页面后:', checkTab.value, sortField.value, sortOrder.value)}
// 已驳回
const adminReject = async function () { checkTab.value = 'reject' searchForm.value.status = STATUS.REJECTED await get() await getStats() console.log('切换页面后:', checkTab.value, sortField.value, sortOrder.value)}const handleMarketChange = (value) => { if (value && value.length > 0) { const lastValue = value[value.length - 1] searchForm.value.market = reverseMarketMapping[lastValue] } else { searchForm.value.market = '' }}const handleDatePickerChange = () => { activeTimeRange.value = ''}const handlePageSizeChange = function (val) { pagination.value.pageSize = val get()}const handleCurrentChange = function (val) { pagination.value.pageNum = val get()}const format3 = (num) => { // 每三位添加逗号
return num.toLocaleString('en-US')}
// 获取地区,修改为级联下拉框
const getmarkets = async function () { try { const result = await API({ url: '/market/selectMarket', }); console.log('请求成功', result) // 递归转换树形结构为级联选择器需要的格式(跳过第一级节点)
const transformTree = (nodes) => { // 直接处理第一级节点的子节点
const allChildren = nodes.flatMap(node => node.children || []); return allChildren.map(child => { const grandchildren = child.children && child.children.length ? transformTree([child]) // 递归处理子节点
: null; return { value: child.name, label: child.name, children: grandchildren } }) } marketOptions.value = transformTree(result.data) console.log('转换后的地区树==============', marketOptions.value) } catch (error) { console.log('请求失败', error) }}const trimJwCode = () => { if (searchForm.value.jwcode) { searchForm.value.jwcode = searchForm.value.jwcode.replace(/\s/g, ''); }}onMounted(async () => { await initPermissions() if (hasbeanWaitShow.value) { searchForm.value.status = 0 } else if (hasbeanThrough.value) { searchForm.value.status = 1 } else if (hasbeanReject) { searchForm.value.status = 2 } getmarkets() await get() await getStats() console.log("看看通信来的用户身份", adminData.value)})</script>
<style scoped lang="scss">.pagination { display: flex; margin-top: 1vh;}
/* 搜索的卡片样式 */.card1 { background: #F3FAFE;}
/* 表单的卡片样式 */.card2 { background: #E7F4FD;}
/* 自定义按钮组布局 */.custom-button-group { display: flex; margin-bottom: 16px;}
/* 自定义按钮样式 */:deep(.el-button.custom-tab-button) { border-radius: 4px; transition: all 0.3s ease;}
/* 激活状态的按钮样式 */:deep(.el-button.custom-tab-button.el-button--primary) { background-color: #2741DE !important; border-color: #2741DE !important; color: #F3FAFE !important;
}
/* 悬停效果 */:deep(.el-button.custom-tab-button:hover:not(.is-disabled)) { opacity: 0.8;}
/* 充值新币总数等等 */.goldStatistics { margin-left: 1vw; margin-bottom: 1vh; color: #000000; font-family: "PingFang SC"; font-size: 16px; font-style: normal; font-weight: 700; line-height: 20px;}
/* 表头/表体 wrapper 与 table body 单元格 */:deep(.el-table__header-wrapper),:deep(.el-table__body-wrapper),:deep(.el-table__cell),
/* 表格 */:deep(.el-table__body td) { background-color: #F3FAFE !important;}
/* 表头 */:deep(.el-table__header th) { background-color: #F3FAFE !important;}
/* 鼠标悬停 */:deep(.el-table__row:hover > .el-table__cell) { background-color: #E5EBFE !important;}
.select { display: flex;
.selectRow { width: 17vw; display: flex; align-items: center; justify-content: center; padding: 0 0.5vw;
.text { width: 5vw; font-size: 15px; }
.selectContent { flex: 1; } }}</style>
|