vue3.0 首页用户勾选常用菜单

163 阅读1分钟
<template>
  <div v-bind="$attrs">
    <div class="title flex flex-jus-sp flex-align-c"><span>常用功能</span>
      <a v-if="!loading" @click="openModal()">
        <Icon icon="ant-design:setting-outlined" />
      </a>
    </div>
    <a-spin :spinning="loading">
      <div class="content flex flex-wrap-w" v-if="menu.length">
        <div class="menu-item" v-for="item in menu" :key="item.id" @click="handleHref(item.menuHref, item.menuType)">
          <span>{{ item.menuName }}</span></div>
      </div>
      <a-empty description="暂无配置" v-else></a-empty>
    </a-spin>
    <a-modal v-model:visible="visible" title="菜单配置" width="800px" @ok="handleSave">
      <a-spin :spinning="spinning">
        <a-tabs v-model:activeKey="activeKey" @change="checkTab" type="card">
          <a-tab-pane key="1" tab="全部功能"></a-tab-pane>
          <a-tab-pane key="2" tab="已选功能"></a-tab-pane>
        </a-tabs>
        <div style="padding:0 16px 15px;" v-if="record.length && activeKey == '1'">
          <div class="flex">
            <div class="modal-title modal-title-0">模块</div>
            <div class="modal-title modal-title-1">子模块</div>
            <div class="modal-title modal-title-2">功能</div>
          </div>
          <div class="flex">
            <div class="modal modal-0">
              <div class="li" :class="{ active: index == active[0] }" v-for="(item, index) in record" :key="index">
                <a-checkbox :checked="item.checked" :indeterminate="item.indeterminate"
                  @change="handleChange($event, item, index)"></a-checkbox> <span class="choose"
                  @click="handleClickChoose(0, index)">{{ item.menuName }}</span>
              </div>
            </div>
            <div class="modal modal-1">
              <div class="li" :class="{ active: index == active[1] }" v-for="(item, index) in record[active[0]].childList"
                :key="index"><a-checkbox :checked="item.checked" :indeterminate="item.indeterminate"
                  :disabled="!item.page.length && !item.ctrl.length"
                  @change="handleChange($event, item, index)"></a-checkbox> <span class="choose"
                  @click="handleClickChoose(1, index)">{{ item.menuName }}</span>
              </div>
            </div>
            <div class="modal modal-2">
              <div class="modal-title">页面</div>
              <div class="li" v-for="(item, index) in record[active[0]].childList[active[1]].page" :key="index">
                <a-checkbox v-model:checked="item.checked" @change="handleChange($event, item, index)">{{ item.menuName
                }}</a-checkbox>
              </div>
            </div>
            <div class="modal modal-3">
              <div class="modal-title">操作</div>
              <div class="li" v-for="(item, index) in record[active[0]].childList[active[1]].ctrl" :key="index">
                <a-checkbox v-model:checked="item.checked" @change="handleChange($event, item, index)">{{ item.menuName
                }}</a-checkbox>
              </div>
            </div>
          </div>
        </div>
        <div style="padding:0 10px 15px;" v-show="activeKey == '2'">
          <BasicTable @register="registerTable">
            <template #choose="{ record }"><a-checkbox v-model:checked="record.checked"></a-checkbox></template>
          </BasicTable>
        </div>
      </a-spin>
    </a-modal>
    <component :is="inputFormComponent" v-model:visible="drawerVisible" @register="registerDrawer" />
    <component :is="inputFormComponent" v-model:visible="modalVisible" @register="registerModal" />
  </div>
</template>
<script lang="ts">
export default defineComponent({
  name: 'homeCommonExtension',
});
</script>
<script lang="ts" setup>
import { defineComponent, ref, shallowRef } from 'vue';
import { Icon } from '/@/components/Icon';
import { BasicTable, useTable } from '/@/components/Table';
import { hpMenuSetListData, hpMenuSetSave } from '/@/api/hp/menu/hpMenuSet'
import { useMessage } from '/@/hooks/web/useMessage';
import { useDrawer } from '/@/components/Drawer';
import { useModal } from '/@/components/Modal';
import { useGo } from '/@/hooks/web/usePage';
import { dynamicImport } from '/@/router/helper/routeHelper';
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
import qs from 'qs';

const go = useGo();
const { showMessage } = useMessage();
const visible = ref(false)
const loading = ref(true)
const spinning = ref(false)

const record = ref<any>([])
const active = ref([0, 0])
const activeKey = ref("1")

const menu = ref<any>([])

init()

async function init() {
  getUserInfo()
  let menu = sessionStorage.getItem("menuItem")
  if (menu) { record.value = JSON.parse(menu) }
  else {
    spinning.value = true;
    record.value = await getData(0, 0)
    sessionStorage.setItem("menuItem", JSON.stringify(record.value))
    spinning.value = false;
  }
  defaultChecked()
}

function checkTab() {
  if (activeKey.value == "2") {
    let arr = getChecked()
    setTableData(arr)
  } else {
    let arr = getDataSource().filter(item => item.checked)
    menu.value = arr
    defaultChecked()
  }
}

function checkAll(item: any) {
  let val = { checked: false, indeterminate: false }
  if (item.childList && item.level == 0) {
    let count = 0, total = 0;
    for (let i in item.childList) {
      let data = foreachAll(item.childList[i])
      count += data.count
      total += data.total
    }
    val = { checked: count > 0 && total == count ? true : false, indeterminate: count > 0 && total != count ? true : false }
  }
  if (item.level == 1) {
    let { count, total } = foreachAll(item)
    val = { checked: count > 0 && total == count ? true : false, indeterminate: count > 0 && total != count ? true : false }
  }
  return val
}

function foreachAll(item) {
  let total = item.page.length + item.ctrl.length;
  let count = 0;
  if (item.page.length + item.ctrl.length > 0) {
    count += item.page.filter(item => item.checked).length
    count += item.ctrl.filter(item => item.checked).length
  }
  return { count, total }
}

function chooseAll(item: any, bool) {
  if (item.childList) {
    item.childList.forEach(items => {
      chooseAll(items, bool)
    })
  }
  if (item.page) {
    item.page.forEach(items => {
      items.checked = bool
    })
  }
  if (item.ctrl) {
    item.ctrl.forEach(items => {
      items.checked = bool
    })
  }
}

function calcChecked(arr) {
  arr.forEach(item => {
    let { checked, indeterminate } = checkAll(item)
    item.checked = checked
    item.indeterminate = indeterminate
    if (item.childList) {
      item.childList.forEach(items => {
        let { checked, indeterminate } = checkAll(items)
        items.checked = checked
        items.indeterminate = indeterminate
      })
    }
  })
  return arr
}

async function getUserInfo() {
  loading.value = true
  let res = await hpMenuSetListData({ isAll: 0 })
  menu.value = res
  loading.value = false
}

async function getData(parentCode: number, level: number) {
  try {
    // 获取所有
    let res = await hpMenuSetListData({ isAll: 1, parentCode })
    if (res.length) {
      for (let i = 0; i < res.length; i++) {
        let item: any = res[i]
        item.level = level;
        if (level == 0) {
          item.childList = await getData(item.id, level + 1)
        }
        if (level == 1) {
          let list = await getData(item.id, level + 1)
          item.page = list?.filter(item => item.menuType == 2)
          item.ctrl = list?.filter(item => item.menuType == 3)
        }
      }
      return res
    } else {
      return []
    }
  } catch (e) { }
}

async function handleClickChoose(level, index) {
  if (level == 0) {
    active.value[1] = 0
  }
  active.value[level] = index
}

function handleChange(e, item, index) {
  if (item.level > 1) {
    let type = item.menuType == 3 ? "ctrl" : "page"
    record.value[active.value[0]].childList[active.value[1]][type][index] = item
  } else {
    chooseAll(item, e.target.checked)
  }
  record.value = calcChecked(record.value)
}

const [registerDrawer, { setDrawerData }] = useDrawer();
const [registerModal, { setModalData }] = useModal();
const inputFormComponent = shallowRef<Nullable<any>>(null);
const drawerVisible = ref<Boolean>(false);
const modalVisible = ref<Boolean>(false);

function handleHref(url, menuType) {
  let idx = url.indexOf('?');
  let paramStr = idx == -1 ? '' : url.substring(idx + 1);
  let compStr = idx == -1 ? url : url.substring(0, idx);

  let isTab = paramStr && paramStr.indexOf('type=tab') >= 0 ? true : false
  let isModal = paramStr && paramStr.indexOf('type=modal') >= 0 ? true : false
  //开启TAB
  if (isTab || menuType == '2') {
    go(url)
    return
  }
  //抽屉
  // component
  let component: ReturnType<typeof defineComponent>;
  if (compStr && compStr != '') {
    const imp = dynamicImport(compStr);
    if (imp) component = createAsyncComponent(imp);
  }
  // params
  let params = {};

  if (paramStr && paramStr != '') {
    params = qs.parse(paramStr);
  }
  // open
  if (component) {
    inputFormComponent.value = component;
    drawerVisible.value = false;
    modalVisible.value = false;
    if (isModal) {
      modalVisible.value = true;
      setModalData(params)
    } else {
      drawerVisible.value = true;
      setDrawerData(params)
    }
  }
}

function openModal() {
  // push(href)
  visible.value = true

  if (!spinning.value) defaultChecked()
}

// 初始化弹窗默认选中模块
function defaultChecked() {
  record.value.forEach(item => {
    if (item.childList) {
      item.childList.forEach(items => {
        items.page.forEach(p => {
          let obj = menu.value.find(m => m.id == p.id)
          p.checked = obj ? true : false
        })
        items.ctrl.forEach(p => {
          let obj = menu.value.find(m => m.id == p.id)
          p.checked = obj ? true : false
        })
        let { checked, indeterminate } = checkAll(items)
        items.checked = checked
        items.indeterminate = indeterminate
      })
    }

    let { checked, indeterminate } = checkAll(item)
    item.checked = checked
    item.indeterminate = indeterminate
  })
}

// 获取弹窗选中模块
function getChecked() {
  let ids: any = []
  record.value.forEach(item => {
    if (item.childList) {
      item.childList.forEach(items => {
        items.page.forEach(p => {
          if (p.checked) ids.push(Object.assign({}, p))
        })
        items.ctrl.forEach(p => {
          if (p.checked) ids.push(Object.assign({}, p))
        })
      })
    }
  })
  return ids
}

// 配置常用功能
async function handleSave() {
  spinning.value = true
  let ids: any = []
  if (activeKey.value == "1") ids = getChecked().map(item => item.id)
  if (activeKey.value == "2") ids = getDataSource().filter(item => item.checked).map(item => item.id)

  try {
    let res = await hpMenuSetSave({ menuCodes: ids.join(",") })
    showMessage(res.message);
    setTimeout(() => {
      visible.value = false
      activeKey.value = "1"
      getUserInfo()
    }, 500)
  } catch (err) { } finally {
    spinning.value = false
  }

}

// 初始化表格
const [registerTable, { setTableData, getDataSource }] = useTable({
  columns: [
    {
      title: '功能',
      dataIndex: 'menuName',
      sorter: false,
      width: 130,
      align: 'center',
    },
    {
      title: '功能类型',
      dataIndex: 'menuType',
      sorter: false,
      width: 130,
      align: 'center',
      customRender: ({ record }) => {
        return record.menuType == 2 ? "页面" : "操作"
      }
    },
    {
      title: '所属模块',
      dataIndex: 'treeNames',
      sorter: false,
      width: 130,
      align: 'center',
      customRender: ({ record }) => {
        let arr = record.treeNames.split("/").splice(0, 2)
        return arr.join("-")
      }
    },
    {
      title: '操作',
      dataIndex: 'choose',
      sorter: false,
      width: 50,
      align: 'center',
      slot: "choose"
    },
  ],
  useSearchForm: false,
  pagination: false,
  scroll: { y: 360 }
});

</script>
<style lang="less" scoped>
.content {
  max-height: 180px;
  overflow: auto;
}

.menu-item {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 84px;
  height: 60px;
  padding: 3px;
  border: 1px solid #ddd;
  border-radius: 4px;
  margin-right: 10px;
  margin-bottom: 10px;
  user-select: none;
  cursor: pointer;

  >span {
    text-align: center;
    line-height: 1.2;
  }

  &:hover {
    background: #fafafa;
  }
}

.modal-title {
  text-align: center;
  font-weight: bold;
  padding: 10px 0;
  border-left: 1px solid #ddd;
  border-top: 1px solid #ddd;
  border-bottom: 1px solid #ddd;

  &:last-child {
    border-right: 1px solid #ddd;
  }
}

.modal {
  height: 360px;
  padding: 6px;
  border-left: 1px solid #ddd;
  border-bottom: 1px solid #ddd;

  &:last-child {
    border-right: 1px solid #ddd;
  }

  .modal-title {
    border: none;
    padding: 2px;
  }

  .li {
    margin-bottom: 4px;

    .choose {
      cursor: pointer;
    }

    &.active .choose {
      color: #2a50ec;
      font-weight: bold;
    }
  }
}


.modal-0,
.modal-title-0 {
  flex: 1
}

.modal-1,
.modal-title-1 {
  width: 160px
}

.modal-title-2 {
  width: 440px;
}

.modal-2 {
  width: 220px
}

.modal-3 {
  width: 220px
}

.ant-tabs.ant-tabs-card {
  background: transparent;
}
</style>

image.png

image.png