20260808172946

This commit is contained in:
oneao committed 2026-08-08 17:29:51 +08:00
1 parent 4fbfea82f2
commit d72ca4193d
293 files changed
+37523 -73467

No files matched your search

+5 -5
View File
@@ -87,23 +87,23 @@ watch(
.slide-enter-active,
.slide-leave-active {
transition:
opacity 0.24s ease,
transform 0.24s ease;
opacity 180ms cubic-bezier(0.22, 1, 0.36, 1),
transform 180ms cubic-bezier(0.22, 1, 0.36, 1);
}
.slide-enter-from {
opacity: 0;
transform: translateX(18px);
transform: translateY(6px);
}
.slide-leave-to {
opacity: 0;
transform: translateX(-18px);
transform: translateY(-3px);
}
.slide-enter-to,
.slide-leave-from {
opacity: 1;
transform: translateX(0);
transform: translateY(0);
}
</style>
+39 -38
View File
@@ -1,25 +1,39 @@
<template>
<el-sub-menu v-if="hasChildren" :index="menuIndex" :class="levelClass">
<el-sub-menu
v-if="hasChildren"
:index="item.id"
class="g3-sub-menu"
:class="{
'level-0': props.level === 0,
}"
:style="
props.level > 0
? {
'--g3-indent': `${props.level * 20}px`,
}
: {}
"
>
<template #title>
<el-icon v-if="item.icon">
<G3Icon :icon="item.icon" />
</el-icon>
<span class="menu-label">{{ item.label }}</span>
</template>
<MenuItem
v-for="(child, idx) in item.children"
:key="child.path || idx"
v-for="child in item.children"
:key="child.id"
:item="child"
:parent-index="menuIndex + '-' + idx"
:parent-index="child.id"
:level="props.level + 1"
/>
</el-sub-menu>
<el-menu-item
v-else
:index="item.path"
:route="{
path: item.path,
:index="item.id"
class="g3-menu-item"
:style="{
'--g3-indent': `${props.level * 20}px`,
}"
>
<el-icon v-if="item.icon">
@@ -28,7 +42,6 @@
<span class="menu-label">{{ item.label }}</span>
</el-menu-item>
</template>
<script setup lang="js">
import { computed } from "vue";
import G3Icon from "@/components/g3-icon/index.vue";
@@ -36,36 +49,14 @@ import G3Icon from "@/components/g3-icon/index.vue";
const props = defineProps({
item: { type: Object, required: true },
parentIndex: { type: String, default: "0" },
level: {
type: Number,
default: 0,
},
});
const hasChildren = props.item.children && props.item.children.length > 0;
// 父菜单没有 path,用索引生成唯一 index,子菜单 path 就用真实路由
const menuIndex = props.item.path || props.parentIndex;
// 一级、二级 class
const levelClass = computed(() => {
if (!hasChildren) return "";
const level = props.parentIndex.split("-").length;
// 一级菜单
if (level === 1) {
// 判断子节点是否还有 children
const childHasChildren = props.item.children.some(
(child) => child.children && child.children.length > 0,
);
// 子节点都是叶子节点,才加 g3level1
return childHasChildren ? "" : "g3level1";
}
// 二级菜单,只要有子菜单就加 g3level2
if (level === 2) {
return "g3level2";
}
return "";
const hasChildren = computed(() => {
return props.item.children && props.item.children.length > 0;
});
</script>
@@ -79,4 +70,14 @@ const levelClass = computed(() => {
width: 16px;
height: 16px;
}
:deep(.g3-sub-menu > .el-sub-menu__title) {
padding-left: 20px;
}
:deep(.g3-sub-menu:not(.level-0) > .el-sub-menu__title) {
padding-left: calc(30px + var(--g3-indent)) !important;
}
:deep(.el-menu-item.g3-menu-item) {
padding-left: calc(30px + var(--g3-indent)) !important;
}
</style>
@@ -0,0 +1,83 @@
<template>
<!-- <el-sub-menu v-if="hasChildren" :index="menuIndex" :class="levelClass"> -->
<el-sub-menu v-if="hasChildren" :index="menuIndex">
<template #title>
<el-icon v-if="item.icon">
<G3Icon :icon="item.icon" />
</el-icon>
<span class="menu-label">{{ item.label }}</span>
</template>
<MenuItem
v-for="(child, idx) in item.children"
:key="child.path || idx"
:item="child"
:parent-index="menuIndex + '-' + idx"
/>
</el-sub-menu>
<el-menu-item
v-else
:index="item.path"
:route="{
path: item.path,
}"
>
<el-icon v-if="item.icon">
<G3Icon :icon="item.icon" />
</el-icon>
<span class="menu-label">{{ item.label }}</span>
</el-menu-item>
</template>
<script setup lang="js">
import { computed } from "vue";
import G3Icon from "@/components/g3-icon/index.vue";
const props = defineProps({
item: { type: Object, required: true },
parentIndex: { type: String, default: "0" },
});
const hasChildren = props.item.children && props.item.children.length > 0;
// 父菜单没有 path,用索引生成唯一 index,子菜单 path 就用真实路由
const menuIndex = props.item.path || props.parentIndex;
// 一级、二级 class
const levelClass = computed(() => {
if (!hasChildren) return "";
const level = props.parentIndex.split("-").length;
// 一级菜单
if (level === 1) {
// 判断子节点是否还有 children
const childHasChildren = props.item.children.some(
(child) => child.children && child.children.length > 0,
);
// 子节点都是叶子节点,才加 g3level1
return childHasChildren ? "" : "g3level1";
}
// 二级菜单,只要有子菜单就加 g3level2
if (level === 2) {
return "g3level2";
}
return "";
});
</script>
<style scoped>
.menu-label {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
:deep(.el-icon svg) {
width: 16px;
height: 16px;
}
</style>
@@ -0,0 +1,48 @@
<template>
<el-sub-menu v-if="hasChildren" :index="item.id">
<template #title>
<el-icon v-if="item.icon">
<G3Icon :icon="item.icon" />
</el-icon>
<span class="menu-label">{{ item.label }}</span>
</template>
<MenuItem
v-for="child in item.children"
:key="child.id"
:item="child"
:parent-index="child.id"
/>
</el-sub-menu>
<el-menu-item v-else :index="item.id">
<el-icon v-if="item.icon">
<G3Icon :icon="item.icon" />
</el-icon>
<span class="menu-label">{{ item.label }}</span>
</el-menu-item>
</template>
<script setup lang="js">
import { computed } from "vue";
import G3Icon from "@/components/g3-icon/index.vue";
const props = defineProps({
item: { type: Object, required: true },
parentIndex: { type: String, default: "0" },
});
const hasChildren = props.item.children?.length > 0;
</script>
<style scoped>
.menu-label {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
:deep(.el-icon svg) {
width: 16px;
height: 16px;
}
</style>
+337 -161
View File
@@ -1,13 +1,16 @@
<script setup>
import { ref, computed, onMounted } from "vue";
import { useRoute, useRouter } from "vue-router";
import axios from "@/utils/axios";
import G3Icon from "@/components/g3-icon/index.vue";
import MenuItem from "./menu-item.vue";
import { getDataBySql } from "@/utils/doSql";
import { useUserStore } from "@/store/user";
import { useMenuStore } from "@/store/menu";
const route = useRoute();
const router = useRouter();
const userStore = useUserStore();
/*
const props = defineProps({
collapse: {
type: Boolean,
@@ -17,136 +20,172 @@ const props = defineProps({
type: Boolean,
default: false,
},
}); */
const props = defineProps({
item: { type: Object, required: true },
parentIndex: { type: String, default: "0" },
});
const emits = defineEmits(["collapse"]);
const title = ref("G3后台管理系统");
// 方便以后根据不同的权限显示不同的菜单
const selectedMenuKey = computed(() => route.fullPath);
// 首页
const homeobj = {
id: "__home__",
label: "首页",
path: "/home",
icon: "svghome",
};
// 业务管理
const bsobj = {
label: "业务管理",
icon: "ri:folder-chart-line",
children: [
{ label: "业务管理", path: "/business" },
{ label: "排车列表", path: "/business/listcontainer" },
],
};
// 财务管理
const cwobj = {
label: "财务管理",
icon: "ri:wallet-line",
children: [
{
label: "应收管理",
children: [
{ label: "应收费用列表", path: "/cw/ys/list" },
{ label: "应收账单", path: "/cw/ys/listbill" },
{ label: "应收发票", path: "/cw/ys/listinvoice" },
{ label: "收款管理", path: "/cw/ys/listskd" },
],
},
{
label: "应付管理",
children: [
{ label: "应付费用列表", path: "/cw/yf/list" },
{ label: "应付账单", path: "/cw/yf/listbill" },
{ label: "应付发票", path: "/cw/yf/listinvoice" },
{ label: "付款管理", path: "/cw/yf/listfkd" },
],
},
{
label: "财务辅助",
children: [
{ label: "日常费用", path: "/cw/assistance/daily-expenses" },
{ label: "收付账户", path: "/cw/assistance/payment-account" },
],
},
],
};
const tjobj = {
label: "统计分析",
icon: "ri:bar-chart-box-line",
children: [
{
label: "收款统计分析",
children: [
{ label: "应收未收明细", path: "/tj/ystj/tj_nots" },
{ label: "应收未收按单位汇总", path: "/tj/ystj/tj_notsbydw" },
{ label: "未收款按业务员汇总", path: "/tj/ystj/tj_notsbysales" },
{ label: "收款明细统计", path: "/tj/ystj/tj_cwskxz" },
],
},
{
label: "付款统计分析",
children: [
{ label: "应付未付明细", path: "/tj/yftj/tj_alone_notf" },
{ label: "应付未付按单位汇总", path: "/tj/yftj/tj_notfbydw" },
{ label: "付款明细统计", path: "/tj/yftj/tj_cwfkxz" },
],
},
{
label: "利润统计分析",
children: [
{ label: "利润分析单票明细", path: "/tj/lrtj/tj_lr_module_id" },
{ label: "利润分析按业务员", path: "/tj/lrtj/tj_lrbysales" },
{ label: "利润分析按客户", path: "/tj/lrtj/tj_lrbycustomer" },
],
},
],
};
const mgobj = {
label: "管理中心",
icon: "ri:government-line",
children: [
{ label: "审批中心", path: "/shcenter" },
{ label: "合同管理", path: "/partners/khht" },
{ label: "运费模版", path: "/pricelist/list" },
{ label: "企业公告", path: "/system/oa-management/companyAnnouncement" },
{ label: "提醒设置", path: "/system/oa-management/userReminder" },
{ label: "结算汇率", path: "/partners/hl" },
{ label: "审批设置", path: "/system/approvalSettings" },
],
};
const baseobj = {
label: "基础数据",
icon: "ri:database-line",
children: [
{ label: "合作伙伴", path: "/partners" },
{ label: "联系人", path: "/partners/contact" },
{ label: "其他数据", path: "/base/other" },
],
};
const sysobj = {
label: "系统管理",
icon: "ri:settings-line",
children: [{ label: "权限管理", path: "/system/permission" }],
};
const g3modobj = {
id: "sys_module",
label: "模块设置",
icon: "ri:settings-line",
expanded: false,
children: [
{ id: "sys_moduleset", label: "模块设置", path: "/system/moduleSettings" },
{ id: "sys_field", label: "字段配置", path: "/system/field" },
{ id: "sys_Settings", label: "查询设置", path: "/system/statisticalSettings" },
{ id: "sys_mappingConfig", label: "映射配置", path: "/system/mappingConfig" },
{ id: "sys_autoId", label: "自动编码", path: "/system/autoId" },
{ id: "sys_reminder", label: "提醒设置", path: "/system/oa-management/userReminder" },
{ id: "sys_logdatas", label: "数据日志", path: "/system/logdatas" },
{ id: "sys_loginrecords", label: "登陆日志", path: "/system/loginrecords" },
],
};
// 根据当前路由在菜单树中找到对应节点 id,用于 el-menu 的 default-active 高亮
const selectedMenuKey = computed(() => {
// 1) 优先通过 path 精确匹配
let node = findNodeByPath(menuList.value, route.path);
if (node?.id) return node.id;
// 2) fallback: /tj 等通用路由,通过 query.moduleid 匹配
if (route.query.moduleid) {
node = findNodeByModuleId(menuList.value, route.query.moduleid);
if (node?.id) return node.id;
}
return "";
});
const menuList = ref([
// 首页
{
label: "首页",
path: "/home",
icon: "svghome",
},
homeobj,
// 业务管理
{
label: "业务管理",
icon: "ri:folder-chart-line",
children: [
{ label: "业务管理", path: "/business" },
{ label: "排车列表", path: "/business/listcontainer" },
],
},
// bsobj,
// 财务管理
{
label: "财务管理",
icon: "ri:wallet-line",
children: [
{
label: "应收管理",
children: [
{ label: "应收费用列表", path: "/cw/ys/list" },
{ label: "应收账单", path: "/cw/ys/listbill" },
{ label: "应收发票", path: "/cw/ys/listinvoice" },
{ label: "收款管理", path: "/cw/ys/listskd" },
],
},
{
label: "应付管理",
children: [
{ label: "应付费用列表", path: "/cw/yf/list" },
{ label: "应付账单", path: "/cw/yf/listbill" },
{ label: "应付发票", path: "/cw/yf/listinvoice" },
{ label: "付款管理", path: "/cw/yf/listfkd" },
],
},
{
label: "日常费用",
children: [
{ label: "日常费用", path: "/cw/assistance/daily-expenses" },
{ label: "收付账户", path: "/cw/assistance/payment-account" },
],
},
],
},
// cwobj,
// 统计分析
{
label: "统计分析",
icon: "ri:bar-chart-box-line",
children: [
{
label: "收款统计分析",
children: [
{ label: "应收未收明细", path: "/tj/ystj/tj_nots" },
{ label: "应收未收按单位汇总", path: "/tj/ystj/tj_notsbydw" },
{ label: "未收款按业务员汇总", path: "/tj/ystj/tj_notsbysales" },
{ label: "收款明细统计", path: "/tj/ystj/tj_cwskxz" },
],
},
{
label: "付款统计分析",
children: [
{ label: "应付未付明细", path: "/tj/yftj/tj_alone_notf" },
{ label: "应付未付按单位汇总", path: "/tj/yftj/tj_notfbydw" },
{ label: "付款明细统计", path: "/tj/yftj/tj_cwfkxz" },
],
},
{
label: "利润统计分析",
children: [
{ label: "利润分析单票明细", path: "/tj/lrtj/tj_lr_module_id" },
{ label: "利润分析按业务员", path: "/tj/lrtj/tj_lrbysales" },
{ label: "利润分析按客户", path: "/tj/lrtj/tj_lrbycustomer" },
],
},
],
},
// 审批中心
{
label: "管理中心",
icon: "ri:government-line",
children: [
{ label: "审批中心", path: "/shcenter" },
{ label: "合同管理", path: "/partners/khht" },
{ label: "运费模版", path: "/pricelist/list" },
{
label: "企业公告",
path: "/system/oa-management/companyAnnouncement",
},
{ label: "审批设置", path: "/system/approvalSettings" },
{ label: "提醒设置", path: "/system/oa-management/userReminder" },
{ label: "结算汇率", path: "/partners/hl" },
],
},
// tjobj,
// 管理中心
// mgobj,
// 基础数据
{
label: "基础数据",
icon: "ri:database-line",
children: [
{ label: "合作伙伴", path: "/partners" },
{ label: "联系人", path: "/partners/contact" },
{ label: "其他数据", path: "/base/other" },
],
},
// baseobj,
// 系统管理
{
label: "系统管理",
icon: "ri:settings-line",
children: [
{ label: "权限管理", path: "/system/permission" },
{ label: "字段配置", path: "/system/field" },
{ label: "查询设置", path: "/system/statisticalSettings" },
{ label: "映射配置", path: "/system/mappingConfig" },
{ label: "模块设置", path: "/system/moduleSettings" },
{ label: "自动编码", path: "/system/autoId" },
],
},
// sysobj,
g3modobj,
// 系统帮助
{
/* {
label: "系统帮助",
icon: "ri:question-line",
children: [
@@ -159,27 +198,177 @@ const menuList = ref([
label: "功能测试",
icon: "ri:bug-line",
children: [{ label: "测试页面", path: "/test/test" }],
},
}, */
]);
/*
function handleSelect(val) {
router.push(val);
console.log(val)
// router.push(val);
router.push({
path: val.path,
query: {
moduleid: val.moduleid
}
})
} */
const activeMenuId = computed(() => {
return findNodeByPath(menuList.value, route.path)?.id || "";
});
function handleSelect(menuId) {
// console.log('index:', index); // '/tj/ystj/tj_nots'
// console.log('indexPath:', indexPath); // ['4', 'TJ_ystj', 'TJ_ystj_01']
// 从 menuList 里找到完整节点
const node = findNodeById(menuList.value, menuId);
if (!node || !node.path) return;
router.push({
path: node.path,
query: node.moduleid ? { moduleid: node.moduleid } : {},
});
}
function findNodeById(tree, id) {
for (const node of tree) {
if (node.id === id) return node;
if (node.children) {
const found = findNodeById(node.children, id);
if (found) return found;
}
}
return null;
}
function findNodeByPath(tree, path) {
for (const node of tree) {
if (node.path === path) return node;
if (node.children) {
const found = findNodeByPath(node.children, path);
if (found) return found;
}
}
return null;
}
function findNodeByModuleId(tree, moduleid) {
for (const node of tree) {
if (node.moduleid === moduleid) return node;
if (node.children) {
const found = findNodeByModuleId(node.children, moduleid);
if (found) return found;
}
}
return null;
}
function handleCollapse() {
emits("collapse");
}
onMounted(async () => {
let searchsql = `select * from s_keyvalue where b_key='bodycaption'`;
const response = await axios.get("/selectlistbysql", {
params: { sql: searchsql },
});
if (response.data?.success) {
const records = response.data.data.records || [];
if (records.length > 0) {
title.value = records[0].b_value;
const js_1_icon = [
{ b_id: "HY", icon: "ri:folder-chart-line" },
{ b_id: "CK", icon: "ri:folder-chart-line" },
{ b_id: "gjqy", icon: "ri:folder-chart-line" },
{ b_id: "CW", icon: "ri:wallet-line" },
{ b_id: "glzx", icon: "ri:government-line" },
{ b_id: "TJ", icon: "ri:bar-chart-box-line" },
{ b_id: "base", icon: "ri:database-line" },
{ b_id: "sys", icon: "ri:settings-line" },
];
const iconMap = new Map(js_1_icon.map((item) => [item.b_id, item.icon]));
function buildModuleTree(records) {
const mainTree = [];
// ✅ 首页 默认添加
mainTree.push(homeobj);
records.forEach((row) => {
const js = Number(row.js);
const bid = row.b_id;
const moduleid = row.b_moduleid;
const bname = row.mc_b;
const bpath = row.b_path || "";
const node = {
label: bname,
id: bid,
...(js === 1 ? { icon: iconMap.get(bid) || "" } : {}),
...(bpath ? { path: bpath } : {}),
...(moduleid ? { moduleid: moduleid } : {}),
};
if (js === 1) {
mainTree.push(node);
}
if (js === 2) {
const p1 = mainTree.find((p) => p.id === row.bm_1_b);
if (p1) {
if (!p1.children) p1.children = [];
p1.children.push(node);
}
}
if (js === 3) {
const p1 = mainTree.find((p) => p.id === row.bm_1_b);
const p2 = p1?.children?.find((c) => c.id === row.bm_2_b);
if (p2) {
if (!p2.children) p2.children = [];
p2.children.push(node);
}
}
if (js === 4) {
const p1 = mainTree.find((p) => p.id === row.bm_1_b);
const p2 = p1?.children?.find((c) => c.id === row.bm_2_b);
const p3 = p2?.children?.find((c) => c.id === row.bm_3_b);
if (p3) {
if (!p3.children) p3.children = [];
p3.children.push(node);
}
}
});
// ✅ g3soft 特殊节点:循环结束后再追加
if (userStore.getUserId() === "g3soft") {
mainTree.push(g3modobj);
}
return mainTree;
}
async function getmodulebmfl() {
let searchsql = `select * from s_module_bmfl where b_canmenu='1' order by js,b_xh`;
let datas = await getDataBySql(searchsql);
if (!datas.records || datas.records.length === 0) {
//=== 树形菜单 没有设置 默认有首页,g3soft账号有模块设置
const mainTree = [];
// ✅ 首页 默认添加
mainTree.push(homeobj);
// ✅ g3soft 特殊节点:循环结束后再追加
if (userStore.getUserId() === "g3soft") {
mainTree.push(g3modobj);
}
menuList.value = mainTree;
} else {
menuList.value = buildModuleTree(datas.records);
}
// 将菜单树中的 moduleid→节点 映射存入 menuStore,供 router 动态获取 title
const menuStore = useMenuStore();
menuStore.setModuleMap(menuList.value);
console.log("menuList.value=-=-==-==-");
console.log(menuList.value);
}
async function getbodycaption() {
let searchsql = `select * from s_keyvalue where b_key='bodycaption'`;
let datas = await getDataBySql(searchsql);
if (!datas.records || datas.records.length === 0) {
return;
}
title.value = datas.records[0].b_value;
}
onMounted(async () => {
try {
getbodycaption();
getmodulebmfl();
} catch (err) {
console.log(err);
ElMessage.warning(err.message || "没有查询语句");
}
});
</script>
@@ -195,7 +384,7 @@ onMounted(async () => {
"
>
<img src="@/assets/g3logo.png" alt="Logo" class="logo" />
<div v-show="!props.collapse" class="title">{{ title }}</div>
<!-- <div v-show="!props.collapse" class="title">{{ title }}</div> -->
</div>
<div class="menu-container">
<el-menu
@@ -206,10 +395,11 @@ onMounted(async () => {
@select="handleSelect"
>
<MenuItem
v-for="(item, idx) in menuList"
:key="item.path || idx"
v-for="item in menuList"
:key="item.id"
:item="item"
:parent-index="idx.toString()"
:index="item.id"
:level="0"
/>
</el-menu>
</div>
@@ -228,7 +418,8 @@ onMounted(async () => {
align-items: center;
height: 100%;
width: 100%;
background-color: #f8f8f8;
// background-color: #f8f8f8;
background-color: #003152;
overflow: hidden;
transition: background-color 0.3s ease;
@@ -281,7 +472,7 @@ onMounted(async () => {
position: relative;
width: 100%;
overflow: hidden;
background-color: #f8f8f8;
background-color: #003152;
.menu-inner {
position: absolute;
top: 0;
@@ -319,7 +510,7 @@ onMounted(async () => {
height: 28px !important;
line-height: 28px !important;
box-sizing: border-box;
color: #525252;
color: #fff;
font-size: 13px;
font-family:
"InterVariable", "Inter", "-apple-system", "BlinkMacSystemFont", "Segoe UI", "Roboto",
@@ -330,7 +521,7 @@ onMounted(async () => {
height: 28px !important;
line-height: 28px !important;
box-sizing: border-box;
color: #525252;
color: #fff;
font-size: 13px;
font-family:
"InterVariable", "Inter", "-apple-system", "BlinkMacSystemFont", "Segoe UI", "Roboto",
@@ -343,7 +534,7 @@ onMounted(async () => {
}
}
:deep(.el-menu-item:hover) {
background-color: #f3f3f3;
background-color: #03273f;
border-radius: 8px;
}
@@ -358,41 +549,25 @@ onMounted(async () => {
}
:deep(.el-sub-menu),
:deep(.el-menu) {
background-color: #f8f8f8 !important;
background-color: #003152;
}
:deep(.el-sub-menu.is-active .el-sub-menu__title) {
border-bottom-color: #525252;
border-bottom-color: #fff;
}
:deep(.el-sub-menu__title:hover) {
background-color: #f3f3f3;
background-color: #03273f;
}
:deep(.menu-container .el-menu-item.is-active) {
color: #525252;
color: #fff;
font-weight: 500;
background: rgba(255, 255, 255, 1);
background: #093e63;
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.1);
border-radius: 8px;
}
:deep(.menu-container .el-sub-menu.is-active > .el-sub-menu__title) {
color: #525252;
color: #fff;
font-weight: 500;
}
:deep(.g3level1 .el-menu) {
margin-left: 30px;
}
:deep(.g3level1 .el-menu .el-menu-item) {
padding: 0px 20px 0px 20px !important;
}
:deep(.g3level2 .el-sub-menu__title) {
margin-left: 30px;
padding: 0px 20px 0px 20px !important;
}
:deep(.g3level2 .el-menu) {
margin-left: 42px;
}
:deep(.g3level2 .el-menu .el-menu-item) {
padding: 0px 20px 0px 20px !important;
}
.menu-footer {
display: flex;
@@ -405,6 +580,7 @@ onMounted(async () => {
padding-right: 12px;
overflow: hidden;
transition: padding-right 0.3s ease;
color: #fff;
div {
display: flex;
@@ -1,9 +1,9 @@
<script setup>
import { ref, computed, onMounted } from "vue";
import { useRoute, useRouter } from "vue-router";
import axios from "@/utils/axios";
import G3Icon from "@/components/g3-icon/index.vue";
import MenuItem from "./menu-item.vue";
import { getDataBySql } from "@/utils/doSql";
const route = useRoute();
const router = useRouter();
@@ -30,7 +30,7 @@ const menuList = ref([
{
label: "首页",
path: "/home",
icon: "mdi:home",
icon: "svghome",
},
// 业务管理
{
@@ -41,6 +41,27 @@ const menuList = ref([
{ label: "排车列表", path: "/business/listcontainer" },
],
},
{
label: "仓库管理",
icon: "ri:folder-chart-line",
children: [
{ label: "入库管理", path: "/ck/in/list" },
{ label: "出库管理", path: "/ck/out/list" },
{
label: "统计报表",
children: [
{ label: "入库查询", path: "/ck/tj/listin" },
{ label: "出库查询", path: "/ck/tj/listout" },
{ label: "库存查询", path: "/ck/tj/listkc" },
],
},
],
},
{
label: "国际汽运",
icon: "ri:folder-chart-line",
children: [{ label: "国际汽运", path: "/Intlroad" }],
},
// 财务管理
{
label: "财务管理",
@@ -143,6 +164,8 @@ const menuList = ref([
{ label: "映射配置", path: "/system/mappingConfig" },
{ label: "模块设置", path: "/system/moduleSettings" },
{ label: "自动编码", path: "/system/autoId" },
{ label: "登陆日志", path: "/system/loginrecords" },
{ label: "数据日志", path: "/system/logdatas" },
],
},
// 系统帮助
@@ -163,6 +186,8 @@ const menuList = ref([
]);
function handleSelect(val) {
console.log(val);
router.push(val);
}
@@ -171,15 +196,18 @@ function handleCollapse() {
}
onMounted(async () => {
let searchsql = `select * from s_keyvalue where b_key='bodycaption'`;
const response = await axios.get("/selectlistbysql", {
params: { sql: searchsql },
});
if (response.data?.success) {
const records = response.data.data.records || [];
if (records.length > 0) {
title.value = records[0].b_value;
try {
let searchsql = `select * from s_keyvalue where b_key='bodycaption'`;
let datas = await getDataBySql(searchsql);
if (!datas.records || datas.records.length === 0) {
return;
}
title.value = datas.records[0].b_value;
} catch (err) {
console.log(err);
ElMessage.warning(err.message || "没有查询语句");
}
});
</script>
@@ -228,7 +256,7 @@ onMounted(async () => {
align-items: center;
height: 100%;
width: 100%;
background-color: white;
background-color: #f8f8f8;
overflow: hidden;
transition: background-color 0.3s ease;
@@ -281,6 +309,7 @@ onMounted(async () => {
position: relative;
width: 100%;
overflow: hidden;
background-color: #f8f8f8;
.menu-inner {
position: absolute;
top: 0;
@@ -315,15 +344,25 @@ onMounted(async () => {
}
:deep(.el-sub-menu__title) {
height: 42px !important;
line-height: 42px !important;
height: 28px !important;
line-height: 28px !important;
box-sizing: border-box;
color: #525252;
font-size: 13px;
font-family:
"InterVariable", "Inter", "-apple-system", "BlinkMacSystemFont", "Segoe UI", "Roboto",
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
}
:deep(.el-menu-item) {
height: 42px !important;
line-height: 42px !important;
height: 28px !important;
line-height: 28px !important;
box-sizing: border-box;
color: #525252;
font-size: 13px;
font-family:
"InterVariable", "Inter", "-apple-system", "BlinkMacSystemFont", "Segoe UI", "Roboto",
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
&.is-active {
background-color: var(--el-color-primary-light-9);
@@ -331,6 +370,10 @@ onMounted(async () => {
font-weight: 500;
}
}
:deep(.el-menu-item:hover) {
background-color: #f3f3f3;
border-radius: 8px;
}
:deep(.el-sub-menu) {
&.is-active {
@@ -341,6 +384,43 @@ onMounted(async () => {
}
}
}
:deep(.el-sub-menu),
:deep(.el-menu) {
background-color: #f8f8f8 !important;
}
:deep(.el-sub-menu.is-active .el-sub-menu__title) {
border-bottom-color: #525252;
}
:deep(.el-sub-menu__title:hover) {
background-color: #f3f3f3;
}
:deep(.menu-container .el-menu-item.is-active) {
color: #525252;
font-weight: 500;
background: rgba(255, 255, 255, 1);
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.1);
border-radius: 8px;
}
:deep(.menu-container .el-sub-menu.is-active > .el-sub-menu__title) {
color: #525252;
font-weight: 500;
}
:deep(.g3level1 .el-menu) {
margin-left: 30px;
}
:deep(.g3level1 .el-menu .el-menu-item) {
padding: 0px 20px 0px 20px !important;
}
:deep(.g3level2 .el-sub-menu__title) {
margin-left: 30px;
padding: 0px 20px 0px 20px !important;
}
:deep(.g3level2 .el-menu) {
margin-left: 42px;
}
:deep(.g3level2 .el-menu .el-menu-item) {
padding: 0px 20px 0px 20px !important;
}
.menu-footer {
display: flex;
@@ -0,0 +1,603 @@
<script setup>
import { ref, computed, onMounted } from "vue";
import { useRoute, useRouter } from "vue-router";
import G3Icon from "@/components/g3-icon/index.vue";
import MenuItem from "./menu-item.vue";
import { getDataBySql } from "@/utils/doSql";
import { useUserStore } from "@/store/user";
import { useMenuStore } from "@/store/menu";
const route = useRoute();
const router = useRouter();
const userStore = useUserStore();
/*
const props = defineProps({
collapse: {
type: Boolean,
default: false,
},
animating: {
type: Boolean,
default: false,
},
}); */
const props = defineProps({
item: { type: Object, required: true },
parentIndex: { type: String, default: "0" },
});
const emits = defineEmits(["collapse"]);
const title = ref("G3后台管理系统");
// 首页
const homeobj = {
id: "__home__",
label: "首页",
path: "/home",
icon: "svghome",
};
// 业务管理
const bsobj = {
label: "业务管理",
icon: "ri:folder-chart-line",
children: [
{ label: "业务管理", path: "/business" },
{ label: "排车列表", path: "/business/listcontainer" },
],
};
// 财务管理
const cwobj = {
label: "财务管理",
icon: "ri:wallet-line",
children: [
{
label: "应收管理",
children: [
{ label: "应收费用列表", path: "/cw/ys/list" },
{ label: "应收账单", path: "/cw/ys/listbill" },
{ label: "应收发票", path: "/cw/ys/listinvoice" },
{ label: "收款管理", path: "/cw/ys/listskd" },
],
},
{
label: "应付管理",
children: [
{ label: "应付费用列表", path: "/cw/yf/list" },
{ label: "应付账单", path: "/cw/yf/listbill" },
{ label: "应付发票", path: "/cw/yf/listinvoice" },
{ label: "付款管理", path: "/cw/yf/listfkd" },
],
},
{
label: "财务辅助",
children: [
{ label: "日常费用", path: "/cw/assistance/daily-expenses" },
{ label: "收付账户", path: "/cw/assistance/payment-account" },
],
},
],
};
const tjobj = {
label: "统计分析",
icon: "ri:bar-chart-box-line",
children: [
{
label: "收款统计分析",
children: [
{ label: "应收未收明细", path: "/tj/ystj/tj_nots" },
{ label: "应收未收按单位汇总", path: "/tj/ystj/tj_notsbydw" },
{ label: "未收款按业务员汇总", path: "/tj/ystj/tj_notsbysales" },
{ label: "收款明细统计", path: "/tj/ystj/tj_cwskxz" },
],
},
{
label: "付款统计分析",
children: [
{ label: "应付未付明细", path: "/tj/yftj/tj_alone_notf" },
{ label: "应付未付按单位汇总", path: "/tj/yftj/tj_notfbydw" },
{ label: "付款明细统计", path: "/tj/yftj/tj_cwfkxz" },
],
},
{
label: "利润统计分析",
children: [
{ label: "利润分析单票明细", path: "/tj/lrtj/tj_lr_module_id" },
{ label: "利润分析按业务员", path: "/tj/lrtj/tj_lrbysales" },
{ label: "利润分析按客户", path: "/tj/lrtj/tj_lrbycustomer" },
],
},
],
};
const mgobj = {
label: "管理中心",
icon: "ri:government-line",
children: [
{ label: "审批中心", path: "/shcenter" },
{ label: "合同管理", path: "/partners/khht" },
{ label: "运费模版", path: "/pricelist/list" },
{ label: "企业公告", path: "/system/oa-management/companyAnnouncement" },
{ label: "提醒设置", path: "/system/oa-management/userReminder" },
{ label: "结算汇率", path: "/partners/hl" },
{ label: "审批设置", path: "/system/approvalSettings" },
],
};
const baseobj = {
label: "基础数据",
icon: "ri:database-line",
children: [
{ label: "合作伙伴", path: "/partners" },
{ label: "联系人", path: "/partners/contact" },
{ label: "其他数据", path: "/base/other" },
],
};
const sysobj = {
label: "系统管理",
icon: "ri:settings-line",
children: [{ label: "权限管理", path: "/system/permission" }],
};
const g3modobj = {
id: "sys_module",
label: "模块设置",
icon: "ri:settings-line",
expanded: false,
children: [
{ id: "sys_moduleset", label: "模块设置", path: "/system/moduleSettings" },
{ id: "sys_logdatas", label: "数据日志", path: "/system/logdatas" },
{ label: "字段配置", path: "/system/field" },
{ label: "自动编码", path: "/system/autoId" },
{ label: "查询设置", path: "/system/statisticalSettings" },
{ label: "映射配置", path: "/system/mappingConfig" },
{ label: "登陆日志", path: "/system/loginrecords" },
],
};
// 根据当前路由在菜单树中找到对应节点 id,用于 el-menu 的 default-active 高亮
const selectedMenuKey = computed(() => {
// 1) 优先通过 path 精确匹配
let node = findNodeByPath(menuList.value, route.path);
if (node?.id) return node.id;
// 2) fallback: /tj 等通用路由,通过 query.moduleid 匹配
if (route.query.moduleid) {
node = findNodeByModuleId(menuList.value, route.query.moduleid);
if (node?.id) return node.id;
}
return "";
});
const menuList = ref([
// 首页
homeobj,
// 业务管理
// bsobj,
// 财务管理
// cwobj,
// 统计分析
// tjobj,
// 管理中心
// mgobj,
// 基础数据
// baseobj,
// 系统管理
// sysobj,
g3modobj,
// 系统帮助
/* {
label: "系统帮助",
icon: "ri:question-line",
children: [
{ label: "帮助文档", path: "/help" },
{ label: "操作视频", path: "/videos" },
],
},
// 功能测试
{
label: "功能测试",
icon: "ri:bug-line",
children: [{ label: "测试页面", path: "/test/test" }],
}, */
]);
/*
function handleSelect(val) {
console.log(val)
// router.push(val);
router.push({
path: val.path,
query: {
moduleid: val.moduleid
}
})
} */
const activeMenuId = computed(() => {
return findNodeByPath(menuList.value, route.path)?.id || "";
});
function handleSelect(menuId) {
// console.log('index:', index); // '/tj/ystj/tj_nots'
// console.log('indexPath:', indexPath); // ['4', 'TJ_ystj', 'TJ_ystj_01']
// 从 menuList 里找到完整节点
const node = findNodeById(menuList.value, menuId);
if (!node || !node.path) return;
router.push({
path: node.path,
query: node.moduleid ? { moduleid: node.moduleid } : {},
});
}
function findNodeById(tree, id) {
for (const node of tree) {
if (node.id === id) return node;
if (node.children) {
const found = findNodeById(node.children, id);
if (found) return found;
}
}
return null;
}
function findNodeByPath(tree, path) {
for (const node of tree) {
if (node.path === path) return node;
if (node.children) {
const found = findNodeByPath(node.children, path);
if (found) return found;
}
}
return null;
}
function findNodeByModuleId(tree, moduleid) {
for (const node of tree) {
if (node.moduleid === moduleid) return node;
if (node.children) {
const found = findNodeByModuleId(node.children, moduleid);
if (found) return found;
}
}
return null;
}
function handleCollapse() {
emits("collapse");
}
const js_1_icon = [
{ b_id: "HY", icon: "ri:folder-chart-line" },
{ b_id: "CK", icon: "ri:folder-chart-line" },
{ b_id: "gjqy", icon: "ri:folder-chart-line" },
{ b_id: "CW", icon: "ri:wallet-line" },
{ b_id: "glzx", icon: "ri:government-line" },
{ b_id: "TJ", icon: "ri:bar-chart-box-line" },
{ b_id: "base", icon: "ri:database-line" },
{ b_id: "sys", icon: "ri:settings-line" },
];
const iconMap = new Map(js_1_icon.map((item) => [item.b_id, item.icon]));
function buildModuleTree(records) {
const mainTree = [];
// ✅ 首页 默认添加
mainTree.push(homeobj);
records.forEach((row) => {
const js = Number(row.js);
const bid = row.b_id;
const moduleid = row.b_moduleid;
const bname = row.mc_b;
const bpath = row.b_path || "";
const node = {
label: bname,
id: bid,
...(js === 1 ? { icon: iconMap.get(bid) || "" } : {}),
...(bpath ? { path: bpath } : {}),
...(moduleid ? { moduleid: moduleid } : {}),
};
if (js === 1) {
mainTree.push(node);
}
if (js === 2) {
const p1 = mainTree.find((p) => p.id === row.bm_1_b);
if (p1) {
if (!p1.children) p1.children = [];
p1.children.push(node);
}
}
if (js === 3) {
const p1 = mainTree.find((p) => p.id === row.bm_1_b);
const p2 = p1?.children?.find((c) => c.id === row.bm_2_b);
if (p2) {
if (!p2.children) p2.children = [];
p2.children.push(node);
}
}
if (js === 4) {
const p1 = mainTree.find((p) => p.id === row.bm_1_b);
const p2 = p1?.children?.find((c) => c.id === row.bm_2_b);
const p3 = p2?.children?.find((c) => c.id === row.bm_3_b);
if (p3) {
if (!p3.children) p3.children = [];
p3.children.push(node);
}
}
});
// ✅ g3soft 特殊节点:循环结束后再追加
if (userStore.getUserId() === "g3soft") {
mainTree.push(g3modobj);
}
return mainTree;
}
async function getmodulebmfl() {
let searchsql = `select * from s_module_bmfl where b_canmenu='1' order by js,b_xh`;
let datas = await getDataBySql(searchsql);
if (!datas.records || datas.records.length === 0) {
//=== 树形菜单 没有设置 默认有首页,g3soft账号有模块设置
const mainTree = [];
// ✅ 首页 默认添加
mainTree.push(homeobj);
// ✅ g3soft 特殊节点:循环结束后再追加
if (userStore.getUserId() === "g3soft") {
mainTree.push(g3modobj);
}
menuList.value = mainTree;
} else {
menuList.value = buildModuleTree(datas.records);
}
// 将菜单树中的 moduleid→节点 映射存入 menuStore,供 router 动态获取 title
const menuStore = useMenuStore();
menuStore.setModuleMap(menuList.value);
}
async function getbodycaption() {
let searchsql = `select * from s_keyvalue where b_key='bodycaption'`;
let datas = await getDataBySql(searchsql);
if (!datas.records || datas.records.length === 0) {
return;
}
title.value = datas.records[0].b_value;
}
onMounted(async () => {
try {
getbodycaption();
getmodulebmfl();
} catch (err) {
console.log(err);
ElMessage.warning(err.message || "没有查询语句");
}
});
</script>
<template>
<div class="app-menu" :class="{ collapse: props.collapse, 'is-animating': props.animating }">
<div
class="menu-logo"
@click="
() => {
router.push('/home');
}
"
>
<img src="@/assets/g3logo.png" alt="Logo" class="logo" />
<!-- <div v-show="!props.collapse" class="title">{{ title }}</div> -->
</div>
<div class="menu-container">
<el-menu
:collapse="props.collapse"
:collapse-transition="false"
class="menu-inner"
:default-active="selectedMenuKey"
@select="handleSelect"
>
<MenuItem v-for="item in menuList" :key="item.id" :item="item" :index="item.id" />
</el-menu>
</div>
<div class="menu-footer">
<div @click="handleCollapse()">
<G3Icon :icon="props.collapse ? 'ri:menu-fold-4-line' : 'ri:menu-fold-3-line'" :size="20" />
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.app-menu {
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
width: 100%;
background-color: #f8f8f8;
overflow: hidden;
transition: background-color 0.3s ease;
&.collapse {
.menu-logo .logo {
width: 46px;
height: 16px;
}
.menu-footer {
justify-content: center;
padding-right: 0px;
}
}
&.is-animating {
pointer-events: none;
}
}
.menu-logo {
display: flex;
align-items: center;
justify-content: center;
height: 42px;
padding: 19px 0px;
cursor: pointer;
width: 100%;
overflow: hidden;
transition: padding 0.3s ease;
.logo {
width: 80px;
height: 24px;
transition: all 0.3s ease;
}
.title {
margin-left: 10px;
font-size: 16px;
font-weight: 700;
color: #70cfb1;
white-space: nowrap;
transition: all 0.3s ease;
}
}
.menu-container {
flex: 1;
position: relative;
width: 100%;
overflow: hidden;
background-color: #f8f8f8;
.menu-inner {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow-y: auto;
overflow-x: hidden;
min-width: 64px;
box-sizing: border-box;
width: 100% !important;
border-right: none !important;
scrollbar-width: thin;
scrollbar-color: #cfd3dc transparent;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background-color: #cfd3dc;
border-radius: 999px;
&:hover {
background-color: #a8abb2;
}
}
}
:deep(.el-sub-menu__title) {
height: 28px !important;
line-height: 28px !important;
box-sizing: border-box;
color: #525252;
font-size: 13px;
font-family:
"InterVariable", "Inter", "-apple-system", "BlinkMacSystemFont", "Segoe UI", "Roboto",
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
}
:deep(.el-menu-item) {
height: 28px !important;
line-height: 28px !important;
box-sizing: border-box;
color: #525252;
font-size: 13px;
font-family:
"InterVariable", "Inter", "-apple-system", "BlinkMacSystemFont", "Segoe UI", "Roboto",
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
&.is-active {
background-color: var(--el-color-primary-light-9);
color: var(--el-color-primary);
font-weight: 500;
}
}
:deep(.el-menu-item:hover) {
background-color: #f3f3f3;
border-radius: 8px;
}
:deep(.el-sub-menu) {
&.is-active {
> .el-sub-menu__title {
color: var(--el-color-primary);
font-weight: 500;
}
}
}
}
:deep(.el-sub-menu),
:deep(.el-menu) {
background-color: #f8f8f8 !important;
}
:deep(.el-sub-menu.is-active .el-sub-menu__title) {
border-bottom-color: #525252;
}
:deep(.el-sub-menu__title:hover) {
background-color: #f3f3f3;
}
:deep(.menu-container .el-menu-item.is-active) {
color: #525252;
font-weight: 500;
background: rgba(255, 255, 255, 1);
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.1);
border-radius: 8px;
}
:deep(.menu-container .el-sub-menu.is-active > .el-sub-menu__title) {
color: #525252;
font-weight: 500;
}
:deep(.g3level1 .el-menu) {
margin-left: 30px;
}
:deep(.g3level1 .el-menu .el-menu-item) {
padding: 0px 20px 0px 20px !important;
}
:deep(.g3level2 .el-sub-menu__title) {
margin-left: 30px;
padding: 0px 20px 0px 20px !important;
}
:deep(.g3level2 .el-menu) {
margin-left: 42px;
}
:deep(.g3level2 .el-menu .el-menu-item) {
padding: 0px 20px 0px 20px !important;
}
.menu-footer {
display: flex;
align-items: center;
justify-content: flex-end;
width: 100%;
cursor: pointer;
box-sizing: border-box;
padding: 8px 0px 8px 0px;
padding-right: 12px;
overflow: hidden;
transition: padding-right 0.3s ease;
div {
display: flex;
align-items: center;
justify-content: center;
width: 38px;
height: 38px;
&:hover {
background-color: rgba(45, 140, 240, 0.1);
color: rgba(45, 140, 240, 0.8);
}
}
}
</style>
@@ -0,0 +1,608 @@
<script setup>
import { ref, computed, onMounted } from "vue";
import { useRoute, useRouter } from "vue-router";
import G3Icon from "@/components/g3-icon/index.vue";
import MenuItem from "./menu-item.vue";
import { getDataBySql } from "@/utils/doSql";
import { useUserStore } from "@/store/user";
import { useMenuStore } from "@/store/menu";
const route = useRoute();
const router = useRouter();
const userStore = useUserStore();
/*
const props = defineProps({
collapse: {
type: Boolean,
default: false,
},
animating: {
type: Boolean,
default: false,
},
}); */
const props = defineProps({
item: { type: Object, required: true },
parentIndex: { type: String, default: "0" },
});
const emits = defineEmits(["collapse"]);
const title = ref("G3后台管理系统");
// 首页
const homeobj = {
id: "__home__",
label: "首页",
path: "/home",
icon: "svghome",
};
// 业务管理
const bsobj = {
label: "业务管理",
icon: "ri:folder-chart-line",
children: [
{ label: "业务管理", path: "/business" },
{ label: "排车列表", path: "/business/listcontainer" },
],
};
// 财务管理
const cwobj = {
label: "财务管理",
icon: "ri:wallet-line",
children: [
{
label: "应收管理",
children: [
{ label: "应收费用列表", path: "/cw/ys/list" },
{ label: "应收账单", path: "/cw/ys/listbill" },
{ label: "应收发票", path: "/cw/ys/listinvoice" },
{ label: "收款管理", path: "/cw/ys/listskd" },
],
},
{
label: "应付管理",
children: [
{ label: "应付费用列表", path: "/cw/yf/list" },
{ label: "应付账单", path: "/cw/yf/listbill" },
{ label: "应付发票", path: "/cw/yf/listinvoice" },
{ label: "付款管理", path: "/cw/yf/listfkd" },
],
},
{
label: "财务辅助",
children: [
{ label: "日常费用", path: "/cw/assistance/daily-expenses" },
{ label: "收付账户", path: "/cw/assistance/payment-account" },
],
},
],
};
const tjobj = {
label: "统计分析",
icon: "ri:bar-chart-box-line",
children: [
{
label: "收款统计分析",
children: [
{ label: "应收未收明细", path: "/tj/ystj/tj_nots" },
{ label: "应收未收按单位汇总", path: "/tj/ystj/tj_notsbydw" },
{ label: "未收款按业务员汇总", path: "/tj/ystj/tj_notsbysales" },
{ label: "收款明细统计", path: "/tj/ystj/tj_cwskxz" },
],
},
{
label: "付款统计分析",
children: [
{ label: "应付未付明细", path: "/tj/yftj/tj_alone_notf" },
{ label: "应付未付按单位汇总", path: "/tj/yftj/tj_notfbydw" },
{ label: "付款明细统计", path: "/tj/yftj/tj_cwfkxz" },
],
},
{
label: "利润统计分析",
children: [
{ label: "利润分析单票明细", path: "/tj/lrtj/tj_lr_module_id" },
{ label: "利润分析按业务员", path: "/tj/lrtj/tj_lrbysales" },
{ label: "利润分析按客户", path: "/tj/lrtj/tj_lrbycustomer" },
],
},
],
};
const mgobj = {
label: "管理中心",
icon: "ri:government-line",
children: [
{ label: "审批中心", path: "/shcenter" },
{ label: "合同管理", path: "/partners/khht" },
{ label: "运费模版", path: "/pricelist/list" },
{ label: "企业公告", path: "/system/oa-management/companyAnnouncement" },
{ label: "提醒设置", path: "/system/oa-management/userReminder" },
{ label: "结算汇率", path: "/partners/hl" },
{ label: "审批设置", path: "/system/approvalSettings" },
],
};
const baseobj = {
label: "基础数据",
icon: "ri:database-line",
children: [
{ label: "合作伙伴", path: "/partners" },
{ label: "联系人", path: "/partners/contact" },
{ label: "其他数据", path: "/base/other" },
],
};
const sysobj = {
label: "系统管理",
icon: "ri:settings-line",
children: [{ label: "权限管理", path: "/system/permission" }],
};
const g3modobj = {
id: "sys_module",
label: "模块设置",
icon: "ri:settings-line",
expanded: false,
children: [
{ id: "sys_moduleset", label: "模块设置", path: "/system/moduleSettings" },
{ id: "sys_field", label: "字段配置", path: "/system/field" },
{ id: "sys_Settings", label: "查询设置", path: "/system/statisticalSettings" },
{ id: "sys_mappingConfig", label: "映射配置", path: "/system/mappingConfig" },
{ id: "sys_autoId", label: "自动编码", path: "/system/autoId" },
{ id: "sys_reminder", label: "提醒设置", path: "/system/oa-management/userReminder" },
{ id: "sys_logdatas", label: "数据日志", path: "/system/logdatas" },
{ id: "sys_loginrecords", label: "登陆日志", path: "/system/loginrecords" },
],
};
// 根据当前路由在菜单树中找到对应节点 id,用于 el-menu 的 default-active 高亮
const selectedMenuKey = computed(() => {
// 1) 优先通过 path 精确匹配
let node = findNodeByPath(menuList.value, route.path);
if (node?.id) return node.id;
// 2) fallback: /tj 等通用路由,通过 query.moduleid 匹配
if (route.query.moduleid) {
node = findNodeByModuleId(menuList.value, route.query.moduleid);
if (node?.id) return node.id;
}
return "";
});
const menuList = ref([
// 首页
homeobj,
// 业务管理
// bsobj,
// 财务管理
// cwobj,
// 统计分析
// tjobj,
// 管理中心
// mgobj,
// 基础数据
// baseobj,
// 系统管理
// sysobj,
g3modobj,
// 系统帮助
/* {
label: "系统帮助",
icon: "ri:question-line",
children: [
{ label: "帮助文档", path: "/help" },
{ label: "操作视频", path: "/videos" },
],
},
// 功能测试
{
label: "功能测试",
icon: "ri:bug-line",
children: [{ label: "测试页面", path: "/test/test" }],
}, */
]);
/*
function handleSelect(val) {
console.log(val)
// router.push(val);
router.push({
path: val.path,
query: {
moduleid: val.moduleid
}
})
} */
const activeMenuId = computed(() => {
return findNodeByPath(menuList.value, route.path)?.id || "";
});
function handleSelect(menuId) {
// console.log('index:', index); // '/tj/ystj/tj_nots'
// console.log('indexPath:', indexPath); // ['4', 'TJ_ystj', 'TJ_ystj_01']
// 从 menuList 里找到完整节点
const node = findNodeById(menuList.value, menuId);
if (!node || !node.path) return;
router.push({
path: node.path,
query: node.moduleid ? { moduleid: node.moduleid } : {},
});
}
function findNodeById(tree, id) {
for (const node of tree) {
if (node.id === id) return node;
if (node.children) {
const found = findNodeById(node.children, id);
if (found) return found;
}
}
return null;
}
function findNodeByPath(tree, path) {
for (const node of tree) {
if (node.path === path) return node;
if (node.children) {
const found = findNodeByPath(node.children, path);
if (found) return found;
}
}
return null;
}
function findNodeByModuleId(tree, moduleid) {
for (const node of tree) {
if (node.moduleid === moduleid) return node;
if (node.children) {
const found = findNodeByModuleId(node.children, moduleid);
if (found) return found;
}
}
return null;
}
function handleCollapse() {
emits("collapse");
}
const js_1_icon = [
{ b_id: "HY", icon: "ri:folder-chart-line" },
{ b_id: "CK", icon: "ri:folder-chart-line" },
{ b_id: "gjqy", icon: "ri:folder-chart-line" },
{ b_id: "CW", icon: "ri:wallet-line" },
{ b_id: "glzx", icon: "ri:government-line" },
{ b_id: "TJ", icon: "ri:bar-chart-box-line" },
{ b_id: "base", icon: "ri:database-line" },
{ b_id: "sys", icon: "ri:settings-line" },
];
const iconMap = new Map(js_1_icon.map((item) => [item.b_id, item.icon]));
function buildModuleTree(records) {
const mainTree = [];
// ✅ 首页 默认添加
mainTree.push(homeobj);
records.forEach((row) => {
const js = Number(row.js);
const bid = row.b_id;
const moduleid = row.b_moduleid;
const bname = row.mc_b;
const bpath = row.b_path || "";
const node = {
label: bname,
id: bid,
...(js === 1 ? { icon: iconMap.get(bid) || "" } : {}),
...(bpath ? { path: bpath } : {}),
...(moduleid ? { moduleid: moduleid } : {}),
};
if (js === 1) {
mainTree.push(node);
}
if (js === 2) {
const p1 = mainTree.find((p) => p.id === row.bm_1_b);
if (p1) {
if (!p1.children) p1.children = [];
p1.children.push(node);
}
}
if (js === 3) {
const p1 = mainTree.find((p) => p.id === row.bm_1_b);
const p2 = p1?.children?.find((c) => c.id === row.bm_2_b);
if (p2) {
if (!p2.children) p2.children = [];
p2.children.push(node);
}
}
if (js === 4) {
const p1 = mainTree.find((p) => p.id === row.bm_1_b);
const p2 = p1?.children?.find((c) => c.id === row.bm_2_b);
const p3 = p2?.children?.find((c) => c.id === row.bm_3_b);
if (p3) {
if (!p3.children) p3.children = [];
p3.children.push(node);
}
}
});
// ✅ g3soft 特殊节点:循环结束后再追加
if (userStore.getUserId() === "g3soft") {
mainTree.push(g3modobj);
}
return mainTree;
}
async function getmodulebmfl() {
let searchsql = `select * from s_module_bmfl where b_canmenu='1' order by js,b_xh`;
let datas = await getDataBySql(searchsql);
if (!datas.records || datas.records.length === 0) {
//=== 树形菜单 没有设置 默认有首页,g3soft账号有模块设置
const mainTree = [];
// ✅ 首页 默认添加
mainTree.push(homeobj);
// ✅ g3soft 特殊节点:循环结束后再追加
if (userStore.getUserId() === "g3soft") {
mainTree.push(g3modobj);
}
menuList.value = mainTree;
} else {
menuList.value = buildModuleTree(datas.records);
}
// 将菜单树中的 moduleid→节点 映射存入 menuStore,供 router 动态获取 title
const menuStore = useMenuStore();
menuStore.setModuleMap(menuList.value);
console.log("menuList.value=-=-==-==-");
console.log(menuList.value);
}
async function getbodycaption() {
let searchsql = `select * from s_keyvalue where b_key='bodycaption'`;
let datas = await getDataBySql(searchsql);
if (!datas.records || datas.records.length === 0) {
return;
}
title.value = datas.records[0].b_value;
}
onMounted(async () => {
try {
getbodycaption();
getmodulebmfl();
} catch (err) {
console.log(err);
ElMessage.warning(err.message || "没有查询语句");
}
});
</script>
<template>
<div class="app-menu" :class="{ collapse: props.collapse, 'is-animating': props.animating }">
<div
class="menu-logo"
@click="
() => {
router.push('/home');
}
"
>
<img src="@/assets/g3logo.png" alt="Logo" class="logo" />
<!-- <div v-show="!props.collapse" class="title">{{ title }}</div> -->
</div>
<div class="menu-container">
<el-menu
:collapse="props.collapse"
:collapse-transition="false"
class="menu-inner"
:default-active="selectedMenuKey"
@select="handleSelect"
>
<MenuItem v-for="item in menuList" :key="item.id" :item="item" :index="item.id" />
</el-menu>
</div>
<div class="menu-footer">
<div @click="handleCollapse()">
<G3Icon :icon="props.collapse ? 'ri:menu-fold-4-line' : 'ri:menu-fold-3-line'" :size="20" />
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.app-menu {
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
width: 100%;
// background-color: #f8f8f8;
background-color: #003152;
overflow: hidden;
transition: background-color 0.3s ease;
&.collapse {
.menu-logo .logo {
width: 46px;
height: 16px;
}
.menu-footer {
justify-content: center;
padding-right: 0px;
}
}
&.is-animating {
pointer-events: none;
}
}
.menu-logo {
display: flex;
align-items: center;
justify-content: center;
height: 42px;
padding: 19px 0px;
cursor: pointer;
width: 100%;
overflow: hidden;
transition: padding 0.3s ease;
.logo {
width: 80px;
height: 24px;
transition: all 0.3s ease;
}
.title {
margin-left: 10px;
font-size: 16px;
font-weight: 700;
color: #70cfb1;
white-space: nowrap;
transition: all 0.3s ease;
}
}
.menu-container {
flex: 1;
position: relative;
width: 100%;
overflow: hidden;
background-color: #003152;
.menu-inner {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow-y: auto;
overflow-x: hidden;
min-width: 64px;
box-sizing: border-box;
width: 100% !important;
border-right: none !important;
scrollbar-width: thin;
scrollbar-color: #cfd3dc transparent;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background-color: #cfd3dc;
border-radius: 999px;
&:hover {
background-color: #a8abb2;
}
}
}
:deep(.el-sub-menu__title) {
height: 28px !important;
line-height: 28px !important;
box-sizing: border-box;
color: #fff;
font-size: 13px;
font-family:
"InterVariable", "Inter", "-apple-system", "BlinkMacSystemFont", "Segoe UI", "Roboto",
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
}
:deep(.el-menu-item) {
height: 28px !important;
line-height: 28px !important;
box-sizing: border-box;
color: #fff;
font-size: 13px;
font-family:
"InterVariable", "Inter", "-apple-system", "BlinkMacSystemFont", "Segoe UI", "Roboto",
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
&.is-active {
background-color: var(--el-color-primary-light-9);
color: var(--el-color-primary);
font-weight: 500;
}
}
:deep(.el-menu-item:hover) {
background-color: #03273f;
border-radius: 8px;
}
:deep(.el-sub-menu) {
&.is-active {
> .el-sub-menu__title {
color: var(--el-color-primary);
font-weight: 500;
}
}
}
}
:deep(.el-sub-menu),
:deep(.el-menu) {
background-color: #003152;
}
:deep(.el-sub-menu.is-active .el-sub-menu__title) {
border-bottom-color: #fff;
}
:deep(.el-sub-menu__title:hover) {
background-color: #03273f;
}
:deep(.menu-container .el-menu-item.is-active) {
color: #fff;
font-weight: 500;
background: #093e63;
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.1);
border-radius: 8px;
}
:deep(.menu-container .el-sub-menu.is-active > .el-sub-menu__title) {
color: #fff;
font-weight: 500;
}
:deep(.g3level1 .el-menu) {
margin-left: 30px;
}
:deep(.g3level1 .el-menu .el-menu-item) {
padding: 0px 20px 0px 20px !important;
}
:deep(.g3level2 .el-sub-menu__title) {
margin-left: 30px;
padding: 0px 20px 0px 20px !important;
}
:deep(.g3level2 .el-menu) {
margin-left: 42px;
}
:deep(.g3level2 .el-menu .el-menu-item) {
padding: 0px 20px 0px 20px !important;
}
.menu-footer {
display: flex;
align-items: center;
justify-content: flex-end;
width: 100%;
cursor: pointer;
box-sizing: border-box;
padding: 8px 0px 8px 0px;
padding-right: 12px;
overflow: hidden;
transition: padding-right 0.3s ease;
color: #fff;
div {
display: flex;
align-items: center;
justify-content: center;
width: 38px;
height: 38px;
&:hover {
background-color: rgba(45, 140, 240, 0.1);
color: rgba(45, 140, 240, 0.8);
}
}
}
</style>
+15 -16
View File
@@ -2,9 +2,9 @@
import { ArrowDown } from "@element-plus/icons-vue";
import { useRouter } from "vue-router";
import { onMounted, ref } from "vue";
import axios from "@/utils/axios";
import updatePassword from "@/views/user-center/updatepassword.vue";
import { useTabStore, useUserStore } from "@/store";
import { getDataBySql } from "@/utils/doSql";
const router = useRouter();
const userStore = useUserStore();
@@ -42,13 +42,18 @@ function logout() {
}
onMounted(async () => {
let searchsql = `select * from s_keyvalue where b_key='maincaption'`;
const response = await axios.get("/selectlistbysql", { params: { sql: searchsql } });
if (response.data?.success) {
const records = response.data.data.records || [];
if (records.length > 0) {
title.value = records[0].b_value;
try {
let searchsql = `select * from s_keyvalue where b_key='bodycaption'`;
let datas = await getDataBySql(searchsql);
if (!datas.records || datas.records.length === 0) {
return;
}
title.value = datas.records[0].b_value;
} catch (err) {
console.log(err);
ElMessage.warning(err.message || "没有查询语句");
}
});
</script>
@@ -65,10 +70,7 @@ onMounted(async () => {
<el-dropdown placement="bottom-end">
<div class="user-card">
<img
class="avatar"
src="https://api.dicebear.com/9.x/initials/svg?seed=User&backgroundColor=e5e7eb&fontColor=6b7280"
/>
<img class="avatar" src="/avatar.jpeg" />
<div class="name">{{ userStore.userInfo.userid || "未登录" }}</div>
<el-icon :size="14">
<arrow-down />
@@ -78,10 +80,7 @@ onMounted(async () => {
<template #dropdown>
<div class="user-dropdown">
<div class="user-info">
<img
class="avatar"
src="https://api.dicebear.com/9.x/initials/svg?seed=User&backgroundColor=e5e7eb&fontColor=6b7280"
/>
<img class="avatar" src="/avatar.jpeg" />
<div>
<div class="name">{{ userStore.userInfo.userid || "未登录" }}</div>
<div class="orgid">
@@ -284,4 +283,4 @@ onMounted(async () => {
width: 100%;
height: 1px;
}
</style>
</style>