feat(角色管理): 优化菜单树处理逻辑并添加注释
重构菜单树监听逻辑,添加详细注释说明处理流程。将叶子节点收集和菜单分类逻辑分离,提高代码可读性。 feat(企业余额): 重构用户余额页面布局和搜索功能 - 添加用户总余额卡片展示 - 合并姓名和手机号搜索框为统一搜索框 - 优化金额显示格式,添加千位分隔符 - 重构页面样式,采用卡片式布局
This commit is contained in:
parent
c73260a207
commit
28789ce95d
|
@ -1,6 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { PureTableBar } from "@/components/RePureTableBar";
|
|
||||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||||
import { getQyUserListApi, getTotalBalanceApi, QyUserDTO, QyUserQuery } from "@/api/qy/qyUser";
|
import { getQyUserListApi, getTotalBalanceApi, QyUserDTO, QyUserQuery } from "@/api/qy/qyUser";
|
||||||
import Search from "@iconify-icons/ep/search";
|
import Search from "@iconify-icons/ep/search";
|
||||||
|
@ -17,9 +17,10 @@ const formRef = ref();
|
||||||
const tableRef = ref();
|
const tableRef = ref();
|
||||||
|
|
||||||
// 搜索表单
|
// 搜索表单
|
||||||
const searchFormParams = ref<QyUserQuery>({
|
const searchFormParams = ref<QyUserQuery & { search?: string }>({
|
||||||
name: null,
|
name: null,
|
||||||
mobile: null,
|
mobile: null,
|
||||||
|
search: null,
|
||||||
corpid: wxStore.corpid, // 企业ID
|
corpid: wxStore.corpid, // 企业ID
|
||||||
balancePage: 1
|
balancePage: 1
|
||||||
});
|
});
|
||||||
|
@ -35,10 +36,19 @@ const loading = ref(false);
|
||||||
const dataList = ref<QyUserDTO[]>([]);
|
const dataList = ref<QyUserDTO[]>([]);
|
||||||
const totalBalance = ref(0);
|
const totalBalance = ref(0);
|
||||||
|
|
||||||
|
const balanceData = ref([
|
||||||
|
{
|
||||||
|
name: '用户总余额', value:
|
||||||
|
totalBalance.value.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
||||||
|
+ '元'
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
// 获取用户余额列表
|
// 获取用户余额列表
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
handleSearchInput(searchFormParams.value.search);
|
||||||
const { data } = await getQyUserListApi({
|
const { data } = await getQyUserListApi({
|
||||||
...searchFormParams.value,
|
...searchFormParams.value,
|
||||||
pageSize: pagination.value.pageSize,
|
pageSize: pagination.value.pageSize,
|
||||||
|
@ -51,6 +61,19 @@ const getList = async () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSearchInput = (value) => {
|
||||||
|
// 手机号正则
|
||||||
|
const phoneRegex = /^1[3-9]\d{9}$/;
|
||||||
|
|
||||||
|
if (phoneRegex.test(value)) {
|
||||||
|
searchFormParams.value.mobile = value;
|
||||||
|
searchFormParams.value.name = '';
|
||||||
|
} else {
|
||||||
|
searchFormParams.value.name = value;
|
||||||
|
searchFormParams.value.mobile = '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 搜索
|
// 搜索
|
||||||
const onSearch = () => {
|
const onSearch = () => {
|
||||||
pagination.value.currentPage = 1;
|
pagination.value.currentPage = 1;
|
||||||
|
@ -71,6 +94,9 @@ const getTotalBalance = async () => {
|
||||||
try {
|
try {
|
||||||
const { data } = await getTotalBalanceApi(wxStore.corpid);
|
const { data } = await getTotalBalanceApi(wxStore.corpid);
|
||||||
totalBalance.value = data;
|
totalBalance.value = data;
|
||||||
|
balanceData.value[0].value = data.toLocaleString('en-US',
|
||||||
|
{ minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
||||||
|
+ '元';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取总余额失败:', error);
|
console.error('获取总余额失败:', error);
|
||||||
}
|
}
|
||||||
|
@ -83,7 +109,18 @@ getList().then(() => getTotalBalance());
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<el-form ref="formRef" :inline="true" :model="searchFormParams"
|
<el-row :gutter="12" class="data-section">
|
||||||
|
<!-- 商店数据 -->
|
||||||
|
<el-col :span="4" v-for="item in balanceData" :key="item.name">
|
||||||
|
<el-card shadow="never" :body-style="{ padding: ' 20px 0' }" class="todo-card">
|
||||||
|
<div class="todo-content">
|
||||||
|
<div class="todo-name">{{ item.name }}</div>
|
||||||
|
<div class="todo-count">{{ item.value }}</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<!-- <el-form ref="formRef" :inline="true" :model="searchFormParams"
|
||||||
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px]">
|
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px]">
|
||||||
<el-form-item label="姓名:" prop="name">
|
<el-form-item label="姓名:" prop="name">
|
||||||
<el-input @keydown.enter.prevent="onSearch" v-model="searchFormParams.name" placeholder="请输入姓名" clearable
|
<el-input @keydown.enter.prevent="onSearch" v-model="searchFormParams.name" placeholder="请输入姓名" clearable
|
||||||
|
@ -101,27 +138,87 @@ getList().then(() => getTotalBalance());
|
||||||
重置
|
重置
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
</el-form> -->
|
||||||
|
|
||||||
|
<div class="table-container">
|
||||||
|
<el-form ref="formRef" :inline="true" :model="searchFormParams" class="search-form bg-bg_color w-[99/100]">
|
||||||
|
<el-form-item prop="search">
|
||||||
|
<el-input v-model="searchFormParams.search" placeholder="请输入姓名/手机号" clearable class="!w-[300px]"
|
||||||
|
@keydown.enter.prevent="onSearch" @change="handleSearchInput" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" :icon="useRenderIcon(Search)" @click="onSearch">
|
||||||
|
搜索
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<PureTableBar :title="`用户总余额: ${totalBalance}元`" @refresh="getList">
|
|
||||||
<el-table ref="tableRef" v-loading="loading" :data="dataList" border>
|
<el-table ref="tableRef" v-loading="loading" :data="dataList" border>
|
||||||
<el-table-column label="用户ID" prop="id" width="80" />
|
<el-table-column label="用户ID" prop="id" width="80" />
|
||||||
<el-table-column label="姓名" prop="name" width="100" />
|
<el-table-column label="姓名" prop="name" width="200" />
|
||||||
<el-table-column label="余额" prop="balance" width="130">
|
<el-table-column label="余额" prop="balance" width="230">
|
||||||
<template #default="{ row }">{{ row.balance || 0 }}元</template>
|
<template #default="{ row }">{{ row.balance.toLocaleString('en-US', { minimumFractionDigits: 0, maximumFractionDigits: 2 }) || 0 }}元</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="手机号" prop="mobile" />
|
<el-table-column label="手机号" prop="mobile" />
|
||||||
<!-- <el-table-column label="所属部门" prop="department" /> -->
|
<!-- <el-table-column label="所属部门" prop="department" /> -->
|
||||||
</el-table>
|
</el-table>
|
||||||
<el-pagination v-model:current-page="pagination.currentPage" v-model:page-size="pagination.pageSize"
|
<el-pagination class="table-pagination" v-model:current-page="pagination.currentPage"
|
||||||
:page-sizes="[10, 20, 50]" layout="total, sizes, prev, pager, next, jumper" :total="pagination.total"
|
v-model:page-size="pagination.pageSize" :page-sizes="[10, 20, 50]"
|
||||||
@size-change="handlePaginationChange" @current-change="handlePaginationChange" />
|
layout="total, sizes, prev, pager, next, jumper" :total="pagination.total" @size-change="handlePaginationChange"
|
||||||
</PureTableBar>
|
@current-change="handlePaginationChange" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.main {
|
.main {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-section {
|
||||||
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
.todo-card {
|
||||||
|
height: 100%;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
.todo-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: start;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 10px 0 10px 26px;
|
||||||
|
|
||||||
|
.todo-name {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-icon {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
margin-top: 12px;
|
||||||
|
background-color: var(--el-bg-color);
|
||||||
|
border-radius: 4px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.el-table {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-pagination {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -102,35 +102,51 @@ async function getRoleInfo(type: "add" | "update", row?: RoleDTO) {
|
||||||
}
|
}
|
||||||
const processedMenuOptions = ref<{ categoryName: string; items: MenuDTO[] }[]>([]);
|
const processedMenuOptions = ref<{ categoryName: string; items: MenuDTO[] }[]>([]);
|
||||||
|
|
||||||
|
// 监听菜单树变化,处理菜单选项结构
|
||||||
watch(menuTree, (val) => {
|
watch(menuTree, (val) => {
|
||||||
|
// 初始化分类数组
|
||||||
const categories = [];
|
const categories = [];
|
||||||
|
|
||||||
|
// 收集叶子节点(没有子菜单的节点)
|
||||||
const collectLeaves = (menu: MenuDTO) => {
|
const collectLeaves = (menu: MenuDTO) => {
|
||||||
if (!menu.children) return [];
|
if (!menu.children) return [];
|
||||||
|
// 过滤出没有子菜单的子节点
|
||||||
return menu.children.filter(child => child.children?.length ? false : true);
|
return menu.children.filter(child => child.children?.length ? false : true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 递归收集菜单项
|
||||||
const collectMenus = (menuOption: MenuDTO) => {
|
const collectMenus = (menuOption: MenuDTO) => {
|
||||||
|
// 获取当前菜单的叶子节点
|
||||||
const leaves = collectLeaves(menuOption);
|
const leaves = collectLeaves(menuOption);
|
||||||
|
// 将当前菜单项添加到叶子节点数组开头
|
||||||
leaves.unshift(menuOption);
|
leaves.unshift(menuOption);
|
||||||
|
|
||||||
|
// 如果有叶子节点,则创建分类
|
||||||
if (leaves.length) {
|
if (leaves.length) {
|
||||||
categories.push({
|
categories.push({
|
||||||
categoryName: menuOption.menuName,
|
categoryName: menuOption.menuName, // 使用菜单名作为分类名
|
||||||
items: leaves
|
items: leaves // 包含当前菜单及其叶子节点
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理有子菜单的节点
|
||||||
if (menuOption.children?.length) {
|
if (menuOption.children?.length) {
|
||||||
|
// 筛选出还有子菜单的子节点
|
||||||
const lastMenu = menuOption.children.filter(child => child.children?.length);
|
const lastMenu = menuOption.children.filter(child => child.children?.length);
|
||||||
|
// 递归处理这些子节点
|
||||||
lastMenu.forEach(child => {
|
lastMenu.forEach(child => {
|
||||||
collectMenus(child);
|
collectMenus(child);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 遍历菜单树,开始收集菜单项
|
||||||
val.forEach(menuOption => {
|
val.forEach(menuOption => {
|
||||||
collectMenus(menuOption);
|
collectMenus(menuOption);
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log("categories", categories); // 输出处理后的菜单选项结构,方便调试和查看结构
|
console.log("categories", categories); // 输出处理后的菜单选项结构,方便调试和查看结构
|
||||||
|
// 更新处理后的菜单选项
|
||||||
processedMenuOptions.value = categories;
|
processedMenuOptions.value = categories;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue