Kaynağa Gözat

新增渠道推广、分配应用需求

wangzhiqiang 4 hafta önce
ebeveyn
işleme
ac2e10f71b

+ 56 - 0
src/api/outBagModule.js

@@ -36,3 +36,59 @@ export function ditchUpdateOne(data) {
     })
 }
 
+// 新增渠道所属用户
+export function promoterUserAddOne(data) {
+    return request({
+        url: '/agent-service/promoterUser/addOne',
+        method: 'post',
+        data
+    })
+}
+
+// 分配推广人
+export function promoterUserAssignment(data) {
+    return request({
+        url: '/agent-service/promoterUser/assignment',
+        method: 'post',
+        data
+    })
+}
+
+//取消分配推广人
+export function promoterUserAssignmentDelete(data) {
+    return request({
+        url: '/agent-service/promoterUser/assignment/delete',
+        method: 'post',
+        data
+    })
+}
+
+// 删除渠道所属用户
+export function promoterUserDeleteOne(params) {
+    return request({
+        url: '/agent-service/promoterUser/deleteOne',
+        method: 'get',
+        params
+    })
+}
+
+// 查询渠道所属用户列表
+export function promoterUserQueryList(data) {
+    return request({
+        url: '/agent-service/promoterUser/queryList',
+        method: 'post',
+        data
+    })
+}
+
+// 修改渠道所属用户
+export function promoterUserUpdateOne(data) {
+    return request({
+        url: '/agent-service/promoterUser/updateOne',
+        method: 'post',
+        data
+    })
+}
+
+
+

+ 6 - 0
src/router/modules/outBagModule.js

@@ -14,6 +14,12 @@ const route = [
                 component: createNameComponent(() => import('@/views/main/outBagModule/channelTypeAdmin.vue')),
                 meta: { title: '出包渠道' },
             },
+            {
+                path: 'ChannelPromotion/index',
+                name: 'ChannelPromotion',
+                component: createNameComponent(() => import('@/views/main/outBagModule/channelPromotion.vue')),
+                meta: { title: '渠道推广' },
+            },
             {
                 path: 'appAdmin/index',
                 name: 'AppAdmin',

+ 28 - 1
src/router/permission.js

@@ -12,6 +12,9 @@ import FrontRoutes from './permission/front'
 /** 引入后端路由 */
 import getMenu from './permission/back'
 
+import Layout from '@/layout/index.vue'
+import { createNameComponent } from './createNode'
+
 /** 
  * @name 动态路由的权限新增,供登录后调用
  * @other 如果需要进行后端接口控制菜单的话,请在此拿到后端的菜单树与asyncRoutes对比,生成一个新的值
@@ -46,6 +49,30 @@ async function addRoutes() {
 export async function getAuthRoutes() {
   // 判断token是否存在,存在则调用添加路由的方法
   if (store.state.user.token) {
-    await addRoutes()
+    // 判断当前账号是否为推广人员
+    if(store.state.user.info.userType === 11) {
+      modules.push(...userModule)
+      router.addRoute(...userModule)
+    } else {
+      await addRoutes()
+    }
   }
 }
+
+//推广人路由权限
+const userModule = [
+    {
+        path: '/userModule',
+        redirect: '/userModule/userList/index',
+        component: Layout,
+        meta: { title: '用户模块', icon: 'iconfont icon-user' },
+        children: [
+            {
+                path: 'userList/index',
+                name: 'UserList',
+                component: createNameComponent(() => import('@/views/main/userModule/userList.vue')),
+                meta: { title: '用户列表' },
+            },  
+        ],
+    },
+]

+ 1 - 0
src/router/permission/backConfig.js

@@ -37,6 +37,7 @@ const riskModule = {
 
 const outBagModule = {
   channelTypeAdmin: createNameComponent(() => import('@/views/main/outBagModule/channelTypeAdmin.vue')),
+  channelPromotion: createNameComponent(() => import('@/views/main/outBagModule/channelPromotion.vue')),
   appAdmin: createNameComponent(() => import('@/views/main/outBagModule/appAdmin.vue')),
 }
 

+ 148 - 31
src/views/main/outBagModule/appAdmin.vue

@@ -2,60 +2,78 @@
   <div class="layout-container">
     <!-- 菜单栏 -->
     <From :form-items="dynamicFormItems" @formSubmitted="handleFormSubmitted" @formReset="handleFormReset"
-    :is_add_button="checkPermission('permission') ? '新增' : null" @addForm="edit" />
+      :is_add_button="checkPermission('permission') ? '新增' : null" @addForm="edit" />
 
     <!-- 表格 -->
     <div class="layout-container">
       <Table @getTableData="changeTableData" v-model:page="page" ref="table" :data="tableData"
         @selection-change="handleSelectionChange">
         <el-table-column prop="appId" label="应用ID" />
-        <el-table-column prop="appName" label="应用名称" width="160" />
+        <el-table-column prop="appName" label="应用名称" width="150" />
         <el-table-column prop="superiorName" label="所属应用" width="120" />
         <el-table-column prop="ditchName" label="渠道名称" width="140">
           <template #default="scope">
-            <el-tag
-              type="success"
-              effect="dark"
-              v-if="scope.row.ditchName"
-            >
+            <el-tag type="success" effect="dark" v-if="scope.row.ditchName">
               {{ scope.row.ditchName }}
             </el-tag>
           </template>
         </el-table-column>
-        <el-table-column prop="nickName" label="所属用户" width="90" />
+        <el-table-column prop="nickName" label="所属用户" width="140" />
         <el-table-column prop="appType" label="应用类型" width="90">
           <template #default="scope">
             {{ getDictionaryName("app_type", scope.row.appType) }}
           </template>
         </el-table-column>
-        <el-table-column prop="apkUrl" label="下载链接"  width="180">
+        <el-table-column prop="apkUrl" label="下载链接" width="180">
           <template #default="scope">
             <a v-if="scope.row.apkUrl" :href="scope.row.apkUrl" target="_blank">{{ scope.row.appName }}.apk</a>
           </template>
         </el-table-column>
-        <el-table-column prop="qrCode" width="140" label="二维码">
+        <el-table-column prop="qrCode" width="130" label="二维码">
           <template #default="scope">
-            <el-image v-if="scope.row.qrCode" style="width: 100px; height: 100px;" :z-index="99999" :src="scope.row.qrCode" fit="fill"
-              :preview-src-list="[scope.row.qrCode]" preview-teleported="true" />
+            <el-image v-if="scope.row.qrCode" style="width: 100px; height: 100px;" :z-index="99999"
+              :src="scope.row.qrCode" fit="fill" :preview-src-list="[scope.row.qrCode]" preview-teleported="true" />
           </template>
         </el-table-column>
-        <el-table-column prop="updateTips" label="更新提示"  width="140" />
-        <el-table-column prop="enabled" label="是否启用"  width="90">
+        <el-table-column prop="updateTips" label="更新提示" width="140" />
+        <el-table-column prop="enabled" label="是否启用" width="90">
           <template #default="scope">
             {{ getDictionaryName("enabled", scope.row.enabled) }}
           </template>
         </el-table-column>
-        <el-table-column label="操作" width="100" v-permission="'permission'">
+        <el-table-column prop="enabled" label="推广人" width="90">
+          <template #default="scope">
+            <div>
+              <div>{{ scope.row.promoterUserLoginName }}</div>
+              <div v-if="scope.row.promoterUserShareRate">{{ scope.row.promoterUserShareRate }}%</div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="130" v-permission="'permission'">
           <template #default="scope">
             <div class="button">
-              <el-link class="button-item" type="primary" style="margin-bottom: 5px" @click="edit(scope.row)">
-                编辑
-              </el-link>
-              <el-popconfirm placement="left" title="确认删除该数据?" @confirm="removeApp(scope.row)">
-                <template #reference>
-                  <el-link class="button-item" style="margin-bottom: 5px" type="warning">删除</el-link>
-                </template>
-              </el-popconfirm>
+              <div>
+                <el-link class="button-item" type="primary" style="margin-bottom: 5px" @click="edit(scope.row)">
+                  编辑
+                </el-link>
+                <el-popconfirm placement="left" title="确认删除该数据?" @confirm="removeApp(scope.row)">
+                  <template #reference>
+                    <el-link class="button-item" style="margin-bottom: 5px" type="warning">删除</el-link>
+                  </template>
+                </el-popconfirm>
+              </div>
+              <div>
+                <el-link class="button-item" type="primary" style="margin-bottom: 5px"
+                  @click="assignPromoter(scope.row)">
+                  分配
+                </el-link>
+                <el-popconfirm v-if="scope.row.promoterUserId" placement="left" title="确认取消该应用的分配?"
+                  @confirm="closePromoter(scope.row)">
+                  <template #reference>
+                    <el-link class="button-item" style="margin-bottom: 5px" type="warning">取消分配</el-link>
+                  </template>
+                </el-popconfirm>
+              </div>
             </div>
           </template>
         </el-table-column>
@@ -84,7 +102,8 @@
           <el-col :span="10">
             <el-form-item label="出包渠道:" required prop="ditchId">
               <el-select v-model="formEdit.ditchId">
-                <el-option v-for="item in ditchListData" :key="item.ditchId" :value="item.ditchId" :label="item.ditchName" />
+                <el-option v-for="item in ditchListData" :key="item.ditchId" :value="item.ditchId"
+                  :label="item.ditchName" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -109,6 +128,21 @@
         </el-form-item>
       </el-form>
     </Layer>
+
+    <!-- 分配推广人 -->
+    <Layer :layer="layer1" @confirm="submit1(ruleForm1)" @close="layer1.show = false">
+      <el-form :model="formEdit1" :rules="rules1" ref="ruleForm1" label-width="130px" style="margin-right: 30px">
+        <el-form-item label="推广人:" required prop="userId">
+          <el-select v-model="formEdit1.userId" style="width: 100%">
+            <el-option v-for="item in promoterList" :key="item.userId" :value="item.userId" :label="item.nickName" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="分成比率:" required prop="shareRate">
+          <el-input-number v-model="formEdit1.shareRate" :precision="2" :step="0.1" :min="0" :max="100" />
+          <text style="margin-left: 5px;font-size: 18px;">%</text>
+        </el-form-item>
+      </el-form>
+    </Layer>
   </div>
 </template>
 <script setup>
@@ -125,6 +159,7 @@ import From from "@/components/from/index.vue";
 import Table from "@/components/table/index.vue";
 import Layer from "@/components/layer/index.vue";
 import { usePermission } from '@/hooks/usePermission.js'
+import { promoterUserAssignment, promoterUserAssignmentDelete, promoterUserQueryList } from "@/api/outBagModule.js";
 
 const { checkPermission } = usePermission()
 const { loadDictData, getDictionaryName } = useGetDictList();
@@ -152,6 +187,7 @@ onBeforeMount(() => {
   getList();
   getDitchList();
   getIssuedAppList()
+  getPromoterList()
 });
 
 
@@ -190,7 +226,7 @@ const settingData = () => {
 };
 // 获取出包渠道
 const ditchListData = ref([])
-const getDitchList = async() => {
+const getDitchList = async () => {
   let res = await ditchList({
     page: 1,
     limit: 999,
@@ -198,7 +234,7 @@ const getDitchList = async() => {
   ditchListData.value = res.data
 }
 const issuedAppData = ref([])
-const getIssuedAppList = async ()=>{
+const getIssuedAppList = async () => {
   let res = await issuedApps({})
   issuedAppData.value = res.data
 }
@@ -215,10 +251,12 @@ const getList = async () => {
 };
 
 const changeTableData = (type) => {
-    formSearch.value.page = type ? 1 : page.pageNum;
-    formSearch.value.limit = page.pageSize;
-    // 分页切换
-    getList();
+  console.log('type :===>>', type);
+  console.log(' page.pageNum:===>>', page.pageNum);
+  formSearch.value.page = type ? 1 : page.pageNum;
+  formSearch.value.limit = page.pageSize;
+  // 分页切换
+  getList();
 };
 
 
@@ -347,6 +385,85 @@ const submit = async (formEl) => {
   });
 };
 
+// #region 分配推广人
+
+// 获取推广人列表
+const promoterList = ref([])
+const getPromoterList = async () => {
+  const res = await promoterUserQueryList({ page: 1, limit: 999 })
+  promoterList.value = res.data
+}
+
+// 弹窗
+const layer1 = ref({
+  show: false,
+  title: "分配推广人",
+  showButton: true,
+  width: "30vw",
+  edit: false,
+});
+const formEdit1 = ref({
+  appId: "",
+  ditchId: 0,
+  endTime: undefined,
+  shareRate: "", //分成比率
+  startTime: undefined,
+  userId: "", //推广人
+});
+
+const assignPromoter = (row) => {
+  ruleForm1.value?.resetFields();
+  formEdit1.value = {}
+
+  formEdit1.value.appId = row.appId
+  formEdit1.value.ditchId = row.ditchId
+  layer1.value.show = true;
+};
+
+const ruleForm1 = ref(null);
+
+const rules1 = reactive({
+  userId: [
+    { required: true, message: "请选择推广人", trigger: "change" },
+  ],
+  shareRate: [
+    { required: true, message: "请输入分成比率", trigger: "blur" },
+  ],
+});
+
+const submit1 = async (formEl) => {
+  await formEl.validate(async (valid, fields) => {
+    if (valid) {
+      console.log('数据 :===>>', formEdit1.value);
+      formEdit1.value.shareRate = String(formEdit1.value.shareRate)
+      await promoterUserAssignment({ ...formEdit1.value }).then(() => {
+        ElMessage.success('分配成功')
+        layer1.value.show = false
+        getList();
+      }).catch(() => {
+        ElMessage.error('分配失败,请重试!')
+      })
+    } else {
+      console.log("error submit!", fields);
+    }
+  });
+};
+
+//取消分配
+const closePromoter = async (row) => {
+  await promoterUserAssignmentDelete({
+    appId: row.appId,
+    userId: row.promoterUserId
+  }).then(() => {
+    ElMessage.success('取消分配成功')
+    getList();
+  }).catch(() => {
+    ElMessage.error('取消分配失败,请重试!')
+  })
+}
+
+// #endregion
+
 // 删除
 const removeApp = async (row) => {
   await delApp({ appId: row.appId }).then((res) => {
@@ -374,7 +491,7 @@ const removeApp = async (row) => {
 
   .button {
     display: flex;
-    // flex-direction: column;
+    flex-direction: column;
 
     .button-item {
       margin: 4px;

+ 487 - 0
src/views/main/outBagModule/channelPromotion.vue

@@ -0,0 +1,487 @@
+<template>
+    <div class="content">
+        <el-card class="right_card">
+            <div class="container">
+                <!-- 菜单栏 -->
+                <From class="from" :form-items="dynamicFormItems" @formSubmitted="handleFormSubmitted"
+                    @formReset="handleFormReset" :is_add_button="'新增'" @addForm="edit" />
+
+                <!-- 表格 -->
+                <div class="table">
+                    <Table @getTableData="changeTableData" v-model:page="page" ref="table" :data="tableData"
+                        @selection-change="handleSelectionChange">
+                        <el-table-column prop="userId" label="用户ID" />
+                        <el-table-column prop="nickName" label="用户昵称">
+                            <template #default="scope">
+                                <div style="display: flex; align-items: center">
+                                    <!-- <el-image style="width: 50px; height: 50px; margin-right: 5px"
+                                        :src="scope.row.headImage" :z-index="99999" fit="fill"
+                                        :preview-src-list="[scope.row.headImage]" :preview-teleported="true">
+                                    </el-image> -->
+                                    <div>{{ scope.row.nickName }}</div>
+                                </div>
+                            </template>
+                        </el-table-column>
+                        <el-table-column prop="loginName" label="登录用户名" />
+                        <!-- <el-table-column prop="deptName" label="所属部门" width="120" /> -->
+                        <!-- <el-table-column prop="roleList" label="角色">
+                            <template #default="scope">
+                                <el-tag style="margin: 5px;" v-for="item in scope.row.roleList" :key="item.roleId"
+                                    type="success" effect="light">
+                                    {{ item.roleName }}
+                                </el-tag>
+                            </template>
+                        </el-table-column> -->
+                        <el-table-column prop="phone" label="手机号" />
+
+                        <el-table-column prop="accountStatus" label="状态">
+                            <template #default="scope">
+                                <el-tag :type="scope.row.accountStatus ? 'success' : 'info'" effect="light">
+                                    {{ scope.row.accountStatus ? "生效" : "失效" }}
+                                </el-tag>
+                            </template>
+                        </el-table-column>
+                        <el-table-column prop="lastLoginTime" label="最新登录时间">
+                            <template #default="scope">
+                                {{ convertUTCToBeijing(scope.row.lastLoginTime) }}
+                            </template>
+                        </el-table-column>
+
+                        <el-table-column label="操作" width="100">
+                            <template #default="scope">
+                                <div class="button">
+                                    <el-link class="button-item" type="primary" style="margin-bottom: 5px"
+                                        @click="edit(scope.row)">
+                                        编辑
+                                    </el-link>
+                                    <el-link class="button-item" type="danger" style="margin-bottom: 5px"
+                                        @click="removeUser(scope.row)">
+                                        删除
+                                    </el-link>
+                                </div>
+                            </template>
+                        </el-table-column>
+                    </Table>
+                </div>
+
+                <!-- 操作弹窗 -->
+                <Layer :layer="layer" @confirm="submit(ruleForm)" @close="layer.show = false">
+                    <el-form :model="formEdit" :rules="rules" ref="ruleForm" label-width="120px"
+                        style="margin-right: 30px">
+                        <el-row :gutter="40">
+                            <el-col :span="12" class="grid-cell">
+                                <el-form-item label="用户昵称" required prop="nickName">
+                                    <el-input v-model="formEdit.nickName" placeholder="请输入用户名" clearable />
+                                </el-form-item>
+                                <el-form-item label="登录用户名" required prop="loginName">
+                                    <el-input :disabled="layer.edit" v-model="formEdit.loginName" placeholder="请输入用户名"
+                                        clearable />
+                                </el-form-item>
+
+
+                            </el-col>
+                            <el-col :span="12" class="grid-cell">
+                                <!--   <el-form-item label="所属部门" prop="deptId">
+                                    <el-tree-select v-model="formEdit.deptId" :data="deptTreeData"
+                                        :render-after-expand="false" style="width: 240px" />
+                                </el-form-item> -->
+                                <el-form-item label="手机号" required prop="phone">
+                                    <el-input v-model="formEdit.phone" placeholder="请输入用户名" clearable />
+                                </el-form-item>
+                                <el-form-item label="密码" prop="password">
+                                    <el-input v-model="formEdit.password" placeholder="请输入用户密码" clearable />
+                                </el-form-item>
+                                <!-- <el-form-item label="头像" prop="headImage">
+                                    <el-upload class="avatar-uploader"
+                                        accept="image/jpg,image/jpeg,image/png,image/webp" action="#"
+                                        :show-file-list="false" :before-upload="beforeAvatarUpload"
+                                        :http-request="selfUpload">
+                                        <el-image v-if="formEdit.headImage" style="width: 100px; height: 100px"
+                                            :src="formEdit.headImage" fit="fill" />
+                                        <el-icon v-else class="avatar-uploader-icon">
+                                            <Plus />
+                                        </el-icon>
+                                        <template #tip>
+                                            <div class="el-upload__tip">
+                                                仅支持上传 jpg / png / webp 图片格式
+                                            </div>
+                                        </template>
+                                    </el-upload>
+                                </el-form-item> -->
+                                <!-- <el-form-item label="角色" prop="roleList">
+                                    <el-select v-model="formEdit.roleList" multiple placeholder="请选择" clearable
+                                        style="width: 240px">
+                                        <el-option v-for="item in roleList" :key="item.roleId" :label="item.roleName"
+                                            :value="item.roleId" />
+                                    </el-select>
+                                </el-form-item> -->
+                                <!--  <el-form-item label="所属渠道" prop="channelId">
+                                    <el-input v-model="formEdit.channelId" placeholder="请输入所属渠道" clearable />
+                                </el-form-item> -->
+                                <!-- <el-form-item label="是否启用" prop="accountStatus">
+                                    <el-switch v-model="formEdit.accountStatus" :active-value="1" :inactive-value="0" />
+                                </el-form-item> -->
+                            </el-col>
+                        </el-row>
+                    </el-form>
+                </Layer>
+            </div>
+        </el-card>
+    </div>
+</template>
+
+<script setup>
+import { onBeforeMount, ref, reactive, watch, computed } from "vue";
+import { useEventListener } from "@vueuse/core";
+import { ElMessage, ElLoading } from "element-plus";
+import { useGetDictList } from "@/hooks/useGetDictList.js";
+import { convertUTCToBeijing } from "@/utils/index.js";
+import From from "@/components/from/index.vue";
+import Table from "@/components/table/index.vue";
+import Layer from "@/components/layer/index.vue";
+import { attachFile, attachImage } from "@/api/common.js";
+import {
+    promoterUserAddOne, promoterUserDeleteOne, promoterUserQueryList, promoterUserUpdateOne,
+} from "@/api/outBagModule.js";
+
+const { dictData, loadDictData, getOptions, getDictionaryName } = useGetDictList();
+const form = ref(null);
+const tableData = ref([]);
+
+// 分页参数, 供table使用
+const page = reactive({
+    pageNum: 1,
+    pageSize: 20,
+    total: 0,
+});
+
+const formSearch = ref({
+    deptId: undefined, //部门ID
+    accountStatus: undefined, //状态
+    loginName: undefined, //登录用户名
+    nickName: undefined, //用户昵称
+    phone: undefined, //手机号
+    page: 1, // 当前页码
+    limit: 20, // 当前页数量(查询量)
+});
+
+const dynamicFormItems = ref([]);
+
+// 展开收缩部门组织
+const isCollapse = ref(false);
+const resizeHandler = () => {
+    // 页面小于1000px自动收缩
+    if (document.body.clientWidth <= 1000 && !isCollapse.value) {
+        isCollapse.value = true;
+    } else if (document.body.clientWidth > 1000 && isCollapse.value) {
+        isCollapse.value = false;
+    }
+};
+// 初始化调用
+resizeHandler();
+
+onBeforeMount(() => {
+    // 监听页面变化
+    useEventListener("resize", resizeHandler);
+    settingData();
+    getList();
+});
+
+// 获取缓存数据设置筛选数据
+const settingData = () => {
+    loadDictData().then(() => {
+        dynamicFormItems.value = [
+            {
+                label: "登录用户名",
+                prop: "loginName",
+                type: "input",
+                needEnterEvent: true,
+            },
+            {
+                label: "用户昵称",
+                prop: "nickName",
+                type: "input",
+                needEnterEvent: true,
+            },
+            {
+                label: "手机号码",
+                prop: "phone",
+                type: "input",
+                needEnterEvent: true,
+            },
+        ];
+    });
+};
+
+// 分页数据
+const getList = async () => {
+    let res = await promoterUserQueryList({ ...formSearch.value });
+    console.log(res);
+    tableData.value = res.data;
+    page.total = res.pageMeta.total;
+};
+
+const changeTableData = (type) => {
+    formSearch.value.page = type ? 1 : page.pageNum;
+    formSearch.value.limit = page.pageSize;
+    // 分页切换
+    getList();
+};
+
+// 搜索
+const handleFormSubmitted = (formData) => {
+    // console.log("接收到子组件传递的数据", formData);
+    formSearch.value.page = 1;
+    formSearch.value.limit = 20;
+
+    formSearch.value.accountStatus = formData.accountStatus;
+    formSearch.value.loginName = formData.loginName;
+    formSearch.value.nickName = formData.nickName;
+    formSearch.value.phone = formData.phone;
+
+    getList();
+};
+
+// 表单重置
+const handleFormReset = () => {
+    formSearch.value = {
+        deptId: undefined, //部门ID
+        accountStatus: undefined, //状态
+        loginName: undefined, //登录用户名
+        nickName: undefined, //用户昵称
+        phone: undefined, //手机号
+        page: 1, // 当前页码
+        limit: 20, // 当前页数量(查询量)
+    };
+
+    getList();
+};
+
+// 选择监听器
+const handleSelectionChange = (val) => {
+    context.emit("selection-change", val);
+};
+
+// 弹窗
+const layer = ref({
+    show: false,
+    title: "新增用户",
+    showButton: true,
+    width: "50vw",
+    edit: false, //是否编辑
+});
+
+const formEdit = ref({
+    userId: undefined, //用户ID 编辑带入 *
+    deptId: undefined, //所属部门ID *
+    headImage: undefined, //头像
+    loginName: undefined, //登录用户名 *
+    nickName: undefined, //用户昵称 *
+    password: undefined, //密码 *
+    phone: undefined, //手机号 *
+    roleList: undefined, //角色
+    accountStatus: undefined, //是否启用
+    channelId: undefined, //所属渠道
+});
+
+const edit = (row) => {
+    formEdit.value = {};
+    ruleForm.value?.resetFields();
+    if (row) {
+        layer.value.title = "编辑用户";
+        layer.value.edit = true;
+        formEdit.value = { ...row };
+        formEdit.value.password = undefined
+        formEdit.value.deptId = String(row.deptId);
+    } else {
+        layer.value.title = "新增用户";
+        layer.value.edit = false;
+        // 重置数据
+        formEdit.value = {};
+        formEdit.value.deptId = 1
+
+    }
+    layer.value.show = true;
+};
+
+const ruleForm = ref(null);
+
+const rules = reactive({
+    loginName: [{ required: true, message: "请输入登录用户名", trigger: "blur" }],
+    nickName: [{ required: true, message: "请输入用户昵称", trigger: "blur" }],
+    phone: [{ required: true, message: "请输入手机号码", trigger: "blur" }],
+});
+
+const submit = async (formEl) => {
+    await formEl.validate(async (valid, fields) => {
+        if (valid) {
+            console.log("提交参数", { ...formEdit.value });
+            if (layer.value.edit) {
+                delete formEdit.value.loginName;
+                await promoterUserUpdateOne({ ...formEdit.value }).then((res) => {
+                    ElMessage.success("修改成功");
+                    getList();
+                    layer.value.show = false;
+                });
+            } else {
+                if (!formEdit.value.password) {
+                    ElMessage.error("请输入登录密码");
+                    return;
+                }
+                await promoterUserAddOne({ ...formEdit.value }).then((res) => {
+                    ElMessage.success("新增成功");
+                    getList();
+                    layer.value.show = false;
+                });
+            }
+        } else {
+            console.log("error submit!", fields);
+        }
+    });
+};
+
+const loading = ref(null);
+// 加载信息
+const openFullScreen = (loadText) => {
+    loading.value = ElLoading.service({
+        lock: true,
+        text: loadText,
+        background: "rgba(0, 0, 0, 0.7)",
+    });
+};
+
+const closeFullScreen = () => {
+    loading.value.close();
+};
+
+// 上传二维码图片
+const beforeAvatarUpload = (rawFile) => {
+    let fileType = ["image/jpeg", "image/png", "image/webp"];
+    if (!fileType.includes(rawFile.type)) {
+        ElMessage.error("请上传图片格式为jpg/png/webp!");
+        return false;
+    } else if (rawFile.size / 1024 / 1024 > 2) {
+        ElMessage.error(`图片大小不能超过 2MB!`);
+        return false;
+    }
+    return true;
+};
+
+const selfUpload = async (param) => {
+    const file = param.file;
+    const formData = new FormData();
+    formData.append("file", file);
+    openFullScreen("图片上传中");
+    try {
+        const res = await attachFile(formData);
+        formEdit.value.headImage = res.data.url;
+        ElMessage.success("图片上传成功");
+    } catch (err) {
+        ElMessage.error("图片上传失败");
+        console.error(err);
+    } finally {
+        closeFullScreen();
+    }
+};
+
+// 删除用户
+const removeUser = async (row) => {
+    try {
+        await promoterUserDeleteOne({ userId: row.userId });
+        ElMessage.success("删除成功");
+    } catch (err) {
+        ElMessage.success("删除失败,请重试!");
+    } finally {
+        getList();
+    }
+};
+
+</script>
+
+<style lang='scss' scoped>
+.button {
+    display: flex;
+
+    .button-item {
+        margin: 4px;
+    }
+}
+
+:deep() {
+
+    .el-input__wrapper,
+    .el-tree {
+        background-color: var(--system-page-background);
+        color: var(--system-page-color);
+        border-color: var(--system-page-border-color);
+    }
+}
+
+.content {
+    display: flex;
+    margin: 15px;
+    width: calc(100% - 30px);
+    height: calc(100% - 30px);
+
+    .left_card {
+        // min-width: 300px;
+        flex: 1;
+        background-color: var(--system-page-background);
+        color: var(--system-page-color);
+        border-color: var(--system-page-border-color);
+
+        .filter_tree {
+            width: 100%;
+            margin-top: 20px;
+        }
+    }
+
+    .right_card {
+        flex: 5;
+        height: calc(100% - 2px);
+        width: 100%;
+        position: relative;
+        background-color: var(--system-page-background);
+        color: var(--system-page-color);
+        border-color: var(--system-page-border-color);
+
+        .icon {
+            position: absolute;
+            left: 2px;
+            top: 50%;
+            transform: translateY(-50%);
+            background-color: var(--system-primary-color);
+            color: var(--system-primary-text-color);
+            width: 30px;
+            height: 30px;
+            border-radius: 50%;
+        }
+
+        .container {
+            width: 100%;
+            height: calc(100vh - 155px);
+            display: flex;
+            flex-direction: column;
+        }
+
+        .from {
+            margin-bottom: 10px;
+        }
+
+        .table {
+            flex: 1;
+            padding: 15px;
+            overflow: auto;
+        }
+    }
+}
+
+.el-icon.avatar-uploader-icon {
+    font-size: 28px;
+    color: #8c939d;
+    width: 90px;
+    height: 90px;
+    text-align: center;
+    border: 1px solid #e4e7ed;
+}
+</style>