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.

586 lines
19 KiB

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