瀏覽代碼

Merge remote-tracking branch 'origin/saas_adminUi' into saas_adminUi

xgb 1 月之前
父節點
當前提交
f6319c5b0c
共有 2 個文件被更改,包括 205 次插入45 次删除
  1. 38 0
      src/plugins/modal.js
  2. 167 45
      src/views/admin/sysCompany/index.vue

+ 38 - 0
src/plugins/modal.js

@@ -0,0 +1,38 @@
+import { Message, MessageBox } from 'element-ui'
+
+/**
+ * 全局 $modal 插件 — 兼容若依风格调用
+ * 用法:
+ *   this.$modal.msgSuccess('操作成功')
+ *   this.$modal.msgError('操作失败')
+ *   this.$modal.confirm('确认删除?').then(() => { ... })
+ */
+export default {
+  install(Vue) {
+    const modal = {
+      /** 成功消息 */
+      msgSuccess(msg) {
+        Message({ showClose: true, message: msg, type: 'success' })
+      },
+      /** 错误消息 */
+      msgError(msg) {
+        Message({ showClose: true, message: msg, type: 'error' })
+      },
+      /** 警告消息 */
+      msgWarning(msg) {
+        Message({ showClose: true, message: msg, type: 'warning' })
+      },
+      /** 确认对话框 */
+      confirm(msg, title = '系统提示', options = {}) {
+        return MessageBox.confirm(msg, title, {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning',
+          ...options
+        })
+      }
+    }
+
+    Vue.prototype.$modal = modal
+  }
+}

+ 167 - 45
src/views/admin/sysCompany/index.vue

@@ -245,17 +245,20 @@
     </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-dialog :title="menuDialog.title" :visible.sync="menuDialog.visible" width="560px" append-to-body destroy-on-close @opened="onMenuDialogOpened">
+      <div v-loading="menuDialog.loading" class="menu-tree-scroll">
+        <div v-if="menuDialog.treeReady && menuDialog.treeData.length" class="menu-tree-toolbar">
+          <el-button type="text" size="mini" @click="expandAllMenuNodes">展开全部</el-button>
+          <el-button type="text" size="mini" @click="collapseAllMenuNodes">收起全部</el-button>
+        </div>
         <el-tree
-          v-if="menuDialog.treeData.length"
+          v-if="menuDialog.treeReady"
           ref="menuTree"
           :data="menuDialog.treeData"
           show-checkbox
           node-key="id"
-          :default-checked-keys="menuDialog.checkedKeys"
-          :props="{ label: 'label', children: 'children' }"
-          default-expand-all
+          :props="menuTreeProps"
+          :expand-on-click-node="false"
         >
           <span slot-scope="{ data }" class="custom-tree-node">
             <span>{{ data.label }}</span>
@@ -263,7 +266,7 @@
             <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="暂无菜单数据" />
+        <el-empty v-if="!menuDialog.loading && menuDialog.treeReady && !menuDialog.treeData.length" description="暂无菜单数据" />
       </div>
       <div slot="footer">
         <el-button @click="menuDialog.visible = false">取 消</el-button>
@@ -376,16 +379,18 @@ export default {
         operateType: [{ required: true, message: '请选择操作类型', trigger: 'change' }],
         amount: [{ required: true, message: '请输入金额', trigger: 'blur' }]
       },
+      menuTreeProps: { label: 'label', children: 'children' },
       // 菜单编辑弹窗
       menuDialog: {
         visible: false,
         title: '',
-        flag: 'sys',       // sys=管理端菜单, com=销售菜单
+        flag: 'sys',
         companyId: null,
         companyName: '',
         treeData: [],
-        checkedKeys: [],    // 初始勾选的菜单ID(visible=0)
-        allMenuIds: [],     // 所有菜单ID
+        treeReady: false,
+        checkedKeys: [],
+        checkedKeySet: null,
         loading: false,
         submitting: false
       },
@@ -583,6 +588,76 @@ export default {
         this.exportLoading = false
       }).catch(() => { this.exportLoading = false })
     },
+    collectCheckedFromTree(nodes) {
+      const ids = []
+      const walk = (list) => {
+        (list || []).forEach(n => {
+          if (n.visible === '0') ids.push(n.id)
+          if (n.children && n.children.length) walk(n.children)
+        })
+      }
+      walk(nodes)
+      return ids
+    },
+    applyMenuTreeChecked(menus) {
+      const tree = this.$refs.menuTree
+      const d = this.menuDialog
+      if (!tree) return
+      tree.setCheckedKeys(this.collectCheckedFromTree(menus))
+      const keys = tree.getCheckedKeys().concat(tree.getHalfCheckedKeys())
+      d.checkedKeys = keys
+      d.checkedKeySet = new Set(keys)
+    },
+    walkMenuTreeNodes(nodes, fn) {
+      (nodes || []).forEach(n => {
+        fn(n)
+        if (n.children && n.children.length) this.walkMenuTreeNodes(n.children, fn)
+      })
+    },
+    expandAllMenuNodes() {
+      const tree = this.$refs.menuTree
+      if (!tree) return
+      this.walkMenuTreeNodes(this.menuDialog.treeData, n => {
+        const node = tree.getNode(n.id)
+        if (node) node.expanded = true
+      })
+    },
+    collapseAllMenuNodes() {
+      const tree = this.$refs.menuTree
+      if (!tree) return
+      this.walkMenuTreeNodes(this.menuDialog.treeData, n => {
+        const node = tree.getNode(n.id)
+        if (node) node.expanded = false
+      })
+    },
+    onMenuDialogOpened() {
+      if (this.menuDialog.companyId) {
+        this.loadTenantMenuTree()
+      }
+    },
+    loadTenantMenuTree() {
+      const d = this.menuDialog
+      if (!d.companyId) return
+      d.loading = true
+      d.treeReady = false
+      d.treeData = []
+      getTenantMenuTree(d.companyId, d.flag).then(res => {
+        if (res.code !== 200) {
+          return Promise.reject(new Error(res.msg || '加载菜单失败'))
+        }
+        const menus = res.menus || []
+        // 延迟挂载树,先出弹窗再渲染,默认全部收起
+        d.treeData = menus
+        requestAnimationFrame(() => {
+          d.treeReady = true
+          this.$nextTick(() => this.applyMenuTreeChecked(menus))
+        })
+      }).catch(err => {
+        this.$message.error(err.message || '加载菜单失败')
+      }).finally(() => {
+        d.loading = false
+      })
+    },
     /** 编辑菜单(管理端/销售端) */
     handleEditMenu(row, flag) {
       this.menuDialog = {
@@ -592,43 +667,78 @@ export default {
         companyId: row.id,
         companyName: row.tenantName,
         treeData: [],
+        treeReady: false,
         checkedKeys: [],
-        allMenuIds: [],
-        loading: true,
+        checkedKeySet: null,
+        loading: false,
         submitting: false
       }
-      getTenantMenuTree(row.id, 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)
-            })
+    },
+    /** 模块定价 */
+    handleModulePricing(row) {
+      this.pricingDialog = {
+        visible: true,
+        tenantId: row.id,
+        tenantName: row.tenantName,
+        loading: true,
+        submitting: false,
+        modules: []
+      }
+      // 从收费配置API获取启用的模块列表(排除手拨外呼serviceType=6)
+      Promise.all([
+        listServiceCost(),
+        listTrafficPricing({ tenantId: row.id, pageSize: 100 })
+      ]).then(([costRes, pricingRes]) => {
+        const costList = (costRes.data || []).filter(c => c.enabled === 1 && c.serviceType !== 6)
+        const tenantPricings = pricingRes.rows || []
+        const pricingMap = {}
+        tenantPricings.forEach(p => { pricingMap[p.serviceType] = p })
+        this.pricingDialog.modules = costList.map(c => {
+          const existing = pricingMap[c.serviceType]
+          return {
+            serviceType: c.serviceType,
+            moduleName: c.configName,
+            unit: c.feeUnit,
+            globalPrice: c.feeStandard,
+            globalCost: c.platformCost,
+            price: existing ? existing.price : null,
+            costPrice: existing ? existing.costPrice : null,
+            status: existing ? existing.status : 1,
+            id: existing ? existing.id : null
           }
-          walk(menus)
-          this.menuDialog.checkedKeys = checkedIds
-          this.menuDialog.allMenuIds = allIds
-          // 等tree渲染后捕获级联展开的实际勾选状态作为基准
-          this.$nextTick(() => {
-            const tree = this.$refs.menuTree
-            if (tree) {
-              this.menuDialog.checkedKeys = tree.getCheckedKeys().concat(tree.getHalfCheckedKeys())
-            }
-          })
+        })
+        this.pricingDialog.loading = false
+      }).catch(() => {
+        this.pricingDialog.loading = false
+        this.$message.error('加载定价数据失败')
+      })
+    },
+    /** 提交模块定价 */
+    submitModulePricing() {
+      this.pricingDialog.submitting = true
+      const tenantId = this.pricingDialog.tenantId
+      const promises = this.pricingDialog.modules.map(m => {
+        const data = {
+          tenantId: tenantId,
+          serviceType: m.serviceType,
+          price: m.price,
+          costPrice: m.costPrice,
+          status: m.status
+        }
+        if (m.id) {
+          data.id = m.id
+          return updateTrafficPricing(data)
         } else {
-          this.$message.error(res.msg || '加载菜单失败')
+          return addTrafficPricing(data)
         }
+      })
+      Promise.all(promises).then(() => {
+        this.$message.success('模块定价保存成功')
+        this.pricingDialog.visible = false
       }).catch(() => {
-        this.$message.error('加载菜单失败')
+        this.$message.error('部分定价保存失败,请重试')
       }).finally(() => {
-        this.menuDialog.loading = false
+        this.pricingDialog.submitting = false
       })
     },
     /** 模块定价 */
@@ -704,11 +814,10 @@ export default {
       if (!tree) return
       // 级联模式:纳入全选+半选节点作为当前勾选状态
       const currentChecked = tree.getCheckedKeys().concat(tree.getHalfCheckedKeys())
-      const originalChecked = this.menuDialog.checkedKeys
-      // selected = 新勾选的(当前有,原来没有)
-      const selected = currentChecked.filter(id => !originalChecked.includes(id))
-      // unSelected = 新取消的(原来有,当前没有)
-      const unSelected = originalChecked.filter(id => !currentChecked.includes(id))
+      const currentSet = new Set(currentChecked)
+      const originalSet = this.menuDialog.checkedKeySet || new Set(this.menuDialog.checkedKeys)
+      const selected = currentChecked.filter(id => !originalSet.has(id))
+      const unSelected = this.menuDialog.checkedKeys.filter(id => !currentSet.has(id))
       if (selected.length === 0 && unSelected.length === 0) {
         this.$message.info('菜单无变化')
         this.menuDialog.visible = false
@@ -740,5 +849,18 @@ 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; }
+.menu-tree-scroll {
+  min-height: 120px;
+  max-height: 420px;
+  overflow-y: auto;
+}
+.menu-tree-toolbar {
+  margin-bottom: 8px;
+  text-align: right;
+}
+.custom-tree-node {
+  display: flex;
+  align-items: center;
+  font-size: 13px;
+}
 </style>