You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

496 lines
18 KiB

2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
3 weeks ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
  1. <template>
  2. <el-card style="margin-bottom: 0.5vh;width:82vw">
  3. <div style="margin-bottom: 1vh">
  4. <el-text size="large">精网号</el-text>
  5. <el-input v-model="searchForm.jwcode" placeholder="请输入精网号" style="width: 12vw;margin-right:1vw" clearable />
  6. <el-text size="large">所属地区</el-text>
  7. <el-cascader v-model="selectedMarkets" :options="marketOptions" placeholder="请选择所属地区" clearable
  8. style="width: 12vw" @change="handleMarketChange" />
  9. </div>
  10. <el-col>
  11. <el-text size="large" style="width: 80px" v-show="checkTab === 'pending'">提交时间</el-text>
  12. <el-text size="large" style="width: 80px" v-show="checkTab === 'reject' || checkTab === 'pass'">审核时间</el-text>
  13. <el-date-picker v-model="dateRange" type="datetimerange" range-separator="" start-placeholder="开始时间"
  14. end-placeholder="结束时间" style="width: 25vw;margin-right:1vw" @change="handleDatePickerChange"
  15. :default-time="defaultTime" />
  16. <el-button @click="getToday()" :type="activeTimeRange === 'today' ? 'primary' : ''"></el-button>
  17. <el-button @click="getYesterday()" :type="activeTimeRange === 'yesterday' ? 'primary' : ''"></el-button>
  18. <el-button @click="get7Days()" :type="activeTimeRange === '7days' ? 'primary' : ''">近7天</el-button>
  19. <el-button type="success" @click="resetSearch">重置</el-button>
  20. <el-button type="primary" @click="handleSearch">查询</el-button>
  21. </el-col>
  22. </el-card>
  23. <el-card>
  24. <el-tabs v-model="checkTab" type="card" @tab-click="handleClick">
  25. <el-tab-pane label="待审核" name="pending"></el-tab-pane>
  26. <el-tab-pane label="已通过" name="pass"></el-tab-pane>
  27. <el-tab-pane label="已驳回" name="reject"></el-tab-pane>
  28. </el-tabs>
  29. <div>
  30. 总条数{{ format3(stats.num) }}&nbsp;&nbsp;&nbsp;&nbsp;
  31. 总金豆数{{ format3(stats.beanNum) }}金豆&nbsp;&nbsp;&nbsp;&nbsp;
  32. 付费金豆{{ format3(stats.permanentBean) }}金豆&nbsp;&nbsp;&nbsp;&nbsp;
  33. 免费金豆{{ format3(stats.freeBean) }}金豆
  34. </div>
  35. <el-table :data="tableData" height="55vh" @sort-change="handleSortChange">
  36. <el-table-column type="index" label="序号" width="80">
  37. <template #default="scope">
  38. {{ scope.$index + 1 + (pagination.pageNum - 1) * pagination.pageSize }}
  39. </template>
  40. </el-table-column>
  41. <el-table-column prop="name" label="姓名" width="120" show-overflow-tooltip />
  42. <el-table-column prop="jwcode" label="精网号" width="120" />
  43. <el-table-column prop="market" label="所属地区" width="120" />
  44. <el-table-column prop="permanentBean" label="付费金豆" width="120" sortable="custom" />
  45. <el-table-column prop="freeBean" label="免费金豆" width="120" sortable="custom" />
  46. <el-table-column prop="remark" label="备注" width="150" show-overflow-tooltip />
  47. <el-table-column prop="submitName" label="提交人" width="120" />
  48. <el-table-column v-if="checkTab === 'reject'" prop="reason" label="驳回理由" width="120" show-overflow-tooltip />
  49. <el-table-column v-if="checkTab !== 'pending'" prop="auditName" label="审核人" width="120" />
  50. <el-table-column prop="createTime" label="提交时间" width="180" sortable="custom">
  51. <template #default="{ row }">
  52. {{ moment(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}
  53. </template>
  54. </el-table-column>
  55. <el-table-column v-if="checkTab !== 'pending'" prop="auditTime" label="审核时间" width="240" sortable="custom">
  56. <template #default="{ row }">
  57. {{ row.auditTime ? moment(row.auditTime).format('YYYY-MM-DD HH:mm:ss') : '--' }}
  58. </template>
  59. </el-table-column>
  60. <el-table-column v-if="checkTab === 'pending'" fixed="right" prop="operation" label="操作" width="400px">
  61. <template #default="scope">
  62. <div class="operation">
  63. <el-popconfirm title="确定要通过此条记录吗?" @confirm="handleApprove(scope.row)">
  64. <template #reference>
  65. <el-button :disabled="clicked || cancelClicked" type="primary" text>
  66. 通过
  67. </el-button>
  68. </template>
  69. <template #actions="{ confirm }">
  70. <el-button size="small">取消</el-button>
  71. <el-button type="primary" size="small" :disabled="clicked" @click="confirm">
  72. 确认
  73. </el-button>
  74. </template>
  75. </el-popconfirm>
  76. <el-button :disabled="clicked || cancelClicked" type="primary" text @click="showRejectDialog(scope.row)">
  77. 驳回
  78. </el-button>
  79. </div>
  80. </template>
  81. </el-table-column>
  82. </el-table>
  83. <el-pagination style="margin-top:20px" v-model:current-page="pagination.pageNum"
  84. v-model:page-size="pagination.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="stats.num"
  85. @size-change="handlePageSizeChange" @current-change="handleCurrentChange"></el-pagination>
  86. </el-card>
  87. <el-dialog v-model="rejectVisible" title="驳回理由" width="500px">
  88. <el-form>
  89. <el-form-item label="驳回理由" required>
  90. <el-input v-model="reason" type="textarea" :rows="4" placeholder="请输入驳回理由" maxlength="200" show-word-limit />
  91. </el-form-item>
  92. </el-form>
  93. <template #footer>
  94. <span class="dialog-footer">
  95. <el-button @click="rejectVisible = false">取消</el-button>
  96. <el-button type="primary" @click="handleReject()">确定</el-button>
  97. </span>
  98. </template>
  99. </el-dialog>
  100. </template>
  101. <script setup>
  102. import { onMounted, ref } from 'vue'
  103. import { ElMessage } from 'element-plus'
  104. import API from '@/util/http.js'
  105. import moment from 'moment'
  106. import { useAdminStore } from "@/store/index.js";
  107. import { storeToRefs } from "pinia";
  108. import _ from 'lodash'
  109. const defaultTime = [
  110. new Date(2000, 1, 1, 0, 0, 0),
  111. new Date(2000, 2, 1, 23, 59, 59),
  112. ]
  113. const adminStore = useAdminStore();
  114. const { adminData, menuTree } = storeToRefs(adminStore);
  115. import { permissionMapping, findMenuById } from "@/utils/menuTreePermission.js"
  116. import dayjs from "dayjs";
  117. const tableData = ref([])
  118. const marketOptions = ref("")
  119. const dateRange = ref([])
  120. const activeTimeRange = ref('')
  121. const sortField = ref('')
  122. const sortOrder = ref('')
  123. const checkTab = ref('pending')
  124. const rejectVisible = ref(false)
  125. const reason = ref('')
  126. const rejectRow = ref({
  127. id: null
  128. })// 驳回行数据
  129. // 操作权限(金豆审核相关,与充值权限格式对齐)
  130. const hasbeanWait = ref(false) // 金豆审核待审核(对应beanWait:42)
  131. const hasbeanThrough = ref(false) // 金豆审核已通过(对应beanThrough:43)
  132. const hasbeanReject = ref(false) // 金豆审核已驳回(对应beanReject:44)
  133. const hasbeanWaitShow = ref(false) // 金豆审核待审核查看(对应beanWaitShow:45)
  134. const hasbeanWaitThough = ref(false) // 金豆审核通过(对应beanWaitThough:46)
  135. const hasbeanWaitReject = ref(false) // 金豆审核驳回(对应beanWaitReject:47)
  136. // 初始化权限状态(补充金豆审核权限,与充值权限初始化逻辑并列)
  137. const initPermissions = () => {
  138. if (!menuTree.value || !menuTree.value.length) return;
  139. // 2. 金豆审核相关权限初始化(新增,与充值权限结构一致)
  140. hasbeanWait.value = hasMenuPermission(menuTree.value, permissionMapping.hasbeanWait);
  141. hasbeanThrough.value = hasMenuPermission(menuTree.value, permissionMapping.hasbeanThrough);
  142. hasbeanReject.value = hasMenuPermission(menuTree.value, permissionMapping.hasbeanReject);
  143. hasbeanWaitShow.value = hasMenuPermission(menuTree.value, permissionMapping.hasbeanWaitShow);
  144. hasbeanWaitThough.value = hasMenuPermission(menuTree.value, permissionMapping.hasbeanWaitThough);
  145. hasbeanWaitReject.value = hasMenuPermission(menuTree.value, permissionMapping.hasbeanWaitReject);
  146. };
  147. const clicked = ref(false)
  148. // 状态常量
  149. const STATUS = {
  150. PENDING: 0, // 待审核
  151. APPROVED: 1, // 通过
  152. REJECTED: 2 // 驳回
  153. }
  154. const cancelClicked = ref(false)
  155. // 存储地区选择变化
  156. const selectedMarkets = ref("")
  157. const searchForm = ref({
  158. jwcode: '',
  159. market: '',
  160. createStartTime: '',
  161. createEndTime: '',
  162. status: STATUS.PENDING,
  163. auditStartTime: '',
  164. auditEndTime: ''
  165. })
  166. const pagination = ref({
  167. pageNum: 1,
  168. pageSize: 50
  169. })
  170. // 合计数
  171. const stats = ref({
  172. num: 0,
  173. beanNum: 0,
  174. permanentBean: 0,
  175. freeBean: 0
  176. })
  177. const handleSortChange = (column) => {
  178. if (column.prop === 'permanentBean') {
  179. sortField.value = 'permanentBean'
  180. } else if (column.prop === 'freeBean') {
  181. sortField.value = 'freeBean'
  182. } else if (column.prop === 'createTime') {
  183. sortField.value = 'createTime'
  184. } else if (column.prop === 'auditTime') {
  185. sortField.value = 'auditTime'
  186. } else {
  187. sortField.value = ''
  188. }
  189. sortOrder.value = column.order === 'ascending' ? 'ASC' : 'DESC'
  190. console.log('排序字段:', sortField.value)
  191. console.log('排序方式:', sortOrder.value)
  192. get()
  193. }
  194. const handleSearch = async function () {
  195. trimJwCode()
  196. if (searchForm.value.jwcode) {
  197. const numRef = /^\d{1,9}$/;
  198. if (!numRef.test(searchForm.value.jwcode)) {
  199. ElMessage.error('请检查精网号格式')
  200. return
  201. }
  202. }
  203. await get()
  204. await getStats()
  205. }
  206. const get = async function () {
  207. if (findMenuById(menuTree.value, permissionMapping.View_Golden_Bean_Recharge_Audit)) {
  208. try {
  209. if (dateRange.value && dateRange.value.length === 2) {
  210. if (checkTab.value === 'pending') {
  211. searchForm.value.createStartTime = moment(dateRange.value[0]).format('YYYY-MM-DD HH:mm:ss')
  212. searchForm.value.createEndTime = moment(dateRange.value[1]).format('YYYY-MM-DD HH:mm:ss')
  213. } else {
  214. searchForm.value.auditStartTime = moment(dateRange.value[0]).format('YYYY-MM-DD HH:mm:ss')
  215. searchForm.value.auditEndTime = moment(dateRange.value[1]).format('YYYY-MM-DD HH:mm:ss')
  216. }
  217. } else {
  218. searchForm.value.createStartTime = ''
  219. searchForm.value.createEndTime = ''
  220. }
  221. if(checkTab.value === 'pending'){
  222. sortField.value = 'createTime'
  223. sortOrder.value = 'desc'
  224. }else{
  225. sortField.value = 'auditTime'
  226. sortOrder.value = 'desc'
  227. }
  228. const params = {
  229. pageNum: pagination.value.pageNum,//页码
  230. pageSize: pagination.value.pageSize,//页面大小
  231. beanAuditInfo: {
  232. jwcode: searchForm.value.jwcode,
  233. status: searchForm.value.status,
  234. market: searchForm.value.market,
  235. createStartTime: searchForm.value.createStartTime,
  236. createEndTime: searchForm.value.createEndTime,
  237. auditStartTime: searchForm.value.auditStartTime,
  238. auditEndTime: searchForm.value.auditEndTime,
  239. sortField: sortField.value,
  240. sortOrder: sortOrder.value
  241. }
  242. }
  243. console.log('看看传给后端的参数:', params)
  244. const res = await API({ url: '/beanAudit/selectBy', data: params })
  245. tableData.value = res.data.list || []
  246. } catch (error) {
  247. console.error('获取数据失败', error)
  248. }
  249. } else {
  250. ElMessage.error('无此权限')
  251. }
  252. }
  253. const getStats = async () => {
  254. if (findMenuById(menuTree.value, permissionMapping.View_Golden_Bean_Recharge_Audit)) {
  255. try {
  256. const params = {
  257. jwcode: searchForm.value.jwcode,
  258. status: searchForm.value.status,
  259. market: searchForm.value.market,
  260. createStartTime: searchForm.value.createStartTime,
  261. createEndTime: searchForm.value.createEndTime,
  262. auditStartTime: searchForm.value.auditStartTime,
  263. auditEndTime: searchForm.value.auditEndTime
  264. }
  265. const res = await API({
  266. url: '/beanAudit/statsBean',
  267. data: params
  268. })
  269. stats.value.num = res.data.num
  270. stats.value.permanentBean = res.data.permanentBean
  271. stats.value.freeBean = res.data.freeBean
  272. stats.value.beanNum = res.data.beanNum
  273. console.log('see see stats和搜索对象', stats.value, params)
  274. } catch (error) {
  275. console.log('请求失败', error)
  276. }
  277. } else {
  278. ElMessage.error('无此权限')
  279. }
  280. }
  281. // 处理通过操作
  282. const handleApprove = async (row) => {
  283. clicked.value = true
  284. if (findMenuById(menuTree.value, permissionMapping.Golden_Bean_Recharge_Approval)) {
  285. try {
  286. const params = {
  287. id: row.id,
  288. auditName: adminData.value.adminName
  289. }
  290. await API({ url: '/beanAudit/status1', data: params })
  291. ElMessage.success('审核通过成功')
  292. await get()
  293. clicked.value = false
  294. await getStats()
  295. } catch (error) {
  296. console.error('审核通过失败', error)
  297. ElMessage.error('操作失败')
  298. }
  299. } else {
  300. ElMessage.error('无此权限')
  301. clicked.value = false
  302. }
  303. }
  304. // 处理驳回操作
  305. const handleReject = async () => {
  306. cancelClicked.value = true
  307. if (findMenuById(menuTree.value, permissionMapping.Golden_Bean_Recharge_Approval)) {
  308. if (!reason.value.trim()) {
  309. ElMessage.warning('请输入驳回理由')
  310. return
  311. }
  312. try {
  313. const params = {
  314. id: rejectRow.value.id,
  315. auditName: adminData.value.adminName,
  316. reason: reason.value
  317. }
  318. await API({ url: '/beanAudit/status2', data: params })
  319. ElMessage.success('驳回成功')
  320. rejectVisible.value = false
  321. await get()
  322. cancelClicked.value = false
  323. await getStats()
  324. } catch (error) {
  325. console.error('驳回失败', error)
  326. ElMessage.error('操作失败')
  327. }
  328. } else {
  329. ElMessage.error('无此权限')
  330. cancelClicked.value = false
  331. }
  332. }
  333. const getToday = async function () {
  334. const today = dayjs()
  335. const startTime = today.startOf('day').format('YYYY-MM-DD HH:mm:ss')
  336. const endTime = today.endOf('day').format('YYYY-MM-DD HH:mm:ss')
  337. dateRange.value = [startTime, endTime]
  338. console.log('dateRange', dateRange.value)
  339. activeTimeRange.value = 'today'
  340. await get()
  341. await getStats()
  342. }
  343. const getYesterday = async function () {
  344. const today = dayjs()
  345. const startTime = today.subtract(1, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss')
  346. const endTime = today.subtract(1, 'day').endOf('day').format('YYYY-MM-DD HH:mm:ss')
  347. dateRange.value = [startTime, endTime]
  348. console.log('dateRange', dateRange.value)
  349. activeTimeRange.value = 'yesterday'
  350. await get()
  351. await getStats()
  352. }
  353. const get7Days = async function () {
  354. const today = dayjs()
  355. const startTime = today.subtract(6, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss')
  356. const endTime = today.endOf('day').format('YYYY-MM-DD HH:mm:ss')
  357. dateRange.value = [startTime, endTime]
  358. console.log('dateRange', dateRange.value)
  359. activeTimeRange.value = '7days'
  360. await get()
  361. await getStats()
  362. }
  363. const resetSearch = async function () {
  364. const status = searchForm.value.status;
  365. searchForm.value = {
  366. jwcode: '',
  367. market: '',
  368. createStartTime: '',
  369. createEndTime: '',
  370. status: status,
  371. auditStartTime: '',
  372. auditEndTime: ''
  373. }
  374. selectedMarkets.value = []
  375. dateRange.value = []
  376. activeTimeRange.value = ''
  377. await get()
  378. await getStats()
  379. }
  380. const handleClick = async function (tab) {
  381. checkTab.value = tab.props.name
  382. if (tab.props.name === 'pending') {
  383. adminWait()
  384. } else if (tab.props.name === 'pass') {
  385. adminPass()
  386. } else if (tab.props.name === 'reject') {
  387. adminReject()
  388. }
  389. }
  390. // 待审核
  391. const adminWait = async function () {
  392. checkTab.value = 'pending'
  393. searchForm.value.status = STATUS.PENDING
  394. await get()
  395. await getStats()
  396. console.log('切换页面后:', checkTab.value, sortField.value, sortOrder.value)
  397. }
  398. // 已通过
  399. const adminPass = async function () {
  400. checkTab.value = 'pass'
  401. searchForm.value.status = STATUS.APPROVED
  402. await get()
  403. await getStats()
  404. console.log('切换页面后:', checkTab.value, sortField.value, sortOrder.value)
  405. }
  406. // 已驳回
  407. const adminReject = async function () {
  408. checkTab.value = 'reject'
  409. searchForm.value.status = STATUS.REJECTED
  410. await get()
  411. await getStats()
  412. console.log('切换页面后:', checkTab.value, sortField.value, sortOrder.value)
  413. }
  414. const handleMarketChange = (value) => {
  415. if (value && value.length > 0) {
  416. searchForm.value.market = value[value.length - 1]
  417. } else {
  418. searchForm.value.market = ''
  419. }
  420. }
  421. const handleDatePickerChange = () => {
  422. activeTimeRange.value = ''
  423. }
  424. const handlePageSizeChange = function (val) {
  425. pagination.value.pageSize = val
  426. get()
  427. }
  428. const handleCurrentChange = function (val) {
  429. pagination.value.pageNum = val
  430. get()
  431. }
  432. const format3 = (num) => {
  433. // 每三位添加逗号
  434. return num.toLocaleString('en-US')
  435. }
  436. const throttledHandleApprove = _.throttle(handleApprove, 5000, {
  437. trailing: false
  438. })
  439. const showRejectDialog = (row) => {
  440. rejectRow.value.id = row.id
  441. reason.value = ''
  442. rejectVisible.value = true
  443. }
  444. // 获取地区,修改为级联下拉框
  445. const getmarkets = async function () {
  446. try {
  447. const result = await API({
  448. url: '/market/selectMarket',
  449. });
  450. console.log('请求成功', result)
  451. // 递归转换树形结构为级联选择器需要的格式(跳过第一级节点)
  452. const transformTree = (nodes) => {
  453. // 直接处理第一级节点的子节点
  454. const allChildren = nodes.flatMap(node => node.children || []);
  455. return allChildren.map(child => {
  456. const grandchildren = child.children && child.children.length
  457. ? transformTree([child]) // 递归处理子节点
  458. : null;
  459. return {
  460. value: child.name,
  461. label: child.name,
  462. children: grandchildren
  463. }
  464. })
  465. }
  466. marketOptions.value = transformTree(result.data)
  467. console.log('转换后的地区树==============', marketOptions.value)
  468. } catch (error) {
  469. console.log('请求失败', error)
  470. }
  471. }
  472. const trimJwCode = () => {
  473. if (searchForm.value.jwcode) {
  474. searchForm.value.jwcode = searchForm.value.jwcode.replace(/\s/g, '');
  475. }
  476. }
  477. onMounted(async () => {
  478. getmarkets()
  479. await get()
  480. await getStats()
  481. console.log("看看通信来的用户身份", adminData.value)
  482. })
  483. </script>
  484. <style scoped></style>