boss пре 1 дан
родитељ
комит
9e08eb2ae4

+ 18 - 0
src/api/admin/ipadServer.js

@@ -0,0 +1,18 @@
+import request from '@/utils/request'
+
+// 查询iPad服务器列表(含租户使用统计)
+export function listIpadServers(query) {
+  return request({
+    url: '/admin/ipad-server-list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 获取iPad租户使用统计
+export function getIpadStats() {
+  return request({
+    url: '/admin/ipad-stats',
+    method: 'get'
+  })
+}

+ 18 - 0
src/api/admin/sysCompany.js

@@ -69,4 +69,22 @@ export function exportCompany(query) {
     params: query,
     responseType: 'blob'
   })
+}
+
+// 获取租户菜单树(管理端/销售端)
+export function getTenantMenuTree(companyId, flag) {
+  return request({
+    url: '/admin/company/' + companyId + '/menu',
+    method: 'post',
+    data: { flag: flag }
+  })
+}
+
+// 编辑租户菜单(勾选/取消)
+export function editTenantMenu(companyId, data) {
+  return request({
+    url: '/admin/company/' + companyId + '/menu/edit',
+    method: 'post',
+    data: data
+  })
 }

+ 13 - 38
src/api/admin/sysUser.js

@@ -1,7 +1,7 @@
 import request from '@/utils/request'
 
-// 查询所有租户员工列表
-export function listAllUsers(query) {
+// 查询管理员列表(sys_user 中 company_id IS NULL)
+export function listAdminUsers(query) {
   return request({
     url: '/admin/companyUser/list',
     method: 'get',
@@ -9,62 +9,37 @@ export function listAllUsers(query) {
   })
 }
 
-// 根据租户ID查询员工列表
-export function listByCompany(companyId) {
-  return request({
-    url: '/admin/companyUser/byCompany/' + companyId,
-    method: 'get'
-  })
-}
-
-// 获取员工详情
-export function getUserInfo(userId) {
+// 获取管理员详情
+export function getAdminUserInfo(userId) {
   return request({
     url: '/admin/companyUser/' + userId,
     method: 'get'
   })
 }
 
-// 禁用员账户
-export function disableUser(userId) {
+// 禁用管理员账户(status=1 停用)
+export function disableAdminUser(userId) {
   return request({
     url: '/admin/companyUser/status/' + userId,
     method: 'put',
-    params: { status: 1 }
+    params: { status: '1' }
   })
 }
 
-// 启用员账户
-export function enableUser(userId) {
+// 启用管理员账户(status=0 正常)
+export function enableAdminUser(userId) {
   return request({
     url: '/admin/companyUser/status/' + userId,
     method: 'put',
-    params: { status: 0 }
+    params: { status: '0' }
   })
 }
 
-// 查询员工账户变化记录
-export function listChangeLogs(query) {
-  return request({
-    url: '/admin/companyUser/changeList',
-    method: 'get',
-    params: query
-  })
-}
-
-// 获取统计信息
-export function getStatistics() {
-  return request({
-    url: '/admin/companyUser/statistics',
-    method: 'get'
-  })
-}
-
-// 导出员工列表
-export function exportSysUser(query) {
+// 导出管理员列表
+export function exportAdminUser(query) {
   return request({
     url: '/admin/companyUser/export',
     method: 'get',
     params: query
   })
-}
+}

+ 2 - 100
src/router/index.js

@@ -5,7 +5,6 @@ Vue.use(Router)
 
 /* Layout */
 import Layout from '@/layout'
-import ParentView from '@/components/ParentView';
 import LiveConsole from "@/views/live/liveConsole/index.vue";
 
 
@@ -246,105 +245,8 @@ export const constantRoutes = [
   ]
   },
   // ======== 龙虾引擎 (Lobster Workflow Engine) ========
-  {
-    path: '/lobster',
-    component: Layout,
-    redirect: '/lobster/production-workflow',
-    name: 'Lobster',
-    meta: { title: '龙虾引擎', icon: 'system' },
-    children: [
-      {
-        path: 'production-workflow',
-        component: ParentView,
-        redirect: '/lobster/production-workflow/canvas',
-        name: 'ProductionWorkflow',
-        hidden: true,
-        meta: { title: 'AI生产工作流', icon: 'component' },
-        children: [
-          {
-            path: 'canvas',
-            component: () => import('@/views/lobster/workflow-canvas/index'),
-            name: 'LobsterCanvas',
-            hidden: true,
-            meta: { title: '工作流画布', icon: 'chart' }
-          },
-          {
-            path: 'template',
-            component: () => import('@/views/lobster/template/index'),
-            name: 'LobsterTemplate',
-            hidden: true,
-            meta: { title: '工作流模板库', icon: 'documentation' }
-          }
-        ]
-      },
-      {
-        path: 'workflow-generate',
-        component: () => import('@/views/lobster/workflow-generate/index'),
-        name: 'LobsterGenerate',
-        meta: { title: 'AI生成工作流', icon: 'build' }
-      },
-      {
-        path: 'instance',
-        component: () => import('@/views/lobster/instance/index'),
-        name: 'LobsterInstance',
-        meta: { title: '实例监控', icon: 'monitor' }
-      },
-      {
-        path: 'optimization',
-        component: () => import('@/views/lobster/optimization/index'),
-        name: 'LobsterOptimization',
-        meta: { title: 'AI优化建议', icon: 'eye-open' }
-      },
-      {
-        path: 'prompt',
-        component: () => import('@/views/lobster/prompt/index'),
-        name: 'LobsterPrompt',
-        meta: { title: '提示词管理', icon: 'edit' }
-      },
-      {
-        path: 'sales-corpus',
-        component: () => import('@/views/lobster/sales-corpus/index'),
-        name: 'SalesCorpus',
-        meta: { title: '销冠语料学习', icon: 'star' }
-      },
-      {
-        path: 'api-registry',
-        component: () => import('@/views/lobster/api-registry/index'),
-        name: 'LobsterApiRegistry',
-        meta: { title: '接口注册中心', icon: 'nested' }
-      },
-      {
-        path: 'dead-letter',
-        component: () => import('@/views/lobster/dead-letter/index'),
-        name: 'LobsterDeadLetter',
-        meta: { title: '死信队列', icon: 'bug' }
-      },
-      {
-        path: 'event-audit',
-        component: () => import('@/views/lobster/event-audit/index'),
-        name: 'LobsterEventAudit',
-        meta: { title: '节点审核', icon: 'checkbox' }
-      },
-      {
-        path: 'chat-aggregate',
-        component: () => import('@/views/lobster/chat-aggregate/index'),
-        name: 'ChatAggregate',
-        meta: { title: '聚合聊天', icon: 'message' }
-      },
-      {
-        path: 'model-config',
-        component: () => import('@/views/lobster/model-config/index'),
-        name: 'LobsterModelConfig',
-        meta: { title: '模型配置', icon: 'server' }
-      },
-      {
-        path: 'billing',
-        component: () => import('@/views/lobster/billing/index'),
-        name: 'LobsterBilling',
-        meta: { title: 'Token系数管理', icon: 'money' }
-      }
-    ]
-  },
+  // 已迁移到 sys_menu 数据库管理,不再硬编码到 constantRoutes
+  // 子路由在 Admin > Lobster-mgmt 下通过数据库驱动
 ]
 
 const originalPush = Router.prototype.push

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

@@ -8,7 +8,7 @@
       <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-option v-for="a in proxyList" :key="a.proxyId" :label="a.proxyName + ' — 折扣' + a.platformDiscount + '%'" :value="a.proxyId" />
           </el-select>
         </el-form-item>
         <el-form-item v-if="selectedProxyId && proxyDiscount">

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

@@ -29,7 +29,6 @@
       <el-table :data="agentReport" border v-loading="loading" size="small" style="width:100%">
         <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">
@@ -70,7 +69,6 @@ export default {
         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,

+ 131 - 26
src/views/admin/ipadServer/index.vue

@@ -1,15 +1,63 @@
 <template>
   <div class="app-container">
+    <!-- ===== 统计卡片 ===== -->
+    <el-row :gutter="16" class="mb16">
+      <el-col :span="6">
+        <el-card shadow="hover" class="stat-card">
+          <div class="stat-label">服务器总数</div>
+          <div class="stat-value">{{ stats.serverCount }}</div>
+        </el-card>
+      </el-col>
+      <el-col :span="6">
+        <el-card shadow="hover" class="stat-card">
+          <div class="stat-label">总容量</div>
+          <div class="stat-value">{{ stats.totalCapacity }}</div>
+        </el-card>
+      </el-col>
+      <el-col :span="6">
+        <el-card shadow="hover" class="stat-card stat-used">
+          <div class="stat-label">已使用</div>
+          <div class="stat-value">{{ stats.used }}</div>
+        </el-card>
+      </el-col>
+      <el-col :span="6">
+        <el-card shadow="hover" class="stat-card stat-remain">
+          <div class="stat-label">剩余可用</div>
+          <div class="stat-value">{{ stats.remaining }}</div>
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <!-- ===== 租户使用概览 ===== -->
+    <el-card shadow="never" class="mb16" v-if="stats.tenantStats && stats.tenantStats.length > 0">
+      <div slot="header" class="clearfix">
+        <span>租户 iPad 使用概览</span>
+      </div>
+      <el-table :data="stats.tenantStats" size="mini" border style="width:100%">
+        <el-table-column label="租户名称" prop="companyName" min-width="160" />
+        <el-table-column label="已绑定iPad数" prop="usedCount" width="120" align="center" />
+        <el-table-column label="限制数" prop="maxPadNum" width="100" align="center">
+          <template slot-scope="s">
+            {{ s.row.maxPadNum === -1 ? '不限' : s.row.maxPadNum }}
+          </template>
+        </el-table-column>
+        <el-table-column label="状态" prop="status" width="100" align="center">
+          <template slot-scope="s">
+            <el-tag :type="s.row.status === '超限' ? 'danger' : 'success'" size="mini">{{ s.row.status }}</el-tag>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-card>
+
     <!-- ===== 搜索区 ===== -->
     <el-card shadow="never" class="mb16 filter-card">
       <el-form :model="queryParams" ref="queryForm" size="small" :inline="true">
-        <el-form-item label="服务器地址" prop="serverIp">
-          <el-input v-model="queryParams.serverIp" placeholder="请输入服务器IP" clearable style="width:200px" @keyup.enter.native="handleQuery" />
+        <el-form-item label="服务器IP" prop="ip">
+          <el-input v-model="queryParams.ip" placeholder="请输入服务器IP" clearable style="width:200px" @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 label="在线" :value="1" />
-            <el-option label="离线" :value="0" />
+        <el-form-item label="租户" prop="companyId">
+          <el-select v-model="queryParams.companyId" placeholder="全部租户" clearable style="width:180px">
+            <el-option v-for="t in tenantOptions" :key="t.companyId" :label="t.companyName" :value="t.companyId" />
           </el-select>
         </el-form-item>
         <el-form-item>
@@ -32,17 +80,29 @@
 
     <!-- ===== 列表 ===== -->
     <el-table border v-loading="loading" :data="dataList" size="small" style="width:100%">
-      <el-table-column label="服务器ID" prop="id" width="80" align="center" />
-      <el-table-column label="服务器地址/IP" prop="serverIp" min-width="180" />
-      <el-table-column label="端口" prop="port" width="100" align="center" />
-      <el-table-column label="状态" prop="status" width="100" align="center">
+      <el-table-column label="ID" prop="id" width="60" align="center" />
+      <el-table-column label="标题" prop="title" min-width="120" />
+      <el-table-column label="服务器IP" prop="ip" min-width="140" />
+      <el-table-column label="端口" prop="port" width="80" align="center" />
+      <el-table-column label="总容量" prop="totalCount" width="80" align="center" />
+      <el-table-column label="剩余" prop="count" width="70" align="center">
+        <template slot-scope="s">
+          <span :class="{'text-danger': s.row.count !== null && s.row.count <= 0}">{{ s.row.count }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="已分配租户数" prop="tenantCount" width="110" align="center">
         <template slot-scope="s">
-          <el-tag v-if="s.row.status === 1" type="success" size="mini">在线</el-tag>
-          <el-tag v-else-if="s.row.status === 0" type="danger" size="mini">离线</el-tag>
-          <el-tag v-else size="mini">{{ s.row.status || '-' }}</el-tag>
+          <el-tag v-if="s.row.tenantCount > 0" size="mini" type="warning">{{ s.row.tenantCount }}</el-tag>
+          <span v-else>-</span>
         </template>
       </el-table-column>
-      <el-table-column label="在线设备数" prop="onlineDeviceCount" width="100" align="center" />
+      <el-table-column label="已绑定用户数" prop="usedCount" width="110" align="center">
+        <template slot-scope="s">
+          <span v-if="s.row.usedCount > 0">{{ s.row.usedCount }} / {{ s.row.totalCount }}</span>
+          <span v-else>-</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="使用租户" prop="tenantNames" min-width="160" show-overflow-tooltip />
       <el-table-column label="创建时间" prop="createTime" width="160" align="center" />
       <el-table-column label="操作" align="center" width="160" fixed="right" class-name="small-padding fixed-width">
         <template slot-scope="s">
@@ -57,11 +117,23 @@
     <!-- ===== 编辑弹窗 ===== -->
     <el-dialog :title="formTitle" :visible.sync="dialogVisible" width="500px" append-to-body>
       <el-form ref="form" :model="form" :rules="rules" label-width="100px">
-        <el-form-item label="服务器地址" prop="serverIp">
-          <el-input v-model="form.serverIp" placeholder="请输入服务器地址/IP" />
+        <el-form-item label="标题" prop="title">
+          <el-input v-model="form.title" placeholder="请输入服务器标题" />
+        </el-form-item>
+        <el-form-item label="服务器地址" prop="ip">
+          <el-input v-model="form.ip" placeholder="请输入服务器IP" />
         </el-form-item>
         <el-form-item label="端口" prop="port">
-          <el-input-number v-model="form.port" :min="1" :max="65535" :precision="0" style="width:100%" placeholder="请输入端口号" />
+          <el-input v-model="form.port" placeholder="请输入端口号" />
+        </el-form-item>
+        <el-form-item label="URL" prop="url">
+          <el-input v-model="form.url" placeholder="请输入URL(可选)" />
+        </el-form-item>
+        <el-form-item label="总容量" prop="totalCount">
+          <el-input-number v-model="form.totalCount" :min="0" :precision="0" style="width:100%" placeholder="总容量" />
+        </el-form-item>
+        <el-form-item label="剩余数量" prop="count">
+          <el-input-number v-model="form.count" :min="0" :precision="0" style="width:100%" placeholder="剩余数量" />
         </el-form-item>
         <el-form-item label="备注" prop="remark">
           <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入备注" />
@@ -76,6 +148,7 @@
 </template>
 
 <script>
+import { listIpadServers, getIpadStats } from '@/api/admin/ipadServer'
 import request from '@/utils/request'
 
 export default {
@@ -86,28 +159,53 @@ export default {
       loading: false,
       dataList: [],
       total: 0,
-      queryParams: { pageNum: 1, pageSize: 10, serverIp: '', status: '' },
+      queryParams: { pageNum: 1, pageSize: 10, ip: '', companyId: '' },
+      stats: { serverCount: 0, totalCapacity: 0, used: 0, remaining: 0, tenantStats: [] },
+      tenantOptions: [],
       dialogVisible: false,
       formTitle: '',
       submitting: false,
-      form: { id: null, serverIp: '', port: 8080, remark: '' },
+      form: { id: null, title: '', ip: '', port: '8080', url: '', totalCount: 0, count: 0, remark: '' },
       rules: {
-        serverIp: [{ required: true, message: '请输入服务器地址', trigger: 'blur' }],
+        ip: [{ required: true, message: '请输入服务器地址', trigger: 'blur' }],
         port: [{ required: true, message: '请输入端口', trigger: 'blur' }]
       }
     }
   },
   created() {
+    this.loadStats()
     this.loadList()
   },
   methods: {
+    /** 加载统计数据 */
+    loadStats() {
+      getIpadStats().then(res => {
+        const data = res.data
+        this.stats = {
+          serverCount: data.serverCount || 0,
+          totalCapacity: data.totalCapacity || 0,
+          used: data.used || 0,
+          remaining: data.remaining || 0,
+          tenantStats: data.tenantStats || []
+        }
+        // 租户筛选下拉框复用统计数据
+        this.tenantOptions = (data.tenantStats || []).map(t => ({
+          companyId: t.companyId,
+          companyName: t.companyName
+        }))
+      })
+    },
+    /** 加载服务器列表 */
     loadList() {
       this.loading = true
-      request({ url: '/qw/qwIpadServer/list', method: 'get', params: this.queryParams }).then(r => {
+      const params = { pageNum: this.queryParams.pageNum, pageSize: this.queryParams.pageSize }
+      if (this.queryParams.ip) params.ip = this.queryParams.ip
+      if (this.queryParams.companyId) params.companyId = this.queryParams.companyId
+      listIpadServers(params).then(r => {
         this.dataList = r.rows
         this.total = r.total
         this.loading = false
-      })
+      }).catch(() => { this.loading = false })
     },
     openDialog(row) {
       this.reset()
@@ -133,14 +231,16 @@ export default {
           this.$message.success(this.form.id ? '修改成功' : '新增成功')
           this.dialogVisible = false
           this.loadList()
+          this.loadStats()
         }).finally(() => { this.submitting = false })
       })
     },
     handleDelete(row) {
-      this.$confirm(`确认删除服务器 "${row.serverIp}"?`, '提示', { type: 'warning' }).then(() => {
+      this.$confirm(`确认删除服务器 "${row.ip || row.title}"?`, '提示', { type: 'warning' }).then(() => {
         request({ url: `/qw/qwIpadServer/${row.id}`, method: 'delete' }).then(() => {
           this.$message.success('删除成功')
           this.loadList()
+          this.loadStats()
         })
       })
     },
@@ -160,11 +260,11 @@ export default {
     },
     resetQuery() {
       this.$refs['queryForm'].resetFields()
-      this.queryParams = { pageNum: 1, pageSize: 10, serverIp: '', status: '' }
+      this.queryParams = { pageNum: 1, pageSize: 10, ip: '', companyId: '' }
       this.loadList()
     },
     reset() {
-      this.form = { id: null, serverIp: '', port: 8080, remark: '' }
+      this.form = { id: null, title: '', ip: '', port: '8080', url: '', totalCount: 0, count: 0, remark: '' }
       if (this.$refs['form']) this.$refs['form'].resetFields()
     }
   }
@@ -175,5 +275,10 @@ export default {
 .mb8 { margin-bottom: 8px; }
 .mb16 { margin-bottom: 16px; }
 .filter-card { padding-bottom: 0; }
-
+.stat-card { text-align: center; }
+.stat-card .stat-label { font-size: 13px; color: #909399; margin-bottom: 8px; }
+.stat-card .stat-value { font-size: 28px; font-weight: bold; color: #303133; }
+.stat-card.stat-used .stat-value { color: #e6a23c; }
+.stat-card.stat-remain .stat-value { color: #67c23a; }
+.text-danger { color: #f5222d; font-weight: bold; }
 </style>

+ 13 - 2
src/views/admin/menu.js

@@ -86,9 +86,20 @@ const adminRoutes = {
     { 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: '关键词管理' } },
 
-    // 14. Lobster 引擎(挂在 /admin 下,避免 /workflow 代理到 8006 导致 401
+    // 14. Lobster 引擎(挂在 /admin/lobster-mgmt 下,通过 sys_menu 数据库驱动
     { path: 'workflowGenerate', component: () => import('@/views/lobster/workflow-generate/index'), name: 'AdminWorkflowGenerate', meta: { title: 'AI生成工作流' } },
-    { path: 'salesCorpus', component: () => import('@/views/lobster/sales-corpus/index'), name: 'AdminSalesCorpus', meta: { title: '销冠语料学习' } }
+    { path: 'salesCorpus', component: () => import('@/views/lobster/sales-corpus/index'), name: 'AdminSalesCorpus', meta: { title: '销冠语料学习' } },
+    { path: 'workflowCanvas', component: () => import('@/views/lobster/workflow-canvas/index'), name: 'AdminWorkflowCanvas', meta: { title: '工作流画布' } },
+    { path: 'workflowTemplate', component: () => import('@/views/lobster/template/index'), name: 'AdminWorkflowTemplate', meta: { title: '工作流模板库' } },
+    { path: 'instance', component: () => import('@/views/lobster/instance/index'), name: 'AdminLobsterInstance', meta: { title: '实例监控' } },
+    { path: 'optimization', component: () => import('@/views/lobster/optimization/index'), name: 'AdminLobsterOptimization', meta: { title: 'AI优化建议' } },
+    { path: 'prompt', component: () => import('@/views/lobster/prompt/index'), name: 'AdminLobsterPrompt', meta: { title: '提示词管理' } },
+    { path: 'apiRegistry', component: () => import('@/views/lobster/api-registry/index'), name: 'AdminLobsterApiRegistry', meta: { title: '接口注册中心' } },
+    { path: 'deadLetter', component: () => import('@/views/lobster/dead-letter/index'), name: 'AdminLobsterDeadLetter', meta: { title: '死信队列' } },
+    { path: 'eventAudit', component: () => import('@/views/lobster/event-audit/index'), name: 'AdminLobsterEventAudit', meta: { title: '节点审核' } },
+    { path: 'chatAggregate', component: () => import('@/views/lobster/chat-aggregate/index'), name: 'AdminLobsterChatAggregate', meta: { title: '聚合聊天' } },
+    { path: 'lobsterModelConfig', component: () => import('@/views/lobster/model-config/index'), name: 'AdminLobsterModelConfig', meta: { title: '模型配置' } },
+    { path: 'billing', component: () => import('@/views/lobster/billing/index'), name: 'AdminLobsterBilling', meta: { title: 'Token系数管理' } }
   ]
 }
 

+ 3 - 24
src/views/admin/proxy/index.vue

@@ -4,7 +4,7 @@
     <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-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">
@@ -30,18 +30,6 @@
     <!-- ===== 代理列表 ===== -->
     <el-table border v-loading="proxyLoading" :data="proxyList" size="small" style="width:100%">
       <el-table-column label="ID" prop="proxyId" min-width="60" align="center" />
-      <el-table-column label="代理编码" prop="proxyCode" min-width="100" align="center">
-        <template slot-scope="s">
-          <span style="color:#1890ff">{{ s.row.proxyCode || '-' }}</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="层级" prop="proxyLevel" min-width="80" align="center">
-        <template slot-scope="s">
-          <el-tag v-if="s.row.proxyLevel === 1" type="success" size="mini">一级代理</el-tag>
-          <el-tag v-else-if="s.row.proxyLevel === 2" type="warning" size="mini">二级代理</el-tag>
-          <el-tag v-else size="mini">{{ s.row.proxyLevel || '-' }}</el-tag>
-        </template>
-      </el-table-column>
       <el-table-column label="代理名称" prop="proxyName" min-width="110" />
       <el-table-column label="联系人" prop="contactName" min-width="80" align="center" />
       <el-table-column label="联系方式" prop="contactMobile" min-width="110" align="center" />
@@ -82,15 +70,6 @@
         <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>
@@ -150,7 +129,7 @@ export default {
       proxyFormTitle: '',
       proxySubmitting: false,
       proxyForm: {
-        proxyId: null, proxyName: '', proxyCode: '', proxyLevel: 1,
+        proxyId: null, proxyName: '',
         contactName: '', contactMobile: '', email: '', companyName: '',
         profitShareRatio: 0, accountFee: 0, expireTime: null, status: 1, remark: ''
       },
@@ -222,7 +201,7 @@ export default {
     },
     reset() {
       this.proxyForm = {
-        proxyId: null, proxyName: '', proxyCode: '', proxyLevel: 1,
+        proxyId: null, proxyName: '',
         contactName: '', contactMobile: '', email: '', companyName: '',
         profitShareRatio: 0, accountFee: 0, expireTime: null, status: 1, remark: ''
       }

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

@@ -55,16 +55,18 @@
       <el-table-column label="备注" prop="remark" min-width="100" show-overflow-tooltip />
       <el-table-column label="状态" align="center" min-width="70">
         <template slot-scope="scope">
-          <el-tag v-if="scope.row.status == 0" type="success" size="mini">正常</el-tag>
+          <el-tag v-if="scope.row.status == 1" 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" width="200" fixed="right" class-name="small-padding fixed-width">
+      <el-table-column label="操作" align="center" width="340" 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 size="mini" type="text" style="color:#722ed1" icon="el-icon-menu" @click="handleEditMenu(scope.row, 'sys')">管理端菜单</el-button>
+          <el-button size="mini" type="text" style="color:#13c2c2" icon="el-icon-sell" @click="handleEditMenu(scope.row, 'com')">销售菜单</el-button>
           <el-button
-            v-if="scope.row.status == 0"
+            v-if="scope.row.status == 1"
             size="mini" type="text" style="color:#fa8c16"
             icon="el-icon-lock"
             @click="handleDisable(scope.row)"
@@ -106,7 +108,7 @@
         <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-if="viewForm.status == 1" type="success" size="mini">正常</el-tag>
           <el-tag v-else type="danger" size="mini">禁用</el-tag>
         </el-descriptions-item>
       </el-descriptions>
@@ -159,8 +161,8 @@
           <el-col :span="12">
             <el-form-item label="状态" prop="status">
               <el-radio-group v-model="addDialog.form.status">
-                <el-radio :label="0">启用</el-radio>
-                <el-radio :label="1">禁用</el-radio>
+                <el-radio :label="1">启用</el-radio>
+                <el-radio :label="0">禁用</el-radio>
               </el-radio-group>
             </el-form-item>
           </el-col>
@@ -199,11 +201,39 @@
         <el-button type="primary" @click="submitRecharge">确 定</el-button>
       </div>
     </el-dialog>
+
+    <!-- ===== 菜单编辑弹窗 ===== -->
+    <el-dialog :title="menuDialog.title" :visible.sync="menuDialog.visible" width="560px" append-to-body destroy-on-close>
+      <div v-loading="menuDialog.loading" style="min-height:100px">
+        <el-tree
+          v-if="menuDialog.treeData.length"
+          ref="menuTree"
+          :data="menuDialog.treeData"
+          show-checkbox
+          check-strictly
+          node-key="id"
+          :default-checked-keys="menuDialog.checkedKeys"
+          :props="{ label: 'label', children: 'children' }"
+          default-expand-all
+        >
+          <span slot-scope="{ data }" class="custom-tree-node">
+            <span>{{ data.label }}</span>
+            <el-tag v-if="data.visible === '0'" type="success" size="mini" style="margin-left:8px">显示</el-tag>
+            <el-tag v-else type="info" size="mini" style="margin-left:8px">隐藏</el-tag>
+          </span>
+        </el-tree>
+        <el-empty v-if="!menuDialog.loading && !menuDialog.treeData.length" description="暂无菜单数据" />
+      </div>
+      <div slot="footer">
+        <el-button @click="menuDialog.visible = false">取 消</el-button>
+        <el-button type="primary" :loading="menuDialog.submitting" @click="submitMenuEdit">确 定</el-button>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
 <script>
-import { listAllCompanies, getCompanyInfo, addCompany, disableCompany, enableCompany, rechargeCompany, exportCompany } from '@/api/admin/sysCompany'
+import { listAllCompanies, getCompanyInfo, addCompany, disableCompany, enableCompany, rechargeCompany, exportCompany, getTenantMenuTree, editTenantMenu } from '@/api/admin/sysCompany'
 
 export default {
   name: 'SysCompanyAdmin',
@@ -215,8 +245,8 @@ export default {
       total: 0,
       companyList: [],
       statusOptions: [
-        { value: 0, label: '正常' },
-        { value: 1, label: '禁用' }
+        { value: 1, label: '正常' },
+        { value: 0, label: '禁用' }
       ],
       queryParams: {
         pageNum: 1,
@@ -240,7 +270,7 @@ export default {
           manager: '',
           startTime: null,
           limitTime: null,
-          status: 0
+          status: 1
         },
         rules: {
           companyName: [{ required: true, message: '请输入企业名称', trigger: 'blur' }],
@@ -256,6 +286,19 @@ export default {
       rechargeRules: {
         operateType: [{ required: true, message: '请选择操作类型', trigger: 'change' }],
         amount: [{ required: true, message: '请输入金额', trigger: 'blur' }]
+      },
+      // 菜单编辑弹窗
+      menuDialog: {
+        visible: false,
+        title: '',
+        flag: 'sys',       // sys=管理端菜单, com=销售菜单
+        companyId: null,
+        companyName: '',
+        treeData: [],
+        checkedKeys: [],    // 初始勾选的菜单ID(visible=0)
+        allMenuIds: [],     // 所有菜单ID
+        loading: false,
+        submitting: false
       }
     }
   },
@@ -290,7 +333,7 @@ export default {
         manager: '',
         startTime: null,
         limitTime: null,
-        status: 0
+        status: 1
       }
       this.addDialog.visible = true
       this.$nextTick(() => {
@@ -337,10 +380,16 @@ export default {
       this.$refs['rechargeForm'].validate(valid => {
         if (!valid) return
         const { companyId, operateType, amount, remark } = this.rechargeForm
-        rechargeCompany(companyId, { operateType, amount, remark }).then(() => {
-          this.$message.success(operateType === 'recharge' ? '充值成功' : '扣款成功')
-          this.rechargeOpen = false
-          this.getList()
+        rechargeCompany(companyId, { operateType, amount, remark }).then(res => {
+          if (res.code === 200) {
+            this.$message.success(res.msg || (operateType === 'recharge' ? '充值成功' : '扣款成功'))
+            this.rechargeOpen = false
+            this.getList()
+          } else {
+            this.$message.error(res.msg || '操作失败')
+          }
+        }).catch(() => {
+          this.$message.error('操作失败,请重试')
         })
       })
     },
@@ -372,6 +421,80 @@ export default {
         this.download(response.msg)
         this.exportLoading = false
       }).catch(() => { this.exportLoading = false })
+    },
+    /** 编辑菜单(管理端/销售端) */
+    handleEditMenu(row, flag) {
+      this.menuDialog = {
+        visible: true,
+        title: (flag === 'sys' ? '编辑管理端菜单 - ' : '编辑销售菜单 - ') + row.companyName,
+        flag: flag,
+        companyId: row.companyId,
+        companyName: row.companyName,
+        treeData: [],
+        checkedKeys: [],
+        allMenuIds: [],
+        loading: true,
+        submitting: false
+      }
+      getTenantMenuTree(row.companyId, flag).then(res => {
+        if (res.code === 200) {
+          const menus = res.menus || []
+          this.menuDialog.treeData = menus
+          // 递归提取所有菜单ID + visible=0的ID(勾选状态)
+          const allIds = []
+          const checkedIds = []
+          const walk = (nodes) => {
+            if (!nodes) return
+            nodes.forEach(n => {
+              allIds.push(n.id)
+              if (n.visible === '0') checkedIds.push(n.id)
+              if (n.children && n.children.length) walk(n.children)
+            })
+          }
+          walk(menus)
+          this.menuDialog.checkedKeys = checkedIds
+          this.menuDialog.allMenuIds = allIds
+        } else {
+          this.$message.error(res.msg || '加载菜单失败')
+        }
+      }).catch(() => {
+        this.$message.error('加载菜单失败')
+      }).finally(() => {
+        this.menuDialog.loading = false
+      })
+    },
+    /** 提交菜单编辑 */
+    submitMenuEdit() {
+      const tree = this.$refs.menuTree
+      if (!tree) return
+      const currentChecked = tree.getCheckedKeys()
+      const originalChecked = this.menuDialog.checkedKeys
+      // selected = 新勾选的(当前有,原来没有)
+      const selected = currentChecked.filter(id => !originalChecked.includes(id))
+      // unSelected = 新取消的(原来有,当前没有)
+      const unSelected = originalChecked.filter(id => !currentChecked.includes(id))
+      if (selected.length === 0 && unSelected.length === 0) {
+        this.$message.info('菜单无变化')
+        this.menuDialog.visible = false
+        return
+      }
+      this.menuDialog.submitting = true
+      editTenantMenu(this.menuDialog.companyId, {
+        flag: this.menuDialog.flag,
+        selected: selected,
+        unSelected: unSelected
+      }).then(res => {
+        if (res.code === 200) {
+          this.$message.success('菜单更新成功')
+          this.menuDialog.visible = false
+        } else {
+          this.$message.error(res.msg || '菜单更新失败')
+        }
+      }).catch(() => {
+        this.$message.error('菜单更新失败')
+      }).finally(() => {
+        this.menuDialog.submitting = false
+      })
     }
   }
 }
@@ -381,4 +504,5 @@ export default {
 .mb8 { margin-bottom: 8px; }
 .mb16 { margin-bottom: 16px; }
 .filter-card { padding-bottom: 0; }
+.custom-tree-node { display: flex; align-items: center; font-size: 13px; }
 </style>

+ 54 - 128
src/views/admin/sysUser/index.vue

@@ -2,35 +2,26 @@
   <div class="app-container">
     <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-form-item label="管理员名称" prop="nickName">
         <el-input
-          v-model="queryParams.companyName"
-          placeholder="请输入租户名称"
+          v-model="queryParams.nickName"
+          placeholder="请输入管理员名称"
           clearable
           size="small"
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="员工名称" prop="userName">
+      <el-form-item label="登录账号" prop="userName">
         <el-input
           v-model="queryParams.userName"
-          placeholder="请输入员工名称"
+          placeholder="请输入登录账号"
           clearable
           size="small"
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="员工账号" prop="loginName">
-        <el-input
-          v-model="queryParams.loginName"
-          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-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"
@@ -58,40 +49,29 @@
           v-hasPermi="['admin:companyUser:list']"
         >导出</el-button>
       </el-col>
-      <el-col :span="1.5">
-        <el-button
-          type="info"
-          plain
-          icon="el-icon-document"
-          size="mini"
-          @click="showChangeLogs = true"
-        >变化记录</el-button>
-      </el-col>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
     <el-table v-loading="loading" :data="userList" border size="small" style="width:100%">
       <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="租户名称" align="center" prop="companyName" />
-      <el-table-column label="员工账号" align="center" prop="loginName" />
-      <el-table-column label="员工名称" align="center" prop="userName" />
-      <el-table-column label="员工邮箱" align="center" prop="email" />
-      <el-table-column label="员工电话" align="center" prop="phonenumber" />
-      <el-table-column label="创建时间" align="center" prop="createTime" />
-      <el-table-column label="员工状态" align="center" prop="status">
+      <el-table-column label="登录账号" align="center" prop="userName" min-width="100" />
+      <el-table-column label="管理员名称" align="center" prop="nickName" min-width="100" />
+      <el-table-column label="邮箱" align="center" prop="email" min-width="140" show-overflow-tooltip />
+      <el-table-column label="手机号" align="center" prop="phonenumber" min-width="110" />
+      <el-table-column label="创建时间" align="center" prop="createTime" min-width="140" />
+      <el-table-column label="状态" align="center" prop="status" min-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" class-name="small-padding fixed-width" width="150">
         <template slot-scope="scope">
           <el-button
-            v-if="scope.row.status == 0"
+            v-if="scope.row.status == '0'"
             size="mini"
             type="text"
             icon="el-icon-lock"
-            plain
             @click="handleDisable(scope.row)"
             v-hasPermi="['admin:companyUser:edit']"
           >禁用</el-button>
@@ -100,7 +80,6 @@
             size="mini"
             type="text"
             icon="el-icon-unlock"
-            plain
             @click="handleEnable(scope.row)"
             v-hasPermi="['admin:companyUser:edit']"
           >启用</el-button>
@@ -108,7 +87,6 @@
             size="mini"
             type="text"
             icon="el-icon-search"
-            plain
             @click="handleView(scope.row)"
           >详情</el-button>
         </template>
@@ -124,162 +102,110 @@
     />
 
     <!-- 详情弹窗 -->
-    <el-dialog title="员工详情" :visible.sync="viewOpen" width="600px" append-to-body>
-      <el-form :model="viewForm" label-width="100px">
-        <el-form-item label="租户名称">
-          <span>{{ viewForm.companyName }}</span>
-        </el-form-item>
-        <el-form-item label="员工账号">
-          <span>{{ viewForm.loginName }}</span>
-        </el-form-item>
-        <el-form-item label="员工名称">
-          <span>{{ viewForm.userName }}</span>
-        </el-form-item>
-        <el-form-item label="员工邮箱">
-          <span>{{ viewForm.email }}</span>
-        </el-form-item>
-        <el-form-item label="员工电话">
-          <span>{{ viewForm.phonenumber }}</span>
-        </el-form-item>
-        <el-form-item label="创建时间">
-          <span>{{ viewForm.createTime }}</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>
-      </el-form>
-    </el-dialog>
-
-    <!-- 变化记录弹窗 -->
-    <el-dialog title="员工账户变化记录" :visible.sync="showChangeLogs" width="800px" append-to-body>
-      <el-table :data="changeLogList" border>
-        <el-table-column label="租户名称" align="center" prop="companyName" />
-        <el-table-column label="员工账号" align="center" prop="loginName" />
-        <el-table-column label="员工名称" align="center" prop="userName" />
-        <el-table-column label="变化类型" align="center" prop="changeType">
-          <template slot-scope="scope">
-            <el-tag v-if="scope.row.changeType == 'create'" type="success">创建</el-tag>
-            <el-tag v-else-if="scope.row.changeType == 'disable'" type="error">禁用</el-tag>
-            <el-tag v-else-if="scope.row.changeType == 'enable'" type="info">启用</el-tag>
-            <el-tag v-else type="warning">{{ scope.row.changeType }}</el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column label="变化时间" align="center" prop="changeTime" />
-        <el-table-column label="操作人" align="center" prop="operator" />
-        <el-table-column label="备注" align="center" prop="remark" />
-      </el-table>
+    <el-dialog title="管理员详情" :visible.sync="viewOpen" width="600px" append-to-body>
+      <el-descriptions :column="2" border size="small">
+        <el-descriptions-item label="登录账号">{{ viewForm.userName }}</el-descriptions-item>
+        <el-descriptions-item label="管理员名称">{{ viewForm.nickName }}</el-descriptions-item>
+        <el-descriptions-item label="邮箱">{{ viewForm.email || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="手机号">{{ viewForm.phonenumber || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="创建时间">{{ viewForm.createTime }}</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>
   </div>
 </template>
 
 <script>
-import { listAllUsers, getUserInfo, disableUser, enableUser, listChangeLogs, exportSysUser } from '@/api/admin/sysUser'
+import { listAdminUsers, getAdminUserInfo, disableAdminUser, enableAdminUser, exportAdminUser } from '@/api/admin/sysUser'
 
 export default {
   name: 'SysUserAdmin',
   data() {
     return {
-      // 遮罩层
       loading: true,
-      // 导出遮罩层
       exportLoading: false,
-      // 显示搜索条件
       showSearch: true,
-      // 总条数
       total: 0,
-      // 员工列表
       userList: [],
-      // 变化记录列表
-      changeLogList: [],
-      // 状态选项
       statusOptions: [
-        { value: 0, label: '正常' },
-        { value: 1, label: '禁用' }
+        { value: '0', label: '正常' },
+        { value: '1', label: '停用' }
       ],
-      // 查询参数
       queryParams: {
         pageNum: 1,
         pageSize: 10,
-        companyName: null,
+        nickName: null,
         userName: null,
-        loginName: null,
         status: null
       },
-      // 详情弹窗
       viewOpen: false,
-      viewForm: {},
-      // 变化记录弹窗
-      showChangeLogs: false
+      viewForm: {}
     }
   },
   created() {
     this.getList()
   },
   methods: {
-    /** 查询列表 */
     getList() {
       this.loading = true
-      listAllUsers(this.queryParams).then(response => {
+      listAdminUsers(this.queryParams).then(response => {
         this.userList = response.rows
         this.total = response.total
         this.loading = false
       })
     },
-    /** 搜索按钮操作 */
     handleQuery() {
       this.queryParams.pageNum = 1
       this.getList()
     },
-    /** 重置按钮操作 */
     resetQuery() {
       this.resetForm('queryForm')
       this.handleQuery()
     },
-    /** 查看详情 */
     handleView(row) {
-      getUserInfo(row.userId).then(response => {
+      getAdminUserInfo(row.userId).then(response => {
         this.viewForm = response.data
         this.viewOpen = true
       })
     },
-    /** 禁用员工 */
     handleDisable(row) {
-      this.$confirm('确定禁用员工 [' + row.userName + '] 吗?', '提示', {
+      this.$confirm('确定禁用管理员 [' + row.nickName + '] 吗?', '提示', {
         confirmButtonText: '确定',
         cancelButtonText: '取消',
         type: 'warning'
       }).then(() => {
-        disableUser(row.userId).then(response => {
-          this.$message.success('禁用成功')
-          this.getList()
+        disableAdminUser(row.userId).then(res => {
+          if (res.code === 200) {
+            this.$message.success('禁用成功')
+            this.getList()
+          } else {
+            this.$message.error(res.msg || '禁用失败')
+          }
         })
       })
     },
-    /** 启用员工 */
     handleEnable(row) {
-      this.$confirm('确定启用员工 [' + row.userName + '] 吗?', '提示', {
+      this.$confirm('确定启用管理员 [' + row.nickName + '] 吗?', '提示', {
         confirmButtonText: '确定',
         cancelButtonText: '取消',
         type: 'info'
       }).then(() => {
-        enableUser(row.userId).then(response => {
-          this.$message.success('启用成功')
-          this.getList()
+        enableAdminUser(row.userId).then(res => {
+          if (res.code === 200) {
+            this.$message.success('启用成功')
+            this.getList()
+          } else {
+            this.$message.error(res.msg || '启用失败')
+          }
         })
       })
     },
-    /** 查看变化记录 */
-    showChangeLogsDialog() {
-      listChangeLogs({}).then(response => {
-        this.changeLogList = response.data
-        this.showChangeLogs = true
-      })
-    },
-    /** 导出按鈕操作 */
     handleExport() {
       this.exportLoading = true
-      exportSysUser(this.queryParams).then(response => {
+      exportAdminUser(this.queryParams).then(response => {
         this.download(response.msg)
         this.exportLoading = false
       }).catch(() => { this.exportLoading = false })
@@ -292,4 +218,4 @@ export default {
 .mb8 { margin-bottom: 8px; }
 .mb16 { margin-bottom: 16px; }
 .filter-card { padding-bottom: 0; }
-</style>
+</style>