Browse Source

Merge branch 'refs/heads/lihui/feature-20251104165712-现金二期' into milestone-20251104-现金二期

lihui/feature-20251104165712-现金二期
lihui 2 weeks ago
parent
commit
9990d237f3
  1. 41
      src/__tests__/environment.test.ts
  2. 5
      src/main.ts
  3. 141
      src/router/index.js
  4. 33
      src/store/index.js
  5. 71
      src/utils/getMessage.js
  6. 36
      src/utils/goToCheck.js
  7. 127
      src/views/bankPaymentPage.vue
  8. 279
      src/views/home.vue
  9. 132
      src/views/permissions/rolePermission.vue
  10. 911
      src/views/workspace/index_.vue

41
src/__tests__/environment.test.ts

@ -0,0 +1,41 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
describe('Environment Variables', () => {
const originalEnv = process.env;
beforeEach(() => {
// 保存原始环境变量
vi.resetModules();
});
afterEach(() => {
// 恢复原始环境变量
process.env = { ...originalEnv };
});
it('should load development environment variables', async () => {
// 在实际项目中,您可能需要根据具体框架加载环境变量
const env = import.meta.env;
// 验证环境变量是否存在
expect(env).toBeDefined();
// 注意:由于 .env.development 是空的,这里不会有很多变量可以测试
// 但在实际项目中,您可以添加类似这样的测试:
// expect(env.VITE_API_BASE).toBe('expected_development_api_url');
});
it('should have required environment variables', () => {
// 检查生产环境中必需的变量是否已定义
const requiredVars = [
// 'VITE_API_BASE',
// 'VITE_UPLOAD_URL'
];
requiredVars.forEach((varName) => {
// 由于 .env.development 是空的,这些测试会失败
// 请根据实际情况取消注释并修改
// expect(import.meta.env[varName]).toBeDefined();
});
});
});

5
src/main.ts

@ -17,6 +17,7 @@ import {useAdminStore} from './store'
import request from "@/util/request";
import "./global.css";
import '@/assets/css/btn.css';
import {useMessageStore} from "@/store";
const app = createApp(App)
@ -43,4 +44,6 @@ app.use(ElementPlus, {
// 在 app 挂载之后再使用 store
const adminStore = useAdminStore()
adminStore.initFromLocalStorage()
const messageStore = useMessageStore()
adminStore.initFromLocalStorage()
messageStore.initFromLocalStorage()

141
src/router/index.js

@ -1,7 +1,7 @@
import { createRouter, createWebHashHistory } from 'vue-router';
import axios from "axios";
import { storeToRefs } from "pinia";
import { useAdminStore } from "@/store/index.js";
import {createRouter, createWebHashHistory} from 'vue-router';
import {storeToRefs} from "pinia";
import {useAdminStore, useMessageStore} from "@/store/index.js";
import API from '@/util/http.js';
// 路由定义(包含权限映射 meta.permissionId)
@ -16,7 +16,7 @@ const routes = [
component: () => import("../views/login.vue"),
},
{
meta: { requireAuth: true },
meta: {requireAuth: true},
path: '/',
component: () => import("../views/home.vue"),
children: [
@ -25,48 +25,48 @@ const routes = [
path: 'workbench',
name: "workbench",
component: () => import("../views/workspace/index.vue"),
meta: { permissionId: 2 }
meta: {permissionId: 2}
},
//金币管理
{
path: '/goldManage',
name: 'goldManage',
meta: { permissionId: 3 },
meta: {permissionId: 3},
children: [
// 审核
{
path: '/audit',
name: "audit",
component: () => import("../views/audit/gold/audit.vue"),
meta: { permissionId: 4 },
meta: {permissionId: 4},
children: [
// 充值审核
{
path: 'rechargeAudit',
name: "rechargeAudit",
component: () => import("../views/audit/gold/rechargeAudit.vue"),
meta: { permissionId: [6, 7, 8, 9, 10, 11, 12] }
meta: {permissionId: [6, 7, 8, 9, 10, 11, 12]}
},
// 退款审核
{
path: 'refundAudit',
name: "refundAudit",
component: () => import("../views/audit/gold/refundAudit.vue"),
meta: { permissionId: [13, 14, 15, 16, 17, 18, 19] }
meta: {permissionId: [13, 14, 15, 16, 17, 18, 19]}
},
]
}, {
path: '/beanAudit',
name: "beanAudit",
component: () => import("../views/audit/bean/beanAudit.vue"),
meta: { permissionId: 20 },
meta: {permissionId: 20},
children: [
// 充值审核
{
path: 'addbeanAudit',
name: "addbeanAudit",
component: () => import("../views/audit/bean/beanAudit.vue"),
meta: { permissionId: [21, 22, 23, 24, 25, 26] }
meta: {permissionId: [21, 22, 23, 24, 25, 26]}
},
]
},
@ -77,21 +77,21 @@ const routes = [
path: '/coinConsume',
name: "coinConsume",
component: () => import("../views/consume/gold/coinConsume.vue"),
meta: { permissionId: 39 },
meta: {permissionId: 39},
children: [
// 金币新增消耗
{
path: 'add',
name: "addCoinConsume",
component: () => import("../views/consume/gold/addCoinConsume.vue"),
meta: { permissionId: 41 }
meta: {permissionId: 41}
},
// 金币消耗明细详情
{
path: 'detail',
name: "coinConsumeDetail",
component: () => import("../views/consume/gold/coinConsumeDetail.vue"),
meta: { permissionId: 40 }
meta: {permissionId: 40}
}
]
},
@ -100,35 +100,35 @@ const routes = [
path: '/beanConsume',
name: "beanConsume",
component: () => import("../views/consume/bean/beanConsume.vue"),
meta: { permissionId: 42 },
meta: {permissionId: 42},
children: [
// 金豆新增消耗
{
path: 'add',
name: "addBeanConsume",
component: () => import("../views/consume/bean/addBeanConsume.vue"),
meta: { permissionId: 46 }
meta: {permissionId: 46}
},
// 直播
{
path: 'live',
name: "liveStream",
component: () => import("../views/consume/bean/liveStream.vue"),
meta: { permissionId: 43 }
meta: {permissionId: 43}
},
// 铁粉
{
path: 'fan',
name: "dieHardFan",
component: () => import("../views/consume/bean/dieHardFan.vue"),
meta: { permissionId: 44 }
meta: {permissionId: 44}
},
// 文章视频
{
path: 'article',
name: "articleVideo",
component: () => import("../views/consume/bean/articleVideo.vue"),
meta: { permissionId: 45 }
meta: {permissionId: 45}
}
]
@ -139,7 +139,7 @@ const routes = [
path: '/rate',
name: "rate",
component: () => import("../views/managerecharge/rate.vue"),
meta: { permissionId: [27, 28, 29] }
meta: {permissionId: [27, 28, 29]}
},
// 金币充值
@ -147,21 +147,21 @@ const routes = [
path: '/coinRecharge',
name: "coinRecharge",
component: () => import("../views/recharge/gold/coinRecharge.vue"),
meta: { permissionId: 31 },
meta: {permissionId: 31},
children: [
// 金币新增充值
{
path: 'add',
name: "addCoinRecharge",
component: () => import("../views/recharge/gold/addCoinRecharge.vue"),
meta: { permissionId: 33 }
meta: {permissionId: 33}
},
// 金币充值明细
{
path: 'detail',
name: "coinRechargeDetail",
component: () => import("../views/recharge/gold/coinRechargeDetail.vue"),
meta: { permissionId: 32 }
meta: {permissionId: 32}
}
]
},
@ -171,28 +171,28 @@ const routes = [
path: '/beanRecharge',
name: "beanRecharge",
component: () => import("../views/recharge/bean/beanRecharge.vue"),
meta: { permissionId: 34 },
meta: {permissionId: 34},
children: [
// 金豆新增充值
{
path: 'add',
name: "addBeanRecharge",
component: () => import("../views/recharge/bean/addBeanRecharge.vue"),
meta: { permissionId: 37 }
meta: {permissionId: 37}
},
// 金豆系统充值
{
path: 'system',
name: "beanSystemRecharge",
component: () => import("../views/recharge/bean/beanSystemRecharge.vue"),
meta: { permissionId: 35 }
meta: {permissionId: 35}
},
// 金豆线上充值
{
path: 'online',
name: "beanOnlineRecharge",
component: () => import("../views/recharge/bean/beanOnlineRecharge.vue"),
meta: { permissionId: 36 }
meta: {permissionId: 36}
}
]
},
@ -202,21 +202,21 @@ const routes = [
path: '/coinRefund',
name: "coinRefund",
component: () => import("../views/refund/gold/coinRefund.vue"),
meta: { permissionId: 47 },
meta: {permissionId: 47},
children: [
// 金币新增退款
{
path: 'add',
name: "addCoinRefund",
component: () => import("../views/refund/gold/addCoinRefund.vue"),
meta: { permissionId: 48 }
meta: {permissionId: 48}
},
// 金币退款明细详情
{
path: 'detail',
name: "coinRefundDetail",
component: () => import("../views/refund/gold/coinRefundDetail.vue"),
meta: { permissionId: 49 }
meta: {permissionId: 49}
}
]
},
@ -226,21 +226,21 @@ const routes = [
path: '/usergold',
name: "usergold",
component: () => import("../views/usergold/gold/clientCount.vue"),
meta: { permissionId: 51 },
meta: {permissionId: 51},
children: [
// 金币明细
{
path: 'detail',
name: "clientCountDetail",
component: () => import("../views/usergold/gold/clientCountDetail.vue"),
meta: { permissionId: 52 }
meta: {permissionId: 52}
},
// 金币余额
{
path: 'balance',
name: "clientCountBalance",
component: () => import("../views/usergold/gold/clientCountBalance.vue"),
meta: { permissionId: 53 }
meta: {permissionId: 53}
},
]
},
@ -248,25 +248,25 @@ const routes = [
path: '/userbean',
name: "userbean",
component: () => import("../views/usergold/bean/userbean.vue"),
meta: { permissionId: 54 }
meta: {permissionId: 54}
},
{
path: '/history',
name: "history",
component: () => import("../views/history/history.vue"),
meta: { permissionId: 55 },
meta: {permissionId: 55},
children: [
{
path: 'newHistory',
name: "newHistory",
component: () => import("../views/history/newHistory.vue"),
meta: { permissionId: 56 }
meta: {permissionId: 56}
},
{
path: 'oldHistory',
name: "oldHistory",
component: () => import("../views/history/oldHistory.vue"),
meta: { permissionId: 57 }
meta: {permissionId: 57}
}
]
},
@ -276,21 +276,21 @@ const routes = [
path: '/permissions',
name: "permissions",
component: () => import("../views/permissions/permissions.vue"),
meta: { permissionId: 128 },
meta: {permissionId: 128},
children: [
// 用户权限
{
path: 'userPermission',
name: "userPermission",
component: () => import("../views/permissions/userPermission.vue"),
meta: { permissionId: 129 }
meta: {permissionId: 129}
},
// 角色权限
{
path: 'rolePermission',
name: "rolePermission",
component: () => import("../views/permissions/rolePermission.vue"),
meta: { permissionId: 130 }
meta: {permissionId: 130}
}
]
}
@ -300,40 +300,40 @@ const routes = [
{
path: '/moneyManage',
name: 'moneyManage',
meta: { permissionId: 58 },
meta: {permissionId: 58},
children: [
// 收款明细
{
path: 'receiveDetail',
name: "receiveDetail",
meta: { permissionId: 74 },
meta: {permissionId: 74},
children: [
// 客服页面
{
path: 'receiveService',
name: "receiveService",
component: () => import("../views/moneyManage/receiveDetail/receiveService.vue"),
meta: { permissionId: 60 }
meta: {permissionId: 60}
},
// 地区负责人页面
{
path: 'receiveManager',
name: "receiveManager",
component: () => import("../views/moneyManage/receiveDetail/receiveManage.vue"),
meta: { permissionId: [67, 79] }
meta: {permissionId: [67, 79]}
},
{//地区财务
path: 'receiveFinance',
name: "receiveFinance",
component: () => import("../views/moneyManage/receiveDetail/receiveFinance.vue"),
meta: { permissionId: [67, 79] }
meta: {permissionId: [67, 79]}
},
//总部管理员及财务
{
path: 'receiveHeader',
name: "receiveHeader",
component: () => import("../views/moneyManage/receiveDetail/receiveHead.vue"),
meta: { permissionId: 91 }
meta: {permissionId: 91}
},
]
},
@ -342,35 +342,35 @@ const routes = [
path: 'refundDetail',
name: "refundDetail",
component: () => import("../views/moneyManage/refundDetail/refundDetail.vue"),
meta: { permissionId: 98 },
meta: {permissionId: 98},
children: [
// 客服页面
{
path: 'refundService',
name: "refundService",
component: () => import("../views/moneyManage/refundDetail/refundService.vue"),
meta: { permissionId: 99 }
meta: {permissionId: 99}
},
// 地区财务页面
{
path: 'refundFinance',
name: "refundFinance",
component: () => import("../views/moneyManage/refundDetail/refundFinance.vue"),
meta: { permissionId: 103 }
meta: {permissionId: 103}
},
// 地区负责人页面
{
path: 'refundCharge',
name: "refundCharge",
component: () => import("../views/moneyManage/refundDetail/refundCharge.vue"),
meta: { permissionId: 107 }
meta: {permissionId: 107}
},
//总部管理员及财务
{
path: 'refundHeader',
name: "refundHeader",
component: () => import("../views/moneyManage/refundDetail/refundHeader.vue"),
meta: { permissionId: 111 }
meta: {permissionId: 111}
},
]
},
@ -379,7 +379,7 @@ const routes = [
path: 'executor',
name: "executor",
component: () => import("../views/moneyManage/executor/executor.vue"),
meta: { permissionId: 115 }
meta: {permissionId: 115}
},
]
},
@ -387,29 +387,29 @@ const routes = [
{
path: 'channelManage',
name: 'channelManage',
meta: { permissionId: 124 },
meta: {permissionId: 124},
children: [
// 打赏
{
path: 'reward',
name: "reward",
component: () => import("../views/channelManage/reward/reward.vue"),
meta: { permissionId: 125 }
meta: {permissionId: 125}
},
// 铁粉
{
path: 'fans',
name: "fans",
component: () => import("../views/channelManage/fans/fans.vue"),
meta: { permissionId: 126 }
meta: {permissionId: 126}
},
// 购物车
{
path: 'cart',
name: "cart",
component: () => import("../views/noPermissionPage.vue"),
meta: { permissionId: 127 }
}
meta: {permissionId: 127}
}
]
},
// 活动管理
@ -417,7 +417,7 @@ const routes = [
path: 'activityManage',
name: "activityManage",
component: () => import("../views/activityManage/activity.vue"),
meta: { permissionId: 119 }
meta: {permissionId: 119}
},
// 没有权限
{
@ -457,12 +457,11 @@ const getAllPermissionIds = (menuTree) => {
return permissionIds;
};
// 全局路由守卫
router.beforeEach(async (to, from, next) => {
const adminStore = useAdminStore()
const { adminData, menuTree } = storeToRefs(adminStore)
const {adminData, menuTree} = storeToRefs(adminStore)
const token = localStorage.getItem("token");
const machineId = localStorage.getItem("machineId");
@ -520,4 +519,24 @@ router.beforeEach(async (to, from, next) => {
next();
});
// 全局后置守卫:每次路由切换后执行
router.afterEach(async () => {
try {
// 执行/getMessage请求
const newMessageRes = await API({
url: '/getMessage',
method: 'POST',
data: {}
});
console.log('newMessageRes=======================:', newMessageRes.data)
// 存入全局状态,供所有页面访问
const messageStore = useMessageStore();
// 过滤 flag=1的消息
newMessageRes.data = newMessageRes.data.filter(item => item.flag !== 1);
messageStore.setMessages(newMessageRes.data);
} catch (error) {
console.error('获取消息失败:', error);
}
});
export default router;

33
src/store/index.js

@ -1,4 +1,6 @@
// src/store/index.js
// useMessageStore
// 引入Pinia的defineStore
import {defineStore} from 'pinia'
export const useAdminStore = defineStore('admin', {
@ -68,4 +70,35 @@ export const useAdminStore = defineStore('admin', {
// localStorage.removeItem('token')
}
}
})
// 最基础的messageStore
// 模仿adminStore风格的messageStore
export const useMessageStore = defineStore('messages', {
state: () => ({
messages: [], // 消息列表数据
}),
actions: {
// 设置消息列表并同步到localStorage
setMessages(data) {
this.messages = data
localStorage.setItem('messages', JSON.stringify(data))
},
// 从localStorage初始化消息相关数据
initFromLocalStorage() {
const messages = localStorage.getItem('messages')
if (messages) {
this.messages = JSON.parse(messages)
}
},
// 清空消息状态并移除localStorage数据
clearMessages() {
this.messages = []
localStorage.removeItem('messages')
},
}
})

71
src/utils/getMessage.js

@ -0,0 +1,71 @@
// 所有导入放在顶部
import API from '@/util/http.js';
import {ref} from "vue";
// 声明变量
const messageList1 = ref()
/**
* 格式化时间为相对时间
* @param {String} timeStr - 原始时间字符串
* @returns {String} 格式化后的时间
*/
function formatTime(timeStr) {
// 函数逻辑不变...
const now = new Date();
const msgTime = new Date(timeStr);
const diffMs = now - msgTime;
const diffMins = Math.floor(diffMs / (1000 * 60));
const diffHours = Math.floor(diffMins / 60);
const diffDays = Math.floor(diffHours / 24);
if (diffHours < 1) {
return `${diffMins}分钟前`;
} else if (diffDays < 1) {
return `${diffHours}小时前`;
} else if (diffDays === 1) {
return '昨天';
} else {
return `${msgTime.getFullYear()}-${String(msgTime.getMonth() + 1).padStart(2, '0')}-${String(msgTime.getDate()).padStart(2, '0')}`;
}
}
/**
* 按日期分组消息返回扁平化列表每条消息包含group字段
* @param {Array} messages - 原始消息列表
* @returns {Array} 带分组信息的消息列表
*/
export function groupMessages(messages) {
const today = new Date();
today.setHours(0, 0, 0, 0);
const yesterday = new Date(today);
yesterday.setDate(yesterday.getDate() - 1);
// 直接返回处理后的消息数组(每条消息带group字段)
return messages
.filter(msg => msg.flag !== 1)
.map(msg => {
const msgTime = new Date(msg.czTime);
const formattedTime = formatTime(msg.czTime);
let group;
if (msgTime >= today) {
group = '今天';
} else if (msgTime >= yesterday) {
group = '昨天';
} else {
group = '更早';
}
return {...msg, czTime: formattedTime, group};
});
}
// // 异步获取消息
// export const newMessageRes = await API({
// url: '/getMessage',
// method: 'POST',
// data: {}
// });

36
src/utils/goToCheck.js

@ -0,0 +1,36 @@
export function getOrderPage(status) {
// 收款相关状态(0-5)
const receiveStatusMap = {
0: '/moneyManage/receiveDetail/receiveFinance', // 线下财务待审核
1: '/moneyManage/receiveDetail/receiveService', // 线下财务审核通过待填手续费
2: '/moneyManage/receiveDetail/receiveService', // 线下财务审核驳回
3: '/moneyManage/receiveDetail/receiveService', // link线上财务复核待填手续费
// 4: '/moneyManage/receiveDetail', // 收款流程全部结束
5: '/moneyManage/receiveDetail/receiveService' // 手动撤回待编辑提交
};
// 退款相关状态(6及10-41中退款流程状态)
const refundStatusMap = {
6: '/moneyManage/refundDetail', // 退款
10: '/moneyManage/refundDetail/refundFinance', // 地区财务待审核
11: '/moneyManage/refundDetail/refundFinance', // 地区财务手动撤回待编辑提交
12: '/moneyManage/refundDetail/refundFinance', // 地区财务驳回
20: '/moneyManage/refundDetail/refundCharge', // 地区负责人待审核
22: '/moneyManage/refundDetail/refundCharge', // 地区负责人驳回
30: '/moneyManage/refundDetail/refundHeader', // 总部财务待审核
32: '/moneyManage/refundDetail/refundHeader', // 总部财务驳回
40: '/moneyManage/refundDetail/refundService', // 执行人待处理
41: '/moneyManage/refundDetail/refundService' // 执行人已处理,退款结束
};
// 优先匹配退款状态(包含6和10-41区间)
if (refundStatusMap.hasOwnProperty(status)) {
return refundStatusMap[status];
}
// 匹配收款状态(0-5)
if (receiveStatusMap.hasOwnProperty(status)) {
return receiveStatusMap[status];
}
// 未知状态返回工作台
return '/workbench';
}

127
src/views/bankPaymentPage.vue

@ -0,0 +1,127 @@
<template>
<div class="payment-table-container">
<h3 class="table-title">支付记录详情</h3>
<div class="filter-container">
<el-date-picker
v-model="selectedDate"
type="date"
placeholder="选择日期"
value-format="YYYYMMDD"
format="YYYY-MM-DD"
/>
<el-button type="primary" @click="fetchPaymentList">查询</el-button>
</div>
<el-table :data="paymentList" style="width: 100%" v-loading="loading">
<el-table-column prop="type" label="交易类型" align="center" width="120" />
<el-table-column prop="provider" label="支付渠道" align="center" width="120" />
<el-table-column prop="request_reference" label="请求编号" align="center" width="120" show-overflow-tooltip/>
<el-table-column prop="merchant_reference" label="商户订单号" align="center" width="120" show-overflow-tooltip/>
<el-table-column prop="provider_reference" label="渠道参考号" align="center" width="120" show-overflow-tooltip />
<el-table-column prop="currency" label="货币类型" align="center" width="100" />
<el-table-column
prop="order_amount"
label="订单金额"
align="center"
width="120"
/>
<el-table-column
prop="charge"
label="手续费"
align="center"
width="120"
/>
<el-table-column
prop="net_amount"
label="净收入"
align="center"
width="120"
/>
<el-table-column prop="status" label="交易状态" align="center" width="120">
<template #default="scope">
<el-tag :type="scope.row.status === 'SUCCESS' ? 'success' : 'danger'" size="small">
{{ scope.row.status }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="created_time" label="创建时间" align="center" width="180" />
<el-table-column prop="completed_time" label="完成时间" align="center" width="180" />
</el-table>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import API from '@/util/http'
import 'element-plus/dist/index.css'
const paymentList = ref([])
const selectedDate = ref('')
const loading = ref(false)
//
const formatAmount = (row, column) => {
const value = row[column.prop]
return `${row.currency} ${Number(value).toFixed(2)}`
}
//
const fetchPaymentList = async () => {
if (!selectedDate.value) {
ElMessage.warning('请先选择日期')
return
}
loading.value = true
try {
const res = await API({
url: '/admin/cash/bank/payment',
data: { time: selectedDate.value },
})
if (res.code === 200) {
paymentList.value = res.data.paymentDTOList || []
} else {
ElMessage.error(res.msg || '加载失败')
}
} catch (err) {
console.error(err)
ElMessage.error('请求出错,请稍后再试')
} finally {
loading.value = false
}
}
//
onMounted(() => {
const today = new Date()
const y = today.getFullYear()
const m = String(today.getMonth() + 1).padStart(2, '0')
const d = String(today.getDate()).padStart(2, '0')
selectedDate.value = `${y}${m}${d}`
fetchPaymentList()
})
</script>
<style scoped>
.payment-table-container {
padding: 20px;
max-width: 1400px;
margin: 0 auto;
}
.table-title {
margin-bottom: 16px;
color: #1989fa;
font-weight: 500;
}
.filter-container {
margin-bottom: 16px;
display: flex;
align-items: center;
gap: 10px;
}
</style>

279
src/views/home.vue

@ -1,19 +1,23 @@
<script setup>
//
import {computed, onMounted, onUnmounted, ref} from 'vue'
import {computed, onMounted, ref} from 'vue'
import {useRoute, useRouter} from 'vue-router'
import {ElMessage} from 'element-plus'
import ChangePassword from '@/components/dialogs/changePassword.vue'
import {useAdminStore} from '@/store'
import {storeToRefs} from 'pinia'
import {filterMenu, getRoutePath} from "@/utils/menuUtils.js";
//
import API from '@/util/http.js'
import bell from '@/assets/SvgIcons/bell.svg'
import bell from '@/assets/SvgIcons/bell.svg'
import noMessage from '@/assets/images/no-message.svg'
import goTop from '@/assets/SvgIcons/go-top.svg'
import {getOrderPage} from '@/utils/goToCheck.js'
import {groupMessages} from "@/utils/getMessage.js"
// 使import.meta.globSVG
import {useMessageStore} from '@/store/index.js'
// ------------------ ICONS ------------------
const icons = import.meta.glob('@/assets/SvgIcons/*.svg', {eager: true})
const menuNameMap = {
@ -25,124 +29,83 @@ const menuNameMap = {
'权限管理': 'permission-management',
}
//
const getIconPath = (menuName) => {
const englishName = menuNameMap[menuName] || menuName;
// key
const possibleKeys = [
`@/assets/SvgIcons/${englishName}.svg`,
`./SvgIcons/${englishName}.svg`,
`/src/assets/SvgIcons/${englishName}.svg`
]
// icons
for (const key of possibleKeys) {
if (icons[key]) {
const iconModule = icons[key]
// default
return iconModule.default || iconModule
}
}
}
//
// ------------------ ------------------
const refreshData = async () => {
try {
// durationshowclose×
ElMessage({message: '数据刷新中,请稍候...', type: 'info'});
// Mysql
const response = await API({url: '/Mysql', method: 'POST', data: {}});
if (response && response.code === 200) {
if (response && response.code === 200) {
const currentRoute = route.fullPath;
router.replace('/blank'); //
setTimeout(() => {
router.replace(currentRoute); //
}, 10);
router.replace('/blank');
setTimeout(() => router.replace(currentRoute), 10);
ElMessage.success('数据刷新成功');
} else {
ElMessage.error('数据刷新失败:' + (response?.msg || '未知错误'));
}
} catch (error) {
console.error('数据刷新异常:', error);
console.error(error)
ElMessage.error('数据刷新异常,请重试');
}
}
//
const menuList = ref([])
// ------------------ ------------------
const route = useRoute()
const router = useRouter()
//
const adminStore = useAdminStore()
// adminData menuTree
const {adminData, menuTree, flag} = storeToRefs(adminStore)
// ,menuTree
menuList.value = filterMenu(menuTree.value)
console.log("menuList", menuList.value)
//
const route = useRoute()
const menuList = ref(filterMenu(menuTree.value))
// index
function findBestMatch(menuList, path) {
let bestMatch = ''
function traverse(menus) {
for (const item of menus) {
const itemPath = getRoutePath(item)
// path
if (path.startsWith(itemPath) && itemPath.length > bestMatch.length) {
bestMatch = itemPath
}
if (item.children && item.children.length > 0) {
traverse(item.children)
}
if (item.children?.length) traverse(item.children)
}
}
traverse(menuList)
return bestMatch || path // fallback
return bestMatch || path
}
//
const activeMenu = computed(() => {
return findBestMatch(menuList.value, route.path)
})
const router = useRouter()
const messageVisible = ref(false)
const activeMenu = computed(() => findBestMatch(menuList.value, route.path))
//
const openMessage = function () {
messageVisible.value = true
}
//
const closeMessage = function () {
messageVisible.value = false
}
const message = function () {
openMessage()
}
// ------------------ / ------------------
const messageVisible = ref(false)
const openMessage = () => (messageVisible.value = true)
const closeMessage = () => (messageVisible.value = false)
//
const showPasswordDialog = ref(false)
const pwdRef = ref()
//
const openChangePassword = () => {
showPasswordDialog.value = true
}
//
function onPwdDialogClosed() {
// resetFields
pwdRef.value?.resetFields()
}
const openChangePassword = () => (showPasswordDialog.value = true)
const onPwdDialogClosed = () => pwdRef.value?.resetFields()
// ------------------ 退 ------------------
function logout() {
const machineId = localStorage.getItem('machineId')
localStorage.removeItem('token')
@ -151,124 +114,117 @@ function logout() {
ElMessage.success('退出成功')
}
//
// ------------------ ------------------
const toggleFlag = () => {
const newFlag = flag.value === 1 ? 0 : 1
adminStore.setFlag(newFlag)
ElMessage.success(newFlag === 1 ? '员工数据已隐藏' : '员工数据已显示')
console.log('flag', newFlag)
}
// ------------------ ------------------
const messageStore = useMessageStore()
const {messages} = storeToRefs(messageStore)
//
// MessageDialog
//
const getMessage = async () => {
try {
const res = await API({
url: '/getMessage',
method: 'POST',
data: {}
});
if (res?.data) {
const cleanList = res.data.filter(i => i.flag !== 1)
messageStore.setMessages(cleanList)
}
} catch (e) {
console.error("getMessage error:", e)
}
}
const messageNum = ref(0)
//
const showMessageDialog = ref(false)
//
const messageDot = ref(true)
//
const openMessageDialog = () => {
const openMessageDialog = async () => {
showMessageDialog.value = true
messageDot.value = false
}
//
const closeMessageDialog = () => {
showMessageDialog.value = false
}
//
const messageList = ref([
{id: 1, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '2分钟前', group: '今天'},
{id: 2, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '1小时前', group: '今天'},
{id: 2, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '1小时前', group: '今天'},
{id: 2, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '1小时前', group: '今天'},
{id: 3, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '昨天 09:30', group: '昨天'},
{id: 4, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '昨天 08:30', group: '昨天'},
{id: 5, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '2025-11-05 10:05:20', group: '更早'},
{id: 6, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '2025-11-05 10:05:20', group: '更早'},
{id: 7, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '2025-11-05 10:05:20', group: '更早'},
{id: 8, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '2025-11-05 10:05:20', group: '更早'},
{id: 9, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '2025-11-05 10:05:20', group: '更早'},
{id: 10, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '2025-11-05 10:05:20', group: '更早'},
{id: 11, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '2025-11-05 10:05:20', group: '更早'},
{id: 12, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '2025-11-05 10:05:20', group: '更早'},
{id: 13, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '2025-11-05 10:05:20', group: '更早'},
{id: 14, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '2025-11-05 10:05:20', group: '更早'},
{id: 15, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '2025-11-05 10:05:20', group: '更早'},
{id: 16, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '2025-11-05 10:05:20', group: '更早'},
]);
//
const groupedMessages = ref({});
messageList.value.forEach(item => {
if (!groupedMessages.value[item.group]) {
groupedMessages.value[item.group] = [];
}
groupedMessages.value[item.group].push(item);
});
await getMessage() //
}
//
const showAll = ref(false);
//
const closeMessageDialog = () => showMessageDialog.value = false
//
const messageDot = computed(() => messages.value.length > 0)
// showAll
const displayMessages = computed(() => {
if (showAll.value) {
return groupedMessages.value;
}
//
const messageNum = computed(() => messages.value.length)
// computed
const messageList = computed(() => groupMessages(messages.value))
//
const limitedMessages = {};
let count = 0;
// computed
const groupedMessages = computed(() => {
const result = {}
messageList.value.forEach(item => {
if (!result[item.group]) result[item.group] = []
result[item.group].push(item)
})
return result
})
// or
const showAll = ref(false)
// -> ->
const groupOrder = ['今天', '昨天', '更早'];
const displayMessages = computed(() => {
if (showAll.value) return groupedMessages.value
for (const groupName of groupOrder) {
const group = groupedMessages.value[groupName];
if (!group) continue;
let count = 0
const limited = {}
const groupOrder = ['今天', '昨天', '更早']
limitedMessages[groupName] = [];
for (const g of groupOrder) {
const group = groupedMessages.value[g]
if (!group) continue
limited[g] = []
for (const item of group) {
if (count < 2) {
limitedMessages[groupName].push(item);
count++;
} else {
break;
limited[g].push(item)
count++
}
}
//
if (limitedMessages[groupName].length === 0) {
delete limitedMessages[groupName];
}
if (count >= 2) break;
if (limited[g].length === 0) delete limited[g]
if (count >= 2) break
}
return limited
})
return limitedMessages;
});
// /
const toggleShowAll = () => {
showAll.value = !showAll.value;
};
//
const toggleShowAll = () => showAll.value = !showAll.value
//
const scrollContainer = ref(null)
const scrollToTop = () => {
scrollContainer.value?.scrollTo({top: 0, behavior: 'smooth'})
const scrollToTop = () => scrollContainer.value?.scrollTo({top: 0, behavior: 'smooth'})
// +
const handleMessageClick = async (item) => {
const res = await API({
url: '/getMessage/update',
method: 'POST',
data: {id: item.id}
});
if (res.code === 200) {
closeMessageDialog()
await router.push(getOrderPage(item.status))
await getMessage()
ElMessage.success('跳转成功')
} else {
ElMessage.error('跳转失败')
}
}
import goTop from '@/assets/SvgIcons/go-top.svg'
onMounted(() => getMessage())
</script>
<template>
<div class="main-container">
<!-- 背景毛玻璃层作为内容容器 -->
@ -353,7 +309,7 @@ import goTop from '@/assets/SvgIcons/go-top.svg'
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="refreshData()">数据刷新</el-dropdown-item>
<!-- <el-dropdown-item @click="refreshData()">数据刷新</el-dropdown-item>-->
<!-- 员工数据开关 -->
<el-dropdown-item @click="toggleFlag()">
{{ flag === 1 ? '显示员工数据' : '隐藏员工数据' }}
@ -432,7 +388,7 @@ import goTop from '@/assets/SvgIcons/go-top.svg'
消息中心 ({{ messageNum }})
</div>
<!-- todo 这是为了样式显示 一定要改逻辑-->
<div v-if="messageNum !== 0">
<div v-if="messageNum === 0">
<div class="no-message">
<el-image :src="noMessage"></el-image>
<p class="no-message-text">暂无未办消息快去处理工作吧</p>
@ -461,13 +417,19 @@ import goTop from '@/assets/SvgIcons/go-top.svg'
<div style="display: flex; margin-bottom: 10px">
<span class="red-dot"></span>
<span class="message-card-title">{{ item.title }}</span>
<div class="message-time">{{ item.time }}</div>
<div
class="message-time"
:style="{ color: item.czTime.includes('分钟') ? 'red' : '' }"
>
{{ item.czTime }}
</div>
</div>
<p class="message-desc">{{ item.desc }}</p>
<el-button
type="primary"
style="margin: 0 auto; display: block;"
@click="handleMessageClick(item)"
>
前往查看
</el-button>
@ -481,6 +443,7 @@ import goTop from '@/assets/SvgIcons/go-top.svg'
<div>
<el-button
type="text"
v-if="messageNum > 2"
class="view-all"
@click="toggleShowAll"
>

132
src/views/permissions/rolePermission.vue

@ -1,16 +1,17 @@
<script setup>
import { nextTick, onMounted, reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
import {nextTick, onMounted, reactive, ref} from 'vue'
import {ElMessage} from 'element-plus'
import _ from 'lodash'
import request from '@/util/http'
import API from '@/util/http'
import { useAdminStore } from "@/store/index.js"
import { storeToRefs } from "pinia"
import {useAdminStore} from "@/store/index.js"
import {storeToRefs} from "pinia"
const adminStore = useAdminStore();
const { adminData, menuTree } = storeToRefs(adminStore);
import { permissionMapping, findMenuById } from "@/utils/menuTreePermission.js"
import { tr } from 'element-plus/es/locales.mjs'
const {adminData, menuTree} = storeToRefs(adminStore);
import {permissionMapping, findMenuById} from "@/utils/menuTreePermission.js"
import {tr} from 'element-plus/es/locales.mjs'
// ref
const Ref = ref(null)
const roleData = ref([])
@ -138,7 +139,7 @@ const handleDialogClose = function () {
const permissionList = ref([])
const getRoles = async function () {
try {
const res = await API({ url: '/role/selectAll' })
const res = await API({url: '/role/selectAll'})
permissionList.value = res.data.map(item => ({
label: item.roleName,
value: item.id
@ -237,7 +238,7 @@ const getLists = async function () {
}
const res = await API({
url: '/menu/tree',
data: { id: roleId }
data: {id: roleId}
})
data.value = res.data
data.value = filterPermission(data.value)
@ -247,7 +248,7 @@ const getLists = async function () {
if (addRole.value.parentId && addRole.value.parentId !== 2) {
const result = await API({
url: '/general/roleMarket',
data: { id: addRole.value.parentId }
data: {id: addRole.value.parentId}
})
if (result.code === 200) {
if (typeof result.data === 'string' && result.data) {
@ -305,25 +306,24 @@ const goldenBeanMenuIds = new Set([
// 23//
const filterGoldenBeanMenus = (tree) => {
return tree
.filter(item => {
//
if (goldenBeanMenuIds.has(item.id)) {
return false
}
//
if (item.children && item.children.length > 0) {
item.children = filterGoldenBeanMenus(item.children)
}
return true
})
.filter(item => {
//
if (goldenBeanMenuIds.has(item.id)) {
return false
}
//
if (item.children && item.children.length > 0) {
item.children = filterGoldenBeanMenus(item.children)
}
return true
})
}
// (????????)
const filterPermission = (tree) => {
return tree.filter(item => {
if (item.id === permissionMapping.permission_management) {
return false
}
else if (item.children && item.children.length > 0) {
} else if (item.children && item.children.length > 0) {
item.children = filterPermission(item.children)
}
return true
@ -331,7 +331,7 @@ const filterPermission = (tree) => {
}
//
const handleEditRolePermissionCheck = (checkedNodes, checkedInfo) => {
const { checkedKeys, checkedNodes: allCheckedNodes } = checkedInfo
const {checkedKeys, checkedNodes: allCheckedNodes} = checkedInfo
//
if (allCheckedNodes.length === 0) {
@ -356,7 +356,7 @@ const handleEditRolePermissionCheck = (checkedNodes, checkedInfo) => {
//
const ifHasChannel = ref(false)
const handleCheckChange = async (checkedNodes, checkedInfo) => {
const { checkedKeys, checkedNodes: allCheckedNodes } = checkedInfo
const {checkedKeys, checkedNodes: allCheckedNodes} = checkedInfo
//
if (allCheckedNodes.length === 0) {
@ -429,7 +429,7 @@ const permissionEditRoleObj = ref({
parentId: null,
parentName: '',
checkedKeys: [],
channel:''
channel: ''
})
//
@ -476,8 +476,8 @@ const permissionEditRoleInit = async function (row) {
permissionEditRoleObj.value.parentId = row.fatherId
permissionEditRoleObj.value.parentName = row.fatherName
permissionEditRoleObj.value.channel = row.channel
console.log('permissionEditRoleObj.value',permissionEditRoleObj.value);
console.log('permissionEditRoleObj.value', permissionEditRoleObj.value);
if (EditIds.includes(124)) {
ifHasChannel.value = true
} else {
@ -492,7 +492,7 @@ const permissionEditRoleInit = async function (row) {
// /tree 使 ID
const res = await API({
url: '/menu/tree',
data: { id: roleId }
data: {id: roleId}
});
data.value = res.data;
data.value = filterPermission(data.value)
@ -602,11 +602,11 @@ const selectParentNodesForSubmit = (treeData, nodeId, checkedKeys) => {
const Rolerules = reactive({
roleName: [
{ required: true, message: '请输入角色名称', trigger: 'blur' },
{ min: 2, max: 20, message: '角色名称长度应在2-20个字符之间', trigger: 'blur' }
{required: true, message: '请输入角色名称', trigger: 'blur'},
{min: 2, max: 20, message: '角色名称长度应在2-20个字符之间', trigger: 'blur'}
],
market: [
{ required: true, message: '请选择归属地区', trigger: 'change' }
{required: true, message: '请选择归属地区', trigger: 'change'}
],
checkedKeys: [
{
@ -643,7 +643,7 @@ onMounted(async function () {
<el-card class="card1" style="margin-bottom: 1vh;">
<div style="display: flex;">
<el-text size="large">角色名称</el-text>
<el-input v-model="role.name" style="width: 240px" placeholder="请输入角色名称" clearable />
<el-input v-model="role.name" style="width: 240px" placeholder="请输入角色名称" clearable/>
<div style="margin-left: auto;">
<el-button type="primary" @click="searchRole()" :disabled="!canLook" v-if="canLook">查询</el-button>
<el-button type="success" @click="reset()">重置</el-button>
@ -654,20 +654,21 @@ onMounted(async function () {
<el-card class="card2">
<div class="add-item">
<el-button style="color: #048efb; border: 1px solid #048efb" @click="permissionAddInit()" :disabled="!canAdd"
v-if="canAdd">新增角色</el-button>
v-if="canAdd">新增角色
</el-button>
</div>
<div>
<el-table :data="roleData" style="width: 82vw;height:71.3vh" show-overflow-tooltip
:row-style="{ height: '56px' }">
:row-style="{ height: '56px' }">
<el-table-column type="index" label="序号" width="100px" fixed="left">
<template #default="scope">
<span>{{
scope.$index + 1 + (getRoleObj.pageNum - 1) * getRoleObj.pageSize
}}</span>
scope.$index + 1 + (getRoleObj.pageNum - 1) * getRoleObj.pageSize
}}</span>
</template>
</el-table-column>
<el-table-column prop="roleName" label="角色名称" />
<el-table-column prop="roleName" label="角色名称"/>
<el-table-column prop="fatherName" label="上级角色">
<template #default="scope">
{{ scope.row.fatherName || '-' }}
@ -683,7 +684,7 @@ onMounted(async function () {
<el-table-column prop="operation" label="操作" width="200px">
<template #default="scope">
<el-button type="warning" text @click="permissionEditRoleInit(scope.row)"
:disabled="(scope.row.id === 2) || (scope.row.id === 1) || !canEdit" v-if="canEdit">
:disabled="(scope.row.id === 2) || (scope.row.id === 1) || !canEdit" v-if="canEdit">
编辑
</el-button>
</template>
@ -693,8 +694,10 @@ onMounted(async function () {
<div style="margin-top: 20px;display: flex;">
<el-pagination background :current-page="getRoleObj.pageNum" :page-size="getRoleObj.pageSize"
:page-sizes="[5, 10, 20, 50, 100]" layout="total, sizes, prev, pager, next, jumper" :total="roleTotal"
@size-change="handleRolePageSizeChange" @current-change="handleRoleCurrentChange"></el-pagination>
:page-sizes="[5, 10, 20, 50, 100]" layout="total, sizes, prev, pager, next, jumper"
:total="roleTotal"
@size-change="handleRolePageSizeChange"
@current-change="handleRoleCurrentChange"></el-pagination>
</div>
</el-card>
</div>
@ -702,8 +705,8 @@ onMounted(async function () {
<!-- 角色菜单树展示 -->
<el-dialog v-model="menuTreeVisible" :title='`权限详情:${currentRoleName}`' width="600px">
<el-tree :data="currentRoleMenuTree" node-key="id" :props="{ label: 'menuName', children: 'children' }"
show-checkbox check-strictly :expand-on-click-node="false"
:default-expanded-keys="currentRoleMenuTree.map(item => item.id)" :default-checked-keys="Rolecheckedkeys" />
show-checkbox check-strictly :expand-on-click-node="false"
:default-expanded-keys="currentRoleMenuTree.map(item => item.id)" :default-checked-keys="Rolecheckedkeys"/>
<template #footer>
<el-button @click="menuTreeVisible = false" type="primary">关闭</el-button>
</template>
@ -711,29 +714,30 @@ onMounted(async function () {
<!-- 新增角色 -->
<el-dialog v-model="permissionAddVisible" title="新增角色" width="800px" :close-on-click-modal="false"
@close="handleDialogClose">
@close="handleDialogClose">
<template #footer>
<el-form ref="Ref" :rules="Rolerules" :model="addRole" label-width="auto"
style="max-width: 600px; align-items: center">
style="max-width: 600px; align-items: center">
<el-form-item prop="roleName" label="角色名称:" required>
<el-input v-model="addRole.roleName" placeholder="请输入角色名称" style="width: 220px" />
<el-input v-model="addRole.roleName" placeholder="请输入角色名称" style="width: 220px"/>
</el-form-item>
<el-form-item prop="parentName" label="上级角色:">
<el-select v-model="addRole.parentId" placeholder="请选择上级角色" style="width: 220px" @change="getLists" clearable>
<el-select v-model="addRole.parentId" placeholder="请选择上级角色" style="width: 220px" @change="getLists"
clearable>
<el-option v-for="item in permissionList" :key="item.value" :label="item.label"
:value="item.value"></el-option>
:value="item.value"></el-option>
</el-select>
</el-form-item>
<el-form-item prop="market" label="归属地区:" required>
<el-select v-model="addRole.market" placeholder="请选择归属地区" style="width: 220px" clearable>
<el-option v-for="item in addRoleMarket" :key="item" :label="item" :value="item" />
<el-option v-for="item in addRoleMarket" :key="item" :label="item" :value="item"/>
</el-select>
<text>(此地区无实际意义仅用于各分部负责人查看其地区角色)</text>
</el-form-item>
<el-form-item prop="checkedKeys" label="权限列表:" required>
<el-tree v-if="data.length > 0" :data="data" show-checkbox node-key="id"
:props="{ label: 'menuName', children: 'children' }" :checked-keys="addRole.checkedKeys"
:check-strictly="false" @check="handleCheckChange">
:props="{ label: 'menuName', children: 'children' }" :checked-keys="addRole.checkedKeys"
:check-strictly="false" @check="handleCheckChange">
<template #default="{ node }">
<span>{{ node.label }}</span>
</template>
@ -742,9 +746,9 @@ onMounted(async function () {
<span style="color: #999;">暂无数据</span>
</div>
</el-form-item>
<el-form-item v-show="ifHasChannel" prop="channel" label="频道名称:" required>
<el-form-item v-show="ifHasChannel" label="频道名称:" required>
<el-select v-model="addRole.channel" placeholder="请选择频道" style="width: 220px" filterable clearable>
<el-option v-for="item in channelList" :key="item" :label="item" :value="item" />
<el-option v-for="item in channelList" :key="item" :label="item" :value="item"/>
</el-select>
</el-form-item>
</el-form>
@ -762,25 +766,25 @@ onMounted(async function () {
<el-dialog v-model="permissionEditRoleVisible" title="编辑角色" width="800px" :close-on-click-modal="false">
<template #footer>
<el-form ref="Ref" :rules="Rolerules" :model="permissionEditRoleObj" label-width="auto"
style="max-width: 600px; align-items: center">
style="max-width: 600px; align-items: center">
<el-form-item prop="roleName" label="角色名称:" required>
<el-input v-model="permissionEditRoleObj.roleName" placeholder="请输入角色名称" style="width: 220px" />
<el-input v-model="permissionEditRoleObj.roleName" placeholder="请输入角色名称" style="width: 220px"/>
</el-form-item>
<el-form-item prop="parentName" label="上级角色:">
<el-input v-model="permissionEditRoleObj.parentName" placeholder="无上级角色" disabled style="width: 220px">
<el-option v-for="item in permissionList" :key="item.value" :label="item.label"
:value="item.value"></el-option>
:value="item.value"></el-option>
</el-input>
</el-form-item>
<el-form-item prop="market" label="归属地区" required>
<el-input v-model="permissionEditRoleObj.market" placeholder="请输入归属地区" style="width: 220px" disabled />
<el-input v-model="permissionEditRoleObj.market" placeholder="请输入归属地区" style="width: 220px" disabled/>
<text>(此地区无实际意义仅用于各分部负责人查看其地区角色)</text>
</el-form-item>
<el-form-item prop="checkedKeys" label="权限列表:" required>
<el-tree v-if="data.length > 0" :data="data" show-checkbox node-key="id" ref="treeRef"
:props="{ label: 'menuName', children: 'children' }"
:default-checked-keys="permissionEditRoleObj.checkedKeys" :check-strictly="false"
@check="handleEditRolePermissionCheck">
:props="{ label: 'menuName', children: 'children' }"
:default-checked-keys="permissionEditRoleObj.checkedKeys" :check-strictly="false"
@check="handleEditRolePermissionCheck">
<!-- <template #default="{ node, data }"> data删掉了不影响功能 -->
<template #default="{ node }">
<span>{{ node.label }}</span>
@ -791,9 +795,9 @@ onMounted(async function () {
</div>
</el-form-item>
<el-form-item v-show="ifHasChannel" prop="channel" label="频道名称:" required>
<el-form-item v-show="ifHasChannel" label="频道名称:" required>
<el-select v-model="permissionEditRoleObj.channel" placeholder="请选择频道" style="width: 220px" clearable>
<el-option v-for="item in channelList" :key="item" :label="item" :value="item" />
<el-option v-for="item in channelList" :key="item" :label="item" :value="item"/>
</el-select>
</el-form-item>
</el-form>
@ -829,7 +833,7 @@ onMounted(async function () {
:deep(.el-table__header-wrapper),
:deep(.el-table__body-wrapper),
:deep(.el-table__cell),
/* 表格 */
/* 表格 */
:deep(.el-table__body td) {
background-color: #F3FAFE !important;
}

911
src/views/workspace/index_.vue

@ -0,0 +1,911 @@
<template>
<div class="top">
<el-card style="width:10vw" class="center-card">数据总览</el-card>
<span class="text">
最后更新时间{{
workDataUpdateTime && workDataUpdateTime !== '1970-01-01 08:00:00' ? workDataUpdateTime : '该地区暂无数据'
}}
</span>
</div>
<div class="card">
<!-- 第一个卡片 -->
<el-card class="card-item">
<template #header>
<div class="card-title">当前金币余量</div>
<div>
<span style="font-weight: bold">{{ currentGold / 100
}}</span>&nbsp;&nbsp;&nbsp;&nbsp;较前一日
{{ dailyChange / 100 }}
<template v-if="dailyChange > 0">
<el-icon style="color:red">
<ArrowUpBold />
</el-icon>
</template>
<template v-else-if="dailyChange < 0">
<el-icon style="color:forestgreen">
<ArrowDownBold />
</el-icon>
</template>
<template v-else>
<el-icon style="color:grey">
<SemiSelect />
</el-icon>
</template>
</div>
</template>
<div>
<div class="margin-bottom">永久金币{{ currentPermanent / 100 }}</div>
<div class="margin-bottom">免费金币{{ currentFree / 100 }}</div>
<div class="margin-bottom">[六月到期|{{ currentFreeJune / 100 }}]&nbsp;&nbsp;
[十二月到期|{{ currentFreeDecember / 100 }}]
</div>
<div>任务金币{{ currentTask / 100 }}</div>
</div>
</el-card>
<!-- 第二个卡片 -->
<el-card class="card-item">
<div class="card-title">全年累计充值金币数</div>
<div class="card-title">{{ yearlyRecharge / 100 }}</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</div>
<div class="center-card">折合新币累计金额:{{ yearlyMoney / 100 }}</div>
<template #footer>
<el-col class="margin-bottom center-card">昨日新增金币{{ recharge / 100 }}</el-col>
<el-col class="margin-bottom center-card">其中永久金币{{ money / 100 }}</el-col>
</template>
</el-card>
<!-- 第三个卡片 -->
<el-card class="card-item">
<div class="card-title">全年累计消费金币数</div>
<div class="card-title">{{ yearlyReduce / 100 }}</div>
<div style="padding-left: 30%;">消耗{{ yearlyConsume / 100 }}</div>
<div style="padding-left: 30%;">退款{{ yearlyRefund / 100 }}</div>
<template #footer>
<div style="margin-bottom:0.5%;padding-left: 30%;">昨日新增消费{{ dailyConsume / 100 }}</div>
<div style="margin-bottom:0.5%;padding-left: 30%;">昨日新增消耗{{ dailyReduce / 100 }}</div>
<div style="margin-bottom:0.5%;padding-left: 30%;">昨日新增退款{{ dailyRefund / 100 }}</div>
</template>
</el-card>
<!-- 第四个卡片 -->
<el-card class="card-item">
<el-col class="card-title">全年累计充值人头数</el-col>
<el-col class="card-title">{{ yearlyRechargeNum }}</el-col>
<el-col style="padding-left: 35%;">周同比:{{ sumWow }}%&nbsp;&nbsp;&nbsp;&nbsp;
<template v-if="sumWow > 0">
<el-icon style="color:red">
<ArrowUpBold />
</el-icon>
</template>
<template v-else-if="sumWow < 0">
<el-icon style="color:forestgreen">
<ArrowDownBold />
</el-icon>
</template>
<template v-else>
<el-icon style="color:grey">
<SemiSelect />
</el-icon>
</template>
</el-col>
<el-col style="padding-left: 35%;">日环比:{{ sumDaily }}%&nbsp;&nbsp;&nbsp;&nbsp;
<template v-if="sumDaily > 0">
<el-icon style="color:red">
<ArrowUpBold />
</el-icon>
</template>
<template v-else-if="sumDaily < 0">
<el-icon style="color:forestgreen">
<ArrowDownBold />
</el-icon>
</template>
<template v-else>
<el-icon style="color:grey">
<SemiSelect />
</el-icon>
</template>
</el-col>
<template #footer>
<el-col style="padding-left: 35%;margin-bottom:0.5%">昨日充值人数{{ ydayRechargeNum }}</el-col>
<el-col style="padding-left: 35%;">其中首充{{ firstRecharge }}</el-col>
</template>
</el-card>
</div>
<div class="graph">
<el-card style="width:84vw;">
<div>
<el-tabs v-model="activeTab" @tab-change="handleTabChange">
<el-tab-pane label="金币充值" name="recharge"></el-tab-pane>
<el-tab-pane label="金币消费" name="consume"></el-tab-pane>
</el-tabs>
</div>
<div class="condition">
<div class="stats">
<div v-if="activeTab === 'consume'">合计{{ sumConsume / 100 }}</div>&nbsp;&nbsp;
永久金币: {{ activeTab === 'recharge' ? sumRechargePermanent / 100 : sumConsumePermanent / 100 }}&nbsp;&nbsp;
免费金币: {{ activeTab === 'recharge' ? sumRechargeFree / 100 : sumConsumeFree / 100 }}&nbsp;&nbsp;
任务金币: {{ activeTab === 'recharge' ? sumRechargeTask / 100 : sumConsumeTask / 100 }}&nbsp;&nbsp;
</div>
<div>
<el-button @click="getYes()" size="small" :type="activeTimeRange === 'yes' ? 'primary' : ''">昨天
</el-button>
<el-button @click="getToday()" size="small" :type="activeTimeRange === 'today' ? 'primary' : ''">今天
</el-button>
<el-button @click="getWeek()" size="small" :type="activeTimeRange === 'week' ? 'primary' : ''">本周
</el-button>
<el-button @click="getMonth()" size="small" :type="activeTimeRange === 'month' ? 'primary' : ''">本月
</el-button>
<el-button @click="getYear()" size="small" :type="activeTimeRange === 'year' ? 'primary' : ''">本年
</el-button>
</div>
<div>
<el-date-picker size="small" v-model="dateRange" type="datetimerange" range-separator=""
start-placeholder="开始时间" end-placeholder="结束时间" format="YYYY-MM-DD HH:mm:ss"
style="width:20vw;margin-left:0.5vw;" value-format="YYYY-MM-DD HH:mm:ss" :default-time="defaultTime"
:disabled-date="disabledDate" @change="handleDatePickerChange" />
<el-button type="primary" size="small" style="margin-left: 0.5vw" @click="getChartData">查询</el-button>
</div>
</div>
<div class="graph-content">
<div ref="chartRef" class="left"></div>
<div class="right">
<el-card>
<div class="card-large">金币{{ activeTab === 'recharge' ? '充值' : '消费' }}排名</div>
<el-select v-model="selectedType" style="width: 100%; margin-bottom: 15px">
<el-option label="全部类型" value="all"></el-option>
<el-option label="永久金币" value="permanent"></el-option>
<el-option label="免费金币" value="free"></el-option>
<el-option label="任务金币" value="task"></el-option>
</el-select>
<el-table :data="tableData" height="320px">
<el-table-column prop="rank" label="排名" width="60" align="center"></el-table-column>
<el-table-column prop="market" label="地区" align="center">
<template #default="scope">
<span>{{ marketMapping[scope.row.market] || scope.row.market }}</span>
</template>
</el-table-column>
<el-table-column prop="coinAmount" label="金币数量" align="center">
<template #default="{ row }">
{{ row.coinAmount.toLocaleString() }}
</template>
</el-table-column>
</el-table>
</el-card>
</div>
</div>
</el-card>
</div>
</template>
<script setup>
import * as echarts from 'echarts'
import { ref, onMounted, nextTick, watch, onUnmounted } from 'vue'
import API from '@/util/http'
import { ElMessage } from 'element-plus'
import dayjs from 'dayjs';
import utc from 'dayjs-plugin-utc'
import weekday from 'dayjs/plugin/weekday'
dayjs.extend(utc)
import { ArrowUpBold, ArrowDownBold, SemiSelect } from '@element-plus/icons-vue'
import { marketMapping } from "@/utils/marketMap.js";
const defaultTime = [
new Date(2000, 1, 1, 0, 0, 0),
new Date(2000, 2, 1, 23, 59, 59),
]
//
const markets = ref([])
//
const dateRange = ref([])
const activeTab = ref('recharge')
const selectedType = ref('all')
const tableData = ref([])
const chartRef = ref(null)
let chartInstance = null
//
const sumRechargePermanent = ref(0)
const sumRechargeFree = ref(0)
const sumRechargeTask = ref(0)
const sumConsumePermanent = ref(0)
const sumConsumeFree = ref(0)
const sumConsumeTask = ref(0)
const sumConsume = ref(0)
//
const adminData = ref({})
//
const currentGold = ref(0)
const dailyChange = ref(0)
const currentPermanent = ref(0)
const currentFree = ref(0)
const currentFreeJune = ref(0)
const currentFreeDecember = ref(0)
const currentTask = ref(0)
const yearlyRecharge = ref(0)
const yearlyMoney = ref(0)
const recharge = ref(0)
const money = ref(0)
const yearlyReduce = ref(0)
const yearlyConsume = ref(0)
const yearlyRefund = ref(0)
const dailyReduce = ref(0)
const dailyConsume = ref(0)
const dailyRefund = ref(0)
const yearlyRechargeNum = ref(0)
const sumWow = ref(0)
const sumDaily = ref(0)
const rechargeNum = ref(0)
const ydayRechargeNum = ref(0)
const firstRecharge = ref(0)
const length = ref(0)
//
const chartLoading = ref(true)
const handleResize = () => {
if (chartInstance.value) {
try {
chartInstance.value.resize()
console.log('resize一下')
} catch (error) {
console.error('图表resize失败:', error)
}
}
}
//
const initChart = () => {
if (!chartInstance && chartRef.value) {
chartInstance = echarts.init(chartRef.value)
window.addEventListener('resize', handleResize)
}
}
//
const destroyChart = () => {
if (chartInstance.value) {
try {
chartInstance.value.dispose()
} catch (error) {
console.error('图表销毁失败:', error)
}
chartInstance.value = null
}
window.removeEventListener('resize', handleResize)
}
const formatDate = function (date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
//
const getYes = function () {
const yesterday = dayjs().subtract(1, 'day')
const startTime = yesterday.startOf('day').format('YYYY-MM-DD HH:mm:ss')
const endTime = yesterday.endOf('day').format('YYYY-MM-DD HH:mm:ss')
dateRange.value = [startTime, endTime]
console.log('看看dateRange', dateRange.value)
activeTimeRange.value = 'yes' //
getChartData()
}
//
const getToday = 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')
// const endTime = today.add(1, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss')
dateRange.value = [startTime, endTime]
console.log('看看dateRange', dateRange.value)
activeTimeRange.value = 'today' //
getChartData()
}
//
const getWeek = function () {
const today = dayjs();
// 01...6
const day = today.day();
// 6
let monday = today.subtract(day === 0 ? 6 : day - 1, 'day');
// (7 - day)
let sunday = today.add(day === 0 ? 0 : 7 - day, 'day');
//
const startTime = monday.startOf('day').format('YYYY-MM-DD HH:mm:ss');
const endTime = sunday.endOf('day').format('YYYY-MM-DD HH:mm:ss');
dateRange.value = [startTime, endTime];
console.log('本周时间范围(周一到周日):', dateRange.value);
activeTimeRange.value = 'week';
getChartData();
};
//
const getMonth = function () {
const today = dayjs()
const startTime = today.startOf('month').format('YYYY-MM-DD HH:mm:ss')
// const endTime = today.add(1, 'month').startOf('month').format('YYYY-MM-DD HH:mm:ss')
const endTime = today.endOf('month').format('YYYY-MM-DD HH:mm:ss')
dateRange.value = [startTime, endTime]
console.log('看看dateRange', dateRange.value)
activeTimeRange.value = 'month' //
getChartData()
}
//
const getYear = function () {
const today = dayjs()
const startTime = today.startOf('year').format('YYYY-MM-DD HH:mm:ss')
const endTime = today.endOf('year').format('YYYY-MM-DD HH:mm:ss')
// const endTime = today.add(1, 'year').startOf('year').format('YYYY-MM-DD HH:mm:ss')
dateRange.value = [startTime, endTime]
console.log('看看dateRange', dateRange.value)
activeTimeRange.value = 'year' //
getChartData()
}
// ( = + 6 + 12 + + )
const processData = (data) => {
const summary = {
currentGold: 0,
dailyChange: 0,
currentPermanent: 0,
currentFreeJune: 0,
currentFreeDecember: 0,
currentTask: 0,
currentFree: 0,
recharge: 0,
money: 0,
yearlyRecharge: 0,
yearlyMoney: 0,
consumePermanent: 0,
consumeFreeJune: 0,
consumeFreeDecember: 0,
consumeTask: 0,
refundPermanent: 0,
refundFreeJune: 0,
refundFreeDecember: 0,
refundTask: 0,
dailyReduce: 0,
yearlyConsume: 0,
yearlyRefund: 0,
yearlyReduce: 0,
rechargeNum: 0,
ydayRechargeNum: 0,
firstRecharge: 0,
sumWow: 0,
sumDaily: 0,
yearlyRechargeNum: 0
}
//
data.marketCards.forEach(market => {
for (const i in summary) {
if (market[i] !== undefined && market[i] !== null) { // number
summary[i] += market[i]
}
}
})
// wowdaily
length.value = data.markets.length
console.log(length.value)
// 退
const yesterdayConsume = summary.consumePermanent + summary.consumeFreeJune + summary.consumeFreeDecember + summary.consumeTask
const yesterdayRefund = summary.refundPermanent + summary.refundFreeJune + summary.refundFreeDecember + summary.refundTask
//
currentGold.value = summary.currentGold.toFixed(2)
dailyChange.value = summary.dailyChange.toFixed(2)
currentPermanent.value = summary.currentPermanent.toFixed(2)
currentFree.value = summary.currentFree.toFixed(2)
currentFreeJune.value = summary.currentFreeJune.toFixed(2)
currentFreeDecember.value = summary.currentFreeDecember.toFixed(2)
currentTask.value = summary.currentTask.toFixed(2)
yearlyRecharge.value = summary.yearlyRecharge.toFixed(2)
yearlyMoney.value = summary.yearlyMoney.toFixed(2)
recharge.value = summary.recharge.toFixed(2)
money.value = summary.money.toFixed(2)
yearlyReduce.value = summary.yearlyReduce.toFixed(2)
yearlyConsume.value = summary.yearlyConsume.toFixed(2)
yearlyRefund.value = summary.yearlyRefund.toFixed(2)
dailyReduce.value = summary.dailyReduce.toFixed(2)
dailyConsume.value = yesterdayConsume.toFixed(2)
dailyRefund.value = yesterdayRefund.toFixed(2)
yearlyRechargeNum.value = summary.yearlyRechargeNum
// //
// sumWow.value = (marketCards.sumWow / length.value).toFixed(2)
// //
// sumDaily.value = (marketCards.sumDaily / length.value).toFixed(2)
// rechargeNum.value = summary.rechargeNum
ydayRechargeNum.value = summary.ydayRechargeNum
firstRecharge.value = summary.firstRecharge
}
//
const disabledDate = (time) => {
const limitDate = new Date(2025, 0, 1);
return time.getTime() < limitDate.getTime();
}
//
const getMarkets = async () => {
console.log("adminData", adminData.value.account)
try {
const response = await API({
url: '/general/adminMarkets',
data: {
account: adminData.value.account
}
})
if (Array.isArray(response.data)) {
// markets.value = response.data.filter(data => data !== "1")
markets.value = response.data
console.log('市场列表获取成功:', markets.value)
} else {
console.error('获取市场列表失败', response)
ElMessage.error('获取市场列表失败')
}
} catch (error) {
console.error('获取市场列表失败:', error)
ElMessage.error('获取市场列表失败')
}
}
//
const getChartData = async () => {
try {
//
if (!markets.value || markets.value.length === 0) {
await getMarkets()
}
//
if (!dateRange.value || dateRange.value.length === 0) {
getYear()
}
const params = {
markets: markets.value,
startDate: dateRange.value[0],
endDate: dateRange.value[1]
};
const response = await API({
url: '/workbench/getGraph',
data: params
})
console.log('看看params', params)
if (Array.isArray(response.marketGraphs)) {
// const filteredGraphs = response.marketGraphs.filter(data => data.market !== "1");
//
processChartData(response.marketGraphs)
//
processRankingData(response.marketGraphs)
} else {
console.error('获取图表数据失败:', response)
ElMessage.error('获取图表数据失败')
}
} catch (error) {
console.error('获取图表数据失败:', error)
ElMessage.error('获取图表数据失败')
}
}
//
const processChartData = (marketCards) => {
const chartData = {
rechargePermanent: [],
rechargeFree: [],
rechargeTask: [],
consumePermanent: [],
consumeFree: [],
consumeTask: [],
sumConsume: []
}
//
const sumRechargePermanent1 = ref(0)
const sumRechargeFree1 = ref(0)
const sumRechargeTask1 = ref(0)
const sumConsumePermanent1 = ref(0)
const sumConsumeFree1 = ref(0)
const sumConsumeTask1 = ref(0)
const sumConsume1 = ref(0)
marketCards.forEach(market => {
chartData.rechargePermanent.push(market.sumRechargePermanent / 100 || 0)
chartData.rechargeFree.push(market.sumRechargeFree / 100 || 0)
chartData.rechargeTask.push(market.sumRechargeTask / 100 || 0)
chartData.consumePermanent.push(market.sumConsumePermanent / 100 || 0)
chartData.consumeFree.push(market.sumConsumeFree / 100 || 0)
chartData.consumeTask.push(market.sumConsumeTask / 100 || 0)
chartData.sumConsume.push(market.sumConsume / 100 || 0)
//
sumRechargePermanent1.value += (market.sumRechargePermanent || 0)
sumRechargeFree1.value += (market.sumRechargeFree || 0)
//sumRechargeTask1.value += (market.sumRechargeTask || 0)
sumConsumePermanent1.value += (market.sumConsumePermanent || 0)
sumConsumeFree1.value += (market.sumConsumeFree || 0)
sumConsumeTask1.value += (market.sumConsumeTask || 0)
sumConsume1.value += (market.sumConsume || 0)
})
sumRechargePermanent.value = sumRechargePermanent1.value
sumRechargeFree.value = sumRechargeFree1.value
sumRechargeTask.value = 0
sumConsumePermanent.value = sumConsumePermanent1.value
sumConsumeFree.value = sumConsumeFree1.value
sumConsumeTask.value = sumConsumeTask1.value
sumConsume.value = sumConsume1.value
updateChart(chartData)
}
const processRankingData = (marketCards) => {
//
const rankingData = marketCards.map(market => {
let coinAmount = 0;
if (activeTab.value === 'recharge') {
//
switch (selectedType.value) {
case 'all':
coinAmount = (market.sumRechargePermanent / 100 || 0) + (market.sumRechargeFree / 100 || 0) + (market.sumRechargeTask / 100 || 0);
break;
case 'permanent':
coinAmount = market.sumRechargePermanent / 100 || 0;
break;
case 'free':
coinAmount = market.sumRechargeFree / 100 || 0;
break;
case 'task':
coinAmount = market.sumRechargeTask / 100 || 0;
break;
}
} else {
//
switch (selectedType.value) {
case 'all':
coinAmount = (market.sumConsumePermanent / 100 || 0) + (market.sumConsumeFree / 100 || 0) + (market.sumConsumeTask / 100 || 0);
break;
case 'permanent':
coinAmount = market.sumConsumePermanent / 100 || 0;
break;
case 'free':
coinAmount = market.sumConsumeFree / 100 || 0;
break;
case 'task':
coinAmount = market.sumConsumeTask / 100 || 0;
break;
}
}
return {
market: market.market,
coinAmount: coinAmount
};
});
//
rankingData.sort((a, b) => b.coinAmount - a.coinAmount);
//
tableData.value = rankingData.map((item, index) => ({
rank: index + 1,
...item
}));
}
watch(selectedType, () => {
getChartData();
});
//
const updateChart = (chartData) => {
if (!chartInstance) {
initChart()
}
chartLoading.value = true
try {
let series = []
let legend = []
if (activeTab.value === 'recharge') {
series = [
{
name: '永久金币',
type: 'bar',
stack: 'recharge',
data: chartData.rechargePermanent,
itemStyle: { color: '#5470c6' },
barWidth: 30
},
{
name: '免费金币',
type: 'bar',
stack: 'recharge',
data: chartData.rechargeFree,
itemStyle: { color: '#91cc75' },
barWidth: 30
},
{
name: '任务金币',
type: 'bar',
stack: 'recharge',
data: chartData.rechargeTask,
itemStyle: { color: '#fac858' },
barWidth: 30
}
]
legend = ['永久金币', '免费金币', '任务金币']
} else {
series = [
{
name: '永久金币',
type: 'bar',
stack: 'consume',
data: chartData.consumePermanent,
itemStyle: { color: '#5470c6' },
barWidth: 30
},
{
name: '免费金币',
type: 'bar',
stack: 'consume',
data: chartData.consumeFree,
itemStyle: { color: '#91cc75' },
barWidth: 30
},
{
name: '任务金币',
type: 'bar',
stack: 'consume',
data: chartData.consumeTask,
itemStyle: { color: '#fac858' },
barWidth: 30
}
]
legend = ['永久金币', '免费金币', '任务金币']
}
const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
formatter: function (params) {
let result = params[0].name + '<br/>'
let total = 0;
params.forEach(param => {
result += `${param.seriesName}: ${param.value.toLocaleString()}<br/>`;
total += param.value;
})
result += `${activeTab.value === 'recharge' ? '充值' : '消费'}: ${total.toLocaleString()}`;
return result
}
},
legend: {
data: legend,
bottom: 10
},
grid: {
left: '3%',
right: '4%',
bottom: '10%',
containLabel: true
},
xAxis: {
type: 'category',
data: markets.value,
axisLabel: {
interval: 0,
rotate: 30
}
},
yAxis: {
type: 'value',
axisLabel: {
formatter: function (value) {
return value.toLocaleString()
}
}
},
series: series,
// dataZoom: [
// {
// type: 'slider',
// show: true,
// start: 0,
// end: 100,
// maxSpan: 100,
// minSpan: 100,
//
// height: 2,
// },
// ]
}
chartInstance.setOption(option)
} catch (error) {
console.error('图表更新失败:', error)
ElMessage.error('图表渲染失败')
} finally {
setTimeout(() => {
chartLoading.value = false
}, 300)
}
}
//
const handleTabChange = () => {
getChartData()
console.log('标签切换调用图表')
}
const getAdminData = async function () {
try {
const result = await API({ url: '/admin/userinfo', data: {} })
adminData.value = result
console.log('用户信息', adminData.value)
} catch (error) {
console.log('请求失败', error)
}
}
//
const getCardData = async () => {
try {
const response = await API({ url: '/workbench/getCard', data: {} })
workDataUpdateTime.value = response.updateTime
//
sumWow.value = response.sumWow.toFixed(2)
//
sumDaily.value = response.sumDaily.toFixed(2)
if (response && response.data) {
processData(response.data)
} else if (Array.isArray(response?.marketCards)) {
processData(response)
} else {
console.error('无效的API响应结构:', response)
}
} catch (error) {
console.error('获取卡片数据失败:', error)
}
}
const workDataUpdateTime = ref(null)
//
const activeTimeRange = ref('')
//
const handleDatePickerChange = () => {
activeTimeRange.value = ''
}
onMounted(async () => {
await getAdminData()
await getCardData()
await getMarkets()
getYear()
window.addEventListener('resize', () => {
chartInstance.resize()
})
})
onUnmounted(() => {
destroyChart()
})
</script>
<style scoped>
.top {
height: 5.5vh;
width: 80vw;
display: flex;
margin-bottom: 0.5vh;
.text {
margin-left: 2vw;
width: 20vw;
display: flex;
align-items: center;
font-size: 18px;
}
}
.card {
height: 28vh;
margin-bottom: 0.5vh;
display: flex;
justify-content: center;
}
.graph {
width: 100%;
display: flex;
height: 64%;
.condition {
width: 100%;
height: 1%;
display: flex;
align-items: center;
.stats {
display: flex;
align-items: center;
width: 35vw;
font-size: 15px;
}
}
.graph-content {
flex: 1;
height: auto;
display: flex;
.left {
width: 70%;
height: auto;
}
.right {
flex: 1;
padding: 0.5vw 2vh;
}
}
}
.center-card {
display: flex;
justify-content: center;
align-items: center;
}
.margin-bottom {
margin-bottom: 0.5vh;
}
.card-item {
width: 25%;
height: 28vh;
display: flex;
flex-direction: column;
justify-content: center;
margin-right: 0.25vw;
}
.card-title {
font-weight: bold;
margin-bottom: 1vh;
display: flex;
justify-content: center;
align-items: center;
}
.card-large {
font-weight: bold;
font-size: 16px;
text-align: center;
margin-bottom: 15px;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
Loading…
Cancel
Save