Browse Source

新增金豆界面创建初始化

zhangrenyuan/feature-20250728113353-金币前端三期
ZhangYong 3 weeks ago
parent
commit
8c99d3fc8a
  1. 3
      .env.development
  2. 38
      src/router/index.js
  3. 69
      src/utils/menuTreePermission.js
  4. 9
      src/utils/menuUtils.js
  5. 28
      src/views/audit/rechargeAudit.vue
  6. 11
      src/views/consume/addBeanConsume.vue
  7. 11
      src/views/consume/articleVideo.vue
  8. 136
      src/views/consume/beanConsume.vue
  9. 11
      src/views/consume/dieHardFan.vue
  10. 11
      src/views/consume/liveStream.vue
  11. 68
      src/views/recharge/coinRechargeDetail.vue

3
.env.development

@ -3,9 +3,10 @@
# VITE_API_BASE='http://18.143.76.3:10704/' # VITE_API_BASE='http://18.143.76.3:10704/'
# VITE_API_BASE='http://192.168.9.52:10705/' # VITE_API_BASE='http://192.168.9.52:10705/'
VITE_UPLOAD_URL=http://39.101.133.168:8828/hljw/api/aws/upload VITE_UPLOAD_URL=http://39.101.133.168:8828/hljw/api/aws/upload
VITE_API_BASE='http://192.168.9.28:8081/'
# VITE_API_BASE='http://192.168.9.28:8081/' # VITE_API_BASE='http://192.168.9.28:8081/'
# sunjiabei # sunjiabei
# VITE_API_BASE='http://192.168.9.28:8081/' # VITE_API_BASE='http://192.168.9.28:8081/'
# VITE_API_BASE='http://192.168.5.92:8081/' # VITE_API_BASE='http://192.168.5.92:8081/'
# zhangyong # zhangyong
VITE_API_BASE='http://192.168.3.83:8081/'
# VITE_API_BASE='http://192.168.3.83:8081/'

38
src/router/index.js

@ -79,6 +79,44 @@ const routes = [
} }
] ]
}, },
// 金豆消耗
{
path: '/beanConsume',
name: "beanConsume",
component: () => import("../views/consume/beanConsume.vue"),
meta: {permissionId: 6},
children: [
// 金豆新增消耗
{
path: 'add',
name: "addBeanConsume",
component: () => import("../views/consume/addBeanConsume.vue"),
meta: {permissionId: 23} // 对应"提交金豆消耗"id=?
},
// 直播
{
path: 'live',
name: "liveStream",
component: () => import("../views/consume/liveStream.vue"),
meta: {permissionId: 24} // 对应"直播"id=?
},
// 铁粉
{
path: 'fan',
name: "dieHardFan",
component: () => import("../views/consume/dieHardFan.vue"),
meta: {permissionId: 25} // 对应"铁粉"id=?
},
// 文章视频
{
path: 'article',
name: "articleVideo",
component: () => import("../views/consume/articleVideo.vue"),
meta: {permissionId: 26} // 对应"文章视频"id=?
}
]
},
// 汇率管理 // 汇率管理
{ {

69
src/utils/menuTreePermission.js

@ -0,0 +1,69 @@
// 菜单权限映射(按 menu_type 排序)
export const permissionMapping = {
System_Management: 1, // 系统管理
// menu_type 2: 主功能菜单
Workbench: 2, // 工作台
Financial_Audit: 3, // 财务审核
Exchange_Rate_Management: 4, // 汇率管理
Recharge_Management: 5, // 充值管理
Consumption_Management: 6, // 消耗管理
Refund_Management: 7, // 退款管理
Customer_Account_Details: 8, // 客户账户明细
Permission_Management: 9, // 权限管理
// menu_type 3: 子功能菜单
Gold_Coin_Recharge: 34, // 金币充值
Gold_Coin_Consumption: 35, // 金币消耗
Gold_Coin_Refund: 37, // 金币退款
Gold_Coin_Audit: 40, // 金币审核
Golden_Bean_Recharge: 41, // 金豆充值
Golden_Bean_Consumption: 42, // 金豆消耗
Golden_Bean_Audit: 43, // 金豆审核
Gold_Coin_Customer_Account_Details: 44, // 金币客户账户明细
Golden_Bean_Customer_Account_Details: 45, // 金豆客户账户明细
// menu_type 4: 功能操作权限
Workbench_Display: 10, // 工作台展示
View_Recharge_Audit: 11, // 查看充值审核
Recharge_Approval: 12, // 充值审批
View_Refund_Audit: 13, // 查看退款审核
Refund_Approval: 14, // 退款审批
Exchange_Rate_View: 15, // 汇率查看
Exchange_Rate_Modification: 16, // 汇率修改
Submit_Gold_Coin_Recharge: 17, // 提交金币充值
View_Gold_Coin_Recharge_Details: 18, // 查看金币充值明细
Submit_Gold_Coin_Consumption: 19, // 提交金币消耗
View_Gold_Coin_Consumption_Details: 20, // 查看金币消耗明细
Submit_Gold_Coin_Refund: 21, // 提交金币退款
View_Gold_Coin_Refund_Details: 22, // 查看金币退款明细
View_Gold_Coin_Details: 23, // 查看金币明细
View_Gold_Coin_Balance: 24, // 查看金币余额
View_Permission: 25, // 查看权限
Add_User: 26, // 新增用户
Change_Status: 27, // 改变状态
Modify_Permission: 28, // 修改权限
Delete_User: 29, // 删除用户
View_Role: 30, // 查看角色
Edit_Role: 36, // 编辑角色
Recharge_Audit: 31, // 充值审核
Refund_Audit: 32, // 退款审核
};
// 递归查找菜单中是否存在目标id
export const findMenuById = (menuList, targetId) => {
for (const menu of menuList) {
if (menu.id === targetId) {
return true; // 找到目标菜单
}
// 如果有子菜单,递归查找
if (menu.children && menu.children.length > 0) {
const found = findMenuById(menu.children, targetId);
if (found) return true;
}
}
return false;
};

9
src/utils/menuUtils.js

@ -7,7 +7,7 @@ export function filterMenu(menuList) {
...menu, ...menu,
children: menu.children ? filterMenu(menu.children) : [] children: menu.children ? filterMenu(menu.children) : []
})) }))
.sort((a, b) => a.priority - b.priority); // 按 id 升序
.sort((a, b) => a.id - b.id); // 按 id 升序
} }
// 辅助函数:查找第一个可访问的菜单项 // 辅助函数:查找第一个可访问的菜单项
@ -21,6 +21,7 @@ export function findFirstAccessibleMenu(menuList) {
} else if (menu.menuType === 2) { // 目录 } else if (menu.menuType === 2) { // 目录
return menu return menu
} else if (menu.menuType === 3) { // 菜单 } else if (menu.menuType === 3) { // 菜单
console.log('菜单:', menu)
return menu return menu
} }
} }
@ -33,24 +34,30 @@ export const getRoutePath = (menu) => {
const routeMap = { const routeMap = {
'工作台': '/workspace', '工作台': '/workspace',
'财务审核': '/audit',
'金币审核': '/audit', '金币审核': '/audit',
'金豆审核': '/beanAudit', '金豆审核': '/beanAudit',
'汇率管理': '/rate', '汇率管理': '/rate',
'充值管理': '/coinRecharge',
'金币充值': '/coinRecharge', '金币充值': '/coinRecharge',
'金豆充值': '/beanRecharge', '金豆充值': '/beanRecharge',
'消耗管理': '/coinConsume',
'金币消耗': '/coinConsume', '金币消耗': '/coinConsume',
'金豆消耗': '/beanConsume', '金豆消耗': '/beanConsume',
'退款管理': '/coinRefund',
'金币退款': '/coinRefund', '金币退款': '/coinRefund',
// '金豆退款': '/beanRefund', // '金豆退款': '/beanRefund',
'权限管理': '/permissions', '权限管理': '/permissions',
'客户账户明细': '/usergold',
'金币客户账户明细': '/usergold', '金币客户账户明细': '/usergold',
'金豆客户账户明细': '/userbean', '金豆客户账户明细': '/userbean',
}; };

28
src/views/audit/rechargeAudit.vue

@ -36,9 +36,15 @@
<el-text size="large" style="width: 80px">充值时间</el-text> <el-text size="large" style="width: 80px">充值时间</el-text>
<el-date-picker v-model="getTime" type="datetimerange" range-separator="" start-placeholder="起始时间" <el-date-picker v-model="getTime" type="datetimerange" range-separator="" start-placeholder="起始时间"
end-placeholder="结束时间" style="width: 400px" @change="handleDatePickerChange"/> end-placeholder="结束时间" style="width: 400px" @change="handleDatePickerChange"/>
<el-button @click="getToday()" style="margin-left: 10px" :type="activeTimeRange === 'today' ? 'primary' : ''"> </el-button>
<el-button @click="getYesterday()" style="margin-left: 10px" :type="activeTimeRange === 'yesterday' ? 'primary' : ''"> </el-button>
<el-button @click="get7Days()" style="margin-left: 10px" :type="activeTimeRange === '7days' ? 'primary' : ''"> 近7天</el-button>
<el-button @click="getToday()" style="margin-left: 10px"
:type="activeTimeRange === 'today' ? 'primary' : ''">
</el-button>
<el-button @click="getYesterday()" style="margin-left: 10px"
:type="activeTimeRange === 'yesterday' ? 'primary' : ''">
</el-button>
<el-button @click="get7Days()" style="margin-left: 10px"
:type="activeTimeRange === '7days' ? 'primary' : ''"> 近7天
</el-button>
<el-button @click="resetSearch" type="success">重置</el-button> <el-button @click="resetSearch" type="success">重置</el-button>
<el-button type="primary" @click="handleSearch">查询</el-button> <el-button type="primary" @click="handleSearch">查询</el-button>
</div> </div>
@ -95,7 +101,9 @@
<el-table-column prop="payModel" label="支付方式" width="110px"/> <el-table-column prop="payModel" label="支付方式" width="110px"/>
<el-table-column prop="voucher" label="支付凭证" width="110px"> <el-table-column prop="voucher" label="支付凭证" width="110px">
<template #default="scope"> <template #default="scope">
<div v-if="scope.row.voucher" style="display: flex; justify-content: center; align-items: center; cursor: pointer;" @click="previewImage(scope.row.voucher)">
<div v-if="scope.row.voucher"
style="display: flex; justify-content: center; align-items: center; cursor: pointer;"
@click="previewImage(scope.row.voucher)">
<img :src="scope.row.voucher" alt="支付凭证" style="width: auto; height: 40px;"> <img :src="scope.row.voucher" alt="支付凭证" style="width: auto; height: 40px;">
</div> </div>
<div v-else style="display: flex; justify-content: center; align-items: center; height: 40px;">--</div> <div v-else style="display: flex; justify-content: center; align-items: center; height: 40px;">--</div>
@ -173,6 +181,8 @@ import {ElMessage} from 'element-plus'
import request from '@/util/http' import request from '@/util/http'
import moment from 'moment' import moment from 'moment'
import API from '@/util/http' import API from '@/util/http'
import {useAdminStore} from "@/store/index.js";
import {storeToRefs} from "pinia";
// //
const trimJwCode = () => { const trimJwCode = () => {
if (rechargeAudit.value.jwcode) { if (rechargeAudit.value.jwcode) {
@ -501,8 +511,14 @@ const handlePagination = (type, val) => {
getStats() getStats()
} }
const adminStore = useAdminStore();
const {menuTree} = storeToRefs(adminStore);
import {permissionMapping, findMenuById} from "@/utils/menuTreePermission.js"
// //
const handleApprove = async (row) => { const handleApprove = async (row) => {
if (findMenuById(menuTree.value, permissionMapping.Recharge_Approval)) {
try { try {
const params = { const params = {
orderCode: row.orderCode, orderCode: row.orderCode,
@ -518,6 +534,10 @@ const handleApprove = async (row) => {
console.error('审核通过失败', error) console.error('审核通过失败', error)
ElMessage.error('操作失败') ElMessage.error('操作失败')
} }
}else {
ElMessage.error('无权限')
}
} }
// //

11
src/views/consume/addBeanConsume.vue

@ -0,0 +1,11 @@
<script setup lang="ts">
</script>
<template>
<div>addBeanConsume</div>
</template>
<style scoped>
</style>

11
src/views/consume/articleVideo.vue

@ -0,0 +1,11 @@
<script setup lang="ts">
</script>
<template>
<div>article videos</div>
</template>
<style scoped>
</style>

136
src/views/consume/beanConsume.vue

@ -1,11 +1,137 @@
<script setup lang="ts">
<template>
<div>
<!-- 这里放置标签切换的按钮 -->
<el-button-group>
<!-- 切换后状态显示 primary 样式否则是默认样式 -->
<el-button
:type="activeTab === 'add' ? 'primary' : 'default'"
@click="goToAdd"
class="uniform-btn"
>
新增消耗
</el-button>
<el-button
:type="activeTab === 'live' ? 'primary' : 'default'"
@click="goToLive"
class="uniform-btn"
>
直播
</el-button>
<el-button
:type="activeTab === 'fan' ? 'primary' : 'default'"
@click="goToFan"
class="uniform-btn"
>
铁粉
</el-button>
<el-button
:type="activeTab === 'article' ? 'primary' : 'default'"
@click="goToArticle"
class="uniform-btn"
>
文章/视频
</el-button>
</el-button-group>
<!-- 渲染子路由组件 -->
<router-view></router-view>
</div>
</template>
</script>
<script setup>
import {onMounted, ref, watch} from 'vue';
import {useRouter, useRoute} from 'vue-router';
import {storeToRefs} from "pinia";
import {useAdminStore} from "@/store/index.js";
<template>
const router = useRouter();//
const route = useRoute();//
// activeTab
const activeTab = ref(route.name === 'liveStream' ? 'live' : 'add');
//coinConsumeDetaildetailadd
//coinConsumeadd
const adminStore = useAdminStore();
const {menuTree} = storeToRefs(adminStore);
</template>
const goToAdd = () => {
// activeTab add
activeTab.value = 'add';
router.push({name: 'addBeanConsume'});
};
<style scoped>
const goToLive = () => {
// activeTab live
activeTab.value = 'live';
router.push({name: 'liveStream'});
};
const goToFan = () => {
// activeTab fan
activeTab.value = 'fan';
router.push({name: 'dieHardFan'});
};
const goToArticle = () => {
// activeTab article
activeTab.value = 'article';
router.push({name: 'articleVideo'});
};
//
const navigateTo = (name) => {
activeTab.value = name;
if (name === 'add') {
router.push({name: 'addBeanConsume'});
} else if (name === 'live') {
router.push({name: 'liveStream'});
} else if (name === 'fan') {
router.push({name: 'dieHardFan'});
} else if (name === 'article') {
router.push({name: 'articleVideo'});
}
};
// menuName
const hasMenuPermission = (tree, targetName) => {
for (const node of tree) {
if (node.menuName === targetName) return true;
if (node.children && hasMenuPermission(node.children, targetName)) return true;
}
return false;
};
//
const getDefaultRoute = () => {
if (!menuTree.value) return 'add';
const hasRecharge = hasMenuPermission(menuTree.value, '提交金豆消耗');
return hasRecharge ? 'add' : 'live';
};
//
watch(() => route.name, (newName) => {
if (newName === 'add' || newName === 'live') {
activeTab.value = newName;
} else if (newName === 'beanConsume') {
const defaultRoute = getDefaultRoute();
navigateTo(defaultRoute);
}
});
//
onMounted(() => {
if (route.name === 'beanConsume') {
const defaultRoute = getDefaultRoute();
navigateTo(defaultRoute);
} else {
//
if (route.name === 'add' || route.name === 'live') {
activeTab.value = route.name;
}
}
});
</script>
<style scoped>
.uniform-btn {
width: 120px;
}
</style> </style>

11
src/views/consume/dieHardFan.vue

@ -0,0 +1,11 @@
<script setup lang="ts">
</script>
<template>
<div>die-hard fans</div>
</template>
<style scoped>
</style>

11
src/views/consume/liveStream.vue

@ -0,0 +1,11 @@
<script setup lang="ts">
</script>
<template>
<div>live stream</div>
</template>
<style scoped>
</style>

68
src/views/recharge/coinRechargeDetail.vue

@ -34,6 +34,25 @@ const handleDatePickerChange = () => {
const rechargeUser = ref({ const rechargeUser = ref({
adminId: adminData.value.id adminId: adminData.value.id
}) })
//
const selectedMarketPath = ref([])
//
const handleMarketChange = (value) => {
if(value && value.length > 0){
const lastValue = value[value.length - 1];
if (lastValue.endsWith('_all')) {
//
rechargeUser.value.market = lastValue.replace('_all', '');
} else {
//
rechargeUser.value.market = lastValue;
}
} else {
rechargeUser.value.market = ''
}
}
// //
const getObj = ref({ const getObj = ref({
pageNum: 1, pageNum: 1,
@ -116,25 +135,46 @@ const getActivity = async function () {
// //
} }
} }
//
//
//
const getArea = async function () { const getArea = async function () {
console.log('获取地区adminid', adminData.value) console.log('获取地区adminid', adminData.value)
try { try {
// POST // POST
const result = await API({ const result = await API({
url: '/general/adminMarkets',
url: '/market/selectMarket',
data: {account: adminData.value.account} data: {account: adminData.value.account}
}); });
// //
console.log('请求成功', result) console.log('请求成功', result)
//
const transformTree = (nodes) => {
return nodes.map(node => {
const children = node.children && node.children.length
? transformTree(node.children)
: null;
//
if (children) {
children.unshift({
value: `${node.name}_all`, //
label: '全部',
children: null
});
}
return {
value: node.name, //使
label: node.name, //
children
};
});
}
// //
market.value = result.data
console.log('地区', market.value)
market.value = transformTree(result.data)
console.log('转换后的地区', market.value)
} catch (error) { } catch (error) {
console.log('请求失败', error) console.log('请求失败', error)
//
} }
} }
@ -257,6 +297,7 @@ const reset = function () {
delete rechargeUser.value.activity delete rechargeUser.value.activity
delete rechargeUser.value.payPlatform delete rechargeUser.value.payPlatform
delete rechargeUser.value.market delete rechargeUser.value.market
selectedMarketPath .value = [] //
delete rechargeUser.value.startTime delete rechargeUser.value.startTime
delete rechargeUser.value.endTime delete rechargeUser.value.endTime
delete sortField.value delete sortField.value
@ -487,13 +528,26 @@ const getTagText = (state) => {
</el-select> </el-select>
</div> </div>
</el-col> </el-col>
<el-col :span="6">
<!-- <el-col :span="6">
<div class="head-card-element"> <div class="head-card-element">
<el-text class="mx-1" size="large">所属地区</el-text> <el-text class="mx-1" size="large">所属地区</el-text>
<el-select v-model="rechargeUser.market" placeholder="请选择所属地区" style="width: 180px" clearable> <el-select v-model="rechargeUser.market" placeholder="请选择所属地区" style="width: 180px" clearable>
<el-option v-for="item in market" :key="item" :label="item" :value="item"/> <el-option v-for="item in market" :key="item" :label="item" :value="item"/>
</el-select> </el-select>
</div> </div>
</el-col> -->
<el-col :span="6">
<div class="head-card-element">
<el-text class="mx-1" size="large">所属地区</el-text>
<el-cascader
v-model="selectedMarketPath"
:options="market"
placeholder="请选择所属地区"
clearable
style="width:180px"
@change="handleMarketChange"
/>
</div>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<div class="head-card-element"> <div class="head-card-element">

Loading…
Cancel
Save