diff --git a/f/web-kboss/src/api/gotoYuanJing.js b/f/web-kboss/src/api/gotoYuanJing.js
new file mode 100644
index 0000000..ee75f2b
--- /dev/null
+++ b/f/web-kboss/src/api/gotoYuanJing.js
@@ -0,0 +1,9 @@
+import request from '@/utils/request'
+// 跳转远景
+export function gotoYuanJingAPI(data) {
+ return request({
+ url: `cntoai/get_deerer_header.dspy`,
+ method: 'get',
+ params: data
+ })
+}
\ No newline at end of file
diff --git a/f/web-kboss/src/api/model/model.js b/f/web-kboss/src/api/model/model.js
new file mode 100644
index 0000000..3397ed4
--- /dev/null
+++ b/f/web-kboss/src/api/model/model.js
@@ -0,0 +1,74 @@
+import request from "@/utils/request";
+// 获取模型列表
+export const reqModelList = (params = {}) => {
+ return request({
+ url: 'cntoai/model_management_search.dspy',
+ method: 'get',
+ params
+ })
+}
+// 上架
+export const reqModelUp = (id) => {
+ return request({
+ url: '/cntoai/model_management_list.dspy',
+ method: 'get',
+ params: { id }
+ })
+}
+// 下架
+export const reqModelDown = (id) => {
+ return request({
+ url: '/cntoai/model_management_unlist.dspy',
+ method: 'get',
+ params: { id }
+ })
+}
+// 模型详情
+export const reqModelDetail = (id) => {
+ return request({
+ url: '/cntoai/model_management_detail.dspy',
+ method: 'get',
+ params: { id }
+ })
+}
+// 编辑模型
+export const reqModelEdit = (data) => {
+ return request({
+ url: '/cntoai/model_management_update.dspy',
+ method: 'get',
+ params: data
+ })
+}
+// 置顶
+export const reqModelTop = (id) => {
+ return request({
+ url: '/cntoai/model_management_pin_top.dspy',
+ method: 'get',
+ params: { id }
+ })
+}
+// 下移
+export const reqModelBottom = (id) => {
+ return request({
+ url: '/cntoai/model_management_move_down.dspy',
+ method: 'get',
+ params: { id }
+ })
+}
+
+// apikey列表
+export const reqApikeyList = (params = {}) => {
+ return request({
+ url: '/cntoai/get_model_apikey.dspy',
+ method: 'get',
+ params
+ })
+}
+// 创建apikey
+export const reqCreateApikey = (params = {}) => {
+ return request({
+ url: '/cntoai/create_model_apikey.dspy',
+ method: 'get',
+ params
+ })
+}
diff --git a/f/web-kboss/src/api/newHome.js b/f/web-kboss/src/api/newHome.js
index a5070db..7165756 100644
--- a/f/web-kboss/src/api/newHome.js
+++ b/f/web-kboss/src/api/newHome.js
@@ -101,3 +101,12 @@ export const todoCount = () => {
method: 'post',
})
}
+
+
+// 获取token市集
+export const reqTokenMarket = () => {
+ return request({
+ url: '/cntoai/model_management_customer_search.dspy',
+ method: 'get',
+ })
+}
\ No newline at end of file
diff --git a/f/web-kboss/src/components/modelManagement/ListingConfirmDialog.vue b/f/web-kboss/src/components/modelManagement/ListingConfirmDialog.vue
new file mode 100644
index 0000000..52bc09a
--- /dev/null
+++ b/f/web-kboss/src/components/modelManagement/ListingConfirmDialog.vue
@@ -0,0 +1,186 @@
+
+
+
+
+
+
+
+
{{ action === 'up' ? '确认上架' : '确认下架' }}
+
确认上架该模型到Token市集?
+
确认下架模型后,该模型将从Token市集中移除,用户将无法继续使用。
+
+ 模型名称:{{ getModelDisplayName(model || {}) }}
+
+
+ 取消
+
+ {{ action === 'up' ? '确认上架' : '确认下架' }}
+
+
+
+
+
+
+
+
+
+
diff --git a/f/web-kboss/src/components/modelManagement/ModelDetailDialog.vue b/f/web-kboss/src/components/modelManagement/ModelDetailDialog.vue
new file mode 100644
index 0000000..d568b0f
--- /dev/null
+++ b/f/web-kboss/src/components/modelManagement/ModelDetailDialog.vue
@@ -0,0 +1,257 @@
+
+
+
+ 模型详情
+
+
+
+
+
+
+
{{ getModelDisplayName(model) }}
+
模型ID: {{ model.id || '-' }}
+
+
+ {{ getDetailStatusText(model.listing_status) }}
+
+
+
+
+
+ 模型类型
+ {{ model.model_type || '-' }}
+
+
+ 供应商
+ {{ model.provider || '-' }}
+
+
+ 计费方式
+ {{ model.billing_method || '-' }}
+
+
+
+
+
价格信息
+
+
+ 输入
+ ¥{{ formatPrice(model.input_token_price) }}/千Token
+
+
+ 输出
+ ¥{{ formatPrice(model.output_token_price) }}/千Token
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/f/web-kboss/src/components/modelManagement/ModelFilter.vue b/f/web-kboss/src/components/modelManagement/ModelFilter.vue
new file mode 100644
index 0000000..df6ade8
--- /dev/null
+++ b/f/web-kboss/src/components/modelManagement/ModelFilter.vue
@@ -0,0 +1,134 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 查询
+ 重置
+
+
+
+
+
+
+
+
diff --git a/f/web-kboss/src/components/modelManagement/ModelStats.vue b/f/web-kboss/src/components/modelManagement/ModelStats.vue
new file mode 100644
index 0000000..4a8f9d2
--- /dev/null
+++ b/f/web-kboss/src/components/modelManagement/ModelStats.vue
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+ {{ item.label }}
+ {{ item.value }}
+
+
+
+
+
+
+
+
diff --git a/f/web-kboss/src/layout/components/Navbar.vue b/f/web-kboss/src/layout/components/Navbar.vue
index 6b32943..e8999f1 100644
--- a/f/web-kboss/src/layout/components/Navbar.vue
+++ b/f/web-kboss/src/layout/components/Navbar.vue
@@ -731,11 +731,17 @@ export default {
},
async logout() {
store.commit('tagsView/resetBreadcrumbState');
+ store.commit('permission/RESET_ROUTES');
sessionStorage.removeItem("auths");
sessionStorage.removeItem("routes");
sessionStorage.removeItem("user");
sessionStorage.removeItem("userId");
sessionStorage.removeItem("org_type")
+ sessionStorage.removeItem("userType");
+ sessionStorage.removeItem("orgType");
+ sessionStorage.removeItem("roles");
+ sessionStorage.removeItem("juese");
+ sessionStorage.removeItem("jueseNew");
localStorage.removeItem('userId')
localStorage.removeItem("auths");
localStorage.removeItem("routes");
@@ -752,10 +758,16 @@ export default {
let url = window.location.href;
await this.$router.push(`/login`);
store.commit('tagsView/resetBreadcrumbState');
+ store.commit('permission/RESET_ROUTES');
sessionStorage.removeItem("auths");
sessionStorage.removeItem("routes");
sessionStorage.removeItem("user");
sessionStorage.removeItem("userId");
+ sessionStorage.removeItem("userType");
+ sessionStorage.removeItem("orgType");
+ sessionStorage.removeItem("roles");
+ sessionStorage.removeItem("juese");
+ sessionStorage.removeItem("jueseNew");
},
changeColor() {
this.dialogFormVisible = false
diff --git a/f/web-kboss/src/layout/components/Sidebar/SidebarItem.vue b/f/web-kboss/src/layout/components/Sidebar/SidebarItem.vue
index da82b10..37615ef 100644
--- a/f/web-kboss/src/layout/components/Sidebar/SidebarItem.vue
+++ b/f/web-kboss/src/layout/components/Sidebar/SidebarItem.vue
@@ -97,12 +97,12 @@ export default {
// 给嵌套菜单添加左边距
::v-deep .nest-menu {
.el-menu-item {
- padding-left: 60px !important; // 或者您想要的任何值,比如10px
+ padding-left: 42px !important; // 子菜单稍微缩进,同时保留蓝底圆角选中态
}
// 如果还有更深层的嵌套,可以继续设置
.nest-menu .el-menu-item {
- padding-left: 100px !important;
+ padding-left: 64px !important;
}
}
}
diff --git a/f/web-kboss/src/layout/components/Sidebar/index.vue b/f/web-kboss/src/layout/components/Sidebar/index.vue
index 9763d74..a977547 100644
--- a/f/web-kboss/src/layout/components/Sidebar/index.vue
+++ b/f/web-kboss/src/layout/components/Sidebar/index.vue
@@ -11,7 +11,7 @@
:text-color="variables.menuText"
:unique-opened="true"
:active-text-color="variables.menuActiveText"
- :collapse-transition="false"
+ :collapse-transition="true"
:default-active="activeMenu"
mode="vertical"
class="el-menu-vertical"
@@ -25,6 +25,14 @@
/>
+
@@ -72,6 +80,12 @@ export default {
mounted() {
console.log("Sidebar mounted - 权限路由:", this.permissionRoutes);
+ },
+
+ methods: {
+ toggleSideBar() {
+ this.$store.dispatch("app/toggleSideBar");
+ }
}
};
@@ -88,6 +102,8 @@ export default {
display: flex;
flex-direction: column;
box-sizing: border-box;
+ position: relative;
+ transition: width 0.25s ease;
}
.menu-scroll-container {
@@ -107,6 +123,36 @@ export default {
}
}
+ .sidebar-collapse-btn {
+ position: absolute;
+ right: 16px;
+ bottom: 18px;
+ z-index: 10;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 42px;
+ height: 42px;
+ color: #8a94a6;
+ font-size: 18px;
+ cursor: pointer;
+ background: #ffffff;
+ border: 1px solid #eef2f7;
+ border-radius: 50%;
+ box-shadow: 0 8px 22px rgba(31, 45, 61, 0.12);
+ transition: all 0.25s ease;
+
+ &:hover {
+ color: #1e6fff;
+ transform: translateY(-2px);
+ box-shadow: 0 12px 26px rgba(30, 111, 255, 0.18);
+ }
+
+ &.collapsed {
+ right: 11px;
+ }
+ }
+
// 更具体的选择器
::v-deep .el-menu-vertical {
border: none;
@@ -117,9 +163,37 @@ export default {
.el-submenu__title,
.el-menu-item {
+ height: 56px;
+ line-height: 56px;
+ margin: 6px 14px;
+ padding: 0 18px !important;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
+ border-radius: 12px;
+ transition: all 0.25s ease;
+ }
+
+ .el-submenu__title span,
+ .el-menu-item span {
+ transition: opacity 0.2s ease, transform 0.2s ease;
+ }
+
+ .el-submenu__title:hover,
+ .el-menu-item:not(.is-active):hover {
+ color: #1e6fff !important;
+ background: #f4f8ff !important;
+ }
+
+ .el-menu-item.is-active {
+ color: #ffffff !important;
+ background: linear-gradient(135deg, #1e6fff, #5d8dff) !important;
+
+ }
+
+ .el-menu-item.is-active i,
+ .el-menu-item.is-active span {
+ color: #ffffff !important;
}
// 子菜单容器
@@ -128,23 +202,18 @@ export default {
.el-menu-item {
// 激活的子菜单项
&.is-active {
- background-color: #d7dafd !important;
- color: #296ad9 !important;
+ background: linear-gradient(135deg, #1e6fff, #5d8dff) !important;
+ color: #ffffff !important;
+
&:before {
- content: '';
- position: absolute;
- left: 0;
- top: 0;
- bottom: 0;
- width: 3px;
- background-color: #296ad9;
+ display: none;
}
}
// 非激活状态的悬停效果
&:not(.is-active):hover {
- background-color: #f5f7fa !important;
+ background-color: #f4f8ff !important;
}
}
}
@@ -157,16 +226,24 @@ export default {
.el-submenu__title,
.el-menu-item {
+ margin: 6px 8px;
+ padding: 0 !important;
text-overflow: clip;
justify-content: center;
}
+ .el-submenu__title span,
+ .el-menu-item span {
+ opacity: 0;
+ transform: translateX(-6px);
+ }
+
// 折叠状态下的子菜单激活样式
.el-menu--popup {
.el-menu-item {
&.is-active {
- background-color: #f5f7fa !important;
- color: #296ad9 !important;
+ color: #ffffff !important;
+ background: linear-gradient(135deg, #1e6fff 0%, #244fbd 100%) !important;
}
}
}
diff --git a/f/web-kboss/src/permission.js b/f/web-kboss/src/permission.js
index 891d831..7420a79 100644
--- a/f/web-kboss/src/permission.js
+++ b/f/web-kboss/src/permission.js
@@ -11,7 +11,7 @@ import {getHomePath} from "@/views/setting/tools";
NProgress.configure({showSpinner: false}); // NProgress Configuration
-const whiteList = ["/login", "/homePage", "/registrationPage", "/shoppingCart", "/homePageImage","/h5HomePage",'/H5about','/modelProductDetail','/ncmatchHome']; // no redirect whitelist
+const whiteList = ["product","/login", "/homePage", "/registrationPage", "/shoppingCart", "/homePageImage","/h5HomePage",'/H5about','/modelProductDetail','/ncmatchHome']; // no redirect whitelist
// 获取用户代理字符串
const userAgent = window.navigator.userAgent;
diff --git a/f/web-kboss/src/router/index.js b/f/web-kboss/src/router/index.js
index cc39ae4..55b152d 100644
--- a/f/web-kboss/src/router/index.js
+++ b/f/web-kboss/src/router/index.js
@@ -404,11 +404,36 @@ export const constantRoutes = [
* 需要根据用户角色动态加载的路由
*/
export const asyncRoutes = [
+ // 运营——模型管理
+ {
+ path: "/modelManagement",
+ component: Layout,
+ meta: {
+ // title 是菜单上显示的文字,fullPath 用来和后端权限 path 对权限。
+ title: "模型管理",
+ fullPath: "/modelManagement",
+ noCache: true,
+ // icon 是左侧菜单图标,roles 限制只有运营角色能看到。
+ icon: "el-icon-cpu",
+ roles: ["运营"]
+ },
+ children: [
+ {
+ path: "",
+ component: () => import('@/views/modelManagement/modelManagement.vue'),
+ name: 'modelManagement',
+ meta: {
+ title: "模型管理",
+ fullPath: "/modelManagement",
+ noCache: true,
+ roles: ["运营"]
+ }
+ },
+ ]
+ },
-
- // 全部产品 - 一级菜单
- // 全部产品 - 一级菜单(无子路由)
+ // token市集 - 一级菜单(所有登录用户都能看到)
{
path: "/product",
component: Layout,
@@ -416,7 +441,7 @@ export const asyncRoutes = [
title: "全部产品",
fullPath: "/product",
noCache: true,
- icon: "el-icon-goods"
+ icon: "el-icon-coin"
},
children: [
{
@@ -431,6 +456,66 @@ export const asyncRoutes = [
},
]
},
+ // 令牌管理 - 一级菜单(所有登录用户都能看到)
+ {
+ path: "/tokenManagement",
+ component: Layout,
+ meta: {
+ title: "令牌管理",
+ fullPath: "/tokenManagement",
+ noCache: true,
+ icon: "el-icon-key"
+ },
+ children: [
+ {
+ path: "",
+ component: () => import('@/views/tokenManagement/index.vue'),
+ name: 'TokenManagement',
+ meta: {
+ title: "令牌管理",
+ fullPath: "/tokenManagement",
+ noCache: true,
+ icon: "el-icon-key"
+ }
+ },
+ ]
+ },
+ // 模型体验
+ {
+ path: "/modelExperience",
+ component: () => import('@/views/modelManagement/Experience.vue'),
+ hidden: true,
+ name: 'modelExperience',
+ meta: {
+ title: "模型体验",
+ fullPath: "/modelExperience",
+ noCache: true
+ },
+ },
+ // 模型详情
+ {
+ path: "/modelDetail",
+ component: () => import('@/views/modelManagement/ModelDetail.vue'),
+ hidden: true,
+ name: 'modelDetail',
+ meta: {
+ title: "模型详情",
+ fullPath: "/modelDetail",
+ noCache: true
+ },
+ },
+ // API文档
+ {
+ path: "/modelApiDocument",
+ component: () => import('@/views/modelManagement/ApiDocument.vue'),
+ hidden: true,
+ name: 'modelApiDocument',
+ meta: {
+ title: "API文档",
+ fullPath: "/modelApiDocument",
+ noCache: true
+ },
+ },
{
path: "/overview",
component: Layout,
@@ -453,6 +538,32 @@ export const asyncRoutes = [
}
]
},
+ // 运营——运营报表
+ {
+ path: "/operationReport",
+ component: Layout,
+ meta: {
+ title: "运营报表",
+ fullPath: "/operationReport",
+ noCache: true,
+ icon: "el-icon-data-analysis",
+ roles: ["运营"]
+ },
+ children: [
+ {
+ path: "",
+ component: () => import('@/views/operation/operationReport/index.vue'),
+ name: 'operationReport',
+ meta: {
+ title: "运营报表",
+ fullPath: "/operationReport",
+ noCache: true,
+ icon: "el-icon-data-analysis",
+ roles: ["运营"]
+ }
+ }
+ ]
+ },
{
path: "/orderManagement",
@@ -565,13 +676,15 @@ export const asyncRoutes = [
path: "/consultingMangement",
name: 'ConsultingMangement',
component: Layout,
- meta: { title: "咨询表单", fullPath: "/consultingMangement", noCache: true, icon: "el-icon-s-platform" },
+ // 咨询表单是表单/订单类入口,所以菜单图标用 el-icon-s-order。
+ meta: { title: "咨询表单", fullPath: "/consultingMangement", noCache: true, icon: "el-icon-s-order" },
children: [
{
path: "index",
component: () => import('@/views/operation/consultingMangement/index.vue'),
name: 'ConsultingMangement',
- meta: { title: "咨询表单", fullPath: "/consultingMangement/index", noCache: true, icon: "el-icon-s-platform" },
+ // 子路由也带 icon,单子菜单折叠成一级菜单时能继续显示图标。
+ meta: { title: "咨询表单", fullPath: "/consultingMangement/index", noCache: true, icon: "el-icon-s-order" },
}
]
},
@@ -1180,7 +1293,8 @@ export const asyncRoutes = [
component: Layout,
name: "qualificationReview",
redirect: "/qualificationReview/index",
- meta: { fullPath: "/qualificationReview", title: "资质审核", noCache: true, icon: 'el-icon-s-home' },
+ // 资质审核是审核/校验类菜单,所以用 el-icon-s-check。
+ meta: { fullPath: "/qualificationReview", title: "资质审核", noCache: true, icon: 'el-icon-s-check' },
children: [
{
path: "noApproveInfo",
@@ -1203,7 +1317,8 @@ export const asyncRoutes = [
component: Layout,
name: "approveMangement",
redirect: "/approveMangement/index",
- meta: { fullPath: "/approveMangement", title: "供需审核", noCache: true, icon: 'el-icon-s-home' },
+ // 供需审核表示供给和需求两边协作审核,所以用 el-icon-s-cooperation。
+ meta: { fullPath: "/approveMangement", title: "供需审核", noCache: true, icon: 'el-icon-s-cooperation' },
children: [
{
path: "pendingPro",
@@ -1263,12 +1378,14 @@ export const asyncRoutes = [
},
]
},
+
{
path: "/menuMangement",
component: Layout,
name: "menuMangement",
redirect: "/menuMangement/index",
- meta: { fullPath: "/menuMangement", title: "菜单管理", noCache: true, icon: 'el-icon-s-home' },
+ // 菜单管理就是维护菜单配置,用 Element UI 的菜单图标。
+ meta: { fullPath: "/menuMangement", title: "菜单管理", noCache: true, icon: 'el-icon-menu' },
children: [
{
path: "index",
@@ -1435,7 +1552,8 @@ export const asyncRoutes = [
{
path: "/operation", component: Layout, redirect: "/operation/supplierManagement", meta: {
- title: "运营", icon: "el-icon-s-tools", noCache: true, fullPath: "/operation",
+ // 运营是运营后台入口,用操作/运营类图标。
+ title: "运营", icon: "el-icon-s-operation", noCache: true, fullPath: "/operation",
}, children: [
{
@@ -1559,7 +1677,7 @@ export const asyncRoutes = [
hidden: true,
path: "colony",
- title: "管理集群",
+ title: "管理集群",
component: () => import("@/views/operation/computingCenterManagement/colony/index.vue"),
name: "supplierManagement",
meta: {
diff --git a/f/web-kboss/src/store/modules/permission.js b/f/web-kboss/src/store/modules/permission.js
index d302399..b59a9f4 100644
--- a/f/web-kboss/src/store/modules/permission.js
+++ b/f/web-kboss/src/store/modules/permission.js
@@ -1,24 +1,197 @@
-// permission.js - 修改后的完整代码
import { asyncRoutes, constantRoutes } from "@/router";
-// 获取用户代理字符串
-const userAgent = window.navigator.userAgent;
+// 用浏览器 UA 判断当前是不是手机端,后面会按 PC / 手机过滤菜单。
+const MOBILE_UA_REGEXP = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
-// 判断是否为移动设备
-const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent);
+// 项目里用到的固定角色名,集中放这里,避免代码里到处写字符串。
+const CUSTOMER_ROLE = '客户';
+const OPERATION_ROLE = '运营';
-// 如果是移动设备,添加移动端首页路由和根路径重定向
+// 这个用户能看到订单管理里的特殊子菜单,比如历史订单和订单详情。
+const SPECIAL_ORDER_USER = 'ZhipuHZ';
+
+// 超级管理员只放行这个一级菜单。
+const SUPER_ADMIN_ROUTE_PATH = '/superAdministrator';
+
+// 所有登录用户都能访问的公共路由,不依赖后端 auths 和角色。hidden 路由不会显示在菜单里。
+const COMMON_ROUTE_PATHS = ['/product', '/tokenManagement', '/modelExperience', '/modelDetail', '/modelApiDocument'];
+
+// 运营角色需要额外补出来的菜单。
+const OPERATION_EXTRA_ROUTE_PATHS = ['/modelManagement', '/operationReport'];
+
+// 普通客户账号默认要补出来的基础菜单。
+const BASE_USER_ROUTE_PATHS = ['/orderManagement', '/resourceManagement'];
+
+// 客户角色额外能看到的一级菜单。
+const CUSTOMER_EXTRA_ROUTE_PATHS = [
+ '/unsubscribeManagement',
+ '/informationPerfect',
+ '/rechargeManagement',
+ '/invoiceManagement',
+ '/workOrderManagement'
+];
+
+// 这些菜单只允许客户角色看到,非客户就算后端给了权限也不展示。
+const CUSTOMER_ONLY_ROUTE_PATHS = [
+ '/overview',
+ ...CUSTOMER_EXTRA_ROUTE_PATHS
+];
+
+// 客户登录后必须能看到的入口菜单,不完全依赖后端 auths 返回。
+const CUSTOMER_ALWAYS_VISIBLE_ROUTE_PATHS = ['/overview'];
+
+// 订单管理里只给 SPECIAL_ORDER_USER 看的子菜单 path。
+const ORDER_CHILDREN_ONLY_FOR_SPECIAL_USER = ['HistoricalOrders', 'orderDetails'];
+
+const isMobile = MOBILE_UA_REGEXP.test(window.navigator.userAgent);
+
+// 把角色统一整理成数组,兼容 undefined、数组、逗号字符串这几种写法。
+function normalizeRoles(roles) {
+ if (!roles) {
+ return [];
+ }
+
+ if (Array.isArray(roles)) {
+ return roles;
+ }
+
+ if (typeof roles === 'string') {
+ return roles.split(',').filter(Boolean);
+ }
+
+ return [];
+}
+
+// 从 sessionStorage 里取 roles,取不到或格式坏了就当成没有角色。
+function getSessionRoles() {
+ try {
+ return JSON.parse(sessionStorage.getItem('roles') || '[]');
+ } catch (error) {
+ console.warn('读取 roles 失败:', error);
+ return [];
+ }
+}
+
+// 汇总当前用户的所有角色来源:接口参数、vuex、sessionStorage、新旧字段。
+function getCurrentRoles(params, rootState) {
+ return [
+ ...normalizeRoles(params.roles),
+ ...normalizeRoles(rootState.user.roles),
+ ...normalizeRoles(getSessionRoles()),
+ ...normalizeRoles(sessionStorage.getItem('jueseNew'))
+ ];
+}
+
+// 判断当前用户是不是客户角色。
+function isCustomer(userRoles = []) {
+ return userRoles.includes(CUSTOMER_ROLE);
+}
+
+// 把布尔值转成更好读的设备类型,后面的判断都用 pc / mobile。
+function getDeviceType(isMobileDevice) {
+ return isMobileDevice ? 'mobile' : 'pc';
+}
+
+// 判断路由 meta.roles 是否满足。没写 roles 的路由默认所有角色都能继续往下判断。
+function hasRouteRole(route, userRoles = []) {
+ const routeRoles = route.meta?.roles;
+
+ if (!routeRoles || routeRoles.length === 0) {
+ return true;
+ }
+
+ return routeRoles.some(role => userRoles.includes(role));
+}
+
+// 根据设备过滤路由:手机只要手机路由,PC 不要手机专用路由。
+function isRouteAllowedByDevice(route, deviceType) {
+ if (deviceType === 'mobile') {
+ return route.meta?.isMobile || route.meta?.isMobile === true;
+ }
+
+ return route.meta?.isMobile !== true;
+}
+
+// 在一组路由里按 path 找某个路由。
+function findRouteByPath(routes, path) {
+ return routes.find(route => route.path === path);
+}
+
+// 后端 auths 里的 path 要和路由 meta.fullPath 对上,对上才算有权限。
+function routeHasPermission(route, permissions) {
+ return permissions.some(permission => permission.path === route.meta?.fullPath);
+}
+
+// 客户专属菜单要再卡一层客户角色,防止非客户误展示。
+function canShowCustomerOnlyRoute(route, userRoles) {
+ return !CUSTOMER_ONLY_ROUTE_PATHS.includes(route.path) || isCustomer(userRoles);
+}
+
+// 把所有动态路由的 fullPath 收集出来。后端返回 path 为空时,表示拥有全部权限。
+function getAllRoutePermissions(routes) {
+ const permissions = [];
+
+ routes.forEach(route => {
+ if (route.meta?.fullPath) {
+ permissions.push({ path: route.meta.fullPath });
+ }
+
+ if (route.children) {
+ permissions.push(...getAllRoutePermissions(route.children));
+ }
+ });
+
+ return permissions;
+}
+
+// 复制路由对象。这里不能用 JSON 深拷贝,因为路由里的 component 是函数,会被 JSON 丢掉。
+function cloneRoute(route) {
+ const clonedRoute = { ...route };
+
+ if (route.meta) {
+ clonedRoute.meta = { ...route.meta };
+ }
+
+ if (route.children) {
+ clonedRoute.children = route.children.map(cloneRoute);
+ }
+
+ return clonedRoute;
+}
+
+// 根据 path 列表批量找到对应路由,没找到的自动过滤掉。
+function getRoutesByPath(routes, paths) {
+ return paths
+ .map(path => findRouteByPath(routes, path))
+ .filter(Boolean);
+}
+
+// 判断这个一级路由是不是已经加过了,避免菜单重复出现。
+function shouldAppendRoute(accessedRoutes, route) {
+ return !accessedRoutes.some(item => item.path === route.path);
+}
+
+// 把缺少的路由补到最终菜单里,补之前会先去重并复制一份。
+function appendMissingRoutes(accessedRoutes, routesToAppend) {
+ routesToAppend.forEach(route => {
+ if (shouldAppendRoute(accessedRoutes, route)) {
+ accessedRoutes.push(cloneRoute(route));
+ }
+ });
+
+ return accessedRoutes;
+}
+
+// 如果是手机访问,额外把根路径导到 H5 首页,并注册 H5 首页菜单。
if (isMobile) {
console.log("检测到移动设备,添加移动端路由");
- // 先添加根路径重定向到移动端首页
constantRoutes.unshift({
path: '/',
redirect: '/h5HomePage',
hidden: true
});
- // 添加移动端首页路由
constantRoutes.push({
path: '/h5HomePage',
name: 'H5HomePage',
@@ -82,106 +255,165 @@ if (isMobile) {
});
}
-// 修复:更全面的路由过滤逻辑
+// 核心过滤函数:拿后端权限、角色和设备类型,一层层筛出最终可访问路由。
function filterAsyncRoutes(routes, permissions, userRoles = [], deviceType = 'pc') {
const res = [];
- // 定义需要客户角色才能访问的路由
- const customerOnlyRoutes = [
- "/product", "/overview", "/workOrderManagement",
- "/unsubscribeManagement", "/informationPerfect",
- "/rechargeManagement", "/invoiceManagement"
- ];
-
routes.forEach(route => {
- // 创建路由副本
- const tmpRoute = { ...route };
+ // 先复制一份,避免直接改原始 asyncRoutes。
+ const tmpRoute = cloneRoute(route);
- // 检查当前路由是否在权限列表中
- const hasPermission = permissions.some(p => p.path === route.meta?.fullPath);
-
- // 特殊处理:确保"全部产品"和"资源概览"这两个一级路由在客户角色下显示
- const isCriticalRoute = route.path === "/product" || route.path === "/overview";
-
- // 检查是否为仅客户可访问的路由
- const isCustomerOnlyRoute = customerOnlyRoutes.includes(route.path);
-
- // 如果路由需要客户角色,但用户不是客户,则跳过
- if (isCustomerOnlyRoute && !userRoles.includes('客户')) {
- return; // 跳过当前路由
+ // 第一步:角色不符合,或者客户专属菜单但当前用户不是客户,直接跳过。
+ if (!hasRouteRole(tmpRoute, userRoles) || !canShowCustomerOnlyRoute(route, userRoles)) {
+ return;
}
- // 新增:根据设备类型过滤路由
- if (deviceType === 'mobile' && !(route.meta?.isMobile || route.meta?.isMobile === true)) {
- return; // 移动设备跳过非移动端路由
- }
- if (deviceType === 'pc' && route.meta?.isMobile === true) {
- return; // PC设备跳过移动端路由
+ // 第二步:设备不符合也跳过,比如 PC 端不展示 H5 专用路由。
+ if (!isRouteAllowedByDevice(route, deviceType)) {
+ return;
}
- // 如果当前路由有权限,则加入结果
- if (hasPermission) {
+ // 第三步:看后端 auths 里有没有当前路由的 fullPath。
+ const hasPermission = routeHasPermission(route, permissions);
+
+ // 第四步:客户首页入口特殊处理,客户登录后默认展示。
+ const isAlwaysVisibleCustomerRoute =
+ CUSTOMER_ALWAYS_VISIBLE_ROUTE_PATHS.includes(route.path) && isCustomer(userRoles);
+
+ // 有权限,或者是客户默认入口,就把这个路由放进最终菜单。
+ if (hasPermission || isAlwaysVisibleCustomerRoute) {
res.push(tmpRoute);
- }
- // 如果是关键路由且用户是客户,也要加入结果
- else if (isCriticalRoute && userRoles.includes('客户')) {
- res.push(tmpRoute);
- }
- // 如果没有直接权限,但有子路由,递归处理子路由
- else if (tmpRoute.children) {
+ } else if (tmpRoute.children) {
+ // 父级没权限时继续看子级。只要子级有权限,父级也要保留,否则子菜单没地方挂。
const filteredChildren = filterAsyncRoutes(tmpRoute.children, permissions, userRoles, deviceType);
+
if (filteredChildren.length > 0) {
tmpRoute.children = filteredChildren;
- res.push(tmpRoute); // 即使父路由本身没有权限,只要有子路由有权限,也要保留父路由
+ res.push(tmpRoute);
}
}
- // 如果当前路由既没有权限,也没有有权限的子路由,则不添加到结果中
});
+
return res;
}
-// 新增:为普通用户添加订单管理和资源管理路由
+// 给普通用户和客户补充固定菜单:订单、资源,以及客户专属的工单/充值/发票等。
function addUserRoutes(routes, userType, orgType, userRoles = [], deviceType = 'pc') {
console.log("addUserRoutes - userType:", userType, "orgType:", orgType, "userRoles:", userRoles);
const userRoutes = [];
- // 修复:包含 orgType 为 2 和 3 的情况(公司客户和个人客户)
- if (userType === 'user' || orgType == 2 || orgType == 3) {
- const orderManagementRoute = routes.find(route => route.path === "/orderManagement");
- const resourceManagementRoute = routes.find(route => route.path === "/resourceManagement");
+ // orgType 为 2 或 3 时也按客户账号处理。
+ const isUserAccount = userType === 'user' || orgType == 2 || orgType == 3;
- // 新增:根据设备类型过滤
- if (orderManagementRoute && (deviceType === 'pc' || orderManagementRoute.meta?.isMobile === true)) {
- console.log("添加订单管理路由");
- userRoutes.push(JSON.parse(JSON.stringify(orderManagementRoute))); // 深拷贝
- }
+ if (isUserAccount) {
+ // 普通客户账号默认补订单管理和资源管理。
+ const baseUserRoutes = getRoutesByPath(routes, BASE_USER_ROUTE_PATHS)
+ .filter(route => isRouteAllowedByDevice(route, deviceType));
- if (resourceManagementRoute && (deviceType === 'pc' || resourceManagementRoute.meta?.isMobile === true)) {
- console.log("添加资源管理路由");
- userRoutes.push(JSON.parse(JSON.stringify(resourceManagementRoute))); // 深拷贝
- }
+ console.log("添加基础用户菜单路由:", baseUserRoutes.map(route => route.path));
+ userRoutes.push(...baseUserRoutes);
}
- // 新增:为所有用户添加五个新的客户菜单,但只有客户角色才能看到
- const newCustomerRoutes = [
- routes.find(route => route.path === "/unsubscribeManagement"),
- routes.find(route => route.path === "/informationPerfect"),
- routes.find(route => route.path === "/rechargeManagement"),
- routes.find(route => route.path === "/invoiceManagement"),
- routes.find(route => route.path === "/workOrderManagement")
- ].filter(route => {
- // 过滤掉undefined,并且只有客户角色才能看到这些路由
- return route && userRoles.includes('客户') &&
- (deviceType === 'pc' || route.meta?.isMobile === true);
- });
+ if (isCustomer(userRoles)) {
+ // 只有客户角色才补客户专属菜单。
+ const customerRoutes = getRoutesByPath(routes, CUSTOMER_EXTRA_ROUTE_PATHS)
+ .filter(route => isRouteAllowedByDevice(route, deviceType));
- console.log("添加新的客户菜单路由:", newCustomerRoutes.map(r => r.path));
- userRoutes.push(...newCustomerRoutes);
+ console.log("添加客户菜单路由:", customerRoutes.map(route => route.path));
+ userRoutes.push(...customerRoutes);
+ }
return userRoutes;
}
+// 运营角色额外补模型管理菜单,目前只在 PC 端展示。
+function addOperationRoutes(accessedRoutes, routes, userRoles = [], deviceType = 'pc') {
+ if (!userRoles.includes(OPERATION_ROLE) || deviceType !== 'pc') {
+ return accessedRoutes;
+ }
+
+ return appendMissingRoutes(accessedRoutes, getRoutesByPath(routes, OPERATION_EXTRA_ROUTE_PATHS));
+}
+
+// token市集是公共菜单,所有登录用户都要能看到。
+function addCommonRoutes(accessedRoutes, routes, deviceType = 'pc') {
+ const commonRoutes = getRoutesByPath(routes, COMMON_ROUTE_PATHS)
+ .filter(route => isRouteAllowedByDevice(route, deviceType));
+
+ return appendMissingRoutes(accessedRoutes, commonRoutes);
+}
+
+// 订单管理有两个特殊子菜单,只有 SPECIAL_ORDER_USER 能看到,其他用户过滤掉。
+function filterOrderChildrenByUser(routes, username) {
+ if (username === SPECIAL_ORDER_USER) {
+ console.log(`用户 ${username} 是 ${SPECIAL_ORDER_USER},保留所有订单子路由`);
+ return routes;
+ }
+
+ return routes.map(route => {
+ const nextRoute = cloneRoute(route);
+
+ // 找到订单管理后,移除特殊用户专属的子菜单。
+ if (nextRoute.path === '/orderManagement' && nextRoute.children) {
+ console.log(`用户 ${username} 不是 ${SPECIAL_ORDER_USER},过滤订单管理子路由`);
+ nextRoute.children = nextRoute.children.filter(child =>
+ !ORDER_CHILDREN_ONLY_FOR_SPECIAL_USER.includes(child.path)
+ );
+ console.log('过滤后订单子路由:', nextRoute.children.map(child => child.path));
+ }
+
+ if (nextRoute.children) {
+ // 子路由里如果还有订单管理,也继续递归处理。
+ nextRoute.children = filterOrderChildrenByUser(nextRoute.children, username);
+ }
+
+ return nextRoute;
+ });
+}
+
+// 整理后端权限列表。如果包含空 path,就按“拥有全部动态路由权限”处理。
+function getPermissionList(auths = []) {
+ const permissions = JSON.parse(JSON.stringify(auths));
+ const permissionPaths = permissions.map(item => item.path);
+
+ if (permissionPaths.includes('')) {
+ return getAllRoutePermissions(asyncRoutes);
+ }
+
+ return permissions;
+}
+
+// 根据后端 auths 生成第一版可访问路由。没有 auths 就不展示动态菜单。
+function getAccessedRoutesByPermission(auths, userRoles, deviceType) {
+ if (!auths.length) {
+ return [];
+ }
+
+ const permissions = getPermissionList(auths);
+ return filterAsyncRoutes(asyncRoutes, permissions, userRoles, deviceType);
+}
+
+// 判断是不是超级管理员账号:用户名包含 admin,并且不是客户组织。
+function isSuperAdminUser(username, orgType) {
+ return username && username.includes('admin') && orgType != 2 && orgType != 3;
+}
+
+// 超级管理员只拿超级管理员菜单;手机端不展示这个菜单。
+function getSuperAdminRoutes(deviceType) {
+ if (deviceType !== 'pc') {
+ return [];
+ }
+
+ return getRoutesByPath(asyncRoutes, [SUPER_ADMIN_ROUTE_PATH]).map(cloneRoute);
+}
+
+// 在已有权限菜单基础上,补充用户类型/客户角色需要固定展示的菜单。
+function addUserSpecificRoutes(accessedRoutes, userType, orgType, userRoles, deviceType) {
+ const userSpecificRoutes = addUserRoutes(asyncRoutes, userType, orgType, userRoles, deviceType);
+ return appendMissingRoutes(accessedRoutes, userSpecificRoutes);
+}
+
const state = {
routes: [],
addRoutes: [],
@@ -192,12 +424,19 @@ const state = {
const mutations = {
SET_ROUTES: (state, routes) => {
console.log("MUTATION SET_ROUTES - received routes:", routes);
+ // addRoutes 只保存动态生成的菜单,方便 router.addRoutes 使用。
state.addRoutes = routes;
sessionStorage.setItem("routes", JSON.stringify(routes));
- // 将移动端首页路由也包含在内
+ // routes 是侧边栏最终读取的数据:基础路由 + 动态权限路由。
state.routes = constantRoutes.concat(routes);
console.log("MUTATION SET_ROUTES - final state.routes:", state.routes);
},
+ RESET_ROUTES: (state) => {
+ // 退出登录或切换账号时,必须清掉内存里的旧菜单,否则不刷新页面会继续显示上个角色的菜单。
+ state.routes = [];
+ state.addRoutes = [];
+ sessionStorage.removeItem("routes");
+ },
SETUSERS: (state, user) => {
state.users = user;
},
@@ -226,131 +465,46 @@ const actions = {
generateRoutes({ commit, rootState, state }, params) {
console.log("ACTION generateRoutes - params:", params);
return new Promise((resolve) => {
- let accessedRoutes;
-
- // 从参数或sessionStorage中获取用户类型和组织类型
+ // 1. 先拿到用户基础信息,优先用传进来的参数,没有就从 sessionStorage / vuex 兜底。
const userType = params.userType || sessionStorage.getItem('userType') || '';
const orgType = params.orgType || parseInt(sessionStorage.getItem('orgType')) || 0;
-
- // 获取用户角色(从store或sessionStorage)
- const userRoles = rootState.user.roles || JSON.parse(sessionStorage.getItem('roles') || '[]');
- console.log("用户角色:", userRoles);
-
- // 获取用户名
const username = params.user || rootState.user.user || '';
- console.log("当前用户名:", username, "检查是否是ZhipuHZ:", username === 'ZhipuHZ');
+ const userRoles = getCurrentRoles(params, rootState);
+ const deviceType = getDeviceType(state.isMobile);
+ const auths = params.auths ? JSON.parse(JSON.stringify(params.auths)) : [];
- console.log("用户类型:", userType, "orgType:", orgType);
+ // 2. 判断是不是超级管理员,超级管理员走单独菜单逻辑。
+ const isSuperAdmin = isSuperAdminUser(params.user, orgType);
- // 确定设备类型
- const deviceType = state.isMobile ? 'mobile' : 'pc';
- console.log("设备类型:", deviceType);
+ console.log("用户角色:", userRoles);
+ console.log("当前用户名:", username, `检查是否是${SPECIAL_ORDER_USER}:`, username === SPECIAL_ORDER_USER);
+ console.log("用户类型:", userType, "orgType:", orgType, "设备类型:", deviceType);
+ console.log("ACTION generateRoutes - auths:", auths);
- // 修复:包含 orgType 为 2 和 3 的情况
- if (params.user && params.user.includes("admin") && orgType != 2 && orgType != 3) {
- // 管理员:只显示超级管理员菜单(仅PC端)
- if (deviceType === 'pc') {
- accessedRoutes = asyncRoutes.filter(item => item.path === '/superAdministrator');
- } else {
- accessedRoutes = [];
- }
- } else {
- const auths = params.auths ? JSON.parse(JSON.stringify(params.auths)) : [];
- console.log("ACTION generateRoutes - auths:", auths);
+ // 3. 先生成第一版菜单:超级管理员只拿超管菜单,普通用户按后端 auths 过滤。
+ let accessedRoutes = isSuperAdmin
+ ? getSuperAdminRoutes(deviceType)
+ : getAccessedRoutesByPermission(auths, userRoles, deviceType);
- if (auths.length) {
- // 确保 auths 中的 path 与路由 meta.fullPath 匹配
- const paths = auths.map((item) => {
- return item.path;
- });
- console.log("ACTION generateRoutes - paths from auths:", paths);
+ // 4. token市集是公共入口,所有登录用户都补上。
+ accessedRoutes = addCommonRoutes(accessedRoutes, asyncRoutes, deviceType);
- if (paths.includes("")) {
- // 如果权限列表包含空路径,认为用户有所有权限
- accessedRoutes = asyncRoutes || [];
- } else {
- // 传入用户角色和设备类型
- accessedRoutes = filterAsyncRoutes(asyncRoutes, auths, userRoles, deviceType);
- }
- } else {
- // 如果没有权限列表,不显示任何动态路由
- accessedRoutes = [];
- }
-
- // 为普通用户添加订单管理和资源管理路由以及新的五个客户菜单
+ if (!isSuperAdmin) {
+ // 5. 普通用户再补一些固定入口,比如订单、资源、客户专属菜单。
console.log("为用户添加特定路由");
- const userSpecificRoutes = addUserRoutes(asyncRoutes, userType, orgType, userRoles, deviceType);
-
- // 确保不重复添加路由,同时检查角色权限
- userSpecificRoutes.forEach(route => {
- const isCustomerRoute = [
- "/workOrderManagement", "/unsubscribeManagement", "/informationPerfect",
- "/rechargeManagement", "/invoiceManagement"
- ].includes(route.path);
-
- // 如果是客户路由但用户不是客户,则不添加
- if (isCustomerRoute && !userRoles.includes('客户')) {
- return;
- }
-
- if (!accessedRoutes.some(r => r.path === route.path)) {
- accessedRoutes.push(route);
- }
- });
-
+ accessedRoutes = addUserSpecificRoutes(accessedRoutes, userType, orgType, userRoles, deviceType);
console.log("添加用户特定路由后的accessedRoutes:", accessedRoutes);
}
- // ========== 暴力过滤:直接修改 accessedRoutes ==========
- // 遍历所有路由,找到 /orderManagement 路由,然后过滤它的子路由
- accessedRoutes = accessedRoutes.map(route => {
- if (route.path === "/orderManagement") {
- console.log("找到订单管理路由,准备过滤子路由,用户名:", username);
+ // 6. 运营角色额外补模型管理。
+ accessedRoutes = addOperationRoutes(accessedRoutes, asyncRoutes, userRoles, deviceType);
- // 创建路由副本
- const newRoute = { ...route };
-
- if (newRoute.children) {
- // 如果不是 ZhipuHZ 用户,过滤掉 HistoricalOrders 和 orderDetails 路由
- if (username !== 'ZhipuHZ') {
- console.log(`用户 ${username} 不是 ZhipuHZ,过滤订单管理子路由`);
- newRoute.children = newRoute.children.filter(child =>
- child.path !== 'HistoricalOrders' && child.path !== 'orderDetails'
- );
- console.log(`过滤后子路由:`, newRoute.children.map(c => c.path));
- } else {
- console.log(`用户 ${username} 是 ZhipuHZ,保留所有子路由`);
- }
- }
-
- return newRoute;
- }
-
- // 对于其他路由,保持原样
- return route;
- });
-
- // 再次检查,确保没有遗漏的任何 orderManagement 路由
- accessedRoutes.forEach(route => {
- if (route.children) {
- route.children = route.children.filter(child => {
- // 如果子路由是 orderManagement,也需要处理
- if (child.path === "/orderManagement") {
- console.log("在子路由中找到订单管理路由,准备过滤,用户名:", username);
-
- if (child.children && username !== 'ZhipuHZ') {
- child.children = child.children.filter(grandChild =>
- grandChild.path !== 'HistoricalOrders' && grandChild.path !== 'orderDetails'
- );
- }
- }
- return true;
- });
- }
- });
+ // 7. 最后处理订单管理里的特殊子菜单权限。
+ accessedRoutes = filterOrderChildrenByUser(accessedRoutes, username);
console.log("ACTION generateRoutes - 最终 calculated accessedRoutes:", accessedRoutes);
+ // 8. 保存到 vuex 和 sessionStorage,侧边栏会读取 state.permission.routes。
commit("SET_ROUTES", accessedRoutes);
resolve(accessedRoutes);
});
diff --git a/f/web-kboss/src/store/modules/user.js b/f/web-kboss/src/store/modules/user.js
index 22b8fec..c91ee87 100644
--- a/f/web-kboss/src/store/modules/user.js
+++ b/f/web-kboss/src/store/modules/user.js
@@ -13,6 +13,13 @@ const safeToString = (value, defaultValue = '') => {
return value.toString();
};
+const normalizeLoginRoles = (roles) => {
+ if (!roles || roles === 'None') return [];
+ if (Array.isArray(roles)) return roles;
+ if (typeof roles === 'string') return roles.split(',').filter(Boolean);
+ return [];
+};
+
// 从sessionStorage恢复状态
const getStoredState = () => {
return {
@@ -130,8 +137,11 @@ const actions = {
// 修复:org_type 为 2 或 3 都表示客户
const userType = (org_type == 2 || org_type == 3) ? 'user' : 'admin';
- // 设置用户角色 - 如果是客户,则添加'客户'角色
- const userRoles = (org_type == 2 || org_type == 3) ? ['客户'] : ['管理员'];
+ // 使用接口返回的真实角色生成菜单;客户组织兜底补上“客户”角色。
+ const userRoles = normalizeLoginRoles(response.roles);
+ if ((org_type == 2 || org_type == 3) && !userRoles.includes('客户')) {
+ userRoles.push('客户');
+ }
commit("SET_USER_TYPE", userType);
// 确保 org_type 不为 undefined
@@ -141,6 +151,8 @@ const actions = {
console.log("登录用户类型:", userType, "org_type:", org_type, "用户角色:", userRoles);
data ? commit("SET_AUTHS", data) : commit("SET_AUTHS", []);
+ resetRouter();
+ commit("permission/RESET_ROUTES", null, { root: true });
const accessRoutes = await store.dispatch(
"permission/generateRoutes",
{
@@ -151,7 +163,6 @@ const actions = {
roles: userRoles // 新增:传递角色信息
}
)
- resetRouter();
router.addRoutes(accessRoutes);
resolve(response);
}
@@ -215,6 +226,7 @@ const actions = {
commit("SET_AUTHS", []);
removeToken();
resetRouter();
+ commit("permission/RESET_ROUTES", null, { root: true });
// 清除sessionStorage
sessionStorage.removeItem('user');
@@ -223,6 +235,8 @@ const actions = {
sessionStorage.removeItem('orgType');
sessionStorage.removeItem('mybalance');
sessionStorage.removeItem('roles'); // 新增:清除角色信息
+ sessionStorage.removeItem('juese');
+ sessionStorage.removeItem('jueseNew');
// reset visited views and cached views
// to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2485
@@ -243,6 +257,8 @@ const actions = {
commit("SET_USER", "");
commit("SET_AUTHS", []);
removeToken();
+ resetRouter();
+ commit("permission/RESET_ROUTES", null, { root: true });
// 清除sessionStorage
sessionStorage.removeItem('user');
@@ -250,6 +266,8 @@ const actions = {
sessionStorage.removeItem('userType');
sessionStorage.removeItem('orgType');
sessionStorage.removeItem('roles'); // 新增:清除角色信息
+ sessionStorage.removeItem('juese');
+ sessionStorage.removeItem('jueseNew');
resolve();
});
},
diff --git a/f/web-kboss/src/styles/sidebar.scss b/f/web-kboss/src/styles/sidebar.scss
index 8fd79e6..875de5f 100644
--- a/f/web-kboss/src/styles/sidebar.scss
+++ b/f/web-kboss/src/styles/sidebar.scss
@@ -25,7 +25,7 @@
// reset element-ui css
.horizontal-collapse-transition {
- transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
+ transition: width .28s ease, padding-left .28s ease, padding-right .28s ease;
}
.scrollbar-wrapper {
@@ -100,11 +100,11 @@
.hideSidebar {
.sidebar-container {
- width: 54px !important;
+ width: 64px !important;
}
.main-container {
- margin-left: 54px;
+ margin-left: 64px;
}
.submenu-title-noDropdown {
diff --git a/f/web-kboss/src/views/homePage/components/topBox/index.vue b/f/web-kboss/src/views/homePage/components/topBox/index.vue
index 6a4af0d..4de7b51 100644
--- a/f/web-kboss/src/views/homePage/components/topBox/index.vue
+++ b/f/web-kboss/src/views/homePage/components/topBox/index.vue
@@ -20,9 +20,9 @@
- 产品与服务
+ 基础云
- 模型广场
+ token市集
元境
@@ -116,7 +116,11 @@
-
-
+
{{
item.firTitle
}}
@@ -255,6 +259,7 @@ import store from "@/store";
import { getHomePath } from '@/views/setting/tools'
import MessageCenter from '@/components/MessageCenter/MessageCenter.vue'
import { reqAIChat } from '@/api/AI/ai'
+import { gotoYuanJingAPI } from '@/api/gotoYuanJing'
export default Vue.extend({
name: "TopBox",
@@ -376,9 +381,85 @@ export default Vue.extend({
}
},
methods: {
- // 跳转元境 https://ai.opencomputing.cn/#/index
- goYuanjing() {
- window.open('https://ai.opencomputing.cn/#/index')
+ // 点击模型广场前校验登录状态
+ handleModelSquareClick() {
+ if (!this.loginState) {
+ this.$message.warning('请先登录哦~')
+ return
+ }
+ this.$router.push('/product')
+ },
+
+ // 跳转元境
+ async goYuanjing() {
+ if (!this.loginState) {
+ this.$message.warning('请先登录哦~')
+ return
+ }
+
+ const yuanJingWindow = window.open('', '_blank')
+
+ try {
+ const res = await gotoYuanJingAPI({
+ user_id: sessionStorage.getItem('userId')
+ })
+
+ const deerer = this.getYuanJingAuthorization(res)
+
+ if (!deerer) {
+ if (yuanJingWindow) {
+ yuanJingWindow.close()
+ }
+ this.$message.error((res && res.msg) || '获取元境授权参数失败')
+ return
+ }
+
+ const loginUrl = `https://ai.opencomputing.cn/#/getCookie?deerer=${encodeURIComponent(deerer)}`
+
+ if (yuanJingWindow) {
+ yuanJingWindow.location.href = loginUrl
+ } else {
+ window.location.href = loginUrl
+ }
+ } catch (error) {
+ if (yuanJingWindow) {
+ yuanJingWindow.close()
+ }
+ this.$message.error('跳转元境失败,请稍后重试')
+ }
+ },
+ getYuanJingAuthorization(res) {
+ if (!res) {
+ return ''
+ }
+
+ if (typeof res === 'string') {
+ return res
+ }
+
+ const data = res.data || res
+ if (typeof data === 'string') {
+ return data
+ }
+
+ return data.Authorization || data.authorization || data.token || data.header || data.value || ''
+ },
+ isPanelFirClickable(item) {
+ const title = (item && item.firTitle) || ''
+ return title === '元境' || title === 'TOKEN市集' || title === 'token市集'
+ },
+ handlePanelFirClick(item) {
+ const title = (item && item.firTitle) || ''
+ if (title === '元境') {
+ this.$store.commit('setShowHomeNav', false)
+ this.goYuanjing()
+ return
+ }
+
+ if (title === 'TOKEN市集' || title === 'token市集') {
+ this.$store.commit('setShowHomeNav', false)
+ this.handleModelSquareClick()
+ }
},
// 处理AI助手点击
handleAIClick() {
@@ -668,11 +749,17 @@ export default Vue.extend({
async logout() {
this.$store.commit('setLoginState', false)
store.commit('tagsView/resetBreadcrumbState');
+ store.commit('permission/RESET_ROUTES');
sessionStorage.removeItem("auths");
sessionStorage.removeItem("routes");
sessionStorage.removeItem("user");
sessionStorage.removeItem("userId");
sessionStorage.removeItem("org_type")
+ sessionStorage.removeItem("userType");
+ sessionStorage.removeItem("orgType");
+ sessionStorage.removeItem("roles");
+ sessionStorage.removeItem("juese");
+ sessionStorage.removeItem("jueseNew");
localStorage.removeItem("auths");
localStorage.removeItem("routes");
localStorage.removeItem("user");
diff --git a/f/web-kboss/src/views/homePage/ncmatch/mainPage/index.vue b/f/web-kboss/src/views/homePage/ncmatch/mainPage/index.vue
index 027a822..aa78a8b 100644
--- a/f/web-kboss/src/views/homePage/ncmatch/mainPage/index.vue
+++ b/f/web-kboss/src/views/homePage/ncmatch/mainPage/index.vue
@@ -1,58 +1,140 @@
-
+
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
NCMatch
-
算力供需对接平台
+
+
+
+
+
+
+
+
+
+
好用还省钱,Token 就上开元云
+
石景山OPC公共服务平台
+
+ 为 OPC 而生,极致性价比一站式模型平台
+
+
+
+
+
+
+
+
-
+
Token市集
+
汇聚海量精品模型,以更低成本畅享极致 AI 体验
+
+
+
+
+
+
+
+
+
+
创镜工坊
+
以文筑境,以镜生画,全场景 AI 影像创作
+
+
+
+
+
+
+
+
+
+
云枢基座
+
深耕基础云服务,筑牢 AI 平台数字根基
+
-
-
-
+
-
+
-
-
+ -->
@@ -91,6 +173,7 @@
import Vue from 'vue'
import { reqPublishProductSearchFirstPage, reqEnterpriseAuditInfoSearch, reqHomepageProductCategory, reqGetSupplyAndDemandSquareList } from '@/api/ncmatch'
import { mapGetters, mapState } from "vuex";
+import { gotoYuanJingAPI } from '@/api/gotoYuanJing'
export default Vue.extend({
name: "mainPage",
@@ -153,6 +236,59 @@ export default Vue.extend({
this.showTip = false
this.$router.push('/customer/approve')
},
+ async goCreativeWorkshop() {
+ if (!this.loginState) {
+ this.$message.warning('请先登录哦~')
+ return
+ }
+
+ const yuanJingWindow = window.open('', '_blank')
+
+ try {
+ const res = await gotoYuanJingAPI({
+ user_id: sessionStorage.getItem('userId')
+ })
+
+ const deerer = this.getYuanJingAuthorization(res)
+
+ if (!deerer) {
+ if (yuanJingWindow) {
+ yuanJingWindow.close()
+ }
+ this.$message.error((res && res.msg) || '获取元境授权参数失败')
+ return
+ }
+
+ const loginUrl = `https://ai.opencomputing.cn/#/getCookie?deerer=${encodeURIComponent(deerer)}`
+
+ if (yuanJingWindow) {
+ yuanJingWindow.location.href = loginUrl
+ } else {
+ window.location.href = loginUrl
+ }
+ } catch (error) {
+ if (yuanJingWindow) {
+ yuanJingWindow.close()
+ }
+ this.$message.error('跳转元境失败,请稍后重试')
+ }
+ },
+ getYuanJingAuthorization(res) {
+ if (!res) {
+ return ''
+ }
+
+ if (typeof res === 'string') {
+ return res
+ }
+
+ const data = res.data || res
+ if (typeof data === 'string') {
+ return data
+ }
+
+ return data.Authorization || data.authorization || data.token || data.header || data.value || ''
+ },
handleCurrentChange(val) {
this.current_page = val
this.initData()
@@ -265,221 +401,579 @@ export default Vue.extend({
diff --git a/f/web-kboss/src/views/modelManagement/ApiDocument.vue b/f/web-kboss/src/views/modelManagement/ApiDocument.vue
new file mode 100644
index 0000000..62bba37
--- /dev/null
+++ b/f/web-kboss/src/views/modelManagement/ApiDocument.vue
@@ -0,0 +1,243 @@
+
+
+
+
+
+
+ MiniMax-M2.5 API 文档
+ 通过 OpenAI 兼容接口接入模型能力,支持对话生成、工具调用和流式输出。
+
+ {{ item }}
+
+
+
+
+ 1. 接口地址
+ 统一使用 HTTPS 请求,所有接口都需要携带平台签发的 API Key。
+ POST https://api.kboss.example.com/v2/chat/completions
+
+
+
+ 2. 模型能力列表
+
+
+
+
+
+
+ {{ scope.row.status }}
+
+
+
+
+
+
+
+
+
+ 4. 请求示例
+ {{ requestExample }}
+
+
+
+ 5. 返回示例
+ {{ responseExample }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/f/web-kboss/src/views/modelManagement/Experience.vue b/f/web-kboss/src/views/modelManagement/Experience.vue
new file mode 100644
index 0000000..c21828f
--- /dev/null
+++ b/f/web-kboss/src/views/modelManagement/Experience.vue
@@ -0,0 +1,546 @@
+
+
+
+
+
+
+
+
+
+
![logo]()
+
你好,我是模型体验助手
+
可以输入问题体验模型效果,也可以点击下方示例快速开始。
+
+
+
+
+
+
+
+
![logo]()
+
+
+
+
{{ message.role === 'assistant' ? '模型助手' : '我' }}
+
{{ message.content }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/f/web-kboss/src/views/modelManagement/ModelDetail.vue b/f/web-kboss/src/views/modelManagement/ModelDetail.vue
new file mode 100644
index 0000000..04f9165
--- /dev/null
+++ b/f/web-kboss/src/views/modelManagement/ModelDetail.vue
@@ -0,0 +1,349 @@
+
+
+
+
+
+ Token市集
+
+ 控制台
+ 用户后台
+
+
+
+
+
+
+
+
+
+
+
{{ modelInfo.name }}
+ 深度推理
+
+
+ 中文
+ 英文
+ 128K上下文
+
+
{{ modelInfo.description }}
+
+
+
+
+
+
版本介绍
+
+ 模型ID:{{ modelInfo.modelId }}
+
+
+
当前版本能力稳定,适合内容生成、知识问答、工具调用和复杂任务规划。
+
+
+ API文档
+ 体验
+
+
+
+
+
+
模型能力
+
+ {{ item.label }}
+ {{ item.value }}
+
+
+
+
+
模型限制
+
+ {{ item.label }}
+ {{ item.value }}
+
+
+
+
+
+ 服务价格
+
+
+ 模型输入
+ 0.0021
+ 元/千Tokens
+
+
+ 模型输出
+ 0.0084
+ 元/千Tokens
+
+
+
+
+
+ 1. 模型介绍
+ {{ modelInfo.longDescription }}
+
+ 2. 模型亮点
+
+
+
+
推理
+
擅长处理数学、代码、逻辑分析和复杂任务拆解,适合作为业务助手和智能问答底座。
+
+
+
+
+
+
模型调优
+
提供稳定的上下文理解能力,便于后续结合业务数据进行知识增强和场景优化。
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/f/web-kboss/src/views/modelManagement/modelManagement.vue b/f/web-kboss/src/views/modelManagement/modelManagement.vue
new file mode 100644
index 0000000..2da7a2c
--- /dev/null
+++ b/f/web-kboss/src/views/modelManagement/modelManagement.vue
@@ -0,0 +1,580 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ getModelId(scope.row) }}
+
+
+
+
+
+ {{ getModelDisplayName(scope.row) }}
+
+
+
+
+
+
+
+ {{ getModelType(scope.row) }}
+
+ -
+
+
+
+
+
+ {{ getProvider(scope.row) }}
+
+
+
+
+
+
+
输入价格: {{ formatPriceText(getInputPrice(scope.row)) }}
+
输出价格: {{ formatPriceText(getOutputPrice(scope.row)) }}
+
+
+
+
+
+
+
+ {{ scope.row.billing_method || '-' }}
+
+
+
+
+
+
+
+ {{ getUpdateTime(scope.row) }}
+
+
+
+
+
+
+ {{ getListingStatusText(scope.row.listing_status) }}
+
+
+
+
+
+
+
+ 置顶
+ 下移
+
+
+
+
+
+
+ 详情
+
+ 编辑
+ 上架
+
+
+ 下架
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/f/web-kboss/src/views/operation/operationReport/index.vue b/f/web-kboss/src/views/operation/operationReport/index.vue
new file mode 100644
index 0000000..688f2c8
--- /dev/null
+++ b/f/web-kboss/src/views/operation/operationReport/index.vue
@@ -0,0 +1,197 @@
+
+
+
+
+
+
+
活跃用户
+
{{ statCards.activeUsers }}
+
+
+
Token消耗
+
{{ statCards.tokenUsage }}
+
+
+
Tokens总费用
+
¥{{ statCards.totalFee }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 查询
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+ {{ scope.row.modelName }}
+
+
+
+
+
+
+
+
+
+ {{ scope.row.paymentMethod }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/f/web-kboss/src/views/product/allProduct/index.vue b/f/web-kboss/src/views/product/allProduct/index.vue
index 220e37a..5951174 100644
--- a/f/web-kboss/src/views/product/allProduct/index.vue
+++ b/f/web-kboss/src/views/product/allProduct/index.vue
@@ -1,5 +1,40 @@
+
@@ -8,7 +43,7 @@
class="nav-item"
:class="{ active: activeCategory === category.firTitle }"
@click="switchCategory(category)">
- {{ category.firTitle }}
+ {{ category.firTitle }}
@@ -16,7 +51,18 @@