boss 2 дней назад
Родитель
Сommit
7e4f7bed17
39 измененных файлов с 1250 добавлено и 583 удалено
  1. 10 0
      src/api/admin/consumeReport.js
  2. 2 2
      src/api/admin/voiceNumber.js
  3. 3 3
      src/api/admin/voiceOrder.js
  4. 3 3
      src/api/admin/voicePackage.js
  5. 4 4
      src/api/company/companyVoiceApi.js
  6. 7 7
      src/api/company/companyVoiceCaller.js
  7. 7 7
      src/api/company/companyVoiceRoboticCallBlacklist.js
  8. 1 1
      src/assets/styles/index.scss
  9. 8 6
      src/assets/styles/sidebar.scss
  10. 486 257
      src/layout/AdminLayout.vue
  11. 34 24
      src/views/admin/article/index.vue
  12. 7 7
      src/views/admin/callRecord/index.vue
  13. 9 9
      src/views/admin/commissionRecord/index.vue
  14. 219 0
      src/views/admin/consumeReport/index.vue
  15. 35 25
      src/views/admin/course/index.vue
  16. 22 22
      src/views/admin/dailyStatistics/index.vue
  17. 3 3
      src/views/admin/keywordManage/index.vue
  18. 36 26
      src/views/admin/live/index.vue
  19. 31 17
      src/views/admin/liveVideo/index.vue
  20. 20 4
      src/views/admin/menu.js
  21. 38 26
      src/views/admin/moduleUsage/index.vue
  22. 36 26
      src/views/admin/product/index.vue
  23. 12 12
      src/views/admin/proxy/index.vue
  24. 10 10
      src/views/admin/sms/index.vue
  25. 10 10
      src/views/admin/smsOrder/index.vue
  26. 1 1
      src/views/admin/smsPackage/index.vue
  27. 25 12
      src/views/admin/storeOrder/index.vue
  28. 15 15
      src/views/admin/sysCompany/index.vue
  29. 2 4
      src/views/admin/textModel/index.vue
  30. 27 13
      src/views/admin/videoResource/index.vue
  31. 12 1
      src/views/admin/voice/index.vue
  32. 13 7
      src/views/admin/voiceApi/index.vue
  33. 8 2
      src/views/admin/voiceBlacklist/index.vue
  34. 7 1
      src/views/admin/voiceOrder/index.vue
  35. 12 1
      src/views/admin/voicePackage/index.vue
  36. 7 1
      src/views/admin/voiceSeat/index.vue
  37. 25 7
      src/views/lobster/sales-corpus/index.vue
  38. 28 7
      src/views/lobster/workflow-generate/index.vue
  39. 15 0
      vue.config.js

+ 10 - 0
src/api/admin/consumeReport.js

@@ -0,0 +1,10 @@
+import request from '@/utils/request'
+
+// 获取模块消费统计报告(按租户+模块交叉汇总)
+export function getConsumptionReport(params) {
+  return request({
+    url: '/admin/module-consumption/report',
+    method: 'get',
+    params
+  })
+}

+ 2 - 2
src/api/admin/voiceNumber.js

@@ -1,9 +1,9 @@
 import request from '@/utils/request'
 
-// 查询CID主叫号码列表
+// 查询CID主叫号码列表(admin专用,走 /admin/ 前缀,路由到 fs-admin 8004)
 export function listCidNumber(query) {
   return request({
-    url: '/company/companyVoiceRobotic/cidList',
+    url: '/admin/voice-number/list',
     method: 'get',
     params: query
   })

+ 3 - 3
src/api/admin/voiceOrder.js

@@ -1,9 +1,9 @@
 import request from '@/utils/request'
 
-// 查询通话套餐订单列表
+// 查询通话套餐订单列表(admin专用,走 /admin/ 前缀,路由到 fs-admin 8004)
 export function listVoiceOrder(query) {
   return request({
-    url: '/company/companyVoicePackageOrder/list',
+    url: '/admin/voice-order/list',
     method: 'get',
     params: query
   })
@@ -12,7 +12,7 @@ export function listVoiceOrder(query) {
 // 导出通话套餐订单
 export function exportVoiceOrder(query) {
   return request({
-    url: '/company/companyVoicePackageOrder/export',
+    url: '/admin/voice-order/export',
     method: 'get',
     params: query,
     responseType: 'blob'

+ 3 - 3
src/api/admin/voicePackage.js

@@ -1,9 +1,9 @@
 import request from '@/utils/request'
 
-// 查询通话套餐列表
+// 查询通话套餐列表(admin专用,走 /admin/ 前缀,路由到 fs-admin 8004)
 export function listVoicePackage(query) {
   return request({
-    url: '/company/companyVoicePackageOrder/getVoicePackagelist',
+    url: '/admin/voice-package/list',
     method: 'get',
     params: query
   })
@@ -12,7 +12,7 @@ export function listVoicePackage(query) {
 // 查询通话套餐详情
 export function getVoicePackage(id) {
   return request({
-    url: '/company/companyVoicePackageOrder/' + id,
+    url: '/admin/voice-package/' + id,
     method: 'get'
   })
 }

+ 4 - 4
src/api/company/companyVoiceApi.js

@@ -1,16 +1,16 @@
 import request from '@/utils/request'
 
-// 查询呼叫接口列表
+// 查询呼叫接口列表(admin专用,走 /admin/ 前缀,路由到 fs-admin 8004)
 export function listCompanyVoiceApi(query) {
   return request({
-    url: '/company/companyVoiceApi/list',
+    url: '/admin/voice-api/list',
     method: 'get',
     params: query
   })
 }
 export function getVoiceApiList() {
   return request({
-    url: '/company/companyVoiceApi/getVoiceApiList',
+    url: '/admin/voice-api/list',
     method: 'get'
   })
 }
@@ -19,7 +19,7 @@ export function getVoiceApiList() {
 // 查询呼叫接口详细
 export function getCompanyVoiceApi(apiId) {
   return request({
-    url: '/company/companyVoiceApi/' + apiId,
+    url: '/admin/voice-api/' + apiId,
     method: 'get'
   })
 }

+ 7 - 7
src/api/company/companyVoiceCaller.js

@@ -1,9 +1,9 @@
 import request from '@/utils/request'
 
-// 查询企业用户坐席列表
+// 查询企业用户坐席列表(admin专用,走 /admin/ 前缀,路由到 fs-admin 8004)
 export function listCompanyVoiceCaller(query) {
   return request({
-    url: '/company/companyVoiceCaller/list',
+    url: '/admin/voice-seat/list',
     method: 'get',
     params: query
   })
@@ -12,7 +12,7 @@ export function listCompanyVoiceCaller(query) {
 // 查询企业用户坐席详细
 export function getCompanyVoiceCaller(callingId) {
   return request({
-    url: '/company/companyVoiceCaller/' + callingId,
+    url: '/admin/voice-seat/' + callingId,
     method: 'get'
   })
 }
@@ -20,7 +20,7 @@ export function getCompanyVoiceCaller(callingId) {
 // 新增企业用户坐席
 export function addCompanyVoiceCaller(data) {
   return request({
-    url: '/company/companyVoiceCaller',
+    url: '/admin/voice-seat',
     method: 'post',
     data: data
   })
@@ -29,7 +29,7 @@ export function addCompanyVoiceCaller(data) {
 // 修改企业用户坐席
 export function updateCompanyVoiceCaller(data) {
   return request({
-    url: '/company/companyVoiceCaller',
+    url: '/admin/voice-seat',
     method: 'put',
     data: data
   })
@@ -38,7 +38,7 @@ export function updateCompanyVoiceCaller(data) {
 // 删除企业用户坐席
 export function delCompanyVoiceCaller(callingId) {
   return request({
-    url: '/company/companyVoiceCaller/' + callingId,
+    url: '/admin/voice-seat/' + callingId,
     method: 'delete'
   })
 }
@@ -46,7 +46,7 @@ export function delCompanyVoiceCaller(callingId) {
 // 导出企业用户坐席
 export function exportCompanyVoiceCaller(query) {
   return request({
-    url: '/company/companyVoiceCaller/export',
+    url: '/admin/voice-seat/export',
     method: 'get',
     params: query
   })

+ 7 - 7
src/api/company/companyVoiceRoboticCallBlacklist.js

@@ -1,9 +1,9 @@
 import request from '@/utils/request'
 
-// 查询外呼黑名单列表
+// 查询外呼黑名单列表(admin专用,走 /admin/ 前缀,路由到 fs-admin 8004)
 export function listVoiceRoboticCallBlacklist(query) {
     return request({
-        url: '/company/companyVoiceRoboticCallBlacklist/list',
+        url: '/admin/voice-blacklist/list',
         method: 'get',
         params: query
     })
@@ -12,7 +12,7 @@ export function listVoiceRoboticCallBlacklist(query) {
 // 查询外呼黑名单详情
 export function getVoiceRoboticCallBlacklist(id) {
     return request({
-        url: '/company/companyVoiceRoboticCallBlacklist/' + id,
+        url: '/admin/voice-blacklist/' + id,
         method: 'get'
     })
 }
@@ -20,7 +20,7 @@ export function getVoiceRoboticCallBlacklist(id) {
 // 新增外呼黑名单
 export function addVoiceRoboticCallBlacklist(data) {
     return request({
-        url: '/company/companyVoiceRoboticCallBlacklist',
+        url: '/admin/voice-blacklist',
         method: 'post',
         data: data
     })
@@ -29,7 +29,7 @@ export function addVoiceRoboticCallBlacklist(data) {
 // 修改外呼黑名单
 export function updateVoiceRoboticCallBlacklist(data) {
     return request({
-        url: '/company/companyVoiceRoboticCallBlacklist',
+        url: '/admin/voice-blacklist',
         method: 'put',
         data: data
     })
@@ -38,7 +38,7 @@ export function updateVoiceRoboticCallBlacklist(data) {
 // 删除外呼黑名单
 export function delVoiceRoboticCallBlacklist(id) {
     return request({
-        url: '/company/companyVoiceRoboticCallBlacklist/' + id,
+        url: '/admin/voice-blacklist/' + id,
         method: 'delete'
     })
 }
@@ -46,7 +46,7 @@ export function delVoiceRoboticCallBlacklist(id) {
 // 修改状态
 export function changeVoiceRoboticCallBlacklistStatus(data) {
     return request({
-        url: '/company/companyVoiceRoboticCallBlacklist/changeStatus',
+        url: '/admin/voice-blacklist/changeStatus',
         method: 'put',
         data: data
     })

+ 1 - 1
src/assets/styles/index.scss

@@ -97,7 +97,7 @@ div:focus {
   }
 }
 
-aside {
+aside:not(.sidebar) {
   background: #eef1f6;
   padding: 8px 24px;
   margin-bottom: 20px;

+ 8 - 6
src/assets/styles/sidebar.scss

@@ -1,11 +1,13 @@
 #app {
 
-  .main-container {
-    min-height: 100%;
-    transition: margin-left .28s;
-    margin-left: $base-sidebar-width;
-    position: relative;
-  }
+  /* 注意:AdminLayout 使用 flex 布局,不依赖 margin-left 定位侧边栏,
+     以下规则已注释,避免与 AdminLayout.vue 的 .main-container flex 布局冲突 */
+  // .main-container {
+  //   min-height: 100%;
+  //   transition: margin-left .28s;
+  //   margin-left: $base-sidebar-width;
+  //   position: relative;
+  // }
 
   .sidebar-container {
     -webkit-transition: width .28s;

+ 486 - 257
src/layout/AdminLayout.vue

@@ -2,81 +2,145 @@
   <div class="admin-layout">
     <!-- 顶部导航栏 -->
     <header class="top-nav">
-      <div class="nav-inner">
-        <div class="nav-left">
-          <div class="nav-logo" @click="handleMenuClick('/admin/dashboard')">
-            <div class="logo-icon">
-              <i class="el-icon-s-platform"></i>
-            </div>
-            <span class="logo-text">总后台管理</span>
-          </div>
+      <div class="nav-logo" @click="handleMenuClick('/admin/dashboard')" title="返回数据看板">
+        <div class="logo-icon">
+          <i class="el-icon-s-platform"></i>
+        </div>
+        <span class="logo-text">总后台管理</span>
+        <div class="logo-home-badge">
+          <i class="el-icon-s-home"></i>
+        </div>
+      </div>
+      <div class="nav-divider"></div>
+
+      <nav class="nav-menu">
+        <div
+          v-for="(menu, index) in menuData"
+          :key="index"
+          class="nav-item"
+          :class="{ active: activeTopIndex === index && activeTopIndex !== -1 }"
+          @click="switchTopMenu(index)"
+        >
+          <i :class="menu.icon"></i>
+          <span>{{ menu.title }}</span>
         </div>
-        <div class="nav-right">
-          <div class="user-info">
-            <div class="user-avatar">
-              {{ (nickName || 'A').charAt(0).toUpperCase() }}
-            </div>
-            <span class="user-role">管理员</span>
+      </nav>
+
+      <!-- 右侧用户区域 -->
+      <div class="user-area">
+        <div class="user-info">
+          <div class="user-avatar">
+            {{ (nickName || 'A').charAt(0).toUpperCase() }}
           </div>
-          <el-dropdown @command="handleCommand" trigger="click">
-            <div class="nav-user">
-              <span class="nav-username">{{ nickName }}</span>
-              <i class="el-icon-arrow-down dropdown-arrow"></i>
-            </div>
-            <el-dropdown-menu slot="dropdown">
-              <el-dropdown-item command="profile">
-                <i class="el-icon-user"></i> 个人中心
-              </el-dropdown-item>
-              <el-dropdown-item command="logout" divided>
-                <i class="el-icon-switch-button"></i> 退出登录
-              </el-dropdown-item>
-            </el-dropdown-menu>
-          </el-dropdown>
+          <span class="user-role">管理员</span>
         </div>
+        <el-dropdown @command="handleCommand" trigger="click">
+          <div class="nav-user">
+            <span class="nav-username">{{ nickName }}</span>
+            <i class="el-icon-arrow-down dropdown-arrow"></i>
+          </div>
+          <el-dropdown-menu slot="dropdown">
+            <el-dropdown-item command="profile">
+              <i class="el-icon-user"></i> 个人中心
+            </el-dropdown-item>
+            <el-dropdown-item command="logout" divided>
+              <i class="el-icon-switch-button"></i> 退出登录
+            </el-dropdown-item>
+          </el-dropdown-menu>
+        </el-dropdown>
       </div>
     </header>
 
-    <!-- 下方:左侧菜单 + 内容区 -->
-    <div class="admin-body">
-      <aside class="admin-sidebar">
-        <div class="sidebar-menu">
-          <template v-for="item in menuList">
-            <!-- 有子菜单的分组 -->
-            <div v-if="item.children && item.children.length" :key="item.title" class="menu-group">
-              <div class="menu-group-title" @click="toggleGroup(item.title)">
-                <i :class="item.icon"></i>
+    <!-- 主体区域 -->
+    <div class="main-container">
+      <!-- 左侧侧边栏 -->
+      <aside class="sidebar" :class="{ 'sidebar-dashboard': isDashboard }">
+        <!-- Dashboard 快捷入口 -->
+        <div v-if="isDashboard" class="dashboard-shortcuts">
+          <div class="shortcuts-title">
+            <i class="el-icon-s-grid"></i>
+            <span>快捷入口</span>
+          </div>
+          <div
+            v-for="(menu, index) in menuData"
+            :key="index"
+            class="shortcut-item"
+            @click="switchTopMenu(index)"
+          >
+            <i :class="menu.icon"></i>
+            <span>{{ menu.title }}</span>
+          </div>
+        </div>
+
+        <!-- 普通二三级菜单 -->
+        <template v-else>
+          <div class="sidebar-header">
+            <i class="el-icon-menu"></i>
+            <span>菜单入口</span>
+          </div>
+          <el-menu
+            :default-active="activeMenu"
+            :unique-opened="true"
+            background-color="#fafbff"
+            text-color="#555"
+            active-text-color="#4f46e5"
+          >
+          <template v-for="item in currentSideMenu">
+            <!-- 有子菜单的分组(支持三级:el-submenu 内嵌 el-submenu 或 el-menu-item) -->
+            <el-submenu
+              v-if="item.children && item.children.length"
+              :index="item.title"
+              :key="item.title"
+            >
+              <template slot="title">
+                <i v-if="item.icon" :class="item.icon"></i>
                 <span>{{ item.title }}</span>
-                <i class="el-icon-arrow-right group-arrow" :class="{ expanded: !collapsedGroups[item.title] }"></i>
-              </div>
-              <div v-show="!collapsedGroups[item.title]" class="menu-group-items">
-                <div
-                  v-for="sub in item.children"
-                  :key="sub.path"
-                  :class="['menu-item', { active: $route.path === sub.path }]"
-                  @click="handleMenuClick(sub.path)"
+              </template>
+              <template v-for="child in item.children">
+                <!-- 三级分组(如外呼管理 > 通话接口管理等) -->
+                <el-submenu
+                  v-if="child.children && child.children.length"
+                  :index="child.title"
+                  :key="child.title"
                 >
-                  <span>{{ sub.title }}</span>
-                </div>
-              </div>
-            </div>
-            <!-- 无子菜单的单项 -->
-            <div
+                  <template slot="title">
+                    <i v-if="child.icon" :class="child.icon"></i>
+                    <span>{{ child.title }}</span>
+                  </template>
+                  <el-menu-item
+                    v-for="leaf in child.children"
+                    :key="leaf.path"
+                    :index="leaf.path"
+                    @click="handleMenuClick(leaf.path)"
+                  >{{ leaf.title }}</el-menu-item>
+                </el-submenu>
+                <!-- 普通二级菜单项 -->
+                <el-menu-item
+                  v-else
+                  :key="child.path"
+                  :index="child.path"
+                  @click="handleMenuClick(child.path)"
+                >{{ child.title }}</el-menu-item>
+              </template>
+            </el-submenu>
+            <!-- 无子菜单的直接链接项 -->
+            <el-menu-item
               v-else
+              :index="item.path"
               :key="item.path"
-              :class="['menu-item', { active: $route.path === item.path }]"
               @click="handleMenuClick(item.path)"
             >
-              <i :class="item.icon"></i>
-              <span>{{ item.title }}</span>
-            </div>
+              <i v-if="item.icon" :class="item.icon"></i>
+              <span slot="title">{{ item.title }}</span>
+            </el-menu-item>
           </template>
-        </div>
+        </el-menu>
+        </template>
       </aside>
 
-      <main class="admin-main">
-        <div class="main-content">
-          <router-view :key="viewKey" />
-        </div>
+      <!-- 内容区域 -->
+      <main class="content-area">
+        <router-view :key="viewKey" />
       </main>
     </div>
   </div>
@@ -89,123 +153,164 @@ export default {
   name: 'AdminLayout',
   data() {
     return {
-      collapsedGroups: {},
+      activeTopIndex: -1,
       viewKey: Date.now(),
-      menuList: [
-        {
-          title: '数据看板',
-          icon: 'el-icon-s-data',
-          path: '/admin/dashboard'
-        },
+      menuData: [
         {
-          title: '租户理',
+          title: '租户代理',
           icon: 'el-icon-office-building',
           children: [
-            { title: '租户列表', path: '/admin/company' },
-            { title: '租户模块使用统计', path: '/admin/moduleUsage' }
+            { title: '代理管理', path: '/admin/proxy', icon: 'el-icon-user' },
+            { path:'/admin/serviceCost', title:'收费配置', icon: 'el-icon-price-tag' },
+            { path:'/admin/agentReport', title:'收益报表', icon: 'el-icon-data-analysis' },
+            { title: '租户列表', path: '/admin/company', icon: 'el-icon-notebook-2' },
+            { title: '租户模块使用统计', path: '/admin/moduleUsage', icon: 'el-icon-pie-chart' }
           ]
         },
         {
-          title: '代理管理',
-          icon: 'el-icon-user',
-          path: '/admin/proxy'
-        },
-        {
-          title: '员工与组织',
-          icon: 'el-icon-s-custom',
+          title: '内容审计',
+          icon: 'el-icon-view',
           children: [
-            { title: '员工管理', path: '/admin/sysUser' },
-            { title: '角色管理', path: '/admin/role' },
-            { title: '菜单管理', path: '/admin/menu' },
-            { title: '部门管理', path: '/admin/dept' },
-            { title: '岗位管理', path: '/admin/post' }
+            { title: '视频资源', path: '/admin/videoResource', icon: 'el-icon-video-camera' },
+            { title: '公域课程管理', path: '/admin/course', icon: 'el-icon-reading' },
+            { title: '直播间', path: '/admin/live', icon: 'el-icon-video-camera-solid' },
+            { title: '直播视频', path: '/admin/liveVideo', icon: 'el-icon-film' },
+            { title: '商品管理', path: '/admin/product', icon: 'el-icon-goods' },
+            { title: '销售订单', path: '/admin/storeOrder', icon: 'el-icon-shopping-bag-1' },
+            { title: 'AI生成工作流', path: '/admin/workflowGenerate', icon: 'el-icon-cpu' },
+            { title: '销冠语料学习', path: '/admin/salesCorpus', icon: 'el-icon-trophy' },
+            { title: '文章管理', path: '/admin/article', icon: 'el-icon-document' }
           ]
         },
         {
-          title: '系统管理',
-          icon: 'el-icon-setting',
-          children: [
-            { title: '字典管理', path: '/admin/dict' },
-            { title: '参数管理', path: '/admin/config' },
-            { title: '通知公告', path: '/admin/notice' },
-            { title: '违规词语', path: '/admin/keyword' }
-          ]
-        },
-        {
-          title: '系统配置',
-          icon: 'el-icon-s-tools',
+          title: '财务管理',
+          icon: 'el-icon-money',
           children: [
-            { title: 'CID配置', path: '/admin/cidConfig' },
-            { title: '个微配置', path: '/admin/wxConfig' },
-            { title: 'OSS配置', path: '/admin/ossConfig' },
-            { title: '前端配置', path: '/admin/frontConfig' }
+            { title: '消费扣款记录', path: '/admin/consumeRecord', icon: 'el-icon-wallet' },
+            { title: '充值扣款记录', path: '/admin/rechargeRecord', icon: 'el-icon-bank-card' },
+            { title: '返佣记录', path: '/admin/commissionRecord', icon: 'el-icon-coin' },
+            { title: '代理提现管理', path: '/admin/withdrawal', icon: 'el-icon-money' }
           ]
         },
         {
-          title: '外呼管理',
+          title: '通信管理',
           icon: 'el-icon-phone-outline',
           children: [
-            { title: '外呼管理', path: '/admin/voice' },
-            { title: '通话接口管理', path: '/admin/voiceApi' },
-            { title: '号码管理', path: '/admin/voiceNumber' },
-            { title: '通话套餐管理', path: '/admin/voicePackage' },
-            { title: '坐席管理', path: '/admin/voiceSeat' },
-            { title: '黑名单管理', path: '/admin/voiceBlacklist' },
-            { title: '呼叫频率配置', path: '/admin/voiceFrequency' },
-            { title: '通话套餐订单', path: '/admin/voiceOrder' }
-          ]
-        },
-        {
-          title: '短信管理',
-          icon: 'el-icon-message',
-          children: [
-            { title: '短信管理', path: '/admin/sms' },
-            { title: '短信套餐', path: '/admin/smsPackage' },
-            { title: '短信订单', path: '/admin/smsOrder' }
-          ]
-        },
-        {
-          title: '财务管理',
-          icon: 'el-icon-coin',
-          children: [
-            { title: '消费扣款记录', path: '/admin/consumeRecord' },
-            { title: '充值扣款记录', path: '/admin/rechargeRecord' },
-            { title: '返佣记录', path: '/admin/commissionRecord' },
-            { title: '代理提现管理', path: '/admin/withdrawal' }
+            { title: '通话接口管理', icon: 'el-icon-connection', path: '/admin/voiceApi' },
+            { title: '通话套餐管理', icon: 'el-icon-box', path: '/admin/voicePackage' },
+            { title: '坐席管理', icon: 'el-icon-service', path: '/admin/voiceSeat' },
+            { title: '黑名单管理', icon: 'el-icon-warning', path: '/admin/voiceBlacklist' },
+            { title: '呼叫频率配置', icon: 'el-icon-timer', path: '/admin/voiceFrequency' },
+            { title: '通话套餐订单', icon: 'el-icon-tickets', path: '/admin/voiceOrder' },
+            { title: '短信管理', icon: 'el-icon-chat-dot-round', path: '/admin/sms' },
+            { title: '短信套餐', icon: 'el-icon-box', path: '/admin/smsPackage' },
+            { title: '短信订单', icon: 'el-icon-tickets', path: '/admin/smsOrder' }
           ]
         },
         {
-          title: '日志管理',
-          icon: 'el-icon-document',
+          title: '系统管理',
+          icon: 'el-icon-setting',
           children: [
-            { title: '操作日志', path: '/admin/operlog' },
-            { title: '登录日志', path: '/admin/logininfor' },
-            { title: '租户操作日志', path: '/admin/companyOperLog' },
-            { title: '代理操作日志', path: '/admin/proxyOperLog' }
+            { title: '员工管理', icon: 'el-icon-user', path: '/admin/sysUser' },
+            { title: '角色管理', icon: 'el-icon-s-check', path: '/admin/role' },
+            { title: '菜单管理', icon: 'el-icon-menu', path: '/admin/menu' },
+            { title: '部门管理', icon: 'el-icon-s-cooperation', path: '/admin/dept' },
+            { title: '岗位管理', icon: 'el-icon-s-custom', path: '/admin/post' },
+            { title: '操作日志', icon: 'el-icon-document', path: '/admin/operLog' },
+            { title: '登录日志', icon: 'el-icon-key', path: '/admin/loginLog' },
+            { title: '租户操作日志', icon: 'el-icon-notebook-2', path: '/admin/companyOperLog' },
+            { title: '代理操作日志', icon: 'el-icon-document-copy', path: '/admin/proxyOperLog' }
           ]
         },
         {
-          title: '其他管理',
-          icon: 'el-icon-more',
+          title: '系统配置',
+          icon: 'el-icon-s-tools',
           children: [
-            { title: 'Ipad服务器', path: '/admin/ipadServer' },
-            { title: '关键词管理', path: '/admin/keywordManage' },
-            { title: '文本模型配置', path: '/admin/textModel' }
+            { title: '字典管理', icon: 'el-icon-collection', path: '/admin/dict' },
+            { title: '参数管理', icon: 'el-icon-edit-outline', path: '/admin/config' },
+            { title: '通知公告', icon: 'el-icon-bell', path: '/admin/notice' },
+            { title: '违规词语', icon: 'el-icon-warning-outline', path: '/admin/keyword' },
+            { title: 'CID配置', icon: 'el-icon-phone', path: '/admin/cidConfig' },
+            { title: '个微配置', icon: 'el-icon-chat-line-round', path: '/admin/wxConfig' },
+            { title: 'OSS配置', icon: 'el-icon-upload', path: '/admin/ossConfig' },
+            { title: '前端配置', icon: 'el-icon-monitor', path: '/admin/frontConfig' },
+            { title: 'Ipad服务器', icon: 'el-icon-mobile-phone', path: '/admin/ipadServer' },
+            { title: '关键词管理', icon: 'el-icon-search', path: '/admin/keywordManage' },
+            { title: '模型配置', icon: 'el-icon-cpu', path: '/admin/textModel' }
           ]
         }
       ]
     }
   },
   computed: {
-    ...mapGetters(['nickName', 'avatar'])
+    ...mapGetters(['nickName', 'avatar']),
+    // 是否处于数据看板页面
+    isDashboard() {
+      return this.$route.path === '/admin/dashboard' || this.$route.path === '/admin'
+    },
+    // 当前顶部菜单对应的侧边栏二三级菜单(dashboard 时返回空)
+    currentSideMenu() {
+      if (this.activeTopIndex === -1) return []
+      return this.menuData[this.activeTopIndex]?.children || []
+    },
+    // 当前路由高亮
+    activeMenu() {
+      return this.$route.path
+    }
   },
   watch: {
-    '$route'() {
-      // 强制 router-view 重新渲染,解决路由切换后页面不刷新的问题
+    '$route.path'() {
       this.viewKey = Date.now()
+      this.initActiveTop()
     }
   },
+  created() {
+    this.initActiveTop()
+  },
   methods: {
+    switchTopMenu(index) {
+      this.activeTopIndex = index
+      // 自动跳转到该分组下第一个可点击的路由
+      const firstPath = this.getFirstPath(this.menuData[index].children)
+      if (firstPath && this.$route.path !== firstPath) {
+        this.$router.push(firstPath).catch(() => {})
+      }
+    },
+    getFirstPath(items) {
+      if (!items || !items.length) return null
+      for (let i = 0; i < items.length; i++) {
+        if (items[i].path) return items[i].path
+        if (items[i].children) {
+          const found = this.getFirstPath(items[i].children)
+          if (found) return found
+        }
+      }
+      return null
+    },
+    // 根据当前路由自动确定顶部选中项
+    initActiveTop() {
+      const currentPath = this.$route.path
+      // dashboard 是独立首页,不属于任何一级菜单,设为 -1
+      if (currentPath === '/admin/dashboard' || currentPath === '/admin') {
+        this.activeTopIndex = -1
+        return
+      }
+      for (let i = 0; i < this.menuData.length; i++) {
+        if (this.containsPath(this.menuData[i], currentPath)) {
+          this.activeTopIndex = i
+          return
+        }
+      }
+      // 不匹配任何分组时,回到 dashboard 状态
+      this.activeTopIndex = -1
+    },
+    containsPath(menu, path) {
+      if (menu.path === path) return true
+      if (menu.children) {
+        return menu.children.some(child => this.containsPath(child, path))
+      }
+      return false
+    },
     handleMenuClick(path) {
       if (this.$route.path === path) return
       this.$router.push(path).catch(err => {
@@ -214,21 +319,6 @@ export default {
         }
       })
     },
-    toggleGroup(title) {
-      const isCurrentlyOpen = !this.collapsedGroups[title]
-      // 先关闭所有分组(unique-opened 效果)
-      const newState = {}
-      this.menuList.forEach(item => {
-        if (item.children && item.children.length) {
-          newState[item.title] = true // true = collapsed
-        }
-      })
-      // 如果当前分组是关闭状态则打开它,否则保持关闭
-      if (isCurrentlyOpen) {
-        newState[title] = false // false = expanded
-      }
-      this.collapsedGroups = newState
-    },
     handleCommand(cmd) {
       if (cmd === 'logout') {
         this.$store.dispatch('LogOut').then(() => {
@@ -243,68 +333,147 @@ export default {
 </script>
 
 <style scoped>
+/* ===== 整体布局 ===== */
 .admin-layout {
-  min-height: 100vh;
+  height: 100vh;
   display: flex;
   flex-direction: column;
-  background: #f5f6fa;
+  overflow: hidden;
+  margin: 0 !important;
+  padding: 0 !important;
+  /* 抵消 sidebar.scss 给 #app 下 .main-container 注入的 margin-left */
+  position: relative;
 }
 
 /* ===== 顶部导航栏 ===== */
 .top-nav {
-  position: sticky;
-  top: 0;
-  z-index: 100;
+  height: 50px;
   background: #fff;
-  border-bottom: 1px solid #f0f0f0;
-  box-shadow: 0 1px 4px rgba(0,0,0,0.04);
-}
-
-.nav-inner {
-  margin: 0 auto;
-  height: 56px;
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  padding: 0 24px;
-}
-
-.nav-left {
   display: flex;
   align-items: center;
-  gap: 32px;
+  padding: 0 20px 0 0;
+  flex-shrink: 0;
+  z-index: 100;
+  border-bottom: 1px solid #e8e8e8;
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
 }
 
+/* Logo */
 .nav-logo {
   display: flex;
   align-items: center;
   gap: 10px;
   cursor: pointer;
+  margin-right: 32px;
+  flex-shrink: 0;
+  text-decoration: none;
+  padding: 0 16px;
 }
 
-.nav-logo .logo-icon {
+.logo-icon {
   width: 32px;
   height: 32px;
-  background: linear-gradient(135deg, #4f46e5, #7c3aed);
-  border-radius: 10px;
+  background: linear-gradient(135deg, #409EFF, #1890ff);
+  border-radius: 8px;
   display: flex;
   align-items: center;
   justify-content: center;
   color: #fff;
-  font-size: 18px;
+  font-size: 17px;
+  flex-shrink: 0;
 }
 
-.nav-logo .logo-text {
-  font-size: 19px;
+.logo-text {
+  font-size: 17px;
   font-weight: 600;
-  color: #1a1a2e;
+  color: #303133;
   white-space: nowrap;
 }
 
-.nav-right {
+.logo-home-badge {
+  width: 20px;
+  height: 20px;
+  border-radius: 4px;
+  background: rgba(64, 158, 255, 0.1);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-left: 4px;
+  flex-shrink: 0;
+  transition: background 0.2s;
+}
+
+.logo-home-badge i {
+  font-size: 13px;
+  color: #409EFF;
+}
+
+.nav-logo:hover .logo-home-badge {
+  background: rgba(64, 158, 255, 0.2);
+}
+
+.nav-logo:hover .logo-home-badge i {
+  color: #1890ff;
+}
+
+/* Logo 与一级菜单之间的分隔线 */
+.nav-divider {
+  width: 1px;
+  height: 24px;
+  background: #e8e8e8;
+  margin-right: 8px;
+  flex-shrink: 0;
+}
+
+/* 顶部菜单 */
+.nav-menu {
+  display: flex;
+  align-items: center;
+  flex: 1;
+  overflow: hidden;
+}
+
+.nav-item {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  padding: 0 18px;
+  height: 50px;
+  line-height: 50px;
+  cursor: pointer;
+  color: #606266;
+  font-size: 14px;
+  white-space: nowrap;
+  transition: all 0.3s;
+  position: relative;
+  flex-shrink: 0;
+  border-bottom: 2px solid transparent;
+}
+
+.nav-item:hover {
+  color: #409EFF;
+}
+
+.nav-item.active {
+  color: #409EFF;
+  border-bottom-color: #409EFF;
+  font-weight: 500;
+}
+
+.nav-item i {
+  font-size: 16px;
+  flex-shrink: 0;
+}
+
+/* 右侧用户区域 */
+.user-area {
   display: flex;
   align-items: center;
   gap: 12px;
+  margin-left: auto;
+  flex-shrink: 0;
+  padding-left: 20px;
+  color: #606266;
 }
 
 .user-info {
@@ -317,161 +486,221 @@ export default {
   width: 30px;
   height: 30px;
   border-radius: 8px;
-  background: linear-gradient(135deg, #4f46e5, #7c3aed);
+  background: linear-gradient(135deg, #409EFF, #1890ff);
   color: #fff;
   display: flex;
   align-items: center;
   justify-content: center;
-  font-size: 14px;
+  font-size: 13px;
   font-weight: 600;
+  flex-shrink: 0;
 }
 
 .user-role {
   font-size: 13px;
-  color: #666;
+  color: #909399;
+  white-space: nowrap;
 }
 
 .nav-user {
   display: flex;
   align-items: center;
-  gap: 6px;
+  gap: 5px;
   cursor: pointer;
-  font-size: 15px;
-  color: #333;
   padding: 4px 8px;
-  border-radius: 8px;
+  border-radius: 6px;
   transition: background 0.2s;
 }
 
 .nav-user:hover {
-  background: #f5f5f5;
+  background: rgba(64, 158, 255, 0.08);
 }
 
 .nav-username {
-  font-size: 15px;
-  color: #333;
-  font-weight: 500;
+  font-size: 14px;
+  color: #303133;
+  white-space: nowrap;
 }
 
 .dropdown-arrow {
-  color: #999;
+  color: #909399;
   font-size: 12px;
 }
 
-/* ===== 下方布局 ===== */
-.admin-body {
+/* ===== 主体区域 ===== */
+/* 强制覆盖 sidebar.scss 中 #app .main-container { margin-left: 200px } 的全局规则 */
+.main-container {
   flex: 1;
   display: flex;
-  width: 100%;
+  overflow: hidden;
+  margin: 0 !important;
+  padding: 0 !important;
 }
 
-/* 左侧菜单 */
-.admin-sidebar {
+/* ===== 左侧侧边栏 ===== */
+.sidebar {
   width: 200px;
   flex-shrink: 0;
   background: #fff;
-  border-right: 1px solid #f0f0f0;
-  padding: 16px 0;
+  border-right: 1px solid #e8e8e8;
   overflow-y: auto;
+  overflow-x: hidden;
+  /* 覆盖全局 aside 样式 */
+  padding: 0 !important;
+  margin: 0 !important;
 }
 
-.sidebar-menu {
+/* Dashboard 快捷入口侧边栏 */
+.sidebar-dashboard {
+  background: #fafbff;
+}
+
+.dashboard-shortcuts {
+  padding: 16px 12px;
+}
+
+.shortcuts-title {
   display: flex;
-  flex-direction: column;
-  gap: 2px;
-  padding: 0 8px;
+  align-items: center;
+  gap: 7px;
+  font-size: 12px;
+  font-weight: 600;
+  color: #999;
+  text-transform: uppercase;
+  letter-spacing: 0.5px;
+  padding: 0 4px 10px;
+  border-bottom: 1px solid #f0f0f0;
+  margin-bottom: 8px;
 }
 
-.menu-item {
+.shortcuts-title i {
+  font-size: 14px;
+  color: #bbb;
+}
+
+.shortcut-item {
   display: flex;
   align-items: center;
   gap: 10px;
-  padding: 11px 16px;
+  padding: 10px 12px;
   border-radius: 8px;
-  font-size: 15px;
-  color: #555;
   cursor: pointer;
+  color: #555;
+  font-size: 14px;
   transition: all 0.2s;
-  user-select: none;
+  margin-bottom: 2px;
 }
 
-.menu-item:hover {
-  background: #f0f0ff;
-  color: #4f46e5;
-}
-
-.menu-item.active {
+.shortcut-item:hover {
   background: #ede9fe;
   color: #4f46e5;
-  font-weight: 600;
 }
 
-.menu-item i {
-  font-size: 18px;
+.shortcut-item i {
+  font-size: 16px;
   flex-shrink: 0;
+  color: #8b5cf6;
 }
 
-/* 分组菜单 */
-.menu-group {
-  margin-bottom: 4px;
+/* 覆盖 el-menu 默认样式 */
+.sidebar >>> .el-menu {
+  border-right: none;
+  width: 200px;
+  padding: 8px 12px;
+  background-color: #fafbff !important;
 }
 
-.menu-group-title {
+/* 菜单入口 header */
+.sidebar-header {
   display: flex;
   align-items: center;
-  gap: 10px;
-  padding: 11px 16px;
+  gap: 7px;
+  font-size: 12px;
+  font-weight: 600;
+  color: #999;
+  text-transform: uppercase;
+  letter-spacing: 0.5px;
+  padding: 16px 16px 10px;
+  border-bottom: 1px solid #f0f0f0;
+  margin-bottom: 0;
+  background: #fafbff;
+}
+
+.sidebar-header i {
+  font-size: 14px;
+  color: #bbb;
+}
+
+.sidebar >>> .el-menu-item {
+  height: 50px;
+  line-height: 50px;
+  font-size: 14px;
+  padding-left: 24px !important;
+  color: #555 !important;
   border-radius: 8px;
-  font-size: 15px;
-  color: #555;
-  cursor: pointer;
+  margin-bottom: 2px;
   transition: all 0.2s;
-  user-select: none;
+}
+
+.sidebar >>> .el-submenu__title {
+  height: 50px;
+  line-height: 50px;
+  font-size: 14px;
   font-weight: 500;
+  color: #555 !important;
+  border-radius: 8px;
+  margin-bottom: 2px;
+  transition: all 0.2s;
 }
 
-.menu-group-title:hover {
-  background: #f0f0ff;
-  color: #4f46e5;
+.sidebar >>> .el-menu-item i,
+.sidebar >>> .el-submenu__title i {
+  margin-right: 8px;
+  color: #8b5cf6;
 }
 
-.menu-group-title i {
-  font-size: 18px;
-  flex-shrink: 0;
+.sidebar >>> .el-submenu .el-menu-item {
+  padding-left: 24px !important;
+  height: 46px;
+  line-height: 46px;
+  font-size: 13px;
+  min-width: 200px;
+  color: #555 !important;
+  border-radius: 8px;
+  margin-bottom: 2px;
+  transition: all 0.2s;
 }
 
-.group-arrow {
-  margin-left: auto;
-  transition: transform 0.2s;
-  color: #999;
-  font-size: 12px;
+.sidebar >>> .el-submenu .el-menu-item i {
+  color: #8b5cf6;
 }
 
-.group-arrow.expanded {
-  transform: rotate(90deg);
+.sidebar >>> .el-menu-item.is-active {
+  background-color: #ede9fe !important;
+  color: #4f46e5 !important;
+  font-weight: 600;
+  border-right: none !important;
+  border-left: 3px solid #4f46e5 !important;
+  padding-left: 21px !important;
 }
 
-.menu-group-items {
-  padding-left: 8px;
-  display: flex;
-  flex-direction: column;
-  gap: 2px;
+.sidebar >>> .el-menu-item:hover,
+.sidebar >>> .el-submenu__title:hover {
+  background-color: #ede9fe !important;
+  color: #4f46e5 !important;
 }
 
-.menu-group-items .menu-item {
-  padding-left: 44px;
-  font-size: 14px;
+.sidebar >>> .el-submenu .el-menu-item:hover {
+  background-color: #ede9fe !important;
+  color: #4f46e5 !important;
 }
 
-/* 主内容区 */
-.admin-main {
+/* ===== 内容区域 ===== */
+.content-area {
   flex: 1;
-  min-width: 0;
   overflow-y: auto;
   background: #f5f6fa;
-}
-
-.main-content {
   padding: 16px 24px 24px;
+  min-width: 0;
 }
 </style>

+ 34 - 24
src/views/admin/article/index.vue

@@ -2,14 +2,22 @@
   <div class="app-container">
     <el-card shadow="never" class="mb16 filter-card">
       <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px" size="small">
-      <el-form-item label="租户名称" prop="companyName">
-        <el-input
-          v-model="queryParams.companyName"
-          placeholder="请输入租户名称"
+      <el-form-item label="租户名称" prop="companyId">
+        <el-select
+          v-model="queryParams.companyId"
+          placeholder="选择租户"
           clearable
+          filterable
           size="small"
-          @keyup.enter.native="handleQuery"
-        />
+          style="width: 200px"
+        >
+          <el-option
+            v-for="item in companyList"
+            :key="item.companyId"
+            :label="item.companyName"
+            :value="item.companyId"
+          />
+        </el-select>
       </el-form-item>
       <el-form-item label="文章标题" prop="articleTitle">
         <el-input
@@ -31,8 +39,7 @@
         </el-select>
       </el-form-item>
       <el-form-item>
-        <inline-tenant-selector @change="onTenantChange" />
-      <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
         <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
       </el-form-item>
       </el-form>
@@ -73,12 +80,12 @@
 
     <el-table border size="small" style="width:100%" v-loading="loading" :data="articleList">
       <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="租户名称" align="center" prop="companyName" />
-      <el-table-column label="文章标题" align="center" prop="articleTitle" />
-      <el-table-column label="文章分类" align="center" prop="categoryName" />
-      <el-table-column label="作者" align="center" prop="author" />
-      <el-table-column label="创建时间" align="center" prop="createTime" />
-      <el-table-column label="文章状态" align="center" prop="status">
+      <el-table-column label="租户名称" align="center" prop="companyName" min-width="120" />
+      <el-table-column label="文章标题" align="center" prop="articleTitle" min-width="160" show-overflow-tooltip />
+      <el-table-column label="文章分类" align="center" prop="categoryName" min-width="100" />
+      <el-table-column label="作者" align="center" prop="author" min-width="90" />
+      <el-table-column label="创建时间" align="center" prop="createTime" min-width="150" />
+      <el-table-column label="文章状态" align="center" prop="status" min-width="90">
         <template slot-scope="scope">
           <el-tag v-if="scope.row.status == 0" type="warning">待审计</el-tag>
           <el-tag v-else-if="scope.row.status == 1" type="success">已通过</el-tag>
@@ -209,8 +216,7 @@
 
 <script>
 import { listAllArticles, listPendingArticles, getArticleInfo, auditArticle, deleteArticle, getArticleStatistics, exportAllArticles } from '@/api/admin/article'
-
-import InlineTenantSelector from "@/components/InlineTenantSelector"
+import { listAllCompanies } from '@/api/admin/sysCompany'
 
 export default {
   name: 'ArticleAdmin',
@@ -226,6 +232,8 @@ export default {
       total: 0,
       // 文章列表
       articleList: [],
+      // 租户列表
+      companyList: [],
       // 统计数据
       statistics: {
         totalCount: 0,
@@ -243,10 +251,9 @@ export default {
       queryParams: {
         pageNum: 1,
         pageSize: 10,
-        companyName: null,
+        companyId: null,
         articleTitle: null,
-        status: null,
-        companyId: null
+        status: null
       },
       // 详情弹窗
       viewOpen: false,
@@ -264,9 +271,16 @@ export default {
     }
   },
   created() {
+    this.getCompanyList()
     this.getList()
   },
   methods: {
+    /** 获取租户列表 */
+    getCompanyList() {
+      listAllCompanies({ pageSize: 9999 }).then(response => {
+        this.companyList = response.rows || []
+      })
+    },
     /** 查询列表 */
     getList() {
       this.loading = true
@@ -290,14 +304,10 @@ export default {
       this.queryParams.pageNum = 1
       this.getList()
     },
-    /** 租户选择变化 */
-    onTenantChange(companyId) {
-      this.queryParams.companyId = companyId
-      this.handleQuery()
-    },
     /** 重置按钮操作 */
     resetQuery() {
       this.resetForm('queryForm')
+      this.queryParams.companyId = null
       this.handleQuery()
     },
     /** 查看详情 */

+ 7 - 7
src/views/admin/callRecord/index.vue

@@ -72,17 +72,17 @@
 
     <el-table v-loading="loading" :data="callRecordList" border size="small" style="width:100%" @selection-change="handleSelectionChange">
       <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="租户名称" align="center" prop="companyName" />
-      <el-table-column label="员工名称" align="center" prop="userName" />
-      <el-table-column label="客户号码" align="center" prop="customerPhone" />
-      <el-table-column label="通话时长" align="center" prop="callTime">
+      <el-table-column label="租户名称" align="center" prop="companyName" min-width="120" />
+      <el-table-column label="员工名称" align="center" prop="userName" min-width="90" />
+      <el-table-column label="客户号码" align="center" prop="customerPhone" min-width="110" />
+      <el-table-column label="通话时长" align="center" prop="callTime" min-width="90">
         <template slot-scope="scope">
           {{ formatDuration(scope.row.callTime) }}
         </template>
       </el-table-column>
-      <el-table-column label="通话费用" align="center" prop="cost" />
-      <el-table-column label="通话时间" align="center" prop="callTimeStr" />
-      <el-table-column label="是否有录音" align="center" prop="hasAudio">
+      <el-table-column label="通话费用" align="center" prop="cost" min-width="90" />
+      <el-table-column label="通话时间" align="center" prop="callTimeStr" min-width="150" />
+      <el-table-column label="是否有录音" align="center" prop="hasAudio" min-width="90">
         <template slot-scope="scope">
           <el-tag v-if="scope.row.voiceUrl" type="success">有</el-tag>
           <el-tag v-else type="error">无</el-tag>

+ 9 - 9
src/views/admin/commissionRecord/index.vue

@@ -36,30 +36,30 @@
 
     <el-table border v-loading="loading" :data="recordList" size="small" style="width:100%">
       <el-table-column type="index" label="序号" width="55" align="center" />
-      <el-table-column label="代理ID" align="center" prop="proxyId" width="70" />
-      <el-table-column label="代理名称" align="center" prop="proxyName" width="120" />
-      <el-table-column label="租户ID" align="center" prop="tenantId" width="70" />
+      <el-table-column label="代理ID" align="center" prop="proxyId" min-width="60" />
+      <el-table-column label="代理名称" align="center" prop="proxyName" min-width="100" />
+      <el-table-column label="租户ID" align="center" prop="tenantId" min-width="60" />
       <el-table-column label="租户名称" align="center" prop="tenantName" min-width="120" />
-      <el-table-column label="消费金额" align="center" prop="amount" width="110">
+      <el-table-column label="消费金额" align="center" prop="amount" min-width="90">
         <template slot-scope="scope">¥{{ scope.row.amount || '0.00' }}</template>
       </el-table-column>
-      <el-table-column label="平台成本" align="center" prop="platformCost" width="100">
+      <el-table-column label="平台成本" align="center" prop="platformCost" min-width="90">
         <template slot-scope="scope">
           <span style="color:#909399">¥{{ scope.row.platformCost || '0.00' }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="租户售价" align="center" prop="tenantPrice" width="100">
+      <el-table-column label="租户售价" align="center" prop="tenantPrice" min-width="90">
         <template slot-scope="scope">¥{{ scope.row.tenantPrice || '0.00' }}</template>
       </el-table-column>
-      <el-table-column label="分佣比例" align="center" prop="proxyRatio" width="90">
+      <el-table-column label="分佣比例" align="center" prop="proxyRatio" min-width="80">
         <template slot-scope="scope">{{ scope.row.proxyRatio || 0 }}%</template>
       </el-table-column>
-      <el-table-column label="分佣金额" align="center" prop="proxyProfit" width="110">
+      <el-table-column label="分佣金额" align="center" prop="proxyProfit" min-width="90">
         <template slot-scope="scope">
           <span style="color:#E6A23C;font-weight:bold">¥{{ scope.row.proxyProfit || '0.00' }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="消费时间" align="center" prop="consumeTime" width="160" />
+      <el-table-column label="消费时间" align="center" prop="consumeTime" min-width="150" />
       <el-table-column label="操作" align="center" width="80" fixed="right">
         <template slot-scope="scope">
           <el-button size="mini" type="text" icon="el-icon-view" @click="handleDetail(scope.row)">详情</el-button>

+ 219 - 0
src/views/admin/consumeReport/index.vue

@@ -0,0 +1,219 @@
+<template>
+  <div class="app-container">
+    <!-- ===== KPI 概览卡片 ===== -->
+    <el-row :gutter="16" class="mb16">
+      <el-col :span="6" v-for="card in overviewCards" :key="card.label">
+        <el-card shadow="hover" class="overview-card">
+          <div class="card-inner">
+            <div class="card-icon" :style="{ background: card.bg }"><i :class="card.icon"></i></div>
+            <div class="card-info">
+              <span class="card-value">{{ card.value }}</span>
+              <span class="card-label">{{ card.label }}</span>
+            </div>
+          </div>
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <!-- ===== 搜索栏 ===== -->
+    <el-card shadow="never" class="mb16 filter-card">
+      <el-form :model="queryParams" ref="queryForm" :inline="true" size="small">
+        <el-form-item label="租户名称" prop="tenantName">
+          <el-input v-model="queryParams.tenantName" placeholder="请输入租户名称" clearable @keyup.enter.native="handleQuery" />
+        </el-form-item>
+        <el-form-item label="消费时间">
+          <el-date-picker v-model="dateRange" type="daterange" range-separator="至"
+            start-placeholder="开始日期" end-placeholder="结束日期"
+            value-format="yyyy-MM-dd" style="width:240px" />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
+          <el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
+
+    <!-- ===== 模块消费交叉汇总表 ===== -->
+    <el-table border v-loading="loading" :data="tenantList" size="small" style="width:100%" show-summary :summary-method="getSummary">
+      <el-table-column type="index" label="序号" width="55" align="center" fixed="left" />
+      <el-table-column label="租户名称" align="center" prop="tenantName" min-width="120" fixed="left" />
+      <!-- 动态模块列 -->
+      <el-table-column v-for="mod in moduleColumns" :key="mod.code" :label="mod.name" align="center" :min-width="mod.minWidth || 90">
+        <template slot-scope="scope">
+          <span style="color:#1890ff">¥{{ getModuleAmount(scope.row, mod.code) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="消费总额" align="center" prop="totalAmount" width="120" fixed="right">
+        <template slot-scope="scope">
+          <span style="color:#f5222d;font-weight:bold">¥{{ scope.row.totalAmount || '0.00' }}</span>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- ===== 模块消费汇总表(模块维度) ===== -->
+    <el-card shadow="never" class="mt16">
+      <div slot="header"><span>各模块消费汇总</span></div>
+      <el-table border :data="moduleBreakdown" size="small" style="width:100%">
+        <el-table-column type="index" label="序号" width="55" align="center" />
+        <el-table-column label="模块" align="center" prop="moduleName" min-width="120" />
+        <el-table-column label="消费笔数" align="center" prop="count" width="100" />
+        <el-table-column label="消费金额" align="center" prop="totalAmount" width="140">
+          <template slot-scope="scope">
+            <span style="color:#1890ff;font-weight:bold">¥{{ scope.row.totalAmount || '0.00' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="占比" align="center" width="100">
+          <template slot-scope="scope">
+            <el-progress :percentage="getPercent(scope.row.totalAmount)" :stroke-width="16" :show-text="true" />
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-card>
+  </div>
+</template>
+
+<script>
+import { getConsumptionReport } from '@/api/admin/consumeReport'
+
+export default {
+  name: 'AdminConsumeReport',
+  data() {
+    return {
+      loading: false,
+      dateRange: [],
+      // 模块列定义(动态生成)
+      moduleColumns: [],
+      // 模块维度汇总
+      moduleBreakdown: [],
+      // 租户维度汇总
+      tenantList: [],
+      // 各模块总额(用于占比计算)
+      moduleTotalMap: {},
+      queryParams: {
+        tenantName: null,
+        beginTime: null,
+        endTime: null
+      },
+      overviewCards: [
+        { label: '消费总额', value: '¥0.00', icon: 'el-icon-wallet', bg: '#e6f7ff' },
+        { label: '消费笔数', value: 0, icon: 'el-icon-document', bg: '#fff7e6' },
+        { label: '涉及租户数', value: 0, icon: 'el-icon-office-building', bg: '#f6ffed' },
+        { label: '涉及模块数', value: 0, icon: 'el-icon-data-line', bg: '#f9f0ff' }
+      ]
+    }
+  },
+  created() {
+    // 默认查询当月
+    const now = new Date()
+    const y = now.getFullYear()
+    const m = String(now.getMonth() + 1).padStart(2, '0')
+    this.dateRange = [y + '-' + m + '-01', y + '-' + m + '-' + String(new Date(y, now.getMonth() + 1, 0).getDate()).padStart(2, '0')]
+    this.getReport()
+  },
+  methods: {
+    getReport() {
+      this.loading = true
+      if (this.dateRange && this.dateRange.length === 2) {
+        this.queryParams.beginTime = this.dateRange[0]
+        this.queryParams.endTime = this.dateRange[1]
+      } else {
+        this.queryParams.beginTime = null
+        this.queryParams.endTime = null
+      }
+      getConsumptionReport(this.queryParams).then(res => {
+        const data = res.data || {}
+        this.buildModuleColumns(data.moduleBreakdown || [])
+        this.moduleBreakdown = data.moduleBreakdown || []
+        this.tenantList = data.tenantBreakdown || []
+        this.updateOverview(data.summary || {})
+        this.loading = false
+      }).catch(() => { this.loading = false })
+    },
+
+    /** 根据模块明细动态生成表格列 */
+    buildModuleColumns(modules) {
+      this.moduleColumns = modules.map(m => ({
+        code: m.moduleCode,
+        name: m.moduleName,
+        minWidth: m.moduleName.length > 4 ? 90 : 80
+      }))
+      // 构建模块总额映射
+      this.moduleTotalMap = {}
+      modules.forEach(m => {
+        this.moduleTotalMap[m.moduleCode] = parseFloat(m.totalAmount) || 0
+      })
+    },
+
+    /** 从租户的 modules 映射中取指定模块金额 */
+    getModuleAmount(row, moduleCode) {
+      const modules = row.modules || {}
+      const val = modules[moduleCode]
+      return val != null ? parseFloat(val).toFixed(2) : '0.00'
+    },
+
+    /** 计算模块占比 */
+    getPercent(amount) {
+      const val = parseFloat(amount) || 0
+      const total = parseFloat((this.overviewCards[0].value || '¥0').replace('¥', '')) || 1
+      return Math.round(val / total * 100)
+    },
+
+    updateOverview(summary) {
+      this.overviewCards[0].value = '¥' + (parseFloat(summary.totalAmount) || 0).toFixed(2)
+      this.overviewCards[1].value = summary.totalCount || 0
+      this.overviewCards[2].value = summary.tenantCount || 0
+      this.overviewCards[3].value = summary.moduleCount || 0
+    },
+
+    /** 表格合计行 */
+    getSummary({ columns, data }) {
+      const sums = []
+      columns.forEach((col, idx) => {
+        if (idx === 0) { sums[idx] = '合计'; return }
+        if (idx === 1) { sums[idx] = ''; return }
+        const prop = col.property
+        if (prop === 'totalAmount') {
+          const val = data.reduce((s, r) => s + (parseFloat(r[prop]) || 0), 0)
+          sums[idx] = '¥' + val.toFixed(2)
+        } else {
+          sums[idx] = ''
+        }
+      })
+      // 动态模块列求和
+      this.moduleColumns.forEach((mod, mi) => {
+        const colIdx = mi + 2 // 2 = 序号 + 租户名称
+        const val = data.reduce((s, r) => {
+          return s + parseFloat(this.getModuleAmount(r, mod.code))
+        }, 0)
+        sums[colIdx] = '¥' + val.toFixed(2)
+      })
+      return sums
+    },
+
+    handleQuery() {
+      this.getReport()
+    },
+    resetQuery() {
+      this.dateRange = []
+      this.resetForm('queryForm')
+      this.handleQuery()
+    }
+  }
+}
+</script>
+
+<style scoped>
+.overview-card { cursor: default; }
+.card-inner { display: flex; align-items: center; }
+.card-icon {
+  width: 46px; height: 46px; border-radius: 50%;
+  display: flex; align-items: center; justify-content: center;
+  margin-right: 14px; font-size: 22px; color: #fff;
+}
+.card-value { font-size: 22px; font-weight: bold; color: #333; display: block; }
+.card-label { font-size: 12px; color: #999; }
+
+.filter-card { padding-bottom: 0; }
+.mb16 { margin-bottom: 16px; }
+.mt16 { margin-top: 16px; }
+</style>

+ 35 - 25
src/views/admin/course/index.vue

@@ -2,14 +2,22 @@
   <div class="app-container">
     <el-card shadow="never" class="mb16 filter-card">
       <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px" size="small">
-      <el-form-item label="租户名称" prop="companyName">
-        <el-input
-          v-model="queryParams.companyName"
-          placeholder="请输入租户名称"
+      <el-form-item label="租户名称" prop="companyId">
+        <el-select
+          v-model="queryParams.companyId"
+          placeholder="选择租户"
           clearable
+          filterable
           size="small"
-          @keyup.enter.native="handleQuery"
-        />
+          style="width: 200px"
+        >
+          <el-option
+            v-for="item in companyList"
+            :key="item.companyId"
+            :label="item.companyName"
+            :value="item.companyId"
+          />
+        </el-select>
       </el-form-item>
       <el-form-item label="课程名称" prop="courseName">
         <el-input
@@ -31,8 +39,7 @@
         </el-select>
       </el-form-item>
       <el-form-item>
-        <inline-tenant-selector @change="onTenantChange" />
-      <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
         <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
       </el-form-item>
       </el-form>
@@ -64,9 +71,9 @@
 
     <el-table border size="small" style="width:100%" v-loading="loading" :data="courseList">
       <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="租户名称" align="center" prop="companyName" />
-      <el-table-column label="课程名称" align="center" prop="courseName" />
-      <el-table-column label="课程封面" align="center" prop="coverUrl">
+      <el-table-column label="租户名称" align="center" prop="companyName" min-width="120" />
+      <el-table-column label="课程名称" align="center" prop="courseName" min-width="150" show-overflow-tooltip />
+      <el-table-column label="课程封面" align="center" prop="coverUrl" min-width="100">
         <template slot-scope="scope">
           <el-image
             v-if="scope.row.coverUrl"
@@ -75,10 +82,10 @@
             fit="contain" />
         </template>
       </el-table-column>
-      <el-table-column label="课程时长" align="center" prop="duration" />
-      <el-table-column label="播放次数" align="center" prop="playCount" />
-      <el-table-column label="创建时间" align="center" prop="createTime" />
-      <el-table-column label="课程状态" align="center" prop="status">
+      <el-table-column label="课程时长" align="center" prop="duration" min-width="90" />
+      <el-table-column label="播放次数" align="center" prop="playCount" min-width="80" />
+      <el-table-column label="创建时间" align="center" prop="createTime" min-width="150" />
+      <el-table-column label="课程状态" align="center" prop="status" min-width="90">
         <template slot-scope="scope">
           <el-tag v-if="scope.row.status == 0" type="success">上架</el-tag>
           <el-tag v-else type="error">下架</el-tag>
@@ -172,8 +179,7 @@
 
 <script>
 import { listAllCourses, getCourseInfo, getCourseStatistics, exportAllCourses } from '@/api/admin/course'
-
-import InlineTenantSelector from "@/components/InlineTenantSelector"
+import { listAllCompanies } from '@/api/admin/sysCompany'
 
 export default {
   name: 'CourseAdmin',
@@ -189,6 +195,8 @@ export default {
       total: 0,
       // 课程列表
       courseList: [],
+      // 租户列表
+      companyList: [],
       // 统计数据
       statistics: {
         totalCount: 0,
@@ -205,10 +213,9 @@ export default {
       queryParams: {
         pageNum: 1,
         pageSize: 10,
-        companyName: null,
+        companyId: null,
         courseName: null,
-        status: null,
-        companyId: null
+        status: null
       },
       // 详情弹窗
       viewOpen: false,
@@ -218,9 +225,16 @@ export default {
     }
   },
   created() {
+    this.getCompanyList()
     this.getList()
   },
   methods: {
+    /** 获取租户列表 */
+    getCompanyList() {
+      listAllCompanies({ pageSize: 9999 }).then(response => {
+        this.companyList = response.rows || []
+      })
+    },
     /** 查询列表 */
     getList() {
       this.loading = true
@@ -235,14 +249,10 @@ export default {
       this.queryParams.pageNum = 1
       this.getList()
     },
-    /** 租户选择变化 */
-    onTenantChange(companyId) {
-      this.queryParams.companyId = companyId
-      this.handleQuery()
-    },
     /** 重置按钮操作 */
     resetQuery() {
       this.resetForm('queryForm')
+      this.queryParams.companyId = null
       this.handleQuery()
     },
     /** 查看详情 */

+ 22 - 22
src/views/admin/dailyStatistics/index.vue

@@ -36,36 +36,36 @@
       <!-- Tab 1:代理下租户消费 -->
       <el-tab-pane label="租户消费明细" name="tenantConsume">
         <el-table border v-loading="loading" :data="tenantStats" size="small" style="width:100%">
-          <el-table-column label="租户ID" prop="tenantId" width="80" align="center" />
-          <el-table-column label="租户名称" min-width="140">
+          <el-table-column label="租户ID" prop="tenantId" min-width="70" align="center" />
+          <el-table-column label="租户名称" min-width="120">
             <template slot-scope="s">{{ tenantNames[s.row.tenantId] || s.row.remark || '-' }}</template>
           </el-table-column>
-          <el-table-column label="消费金额" align="center" width="140">
+          <el-table-column label="消费金额" align="center" min-width="110">
             <template slot-scope="s">
               <span style="color:#1890ff;font-weight:bold">{{ fmt(s.row.consumeAmount) }}</span>
             </template>
           </el-table-column>
-          <el-table-column label="平台成本" align="center" width="140">
+          <el-table-column label="平台成本" align="center" min-width="110">
             <template slot-scope="s">
               <span style="color:#fa8c16">{{ fmt(s.row.platformCost) }}</span>
             </template>
           </el-table-column>
-          <el-table-column label="租户售价" align="center" width="140">
+          <el-table-column label="租户售价" align="center" min-width="110">
             <template slot-scope="s">{{ fmt(s.row.tenantPrice) }}</template>
           </el-table-column>
-          <el-table-column label="利润(售价-成本)" align="center" width="150">
+          <el-table-column label="利润(售价-成本)" align="center" min-width="120">
             <template slot-scope="s">
               <span style="color:#52c41a;font-weight:bold">
                 {{ fmt((s.row.tenantPrice || 0) - (s.row.platformCost || 0)) }}
               </span>
             </template>
           </el-table-column>
-          <el-table-column label="代理分佣" align="center" width="140">
+          <el-table-column label="代理分佣" align="center" min-width="110">
             <template slot-scope="s">
               <span style="color:#722ed1;font-weight:bold">{{ fmt(s.row.proxyProfit) }}</span>
             </template>
           </el-table-column>
-          <el-table-column label="订单数" prop="orderCount" width="80" align="center" />
+          <el-table-column label="订单数" prop="orderCount" min-width="70" align="center" />
         </el-table>
         <el-empty v-if="!loading && tenantStats.length === 0" description="暂无数据" :image-size="80" />
       </el-tab-pane>
@@ -73,41 +73,41 @@
       <!-- Tab 2:服务类型利润 -->
       <el-tab-pane label="服务类型利润" name="typeProfit">
         <el-table border v-loading="loading" :data="typeStats" size="small" style="width:100%">
-          <el-table-column label="服务类型" min-width="160">
+          <el-table-column label="服务类型" min-width="130">
             <template slot-scope="s">
               <el-tag :type="typeTag(s.row.consumeType)">{{ typeName(s.row.consumeType) }}</el-tag>
             </template>
           </el-table-column>
-          <el-table-column label="消费金额" align="center" width="140">
+          <el-table-column label="消费金额" align="center" min-width="110">
             <template slot-scope="s">
               <span style="color:#1890ff;font-weight:bold">{{ fmt(s.row.consumeAmount) }}</span>
             </template>
           </el-table-column>
-          <el-table-column label="平台成本" align="center" width="140">
+          <el-table-column label="平台成本" align="center" min-width="110">
             <template slot-scope="s">
               <span style="color:#fa8c16">{{ fmt(s.row.platformCost) }}</span>
             </template>
           </el-table-column>
-          <el-table-column label="利润" align="center" width="140">
+          <el-table-column label="利润" align="center" min-width="110">
             <template slot-scope="s">
               <span style="color:#52c41a;font-weight:bold">
                 {{ fmt((s.row.consumeAmount || 0) - (s.row.platformCost || 0)) }}
               </span>
             </template>
           </el-table-column>
-          <el-table-column label="代理分佣" align="center" width="140">
+          <el-table-column label="代理分佣" align="center" min-width="110">
             <template slot-scope="s">
               <span style="color:#722ed1">{{ fmt(s.row.proxyProfit) }}</span>
             </template>
           </el-table-column>
-          <el-table-column label="平台收益" align="center" width="140">
+          <el-table-column label="平台收益" align="center" min-width="110">
             <template slot-scope="s">
               <b style="color:#333">
                 {{ fmt((s.row.tenantPrice || 0) - (s.row.platformCost || 0) - (s.row.proxyProfit || 0)) }}
               </b>
             </template>
           </el-table-column>
-          <el-table-column label="订单数" prop="orderCount" width="80" align="center" />
+          <el-table-column label="订单数" prop="orderCount" min-width="70" align="center" />
         </el-table>
         <el-empty v-if="!loading && typeStats.length === 0" description="暂无数据" :image-size="80" />
       </el-tab-pane>
@@ -115,33 +115,33 @@
       <!-- Tab 3:代理汇总 -->
       <el-tab-pane label="代理汇总" name="agentSummary">
         <el-table border v-loading="loading" :data="agentStats" size="small" style="width:100%">
-          <el-table-column label="代理ID" prop="proxyId" width="80" align="center" />
-          <el-table-column label="代理名称" min-width="140">
+          <el-table-column label="代理ID" prop="proxyId" min-width="70" align="center" />
+          <el-table-column label="代理名称" min-width="120">
             <template slot-scope="s">{{ proxyNames[s.row.proxyId] || '-' }}</template>
           </el-table-column>
-          <el-table-column label="消费金额" align="center" width="140">
+          <el-table-column label="消费金额" align="center" min-width="110">
             <template slot-scope="s">
               <span style="color:#1890ff;font-weight:bold">{{ fmt(s.row.consumeAmount) }}</span>
             </template>
           </el-table-column>
-          <el-table-column label="平台成本" align="center" width="140">
+          <el-table-column label="平台成本" align="center" min-width="110">
             <template slot-scope="s">
               <span style="color:#fa8c16">{{ fmt(s.row.platformCost) }}</span>
             </template>
           </el-table-column>
-          <el-table-column label="总利润" align="center" width="140">
+          <el-table-column label="总利润" align="center" min-width="110">
             <template slot-scope="s">
               <span style="color:#52c41a;font-weight:bold">
                 {{ fmt((s.row.tenantPrice || 0) - (s.row.platformCost || 0)) }}
               </span>
             </template>
           </el-table-column>
-          <el-table-column label="代理分佣" align="center" width="140">
+          <el-table-column label="代理分佣" align="center" min-width="110">
             <template slot-scope="s">
               <span style="color:#722ed1;font-weight:bold">{{ fmt(s.row.proxyProfit) }}</span>
             </template>
           </el-table-column>
-          <el-table-column label="订单数" prop="orderCount" width="80" align="center" />
+          <el-table-column label="订单数" prop="orderCount" min-width="70" align="center" />
         </el-table>
         <el-empty v-if="!loading && agentStats.length === 0" description="暂无数据" :image-size="80" />
       </el-tab-pane>

+ 3 - 3
src/views/admin/keywordManage/index.vue

@@ -26,16 +26,16 @@
 
     <!-- ===== 列表 ===== -->
     <el-table border v-loading="loading" :data="dataList" size="small" style="width:100%">
-      <el-table-column label="关键词ID" prop="keywordId" width="80" align="center" />
+      <el-table-column label="关键词ID" prop="keywordId" min-width="70" align="center" />
       <el-table-column label="关键词内容" prop="keywordContent" min-width="200" />
-      <el-table-column label="类型" prop="keywordType" width="120" align="center">
+      <el-table-column label="类型" prop="keywordType" min-width="100" align="center">
         <template slot-scope="s">
           <el-tag v-if="s.row.keywordType === 1" type="danger" size="mini">违禁词</el-tag>
           <el-tag v-else-if="s.row.keywordType === 2" type="warning" size="mini">敏感词</el-tag>
           <el-tag v-else size="mini">{{ s.row.keywordType || '-' }}</el-tag>
         </template>
       </el-table-column>
-      <el-table-column label="创建时间" prop="createTime" width="160" align="center" />
+      <el-table-column label="创建时间" prop="createTime" min-width="150" align="center" />
       <el-table-column label="操作" align="center" width="160" fixed="right" class-name="small-padding fixed-width">
         <template slot-scope="s">
           <el-button size="mini" type="text" icon="el-icon-edit" @click="openDialog(s.row)">编辑</el-button>

+ 36 - 26
src/views/admin/live/index.vue

@@ -2,14 +2,22 @@
   <div class="app-container">
     <el-card shadow="never" class="mb16 filter-card">
       <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px" size="small">
-      <el-form-item label="租户名称" prop="companyName">
-        <el-input
-          v-model="queryParams.companyName"
-          placeholder="请输入租户名称"
+      <el-form-item label="租户名称" prop="companyId">
+        <el-select
+          v-model="queryParams.companyId"
+          placeholder="选择租户"
           clearable
+          filterable
           size="small"
-          @keyup.enter.native="handleQuery"
-        />
+          style="width: 200px"
+        >
+          <el-option
+            v-for="item in companyList"
+            :key="item.companyId"
+            :label="item.companyName"
+            :value="item.companyId"
+          />
+        </el-select>
       </el-form-item>
       <el-form-item label="直播标题" prop="liveTitle">
         <el-input
@@ -31,8 +39,7 @@
         </el-select>
       </el-form-item>
       <el-form-item>
-        <inline-tenant-selector @change="onTenantChange" />
-      <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
         <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
       </el-form-item>
       </el-form>
@@ -64,9 +71,9 @@
 
     <el-table border size="small" style="width:100%" v-loading="loading" :data="liveList">
       <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="租户名称" align="center" prop="companyName" />
-      <el-table-column label="直播标题" align="center" prop="liveTitle" />
-      <el-table-column label="直播封面" align="center" prop="coverUrl">
+      <el-table-column label="租户名称" align="center" prop="companyName" min-width="120" />
+      <el-table-column label="直播标题" align="center" prop="liveTitle" min-width="150" show-overflow-tooltip />
+      <el-table-column label="直播封面" align="center" prop="coverUrl" min-width="100">
         <template slot-scope="scope">
           <el-image
             v-if="scope.row.coverUrl"
@@ -75,11 +82,11 @@
             fit="contain" />
         </template>
       </el-table-column>
-      <el-table-column label="主播名称" align="center" prop="anchorName" />
-      <el-table-column label="观看人数" align="center" prop="viewerCount" />
-      <el-table-column label="开始时间" align="center" prop="startTime" />
-      <el-table-column label="直播时长" align="center" prop="duration" />
-      <el-table-column label="直播状态" align="center" prop="status">
+      <el-table-column label="主播名称" align="center" prop="anchorName" min-width="90" />
+      <el-table-column label="观看人数" align="center" prop="viewerCount" min-width="80" />
+      <el-table-column label="开始时间" align="center" prop="startTime" min-width="150" />
+      <el-table-column label="直播时长" align="center" prop="duration" min-width="90" />
+      <el-table-column label="直播状态" align="center" prop="status" min-width="90">
         <template slot-scope="scope">
           <el-tag v-if="scope.row.status == 0" type="success">直播中</el-tag>
           <el-tag v-else-if="scope.row.status == 1" type="warning">未开始</el-tag>
@@ -175,8 +182,7 @@
 
 <script>
 import { listAllLives, getLiveInfo, getLiveStatistics, exportAllLives } from '@/api/admin/live'
-
-import InlineTenantSelector from "@/components/InlineTenantSelector"
+import { listAllCompanies } from '@/api/admin/sysCompany'
 
 export default {
   name: 'LiveAdmin',
@@ -192,6 +198,8 @@ export default {
       total: 0,
       // 直播列表
       liveList: [],
+      // 租户列表
+      companyList: [],
       // 统计数据
       statistics: {
         totalCount: 0,
@@ -209,10 +217,9 @@ export default {
       queryParams: {
         pageNum: 1,
         pageSize: 10,
-        companyName: null,
+        companyId: null,
         liveTitle: null,
-        status: null,
-        companyId: null
+        status: null
       },
       // 详情弹窗
       viewOpen: false,
@@ -222,9 +229,16 @@ export default {
     }
   },
   created() {
+    this.getCompanyList()
     this.getList()
   },
   methods: {
+    /** 获取租户列表 */
+    getCompanyList() {
+      listAllCompanies({ pageSize: 9999 }).then(response => {
+        this.companyList = response.rows || []
+      })
+    },
     /** 查询列表 */
     getList() {
       this.loading = true
@@ -239,14 +253,10 @@ export default {
       this.queryParams.pageNum = 1
       this.getList()
     },
-    /** 租户选择变化 */
-    onTenantChange(companyId) {
-      this.queryParams.companyId = companyId
-      this.handleQuery()
-    },
     /** 重置按钮操作 */
     resetQuery() {
       this.resetForm('queryForm')
+      this.queryParams.companyId = null
       this.handleQuery()
     },
     /** 查看详情 */

+ 31 - 17
src/views/admin/liveVideo/index.vue

@@ -2,14 +2,27 @@
   <div class="app-container">
     <el-card shadow="never" class="mb16 filter-card">
       <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" size="small" label-width="100px">
-        <el-form-item label="租户名称" prop="companyName">
-          <el-input v-model="queryParams.companyName" placeholder="请输入租户名称" clearable size="small" @keyup.enter.native="handleQuery" />
+        <el-form-item label="租户名称" prop="companyId">
+          <el-select
+            v-model="queryParams.companyId"
+            placeholder="选择租户"
+            clearable
+            filterable
+            size="small"
+            style="width: 200px"
+          >
+            <el-option
+              v-for="item in companyList"
+              :key="item.companyId"
+              :label="item.companyName"
+              :value="item.companyId"
+            />
+          </el-select>
         </el-form-item>
         <el-form-item label="视频标题" prop="videoTitle">
           <el-input v-model="queryParams.videoTitle" placeholder="请输入视频标题" clearable size="small" @keyup.enter.native="handleQuery" />
         </el-form-item>
         <el-form-item>
-          <inline-tenant-selector @change="onTenantChange" />
           <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
           <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
         </el-form-item>
@@ -17,19 +30,19 @@
     </el-card>
 
     <el-table border size="small" style="width:100%" v-loading="loading" :data="list">
-      <el-table-column label="租户名称" prop="companyName" width="160" />
+      <el-table-column label="租户名称" prop="companyName" min-width="120" />
       <el-table-column label="视频标题" prop="title" :show-overflow-tooltip="true" />
-      <el-table-column label="直播ID" prop="liveId" width="120" />
+      <el-table-column label="直播ID" prop="liveId" min-width="100" />
       <el-table-column label="播放地址" prop="playUrl" :show-overflow-tooltip="true" />
-      <el-table-column label="时长(秒)" prop="duration" width="100" />
-      <el-table-column label="状态" prop="status" width="80" align="center">
+      <el-table-column label="时长(秒)" prop="duration" min-width="80" />
+      <el-table-column label="状态" prop="status" min-width="80" align="center">
         <template slot-scope="scope">
           <el-tag :type="scope.row.status === 1 ? 'success' : 'info'" size="mini">
             {{ scope.row.status === 1 ? '已生成' : '处理中' }}
           </el-tag>
         </template>
       </el-table-column>
-      <el-table-column label="创建时间" prop="createTime" width="160" align="center" />
+      <el-table-column label="创建时间" prop="createTime" min-width="150" align="center" />
     </el-table>
 
     <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
@@ -38,30 +51,35 @@
 
 <script>
 import { listAllLiveVideos } from '@/api/admin/liveVideo'
-import InlineTenantSelector from '@/components/InlineTenantSelector'
+import { listAllCompanies } from '@/api/admin/sysCompany'
 
 export default {
   name: 'AdminLiveVideo',
-  components: { InlineTenantSelector },
   data() {
     return {
       loading: false,
       showSearch: true,
       list: [],
       total: 0,
+      companyList: [],
       queryParams: {
         pageNum: 1,
         pageSize: 10,
-        companyName: null,
-        videoTitle: null,
-        companyId: null
+        companyId: null,
+        videoTitle: null
       }
     }
   },
   created() {
+    this.getCompanyList()
     this.getList()
   },
   methods: {
+    getCompanyList() {
+      listAllCompanies({ pageSize: 9999 }).then(response => {
+        this.companyList = response.rows || []
+      })
+    },
     getList() {
       this.loading = true
       listAllLiveVideos(this.queryParams).then(res => {
@@ -78,10 +96,6 @@ export default {
       this.resetForm('queryForm')
       this.queryParams.companyId = null
       this.handleQuery()
-    },
-    onTenantChange(companyId) {
-      this.queryParams.companyId = companyId
-      this.handleQuery()
     }
   }
 }

+ 20 - 4
src/views/admin/menu.js

@@ -18,6 +18,8 @@ const adminRoutes = {
 
     // 3. 代理管理
     { path: 'proxy', component: () => import('@/views/admin/proxy/index'), name: 'AdminProxy', meta: { title: '代理管理' } },
+    { path: 'serviceCost', component: () => import('@/views/admin/serviceCost/index'), name: 'AdminServiceCost', meta: { title: '收费配置' } },
+    { path: 'agentReport', component: () => import('@/views/admin/agentReport/index'), name: 'AdminAgentReport', meta: { title: '收益报表' } },
 
     // 4. 员工与组织
     { path: 'sysUser', component: () => import('@/views/admin/sysUser/index'), name: 'SysUserAdmin', meta: { title: '员工管理' } },
@@ -55,20 +57,34 @@ const adminRoutes = {
 
     // 9. 财务管理
     { path: 'consumeRecord', component: () => import('@/views/admin/consumeRecord/index'), name: 'AdminConsumeRecord', meta: { title: '消费扣款记录' } },
+    { path: 'consumeReport', component: () => import('@/views/admin/consumeReport/index'), name: 'AdminConsumeReport', meta: { title: '模块消费统计' } },
     { path: 'rechargeRecord', component: () => import('@/views/admin/rechargeRecord/index'), name: 'AdminRechargeRecord', meta: { title: '充值扣款记录' } },
     { path: 'commissionRecord', component: () => import('@/views/admin/commissionRecord/index'), name: 'AdminCommissionRecord', meta: { title: '返佣记录' } },
     { path: 'withdrawal', component: () => import('@/views/admin/withdrawalManage/index'), name: 'AdminWithdrawal', meta: { title: '代理提现管理' } },
 
     // 10. 日志管理
-    { path: 'operlog', component: () => import('@/views/monitor/operlog/index'), name: 'AdminOperlog', meta: { title: '操作日志' } },
-    { path: 'logininfor', component: () => import('@/views/monitor/logininfor/index'), name: 'AdminLogininfor', meta: { title: '登录日志' } },
+    { path: 'operLog', component: () => import('@/views/monitor/operlog/index'), name: 'AdminOperLog', meta: { title: '操作日志' } },
+    { path: 'loginLog', component: () => import('@/views/monitor/logininfor/index'), name: 'AdminLoginLog', meta: { title: '登录日志' } },
     { path: 'companyOperLog', component: () => import('@/views/monitor/componentsOperLog/index'), name: 'AdminCompanyOperLog', meta: { title: '租户操作日志' } },
     { path: 'proxyOperLog', component: () => import('@/views/admin/proxyOperLog/index'), name: 'AdminProxyOperLog', meta: { title: '代理操作日志' } },
 
-    // 11. 其他管理
+    // 11. 内容审计
+    { path: 'videoResource', component: () => import('@/views/admin/videoResource/index'), name: 'AdminVideoResource', meta: { title: '视频资源' } },
+    { path: 'course', component: () => import('@/views/admin/course/index'), name: 'AdminCourse', meta: { title: '公域课程管理' } },
+    { path: 'live', component: () => import('@/views/admin/live/index'), name: 'AdminLive', meta: { title: '直播间' } },
+    { path: 'liveVideo', component: () => import('@/views/admin/liveVideo/index'), name: 'AdminLiveVideo', meta: { title: '直播视频' } },
+    { path: 'product', component: () => import('@/views/admin/product/index'), name: 'AdminProduct', meta: { title: '商品管理' } },
+    { path: 'storeOrder', component: () => import('@/views/admin/storeOrder/index'), name: 'AdminStoreOrder', meta: { title: '销售订单' } },
+    { path: 'article', component: () => import('@/views/admin/article/index'), name: 'AdminArticle', meta: { title: '文章管理' } },
+
+    // 12. 其他管理
     { path: 'ipadServer', component: () => import('@/views/admin/ipadServer/index'), name: 'AdminIpadServer', meta: { title: 'Ipad服务器' } },
     { path: 'keywordManage', component: () => import('@/views/admin/keywordManage/index'), name: 'AdminKeywordManage', meta: { title: '关键词管理' } },
-    { path: 'textModel', component: () => import('@/views/admin/textModel/index'), name: 'AdminTextModel', meta: { title: '文本模型配置' } }
+    { path: 'textModel', component: () => import('@/views/admin/textModel/index'), name: 'AdminTextModel', meta: { title: '文本模型配置' } },
+
+    // 13. Lobster 引擎(挂在 /admin 下,避免 /workflow 代理到 8006 导致 401)
+    { path: 'workflowGenerate', component: () => import('@/views/lobster/workflow-generate/index'), name: 'AdminWorkflowGenerate', meta: { title: 'AI生成工作流' } },
+    { path: 'salesCorpus', component: () => import('@/views/lobster/sales-corpus/index'), name: 'AdminSalesCorpus', meta: { title: '销冠语料学习' } }
   ]
 }
 

+ 38 - 26
src/views/admin/moduleUsage/index.vue

@@ -44,37 +44,43 @@
 
     <!-- ===== 租户用量汇总表 ===== -->
     <el-table border v-loading="loading" :data="list" size="small" style="width:100%" show-summary :summary-method="getSummary">
-      <el-table-column label="租户名称" align="center" prop="tenantName" min-width="140" fixed="left" />
-      <el-table-column label="归属代理" align="center" prop="proxyName" min-width="120" />
+      <el-table-column label="租户名称" align="center" prop="tenantName" min-width="120" fixed="left" />
+      <el-table-column label="归属代理" align="center" prop="proxyName" min-width="100" />
       <!-- AI 模型用量 -->
-      <el-table-column label="AI Token" align="center" prop="aiTokenTotal" width="100" />
-      <el-table-column label="AI外呼" align="center" prop="aiCallTotal" width="80" />
-      <el-table-column label="AI对话" align="center" prop="aiChatTotal" width="80" />
+      <el-table-column label="AI Token" align="center" prop="aiTokenTotal" min-width="90" />
+      <el-table-column label="AI外呼" align="center" prop="aiCallTotal" min-width="70" />
+      <el-table-column label="AI对话" align="center" prop="aiChatTotal" min-width="70" />
       <!-- 通讯 -->
-      <el-table-column label="外呼次数" align="center" prop="outboundCallCount" width="85" />
-      <el-table-column label="短信量" align="center" prop="smsSentCount" width="75" />
+      <el-table-column label="外呼次数" align="center" prop="outboundCallCount" min-width="75" />
+      <el-table-column label="语音分钟" align="center" prop="voiceCallMinutes" min-width="75" />
+      <el-table-column label="短信量" align="center" prop="smsSentCount" min-width="65" />
+      <el-table-column label="流量(GB)" align="center" prop="flowUsageGB" min-width="75" />
       <!-- 个微 -->
-      <el-table-column label="个微用户" align="center" prop="wxUserCount" width="85" />
-      <el-table-column label="个微绑定" align="center" prop="wxAccountCount" width="85" />
-      <el-table-column label="个微客户" align="center" prop="wxCustomerCount" width="85" />
+      <el-table-column label="个微用户" align="center" prop="wxUserCount" min-width="75" />
+      <el-table-column label="CID加个微" align="center" prop="cidAddWxCount" min-width="80" />
+      <el-table-column label="个微绑定" align="center" prop="wxAccountCount" min-width="75" />
+      <el-table-column label="个微客户" align="center" prop="wxCustomerCount" min-width="75" />
       <!-- 企微 -->
-      <el-table-column label="企微用户" align="center" prop="qwUserCount" width="85" />
-      <el-table-column label="企微绑定" align="center" prop="qwAccountCount" width="85" />
+      <el-table-column label="企微用户" align="center" prop="qwUserCount" min-width="75" />
+      <el-table-column label="CID加企微" align="center" prop="cidAddQwCount" min-width="80" />
+      <el-table-column label="企微绑定" align="center" prop="qwAccountCount" min-width="75" />
       <!-- 业务模块 -->
-      <el-table-column label="SOP" align="center" prop="sopCount" width="60" />
-      <el-table-column label="课程" align="center" prop="courseCount" width="60" />
-      <el-table-column label="直播" align="center" prop="liveCount" width="60" />
-      <el-table-column label="商品" align="center" prop="productCount" width="60" />
-      <el-table-column label="订单" align="center" prop="orderCount" width="60" />
+      <el-table-column label="SOP" align="center" prop="sopCount" min-width="55" />
+      <el-table-column label="课程" align="center" prop="courseCount" min-width="55" />
+      <el-table-column label="直播" align="center" prop="liveCount" min-width="55" />
+      <el-table-column label="商品" align="center" prop="productCount" min-width="55" />
+      <el-table-column label="订单" align="center" prop="orderCount" min-width="55" />
       <!-- 龙虾引擎 -->
-      <el-table-column label="工作流" align="center" prop="workflowCount" width="75" />
-      <el-table-column label="龙虾模板" align="center" prop="lobsterTemplateCount" width="85" />
-      <el-table-column label="龙虾任务" align="center" prop="lobsterTaskCount" width="85" />
+      <el-table-column label="工作流" align="center" prop="workflowCount" min-width="65" />
+      <el-table-column label="龙虾模板" align="center" prop="lobsterTemplateCount" min-width="75" />
+      <el-table-column label="龙虾任务" align="center" prop="lobsterTaskCount" min-width="75" />
       <!-- 组织 -->
-      <el-table-column label="部门" align="center" prop="deptCount" width="60" />
-      <el-table-column label="员工" align="center" prop="employeeCount" width="60" />
+      <el-table-column label="部门" align="center" prop="deptCount" min-width="55" />
+      <el-table-column label="员工" align="center" prop="employeeCount" min-width="55" />
+      <el-table-column label="销售账户" align="center" prop="salesAccountCount" min-width="75" />
+      <el-table-column label="AI Token" align="center" prop="aiTokenUsed" min-width="90" />
       <!-- 汇总 -->
-      <el-table-column label="活跃模块" align="center" prop="activeModuleCount" width="85">
+      <el-table-column label="活跃模块" align="center" prop="activeModuleCount" min-width="75">
         <template slot-scope="scope">
           <el-tag type="success" size="mini">{{ scope.row.activeModuleCount || 0 }}</el-tag>
         </template>
@@ -110,14 +116,18 @@
         </el-descriptions-item>
         <!-- 通讯 -->
         <el-descriptions-item label="外呼拨打次数">{{ detail.outboundCallCount || 0 }}</el-descriptions-item>
+        <el-descriptions-item label="语音通话(分钟)">{{ detail.voiceCallMinutes || 0 }}</el-descriptions-item>
         <el-descriptions-item label="短信发送量">{{ detail.smsSentCount || 0 }}</el-descriptions-item>
+        <el-descriptions-item label="流量(GB)">{{ detail.flowUsageGB || '0.00' }}</el-descriptions-item>
         <el-descriptions-item label="SOP数量">{{ detail.sopCount || 0 }}</el-descriptions-item>
         <!-- 个微 -->
         <el-descriptions-item label="个微用户数">{{ detail.wxUserCount || 0 }}</el-descriptions-item>
+        <el-descriptions-item label="CID加个微">{{ detail.cidAddWxCount || 0 }}</el-descriptions-item>
         <el-descriptions-item label="绑定个微账号">{{ detail.wxAccountCount || 0 }}</el-descriptions-item>
         <el-descriptions-item label="个微客户数">{{ detail.wxCustomerCount || 0 }}</el-descriptions-item>
         <!-- 企微 -->
         <el-descriptions-item label="企微用户数">{{ detail.qwUserCount || 0 }}</el-descriptions-item>
+        <el-descriptions-item label="CID加企微">{{ detail.cidAddQwCount || 0 }}</el-descriptions-item>
         <el-descriptions-item label="绑定企微号数">{{ detail.qwAccountCount || 0 }}</el-descriptions-item>
         <el-descriptions-item label="课程数量">{{ detail.courseCount || 0 }}</el-descriptions-item>
         <!-- 业务 -->
@@ -131,6 +141,8 @@
         <!-- 组织 -->
         <el-descriptions-item label="部门数量">{{ detail.deptCount || 0 }}</el-descriptions-item>
         <el-descriptions-item label="员工数量">{{ detail.employeeCount || 0 }}</el-descriptions-item>
+        <el-descriptions-item label="销售账户数">{{ detail.salesAccountCount || 0 }}</el-descriptions-item>
+        <el-descriptions-item label="AI Token用量">{{ detail.aiTokenUsed || 0 }}</el-descriptions-item>
         <el-descriptions-item label="活跃模块数">
           <el-tag type="success" size="mini">{{ detail.activeModuleCount || 0 }}</el-tag>
         </el-descriptions-item>
@@ -204,10 +216,10 @@ export default {
         if (idx === 0) { sums[idx] = '合计'; return }
         const prop = col.property
         if (!prop) { sums[idx] = ''; return }
-        const numFields = ['aiTokenTotal','aiCallTotal','aiChatTotal','outboundCallCount','smsSentCount',
-          'wxUserCount','wxAccountCount','wxCustomerCount','qwUserCount','qwAccountCount',
+        const numFields = ['aiTokenTotal','aiCallTotal','aiChatTotal','outboundCallCount','voiceCallMinutes','smsSentCount','flowUsageGB',
+          'wxUserCount','cidAddWxCount','wxAccountCount','wxCustomerCount','qwUserCount','cidAddQwCount','qwAccountCount',
           'sopCount','courseCount','liveCount','productCount','orderCount',
-          'workflowCount','lobsterTemplateCount','lobsterTaskCount','deptCount','employeeCount','activeModuleCount']
+          'workflowCount','lobsterTemplateCount','lobsterTaskCount','deptCount','employeeCount','salesAccountCount','aiTokenUsed','activeModuleCount']
         if (numFields.includes(prop)) {
           const val = data.reduce((s, r) => s + (r[prop] || 0), 0)
           sums[idx] = val

+ 36 - 26
src/views/admin/product/index.vue

@@ -2,14 +2,22 @@
   <div class="app-container">
     <el-card shadow="never" class="mb16 filter-card">
       <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" size="small" label-width="100px">
-        <el-form-item label="租户名称" prop="companyName">
-          <el-input
-            v-model="queryParams.companyName"
-            placeholder="请输入租户名称"
+        <el-form-item label="租户名称" prop="companyId">
+          <el-select
+            v-model="queryParams.companyId"
+            placeholder="选择租户"
             clearable
+            filterable
             size="small"
-            @keyup.enter.native="handleQuery"
-          />
+            style="width: 200px"
+          >
+            <el-option
+              v-for="item in companyList"
+              :key="item.companyId"
+              :label="item.companyName"
+              :value="item.companyId"
+            />
+          </el-select>
         </el-form-item>
         <el-form-item label="商品名称" prop="productName">
           <el-input
@@ -31,8 +39,7 @@
           </el-select>
         </el-form-item>
         <el-form-item>
-          <inline-tenant-selector @change="onTenantChange" />
-        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+          <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
           <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
         </el-form-item>
       </el-form>
@@ -73,9 +80,9 @@
 
     <el-table border size="small" style="width:100%" v-loading="loading" :data="productList">
       <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="租户名称" align="center" prop="companyName" />
-      <el-table-column label="商品名称" align="center" prop="productName" />
-      <el-table-column label="商品图片" align="center" prop="productImage">
+      <el-table-column label="租户名称" align="center" prop="companyName" min-width="120" />
+      <el-table-column label="商品名称" align="center" prop="productName" min-width="150" show-overflow-tooltip />
+      <el-table-column label="商品图片" align="center" prop="productImage" min-width="100">
         <template slot-scope="scope">
           <el-image
             v-if="scope.row.productImage"
@@ -84,11 +91,11 @@
             fit="contain" />
         </template>
       </el-table-column>
-      <el-table-column label="商品分类" align="center" prop="categoryName" />
-      <el-table-column label="商品价格" align="center" prop="price" />
-      <el-table-column label="库存数量" align="center" prop="stock" />
-      <el-table-column label="创建时间" align="center" prop="createTime" />
-      <el-table-column label="商品状态" align="center" prop="status">
+      <el-table-column label="商品分类" align="center" prop="categoryName" min-width="100" />
+      <el-table-column label="商品价格" align="center" prop="price" min-width="90" />
+      <el-table-column label="库存数量" align="center" prop="stock" min-width="80" />
+      <el-table-column label="创建时间" align="center" prop="createTime" min-width="150" />
+      <el-table-column label="商品状态" align="center" prop="status" min-width="90">
         <template slot-scope="scope">
           <el-tag v-if="scope.row.status == 0" type="warning">待审核</el-tag>
           <el-tag v-else-if="scope.row.status == 1" type="success">已上架</el-tag>
@@ -217,8 +224,7 @@
 
 <script>
 import { listAllProducts, listPendingProducts, getProductInfo, auditProduct, getProductStatistics, exportAllProducts } from '@/api/admin/product'
-
-import InlineTenantSelector from "@/components/InlineTenantSelector"
+import { listAllCompanies } from '@/api/admin/sysCompany'
 
 export default {
   name: 'ProductAdmin',
@@ -234,6 +240,8 @@ export default {
       total: 0,
       // 商品列表
       productList: [],
+      // 租户列表
+      companyList: [],
       // 统计数据
       statistics: {
         totalCount: 0,
@@ -251,10 +259,9 @@ export default {
       queryParams: {
         pageNum: 1,
         pageSize: 10,
-        companyName: null,
+        companyId: null,
         productName: null,
-        status: null,
-        companyId: null
+        status: null
       },
       // 详情弹窗
       viewOpen: false,
@@ -272,9 +279,16 @@ export default {
     }
   },
   created() {
+    this.getCompanyList()
     this.getList()
   },
   methods: {
+    /** 获取租户列表 */
+    getCompanyList() {
+      listAllCompanies({ pageSize: 9999 }).then(response => {
+        this.companyList = response.rows || []
+      })
+    },
     /** 查询列表 */
     getList() {
       this.loading = true
@@ -298,14 +312,10 @@ export default {
       this.queryParams.pageNum = 1
       this.getList()
     },
-    /** 租户选择变化 */
-    onTenantChange(companyId) {
-      this.queryParams.companyId = companyId
-      this.handleQuery()
-    },
     /** 重置按钮操作 */
     resetQuery() {
       this.resetForm('queryForm')
+      this.queryParams.companyId = null
       this.handleQuery()
     },
     /** 查看详情 */

+ 12 - 12
src/views/admin/proxy/index.vue

@@ -29,36 +29,36 @@
 
     <!-- ===== 代理列表 ===== -->
     <el-table border v-loading="proxyLoading" :data="proxyList" size="small" style="width:100%">
-      <el-table-column label="ID" prop="proxyId" width="80" align="center" />
-      <el-table-column label="代理编码" prop="proxyCode" width="110" align="center">
+      <el-table-column label="ID" prop="proxyId" min-width="60" align="center" />
+      <el-table-column label="代理编码" prop="proxyCode" min-width="100" align="center">
         <template slot-scope="s">
           <span style="color:#1890ff">{{ s.row.proxyCode || '-' }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="层级" prop="proxyLevel" width="80" align="center">
+      <el-table-column label="层级" prop="proxyLevel" min-width="80" align="center">
         <template slot-scope="s">
           <el-tag v-if="s.row.proxyLevel === 1" type="success" size="mini">一级代理</el-tag>
           <el-tag v-else-if="s.row.proxyLevel === 2" type="warning" size="mini">二级代理</el-tag>
           <el-tag v-else size="mini">{{ s.row.proxyLevel || '-' }}</el-tag>
         </template>
       </el-table-column>
-      <el-table-column label="代理名称" prop="proxyName" min-width="120" />
-      <el-table-column label="联系人" prop="contactName" width="90" align="center" />
-      <el-table-column label="联系方式" prop="contactMobile" width="120" align="center" />
-      <el-table-column label="平台折扣(%)" prop="platformDiscount" width="100" align="center">
+      <el-table-column label="代理名称" prop="proxyName" min-width="110" />
+      <el-table-column label="联系人" prop="contactName" min-width="80" align="center" />
+      <el-table-column label="联系方式" prop="contactMobile" min-width="110" align="center" />
+      <el-table-column label="平台折扣(%)" prop="platformDiscount" min-width="90" align="center">
         <template slot-scope="s">{{ s.row.platformDiscount || s.row.profitShareRatio || 0 }}%</template>
       </el-table-column>
-      <el-table-column label="代理余额" align="center" width="110">
+      <el-table-column label="代理余额" align="center" min-width="90">
         <template slot-scope="s">
           <span style="color:#1890ff;font-weight:bold">¥{{ s.row.balance || 0 }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="累计佣金" align="center" width="110">
+      <el-table-column label="累计佣金" align="center" min-width="90">
         <template slot-scope="s">¥{{ s.row.totalCommission || 0 }}</template>
       </el-table-column>
-      <el-table-column label="发展客户" align="center" prop="tenantCount" width="80" />
-      <el-table-column label="到期时间" align="center" prop="expireTime" width="100" />
-      <el-table-column label="状态" align="center" width="80">
+      <el-table-column label="发展客户" align="center" prop="tenantCount" min-width="80" />
+      <el-table-column label="到期时间" align="center" prop="expireTime" min-width="95" />
+      <el-table-column label="状态" align="center" min-width="70">
         <template slot-scope="s">
           <el-switch v-model="s.row.status" :active-value="1" :inactive-value="0"
             @change="handleStatusChange(s.row)" v-hasPermi="['admin:proxy:edit']" />

+ 10 - 10
src/views/admin/sms/index.vue

@@ -51,20 +51,20 @@
 
     <!-- 表格 -->
     <el-table v-loading="loading" :data="smsList" border size="small" style="width:100%">
-      <el-table-column label="ID" align="center" prop="smsId" width="80" />
-      <el-table-column label="所属租户" align="center" prop="companyName" min-width="140" />
-      <el-table-column label="短信剩余条数" align="center" prop="smsRemain" width="130">
+      <el-table-column label="ID" align="center" prop="smsId" min-width="60" />
+      <el-table-column label="所属租户" align="center" prop="companyName" min-width="120" />
+      <el-table-column label="短信剩余条数" align="center" prop="smsRemain" min-width="110">
         <template slot-scope="scope">
           <span style="color:#1890ff;font-weight:bold">{{ scope.row.smsRemain || 0 }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="状态" align="center" width="100">
+      <el-table-column label="状态" align="center" min-width="80">
         <template slot-scope="scope">
           <el-tag v-if="scope.row.status === '0' || scope.row.status === 0" type="success" size="mini">正常</el-tag>
           <el-tag v-else type="danger" size="mini">禁用</el-tag>
         </template>
       </el-table-column>
-      <el-table-column label="创建时间" align="center" prop="createTime" width="160" />
+      <el-table-column label="创建时间" align="center" prop="createTime" min-width="150" />
       <el-table-column label="操作" align="center" width="100" fixed="right">
         <template slot-scope="scope">
           <el-button size="mini" type="text" icon="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
@@ -151,7 +151,7 @@ export default {
     getList() {
       this.loading = true
       request({
-        url: '/company/companySms/list',
+        url: '/admin/sms-admin/list',
         method: 'get',
         params: this.queryParams
       }).then(res => {
@@ -162,7 +162,7 @@ export default {
     },
     getCount() {
       request({
-        url: '/company/companySms/getCompanySmsCount',
+        url: '/admin/sms-admin/count',
         method: 'get'
       }).then(res => {
         this.smsCount = res.data || {}
@@ -178,7 +178,7 @@ export default {
     },
     handleEdit(row) {
       request({
-        url: '/company/companySms/' + row.smsId,
+        url: '/admin/sms-admin/' + row.smsId,
         method: 'get'
       }).then(res => {
         const data = res.data || {}
@@ -210,7 +210,7 @@ export default {
           data.smsRemain = Math.max(0, (this.editForm.smsRemain || 0) - (this.editForm.adjustCount || 0))
         }
         request({
-          url: '/company/companySms',
+          url: '/admin/sms-admin',
           method: 'put',
           data: data
         }).then(() => {
@@ -224,7 +224,7 @@ export default {
     handleExport() {
       this.exportLoading = true
       request({
-        url: '/company/companySms/export',
+        url: '/admin/sms-admin/export',
         method: 'get',
         params: this.queryParams,
         responseType: 'blob'

+ 10 - 10
src/views/admin/smsOrder/index.vue

@@ -23,21 +23,21 @@
 
     <!-- 表格 -->
     <el-table v-loading="loading" :data="orderList" border size="small" style="width:100%">
-      <el-table-column label="订单ID" align="center" prop="orderId" width="80" />
-      <el-table-column label="所属租户" align="center" prop="companyName" min-width="140" />
-      <el-table-column label="套餐名称" align="center" prop="packageName" min-width="140" />
-      <el-table-column label="购买条数" align="center" prop="smsCount" width="110">
+      <el-table-column label="订单ID" align="center" prop="orderId" min-width="70" />
+      <el-table-column label="所属租户" align="center" prop="companyName" min-width="120" />
+      <el-table-column label="套餐名称" align="center" prop="packageName" min-width="120" />
+      <el-table-column label="购买条数" align="center" prop="smsCount" min-width="90">
         <template slot-scope="scope">
           <span style="font-weight:bold">{{ scope.row.smsCount || 0 }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="金额" align="center" prop="amount" width="120">
+      <el-table-column label="金额" align="center" prop="amount" min-width="100">
         <template slot-scope="scope">
           <span style="color:#fa8c16;font-weight:bold">¥{{ scope.row.amount ? Number(scope.row.amount).toFixed(2) : '0.00' }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="购买时间" align="center" prop="createTime" width="160" />
-      <el-table-column label="状态" align="center" width="100">
+      <el-table-column label="购买时间" align="center" prop="createTime" min-width="150" />
+      <el-table-column label="状态" align="center" min-width="85">
         <template slot-scope="scope">
           <el-tag v-if="scope.row.status === '0' || scope.row.status === 0" type="success" size="mini">成功</el-tag>
           <el-tag v-else-if="scope.row.status === '1' || scope.row.status === 1" type="warning" size="mini">待支付</el-tag>
@@ -104,7 +104,7 @@ export default {
     getList() {
       this.loading = true
       request({
-        url: '/company/companySmsOrder/list',
+        url: '/admin/sms-order/list',
         method: 'get',
         params: this.queryParams
       }).then(res => {
@@ -123,7 +123,7 @@ export default {
     },
     handleDetail(row) {
       request({
-        url: '/company/companySmsOrder/' + row.orderId,
+        url: '/admin/sms-order/' + row.orderId,
         method: 'get'
       }).then(res => {
         this.detail = res.data || {}
@@ -133,7 +133,7 @@ export default {
     handleExport() {
       this.exportLoading = true
       request({
-        url: '/company/companySmsOrder/export',
+        url: '/admin/sms-order/export',
         method: 'get',
         params: this.queryParams,
         responseType: 'blob'

+ 1 - 1
src/views/admin/smsPackage/index.vue

@@ -82,7 +82,7 @@ export default {
     getList() {
       this.loading = true
       request({
-        url: '/company/companySmsPackage/list',
+        url: '/admin/sms-package/list',
         method: 'get',
         params: this.queryParams
       }).then(res => {

+ 25 - 12
src/views/admin/storeOrder/index.vue

@@ -2,14 +2,27 @@
   <div class="app-container">
     <el-card shadow="never" class="mb16 filter-card">
       <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" size="small" label-width="100px">
-        <el-form-item label="租户名称" prop="companyName">
-          <el-input v-model="queryParams.companyName" placeholder="请输入租户名称" clearable size="small" @keyup.enter.native="handleQuery" />
+        <el-form-item label="租户名称" prop="companyId">
+          <el-select
+            v-model="queryParams.companyId"
+            placeholder="选择租户"
+            clearable
+            filterable
+            size="small"
+            style="width: 200px"
+          >
+            <el-option
+              v-for="item in companyList"
+              :key="item.companyId"
+              :label="item.companyName"
+              :value="item.companyId"
+            />
+          </el-select>
         </el-form-item>
         <el-form-item label="订单编号" prop="orderNo">
           <el-input v-model="queryParams.orderNo" placeholder="请输入订单编号" clearable size="small" @keyup.enter.native="handleQuery" />
         </el-form-item>
         <el-form-item>
-          <inline-tenant-selector @change="onTenantChange" />
           <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
           <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
         </el-form-item>
@@ -38,20 +51,24 @@
 
 <script>
 import { listStoreOrder, exportStoreOrder } from '@/api/admin/storeOrder'
-import InlineTenantSelector from "@/components/InlineTenantSelector"
+import { listAllCompanies } from '@/api/admin/sysCompany'
 
 export default {
   name: 'AdminStoreOrder',
-  components: { InlineTenantSelector },
   data() {
     return {
       loading: false, exportLoading: false, showSearch: true,
-      list: [], total: 0,
-      queryParams: { pageNum: 1, pageSize: 10, orderNo: null, companyName: null, companyId: null }
+      list: [], total: 0, companyList: [],
+      queryParams: { pageNum: 1, pageSize: 10, orderNo: null, companyId: null }
     }
   },
-  created() { this.getList() },
+  created() { this.getCompanyList(); this.getList() },
   methods: {
+    getCompanyList() {
+      listAllCompanies({ pageSize: 9999 }).then(response => {
+        this.companyList = response.rows || []
+      })
+    },
     getList() {
       this.loading = true
       listStoreOrder(this.queryParams).then(res => {
@@ -60,10 +77,6 @@ export default {
         this.loading = false
       }).catch(() => { this.loading = false })
     },
-    onTenantChange(companyId) {
-      this.queryParams.companyId = companyId
-      this.handleQuery()
-    },
     handleQuery() { this.queryParams.pageNum = 1; this.getList() },
     resetQuery() { this.resetForm('queryForm'); this.queryParams.companyId = null; this.handleQuery() },
     handleExport() {

+ 15 - 15
src/views/admin/sysCompany/index.vue

@@ -31,29 +31,29 @@
 
     <!-- ===== 租户列表 ===== -->
     <el-table border v-loading="loading" :data="companyList" size="small" style="width:100%">
-      <el-table-column label="租户编码" prop="companyId" width="100" align="center" />
-      <el-table-column label="租户名称" prop="companyName" min-width="140" />
-      <el-table-column label="联系人" prop="contactName" width="90" align="center" />
-      <el-table-column label="联系电话" prop="contactPhone" width="120" align="center" />
-      <el-table-column label="余额" align="center" width="110">
+      <el-table-column label="租户编码" prop="companyId" min-width="80" align="center" />
+      <el-table-column label="租户名称" prop="companyName" min-width="120" />
+      <el-table-column label="联系人" prop="contactName" min-width="80" align="center" />
+      <el-table-column label="联系电话" prop="contactPhone" min-width="110" align="center" />
+      <el-table-column label="余额" align="center" min-width="100">
         <template slot-scope="scope">
           <span style="color:#1890ff;font-weight:bold">{{ scope.row.balance ? '¥' + Number(scope.row.balance).toFixed(2) : '¥0.00' }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="已消费总额" align="center" width="120">
+      <el-table-column label="已消费总额" align="center" min-width="100">
         <template slot-scope="scope">
           <span>{{ scope.row.totalCost ? '¥' + Number(scope.row.totalCost).toFixed(2) : '¥0.00' }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="开通账户数" align="center" prop="accountCount" width="110" />
-      <el-table-column label="绑定企微账户数" align="center" prop="qwAccountCount" width="130" />
-      <el-table-column label="绑定个微账户数" align="center" prop="wxAccountCount" width="130" />
-      <el-table-column label="客户数" align="center" prop="customerCount" width="100" />
-      <el-table-column label="企微用户数" align="center" prop="qwUserCount" width="110" />
-      <el-table-column label="过期时间" align="center" prop="expireTime" width="110" />
-      <el-table-column label="归属代理" align="center" prop="proxyName" width="110" />
-      <el-table-column label="备注" prop="remark" min-width="120" show-overflow-tooltip />
-      <el-table-column label="状态" align="center" width="80">
+      <el-table-column label="开通账户数" align="center" prop="accountCount" min-width="90" />
+      <el-table-column label="绑定企微账户数" align="center" prop="qwAccountCount" min-width="110" />
+      <el-table-column label="绑定个微账户数" align="center" prop="wxAccountCount" min-width="110" />
+      <el-table-column label="客户数" align="center" prop="customerCount" min-width="80" />
+      <el-table-column label="企微用户数" align="center" prop="qwUserCount" min-width="90" />
+      <el-table-column label="过期时间" align="center" prop="expireTime" min-width="100" />
+      <el-table-column label="归属代理" align="center" prop="proxyName" min-width="90" />
+      <el-table-column label="备注" prop="remark" min-width="100" show-overflow-tooltip />
+      <el-table-column label="状态" align="center" min-width="70">
         <template slot-scope="scope">
           <el-tag v-if="scope.row.status == 0" type="success" size="mini">正常</el-tag>
           <el-tag v-else type="danger" size="mini">禁用</el-tag>

+ 2 - 4
src/views/admin/textModel/index.vue

@@ -52,12 +52,10 @@ export default {
   methods: {
     loadConfig() {
       request({ url: '/system/config/getConfigByKey/text.model.config', method: 'get' }).then(r => {
-        if (r.msg && r.msg !== 'text.model.config') {
-          // r.msg is the configValue string
+        if (r.data && r.data.configValue) {
           try {
-            const saved = JSON.parse(r.msg)
+            const saved = JSON.parse(r.data.configValue)
             if (Array.isArray(saved) && saved.length > 0) {
-              // 合并:保留默认placeholder等,覆盖已保存值
               this.models = this.models.map((m, i) => {
                 if (saved[i]) {
                   return { ...m, ...saved[i] }

+ 27 - 13
src/views/admin/videoResource/index.vue

@@ -2,14 +2,27 @@
   <div class="app-container">
     <el-card shadow="never" class="mb16 filter-card">
       <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" size="small" label-width="100px">
-        <el-form-item label="租户名称" prop="companyName">
-          <el-input v-model="queryParams.companyName" placeholder="请输入租户名称" clearable size="small" @keyup.enter.native="handleQuery" />
+        <el-form-item label="租户名称" prop="companyId">
+          <el-select
+            v-model="queryParams.companyId"
+            placeholder="选择租户"
+            clearable
+            filterable
+            size="small"
+            style="width: 200px"
+          >
+            <el-option
+              v-for="item in companyList"
+              :key="item.companyId"
+              :label="item.companyName"
+              :value="item.companyId"
+            />
+          </el-select>
         </el-form-item>
         <el-form-item label="视频名称" prop="videoName">
           <el-input v-model="queryParams.videoName" placeholder="请输入视频名称" clearable size="small" @keyup.enter.native="handleQuery" />
         </el-form-item>
-        <el-form-item>
-          <inline-tenant-selector @change="onTenantChange" />
+        <el-form-item label-width="0px">
           <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
           <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
         </el-form-item>
@@ -38,30 +51,35 @@
 
 <script>
 import { listAllVideoResources } from '@/api/admin/videoResource'
-import InlineTenantSelector from '@/components/InlineTenantSelector'
+import { listAllCompanies } from '@/api/admin/sysCompany'
 
 export default {
   name: 'AdminVideoResource',
-  components: { InlineTenantSelector },
   data() {
     return {
       loading: false,
       showSearch: true,
       list: [],
       total: 0,
+      companyList: [],
       queryParams: {
         pageNum: 1,
         pageSize: 10,
-        companyName: null,
-        videoName: null,
-        companyId: null
+        companyId: null,
+        videoName: null
       }
     }
   },
   created() {
+    this.getCompanyList()
     this.getList()
   },
   methods: {
+    getCompanyList() {
+      listAllCompanies({ pageSize: 9999 }).then(response => {
+        this.companyList = response.rows || []
+      })
+    },
     getList() {
       this.loading = true
       listAllVideoResources(this.queryParams).then(res => {
@@ -78,10 +96,6 @@ export default {
       this.resetForm('queryForm')
       this.queryParams.companyId = null
       this.handleQuery()
-    },
-    onTenantChange(companyId) {
-      this.queryParams.companyId = companyId
-      this.handleQuery()
     }
   }
 }

+ 12 - 1
src/views/admin/voice/index.vue

@@ -131,7 +131,18 @@
 </template>
 
 <script>
-import { listRobotic, getRobotic, exportRobotic } from '@/api/company/companyVoiceRobotic'
+import request from '@/utils/request'
+
+// admin专用:走 /admin/ 前缀,代理到 fs-admin 8004
+function listRobotic(query) {
+  return request({ url: '/admin/voice-robotic/list', method: 'get', params: query })
+}
+function getRobotic(id) {
+  return request({ url: '/admin/voice-robotic/' + id, method: 'get' })
+}
+function exportRobotic(query) {
+  return request({ url: '/admin/voice-robotic/export', method: 'get', params: query })
+}
 
 export default {
   name: 'AdminVoice',

+ 13 - 7
src/views/admin/voiceApi/index.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="app-container">
     <el-card shadow="never" class="mb16 filter-card">
-      <el-form :model="queryParams" ref="queryForm" :inline="true" size="small" label-width="100px">
+      <el-form :model="queryParams" ref="queryForm" :inline="true" size="small">
         <el-form-item label="租户名称" prop="companyName">
           <el-input
             v-model="queryParams.companyName"
@@ -22,21 +22,21 @@
     </el-row>
 
     <el-table v-loading="loading" :data="dataList" border size="small" style="width:100%">
-      <el-table-column label="接口ID" align="center" prop="apiId" width="80" />
-      <el-table-column label="所属租户" align="center" prop="companyName" min-width="140" show-overflow-tooltip />
-      <el-table-column label="接口类型" align="center" prop="apiType" width="120">
+      <el-table-column label="接口ID" align="center" prop="apiId" min-width="70" />
+      <el-table-column label="所属租户" align="center" prop="companyName" min-width="120" show-overflow-tooltip />
+      <el-table-column label="接口类型" align="center" prop="apiType" min-width="100">
         <template slot-scope="scope">
           <span>{{ getApiTypeLabel(scope.row.apiType) }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="接口名称" align="center" prop="apiName" min-width="140" show-overflow-tooltip />
-      <el-table-column label="状态" align="center" prop="status" width="100">
+      <el-table-column label="接口名称" align="center" prop="apiName" min-width="130" show-overflow-tooltip />
+      <el-table-column label="状态" align="center" prop="status" min-width="80">
         <template slot-scope="scope">
           <el-tag v-if="scope.row.status === 0 || scope.row.status === '0'" type="danger">禁用</el-tag>
           <el-tag v-else type="success">启用</el-tag>
         </template>
       </el-table-column>
-      <el-table-column label="创建时间" align="center" prop="createTime" width="160" />
+      <el-table-column label="创建时间" align="center" prop="createTime" min-width="150" />
       <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="120">
         <template slot-scope="scope">
           <el-button
@@ -160,3 +160,9 @@ export default {
   }
 }
 </script>
+
+<style scoped>
+.mb8 { margin-bottom: 8px; }
+.mb16 { margin-bottom: 16px; }
+.filter-card { padding-bottom: 0; }
+</style>

+ 8 - 2
src/views/admin/voiceBlacklist/index.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="app-container">
     <el-card shadow="never" class="mb16 filter-card">
-      <el-form :model="queryParams" ref="queryForm" :inline="true" size="small" label-width="100px">
+      <el-form :model="queryParams" ref="queryForm" :inline="true" size="small">
         <el-form-item label="手机号" prop="phone">
           <el-input
             v-model="queryParams.phone"
@@ -252,7 +252,7 @@ export default {
     },
     handleExport() {
       this.exportLoading = true
-      this.download('/company/companyVoiceRoboticCallBlacklist/export', { ...this.queryParams }).then(() => {
+      this.download('/admin/voice-blacklist/export', { ...this.queryParams }).then(() => {
         this.exportLoading = false
       }).catch(() => {
         this.exportLoading = false
@@ -274,3 +274,9 @@ export default {
   }
 }
 </script>
+
+<style scoped>
+.mb8 { margin-bottom: 8px; }
+.mb16 { margin-bottom: 16px; }
+.filter-card { padding-bottom: 0; }
+</style>

+ 7 - 1
src/views/admin/voiceOrder/index.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="app-container">
     <el-card shadow="never" class="mb16 filter-card">
-      <el-form :model="queryParams" ref="queryForm" :inline="true" size="small" label-width="80px">
+      <el-form :model="queryParams" ref="queryForm" :inline="true" size="small">
         <el-form-item label="租户名称" prop="companyName">
           <el-input v-model="queryParams.companyName" placeholder="请输入租户名称" clearable @keyup.enter.native="handleQuery" />
         </el-form-item>
@@ -100,3 +100,9 @@ export default {
   }
 }
 </script>
+
+<style scoped>
+.mb8 { margin-bottom: 8px; }
+.mb16 { margin-bottom: 16px; }
+.filter-card { padding-bottom: 0; }
+</style>

+ 12 - 1
src/views/admin/voicePackage/index.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="app-container">
     <el-card shadow="never" class="mb16 filter-card">
-      <el-form :model="queryParams" ref="queryForm" :inline="true" size="small" label-width="80px">
+      <el-form :model="queryParams" ref="queryForm" :inline="true" size="small">
         <el-form-item label="套餐名称" prop="packageName">
           <el-input v-model="queryParams.packageName" placeholder="请输入套餐名称" clearable @keyup.enter.native="handleQuery" />
         </el-form-item>
@@ -12,6 +12,10 @@
       </el-form>
     </el-card>
 
+    <el-row :gutter="10" class="mb8">
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
+    </el-row>
+
     <el-table v-loading="loading" :data="packageList" border size="small" style="width:100%">
       <el-table-column label="套餐ID" align="center" prop="id" width="80" />
       <el-table-column label="套餐名称" align="center" prop="packageName" min-width="140" />
@@ -42,6 +46,7 @@ export default {
   data() {
     return {
       loading: false,
+      showSearch: true,
       total: 0,
       packageList: [],
       queryParams: {
@@ -74,3 +79,9 @@ export default {
   }
 }
 </script>
+
+<style scoped>
+.mb8 { margin-bottom: 8px; }
+.mb16 { margin-bottom: 16px; }
+.filter-card { padding-bottom: 0; }
+</style>

+ 7 - 1
src/views/admin/voiceSeat/index.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="app-container">
     <el-card shadow="never" class="mb16 filter-card">
-      <el-form :model="queryParams" ref="queryForm" :inline="true" size="small" label-width="100px">
+      <el-form :model="queryParams" ref="queryForm" :inline="true" size="small">
         <el-form-item label="租户名称" prop="companyName">
           <el-input
             v-model="queryParams.companyName"
@@ -168,3 +168,9 @@ export default {
   }
 }
 </script>
+
+<style scoped>
+.mb8 { margin-bottom: 8px; }
+.mb16 { margin-bottom: 16px; }
+.filter-card { padding-bottom: 0; }
+</style>

+ 25 - 7
src/views/lobster/sales-corpus/index.vue

@@ -1,8 +1,22 @@
 <template>
   <div class="app-container">
     <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
-      <el-form-item label="租户" prop="tenantId">
-        <tenant-select v-model="queryParams.tenantId" @change="handleQuery" />
+      <el-form-item label="租户名称" prop="companyId">
+        <el-select
+          v-model="queryParams.companyId"
+          placeholder="选择租户"
+          clearable
+          filterable
+          size="small"
+          style="width: 200px"
+        >
+          <el-option
+            v-for="item in companyList"
+            :key="item.companyId"
+            :label="item.companyName"
+            :value="item.companyId"
+          />
+        </el-select>
       </el-form-item>
       <el-form-item label="场景" prop="scenario">
         <el-select v-model="queryParams.scenario" placeholder="请选择场景" clearable size="small">
@@ -54,18 +68,22 @@
 </template>
 <script>
 import { listSalesCorpus, addCorpusDialog, analyzeCorpus, getCorpusScenarios } from '@/api/workflow/lobster'
-import TenantSelect from '@/components/TenantSelect/index'
+import { listAllCompanies } from '@/api/admin/sysCompany'
 export default {
   name: 'SalesCorpus',
-  components: { TenantSelect },
   data() {
-    return { loading: false, showSearch: true, list: [], total: 0, scenarios: [], addVisible: false,
+    return { loading: false, showSearch: true, list: [], total: 0, companyList: [], scenarios: [], addVisible: false,
       addForm: { salespersonName: '', scenario: '', customerQuestion: '', salesAnswer: '' },
       addRules: { customerQuestion: [{ required: true, message: '请输入客户问题', trigger: 'blur' }], salesAnswer: [{ required: true, message: '请输入销冠回答', trigger: 'blur' }] },
-      queryParams: { page: 1, size: 10, scenario: null, status: null, tenantId: null } }
+      queryParams: { page: 1, size: 10, scenario: null, status: null, companyId: null } }
   },
-  created() { this.getList(); this.getScenarios() },
+  created() { this.getCompanyList(); this.getList(); this.getScenarios() },
   methods: {
+    getCompanyList() {
+      listAllCompanies({ pageSize: 9999 }).then(response => {
+        this.companyList = response.rows || []
+      })
+    },
     getList() { this.loading = true; listSalesCorpus(this.$withTenant(this.queryParams)).then(res => { let d = res.data || {}; this.list = d.list || []; this.total = d.total || 0; this.loading = false }).catch(() => { this.loading = false }) },
     getScenarios() { getCorpusScenarios().then(res => { this.scenarios = res.data || [] }) },
     handleQuery() { this.queryParams.page = 1; this.getList() },

+ 28 - 7
src/views/lobster/workflow-generate/index.vue

@@ -20,8 +20,22 @@
     <!-- ===== 筛选 ===== -->
     <el-card shadow="never" class="mb16 filter-card">
       <el-form :inline="true" size="small">
-        <el-form-item label="租户">
-          <el-input v-model="queryParams.tenantId" placeholder="租户ID" clearable size="small" />
+        <el-form-item label="租户名称">
+          <el-select
+            v-model="queryParams.companyId"
+            placeholder="选择租户"
+            clearable
+            filterable
+            size="small"
+            style="width: 200px"
+          >
+            <el-option
+              v-for="item in companyList"
+              :key="item.companyId"
+              :label="item.companyName"
+              :value="item.companyId"
+            />
+          </el-select>
         </el-form-item>
         <el-form-item label="状态">
           <el-select v-model="queryParams.status" placeholder="请选择" clearable size="small">
@@ -41,7 +55,7 @@
     <!-- ===== 实例列表 ===== -->
     <el-table border v-loading="loading" :data="instanceList">
       <el-table-column label="实例ID" prop="instanceId" width="120" align="center" />
-      <el-table-column label="租户ID" prop="tenantId" width="80" align="center" />
+      <el-table-column label="租户ID" prop="companyId" width="80" align="center" />
       <el-table-column label="工作流名称" prop="workflowName" show-overflow-tooltip />
       <el-table-column label="当前节点" prop="currentNode" show-overflow-tooltip />
       <el-table-column label="状态" align="center" width="100">
@@ -63,7 +77,7 @@
     <el-dialog title="实例详情" :visible.sync="detailVisible" width="700px" append-to-body>
       <el-descriptions :column="2" border>
         <el-descriptions-item label="实例ID">{{ detail.instanceId }}</el-descriptions-item>
-        <el-descriptions-item label="租户ID">{{ detail.tenantId }}</el-descriptions-item>
+        <el-descriptions-item label="租户ID">{{ detail.companyId }}</el-descriptions-item>
         <el-descriptions-item label="工作流">{{ detail.workflowName }}</el-descriptions-item>
         <el-descriptions-item label="状态">
           <el-tag :type="statusTag(detail.status)" size="small">{{ statusName(detail.status) }}</el-tag>
@@ -85,6 +99,7 @@
 
 <script>
 import { listAllInstances, getInstanceDetail, getInstanceNodeLogs, getInstanceStats, forceTerminateInstance } from '@/api/admin/lobster'
+import { listAllCompanies } from '@/api/admin/sysCompany'
 
 export default {
   name: 'LobsterInstanceMonitor',
@@ -93,7 +108,8 @@ export default {
       loading: false,
       instanceList: [],
       total: 0,
-      queryParams: { pageNum: 1, pageSize: 10, tenantId: null, status: null },
+      companyList: [],
+      queryParams: { pageNum: 1, pageSize: 10, companyId: null, status: null },
       statCards: [
         { label: '运行中', value: 0, icon: 'el-icon-video-play', bg: '#e6f7ff' },
         { label: '已暂停', value: 0, icon: 'el-icon-video-pause', bg: '#fff7e6' },
@@ -105,8 +121,13 @@ export default {
       nodeLogs: []
     }
   },
-  created() { this.getList(); this.loadStats() },
+  created() { this.getCompanyList(); this.getList(); this.loadStats() },
   methods: {
+    getCompanyList() {
+      listAllCompanies({ pageSize: 9999 }).then(response => {
+        this.companyList = response.rows || []
+      })
+    },
     getList() {
       this.loading = true
       listAllInstances(this.queryParams).then(r => {
@@ -125,7 +146,7 @@ export default {
       }).catch(() => {})
     },
     handleQuery() { this.queryParams.pageNum = 1; this.getList() },
-    resetQuery() { this.queryParams = { pageNum: 1, pageSize: 10, tenantId: null, status: null }; this.getList() },
+    resetQuery() { this.queryParams = { pageNum: 1, pageSize: 10, companyId: null, status: null }; this.getList() },
     handleDetail(row) {
       getInstanceDetail(row.instanceId).then(r => {
         this.detail = r.data || {}

+ 15 - 0
vue.config.js

@@ -49,6 +49,14 @@ module.exports = {
           '^/watch-api': '' // 将 /watch-api 替换为空
         }
       },
+      // 以下路径前缀代理到 fs-admin(8004) - 需要admin token验证的桥接接口
+      [process.env.VUE_APP_BASE_API + '/company/companyOperLog']: {
+        target: 'http://localhost:8004',
+        changeOrigin: true,
+        pathRewrite: {
+          ['^' + process.env.VUE_APP_BASE_API]: ''
+        }
+      },
       // 以下路径前缀代理到 fs-company(8006) - 租户服务端
       // adminui前端API中这些路径只有fs-company有Controller
       [process.env.VUE_APP_BASE_API + '/adv']: {
@@ -121,6 +129,13 @@ module.exports = {
           ['^' + process.env.VUE_APP_BASE_API]: ''
         }
       },
+      [process.env.VUE_APP_BASE_API + '/workflow/lobster']: {
+        target: 'http://localhost:8004',
+        changeOrigin: true,
+        pathRewrite: {
+          ['^' + process.env.VUE_APP_BASE_API]: ''
+        }
+      },
       [process.env.VUE_APP_BASE_API + '/workflow']: {
         target: 'http://localhost:8006',
         changeOrigin: true,