Quellcode durchsuchen

新增用户等级

cldu vor 1 Woche
Ursprung
Commit
e2bb24f675

+ 20 - 0
src/api/customer/modules/user.js

@@ -88,4 +88,24 @@ export default {
   companySearch:params=>{
     return get('/company/search',params)
   },
+  // 用户等级列表
+  getUserLevelList:params=>{
+    return get('/users/level/page_list',params)
+  },
+  //用户等级详情
+  getUserLevelDetail:params=>{
+    return get('/users/level/detail',params)
+  },
+  // 删除用户等级
+  removeUserLevel:params=>{
+    return post('/users/level/remove',params)
+  },
+  // 添加用户等级
+  addUserLevel:params=>{
+    return post('/users/level/add',params)
+  },
+  // 编辑用户等级
+  editUserLevel:params=>{
+    return post('/users/level/edit',params)
+  },
 };

BIN
src/assets/imgs/icons/status_tip.png


+ 24 - 0
src/router/modules/customer.js

@@ -103,6 +103,30 @@ export default[
           fromPath:'/customer/potentialUserList'
         },
       },
+      {
+        path:'user_level_list',
+        component:()=>import('@/views/customer/UserLevelList.vue'),
+        name:"customerLevelList",
+        meta:{
+          title:'客户等级管理'
+        },
+      },
+      {
+        path:'user_level_add',
+        component:()=>import('@/views/customer/UserLevelEdit.vue'),
+        name:"customerLevelAdd",
+        meta:{
+          title:'添加等级'
+        },
+      },
+      {
+        path:'user_level_edit',
+        component:()=>import('@/views/customer/UserLevelEdit.vue'),
+        name:"customerLevelEdit",
+        meta:{
+          title:'编辑等级'
+        },
+      },
     ]
   }
 ]

+ 7 - 0
src/utils/buttonConfig.js

@@ -17,6 +17,13 @@ export const customerManageBtn={
     trial_list:'trial:list', // 查看列表
     trial_convert:'trial:convert', // 转正式
     trial_delete:'trial:delete', // 删除
+
+    /* 客户等级管理 */
+    userLevel_list:'userLevel:list',//查看列表
+    userLevel_add:'userLevel:add',//新增等级
+    userLevel_edit:'userLevel:edit',//编辑等级
+    userLevel_remove:'userLevel:remove',//删除等级
+    userLevel_detail:'userLevel:detail',//查看权限
 }
 
 /* 系统管理 */

+ 44 - 3
src/views/customer/CompanyEdit.vue

@@ -4,6 +4,7 @@ import { useRoute, useRouter } from 'vue-router'
 import ContactTable from './components/ContactTable.vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import OperationRecord from './components/OperationRecord.vue'
+import levelPermission from './components/levelPermission.vue'
 
 const route = useRoute()
 const router = useRouter()
@@ -26,6 +27,7 @@ const formRules = {
   sellerId: [{ required: true, message: '营业部不能为空', trigger: 'blur' }],
   province: [{ required: true, message: '省份不能为空', trigger: 'blur' }],
   city: [{ required: true, message: '城市不能为空', trigger: 'blur' }],
+  UserLevelId: [{ required: true, message: '用户等级不能为空', trigger: 'blur' }],
 }
 const formState = reactive({
   CompanyName: '',
@@ -36,8 +38,20 @@ const formState = reactive({
   city: '',
   CityId: '',
   ProvinceId: '',
+  UserLevelId:'',
 })
 
+const userLevelList = ref([]) // 用户等级列表
+async function getUserLevelList(){
+   const res = await apiCustomerUser.getUserLevelList({
+      PageSize:99999,
+      CurrentIndex:1,
+      OnlyEnabled:true,
+   });
+   if(res.Ret != 200) return
+   userLevelList.value = res.Data.List || [];
+};
+getUserLevelList();
 
 const checkedIds = ref([])
 const AuthStatus = ref(1)
@@ -57,6 +71,7 @@ function getUserInfo() {
       formState.city = Detail.City
       formState.ProvinceId = Detail.ProvinceId
       formState.CityId = Detail.CityId
+      formState.UserLevelId = Detail.UserLevelId || ''
     }
   })
 }
@@ -97,7 +112,7 @@ function checkAddContent(msg) {
 
 async function handleSave(type) {
   await formRef.value.validate()
-  const {CompanyName, CreditCode, Industry, sellerId, province, city, ProvinceId, CityId} = formState
+  const {CompanyName, CreditCode, Industry, sellerId, province, city, ProvinceId, CityId, UserLevelId} = formState
   let params = {
     CompanyName,
     CreditCode,
@@ -107,6 +122,7 @@ async function handleSave(type) {
     City: city,
     ProvinceId: parseInt(ProvinceId),
     CityId: parseInt(CityId),
+    UserLevelId,
   }
   if (CompanyId.value) {
     params.CompanyId = Number(CompanyId.value)
@@ -135,6 +151,12 @@ const contactTableRef = ref(null)
 function handleChangeList() {
   contactTableRef.value.getUserList();
 }
+
+const showUserPermission = ref(false);
+function viewUserPermission(){
+  if (!formState.UserLevelId) return ElMessage.warning('请先选择用户等级');
+  showUserPermission.value = true;
+}
 </script>
 
 <template>
@@ -191,8 +213,24 @@ function handleChangeList() {
             </el-form-item>
           </div>
           <div class="flex form-tr-box">
-            <el-form-item label="权限" prop="CompanyId">
-              {{ AuthStatus === 1 ? '可查看所有报告' : '无报告查看权限' }}
+            <el-form-item label="用户等级" prop="UserLevelId">
+              <el-select
+                v-model="formState.UserLevelId"
+                filterable
+                placeholder="请选择客户等级"
+                clearable
+                style="width: 100%;"
+              >
+                <el-option
+                  v-for="item in userLevelList"
+                  :key="item.UserLevelId"
+                  :label="item.LevelName"
+                  :value="item.UserLevelId"
+                />
+              </el-select>
+              <div style="width: 100%;text-align: right;">
+                <el-button type='primary' link @click="viewUserPermission" style="text-decoration: underline;">查看权限</el-button>
+              </div>
             </el-form-item>
           </div>
           <div class="flex form-tr-box color" v-if="route.path !== '/customer/companyDetail'">
@@ -227,6 +265,9 @@ function handleChangeList() {
       />
     </div>
   </div>
+  <levelPermission 
+     v-model:show="showUserPermission" 
+     :levelId="formState.UserLevelId"/>
 </template>
 
 <style lang="scss" scoped>

+ 46 - 6
src/views/customer/UserEdit.vue

@@ -3,8 +3,9 @@ import { apiSystemCommon } from '@/api/system'
 import { apiCustomerUser } from '@/api/customer'
 import { isMobileNo, patternEmail } from '@/utils/common'
 import { useRoute, useRouter } from 'vue-router'
-import { dayjs, ElMessageBox } from 'element-plus'
+import { dayjs, ElMessageBox,ElMessage } from 'element-plus'
 import OperationRecord from './components/OperationRecord.vue'
+import levelPermission from './components/levelPermission.vue'
 
 const route = useRoute()
 const router = useRouter()
@@ -44,6 +45,7 @@ const formRules = {
     }, trigger: 'blur'
   }],
   sellerId: [{ required: true, message: '请选择销售', trigger: 'change' }],
+  UserLevelId: [{ required: true, message: '用户等级不能为空', trigger: 'blur' }],
 }
 const showPhoneVal=computed({
   get(){
@@ -59,11 +61,23 @@ const formState = reactive({
   areaCode: '86',
   phone: '',
   sellerId: '',
-  isEnabled: false
-
+  isEnabled: false,
+  UserLevelId:'',
 })
 const AuthStatus = ref(1)
 
+const userLevelList = ref([]) // 用户等级列表
+async function getUserLevelList(){
+   const res = await apiCustomerUser.getUserLevelList({
+      PageSize:99999,
+      CurrentIndex:1,
+      OnlyEnabled:true,
+   });
+   if(res.Ret != 200) return
+   userLevelList.value = res.Data.List || [];
+};
+getUserLevelList();
+
 function getUserInfo() {
   if (!userId.value) return
   apiCustomerUser.userInfo({
@@ -71,13 +85,13 @@ function getUserInfo() {
   }).then(res => {
     if (res.Ret === 200) {
       const Detail = res.Data
-      console.log(Detail);
       AuthStatus.value = Detail.AuthStatus
       formState.realName = Detail.RealName
       formState.areaCode = Detail.AreaCode
       formState.phone = Detail.Mobile
       formState.sellerId = Detail.SellerId
       formState.isEnabled = Detail.Status ? true : false
+      formState.UserLevelId = Detail.UserLevelId || ''
     }
   })
 }
@@ -100,6 +114,7 @@ async function handleSave(type) {
     AreaCode: formState.areaCode,
     Mobile: formState.phone,
     SellerId: formState.sellerId,
+    UserLevelId:formState.UserLevelId,
   }
   if (userId.value) {
     params.UserId = Number(userId.value)
@@ -136,6 +151,12 @@ async function handleSave(type) {
 
 }
 
+const showUserPermission = ref(false);
+function viewUserPermission(){
+  if (!formState.UserLevelId) return ElMessage.warning('请先选择用户等级');
+  showUserPermission.value = true;
+}
+
 </script>
 
 <template>
@@ -196,8 +217,24 @@ async function handleSave(type) {
           </el-form-item>
         </div>
         <div class="flex form-tr-box">
-            <el-form-item label="权限">
-              {{ AuthStatus === 1 ? '可查看所有报告' : '无报告查看权限' }}
+            <el-form-item label="用户等级" prop="UserLevelId">
+              <el-select
+                v-model="formState.UserLevelId"
+                filterable
+                placeholder="请选择客户等级"
+                clearable
+                style="width: 100%;"
+              >
+                <el-option
+                  v-for="item in userLevelList"
+                  :key="item.UserLevelId"
+                  :label="item.LevelName"
+                  :value="item.UserLevelId"
+                />
+              </el-select>
+              <div style="width: 100%;text-align: right;">
+                  <el-button type='primary' link  @click="viewUserPermission" style="text-decoration: underline;">查看权限</el-button>
+              </div>
             </el-form-item>
           </div>
       </el-form>
@@ -219,6 +256,9 @@ async function handleSave(type) {
       <OperationRecord v-else/>
     </div>
   </div>
+  <levelPermission 
+     v-model:show="showUserPermission" 
+     :levelId="formState.UserLevelId"/>
 </template>
 
 <style lang="scss" scoped>

+ 317 - 0
src/views/customer/UserLevelEdit.vue

@@ -0,0 +1,317 @@
+<script setup>
+import { nextTick, reactive, ref } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import { apiCustomerUser } from '@/api/customer'
+
+const router = useRouter();
+const route = useRoute();
+const treeRef = ref()
+const ruleFormRef = ref()
+const levelId = route.query.levelId ? +route.query.levelId : 0;
+
+const formData = reactive({
+  LevelRank:'',
+  LevelName:'',
+  State:1,
+});
+const dataLoading = ref(false);
+
+const checkBoxObj = reactive({
+    permissionChecked:false,
+    permissionIndeterminate:false,
+})
+
+const rules = reactive({
+  LevelRank: [
+      { required: true, message: '请输入等级', trigger: 'change' },
+      {
+        validator: (rule, value, callback) => {
+          const reg = /^[1-9][0-9]?$/
+          if (!reg.test(value)) {
+            callback(new Error('等级必须为2位数以内的正整数'))
+          } else {
+            callback()
+          }
+        },
+        trigger: ['change']
+      }
+  ],
+  LevelName: [{ required: true, message: '请输入等级名称', trigger: 'change' }],
+  State:[{ required: true, message: '请选择状态', trigger: 'change' }],
+})
+
+const treeData = ref([])
+const defaultProps = {
+  children: 'Child',
+  label: 'ClassifyName'
+}
+
+function getAllLeafNodeIds(nodes) {
+  const ids = []
+  const traverse = (nodeList) => {
+    nodeList.forEach((node) => {
+      if (!node.Child || node.Child.length === 0) {
+        ids.push(node.Id)
+      } else {
+        traverse(node.Child)
+      }
+    })
+  }
+  traverse(nodes)
+  return ids
+}
+const handlePermissionCheckboxChange = (val) => {
+    if(!treeRef.value) return;
+    if (val) {
+      // 全选所有节点
+      const allNodeIds = getAllLeafNodeIds(treeData.value)
+      treeRef.value.setCheckedKeys(allNodeIds)
+      checkBoxObj.permissionIndeterminate = false
+    } else {
+      // 清空所有选择
+      treeRef.value.setCheckedKeys([])
+      checkBoxObj.permissionIndeterminate = false
+    }
+};
+async function getLevelInfo(){
+    if(!levelId) return;
+    const res = await apiCustomerUser.getUserLevelDetail({
+      UserLevelId:levelId,
+    });
+    if(res.Ret !== 200) return;
+    formData.LevelRank = res.Data.LevelRank;
+    formData.LevelName = res.Data.LevelName;
+    formData.State = res.Data.State;
+    treeRef.value.setCheckedKeys(res.Data.ClassifyIds || []);
+    await nextTick();
+    onTreeCheck();
+};
+
+async function getClassifyList(){
+    dataLoading.value = true;
+    const res = await apiCustomerUser.usersReportClassifyTree();
+    dataLoading.value = false;
+    if(res.Ret !== 200) return;
+    treeData.value = res.Data || [];
+    await nextTick();
+    if(levelId) getLevelInfo();
+};
+getClassifyList();
+
+const onTreeCheck = async () => {
+  await nextTick();
+  if(!treeRef.value) return;
+  const checkedKeys = treeRef.value.getCheckedKeys(true)
+  const allLeafKeys = getAllLeafNodeIds(treeData.value)
+  if (checkedKeys.length === 0) {
+    checkBoxObj.permissionChecked = false
+    checkBoxObj.permissionIndeterminate = false
+  } else if (checkedKeys.length === allLeafKeys.length) {
+    checkBoxObj.permissionChecked = true
+    checkBoxObj.permissionIndeterminate = false
+  } else {
+    checkBoxObj.permissionChecked = false
+    checkBoxObj.permissionIndeterminate = true
+  }
+}
+
+const saveHandle = async () => {
+    ruleFormRef.value && ruleFormRef.value.validate(async (valid) => {
+        if (!valid) return;
+        const permissions = treeRef.value.getCheckedKeys(true);
+        if(!permissions || permissions.length === 0) return ElMessage.error('请选择阅读权限');
+        let params = {
+            LevelRank: +formData.LevelRank,
+            LevelName: formData.LevelName,
+            State: +formData.State,
+            ClassifyIds: permissions
+        };
+        if(levelId) {
+            params.UserLevelId = +levelId;
+        }
+        const res = levelId ? await apiCustomerUser.editUserLevel(params) : await apiCustomerUser.addUserLevel(params);
+        if(res.Ret != 200) return;
+        ElMessage.success(levelId ? '编辑成功' : '添加成功');
+        router.back();
+    })
+};
+</script>
+
+<template>
+    <el-card v-loading="dataLoading">
+        <div class="user-level-edit-page">
+            <el-form
+                ref="ruleFormRef"
+                :model="formData"
+                :rules="rules"
+                label-width="auto"
+            >
+                <el-form-item label="等级" prop="LevelRank" style="width: 360px;">
+                    <el-input v-model="formData.LevelRank" size="large" placeholder="请输入等级会员,正整数"/>
+                </el-form-item>
+                <el-form-item label="等级名称" prop="LevelName" style="width: 360px;">
+                    <el-input v-model="formData.LevelName" size="large" placeholder="请输入等级名称"/>
+                </el-form-item>
+                <el-form-item>
+                    <template #label>
+                        <span class="red-star">*</span> 阅读权限
+                    </template>
+                    <div v-if="treeData && treeData.length" class="permission-checkbox">
+                        <el-checkbox
+                        v-model="checkBoxObj.permissionChecked"
+                        :indeterminate="checkBoxObj.permissionIndeterminate"
+                        @change="handlePermissionCheckboxChange"
+                        >全选</el-checkbox>
+                        <el-tree
+                            ref="treeRef"
+                            :data="treeData"
+                            show-checkbox
+                            node-key="Id"
+                            :default-expand-all="false"
+                            :props="defaultProps"
+                            @check="onTreeCheck"
+                            style="max-height: 50vh;overflow-y: auto;overflow-x: hidden;"
+                        />
+                    </div>
+                </el-form-item>
+                <el-form-item label="状态" prop="State">
+                    <template #label>
+                        <span style="display: flex;align-items: center;">
+                          <span>状态</span>
+                          <el-tooltip
+                            effect="dark"
+                            content="禁用后,设置用户等级时,不支持配置该等级;已配置等级的用户,禁用后,不支持查看该等级所拥有权限的报告。"
+                            placement="top"
+                          >
+                          <img src="@/assets/imgs/icons/status_tip.png" style="width: 14px;height: 14px;vertical-align: middle;margin-left: 4px;">
+                          </el-tooltip>
+                        </span>
+                    </template>
+                    <el-radio-group v-model="formData.State">
+                        <el-radio :value="1">启用</el-radio>
+                        <el-radio :value="0">禁用</el-radio>
+                    </el-radio-group>
+                </el-form-item>
+            </el-form>
+        </div>
+        <div style="text-align: center;">
+            <el-button type="primary" size="large" style="width: 120px;margin-right: 30px;" @click="saveHandle">保存</el-button>
+            <el-button size="large" style="width: 120px;" @click="router.back()">取消</el-button>
+        </div>
+    </el-card>
+</template>
+
+<style lang="scss" scoped>
+.user-level-edit-page{
+    position: relative;
+    width: 100%;
+    height: calc(100vh - 200px);
+    padding: 20px 40px;
+    overflow: auto;
+    box-sizing: border-box;
+    .red-star{
+        color: var(--el-color-danger);
+        content: "*";
+        margin-right: 4px;
+    }
+}
+</style>
+<style lang="scss">
+.user-level-edit-page{
+   .el-form-item {
+      margin-bottom: 30px;
+   }
+    .el-tree {
+        border-top: 1px solid #e5e7ed;
+        border-left: 1px solid #e5e7ed;
+        border-right: 1px solid #e5e7ed;
+        width: 99%;
+    .el-tree-node__label {
+      margin: 10px;
+    }
+
+    .el-tree-node__content {
+      min-width: 300px;
+      width: 300px;
+      white-space: normal;
+      box-sizing: border-box;
+    }
+
+    .el-tree-node {
+      .el-tree-node {
+        .el-tree-node__children {
+          width: 100%;
+        }
+
+        .el-tree-node {
+          &:not(:first-child) {
+            border-top: 1px solid #e5e7ed;
+          }
+          .el-tree-node__content {
+            border-right: 1px solid #e5e7ed;
+          }
+        }
+      }
+    }
+
+    .el-tree-node__content {
+      padding: 5px 10px !important;
+      height: auto;
+    }
+
+    > .el-tree-node {
+      padding: 0 !important;
+      display: flex;
+      border-bottom: 1px solid #e5e7ed;
+
+      > .el-tree-node__children {
+        width: 100%;
+
+        > .el-tree-node {
+          &:not(:first-child) {
+            border-top: 1px solid #e5e7ed;
+          }
+
+          > .el-tree-node__content {
+            border-left: 1px solid #e5e7ed;
+            border-right: 1px solid #e5e7ed;
+          }
+        }
+      }
+    }
+
+    .el-tree-node__children {
+      display: flex;
+      flex-direction: column;
+
+      .el-tree-node {
+        display: flex;
+        flex: 1;
+        padding: 0px !important;
+
+        .el-tree-node__content {
+          border-bottom: none;
+
+          .custom-tree-node {
+            height: 24px;
+            display: flex;
+            align-items: center;
+
+            .tree-btn {
+              margin-left: 10px;
+              display: none;
+            }
+
+            .el-button {
+              padding: 0px !important;
+              border-radius: 4px;
+              background: #363554;
+              color: #ffffff;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 180 - 0
src/views/customer/UserLevelList.vue

@@ -0,0 +1,180 @@
+<script setup>
+import { computed, reactive, ref } from 'vue';
+import { useRouter } from 'vue-router';
+import { ElMessage,ElMessageBox } from 'element-plus';
+import { apiCustomerUser } from '@/api/customer'
+import levelPermission from './components/levelPermission.vue';
+
+const router = useRouter();
+const tableMeta = reactive({
+    page:1,
+    pageSize:5,
+    totals:0,
+})
+const loading = ref(false);
+const showPermissionDia = ref(false);
+const tableData = ref([]);
+
+const getTableData = async () => {
+   if(loading.value) return;
+   loading.value = true;
+   const res = await apiCustomerUser.getUserLevelList({
+        PageSize:tableMeta.pageSize,
+        CurrentIndex:tableMeta.page,
+        OnlyEnabled:false,
+   });
+   loading.value = false;
+   if(res.Ret != 200) return;
+   tableData.value = res.Data.List || [];
+   tableMeta.totals = res.Data.Paging.Totals || 0;
+};
+
+const handlePageChange = (page) => {
+   if(loading.value) return;
+   tableMeta.page = page;
+   getTableData();
+};
+
+
+const tableColumn = computed(() => ([
+    {
+        prop:'LevelRank',
+        label:'等级',
+        width:120,
+    },
+    {
+        prop:'LevelName',
+        label:'等级名称',
+    },
+    {
+        prop:'State',
+        label:'状态',
+    },
+    {
+        prop:'SysUserRealName',
+        label:'创建人',
+    },
+    {
+        prop:'CreateTime',
+        label:'创建时间',
+    }
+]))
+
+const activeLevelId = ref(0);
+const openPermission = (row) => {
+   activeLevelId.value = row.UserLevelId || 0;
+   showPermissionDia.value = true;
+}
+
+const editLevel = (row) => {
+    router.push({
+        path:'/customer/user_level_edit',
+        query:{
+          levelId: row.UserLevelId || 0,
+        }
+    })
+}
+
+const deleteLevel = (row) => {
+    ElMessageBox.confirm(
+      '删除后不可恢复,是否确认删除?',
+      '提示',
+      { type: 'warning', }
+    ).then(() => {
+        apiCustomerUser.removeUserLevel({
+            UserLevelId: row.UserLevelId || 0,
+        }).then(res => {
+            if(res.Ret != 200) return;
+            ElMessage.success('删除成功');
+            getTableData();
+        }).catch(err => {
+            ElMessage.error('删除失败,请稍后再试');
+        });
+    }).catch()
+}
+
+getTableData();
+</script>
+
+<template>
+    <el-card>
+        <div class="user-level-page">
+            <div style="margin-bottom: 20px;">
+                <el-button 
+                   v-if="hasPermission(permissionBtn.customerManageBtn.userLevel_add)" 
+                   type="primary" size="large" 
+                   @click="router.push('/customer/user_level_add')"
+                   style="width: 120px;">新增</el-button>
+            </div>
+
+            <el-table 
+               :header-cell-style="{ background: '#EBEEF5' }" 
+               v-if="hasPermission(permissionBtn.customerManageBtn.userLevel_list)" 
+               :data="tableData" 
+               border 
+               style="width: 100%" 
+               v-loading="loading">
+
+                <el-table-column 
+                    v-for="item in tableColumn" 
+                    :key="item.prop" 
+                    :prop="item.prop" 
+                    :label="item.label"
+                    :width="item.width"
+                    align="center"
+                >
+                  <template #default="{ row }">
+                    <span
+                      v-if="item.prop === 'State'"
+                      >{{ row.State ? "启用" : "禁用" }}</span
+                    >
+                    <span v-else>{{ row[item.prop] }}</span>
+                  </template>
+                </el-table-column>
+
+                <el-table-column  
+                    prop="handle" 
+                    label="操作"
+                    align="center"
+                >
+                    <template #default="{row}">
+                        <el-button type="primary" v-if="hasPermission(permissionBtn.customerManageBtn.userLevel_detail)" link @click="openPermission(row)">查看权限</el-button>
+                        <el-button type="primary" v-if="hasPermission(permissionBtn.customerManageBtn.userLevel_edit)" link @click="editLevel(row)">编辑</el-button>
+                        <el-button type="danger"  v-if="hasPermission(permissionBtn.customerManageBtn.userLevel_remove)" link @click="deleteLevel(row)">删除</el-button>
+                    </template>
+                </el-table-column>
+            </el-table>
+            <el-empty description="无查看权限" v-else/>
+
+            <el-pagination
+                v-if="hasPermission(permissionBtn.customerManageBtn.userLevel_list)"
+                background
+                layout="total,prev,pager,next,jumper"
+                :current-page="tableMeta.page"
+                :page-size="tableMeta.pageSize"
+                :total="tableMeta.totals"
+                @current-change="handlePageChange"
+                style="margin-top: 30px; justify-content: flex-end"
+                class="pagination"
+            />
+
+        </div>
+    </el-card>
+    <levelPermission :levelId="activeLevelId" v-model:show="showPermissionDia"/>
+</template>
+
+<style lang="scss" scoped>
+.user-level-page{
+    position: relative;
+    width: 100%;
+    min-height: calc(100vh - 180px);
+    overflow: hidden;
+    padding: 20px 10px;
+    .pagination {
+      position: absolute;
+      bottom: 0px;
+      right: 0;
+      justify-content: flex-end;
+    }
+}
+</style>

+ 42 - 5
src/views/customer/UserList.vue

@@ -14,7 +14,8 @@ const filterState = reactive({
   sellerId: [],
   createTime: [],
   SortField: 0,
-  SortRule: 0
+  SortRule: 0,
+  UserLevelId:'',
 })
 
 const tableColOpt = computed( ()=>{
@@ -32,6 +33,10 @@ const tableColOpt = computed( ()=>{
         label: '所属行业',
         key: 'Industry'
       },
+      {
+        label: '客户等级',
+        key: 'UserLevelName'
+      },
       {
         label: '客户地址',
         key: 'Address'
@@ -62,6 +67,10 @@ const tableColOpt = computed( ()=>{
         label: '营业部',
         key: 'SellerName'
       },
+      {
+        label: '客户等级',
+        key: 'UserLevelName'
+      },
       {
         label: '是否注册',
         key: 'IsRegistered',
@@ -114,7 +123,8 @@ async function getUserList() {
     EndTime: filterState.createTime ? filterState.createTime[1] : '',
     SortField:filterState.SortField,
     SortRule:filterState.SortRule,
-    UserStatus: 2
+    UserStatus: 2,
+    UserLevelId:filterState.UserLevelId || 0,
   }
 
   const res = radio.value === 1 ? await apiCustomerUser.companyPageList(parames) : await apiCustomerUser.userList(parames)
@@ -131,6 +141,18 @@ function handlePageChange(e) {
   getUserList()
 }
 
+const userLevelList = ref([]) // 用户等级列表
+async function getUserLevelList(){
+   const res = await apiCustomerUser.getUserLevelList({
+      PageSize:99999,
+      CurrentIndex:1,
+      OnlyEnabled:true,
+   });
+   if(res.Ret != 200) return
+   userLevelList.value = res.Data.List || [];
+};
+getUserLevelList();
+
 function changeUser() {
   getUserList()
 }
@@ -159,8 +181,6 @@ function handleTableSort(e) {
   handleFilterList()
 }
 function handleFilterList() {
-  console.log('handleFilterList', filterState.sellerId);
-  
   page.value = 1
   getUserList()
 }
@@ -193,7 +213,6 @@ async function handleChangecompany(row) {
     CompanyId: row.CompanyId,
   })
   if (res.Ret !== 200) return
-  console.log(row);
   row.AuthStatus === 1 ? ElMessage.success('禁用成功') : ElMessage.success('启用成功')
   getUserList()
 }
@@ -355,6 +374,24 @@ function handleGoReadTimes(e){
               @change="handleFilterList"
             />
           </div>
+          <div style="width: 235px">
+            <el-select
+              v-model="filterState.UserLevelId"
+              filterable
+              placeholder="请选择客户等级"
+              size="large"
+              clearable
+              style="width: 100%;"
+              @change="handleFilterList"
+            >
+              <el-option
+                v-for="item in userLevelList"
+                :key="item.UserLevelId"
+                :label="item.LevelName"
+                :value="item.UserLevelId"
+              />
+            </el-select>
+          </div>
           <div style="width: 235px">
             <el-date-picker
               style="width: 235px"

+ 187 - 0
src/views/customer/components/levelPermission.vue

@@ -0,0 +1,187 @@
+<script setup>
+import { ref, watch } from 'vue';
+import { apiCustomerUser } from '@/api/customer'
+import { ElMessage } from 'element-plus';
+const show = defineModel('show', { type: Boolean, default: false })
+const props = defineProps({
+    levelId:{
+        type:Number,
+        default:0,
+    },
+});
+watch(show, (newVal) => {
+    if(!newVal) return;
+    getClassifyList();
+});
+
+const handleClose = () => {
+    dataLoading.value = false;
+    classifyList.value = [];
+    tableData.value = [];
+}
+
+const dataLoading = ref(false);
+const classifyList = ref([]);
+async function getClassifyList(){
+    dataLoading.value = true;
+    try {
+        const res = await apiCustomerUser.usersReportClassifyTree();
+        if(res.Ret !== 200) return;
+        classifyList.value = res.Data || [];
+
+        const detailRes = await apiCustomerUser.getUserLevelDetail({
+            UserLevelId:props.levelId
+        });
+        if(detailRes.Ret !== 200) return;
+        dataLoading.value = false;
+        const filteredClassifyList = filterClassifyTreeByIds(classifyList.value, detailRes.Data.ClassifyIds || []);
+        classifyList.value = filteredClassifyList || [];
+        formatData();
+    } catch (error) {
+        dataLoading.value = false;
+        ElMessage.error('获取数据失败,请稍后重试');
+    }
+};
+   
+const tableData = ref([]);
+const filterClassifyTreeByIds = (tree, allowedIds) => {
+  const result = [];
+  tree.forEach(node => {
+    const newNode = { ...node };
+    if (newNode.Child && newNode.Child.length > 0) {
+      newNode.Child = filterClassifyTreeByIds(newNode.Child, allowedIds);
+    } else {
+      newNode.Child = [];
+    }
+
+    if (allowedIds.includes(newNode.Id) || newNode.Child.length > 0) {
+      result.push(newNode);
+    }
+  });
+
+  return result;
+};
+
+const formatData = () => {
+  const data = [];
+
+  classifyList.value.forEach(l1 => {
+    let l1RowCount = 0;
+    const l1StartIndex = data.length;
+
+    if (l1.Child && l1.Child.length > 0) {
+      l1.Child.forEach(l2 => {
+        let l2RowCount = 0;
+        const l2StartIndex = data.length;
+
+        if (l2.Child && l2.Child.length > 0) {
+          l2.Child.forEach(l3 => {
+            data.push({
+              level1: l1.ClassifyName,
+              level2: l2.ClassifyName,
+              level3: l3.ClassifyName,
+              rowspan1: 0,
+              rowspan2: 0
+            });
+            l2RowCount++;
+            l1RowCount++;
+          });
+        } else {
+          // 没有三级分类也要插入一行
+          data.push({
+            level1: l1.ClassifyName,
+            level2: l2.ClassifyName,
+            level3: '',
+            rowspan1: 0,
+            rowspan2: 0
+          });
+          l2RowCount++;
+          l1RowCount++;
+        }
+
+        // 给该组的第一个设置 rowspan2
+        data[l2StartIndex].rowspan2 = l2RowCount;
+      });
+    } else {
+      // 没有二级分类也要插入一行
+      data.push({
+        level1: l1.ClassifyName,
+        level2: '',
+        level3: '',
+        rowspan1: 1,
+        rowspan2: 1
+      });
+      l1RowCount++;
+    }
+
+    // 给该组的第一个设置 rowspan1
+    data[l1StartIndex].rowspan1 = l1RowCount;
+  });
+
+  tableData.value = data;
+};
+
+const mergeCells = ({ row, columnIndex }) => {
+  if (columnIndex === 0) {
+    return row.rowspan1 > 0
+      ? { rowspan: row.rowspan1, colspan: 1 }
+      : { rowspan: 0, colspan: 0 };
+  }
+
+  if (columnIndex === 1) {
+    return row.rowspan2 > 0
+      ? { rowspan: row.rowspan2, colspan: 1 }
+      : { rowspan: 0, colspan: 0 };
+  }
+
+  // 第三级分类不合并,默认显示
+  return { rowspan: 1, colspan: 1 };
+};
+</script>
+
+<template>
+    <el-dialog
+        v-model="show"
+        :close-on-click-modal="false"
+        :modal-append-to-body="false"
+        width="1000px"
+        top="10vh"
+        title="查看权限"
+        @close="handleClose"
+        class="level-permission-dialog"
+    >
+        <el-table 
+            :data="tableData" 
+            border 
+            style="width: 100%" 
+            :span-method="mergeCells" 
+            max-height="60vh" 
+            v-loading="dataLoading" 
+            :header-cell-style="{ background: '#EBEEF5' }">
+            <el-table-column prop="level1" label="一级分类" />
+            <el-table-column prop="level2" label="二级分类" />
+            <el-table-column prop="level3" label="三级分类" />
+        </el-table>
+        <div style="text-align: center;">
+            <el-button type="primary" class="btns" size="large" @click="show = false">知道了</el-button>
+        </div>
+    </el-dialog>
+</template>
+
+<style lang="scss" scoped>
+.btns{
+    margin: 20px auto 0;
+    width: 120px;
+}
+</style>
+<style lang="scss">
+.level-permission-dialog{
+  .el-dialog__body{
+    padding: 30px 60px 40px!important;
+  }
+  .el-dialog__title{
+    font-size: 16px;
+    font-weight: 400;
+  }
+}
+</style>