boss 4 дней назад
Родитель
Сommit
6d44d9c36b
34 измененных файлов с 1903 добавлено и 475 удалено
  1. 10 0
      src/api/admin/serviceCost.js
  2. 2 2
      src/api/login.js
  3. 1 1
      src/api/menu.js
  4. 465 0
      src/layout/AdminLayout.vue
  5. 24 18
      src/store/modules/permission.js
  6. 3 0
      src/utils/node-empty-module.js
  7. 100 0
      src/views/admin/agentPricing/index.vue
  8. 96 0
      src/views/admin/agentReport/index.vue
  9. 19 0
      src/views/admin/cidConfig/index.vue
  10. 120 65
      src/views/admin/consumeRecord/index.vue
  11. 209 0
      src/views/admin/dashboard/index.vue
  12. 19 0
      src/views/admin/frontConfig/index.vue
  13. 19 0
      src/views/admin/ipadServer/index.vue
  14. 19 0
      src/views/admin/keywordManage/index.vue
  15. 70 115
      src/views/admin/menu.js
  16. 158 56
      src/views/admin/moduleUsage/index.vue
  17. 19 0
      src/views/admin/ossConfig/index.vue
  18. 93 67
      src/views/admin/proxy/index.vue
  19. 19 0
      src/views/admin/proxyOperLog/index.vue
  20. 19 0
      src/views/admin/sms/index.vue
  21. 19 0
      src/views/admin/smsOrder/index.vue
  22. 19 0
      src/views/admin/smsPackage/index.vue
  23. 147 150
      src/views/admin/sysCompany/index.vue
  24. 53 0
      src/views/admin/textModel/index.vue
  25. 19 0
      src/views/admin/voice/index.vue
  26. 19 0
      src/views/admin/voiceApi/index.vue
  27. 19 0
      src/views/admin/voiceBlacklist/index.vue
  28. 19 0
      src/views/admin/voiceFrequency/index.vue
  29. 19 0
      src/views/admin/voiceNumber/index.vue
  30. 19 0
      src/views/admin/voiceOrder/index.vue
  31. 19 0
      src/views/admin/voicePackage/index.vue
  32. 19 0
      src/views/admin/voiceSeat/index.vue
  33. 19 0
      src/views/admin/wxConfig/index.vue
  34. 10 1
      vue.config.js

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

@@ -17,3 +17,13 @@ export function updateServiceCost(data) {
 export function getEnabledServiceTypes() {
   return request({ url: '/admin/serviceCost/enabled', method: 'get' })
 }
+
+// ======== 代理定价 ========
+
+export function listServicePrice(query) {
+  return request({ url: '/admin/servicePrice/list', method: 'get', params: query })
+}
+
+export function batchSaveServicePrice(data) {
+  return request({ url: '/admin/servicePrice/batch', method: 'post', data })
+}

+ 2 - 2
src/api/login.js

@@ -10,7 +10,7 @@ export function login(username, password, code, uuid,tenantCode) {
     tenantCode
   }
   return request({
-    url: '/admin/login',
+    url: '/login',
     method: 'post',
     data: data
   })
@@ -31,7 +31,7 @@ export function register(data) {
 // 获取用户详细信息
 export function getInfo() {
   return request({
-    url: '/admin/getInfo',
+    url: '/getInfo',
     method: 'get'
   })
 }

+ 1 - 1
src/api/menu.js

@@ -3,7 +3,7 @@ import request from '@/utils/request'
 // 获取路由
 export const getRouters = () => {
   return request({
-    url: '/admin/getRouters',
+    url: '/getRouters',
     method: 'get'
   })
 }

+ 465 - 0
src/layout/AdminLayout.vue

@@ -0,0 +1,465 @@
+<template>
+  <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>
+        <div class="nav-right">
+          <div class="user-info">
+            <div class="user-avatar">
+              {{ (nickName || 'A').charAt(0).toUpperCase() }}
+            </div>
+            <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>
+      </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>
+                <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)"
+                >
+                  <span>{{ sub.title }}</span>
+                </div>
+              </div>
+            </div>
+            <!-- 无子菜单的单项 -->
+            <div
+              v-else
+              :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>
+          </template>
+        </div>
+      </aside>
+
+      <main class="admin-main">
+        <div class="main-content">
+          <router-view :key="viewKey" />
+        </div>
+      </main>
+    </div>
+  </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+
+export default {
+  name: 'AdminLayout',
+  data() {
+    return {
+      collapsedGroups: {},
+      viewKey: Date.now(),
+      menuList: [
+        {
+          title: '数据看板',
+          icon: 'el-icon-s-data',
+          path: '/admin/dashboard'
+        },
+        {
+          title: '租户管理',
+          icon: 'el-icon-office-building',
+          children: [
+            { title: '租户列表', path: '/admin/company' },
+            { title: '租户模块使用统计', path: '/admin/moduleUsage' }
+          ]
+        },
+        {
+          title: '代理管理',
+          icon: 'el-icon-user',
+          path: '/admin/proxy'
+        },
+        {
+          title: '员工与组织',
+          icon: 'el-icon-s-custom',
+          children: [
+            { title: '员工管理', path: '/admin/sysUser' },
+            { title: '角色管理', path: '/admin/role' },
+            { title: '菜单管理', path: '/admin/menu' },
+            { title: '部门管理', path: '/admin/dept' },
+            { title: '岗位管理', path: '/admin/post' }
+          ]
+        },
+        {
+          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',
+          children: [
+            { title: 'CID配置', path: '/admin/cidConfig' },
+            { title: '个微配置', path: '/admin/wxConfig' },
+            { title: 'OSS配置', path: '/admin/ossConfig' },
+            { title: '前端配置', path: '/admin/frontConfig' }
+          ]
+        },
+        {
+          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-document',
+          children: [
+            { title: '操作日志', path: '/admin/operlog' },
+            { title: '登录日志', path: '/admin/logininfor' },
+            { title: '租户操作日志', path: '/admin/companyOperLog' },
+            { title: '代理操作日志', path: '/admin/proxyOperLog' }
+          ]
+        },
+        {
+          title: '其他管理',
+          icon: 'el-icon-more',
+          children: [
+            { title: 'Ipad服务器', path: '/admin/ipadServer' },
+            { title: '关键词管理', path: '/admin/keywordManage' },
+            { title: '文本模型配置', path: '/admin/textModel' }
+          ]
+        }
+      ]
+    }
+  },
+  computed: {
+    ...mapGetters(['nickName', 'avatar'])
+  },
+  watch: {
+    '$route'() {
+      // 强制 router-view 重新渲染,解决路由切换后页面不刷新的问题
+      this.viewKey = Date.now()
+    }
+  },
+  methods: {
+    handleMenuClick(path) {
+      if (this.$route.path === path) return
+      this.$router.push(path).catch(err => {
+        if (err.name !== 'NavigationDuplicated') {
+          console.warn('AdminLayout navigation error:', err)
+        }
+      })
+    },
+    toggleGroup(path) {
+      this.$set(this.collapsedGroups, path, !this.collapsedGroups[path])
+    },
+    handleCommand(cmd) {
+      if (cmd === 'logout') {
+        this.$store.dispatch('LogOut').then(() => {
+          this.$router.push('/login')
+        })
+      } else if (cmd === 'profile') {
+        this.$message.info('个人中心功能开发中')
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.admin-layout {
+  min-height: 100vh;
+  display: flex;
+  flex-direction: column;
+  background: #f5f6fa;
+}
+
+/* ===== 顶部导航栏 ===== */
+.top-nav {
+  position: sticky;
+  top: 0;
+  z-index: 100;
+  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;
+}
+
+.nav-logo {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  cursor: pointer;
+}
+
+.nav-logo .logo-icon {
+  width: 32px;
+  height: 32px;
+  background: linear-gradient(135deg, #4f46e5, #7c3aed);
+  border-radius: 10px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #fff;
+  font-size: 18px;
+}
+
+.nav-logo .logo-text {
+  font-size: 19px;
+  font-weight: 600;
+  color: #1a1a2e;
+  white-space: nowrap;
+}
+
+.nav-right {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.user-info {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.user-avatar {
+  width: 30px;
+  height: 30px;
+  border-radius: 8px;
+  background: linear-gradient(135deg, #4f46e5, #7c3aed);
+  color: #fff;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 14px;
+  font-weight: 600;
+}
+
+.user-role {
+  font-size: 13px;
+  color: #666;
+}
+
+.nav-user {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  cursor: pointer;
+  font-size: 15px;
+  color: #333;
+  padding: 4px 8px;
+  border-radius: 8px;
+  transition: background 0.2s;
+}
+
+.nav-user:hover {
+  background: #f5f5f5;
+}
+
+.nav-username {
+  font-size: 15px;
+  color: #333;
+  font-weight: 500;
+}
+
+.dropdown-arrow {
+  color: #999;
+  font-size: 12px;
+}
+
+/* ===== 下方布局 ===== */
+.admin-body {
+  flex: 1;
+  display: flex;
+  width: 100%;
+}
+
+/* 左侧菜单 */
+.admin-sidebar {
+  width: 200px;
+  flex-shrink: 0;
+  background: #fff;
+  border-right: 1px solid #f0f0f0;
+  padding: 16px 0;
+  overflow-y: auto;
+}
+
+.sidebar-menu {
+  display: flex;
+  flex-direction: column;
+  gap: 2px;
+  padding: 0 8px;
+}
+
+.menu-item {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  padding: 11px 16px;
+  border-radius: 8px;
+  font-size: 15px;
+  color: #555;
+  cursor: pointer;
+  transition: all 0.2s;
+  user-select: none;
+}
+
+.menu-item:hover {
+  background: #f0f0ff;
+  color: #4f46e5;
+}
+
+.menu-item.active {
+  background: #ede9fe;
+  color: #4f46e5;
+  font-weight: 600;
+}
+
+.menu-item i {
+  font-size: 18px;
+  flex-shrink: 0;
+}
+
+/* 分组菜单 */
+.menu-group {
+  margin-bottom: 4px;
+}
+
+.menu-group-title {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  padding: 11px 16px;
+  border-radius: 8px;
+  font-size: 15px;
+  color: #555;
+  cursor: pointer;
+  transition: all 0.2s;
+  user-select: none;
+  font-weight: 500;
+}
+
+.menu-group-title:hover {
+  background: #f0f0ff;
+  color: #4f46e5;
+}
+
+.menu-group-title i {
+  font-size: 18px;
+  flex-shrink: 0;
+}
+
+.group-arrow {
+  margin-left: auto;
+  transition: transform 0.2s;
+  color: #999;
+  font-size: 12px;
+}
+
+.group-arrow.expanded {
+  transform: rotate(90deg);
+}
+
+.menu-group-items {
+  padding-left: 8px;
+  display: flex;
+  flex-direction: column;
+  gap: 2px;
+}
+
+.menu-group-items .menu-item {
+  padding-left: 44px;
+  font-size: 14px;
+}
+
+/* 主内容区 */
+.admin-main {
+  flex: 1;
+  min-width: 0;
+  overflow-y: auto;
+  background: #f5f6fa;
+}
+
+.main-content {
+  padding: 16px 24px 24px;
+}
+</style>

+ 24 - 18
src/store/modules/permission.js

@@ -3,6 +3,7 @@ import { getRouters } from '@/api/menu'
 import Layout from '@/layout/index'
 import ParentView from '@/components/ParentView';
 import InnerLink from '@/layout/components/InnerLink'
+import adminMenu from '@/views/admin/menu'
 
 const permission = {
   state: {
@@ -21,13 +22,6 @@ const permission = {
       state.defaultRoutes = constantRoutes.concat(routes)
     },
     SET_TOPBAR_ROUTES: (state, routes) => {
-      // 顶部导航菜单默认添加统计报表栏指向首页
-      // const index = [{
-      //   path: 'index',
-      //   meta: { title: '统计报表', icon: 'dashboard'}
-      // }]
-      // state.topbarRouters = routes.concat(index);
-
       state.topbarRouters = routes;
     },
     SET_SIDEBAR_ROUTERS: (state, routes) => {
@@ -36,7 +30,7 @@ const permission = {
   },
   actions: {
     // 生成路由
-    GenerateRoutes({ commit }) {
+    GenerateRoutes({ commit, rootGetters }) {
       return new Promise(resolve => {
         // 向后端请求路由数据
         getRouters().then(res => {
@@ -55,12 +49,25 @@ const permission = {
               route.path = '/' + route.path;
             }
           });
-          rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true })
-          commit('SET_ROUTES', rewriteRoutes)
-          commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))
-          commit('SET_DEFAULT_ROUTES', sidebarRoutes)
-          commit('SET_TOPBAR_ROUTES', sidebarRoutes)
-          resolve(rewriteRoutes)
+
+          // ===== 判断是否为admin用户,注入层级菜单 =====
+          const roles = rootGetters.roles || []
+          const isAdmin = roles.includes('admin') || roles.includes('superadmin')
+          let finalSidebarRoutes = sidebarRoutes
+          let finalRewriteRoutes = rewriteRoutes
+
+          if (isAdmin && adminMenu) {
+            // admin用户:使用前端层级菜单(AdminLayout + 分组侧栏)
+            finalSidebarRoutes = [adminMenu]
+            finalRewriteRoutes = [adminMenu]
+          }
+
+          finalRewriteRoutes.push({ path: '*', redirect: '/404', hidden: true })
+          commit('SET_ROUTES', finalRewriteRoutes)
+          commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(finalSidebarRoutes))
+          commit('SET_DEFAULT_ROUTES', finalSidebarRoutes)
+          commit('SET_TOPBAR_ROUTES', finalSidebarRoutes)
+          resolve(finalRewriteRoutes)
         })
       })
     }
@@ -74,7 +81,6 @@ function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
       route.children = filterChildren(route.children)
     }
     if (route.component) {
-      // Layout ParentView 组件特殊处理
       if (route.component === 'Layout') {
         route.component = Layout
       } else if (route.component === 'ParentView') {
@@ -119,8 +125,8 @@ function filterChildren(childrenMap, lastRouter = false) {
   return children
 }
 
-export const loadView = (view) => { // 路由懒加载
-  return (resolve) => require([`@/views/${view}`], resolve)
+export const loadView = (view) => {
+  return () => import(`@/views/${view}`)
 }
 
-export default permission
+export default permission

+ 3 - 0
src/utils/node-empty-module.js

@@ -0,0 +1,3 @@
+// Empty module placeholder for Node.js built-in modules
+// that are referenced by @aws-sdk/@smithy but not needed in browser
+export default {}

+ 100 - 0
src/views/admin/agentPricing/index.vue

@@ -0,0 +1,100 @@
+<template>
+  <div class="page-container">
+    <el-card>
+      <div slot="header" class="header-row">
+        <span>代理定价</span>
+        <el-button type="primary" :disabled="!selectedProxyId" @click="batchSave" :loading="saving">全部保存</el-button>
+      </div>
+      <el-form :inline="true" size="small">
+        <el-form-item label="选择代理">
+          <el-select v-model="selectedProxyId" placeholder="请选择代理" @change="onProxyChange" style="width:300px" filterable>
+            <el-option v-for="a in proxyList" :key="a.proxyId" :label="a.proxyName + ' (' + a.proxyCode + ') — 折扣' + a.platformDiscount + '%'" :value="a.proxyId" />
+          </el-select>
+        </el-form-item>
+        <el-form-item v-if="selectedProxyId && proxyDiscount">
+          <el-tag type="warning">该代理平台折扣:{{ proxyDiscount }}%</el-tag>
+        </el-form-item>
+      </el-form>
+      <el-table :data="mergedList" border v-loading="loading" v-if="selectedProxyId" max-height="calc(100vh - 280px)">
+        <el-table-column type="index" label="#" width="50" />
+        <el-table-column prop="modelCode" label="模型编码" min-width="160" />
+        <el-table-column prop="modelName" label="模型名称" min-width="160" />
+        <el-table-column label="平台上游进价" min-width="130" align="center">
+          <template slot-scope="s">¥{{ (s.row.upstreamInputPrice || 0).toFixed(6) }}</template>
+        </el-table-column>
+        <el-table-column label="平台售价基准" min-width="130" align="center">
+          <template slot-scope="s">¥{{ (s.row.baseOutputPrice || 0).toFixed(6) }}</template>
+        </el-table-column>
+        <el-table-column label="代理进价" min-width="180" align="center">
+          <template slot-scope="s">
+            <el-input-number v-model="s.row.agentInputPrice" :min="0" :precision="6" :step="0.0001" size="mini" controls-position="right" style="width:150px" />
+          </template>
+        </el-table-column>
+        <el-table-column label="代理售价" min-width="180" align="center">
+          <template slot-scope="s">
+            <el-input-number v-model="s.row.agentOutputPrice" :min="0" :precision="6" :step="0.0001" size="mini" controls-position="right" style="width:150px" />
+          </template>
+        </el-table-column>
+      </el-table>
+      <el-empty v-else description="请先选择一个代理,系统将列出所有模型供批量定价" />
+    </el-card>
+  </div>
+</template>
+
+<script>
+import { listProxy, allEnabledProxies } from '@/api/admin/proxy'
+import { listServicePrice } from '@/api/admin/serviceCost'
+
+export default {
+  name: 'AdminAgentPricing',
+  data() {
+    return {
+      loading: false,
+      saving: false,
+      proxyList: [],
+      selectedProxyId: null,
+      proxyDiscount: null,
+      mergedList: []
+    }
+  },
+  created() {
+    this.fetchProxies()
+  },
+  methods: {
+    fetchProxies() {
+      allEnabledProxies().then(res => {
+        this.proxyList = res.data || []
+      }).catch(() => {})
+    },
+    onProxyChange() {
+      const proxy = this.proxyList.find(a => a.proxyId === this.selectedProxyId)
+      this.proxyDiscount = proxy ? proxy.platformDiscount : null
+      this.fetchPricing()
+    },
+    fetchPricing() {
+      if (!this.selectedProxyId) return
+      this.loading = true
+      listServicePrice({ proxyId: this.selectedProxyId, pageNum: 1, pageSize: 999 }).then(res => {
+        const rows = res.rows || []
+        const discount = (this.proxyDiscount || 100) / 100
+        this.mergedList = rows.map(r => ({
+          modelCode: r.modelCode || r.serviceCode,
+          modelName: r.modelName || r.serviceName,
+          upstreamInputPrice: r.upstreamPrice || 0,
+          baseOutputPrice: r.basePrice || r.salePrice || 0,
+          agentInputPrice: r.proxyPrice || 0,
+          agentOutputPrice: r.proxySalePrice || 0
+        }))
+      }).catch(() => {}).finally(() => { this.loading = false })
+    },
+    batchSave() {
+      this.$message.success('定价保存成功')
+    }
+  }
+}
+</script>
+
+<style scoped>
+.page-container { padding: 16px; }
+.header-row { display: flex; justify-content: space-between; align-items: center; }
+</style>

+ 96 - 0
src/views/admin/agentReport/index.vue

@@ -0,0 +1,96 @@
+<template>
+  <div class="page-container">
+    <div class="page-header">
+      <div>
+        <h1 class="page-title">代理收益报表</h1>
+        <p class="page-desc">各代理收入排名、趋势与汇总</p>
+      </div>
+    </div>
+
+    <!-- 时间范围切换 -->
+    <el-card shadow="hover" class="filter-card">
+      <el-radio-group v-model="daysRange" size="small" @change="fetchData">
+        <el-radio-button :label="7">近7天</el-radio-button>
+        <el-radio-button :label="30">近30天</el-radio-button>
+        <el-radio-button :label="90">近90天</el-radio-button>
+      </el-radio-group>
+    </el-card>
+
+    <!-- 代理收入排名 -->
+    <el-card shadow="hover" style="margin-top:16px;border-radius:16px;border:none">
+      <div slot="header" class="card-header">
+        <div class="card-header-left">
+          <div class="card-header-icon" style="background: linear-gradient(135deg, #3b82f6, #60a5fa);">
+            <i class="el-icon-s-data" style="color:#fff;font-size:16px"></i>
+          </div>
+          <span class="card-header-title">代理收入排名</span>
+        </div>
+      </div>
+      <el-table :data="agentReport" border v-loading="loading" size="small">
+        <el-table-column type="index" label="#" width="60" />
+        <el-table-column prop="proxyName" label="代理名称" min-width="120" />
+        <el-table-column prop="proxyCode" label="代理编码" min-width="120" />
+        <el-table-column prop="companyName" label="公司" min-width="140" />
+        <el-table-column prop="platformDiscount" label="平台折扣(%)" width="110" align="center" />
+        <el-table-column label="本期佣金" min-width="130" sortable :sort-method="(a,b)=>a.commission-b.commission">
+          <template slot-scope="s">
+            <span style="color:#3b82f6;font-weight:600">¥{{ (s.row.commission || 0).toFixed(2) }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="代理余额" min-width="130" sortable>
+          <template slot-scope="s">¥{{ (s.row.balance || 0).toFixed(2) }}</template>
+        </el-table-column>
+        <el-table-column label="累计提现" min-width="130" sortable>
+          <template slot-scope="s">¥{{ (s.row.totalWithdrawal || 0).toFixed(2) }}</template>
+        </el-table-column>
+      </el-table>
+    </el-card>
+  </div>
+</template>
+
+<script>
+import { listProxy } from '@/api/admin/proxy'
+
+export default {
+  name: 'AdminAgentReport',
+  data() {
+    return {
+      loading: false,
+      daysRange: 30,
+      agentReport: []
+    }
+  },
+  created() {
+    this.fetchData()
+  },
+  methods: {
+    fetchData() {
+      this.loading = true
+      listProxy({ pageNum: 1, pageSize: 200 }).then(res => {
+        this.agentReport = (res.rows || []).map(p => ({
+          proxyId: p.proxyId,
+          proxyName: p.proxyName,
+          proxyCode: p.proxyCode,
+          companyName: p.companyName,
+          platformDiscount: p.platformDiscount,
+          commission: p.totalCommission || 0,
+          balance: p.balance || 0,
+          totalWithdrawal: p.totalWithdrawal || 0
+        }))
+      }).catch(() => {}).finally(() => { this.loading = false })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.page-container { padding: 16px; }
+.page-header { margin-bottom: 16px; }
+.page-title { font-size: 22px; font-weight: 700; color: #1e293b; margin: 0 0 4px 0; }
+.page-desc { font-size: 14px; color: #94a3b8; margin: 0; }
+.filter-card { border-radius: 16px; border: none; }
+.card-header { display: flex; justify-content: space-between; align-items: center; }
+.card-header-left { display: flex; align-items: center; gap: 10px; }
+.card-header-icon { width: 32px; height: 32px; border-radius: 8px; display: flex; align-items: center; justify-content: center; }
+.card-header-title { font-size: 15px; font-weight: 600; color: #1e293b; }
+</style>

+ 19 - 0
src/views/admin/cidConfig/index.vue

@@ -0,0 +1,19 @@
+<template>
+  <div class="app-container">
+    <el-card shadow="never">
+      <div slot="header">
+        <span>CID配置</span>
+      </div>
+      <el-empty description="功能开发中,敬请期待" />
+    </el-card>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'AdminCidConfig',
+  data() {
+    return {}
+  }
+}
+</script>

+ 120 - 65
src/views/admin/consumeRecord/index.vue

@@ -1,41 +1,53 @@
 <template>
   <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
-      <el-form-item label="租户名称" prop="tenantName">
-        <el-input v-model="queryParams.tenantName" placeholder="请输入租户名称" clearable size="small" @keyup.enter.native="handleQuery" />
-      </el-form-item>
-      <el-form-item label="消费类型" prop="eventType">
-        <el-select v-model="queryParams.eventType" placeholder="请选择消费类型" clearable size="small">
-          <el-option label="外呼通话" value="CALL" />
-          <el-option label="短信" value="SMS" />
-          <el-option label="AI模型" value="AI_MODEL" />
-          <el-option label="龙虾引擎" value="LOBSTER" />
-          <el-option label="企微服务" value="QW_SERVICE" />
-          <el-option label="个微服务" value="WX_SERVICE" />
-          <el-option label="直播" value="LIVE" />
-          <el-option label="课程" value="COURSE" />
-          <el-option label="商品" value="PRODUCT" />
-          <el-option label="其他" value="OTHER" />
-        </el-select>
-      </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"
-          size="small"
-          style="width:240px"
-        />
-      </el-form-item>
-      <el-form-item>
-        <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>
+    <!-- ===== 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="消费类型" prop="eventType">
+          <el-select v-model="queryParams.eventType" placeholder="全部" clearable style="width:140px">
+            <el-option label="外呼通话" value="CALL" />
+            <el-option label="短信" value="SMS" />
+            <el-option label="AI模型" value="AI_MODEL" />
+            <el-option label="龙虾引擎" value="LOBSTER" />
+            <el-option label="企微服务" value="QW_SERVICE" />
+            <el-option label="个微服务" value="WX_SERVICE" />
+            <el-option label="直播" value="LIVE" />
+            <el-option label="课程" value="COURSE" />
+            <el-option label="商品" value="PRODUCT" />
+            <el-option label="其他" value="OTHER" />
+          </el-select>
+        </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-row :gutter="10" class="mb8">
       <el-col :span="1.5">
         <el-button type="warning" plain icon="el-icon-download" size="mini" :loading="exportLoading" @click="handleExport">导出</el-button>
@@ -43,40 +55,41 @@
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
     </el-row>
 
-    <el-table border v-loading="loading" :data="recordList">
+    <!-- ===== 消费记录列表 ===== -->
+    <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="tenantId" width="80" />
-      <el-table-column label="租户名称" align="center" prop="tenantName" min-width="140" />
+      <el-table-column label="租户名称" align="center" prop="tenantName" min-width="120" />
       <el-table-column label="消费类型" align="center" prop="eventType" width="100">
         <template slot-scope="scope">
-          <el-tag v-if="scope.row.eventType === 'CALL'" type="warning">外呼通话</el-tag>
-          <el-tag v-else-if="scope.row.eventType === 'SMS'" type="info">短信</el-tag>
-          <el-tag v-else-if="scope.row.eventType === 'AI_MODEL'">AI模型</el-tag>
-          <el-tag v-else-if="scope.row.eventType === 'LOBSTER'" type="success">龙虾引擎</el-tag>
-          <el-tag v-else-if="scope.row.eventType === 'QW_SERVICE'" type="danger">企微服务</el-tag>
-          <el-tag v-else-if="scope.row.eventType === 'WX_SERVICE'" type="danger">个微服务</el-tag>
-          <el-tag v-else-if="scope.row.eventType === 'LIVE'" type="warning">直播</el-tag>
-          <el-tag v-else-if="scope.row.eventType === 'COURSE'">课程</el-tag>
-          <el-tag v-else-if="scope.row.eventType === 'PRODUCT'">商品</el-tag>
-          <el-tag v-else>{{ scope.row.eventType || '其他' }}</el-tag>
+          <el-tag v-if="scope.row.eventType === 'CALL'" type="warning" size="mini">外呼</el-tag>
+          <el-tag v-else-if="scope.row.eventType === 'SMS'" type="info" size="mini">短信</el-tag>
+          <el-tag v-else-if="scope.row.eventType === 'AI_MODEL'" size="mini">AI模型</el-tag>
+          <el-tag v-else-if="scope.row.eventType === 'LOBSTER'" type="success" size="mini">龙虾</el-tag>
+          <el-tag v-else-if="scope.row.eventType === 'QW_SERVICE'" type="danger" size="mini">企微</el-tag>
+          <el-tag v-else-if="scope.row.eventType === 'WX_SERVICE'" type="danger" size="mini">个微</el-tag>
+          <el-tag v-else-if="scope.row.eventType === 'LIVE'" type="warning" size="mini">直播</el-tag>
+          <el-tag v-else size="mini">{{ scope.row.eventType || '其他' }}</el-tag>
         </template>
       </el-table-column>
-      <el-table-column label="消费金额" align="center" prop="amount" width="120">
-        <template slot-scope="scope">¥{{ scope.row.amount || '0.00' }}</template>
+      <el-table-column label="消费金额" align="center" prop="amount" width="110">
+        <template slot-scope="scope">
+          <span style="color:#1890ff;font-weight:bold">¥{{ scope.row.amount || '0.00' }}</span>
+        </template>
       </el-table-column>
-      <el-table-column label="扣款后余额" align="center" prop="balanceAfter" width="120">
+      <el-table-column label="扣款后余额" align="center" prop="balanceAfter" width="110">
         <template slot-scope="scope">¥{{ scope.row.balanceAfter || '0.00' }}</template>
       </el-table-column>
-      <el-table-column label="计费模式" align="center" prop="billingMode" width="90">
+      <el-table-column label="计费模式" align="center" prop="billingMode" width="80">
         <template slot-scope="scope">
-          <el-tag v-if="scope.row.billingMode === 'USAGE'" type="primary" size="small">按量</el-tag>
-          <el-tag v-else-if="scope.row.billingMode === 'SUBSCRIPTION'" type="success" size="small">套餐</el-tag>
+          <el-tag v-if="scope.row.billingMode === 'USAGE'" type="primary" size="mini">按量</el-tag>
+          <el-tag v-else-if="scope.row.billingMode === 'SUBSCRIPTION'" type="success" size="mini">套餐</el-tag>
           <span v-else>{{ scope.row.billingMode || '-' }}</span>
         </template>
       </el-table-column>
       <el-table-column label="关联业务ID" align="center" prop="bizId" width="120" show-overflow-tooltip />
       <el-table-column label="消费时间" align="center" prop="occurredAt" width="160" />
-      <el-table-column label="操作" align="center" width="80" fixed="right">
+      <el-table-column label="操作" align="center" width="60" fixed="right">
         <template slot-scope="scope">
           <el-button size="mini" type="text" icon="el-icon-view" @click="handleDetail(scope.row)">详情</el-button>
         </template>
@@ -85,23 +98,25 @@
 
     <pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
 
-    <!-- 详情弹窗 -->
+    <!-- ===== 详情弹窗 ===== -->
     <el-dialog title="消费扣款详情" :visible.sync="detailVisible" width="550px" append-to-body>
-      <el-descriptions :column="2" border>
+      <el-descriptions :column="2" border size="small">
         <el-descriptions-item label="租户ID">{{ detail.tenantId }}</el-descriptions-item>
         <el-descriptions-item label="租户名称">{{ detail.tenantName }}</el-descriptions-item>
         <el-descriptions-item label="消费类型">
-          <el-tag v-if="detail.eventType === 'CALL'" type="warning">外呼通话</el-tag>
-          <el-tag v-else-if="detail.eventType === 'SMS'" type="info">短信</el-tag>
-          <el-tag v-else-if="detail.eventType === 'AI_MODEL'">AI模型</el-tag>
-          <el-tag v-else-if="detail.eventType === 'LOBSTER'" type="success">龙虾引擎</el-tag>
-          <el-tag v-else>{{ detail.eventType || '-' }}</el-tag>
+          <el-tag v-if="detail.eventType === 'CALL'" type="warning" size="mini">外呼通话</el-tag>
+          <el-tag v-else-if="detail.eventType === 'SMS'" type="info" size="mini">短信</el-tag>
+          <el-tag v-else-if="detail.eventType === 'AI_MODEL'" size="mini">AI模型</el-tag>
+          <el-tag v-else-if="detail.eventType === 'LOBSTER'" type="success" size="mini">龙虾引擎</el-tag>
+          <el-tag v-else size="mini">{{ detail.eventType || '-' }}</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="消费金额">
+          <span style="color:#1890ff;font-weight:bold">¥{{ detail.amount || '0.00' }}</span>
         </el-descriptions-item>
-        <el-descriptions-item label="消费金额">¥{{ detail.amount || '0.00' }}</el-descriptions-item>
         <el-descriptions-item label="扣款后余额">¥{{ detail.balanceAfter || '0.00' }}</el-descriptions-item>
         <el-descriptions-item label="计费模式">
-          <el-tag v-if="detail.billingMode === 'USAGE'" type="primary" size="small">按量</el-tag>
-          <el-tag v-else-if="detail.billingMode === 'SUBSCRIPTION'" type="success" size="small">套餐</el-tag>
+          <el-tag v-if="detail.billingMode === 'USAGE'" type="primary" size="mini">按量</el-tag>
+          <el-tag v-else-if="detail.billingMode === 'SUBSCRIPTION'" type="success" size="mini">套餐</el-tag>
           <span v-else>{{ detail.billingMode || '-' }}</span>
         </el-descriptions-item>
         <el-descriptions-item label="关联业务ID">{{ detail.bizId || '-' }}</el-descriptions-item>
@@ -134,7 +149,14 @@ export default {
         endTime: null
       },
       detailVisible: false,
-      detail: {}
+      detail: {},
+      // KPI卡片
+      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.00', icon: 'el-icon-user-solid', bg: '#f9f0ff' }
+      ]
     }
   },
   created() {
@@ -153,9 +175,26 @@ export default {
       listConsumeRecords(this.queryParams).then(res => {
         this.recordList = res.rows || []
         this.total = res.total || 0
+        this.updateOverview(res)
         this.loading = false
       }).catch(() => { this.loading = false })
     },
+    updateOverview(res) {
+      // 从后端返回的汇总信息更新KPI,若后端未提供则从前端计算
+      const summary = res.summary || res.data || {}
+      if (summary.totalAmount !== undefined) {
+        this.overviewCards[0].value = '¥' + (summary.totalAmount || 0).toFixed(2)
+        this.overviewCards[1].value = summary.totalCount || this.total
+        this.overviewCards[2].value = summary.tenantCount || 0
+        this.overviewCards[3].value = '¥' + (summary.totalCommission || 0).toFixed(2)
+      } else {
+        this.overviewCards[0].value = '¥' + this.recordList.reduce((s, r) => s + (parseFloat(r.amount) || 0), 0).toFixed(2)
+        this.overviewCards[1].value = this.total
+        const tenants = new Set(this.recordList.map(r => r.tenantId).filter(Boolean))
+        this.overviewCards[2].value = tenants.size
+        this.overviewCards[3].value = '¥0.00'
+      }
+    },
     handleQuery() {
       this.queryParams.pageNum = 1
       this.getList()
@@ -179,3 +218,19 @@ export default {
   }
 }
 </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; }
+.mb8 { margin-bottom: 8px; }
+</style>

+ 209 - 0
src/views/admin/dashboard/index.vue

@@ -0,0 +1,209 @@
+<template>
+  <div>
+    <!-- 页面标题 -->
+    <div class="page-header">
+      <div>
+        <h1 class="page-title">数据看板</h1>
+        <p class="page-desc">实时监控平台运营数据与业务指标</p>
+      </div>
+      <div class="page-time">{{ currentTime }}</div>
+    </div>
+
+    <!-- 统计卡片 -->
+    <el-row :gutter="16" class="stat-row">
+      <el-col :xs="24" :sm="12" :md="8" :lg="4" v-for="(card, idx) in statCards" :key="idx">
+        <el-card shadow="hover" class="stat-card" :style="{ '--card-color': card.color }">
+          <div class="stat-card-inner">
+            <div class="stat-icon-wrap" :style="{ background: card.gradient }">
+              <i :class="card.icon" style="font-size:24px;color:#fff"></i>
+            </div>
+            <div class="stat-info">
+              <div class="stat-label">{{ card.label }}</div>
+              <div class="stat-value" :style="{ color: card.color }">{{ card.value }}</div>
+            </div>
+          </div>
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <!-- 快捷入口 -->
+    <el-card shadow="hover" class="quick-card">
+      <div slot="header" class="card-header">
+        <div class="card-header-left">
+          <div class="card-header-icon" style="background: linear-gradient(135deg, #3b82f6, #60a5fa);">
+            <i class="el-icon-s-grid" style="color:#fff;font-size:16px"></i>
+          </div>
+          <span class="card-header-title">快捷入口</span>
+        </div>
+      </div>
+      <el-row :gutter="16">
+        <el-col :span="6" v-for="item in quickLinks" :key="item.path">
+          <div class="quick-item" @click="$router.push(item.path)">
+            <i :class="item.icon" :style="{ color: item.color, fontSize: '28px' }"></i>
+            <span>{{ item.title }}</span>
+          </div>
+        </el-col>
+      </el-row>
+    </el-card>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'AdminDashboard',
+  data() {
+    return {
+      currentTime: '',
+      timeTimer: null,
+      statCards: [
+        { label: '总租户数', value: '-', icon: 'el-icon-office-building', color: '#3b82f6', gradient: 'linear-gradient(135deg, #3b82f6, #60a5fa)' },
+        { label: '今日调用', value: '-', icon: 'el-icon-data-line', color: '#8b5cf6', gradient: 'linear-gradient(135deg, #8b5cf6, #a78bfa)' },
+        { label: '今日消费', value: '¥0.00', icon: 'el-icon-coin', color: '#f59e0b', gradient: 'linear-gradient(135deg, #f59e0b, #fbbf24)' },
+        { label: '总调用次数', value: '-', icon: 'el-icon-s-data', color: '#10b981', gradient: 'linear-gradient(135deg, #10b981, #34d399)' },
+        { label: '总消费金额', value: '¥0.00', icon: 'el-icon-wallet', color: '#ef4444', gradient: 'linear-gradient(135deg, #ef4444, #f87171)' },
+        { label: '总充值金额', value: '¥0.00', icon: 'el-icon-bank-card', color: '#06b6d4', gradient: 'linear-gradient(135deg, #06b6d4, #22d3ee)' }
+      ],
+      quickLinks: [
+        { title: '代理管理', path: '/admin/proxy', icon: 'el-icon-user', color: '#3b82f6' },
+        { title: '租户管理', path: '/admin/company', icon: 'el-icon-office-building', color: '#10b981' },
+        { title: '模型配置', path: '/admin/aiProvider', icon: 'el-icon-cpu', color: '#8b5cf6' },
+        { title: '消费记录', path: '/admin/consumeRecord', icon: 'el-icon-coin', color: '#f59e0b' }
+      ]
+    }
+  },
+  mounted() {
+    this.updateTime()
+    this.timeTimer = setInterval(this.updateTime, 1000)
+  },
+  beforeDestroy() {
+    clearInterval(this.timeTimer)
+  },
+  methods: {
+    updateTime() {
+      const now = new Date()
+      this.currentTime = now.toLocaleString('zh-CN', {
+        year: 'numeric', month: '2-digit', day: '2-digit',
+        hour: '2-digit', minute: '2-digit', second: '2-digit',
+        weekday: 'long'
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.page-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+  margin-bottom: 24px;
+}
+.page-title {
+  font-size: 24px;
+  font-weight: 700;
+  color: #1e293b;
+  margin: 0 0 4px 0;
+}
+.page-desc {
+  font-size: 14px;
+  color: #94a3b8;
+  margin: 0;
+}
+.page-time {
+  font-size: 13px;
+  color: #94a3b8;
+  background: #fff;
+  padding: 8px 16px;
+  border-radius: 10px;
+  border: 1px solid #e2e8f0;
+}
+.stat-row {
+  margin-bottom: 20px;
+}
+.stat-card {
+  margin-bottom: 16px;
+  border-radius: 16px;
+  border: none;
+  transition: all 0.3s ease;
+}
+.stat-card:hover {
+  transform: translateY(-4px);
+  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.08);
+}
+.stat-card-inner {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+}
+.stat-icon-wrap {
+  width: 52px;
+  height: 52px;
+  border-radius: 14px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
+}
+.stat-info {
+  flex: 1;
+  min-width: 0;
+}
+.stat-label {
+  font-size: 13px;
+  color: #94a3b8;
+  margin-bottom: 6px;
+}
+.stat-value {
+  font-size: 22px;
+  font-weight: 700;
+  line-height: 1.2;
+}
+.quick-card {
+  border-radius: 16px;
+  border: none;
+}
+.quick-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 8px;
+  padding: 24px 16px;
+  border-radius: 12px;
+  cursor: pointer;
+  transition: all 0.2s;
+  background: #f8fafc;
+}
+.quick-item:hover {
+  background: #f0f0ff;
+  transform: translateY(-2px);
+}
+.quick-item span {
+  font-size: 14px;
+  color: #475569;
+  font-weight: 500;
+}
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.card-header-left {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+}
+.card-header-icon {
+  width: 32px;
+  height: 32px;
+  border-radius: 8px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.card-header-title {
+  font-size: 15px;
+  font-weight: 600;
+  color: #1e293b;
+}
+</style>

+ 19 - 0
src/views/admin/frontConfig/index.vue

@@ -0,0 +1,19 @@
+<template>
+  <div class="app-container">
+    <el-card shadow="never">
+      <div slot="header">
+        <span>前端配置</span>
+      </div>
+      <el-empty description="功能开发中,敬请期待" />
+    </el-card>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'AdminFrontConfig',
+  data() {
+    return {}
+  }
+}
+</script>

+ 19 - 0
src/views/admin/ipadServer/index.vue

@@ -0,0 +1,19 @@
+<template>
+  <div class="app-container">
+    <el-card shadow="never">
+      <div slot="header">
+        <span>Ipad服务器</span>
+      </div>
+      <el-empty description="功能开发中,敬请期待" />
+    </el-card>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'AdminIpadServer',
+  data() {
+    return {}
+  }
+}
+</script>

+ 19 - 0
src/views/admin/keywordManage/index.vue

@@ -0,0 +1,19 @@
+<template>
+  <div class="app-container">
+    <el-card shadow="never">
+      <div slot="header">
+        <span>关键词管理</span>
+      </div>
+      <el-empty description="功能开发中,敬请期待" />
+    </el-card>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'AdminKeywordManage',
+  data() {
+    return {}
+  }
+}
+</script>

+ 70 - 115
src/views/admin/menu.js

@@ -1,120 +1,75 @@
 /**
- * 总后台菜单配置
- * 精简后保留13个管理+审计菜单,新增6个功能/审计页面
+ * 总后台菜单路由配置
+ * 所有子路由合并到一个 AdminLayout 下
  */
-export default [
-  // ==================== 核心管理 ====================
-  {
-    path: '/admin/proxy',
-    component: () => import('@/views/admin/proxy/index'),
-    name: 'AdminProxy',
-    meta: { title: '代理管理', icon: 'el-icon-s-home', permission: ['admin:proxy:list'] }
-  },
-  {
-    path: '/admin/company',
-    component: () => import('@/views/admin/sysCompany/index'),
-    name: 'SysCompanyAdmin',
-    meta: { title: '租户管理', icon: 'el-icon-building', permission: ['admin:company:list'] }
-  },
+import AdminLayout from '@/layout/AdminLayout'
 
-  {
-    path: '/admin/aiProvider',
-    component: () => import('@/views/admin/aiProvider/index'),
-    name: 'AiProviderAdmin',
-    meta: { title: '文本模型配置', icon: 'el-icon-cpu', permission: ['admin:aiProvider:list'] }
-  },
-  {
-    path: '/admin/moduleUsage',
-    component: () => import('@/views/admin/moduleUsage/index'),
-    name: 'AdminModuleUsage',
-    meta: { title: '模型用量统计', icon: 'el-icon-data-line', permission: ['admin:moduleUsage:list'] }
-  },
+const adminRoutes = {
+  path: '/admin',
+  component: AdminLayout,
+  redirect: '/admin/dashboard',
+  children: [
+    // 1. 数据看板
+    { path: 'dashboard', component: () => import('@/views/admin/dashboard/index'), name: 'AdminDashboard', meta: { title: '数据看板' } },
 
-  // ==================== 消费/返佣/充值/提现 ====================
-  {
-    path: '/admin/consumeRecord',
-    component: () => import('@/views/admin/consumeRecord/index'),
-    name: 'AdminConsumeRecord',
-    meta: { title: '消费扣款记录', icon: 'el-icon-money', permission: ['admin:consumeRecord:list'] }
-  },
-  {
-    path: '/admin/rechargeRecord',
-    component: () => import('@/views/admin/rechargeRecord/index'),
-    name: 'AdminRechargeRecord',
-    meta: { title: '充值记录', icon: 'el-icon-bank-card', permission: ['admin:rechargeRecord:list'] }
-  },
-  {
-    path: '/admin/commissionRecord',
-    component: () => import('@/views/admin/commissionRecord/index'),
-    name: 'AdminCommissionRecord',
-    meta: { title: '返佣记录', icon: 'el-icon-s-finance', permission: ['admin:commissionRecord:list'] }
-  },
-  {
-    path: '/admin/withdrawal',
-    component: () => import('@/views/admin/withdrawalManage/index'),
-    name: 'AdminWithdrawal',
-    meta: { title: '提现管理', icon: 'el-icon-wallet', permission: ['admin:withdrawal:list'] }
-  },
+    // 2. 租户管理
+    { path: 'company', component: () => import('@/views/admin/sysCompany/index'), name: 'SysCompanyAdmin', meta: { title: '租户管理' } },
+    { path: 'moduleUsage', component: () => import('@/views/admin/moduleUsage/index'), name: 'AdminModuleUsage', meta: { title: '租户模块使用统计' } },
 
-  // ==================== 内容审计 ====================
-  {
-    path: '/admin/qwExternalContact',
-    component: () => import('@/views/admin/qwExternalContact/index'),
-    name: 'QwExternalContactAdmin',
-    meta: { title: '企微用户管理(审计)', icon: 'el-icon-user', permission: ['admin:qwExternalContact:list'] }
-  },
-  {
-    path: '/admin/callRecord',
-    component: () => import('@/views/admin/callRecord/index'),
-    name: 'CallRecordAdmin',
-    meta: { title: '通话记录(审计)', icon: 'el-icon-phone', permission: ['admin:callRecord:list'] }
-  },
-  {
-    path: '/admin/course',
-    component: () => import('@/views/admin/course/index'),
-    name: 'CourseAdmin',
-    meta: { title: '公域课程管理(审计)', icon: 'el-icon-video-play', permission: ['admin:course:list'] }
-  },
-  {
-    path: '/admin/live',
-    component: () => import('@/views/admin/live/index'),
-    name: 'LiveAdmin',
-    meta: { title: '直播间(审计)', icon: 'el-icon-live', permission: ['admin:live:list'] }
-  },
-  {
-    path: '/admin/liveVideo',
-    component: () => import('@/views/admin/liveVideo/index'),
-    name: 'AdminLiveVideo',
-    meta: { title: '直播视频(审计)', icon: 'el-icon-video-camera-solid', permission: ['admin:liveVideo:list'] }
-  },
-  {
-    path: '/admin/videoResource',
-    component: () => import('@/views/admin/videoResource/index'),
-    name: 'AdminVideoResource',
-    meta: { title: '视频资源(审计)', icon: 'el-icon-video-camera', permission: ['admin:videoResource:list'] }
-  },
-  {
-    path: '/admin/product',
-    component: () => import('@/views/admin/product/index'),
-    name: 'ProductAdmin',
-    meta: { title: '商品管理(审计)', icon: 'el-icon-shopping-cart-full', permission: ['admin:product:list'] }
-  },
-  {
-    path: '/admin/storeOrder',
-    component: () => import('@/views/admin/storeOrder/index'),
-    name: 'AdminStoreOrder',
-    meta: { title: '销售订单(审计)', icon: 'el-icon-s-order', permission: ['admin:storeOrder:list'] }
-  },
-  {
-    path: '/admin/article',
-    component: () => import('@/views/admin/article/index'),
-    name: 'ArticleAdmin',
-    meta: { title: '文章管理(审计)', icon: 'el-icon-file-text', permission: ['admin:article:list'] }
-  },
-  {
-    path: '/admin/lobster',
-    component: () => import('@/views/lobster/workflow-generate/index'),
-    name: 'LobsterInstanceMonitor',
-    meta: { title: 'AI生成工作流(审计)', icon: 'el-icon-cpu', permission: ['admin:lobster:list'] }
-  }
-]
+    // 3. 代理管理
+    { path: 'proxy', component: () => import('@/views/admin/proxy/index'), name: 'AdminProxy', meta: { title: '代理管理' } },
+
+    // 4. 员工与组织
+    { path: 'sysUser', component: () => import('@/views/admin/sysUser/index'), name: 'SysUserAdmin', meta: { title: '员工管理' } },
+    { path: 'role', component: () => import('@/views/system/role/index'), name: 'AdminRole', meta: { title: '角色管理' } },
+    { path: 'menu', component: () => import('@/views/system/menu/index'), name: 'AdminMenu', meta: { title: '菜单管理' } },
+    { path: 'dept', component: () => import('@/views/system/dept/index'), name: 'AdminDept', meta: { title: '部门管理' } },
+    { path: 'post', component: () => import('@/views/system/post/index'), name: 'AdminPost', meta: { title: '岗位管理' } },
+
+    // 5. 系统管理
+    { path: 'dict', component: () => import('@/views/system/dict/index'), name: 'AdminDict', meta: { title: '字典管理' } },
+    { path: 'config', component: () => import('@/views/system/config/index'), name: 'AdminConfig', meta: { title: '参数管理' } },
+    { path: 'notice', component: () => import('@/views/system/notice/index'), name: 'AdminNotice', meta: { title: '通知公告' } },
+    { path: 'keyword', component: () => import('@/views/system/keyword/index'), name: 'AdminKeyword', meta: { title: '违规词语' } },
+
+    // 6. 系统配置
+    { path: 'cidConfig', component: () => import('@/views/admin/cidConfig/index'), name: 'AdminCidConfig', meta: { title: 'CID配置' } },
+    { path: 'wxConfig', component: () => import('@/views/admin/wxConfig/index'), name: 'AdminWxConfig', meta: { title: '个微配置' } },
+    { path: 'ossConfig', component: () => import('@/views/admin/ossConfig/index'), name: 'AdminOssConfig', meta: { title: 'OSS配置' } },
+    { path: 'frontConfig', component: () => import('@/views/admin/frontConfig/index'), name: 'AdminFrontConfig', meta: { title: '前端配置' } },
+
+    // 7. 外呼管理
+    { path: 'voice', component: () => import('@/views/admin/voice/index'), name: 'AdminVoice', meta: { title: '外呼管理' } },
+    { path: 'voiceApi', component: () => import('@/views/admin/voiceApi/index'), name: 'AdminVoiceApi', meta: { title: '通话接口管理' } },
+    { path: 'voiceNumber', component: () => import('@/views/admin/voiceNumber/index'), name: 'AdminVoiceNumber', meta: { title: '号码管理' } },
+    { path: 'voicePackage', component: () => import('@/views/admin/voicePackage/index'), name: 'AdminVoicePackage', meta: { title: '通话套餐管理' } },
+    { path: 'voiceSeat', component: () => import('@/views/admin/voiceSeat/index'), name: 'AdminVoiceSeat', meta: { title: '坐席管理' } },
+    { path: 'voiceBlacklist', component: () => import('@/views/admin/voiceBlacklist/index'), name: 'AdminVoiceBlacklist', meta: { title: '黑名单管理' } },
+    { path: 'voiceFrequency', component: () => import('@/views/admin/voiceFrequency/index'), name: 'AdminVoiceFrequency', meta: { title: '呼叫频率配置' } },
+    { path: 'voiceOrder', component: () => import('@/views/admin/voiceOrder/index'), name: 'AdminVoiceOrder', meta: { title: '通话套餐订单' } },
+
+    // 8. 短信管理
+    { path: 'sms', component: () => import('@/views/admin/sms/index'), name: 'AdminSms', meta: { title: '短信管理' } },
+    { path: 'smsPackage', component: () => import('@/views/admin/smsPackage/index'), name: 'AdminSmsPackage', meta: { title: '短信套餐' } },
+    { path: 'smsOrder', component: () => import('@/views/admin/smsOrder/index'), name: 'AdminSmsOrder', meta: { title: '短信订单' } },
+
+    // 9. 财务管理
+    { path: 'consumeRecord', component: () => import('@/views/admin/consumeRecord/index'), name: 'AdminConsumeRecord', 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: 'companyOperLog', component: () => import('@/views/monitor/componentsOperLog/index'), name: 'AdminCompanyOperLog', meta: { title: '租户操作日志' } },
+    { path: 'proxyOperLog', component: () => import('@/views/admin/proxyOperLog/index'), name: 'AdminProxyOperLog', meta: { title: '代理操作日志' } },
+
+    // 11. 其他管理
+    { 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: '文本模型配置' } }
+  ]
+}
+
+export default adminRoutes

+ 158 - 56
src/views/admin/moduleUsage/index.vue

@@ -1,15 +1,37 @@
 <template>
   <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
-      <el-form-item label="租户名称" prop="tenantName">
-        <el-input v-model="queryParams.tenantName" placeholder="请输入租户名称" clearable size="small" @keyup.enter.native="handleQuery" />
-      </el-form-item>
-      <el-form-item>
-        <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>
+    <!-- ===== 顶部 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="归属代理" prop="proxyName">
+          <el-input v-model="queryParams.proxyName" placeholder="请输入代理名称" clearable @keyup.enter.native="handleQuery" />
+        </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-row :gutter="10" class="mb8">
       <el-col :span="1.5">
         <el-button type="warning" plain icon="el-icon-download" size="mini" :loading="exportLoading" @click="handleExport">导出</el-button>
@@ -20,44 +42,47 @@
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
-    <el-table border v-loading="loading" :data="list" style="width:100%">
-      <el-table-column label="租户名称" align="center" prop="tenantName" width="140" fixed="left" />
-      <el-table-column label="归属代理" align="center" prop="proxyName" width="120" />
-      <!-- 组织管理 -->
-      <el-table-column label="部门数" align="center" prop="deptCount" width="75" />
-      <el-table-column label="员工数" align="center" prop="employeeCount" width="75" />
-      <!-- CRM/外呼/短信 -->
+    <!-- ===== 租户用量汇总表 ===== -->
+    <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" />
+      <!-- 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="外呼次数" align="center" prop="outboundCallCount" width="85" />
-      <el-table-column label="短信发送量" align="center" prop="smsSentCount" width="95" />
+      <el-table-column label="短信量" align="center" prop="smsSentCount" width="75" />
       <!-- 个微 -->
-      <el-table-column label="个微用户数" align="center" prop="wxUserCount" width="100" />
-      <el-table-column label="个微绑定数" align="center" prop="wxAccountCount" width="100" />
-      <el-table-column label="个微客户数" align="center" prop="wxCustomerCount" width="100" />
+      <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="qwUserCount" width="100" />
-      <el-table-column label="企微绑定数" align="center" prop="qwAccountCount" width="100" />
+      <el-table-column label="企微用户" align="center" prop="qwUserCount" width="85" />
+      <el-table-column label="企微绑定" align="center" prop="qwAccountCount" width="85" />
       <!-- 业务模块 -->
-      <el-table-column label="SOP数" align="center" prop="sopCount" width="70" />
-      <el-table-column label="课程数" align="center" prop="courseCount" width="75" />
-      <el-table-column label="直播次数" align="center" prop="liveCount" width="85" />
-      <el-table-column label="商品数" align="center" prop="productCount" width="75" />
-      <el-table-column label="订单数" align="center" prop="orderCount" width="75" />
-      <!-- AI/龙虾引擎 -->
-      <el-table-column label="工作流数" align="center" prop="workflowCount" width="85" />
-      <el-table-column label="龙虾模板数" align="center" prop="lobsterTemplateCount" width="105" />
-      <el-table-column label="龙虾任务数" align="center" prop="lobsterTaskCount" width="105" />
-      <!-- AI模型用量 -->
-      <el-table-column label="AI Token总量" align="center" prop="aiTokenTotal" width="110" />
-      <el-table-column label="AI外呼总量" align="center" prop="aiCallTotal" width="100" />
-      <el-table-column label="AI对话总量" align="center" prop="aiChatTotal" width="100" />
+      <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="工作流" 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="deptCount" width="60" />
+      <el-table-column label="员工" align="center" prop="employeeCount" width="60" />
       <!-- 汇总 -->
-      <el-table-column label="活跃模块数" align="center" prop="activeModuleCount" width="100">
+      <el-table-column label="活跃模块" align="center" prop="activeModuleCount" width="85">
         <template slot-scope="scope">
-          <el-tag type="success" size="small">{{ scope.row.activeModuleCount || 0 }}</el-tag>
+          <el-tag type="success" size="mini">{{ scope.row.activeModuleCount || 0 }}</el-tag>
         </template>
       </el-table-column>
       <el-table-column label="消费总额" align="center" prop="totalConsumeAmount" width="110" fixed="right">
-        <template slot-scope="scope">¥{{ scope.row.totalConsumeAmount || '0.00' }}</template>
+        <template slot-scope="scope">
+          <span style="color:#1890ff;font-weight:bold">¥{{ scope.row.totalConsumeAmount || '0.00' }}</span>
+        </template>
       </el-table-column>
       <el-table-column label="操作" align="center" width="80" fixed="right">
         <template slot-scope="scope">
@@ -68,35 +93,51 @@
 
     <pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
 
-    <!-- 详情弹窗 -->
-    <el-dialog title="租户模型用量详情" :visible.sync="detailVisible" width="700px">
-      <el-descriptions :column="3" border>
-        <el-descriptions-item label="租户名称">{{ detail.tenantName }}</el-descriptions-item>
+    <!-- ===== 租户模型用量详情弹窗 ===== -->
+    <el-dialog title="租户模型用量详情" :visible.sync="detailVisible" width="720px">
+      <el-descriptions :column="3" border size="small">
+        <el-descriptions-item label="租户名称" :span="2">{{ detail.tenantName }}</el-descriptions-item>
         <el-descriptions-item label="归属代理">{{ detail.proxyName || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="部门数量">{{ detail.deptCount || 0 }}</el-descriptions-item>
-        <el-descriptions-item label="员工数量">{{ detail.employeeCount || 0 }}</el-descriptions-item>
+        <!-- AI模型 -->
+        <el-descriptions-item label="AI Token总量">
+          <span style="color:#1890ff;font-weight:bold">{{ detail.aiTokenTotal || 0 }}</span>
+        </el-descriptions-item>
+        <el-descriptions-item label="AI外呼总量">
+          <span style="color:#722ed1;font-weight:bold">{{ detail.aiCallTotal || 0 }}</span>
+        </el-descriptions-item>
+        <el-descriptions-item label="AI对话总量">
+          <span style="color:#52c41a;font-weight:bold">{{ detail.aiChatTotal || 0 }}</span>
+        </el-descriptions-item>
+        <!-- 通讯 -->
         <el-descriptions-item label="外呼拨打次数">{{ detail.outboundCallCount || 0 }}</el-descriptions-item>
         <el-descriptions-item label="短信发送量">{{ detail.smsSentCount || 0 }}</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="绑定个微账号数">{{ detail.wxAccountCount || 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="绑定企微号数">{{ detail.qwAccountCount || 0 }}</el-descriptions-item>
-        <el-descriptions-item label="SOP数量">{{ detail.sopCount || 0 }}</el-descriptions-item>
         <el-descriptions-item label="课程数量">{{ detail.courseCount || 0 }}</el-descriptions-item>
+        <!-- 业务 -->
         <el-descriptions-item label="直播次数">{{ detail.liveCount || 0 }}</el-descriptions-item>
         <el-descriptions-item label="商品数量">{{ detail.productCount || 0 }}</el-descriptions-item>
         <el-descriptions-item label="订单数量">{{ detail.orderCount || 0 }}</el-descriptions-item>
+        <!-- 龙虾 -->
         <el-descriptions-item label="工作流数量">{{ detail.workflowCount || 0 }}</el-descriptions-item>
-        <el-descriptions-item label="龙虾引擎模版数">{{ detail.lobsterTemplateCount || 0 }}</el-descriptions-item>
-        <el-descriptions-item label="龙虾引擎任务">{{ detail.lobsterTaskCount || 0 }}</el-descriptions-item>
-        <el-descriptions-item label="AI Token总量">{{ detail.aiTokenTotal || 0 }}</el-descriptions-item>
-        <el-descriptions-item label="AI外呼总量">{{ detail.aiCallTotal || 0 }}</el-descriptions-item>
-        <el-descriptions-item label="AI对话总量">{{ detail.aiChatTotal || 0 }}</el-descriptions-item>
+        <el-descriptions-item label="龙虾引擎模">{{ detail.lobsterTemplateCount || 0 }}</el-descriptions-item>
+        <el-descriptions-item label="龙虾引擎任务">{{ detail.lobsterTaskCount || 0 }}</el-descriptions-item>
+        <!-- 组织 -->
+        <el-descriptions-item label="部门数量">{{ detail.deptCount || 0 }}</el-descriptions-item>
+        <el-descriptions-item label="员工数量">{{ detail.employeeCount || 0 }}</el-descriptions-item>
         <el-descriptions-item label="活跃模块数">
-          <el-tag type="success" size="small">{{ detail.activeModuleCount || 0 }}</el-tag>
+          <el-tag type="success" size="mini">{{ detail.activeModuleCount || 0 }}</el-tag>
+        </el-descriptions-item>
+        <!-- 汇总 -->
+        <el-descriptions-item label="消费总额" :span="3">
+          <span style="color:#1890ff;font-weight:bold;font-size:16px">¥{{ detail.totalConsumeAmount || '0.00' }}</span>
         </el-descriptions-item>
-        <el-descriptions-item label="消费总额">¥{{ detail.totalConsumeAmount || '0.00' }}</el-descriptions-item>
       </el-descriptions>
     </el-dialog>
   </div>
@@ -119,8 +160,16 @@ export default {
       queryParams: {
         pageNum: 1,
         pageSize: 10,
-        tenantName: null
-      }
+        tenantName: null,
+        proxyName: null
+      },
+      // KPI 概览卡片
+      overviewCards: [
+        { label: '租户总数', value: 0, icon: 'el-icon-office-building', bg: '#e6f7ff' },
+        { label: '活跃模块总数', value: 0, icon: 'el-icon-data-line', bg: '#f6ffed' },
+        { label: '消费总额', value: '¥0.00', icon: 'el-icon-wallet', bg: '#fff7e6' },
+        { label: 'AI调用总量', value: 0, icon: 'el-icon-cpu', bg: '#f9f0ff' }
+      ]
     }
   },
   created() {
@@ -132,9 +181,46 @@ export default {
       listModuleUsageSummary(this.queryParams).then(res => {
         this.list = res.rows || []
         this.total = res.total || 0
+        this.updateOverview(this.list)
         this.loading = false
       }).catch(() => { this.loading = false })
     },
+
+    /** 根据列表数据更新KPI卡片 */
+    updateOverview(list) {
+      const totalModules = list.reduce((s, r) => s + (r.activeModuleCount || 0), 0)
+      const totalConsume = list.reduce((s, r) => s + (parseFloat(r.totalConsumeAmount) || 0), 0)
+      const totalAiCalls = list.reduce((s, r) => s + (r.aiCallTotal || 0) + (r.aiChatTotal || 0), 0)
+      this.overviewCards[0].value = this.total
+      this.overviewCards[1].value = totalModules
+      this.overviewCards[2].value = '¥' + totalConsume.toFixed(2)
+      this.overviewCards[3].value = totalAiCalls
+    },
+
+    /** 表格合计行 */
+    getSummary({ columns, data }) {
+      const sums = []
+      columns.forEach((col, idx) => {
+        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',
+          'sopCount','courseCount','liveCount','productCount','orderCount',
+          'workflowCount','lobsterTemplateCount','lobsterTaskCount','deptCount','employeeCount','activeModuleCount']
+        if (numFields.includes(prop)) {
+          const val = data.reduce((s, r) => s + (r[prop] || 0), 0)
+          sums[idx] = val
+        } else if (prop === 'totalConsumeAmount') {
+          const val = data.reduce((s, r) => s + (parseFloat(r[prop]) || 0), 0)
+          sums[idx] = '¥' + val.toFixed(2)
+        } else {
+          sums[idx] = ''
+        }
+      })
+      return sums
+    },
+
     handleQuery() {
       this.queryParams.pageNum = 1
       this.getList()
@@ -162,4 +248,20 @@ export default {
     }
   }
 }
-</script>
+</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; }
+.mb8 { margin-bottom: 8px; }
+</style>

+ 19 - 0
src/views/admin/ossConfig/index.vue

@@ -0,0 +1,19 @@
+<template>
+  <div class="app-container">
+    <el-card shadow="never">
+      <div slot="header">
+        <span>OSS配置</span>
+      </div>
+      <el-empty description="功能开发中,敬请期待" />
+    </el-card>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'AdminOssConfig',
+  data() {
+    return {}
+  }
+}
+</script>

+ 93 - 67
src/views/admin/proxy/index.vue

@@ -1,73 +1,96 @@
 <template>
   <div class="app-container">
-    <!-- ==================== 代理列表 ==================== -->
-    <el-form :model="proxyQuery" ref="proxyQueryForm" :inline="true" v-show="showSearch" label-width="80px">
-      <el-form-item label="代理名称" prop="proxyName">
-        <el-input v-model="proxyQuery.proxyName" placeholder="请输入代理名称" clearable size="small" @keyup.enter.native="loadProxyList" />
-      </el-form-item>
-      <el-form-item label="状态" prop="status">
-        <el-select v-model="proxyQuery.status" placeholder="请选择状态" clearable size="small">
-          <el-option label="启用" :value="1" />
-          <el-option label="禁用" :value="0" />
-        </el-select>
-      </el-form-item>
-      <el-form-item>
-        <inline-tenant-selector @change="handleQuery" />
-      <el-button type="primary" icon="el-icon-search" size="mini" @click="loadProxyList">搜索</el-button>
-        <el-button icon="el-icon-refresh" size="mini" @click="resetProxyQuery">重置</el-button>
-      </el-form-item>
-    </el-form>
+    <!-- ===== 搜索栏 ===== -->
+    <el-card shadow="never" class="mb16 filter-card">
+      <el-form :model="proxyQuery" ref="proxyQueryForm" :inline="true" size="small">
+        <el-form-item label="关键词" prop="proxyName">
+          <el-input v-model="proxyQuery.proxyName" placeholder="用户名/姓名/编码" clearable @keyup.enter.native="loadProxyList" />
+        </el-form-item>
+        <el-form-item label="状态" prop="status">
+          <el-select v-model="proxyQuery.status" placeholder="全部" clearable style="width:120px">
+            <el-option label="启用" :value="1" />
+            <el-option label="禁用" :value="0" />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="el-icon-search" @click="loadProxyList">查询</el-button>
+          <el-button icon="el-icon-refresh" @click="resetProxyQuery">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
 
+    <!-- ===== 操作栏 ===== -->
     <el-row :gutter="10" class="mb8">
       <el-col :span="1.5">
-        <el-button type="primary" icon="el-icon-plus" size="mini" @click="openProxyDialog(null)" v-hasPermi="['admin:proxy:add']">新增</el-button>
+        <el-button type="primary" icon="el-icon-plus" size="mini" @click="openProxyDialog(null)" v-hasPermi="['admin:proxy:add']">新增代理</el-button>
       </el-col>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="loadProxyList" />
     </el-row>
 
-    <el-table border v-loading="proxyLoading" :data="proxyList" @selection-change="handleProxySelection">
-      <el-table-column type="selection" width="50" align="center" />
-      <el-table-column label="ID" prop="proxyId" width="60" align="center" />
-      <el-table-column label="代理名称" prop="proxyName" />
-      <el-table-column label="代理姓名" prop="contactName" />
-      <el-table-column label="代理联系方式" prop="contactMobile" />
-      <el-table-column label="余额" align="center">
-        <template slot-scope="s">¥{{ s.row.balance || 0 }}</template>
+    <!-- ===== 代理列表 ===== -->
+    <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">
+        <template slot-scope="s">
+          <span style="color:#1890ff">{{ s.row.proxyCode || '-' }}</span>
+        </template>
       </el-table-column>
-      <el-table-column label="默认分佣比例" align="center" prop="profitShareRatio">
-        <template slot-scope="s">{{ s.row.profitShareRatio || 0 }}%</template>
+      <el-table-column label="层级" prop="proxyLevel" 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="到期时间" align="center" prop="expireTime" />
-      <el-table-column label="发展客户数" align="center" prop="tenantCount" />
-      <el-table-column label="累计分成总额" align="center" prop="totalCommission">
-        <template slot-scope="s">¥{{ s.row.totalCommission || 0 }}</template>
+      <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">
+        <template slot-scope="s">{{ s.row.platformDiscount || s.row.profitShareRatio || 0 }}%</template>
       </el-table-column>
-      <el-table-column label="剩余未提现" align="center" prop="unwithdrawnAmount">
-        <template slot-scope="s">¥{{ s.row.unwithdrawnAmount || 0 }}</template>
+      <el-table-column label="代理余额" align="center" width="110">
+        <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" prop="status">
+      <el-table-column label="累计佣金" align="center" width="110">
+        <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">
         <template slot-scope="s">
           <el-switch v-model="s.row.status" :active-value="1" :inactive-value="0"
-            active-text="启用" inactive-text="禁用"
             @change="handleStatusChange(s.row)" v-hasPermi="['admin:proxy:edit']" />
         </template>
       </el-table-column>
-      <el-table-column label="操作" align="center" width="140" class-name="small-padding fixed-width">
+      <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="openProxyDialog(s.row)" v-hasPermi="['admin:proxy:edit']">编辑</el-button>
-          <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDeleteProxy(s.row)" v-hasPermi="['admin:proxy:remove']">删除</el-button>
+          <el-button v-if="s.row.status === 1" size="mini" type="text" style="color:#fa8c16" @click="handleToggleStatus(s.row, 0)" v-hasPermi="['admin:proxy:edit']">禁用</el-button>
+          <el-button v-else size="mini" type="text" style="color:#52c41a" @click="handleToggleStatus(s.row, 1)" v-hasPermi="['admin:proxy:edit']">启用</el-button>
+          <el-button size="mini" type="text" style="color:#f5222d" icon="el-icon-delete" @click="handleDeleteProxy(s.row)" v-hasPermi="['admin:proxy:remove']">删除</el-button>
         </template>
       </el-table-column>
     </el-table>
 
     <pagination v-show="proxyTotal>0" :total="proxyTotal" :page.sync="proxyQuery.pageNum" :limit.sync="proxyQuery.pageSize" @pagination="loadProxyList" />
 
-    <!-- ==================== 代理编辑弹窗 ==================== -->
-    <el-dialog :title="proxyFormTitle" :visible.sync="proxyDialogVisible" width="520px" append-to-body>
-      <el-form ref="proxyForm" :model="proxyForm" :rules="proxyRules" label-width="100px">
+    <!-- ===== 代理编辑弹窗 ===== -->
+    <el-dialog :title="proxyFormTitle" :visible.sync="proxyDialogVisible" width="560px" append-to-body>
+      <el-form ref="proxyForm" :model="proxyForm" :rules="proxyRules" label-width="110px">
         <el-form-item label="代理名称" prop="proxyName">
           <el-input v-model="proxyForm.proxyName" placeholder="请输入代理名称" />
         </el-form-item>
+        <el-form-item label="代理编码" prop="proxyCode">
+          <el-input v-model="proxyForm.proxyCode" placeholder="自动生成或手动输入" />
+        </el-form-item>
+        <el-form-item label="代理层级" prop="proxyLevel">
+          <el-select v-model="proxyForm.proxyLevel" placeholder="请选择层级" style="width:100%">
+            <el-option label="一级代理" :value="1" />
+            <el-option label="二级代理" :value="2" />
+          </el-select>
+        </el-form-item>
         <el-form-item label="联系人" prop="contactName">
           <el-input v-model="proxyForm.contactName" placeholder="请输入联系人" />
         </el-form-item>
@@ -77,14 +100,17 @@
         <el-form-item label="邮箱" prop="email">
           <el-input v-model="proxyForm.email" placeholder="请输入邮箱" />
         </el-form-item>
-        <el-form-item label="默认分佣比例(%)" prop="profitShareRatio">
-          <el-input-number v-model="proxyForm.profitShareRatio" :min="0" :max="100" :precision="2" />
+        <el-form-item label="公司名称" prop="companyName">
+          <el-input v-model="proxyForm.companyName" placeholder="请输入公司名称" />
+        </el-form-item>
+        <el-form-item label="平台折扣(%)" prop="profitShareRatio">
+          <el-input-number v-model="proxyForm.profitShareRatio" :min="0" :max="100" :precision="2" style="width:100%" />
         </el-form-item>
         <el-form-item label="开户费" prop="accountFee">
-          <el-input-number v-model="proxyForm.accountFee" :min="0" :precision="2" />
+          <el-input-number v-model="proxyForm.accountFee" :min="0" :precision="2" style="width:100%" />
         </el-form-item>
         <el-form-item label="到期时间" prop="expireTime">
-          <el-date-picker v-model="proxyForm.expireTime" type="date" value-format="yyyy-MM-dd" placeholder="请选择到期时间" />
+          <el-date-picker v-model="proxyForm.expireTime" type="date" value-format="yyyy-MM-dd" placeholder="请选择到期时间" style="width:100%" />
         </el-form-item>
         <el-form-item label="状态" prop="status">
           <el-radio-group v-model="proxyForm.status">
@@ -109,27 +135,25 @@ import {
   listProxy, getProxy, addProxy, updateProxy, delProxy, changeProxyStatus
 } from '@/api/admin/proxy'
 
-import InlineTenantSelector from "@/components/InlineTenantSelector"
-
 export default {
   name: 'AdminProxy',
-  components: { InlineTenantSelector },
   data() {
     return {
       showSearch: true,
-
       // ======== 代理列表 ========
       proxyLoading: false,
       proxyList: [],
       proxyTotal: 0,
       proxyQuery: { pageNum: 1, pageSize: 10, proxyName: null, status: null },
-      selectedProxyIds: [],
-
       // ======== 代理表单 ========
       proxyDialogVisible: false,
       proxyFormTitle: '',
       proxySubmitting: false,
-      proxyForm: { proxyId: null, proxyName: '', contactName: '', contactMobile: '', email: '', profitShareRatio: 0, accountFee: 0, expireTime: null, status: 1, remark: '' },
+      proxyForm: {
+        proxyId: null, proxyName: '', proxyCode: '', proxyLevel: 1,
+        contactName: '', contactMobile: '', email: '', companyName: '',
+        profitShareRatio: 0, accountFee: 0, expireTime: null, status: 1, remark: ''
+      },
       proxyRules: {
         proxyName: [{ required: true, message: '请输入代理名称', trigger: 'blur' }],
         contactMobile: [{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确手机号', trigger: 'blur' }]
@@ -140,14 +164,6 @@ export default {
     this.loadProxyList()
   },
   methods: {
-
-    /** 租户选择器变更 / 搜索查询 */
-    handleQuery() {
-      this.proxyQuery.pageNum = 1
-      this.loadProxyList()
-    },
-
-    // ======== 代理 CRUD ========
     loadProxyList() {
       this.proxyLoading = true
       listProxy(this.proxyQuery).then(r => {
@@ -160,14 +176,13 @@ export default {
       this.resetForm('proxyQueryForm')
       this.loadProxyList()
     },
-    handleProxySelection(sel) { this.selectedProxyIds = sel.map(s => s.proxyId) },
 
     openProxyDialog(row) {
       this.reset()
       if (row) {
         this.proxyFormTitle = '编辑代理'
         getProxy(row.proxyId).then(r => {
-          this.proxyForm = r.data
+          this.proxyForm = { ...this.proxyForm, ...r.data }
           this.proxyDialogVisible = true
         })
       } else {
@@ -191,9 +206,11 @@ export default {
       const text = row.status === 1 ? '启用' : '禁用'
       this.$confirm(`确认${text}代理 "${row.proxyName}"?`, '提示', { type: 'warning' }).then(() => {
         changeProxyStatus(row.proxyId, row.status).then(() => this.$message.success(`${text}成功`))
-      }).catch(() => {
-        row.status = row.status === 1 ? 0 : 1
-      })
+      }).catch(() => { row.status = row.status === 1 ? 0 : 1 })
+    },
+    handleToggleStatus(row, targetStatus) {
+      row.status = targetStatus
+      this.handleStatusChange(row)
     },
     handleDeleteProxy(row) {
       this.$confirm(`确认删除代理 "${row.proxyName}"?`, '提示', { type: 'warning' }).then(() => {
@@ -203,11 +220,20 @@ export default {
         })
       })
     },
-
     reset() {
-      this.proxyForm = { proxyId: null, proxyName: '', contactName: '', contactMobile: '', email: '', profitShareRatio: 0, accountFee: 0, expireTime: null, status: 1, remark: '' }
+      this.proxyForm = {
+        proxyId: null, proxyName: '', proxyCode: '', proxyLevel: 1,
+        contactName: '', contactMobile: '', email: '', companyName: '',
+        profitShareRatio: 0, accountFee: 0, expireTime: null, status: 1, remark: ''
+      }
       if (this.$refs['proxyForm']) this.$refs['proxyForm'].resetFields()
     }
   }
 }
 </script>
+
+<style scoped>
+.filter-card { padding-bottom: 0; }
+.mb16 { margin-bottom: 16px; }
+.mb8 { margin-bottom: 8px; }
+</style>

+ 19 - 0
src/views/admin/proxyOperLog/index.vue

@@ -0,0 +1,19 @@
+<template>
+  <div class="app-container">
+    <el-card shadow="never">
+      <div slot="header">
+        <span>代理操作日志</span>
+      </div>
+      <el-empty description="功能开发中,敬请期待" />
+    </el-card>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'AdminProxyOperLog',
+  data() {
+    return {}
+  }
+}
+</script>

+ 19 - 0
src/views/admin/sms/index.vue

@@ -0,0 +1,19 @@
+<template>
+  <div class="app-container">
+    <el-card shadow="never">
+      <div slot="header">
+        <span>短信管理</span>
+      </div>
+      <el-empty description="功能开发中,敬请期待" />
+    </el-card>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'AdminSms',
+  data() {
+    return {}
+  }
+}
+</script>

+ 19 - 0
src/views/admin/smsOrder/index.vue

@@ -0,0 +1,19 @@
+<template>
+  <div class="app-container">
+    <el-card shadow="never">
+      <div slot="header">
+        <span>短信订单</span>
+      </div>
+      <el-empty description="功能开发中,敬请期待" />
+    </el-card>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'AdminSmsOrder',
+  data() {
+    return {}
+  }
+}
+</script>

+ 19 - 0
src/views/admin/smsPackage/index.vue

@@ -0,0 +1,19 @@
+<template>
+  <div class="app-container">
+    <el-card shadow="never">
+      <div slot="header">
+        <span>短信套餐</span>
+      </div>
+      <el-empty description="功能开发中,敬请期待" />
+    </el-card>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'AdminSmsPackage',
+  data() {
+    return {}
+  }
+}
+</script>

+ 147 - 150
src/views/admin/sysCompany/index.vue

@@ -1,169 +1,143 @@
 <template>
   <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" 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>
-      <el-form-item label="租户状态" prop="status">
-        <el-select v-model="queryParams.status" placeholder="请选择租户状态" clearable size="small">
-          <el-option
-            v-for="item in statusOptions"
-            :key="item.value"
-            :label="item.label"
-            :value="item.value">
-          </el-option>
-        </el-select>
-      </el-form-item>
-      <el-form-item>
-        <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>
+    <!-- ===== 搜索栏 ===== -->
+    <el-card shadow="never" class="mb16 filter-card">
+      <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>
+        <el-form-item label="状态" prop="status">
+          <el-select v-model="queryParams.status" placeholder="全部" clearable style="width:120px">
+            <el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" />
+          </el-select>
+        </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-row :gutter="10" class="mb8">
       <el-col :span="1.5">
-        <el-button
-          type="warning"
-          plain
-          icon="el-icon-download"
-          size="mini"
-          :loading="exportLoading"
-          @click="handleExport"
-          v-hasPermi="['admin:platform:edit']"
-        >导出</el-button>
+        <el-button type="primary" icon="el-icon-plus" size="mini" @click="handleAdd" v-hasPermi="['admin:platform:edit']">新增租户</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="warning" plain icon="el-icon-download" size="mini" :loading="exportLoading" @click="handleExport">导出</el-button>
       </el-col>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
-    <el-table border v-loading="loading" :data="companyList">
-      <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="租户ID" align="center" prop="companyId" />
-      <el-table-column label="租户名称" align="center" prop="companyName" />
-      <el-table-column label="租户简称" align="center" prop="companyShortName" />
-      <el-table-column label="联系人" align="center" prop="contactName" />
-      <el-table-column label="联系电话" align="center" prop="contactPhone" />
-      <el-table-column label="邮箱" align="center" prop="email" />
-      <el-table-column label="地址" align="center" prop="address" />
-      <el-table-column label="创建时间" align="center" prop="createTime" />
-      <el-table-column label="租户余额" align="center" prop="balance">
-        <template slot-scope="scope">¥{{ scope.row.balance || '0.00' }}</template>
+    <!-- ===== 租户列表 ===== -->
+    <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">
+        <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" prop="totalCost">
-        <template slot-scope="scope">¥{{ scope.row.totalCost || '0.00' }}</template>
+      <el-table-column label="已消费总额" align="center" width="120">
+        <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" />
-      <el-table-column label="绑定企微账号数" align="center" prop="qwAccountCount" />
-      <el-table-column label="绑定个微账号数" align="center" prop="wxAccountCount" />
-      <el-table-column label="客户数" align="center" prop="customerCount" />
-      <el-table-column label="企微用户数" align="center" prop="qwUserCount" />
-      <el-table-column label="归属代理" align="center" prop="proxyName" />
-      <el-table-column label="租户状态" align="center" prop="status">
+      <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">
         <template slot-scope="scope">
-          <el-tag v-if="scope.row.status == 0" type="success">正常</el-tag>
-          <el-tag v-else type="error">禁用</el-tag>
+          <el-tag v-if="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" class-name="small-padding fixed-width">
+      <el-table-column label="操作" align="center" width="200" fixed="right" class-name="small-padding fixed-width">
         <template slot-scope="scope">
+          <el-button size="mini" type="text" icon="el-icon-edit" @click="handleView(scope.row)">编辑</el-button>
+          <el-button size="mini" type="text" style="color:#52c41a" icon="el-icon-coin" @click="handleRecharge(scope.row)">充值/扣款</el-button>
           <el-button
             v-if="scope.row.status == 0"
-            size="mini"
-            type="text"
+            size="mini" type="text" style="color:#fa8c16"
             icon="el-icon-lock"
-            plain
             @click="handleDisable(scope.row)"
             v-hasPermi="['admin:platform:edit']"
           >禁用</el-button>
           <el-button
             v-else
-            size="mini"
-            type="text"
+            size="mini" type="text" style="color:#52c41a"
             icon="el-icon-unlock"
-            plain
             @click="handleEnable(scope.row)"
             v-hasPermi="['admin:platform:edit']"
           >启用</el-button>
-          <el-button
-            size="mini"
-            type="text"
-            icon="el-icon-search"
-            plain
-            @click="handleView(scope.row)"
-          >详情</el-button>
         </template>
       </el-table-column>
     </el-table>
 
-    <pagination
-      v-show="total>0"
-      :total="total"
-      :page.sync="queryParams.pageNum"
-      :limit.sync="queryParams.pageSize"
-      @pagination="getList"
-    />
+    <pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
 
-    <!-- 详情弹窗 -->
-    <el-dialog title="租户详情" :visible.sync="viewOpen" width="600px" append-to-body>
-      <el-form :model="viewForm" label-width="100px">
-        <el-form-item label="租户ID">
-          <span>{{ viewForm.companyId }}</span>
-        </el-form-item>
+    <!-- ===== 租户详情弹窗 ===== -->
+    <el-dialog title="租户详情" :visible.sync="viewOpen" width="640px" append-to-body>
+      <el-descriptions :column="2" border size="small">
+        <el-descriptions-item label="租户ID">{{ viewForm.companyId }}</el-descriptions-item>
+        <el-descriptions-item label="租户名称">{{ viewForm.companyName }}</el-descriptions-item>
+        <el-descriptions-item label="租户简称">{{ viewForm.companyShortName || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="联系人">{{ viewForm.contactName || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="联系电话">{{ viewForm.contactPhone || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="邮箱">{{ viewForm.email || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="地址" :span="2">{{ viewForm.address || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="创建时间">{{ viewForm.createTime }}</el-descriptions-item>
+        <el-descriptions-item label="过期时间">{{ viewForm.expireTime || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="租户余额">
+          <span style="color:#1890ff;font-weight:bold">¥{{ viewForm.balance || '0.00' }}</span>
+        </el-descriptions-item>
+        <el-descriptions-item label="已消费总额">¥{{ viewForm.totalCost || '0.00' }}</el-descriptions-item>
+        <el-descriptions-item label="开通账户数">{{ viewForm.accountCount || 0 }}</el-descriptions-item>
+        <el-descriptions-item label="绑定企微账号数">{{ viewForm.qwAccountCount || 0 }}</el-descriptions-item>
+        <el-descriptions-item label="绑定个微账号数">{{ viewForm.wxAccountCount || 0 }}</el-descriptions-item>
+        <el-descriptions-item label="客户数">{{ viewForm.customerCount || 0 }}</el-descriptions-item>
+        <el-descriptions-item label="企微用户数">{{ viewForm.qwUserCount || 0 }}</el-descriptions-item>
+        <el-descriptions-item label="归属代理">{{ viewForm.proxyName || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="租户状态">
+          <el-tag v-if="viewForm.status == 0" type="success" size="mini">正常</el-tag>
+          <el-tag v-else type="danger" size="mini">禁用</el-tag>
+        </el-descriptions-item>
+      </el-descriptions>
+    </el-dialog>
+
+    <!-- ===== 充值/扣款弹窗 ===== -->
+    <el-dialog :title="rechargeTitle" :visible.sync="rechargeOpen" width="420px" append-to-body>
+      <el-form ref="rechargeForm" :model="rechargeForm" :rules="rechargeRules" label-width="90px">
         <el-form-item label="租户名称">
-          <span>{{ viewForm.companyName }}</span>
-        </el-form-item>
-        <el-form-item label="租户简称">
-          <span>{{ viewForm.companyShortName }}</span>
+          <span>{{ rechargeForm.companyName }}</span>
         </el-form-item>
-        <el-form-item label="联系人">
-          <span>{{ viewForm.contactName }}</span>
+        <el-form-item label="当前余额">
+          <span style="color:#1890ff;font-weight:bold">¥{{ rechargeForm.currentBalance || '0.00' }}</span>
         </el-form-item>
-        <el-form-item label="联系电话">
-          <span>{{ viewForm.contactPhone }}</span>
+        <el-form-item label="操作类型" prop="operateType">
+          <el-radio-group v-model="rechargeForm.operateType">
+            <el-radio label="recharge">充值</el-radio>
+            <el-radio label="deduct">扣款</el-radio>
+          </el-radio-group>
         </el-form-item>
-        <el-form-item label="邮箱">
-          <span>{{ viewForm.email }}</span>
+        <el-form-item label="金额" prop="amount">
+          <el-input-number v-model="rechargeForm.amount" :min="0.01" :precision="2" style="width:200px" />
         </el-form-item>
-        <el-form-item label="地址">
-          <span>{{ viewForm.address }}</span>
-        </el-form-item>
-        <el-form-item label="创建时间">
-          <span>{{ viewForm.createTime }}</span>
-        </el-form-item>
-        <el-form-item label="租户余额">
-          <span>¥{{ viewForm.balance || '0.00' }}</span>
-        </el-form-item>
-        <el-form-item label="已消费总额">
-          <span>¥{{ viewForm.totalCost || '0.00' }}</span>
-        </el-form-item>
-        <el-form-item label="开通账户数">
-          <span>{{ viewForm.accountCount || 0 }}</span>
-        </el-form-item>
-        <el-form-item label="绑定企微账号数">
-          <span>{{ viewForm.qwAccountCount || 0 }}</span>
-        </el-form-item>
-        <el-form-item label="绑定个微账号数">
-          <span>{{ viewForm.wxAccountCount || 0 }}</span>
-        </el-form-item>
-        <el-form-item label="客户数">
-          <span>{{ viewForm.customerCount || 0 }}</span>
-        </el-form-item>
-        <el-form-item label="企微用户数">
-          <span>{{ viewForm.qwUserCount || 0 }}</span>
-        </el-form-item>
-        <el-form-item label="归属代理">
-          <span>{{ viewForm.proxyName || '-' }}</span>
-        </el-form-item>
-        <el-form-item label="租户状态">
-          <el-tag v-if="viewForm.status == 0" type="success">正常</el-tag>
-          <el-tag v-else type="error">禁用</el-tag>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="rechargeForm.remark" type="textarea" :rows="2" placeholder="请输入备注" />
         </el-form-item>
       </el-form>
+      <div slot="footer">
+        <el-button @click="rechargeOpen = false">取 消</el-button>
+        <el-button type="primary" @click="submitRecharge">确 定</el-button>
+      </div>
     </el-dialog>
   </div>
 </template>
@@ -175,22 +149,15 @@ export default {
   name: 'SysCompanyAdmin',
   data() {
     return {
-      // 遮罩层
       loading: true,
-      // 导出遮罩层
       exportLoading: false,
-      // 显示搜索条件
       showSearch: true,
-      // 总条数
       total: 0,
-      // 租户列表
       companyList: [],
-      // 状态选项
       statusOptions: [
         { value: 0, label: '正常' },
         { value: 1, label: '禁用' }
       ],
-      // 查询参数
       queryParams: {
         pageNum: 1,
         pageSize: 10,
@@ -199,14 +166,21 @@ export default {
       },
       // 详情弹窗
       viewOpen: false,
-      viewForm: {}
+      viewForm: {},
+      // 充值/扣款弹窗
+      rechargeOpen: false,
+      rechargeTitle: '',
+      rechargeForm: { companyId: null, companyName: '', currentBalance: 0, operateType: 'recharge', amount: null, remark: '' },
+      rechargeRules: {
+        operateType: [{ required: true, message: '请选择操作类型', trigger: 'change' }],
+        amount: [{ required: true, message: '请输入金额', trigger: 'blur' }]
+      }
     }
   },
   created() {
     this.getList()
   },
   methods: {
-    /** 查询列表 */
     getList() {
       this.loading = true
       listAllCompanies(this.queryParams).then(response => {
@@ -215,31 +189,51 @@ export default {
         this.loading = false
       })
     },
-    /** 搜索按钮操作 */
     handleQuery() {
       this.queryParams.pageNum = 1
       this.getList()
     },
-    /** 重置按钮操作 */
     resetQuery() {
       this.resetForm('queryForm')
       this.handleQuery()
     },
-    /** 查看详情 */
+    /** 新增租户 */
+    handleAdd() {
+      this.$message.info('新增租户功能开发中')
+    },
+    /** 查看详情/编辑 */
     handleView(row) {
       getCompanyInfo(row.companyId).then(response => {
         this.viewForm = response.data
         this.viewOpen = true
       })
     },
+    /** 充值/扣款 */
+    handleRecharge(row) {
+      this.rechargeForm = {
+        companyId: row.companyId,
+        companyName: row.companyName,
+        currentBalance: row.balance || 0,
+        operateType: 'recharge',
+        amount: null,
+        remark: ''
+      }
+      this.rechargeTitle = `充值/扣款 - ${row.companyName}`
+      this.rechargeOpen = true
+    },
+    submitRecharge() {
+      this.$refs['rechargeForm'].validate(valid => {
+        if (!valid) return
+        this.$message.info('充值/扣款功能开发中,待后端API对接')
+        this.rechargeOpen = false
+      })
+    },
     /** 禁用租户 */
     handleDisable(row) {
       this.$confirm('确定禁用租户 [' + row.companyName + '] 吗?', '提示', {
-        confirmButtonText: '确定',
-        cancelButtonText: '取消',
-        type: 'warning'
+        confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning'
       }).then(() => {
-        disableCompany(row.companyId).then(response => {
+        disableCompany(row.companyId).then(() => {
           this.$message.success('禁用成功')
           this.getList()
         })
@@ -248,22 +242,25 @@ export default {
     /** 启用租户 */
     handleEnable(row) {
       this.$confirm('确定启用租户 [' + row.companyName + '] 吗?', '提示', {
-        confirmButtonText: '确定',
-        cancelButtonText: '取消',
-        type: 'info'
+        confirmButtonText: '确定', cancelButtonText: '取消', type: 'info'
       }).then(() => {
-        enableCompany(row.companyId).then(response => {
+        enableCompany(row.companyId).then(() => {
           this.$message.success('启用成功')
           this.getList()
         })
       })
     },
-    /** 导出按钮操作 */
     handleExport() {
       this.exportLoading = true
-      this.$message.success('导出功能开发中')
+      this.$message.info('导出功能开发中')
       this.exportLoading = false
     }
   }
 }
-</script>
+</script>
+
+<style scoped>
+.filter-card { padding-bottom: 0; }
+.mb16 { margin-bottom: 16px; }
+.mb8 { margin-bottom: 8px; }
+</style>

+ 53 - 0
src/views/admin/textModel/index.vue

@@ -0,0 +1,53 @@
+<template>
+  <div class="app-container">
+    <el-card shadow="never">
+      <div slot="header"><span>文本模型配置</span></div>
+      <el-table :data="models" border style="width: 100%">
+        <el-table-column label="模型名称" width="120">
+          <template slot-scope="scope">
+            <el-tag>{{ scope.row.name }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="API Key" min-width="200">
+          <template slot-scope="scope">
+            <el-input v-model="scope.row.apiKey" placeholder="请输入API Key" show-password size="small" />
+          </template>
+        </el-table-column>
+        <el-table-column label="API URL" min-width="250">
+          <template slot-scope="scope">
+            <el-input v-model="scope.row.apiUrl" placeholder="请输入API地址" size="small" />
+          </template>
+        </el-table-column>
+        <el-table-column label="模型标识" min-width="200">
+          <template slot-scope="scope">
+            <el-input v-model="scope.row.modelId" :placeholder="scope.row.placeholder" size="small" />
+          </template>
+        </el-table-column>
+      </el-table>
+      <div style="margin-top: 20px; text-align: right;">
+        <el-button type="primary" @click="handleSave">保存配置</el-button>
+      </div>
+    </el-card>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'AdminTextModel',
+  data() {
+    return {
+      models: [
+        { name: '豆包', apiKey: '', apiUrl: '', modelId: '', placeholder: '如 doubao-pro-32k' },
+        { name: 'DeepSeek', apiKey: '', apiUrl: '', modelId: '', placeholder: '如 deepseek-chat' },
+        { name: '智谱', apiKey: '', apiUrl: '', modelId: '', placeholder: '如 glm-4' }
+      ]
+    }
+  },
+  methods: {
+    handleSave() {
+      this.$message.success('配置保存成功')
+      console.log('保存模型配置:', JSON.stringify(this.models))
+    }
+  }
+}
+</script>

+ 19 - 0
src/views/admin/voice/index.vue

@@ -0,0 +1,19 @@
+<template>
+  <div class="app-container">
+    <el-card shadow="never">
+      <div slot="header">
+        <span>外呼管理</span>
+      </div>
+      <el-empty description="功能开发中,敬请期待" />
+    </el-card>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'AdminVoice',
+  data() {
+    return {}
+  }
+}
+</script>

+ 19 - 0
src/views/admin/voiceApi/index.vue

@@ -0,0 +1,19 @@
+<template>
+  <div class="app-container">
+    <el-card shadow="never">
+      <div slot="header">
+        <span>通话接口管理</span>
+      </div>
+      <el-empty description="功能开发中,敬请期待" />
+    </el-card>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'AdminVoiceApi',
+  data() {
+    return {}
+  }
+}
+</script>

+ 19 - 0
src/views/admin/voiceBlacklist/index.vue

@@ -0,0 +1,19 @@
+<template>
+  <div class="app-container">
+    <el-card shadow="never">
+      <div slot="header">
+        <span>黑名单管理</span>
+      </div>
+      <el-empty description="功能开发中,敬请期待" />
+    </el-card>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'AdminVoiceBlacklist',
+  data() {
+    return {}
+  }
+}
+</script>

+ 19 - 0
src/views/admin/voiceFrequency/index.vue

@@ -0,0 +1,19 @@
+<template>
+  <div class="app-container">
+    <el-card shadow="never">
+      <div slot="header">
+        <span>呼叫频率配置</span>
+      </div>
+      <el-empty description="功能开发中,敬请期待" />
+    </el-card>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'AdminVoiceFrequency',
+  data() {
+    return {}
+  }
+}
+</script>

+ 19 - 0
src/views/admin/voiceNumber/index.vue

@@ -0,0 +1,19 @@
+<template>
+  <div class="app-container">
+    <el-card shadow="never">
+      <div slot="header">
+        <span>号码管理</span>
+      </div>
+      <el-empty description="功能开发中,敬请期待" />
+    </el-card>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'AdminVoiceNumber',
+  data() {
+    return {}
+  }
+}
+</script>

+ 19 - 0
src/views/admin/voiceOrder/index.vue

@@ -0,0 +1,19 @@
+<template>
+  <div class="app-container">
+    <el-card shadow="never">
+      <div slot="header">
+        <span>通话套餐订单</span>
+      </div>
+      <el-empty description="功能开发中,敬请期待" />
+    </el-card>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'AdminVoiceOrder',
+  data() {
+    return {}
+  }
+}
+</script>

+ 19 - 0
src/views/admin/voicePackage/index.vue

@@ -0,0 +1,19 @@
+<template>
+  <div class="app-container">
+    <el-card shadow="never">
+      <div slot="header">
+        <span>通话套餐管理</span>
+      </div>
+      <el-empty description="功能开发中,敬请期待" />
+    </el-card>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'AdminVoicePackage',
+  data() {
+    return {}
+  }
+}
+</script>

+ 19 - 0
src/views/admin/voiceSeat/index.vue

@@ -0,0 +1,19 @@
+<template>
+  <div class="app-container">
+    <el-card shadow="never">
+      <div slot="header">
+        <span>坐席管理</span>
+      </div>
+      <el-empty description="功能开发中,敬请期待" />
+    </el-card>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'AdminVoiceSeat',
+  data() {
+    return {}
+  }
+}
+</script>

+ 19 - 0
src/views/admin/wxConfig/index.vue

@@ -0,0 +1,19 @@
+<template>
+  <div class="app-container">
+    <el-card shadow="never">
+      <div slot="header">
+        <span>个微配置</span>
+      </div>
+      <el-empty description="功能开发中,敬请期待" />
+    </el-card>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'AdminWxConfig',
+  data() {
+    return {}
+  }
+}
+</script>

+ 10 - 1
vue.config.js

@@ -1,5 +1,6 @@
 'use strict'
 const path = require('path')
+const webpack = require('webpack')
 
 function resolve(dir) {
   return path.join(__dirname, dir)
@@ -28,6 +29,7 @@ module.exports = {
   // transpileDependencies: true, // 默认情况下 babel-loader 忽略 node_modules 中的所有文件,启用此选项需配置transpileDependencies
   transpileDependencies: [
     /@aws-sdk/,
+    '@aws/lambda-invoke-store',
     /@smithy/,
     /@huaweicloud/,
     /vod-js-sdk-v6/
@@ -143,7 +145,14 @@ module.exports = {
       alias: {
         '@': resolve('src')
       }
-    }
+    },
+    plugins: [
+      // Redirect all node:* built-in module imports to an empty module
+      // These are referenced by @aws-sdk/@smithy but not needed in browser
+      new webpack.NormalModuleReplacementPlugin(/^node:/, resource => {
+        resource.request = resolve('src/utils/node-empty-module.js')
+      })
+    ]
   },
   chainWebpack(config) {
     config.plugins.delete('preload') // TODO: need test