Selaa lähdekoodia

复刻选择人员组件

chauncey 11 kuukautta sitten
vanhempi
commit
73dd54190d

+ 3 - 4
mock/login/routers.ts

@@ -114,7 +114,7 @@ const list = [
               activeMenu: null,
               alwaysShow: false,
               frameSrc: '',
-              hidden: true,
+              hidden: false,
               icon: '',
               isFrame: 0,
               isRoot: false,
@@ -134,7 +134,7 @@ const list = [
               activeMenu: null,
               alwaysShow: false,
               frameSrc: '',
-              hidden: false,
+              hidden: true,
               icon: '',
               isFrame: 0,
               isRoot: false,
@@ -194,14 +194,13 @@ const list = [
               activeMenu: null,
               alwaysShow: false,
               frameSrc: '',
-              hidden: false,
+              hidden: true,
               icon: '',
               isFrame: 0,
               isRoot: false,
               noCache: false,
               query: '',
               title: '任务模板详情',
-              isHidden: true,
             },
             name: '/disaster-prevention/disaster-precaution/template-detail',
             parentId: 1028,

+ 175 - 0
mock/push-object/index.ts

@@ -0,0 +1,175 @@
+import { resultSuccess } from '../_util';
+
+const pushObjectGroupInfo = [
+  {
+    userGroupId: 1,
+    name: '测试1',
+    total: 20,
+    userList: [
+      {
+        id: 33,
+        realname: 'test111',
+        username: '3133',
+      },
+      {
+        id: 34,
+        realname: 'test222',
+        username: '3134',
+      },
+      {
+        id: 35,
+        realname: 'test333',
+        username: '3135',
+      },
+      {
+        id: 36,
+        realname: 'test444',
+        username: '3136',
+      },
+      {
+        id: 37,
+        realname: 'test555',
+        username: '3137',
+      },
+      {
+        id: 38,
+        realname: 'test666',
+        username: '3138',
+      },
+      {
+        id: 39,
+        realname: 'test777',
+        username: '3139',
+      },
+      {
+        id: 40,
+        realname: 'test888',
+        username: '3140',
+      },
+      {
+        id: 41,
+        realname: 'test999',
+        username: '3141',
+      },
+      {
+        id: 42,
+        realname: 'test1000',
+        username: '3142',
+      },
+      {
+        id: 33,
+        realname: 'test111',
+        username: '3133',
+      },
+      {
+        id: 34,
+        realname: 'test222',
+        username: '3134',
+      },
+      {
+        id: 35,
+        realname: 'test333',
+        username: '3135',
+      },
+      {
+        id: 36,
+        realname: 'test444',
+        username: '3136',
+      },
+      {
+        id: 37,
+        realname: 'test555',
+        username: '3137',
+      },
+      {
+        id: 38,
+        realname: 'test666',
+        username: '3138',
+      },
+      {
+        id: 39,
+        realname: 'test777',
+        username: '3139',
+      },
+      {
+        id: 40,
+        realname: 'test888',
+        username: '3140',
+      },
+      {
+        id: 41,
+        realname: 'test999',
+        username: '3141',
+      },
+      {
+        id: 42,
+        realname: 'test1000',
+        username: '3142',
+      },
+    ],
+  },
+  {
+    userGroupId: 2,
+    name: '测试2',
+    total: 3,
+    userList: [
+      {
+        id: 43,
+        realname: 'test111',
+        username: '3143',
+      },
+      {
+        id: 44,
+        realname: 'test222',
+        username: '3144',
+      },
+      {
+        id: 45,
+        realname: 'test333',
+        username: '3145',
+      },
+    ],
+  },
+];
+
+const pushObjectUserInfo = [
+  {
+    id: 1,
+    realname: 'test111',
+    username: '3133',
+  },
+  {
+    id: 2,
+    realname: 'test222',
+    username: '3134',
+  },
+  {
+    id: 3,
+    realname: 'test333',
+    username: '3135',
+  },
+  {
+    id: 4,
+    realname: 'test444',
+    username: '3136',
+  },
+];
+
+export default [
+  {
+    url: '/eye_api_bak/api/userGroup/queryUserGroupDetailByIds',
+    timeout: 1000,
+    method: 'get',
+    response: () => {
+      return resultSuccess(pushObjectGroupInfo);
+    },
+  },
+  {
+    url: '/eye_api_bak/api/user/queryAvailableUserList',
+    timeout: 1000,
+    method: 'get',
+    response: () => {
+      return resultSuccess(pushObjectUserInfo);
+    },
+  },
+];

+ 32 - 0
src/api/push-object/index.ts

@@ -0,0 +1,32 @@
+import { http } from '@/utils/http/axios';
+import type { PushObjectGroupInfoResponse, PushObjectUserInfoResponse } from '@/types/push-object';
+/**
+ * 查询推送人员组
+ */
+export const queryUserGroupDetailByIds = (userGroupList: number[]) => {
+  return http.request<PushObjectGroupInfoResponse[]>(
+    {
+      url: '/userGroup/queryUserGroupDetailByIds',
+      method: 'get',
+      params: userGroupList,
+    },
+    {
+      ignoreTargetTenantId: true,
+    },
+  );
+};
+
+/**
+ * 查询推送人员
+ */
+export const queryAvailableUserList = () => {
+  return http.request<PushObjectUserInfoResponse[]>(
+    {
+      url: '/user/queryAvailableUserList',
+      method: 'get',
+    },
+    {
+      ignoreTargetTenantId: true,
+    },
+  );
+};

+ 1 - 0
src/main.scss

@@ -1,5 +1,6 @@
 @use '@/styles/common.scss';
 @use '@/styles/animate.scss';
+@use '@/styles/custom-component.scss';
 
 * {
   padding: 0;

+ 14 - 0
src/styles/custom-component.scss

@@ -0,0 +1,14 @@
+@use './variables.scss' as *;
+
+.customDialog--pushObject {
+  height: 60vh;
+  border-radius: 8px !important;
+  .el-dialog__body {
+    display: flex;
+    flex-direction: column;
+    gap: 10px;
+    width: 100%;
+    height: calc(100% - 50px);
+    overflow-y: auto;
+  }
+}

+ 22 - 0
src/types/push-object/index.ts

@@ -0,0 +1,22 @@
+export interface PushObjectGroupInfoResponse {
+  userGroupId: number;
+  name: string;
+  total: number;
+  userList: UserInfo[];
+}
+
+export interface PushObjectUserInfoResponse extends UserInfo {
+  deptName: string;
+  deptId: number;
+}
+
+export interface UserInfo {
+  id: number;
+  realname: string;
+  username: string;
+}
+
+export interface UserGroupInfo extends PushObjectGroupInfoResponse {
+  isExpand: boolean;
+  isHidden: boolean;
+}

+ 109 - 0
src/views/disaster/components/Group.vue

@@ -0,0 +1,109 @@
+<template>
+  <div class="group" v-for="group in userGroupInfo" :key="group.userGroupId">
+    <span class="group-name">
+      {{ group.name }}
+    </span>
+    <span class="group-info">
+      共
+      <span style="color: #1777ff">{{ group.total }}</span>
+      人
+    </span>
+    <div class="user-info" :class="[group.isExpand ? 'expanded' : 'hidden']" ref="userInfoRefs">
+      <div class="user-info--left">
+        <el-tag type="primary" v-for="user in group.userList" :key="user.id">
+          {{ user.username }}-{{ user.realname }}
+        </el-tag>
+      </div>
+      <div class="user-info--right" v-if="group.isHidden">
+        <span class="user-info--btn" @click="toggleExpand(group)">
+          {{ group.isExpand ? '收起' : '展开' }}
+          <el-icon><component :is="group.isExpand ? ArrowUp : ArrowDown" /></el-icon>
+        </span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { ref, watch } from 'vue';
+  import type { UserGroupInfo } from '@/types/push-object';
+  import { ArrowUp, ArrowDown } from '@element-plus/icons-vue';
+  const userInfoRefs = ref<Record<string, HTMLElement>>({});
+  const userGroupInfo = ref<UserGroupInfo[]>();
+  const toggleExpand = (group: UserGroupInfo) => {
+    group.isExpand = !group.isExpand;
+  };
+  const props = defineProps<{
+    userGroupInfo?: UserGroupInfo[];
+  }>();
+  watch(
+    () => props.userGroupInfo,
+    (newVal) => {
+      userGroupInfo.value = newVal;
+    },
+  );
+  watch(
+    () => userInfoRefs.value,
+    (newRefs) => {
+      if (newRefs) {
+        Object.keys(newRefs).forEach((key) => {
+          const el = newRefs[key];
+          if (el.offsetHeight >= 86) {
+            userGroupInfo.value![Number(key)].isHidden = true;
+          }
+        });
+      }
+    },
+  );
+</script>
+
+<style lang="scss" scoped>
+  .group {
+    font-size: 12px;
+    font-weight: 400;
+    color: rgba($text-color, 0.88);
+  }
+  .group-name {
+    font-size: 16px;
+    line-height: 22px;
+  }
+  .group-info {
+    margin-left: 4px;
+    line-height: 17px;
+  }
+  .user-info {
+    display: flex;
+    &.hidden {
+      max-height: 86px;
+      overflow-y: hidden;
+    }
+    &.expanded {
+      max-height: 100%;
+      overflow-y: auto;
+    }
+    &--left,
+    &--right {
+      margin-top: 20px;
+    }
+    &--left {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 10px;
+      flex: 1;
+    }
+    &--right {
+      width: 50px;
+    }
+    &--btn {
+      @include flex-center;
+      gap: 4px;
+      cursor: pointer;
+    }
+  }
+  .el-tag {
+    font-weight: 500;
+    font-size: 12px;
+    color: $primary-color;
+    line-height: 20px;
+  }
+</style>

+ 110 - 0
src/views/disaster/components/PushObject.vue

@@ -0,0 +1,110 @@
+<template>
+  <el-form :model="ruleForm" ref="ruleFormRef">
+    <el-form-item>
+      <el-radio-group v-model="ruleForm.recipientType">
+        <el-radio :value="1">分组</el-radio>
+        <el-radio :value="2">自定义</el-radio>
+      </el-radio-group>
+    </el-form-item>
+    <div class="userList" v-if="ruleForm.recipientType === 1">
+      <el-form-item label="选择分组" prop="userGroupList" :rules="[{ required: true, message: '请选择分组' }]">
+        <el-select v-model="ruleForm.userGroupList" multiple placeholder="请选择分组">
+          <el-option v-for="item in groupOptions" :key="item.id" :value="item.id" :label="item.name" />
+        </el-select>
+      </el-form-item>
+      <span @click="showGroupInfo"> 人员详情 </span>
+    </div>
+    <div class="userList" v-else-if="ruleForm.recipientType === 2">
+      <el-form-item label="选择人员" prop="customUserList" :rules="[{ required: true, message: '请选择人员' }]">
+        <el-select
+          v-model="ruleForm.customUserList"
+          value-key="id"
+          multiple
+          placeholder="请选择人员"
+          @click="userInfo = true"
+        >
+          <el-option
+            v-for="user in userOptions"
+            :key="user.id"
+            :label="`${user.username}-${user.realname}`"
+            :value="user"
+          />
+        </el-select>
+      </el-form-item>
+    </div>
+    <el-dialog
+      v-model="groupInfo"
+      title="人员详情"
+      align-center
+      :close-on-click-modal="false"
+      :destroy-on-close="true"
+      class="customDialog--pushObject"
+    >
+      <Group :userGroupInfo="userGroupInfo" />
+    </el-dialog>
+    <el-dialog
+      v-model="userInfo"
+      title="添加人员"
+      align-center
+      :close-on-click-modal="false"
+      :destroy-on-close="true"
+      class="customDialog--pushObject"
+    >
+      <User @cancel="userInfo = false" />
+    </el-dialog>
+  </el-form>
+</template>
+
+<script lang="ts" setup>
+  import { ref, reactive } from 'vue';
+  import Group from './Group.vue';
+  import User from './User.vue';
+  import type { GroupOptionType } from '../types';
+  import type { UserInfo, UserGroupInfo } from '@/types/push-object';
+  import { queryUserGroupDetailByIds } from '@/api/push-object';
+  interface RuleForm {
+    recipientType: number;
+    userGroupList: number[];
+    customUserList: UserInfo[];
+  }
+  const groupOptions = ref<GroupOptionType[]>([]);
+  const groupInfo = ref(false);
+  const userInfo = ref(false);
+  const userOptions = ref<UserInfo[]>([]);
+  const ruleForm = reactive<RuleForm>({
+    recipientType: 1,
+    userGroupList: [],
+    customUserList: [],
+  });
+  const showGroupInfo = () => {
+    groupInfo.value = true;
+    getUserGroupInfo();
+  };
+  const userGroupInfo = ref<UserGroupInfo[]>([]);
+  const getUserGroupInfo = async () => {
+    const res = await queryUserGroupDetailByIds(ruleForm.userGroupList);
+    userGroupInfo.value = res.map((item) => ({
+      ...item,
+      isExpand: false,
+      isHidden: false,
+    }));
+  };
+</script>
+
+<style lang="scss" scoped>
+  :deep(.el-select__selection) {
+    min-height: 25px;
+    max-height: 60px;
+    overflow-y: auto;
+  }
+  .userList {
+    width: 100%;
+    padding: 12cpx;
+    span {
+      cursor: pointer;
+      font-size: 10cpx;
+      color: $primary-color;
+      margin-left: 80px;
+    }
+  }
+</style>

+ 169 - 0
src/views/disaster/components/User.vue

@@ -0,0 +1,169 @@
+<template>
+  <div class="User">
+    <div class="left">
+      <div class="filter-title">
+        <el-input v-model="queryContent" placeholder="请输入搜索的内容" clearable>
+          <template #prepend>
+            <el-select v-model="selectType" class="select-type">
+              <el-option v-for="item in searchOptions" :key="item.value" :value="item.value" :label="item.label" />
+            </el-select>
+          </template>
+        </el-input>
+        <el-button type="primary" @click="handleSearch">搜 索</el-button>
+      </div>
+      <div class="filter-result" v-loading="loading">
+        <div class="empty" v-if="nodeData[0].children.length === 0">
+          <img :src="empty" alt="" />
+          <div>暂无数据</div>
+        </div>
+        <el-tree
+          v-else
+          ref="treeRef"
+          :data="nodeData"
+          show-checkbox
+          node-key="id"
+          :props="defaultProps"
+          :default-expand-all="true"
+          style="padding: 10px 0"
+        />
+      </div>
+    </div>
+    <div class="right">
+      <!-- <div class="head">
+        <span style="font-weight: 400; font-size: 16px; color: rgba(0, 0, 0, 0.88); line-height: 22px"
+          >已选择:{{ selectedPersonList.length }}人</span
+        >
+      </div> -->
+      <!-- <div class="selected">
+        <el-tag
+          v-for="person in selectedPersonList"
+          :key="person.id"
+          closable
+          @close="handleRemoveSelectedPerson(person.id)"
+        >
+          {{ person.staffNo + '-' + person.realname }}
+        </el-tag>
+      </div> -->
+      <div class="footer">
+        <el-button @click="emit('cancel')">取消</el-button>
+        <el-button type="primary">确定</el-button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { nextTick, ref } from 'vue';
+  import empty from 'assets/images/empty@1X.png';
+  import { queryAvailableUserList } from '@/api/push-object';
+  import type { TreeNodeData } from '@/views/disaster/types';
+  const loading = ref(false);
+  const queryContent = ref('');
+  const nodeData = ref<TreeNodeData[]>([
+    {
+      id: -1,
+      name: '全部',
+      children: [],
+    },
+  ]);
+  const searchResult = ref<TreeNodeData[]>([]);
+  const defaultProps = {
+    children: 'children',
+    label: 'name',
+  };
+  const searchOptions = ref([
+    { value: 'realname', label: '姓名' },
+    { value: 'staffNo', label: '工号' },
+    { value: 'deptName', label: '部门' },
+  ]);
+  const selectType = ref(searchOptions.value[0].value);
+  const getUserList = async () => {
+    loading.value = true;
+    const res = await queryAvailableUserList();
+    searchResult.value = res.map((user) => {
+      return {
+        id: user.id,
+        name: `${user.realname}-${user.deptName}`,
+        children: [],
+      };
+    });
+    nodeData.value[0].children = searchResult.value;
+    console.log(nodeData.value);
+    await nextTick();
+    loading.value = false;
+  };
+  const handleSearch = () => {
+    console.log(queryContent.value, selectType.value);
+    getUserList();
+  };
+  const emit = defineEmits(['cancel', 'submit']);
+</script>
+
+<style lang="scss" scoped>
+  .User {
+    display: flex;
+    width: 100%;
+    height: 100%;
+    .left {
+      display: flex;
+      flex-direction: column;
+      width: 50%;
+      height: 100%;
+      padding: 0 5px;
+      border-right: 1px solid rgba($text-color, 0.1);
+    }
+    .filter-title {
+      @include flex-center;
+      gap: 10px;
+      justify-content: space-between;
+    }
+    .filter-result {
+      flex: 1;
+      overflow-y: auto;
+      .empty,
+      img {
+        width: 100%;
+      }
+      .empty {
+        @include flex-center;
+        flex-direction: column;
+        height: 100%;
+        gap: 5px;
+      }
+    }
+    .select-type {
+      width: 75px;
+    }
+    .right {
+      display: flex;
+      flex-direction: column;
+      flex: 1;
+      height: 100%;
+      position: relative;
+      margin-left: 16px;
+      .head {
+        display: flex;
+        align-items: center;
+        font-weight: 400;
+        font-size: 16px;
+        color: rgba(0, 0, 0, 0.88);
+        line-height: 22px;
+        margin: 6px 0 6px;
+      }
+      .selected {
+        display: flex;
+        flex-wrap: wrap;
+        gap: 8px;
+        overflow-y: auto;
+        max-height: calc(100% - 120px);
+      }
+      .footer {
+        display: flex;
+        gap: 6px;
+        position: absolute;
+        right: 24px;
+        bottom: 20px;
+      }
+    }
+  }
+</style>

+ 13 - 0
src/views/disaster/disaster-precaution/src/components/CreateTaskItem.vue

@@ -30,6 +30,15 @@
           show-word-limit
         />
       </el-form-item>
+      <el-form-item label="检查人员:" required>
+        <PushObject />
+      </el-form-item>
+      <el-form-item label="是否推送:" prop="isPush">
+        <el-radio-group v-model="ruleForm.isPush">
+          <el-radio :value="true">是</el-radio>
+          <el-radio :value="false">否</el-radio>
+        </el-radio-group>
+      </el-form-item>
       <el-form-item label="创建人:">
         <el-input v-model="ruleForm.createUser" disabled />
       </el-form-item>
@@ -39,6 +48,7 @@
 
 <script setup lang="ts">
   import { reactive, ref } from 'vue';
+  import PushObject from '@/views/disaster/components/PushObject.vue';
   import type { FormInstance, FormRules } from 'element-plus';
   import { TASK_TYPE_OPTIONS } from '../constants/task-execution';
 
@@ -48,6 +58,7 @@
     checkType: string;
     shouldCompleteTime: string;
     checkRequirement: string;
+    isPush: string;
     createUser: string;
   }
 
@@ -58,6 +69,7 @@
     checkType: '',
     shouldCompleteTime: '',
     checkRequirement: '',
+    isPush: '',
     createUser: 'XXX',
   });
 
@@ -84,6 +96,7 @@
         trigger: 'change',
       },
     ],
+    isPush: [{ required: true, message: '请选择是否推送', trigger: 'change' }],
   });
 </script>
 

+ 10 - 0
src/views/disaster/types/index.ts

@@ -0,0 +1,10 @@
+export interface GroupOptionType {
+  id: number;
+  name: string;
+}
+
+export interface TreeNodeData {
+  id: number;
+  name: string;
+  children: TreeNodeData[];
+}