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.

634 lines
20 KiB

4 months ago
4 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
5 months ago
6 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
5 months ago
5 months ago
5 months ago
  1. <script setup>
  2. // 这是客户金币余额页面
  3. import { useAdminStore } from "@/store/index.js"
  4. import { storeToRefs } from "pinia"
  5. import { findMenuById, permissionMapping } from "@/utils/menuTreePermission.js"
  6. const adminStore = useAdminStore()
  7. const { adminData, menuTree, flag } = storeToRefs(adminStore)
  8. // 监听全局flag状态变化(员工数据的)
  9. watch(flag, (newFlag, oldFlag) => {
  10. // 当flag状态改变时,重新发送请求
  11. if (newFlag !== oldFlag) {
  12. console.log('员工数据flag状态改变,重新加载数据', newFlag)
  13. get()
  14. }
  15. })
  16. import { onMounted, ref,watch } from 'vue'
  17. import { ElMessage } from 'element-plus'
  18. import moment from 'moment'
  19. import API from '@/util/http.js'
  20. import { reverseMarketMapping } from "@/utils/marketMap.js";
  21. // 国际化
  22. import { useI18n } from 'vue-i18n'
  23. const { t } = useI18n()
  24. //新增员工数据复选框
  25. // const showEmployeeData = ref(false)
  26. // 变量
  27. const dialogVisible = ref(false)
  28. // 定义加载状态,获取地区数据
  29. const isLoadingmarket = ref(false);
  30. const markets = ref([])
  31. // 充值明细表格
  32. const tableData = ref([])
  33. // 新增金币总数变量
  34. const goldtotal = ref(0)
  35. // 计算用户各金币总数的不分页对象
  36. const tableAllData = ref([])
  37. // 各金币字段
  38. const permanentGold = ref(0) // 修改为 currentPermanentGold 对应字段
  39. const freeJuneGold = ref(0) // 修改为 currentFreeJune 对应字段
  40. const freeDecemberGold = ref(0) // 修改为 currentFreeDecember 对应字段
  41. const taskGold = ref(0) // 修改为 currentTaskGold 对应字段
  42. const freeGold = ref(0) // 计算免费金币总数
  43. //客户消费记录
  44. const tableCountData = ref([])
  45. const userInfo = ref({})
  46. // 搜索===========================================
  47. //分页总条目
  48. const total = ref(100)
  49. // 搜索对象时间
  50. const getTime = ref([])
  51. // 搜索User
  52. const user = ref({
  53. markets: [],
  54. })
  55. // 不分页的搜索对象
  56. const getAllObj = ref({})
  57. // 搜索对象
  58. const getObj = ref({
  59. pageNum: 1,
  60. pageSize: 50
  61. })
  62. // 新增排序字段和排序方式
  63. const sortField = ref('')
  64. const sortOrder = ref('')
  65. // 方法
  66. // 搜索===========================================================================
  67. // 搜索方法
  68. const get = async function (val) {
  69. if (!findMenuById(menuTree.value, permissionMapping.gold_coin_customer_balance)) {
  70. ElMessage.error(t('elmessage.noPermission'))
  71. return
  72. }
  73. try {
  74. // 搜索参数页码赋值
  75. if (typeof val === 'number') {
  76. getObj.value.pageNum = val
  77. }
  78. // 添加排序字段和排序方式到请求参数
  79. user.value.sortField = sortField.value
  80. user.value.sortOrder = sortOrder.value
  81. console.log('搜索参数', getObj.value)
  82. // 发送POST请求
  83. const requestData = { ...getObj.value, user: { ...user.value } };//控制台打印请求的参数
  84. console.log('最终请求参数', JSON.stringify(requestData, null, 2)); // 打印格式化后的请求参数
  85. //console.log('请求参数', requestData);
  86. // 检查markets数组中是否包含'总部'或'研发部'
  87. // if (user.value.markets.includes('9') || user.value.markets.includes('9999')) {
  88. // user.value.markets = [];
  89. // }
  90. if (user.value.jwcode) {
  91. // 纯数字
  92. const numberRegex = /^\d{1,9}$/;
  93. // 检查是否不是数字
  94. if (!numberRegex.test(user.value.jwcode)) {
  95. ElMessage.error(t('elmessage.checkJwcodeFormat'))
  96. // 上面提示过了
  97. return
  98. }
  99. }
  100. const result = await API({
  101. url: '/goldDetail/getGold',
  102. method: 'post',
  103. data: { ...getObj.value, user: { ...user.value, flag: flag.value } }
  104. })
  105. console.log('响应数据', result)
  106. tableData.value = result.data.list
  107. total.value = result.data.total
  108. console.log('兄弟你是什么 user', user.value)
  109. // 获取合计数
  110. const resultGoldTotal = await API({
  111. url: '/goldDetail/goldTotal',
  112. data: {
  113. jwcode: user.value.jwcode,
  114. markets: user.value.markets,
  115. flag: flag.value
  116. }
  117. })
  118. // 判断精网号是否存在,假设精网号不存在时 result.data.list 为空数组
  119. if (result.data.list.length === 0) {
  120. // 将表格数据设置为空数组
  121. tableData.value = []
  122. // 将合计数设置为 0
  123. permanentGold.value = 0
  124. freeJuneGold.value = 0
  125. freeDecemberGold.value = 0
  126. taskGold.value = 0
  127. goldtotal.value = 0
  128. freeGold.value = 0
  129. // // 新增金币总数变量
  130. // const goldtotal = ref(0)
  131. // 分页总数设置为 0
  132. total.value = 0
  133. // ElMessage.warning('精网号不存在,请检查输入')
  134. }
  135. // 判断合计数是否存在,不存在时 result.data.list 为空数组
  136. else if (resultGoldTotal.data === 0) {
  137. // 将表格数据设置为空数组
  138. tableData.value = []
  139. // 将合计数设置为 0
  140. permanentGold.value = 0
  141. freeJuneGold.value = 0
  142. freeDecemberGold.value = 0
  143. taskGold.value = 0
  144. goldtotal.value = 0
  145. freeGold.value = 0
  146. }
  147. else {
  148. // 将响应结果存储到响应式数据中
  149. console.log('总数据请求成功', result)
  150. // 存储表格数据
  151. tableData.value = result.data.list
  152. console.log('tableData', tableData.value)
  153. // 从接口返回数据中获取各金币数值
  154. if (resultGoldTotal.data) {
  155. permanentGold.value = parseFloat(resultGoldTotal.data.permanentGold.toFixed(2))
  156. freeGold.value = parseFloat(resultGoldTotal.data.freeGold.toFixed(2))
  157. taskGold.value = parseFloat(resultGoldTotal.data.taskGold.toFixed(2))
  158. goldtotal.value = parseFloat(resultGoldTotal.data.goldtotal.toFixed(2))
  159. } else {
  160. console.error('合计数数据格式错误', resultGoldTotal)
  161. ElMessage.error(t('elmessage.getTotalFailed'))
  162. }
  163. // 存储分页总数
  164. total.value = result.data.total
  165. console.log('total', total.value)
  166. }
  167. } catch (error) {
  168. console.log('请求失败', error)
  169. // 在这里可以处理错误逻辑,比如显示错误提示等
  170. }
  171. }
  172. // 精网号去空格,同时处理 user 和 putExcel 中的 jwcode
  173. const trimJwCode = () => {
  174. if (user.value.jwcode) {
  175. user.value.jwcode = user.value.jwcode.replace(/\s/g, '');
  176. }
  177. }
  178. // 搜索
  179. const search = function () {
  180. trimJwCode();
  181. getObj.value.pageNum = 1
  182. get()
  183. }
  184. // 重置
  185. const reset = function () {
  186. user.value = {
  187. jwcode: '',
  188. markets: [],
  189. }
  190. sortField.value = ''
  191. sortOrder.value = ''
  192. selectedMarketPath.value = []
  193. // 重置页码
  194. getObj.value.pageNum = 1
  195. get()
  196. }
  197. const cellClick = function (row, column) {
  198. console.log('cellClick', column.label)
  199. if (column.label === t('common.name')) {
  200. dialogVisible.value = true
  201. userInfo.value = row
  202. }
  203. }
  204. // 处理排序事件
  205. const handleSortChange = (column) => {
  206. console.log('排序字段:', column.prop)
  207. console.log('排序方式:', column.order)
  208. if (column.prop === 'currentPermanentGold') {
  209. sortField.value = 'current_permanent_gold'
  210. } else if (column.prop === 'currentTaskGold') {
  211. sortField.value = 'current_task_gold'
  212. } else if (column.prop === 'currentFreeJune') {
  213. sortField.value = 'current_free_june'
  214. } else if (column.prop === 'currentFreeDecember') {
  215. sortField.value = 'current_free_december'
  216. }
  217. sortOrder.value = column.order === 'ascending' ? 'ASC' : 'DESC'
  218. get()
  219. }
  220. // 挂载
  221. onMounted(async function () {
  222. await get()
  223. await getMarket()
  224. await getExportList()
  225. })
  226. const handlePageSizeChange = function (val) {
  227. getObj.value.pageSize = val
  228. get()
  229. }
  230. const handleCurrentChange = function (val) {
  231. getObj.value.pageNum = val
  232. get()
  233. }
  234. const exportExcel = async function () {
  235. const params = {
  236. user: {
  237. jwcode: user.value.jwcode || '',
  238. markets: user.value.markets || [],
  239. flag: flag.value
  240. }
  241. }
  242. const res = await API({ url: '/goldDetail/exportGold', data: params })
  243. if (res.code === 200) {
  244. ElMessage.success(t('elmessage.exportSuccess'))
  245. }
  246. }
  247. const exportListVisible = ref(false)
  248. // 打开导出列表弹窗
  249. const openExportList = () => {
  250. getExportList()
  251. exportListVisible.value = true
  252. }
  253. // 导出列表数据
  254. const exportList = ref([])
  255. // 导出列表加载状态
  256. const exportListLoading = ref(false)
  257. // 获取导出列表
  258. const getExportList = async () => {
  259. exportListLoading.value = true
  260. try {
  261. const result = await API({ url: '/export/export' })
  262. if (result.code === 200) {
  263. const filteredData = result.data.filter(item => {
  264. return item.type === 1; //返回type為0即客户金币余额的数据
  265. });
  266. exportList.value = filteredData
  267. } else {
  268. ElMessage.error(result.msg || t('elmessage.getExportListError'))
  269. }
  270. } catch (error) {
  271. console.error('获取导出列表出错:', error)
  272. ElMessage.error(t('elmessage.getExportListError'))
  273. } finally {
  274. exportListLoading.value = false
  275. }
  276. }
  277. // 下载导出文件
  278. const downloadExportFile = (item) => {
  279. if (item.state === 2) {
  280. const link = document.createElement('a')
  281. link.href = item.url
  282. link.download = item.fileName
  283. link.click()
  284. } else {
  285. ElMessage.warning(t('elmessage.exportingInProgress'))
  286. }
  287. }
  288. //根据状态返回对应的标签类型
  289. const getTagType = (state) => {
  290. switch (state) {
  291. case 0:
  292. return 'info';
  293. case 1:
  294. return 'primary';
  295. case 2:
  296. return 'success';
  297. case 3:
  298. return 'danger';
  299. default:
  300. return 'info';
  301. }
  302. }
  303. //根据状态返回对应的标签文案
  304. const getTagText = (state) => {
  305. switch (state) {
  306. case 0:
  307. return t('elmessage.pendingExecution');
  308. case 1:
  309. return t('elmessage.executing');
  310. case 2:
  311. return t('elmessage.executed');
  312. case 3:
  313. return t('elmessage.errorExecution');
  314. default:
  315. return t('elmessage.unknownStatus');
  316. }
  317. }
  318. // 存储地区选择变化
  319. const selectedMarketPath = ref([])
  320. const handleMarketChange = (value) => {
  321. if (value && value.length > 0) {
  322. const lastValue = value[value.length - 1];
  323. // 确保返回值是数组,如果不是则包装成数组
  324. const marketValue = reverseMarketMapping[lastValue];
  325. user.value.markets = Array.isArray(marketValue) ? marketValue : [marketValue];
  326. } else {
  327. // 保持[]格式
  328. user.value.markets = [];
  329. }
  330. };
  331. // 获取地区,修改为级联下拉框
  332. const getMarket = async function () {
  333. try {
  334. // 发送POST请求
  335. const result = await API({
  336. url: '/market/selectMarket',
  337. });
  338. // 将响应结果存储到响应式数据中
  339. console.log('请求成功', result)
  340. // 递归转换树形结构为级联选择器需要的格式(跳过第一级节点)
  341. const transformTree = (nodes) => {
  342. // 直接处理第一级节点的子节点
  343. const allChildren = nodes.flatMap(node => node.children || []);
  344. return allChildren.map(child => {
  345. const grandchildren = child.children && child.children.length
  346. ? transformTree([child]) // 递归处理子节点
  347. : null;
  348. return {
  349. value: child.name,
  350. label: child.name,
  351. children: grandchildren
  352. };
  353. });
  354. };
  355. // 存储地区信息
  356. markets.value = transformTree(result.data)
  357. console.log('转换后的地区树==============', markets.value)
  358. } catch (error) {
  359. console.log('请求失败', error)
  360. }
  361. }
  362. const format3 = (num) => {
  363. // 每三位添加逗号
  364. return num.toLocaleString('en-US')
  365. }
  366. </script>
  367. <template>
  368. <el-card class="card1" style="margin-bottom: 1vh;">
  369. <div class="head-card">
  370. <div class="head-card-element">
  371. <el-text class="mx-1" size="large">{{ $t('common.jwcode') }}</el-text>
  372. <el-input v-model="user.jwcode" style="width: 160px" :placeholder="$t('common.jwcodePlaceholder')" clearable />
  373. </div>
  374. <div class="head-card-element">
  375. <el-text class="mx-1" size="large">{{ $t('common.market') }}</el-text>
  376. <el-cascader v-model="selectedMarketPath" :options="markets" :placeholder="$t('common.marketPlaceholder')" clearable style="width:180px"
  377. @change="handleMarketChange" />
  378. </div>
  379. <div class="head-card-element">
  380. <!-- <el-checkbox v-model="showEmployeeData" @change="search()">员工数据</el-checkbox> -->
  381. </div>
  382. <el-button type="primary" @click="search()">{{ $t('common.search') }}</el-button>
  383. <el-button type="primary" @click="exportExcel()">{{ $t('common.exportExcel') }}</el-button>
  384. <el-button type="primary" @click="openExportList">{{ $t('common.viewExportList') }}</el-button>
  385. <el-button @click="reset" type="success">{{ $t('common.reset') }}</el-button>
  386. </div>
  387. <!-- </div> -->
  388. </el-card>
  389. <el-card class="card2">
  390. <div class="goldStatistics">
  391. {{ $t('common.totalGoldCoin') }}{{ format3(goldtotal || 0) }}&nbsp;&nbsp;&nbsp;&nbsp;
  392. {{ $t('common.permanentGold') }}{{ format3(permanentGold || 0) }}&nbsp;&nbsp;&nbsp;&nbsp;
  393. {{ $t('common.freeGold') }}{{ format3(freeGold || 0) }}&nbsp;&nbsp;&nbsp;&nbsp;
  394. {{ $t('common.taskGold') }}{{ format3(taskGold || 0) }}
  395. </div>
  396. <!-- 设置表格容器的高度和滚动样式 -->
  397. <div style="height: 69vh; overflow-y: auto">
  398. <el-table :data="tableData" @cellClick="cellClick" style="width: 82vw; height:69vh"
  399. @sort-change="handleSortChange" :row-style="{ height: '50px' }">
  400. <el-table-column type="index" :label="$t('common_list.id')" width="100px" fixed="left">
  401. <template #default="scope">
  402. <span>{{
  403. scope.$index + 1 + (getObj.pageNum - 1) * getObj.pageSize
  404. }}</span>
  405. </template>
  406. </el-table-column>
  407. <el-table-column prop="name" :label="$t('common_list.name')" width="140" />
  408. <el-table-column prop="jwcode" :label="$t('common_list.jwcode')" width="160" />
  409. <el-table-column prop="market" :label="$t('common_list.market')" width="140" />
  410. <el-table-column prop="sumGold" :label="$t('common_list.sumGold')" width="140" aligh="center">
  411. <!-- <template #default="scope">
  412. <span>{{
  413. ((scope.row.currentPermanentGold || 0) +
  414. (scope.row.currentFreeJune || 0) +
  415. (scope.row.currentFreeDecember || 0) +
  416. (scope.row.currentTaskGold || 0))
  417. }}</span>
  418. </template> -->
  419. </el-table-column>
  420. <el-table-column prop="currentPermanentGold" :label="$t('common_list.permanentGold')" sortable="custom" width="150">
  421. <template #default="scope">
  422. <span>{{ (scope.row.currentPermanentGold || 0) }}</span>
  423. </template>
  424. </el-table-column>
  425. <el-table-column prop="currentFreeJune" :label="$t('common_list.freeGold6Month')" sortable="custom" width="170">
  426. <template #default="scope">
  427. <span>{{ (scope.row.currentFreeJune || 0) }}</span>
  428. </template>
  429. </el-table-column>
  430. <el-table-column prop="currentFreeDecember" :label="$t('common_list.freeGold12Month')" sortable="custom" width="180">
  431. <template #default="scope">
  432. <span>{{ (scope.row.currentFreeDecember || 0) }}</span>
  433. </template>
  434. </el-table-column>
  435. <el-table-column prop="currentTaskGold" :label="$t('common_list.taskGold')" sortable="custom" width="180">
  436. <template #default="scope">
  437. <span>{{ (scope.row.currentTaskGold || 0) }}</span>
  438. </template>
  439. </el-table-column>
  440. <!-- <el-table-column prop="rcoin" label="历史金币总额" width="150">
  441. <template #default="scope">
  442. <el-popover trigger="hover" placement="left" width="150">
  443. <template #default>
  444. <div>
  445. <div>永久金币{{ (scope.row.sumPermanentGold || 0) }}</div>
  446. <div>免费金币{{ ((scope.row.sumFreeJune || 0) + (scope.row.sumFreeDecember || 0)) }}</div>
  447. <div>任务金币{{ (scope.row.sumTaskGold || 0) }}</div>
  448. </div>
  449. </template>
  450. <template #reference>
  451. <span>
  452. {{
  453. (scope.row.sumPermanentGold || 0) +
  454. (scope.row.sumFreeJune || 0) +
  455. (scope.row.sumFreeDecember || 0) +
  456. (scope.row.sumTaskGold || 0)
  457. }}</span>
  458. </template>
  459. </el-popover>
  460. </template>
  461. </el-table-column>
  462. <el-table-column prop="sumConsume" label="历史消费" width="150">
  463. <template #default="scope">
  464. <el-popover trigger="hover" placement="left" width="150">
  465. <template #default>
  466. <div>
  467. <div>永久金币{{ (scope.row.sumConsumeGold || 0) }}</div>
  468. <div>免费金币{{
  469. ((scope.row.sumConsumeJune || 0) + (scope.row.sumConsumeDecember || 0))
  470. }}
  471. </div>
  472. <div>任务金币{{ (scope.row.sumConsumeJune || 0) }}</div>
  473. </div>
  474. </template>
  475. <template #reference>
  476. <span>
  477. {{
  478. (scope.row.sumConsumeGold || 0) +
  479. (scope.row.sumConsumeTaskGold || 0) +
  480. (scope.row.sumConsumeJune || 0) +
  481. (scope.row.sumConsumeDecember || 0)
  482. }}</span>
  483. </template>
  484. </el-popover>
  485. </template>
  486. </el-table-column>-->
  487. </el-table>
  488. </div>
  489. <!-- 分页 -->
  490. <div class="pagination" style="margin-top: 20px">
  491. <el-pagination background :current-page="getObj.pageNum" :page-size="getObj.pageSize" :page-sizes="[5, 10, 20, 50, 100]"
  492. layout="total, sizes, prev, pager, next, jumper" :total="total" @size-change="handlePageSizeChange"
  493. @current-change="handleCurrentChange"></el-pagination>
  494. </div>
  495. </el-card>
  496. <el-dialog v-model="exportListVisible" :title="$t('common_export.exportList')" width="80%">
  497. <el-table :data="exportList" style="width: 100% ;height: 60vh;" :loading="exportListLoading">
  498. <el-table-column prop="fileName" :label="$t('common_export.fileName')" />
  499. <el-table-column prop="state" :label="$t('common_export.status')">
  500. <template #default="scope">
  501. <el-tag :type="getTagType(scope.row.state)" :effect="scope.row.state === 3 ? 'light' : 'plain'">
  502. {{ getTagText(scope.row.state) }}
  503. </el-tag>
  504. </template>
  505. </el-table-column>
  506. <el-table-column prop="createTime" :label="$t('common_export.createTime')">
  507. <template #default="scope">
  508. {{ moment(scope.row.createTime).format('YYYY-MM-DD HH:mm:ss') }}
  509. </template>
  510. </el-table-column>
  511. <el-table-column :label="$t('common_export.operation')">
  512. <template #default="scope">
  513. <el-button type="primary" size="small" @click="downloadExportFile(scope.row)"
  514. :disabled="scope.row.state !== 2">
  515. {{ $t('common_export.download') }}
  516. </el-button>
  517. </template>
  518. </el-table-column>
  519. </el-table>
  520. <template #footer>
  521. <div class="dialog-footer">
  522. <el-button text @click="exportListVisible = false">{{ $t('common_export.close') }}</el-button>
  523. </div>
  524. </template>
  525. </el-dialog>
  526. </template>
  527. <style scoped lang="scss">
  528. // 搜索的卡片样式
  529. .card1 {
  530. background: #F3FAFE;
  531. }
  532. // 表单的卡片样式
  533. .card2 {
  534. background: #E7F4FD;
  535. }
  536. // 新币总数等等
  537. .goldStatistics {
  538. margin-left: 1vw;
  539. margin-bottom: 1vh;
  540. color: #000000;
  541. font-family: "PingFang SC";
  542. font-size: 16px;
  543. font-style: normal;
  544. font-weight: 700;
  545. line-height: 20px;
  546. }
  547. // 表头背景等
  548. :deep(.el-table__header-wrapper),
  549. :deep(.el-table__body-wrapper),
  550. :deep(.el-table__cell),
  551. /* 表格 */
  552. :deep(.el-table__body td) {
  553. background-color: #F3FAFE !important;
  554. }
  555. /* 表头 */
  556. :deep(.el-table__header th) {
  557. background-color: #F3FAFE !important;
  558. }
  559. /* 鼠标悬停 */
  560. :deep(.el-table__row:hover > .el-table__cell) {
  561. background-color: #E5EBFE !important;
  562. }
  563. .pagination {
  564. display: flex;
  565. }
  566. .status {
  567. display: flex;
  568. }
  569. .head-card {
  570. display: flex;
  571. }
  572. .head-card-element {
  573. margin-right: 20px;
  574. }
  575. .head-card-btn {
  576. margin-left: auto;
  577. }
  578. .custom-box {
  579. display: flex;
  580. flex-wrap: wrap;
  581. row-gap: 5px;
  582. div:nth-child(1) {
  583. flex: 1 0 100%;
  584. }
  585. div {
  586. margin-right: 20px;
  587. }
  588. }
  589. </style>