Browse Source

用户目录

lrf 8 months ago
parent
commit
196d89f85b

+ 1 - 0
src/lang/package/zh-cn/menus.js

@@ -5,6 +5,7 @@ export default {
   center: '个人中心',
   password: '修改密码',
   system_menus: '目录设置',
+  system_userMenus: '用户目录',
   system_role: '角色设置',
   system_parameter: '系统参数',
   system_dict: '字典管理',

+ 40 - 0
src/store/api/system/userMenus.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/userMenus'
+const axios = new AxiosWrapper()
+
+export const UserMenusStore = defineStore('userMenus', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, 'id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 9 - 1
src/views/system/role/index.vue

@@ -24,8 +24,10 @@ import roleTable from './parts/table.vue'
 import roleForm from './parts/form.vue'
 import { RoleStore } from '@/store/api/system/role'
 import { MenusStore } from '@/store/api/system/menus'
+import { UserMenusStore } from '@/store/api/system/userMenus'
 import { cloneDeep, get } from 'lodash-es'
 const store = RoleStore()
+const userMenusStroe = UserMenusStore()
 const menuStore = MenusStore()
 
 const $checkRes = inject('$checkRes')
@@ -37,11 +39,16 @@ onMounted(() => {
   search({ skip, limit })
 })
 const menuList = ref([])
+const userMenusList = ref([])
 const searchMenus = async () => {
-  const res = await menuStore.query()
+  let res = await menuStore.query()
   if ($checkRes(res)) {
     menuList.value = res.data
   }
+  res = await userMenusStroe.query()
+  if ($checkRes(res)) {
+    userMenusList.value = res.data
+  }
 }
 
 const data = ref([])
@@ -121,6 +128,7 @@ const changePage = (page = currentPage.value) => {
 provide('data', data)
 provide('total', total)
 provide('menuList', menuList)
+provide('userMenusList', userMenusList)
 provide('form', form)
 provide('dialog', dialog)
 provide('toAdd', toAdd)

+ 23 - 5
src/views/system/role/parts/form.vue

@@ -29,11 +29,23 @@
       <el-input v-model="form.brief" type="textarea" :rows="3"></el-input>
     </el-form-item>
     <el-form-item :label="$t('pages.role.menu')">
-      <el-tree ref="roleTree" :data="getMenus()" node-key="code" default-expand-all show-checkbox @check-change="seletNode">
-        <template #default="{ data }">
-          <span>{{ data.name }}</span>
-        </template>
-      </el-tree>
+      <template v-if="form.is_admin_role === '1'">
+        <el-tree ref="roleTree" :data="getMenus()" node-key="code" default-expand-all show-checkbox @check-change="seletNode">
+          <template #default="{ data }">
+            <span>{{ data.name }}</span>
+          </template>
+        </el-tree>
+      </template>
+      <template v-else-if="form.is_admin_role === '0'">
+        <el-tree ref="roleTree" :data="getUserMenus()" node-key="code" default-expand-all show-checkbox @check-change="seletNode">
+          <template #default="{ data }">
+            <span>{{ data.name }}</span>
+          </template>
+        </el-tree>
+      </template>
+      <template v-else>
+        请先选择是否是管理员!
+      </template>
     </el-form-item>
     <el-row justify="center" style="text-align: center">
       <el-col :span="6">
@@ -46,6 +58,7 @@
 <script setup>
 const form = inject('form')
 const menuList = inject('menuList')
+const userMenusList = inject('userMenusList')
 const onSubmit = inject('onSubmit')
 const roleTree = ref()
 const is_dev = import.meta.env.MODE === 'development'
@@ -54,6 +67,11 @@ const getMenus = () => {
   const result = dealMenu(list)
   return result
 }
+const getUserMenus = () => {
+  const list = toRaw(userMenusList.value)
+  const result = dealMenu(list)
+  return result
+}
 onMounted(() => {
   const treeSelected = toRaw(form.value.menu)
   for (const key of treeSelected) {

+ 109 - 0
src/views/system/userMenus/index.vue

@@ -0,0 +1,109 @@
+<template>
+  <div class="main animate__animated animate__backInRight">
+    <el-row>
+      <el-col :span="24" style="text-align: right; padding: 10px">
+        <el-button type="primary" size="small" @click="toAdd()">{{ $t('common.create') }}</el-button>
+      </el-col>
+      <el-col :span="24">
+        <menu-table></menu-table>
+      </el-col>
+    </el-row>
+    <el-dialog v-model="dialog" :title="$t('pages.menus.dialogTitle')" :destroy-on-close="false" @close="toClose">
+      <menu-info></menu-info>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import menuTable from './parts/menu-table.vue'
+import menuInfo from './parts/menu-info.vue'
+import { cloneDeep, get, omit } from 'lodash-es'
+import { UserMenusStore } from '@/store/api/system/userMenus'
+import { DictDataStore } from '@/store/api/system/dictData'
+import { useI18n } from 'vue-i18n'
+const { t } = useI18n()
+const store = UserMenusStore()
+const dictDataStore = DictDataStore()
+const $checkRes = inject('$checkRes')
+const dialog = ref(false)
+const list = ref([])
+const form = ref({})
+const typeList = [
+  // { label: '目录', value: '0' },
+  { label: '页面', value: '1' },
+  { label: '子页面', value: '2' }
+]
+const iconList = ref([])
+onMounted(async () => {
+  await searchOther()
+  await search()
+})
+const searchOther = async () => {
+  const result = await dictDataStore.query({ code: 'icon', is_use: '0' })
+  if ($checkRes(result)) iconList.value = result.data
+}
+// #region 接口函数
+const search = async () => {
+  const res = await store.query()
+  if ($checkRes(res)) {
+    list.value = res.data
+  }
+}
+const toSave = async () => {
+  const data = cloneDeep(omit(form.value, ['children', 'parent_name']))
+  let res
+  if (get(data, 'id')) {
+    res = await store.update(data)
+  } else res = await store.create(data)
+  if ($checkRes(res, true)) {
+    search()
+    toClose()
+  }
+}
+const toDelete = async (row) => {
+  ElMessageBox.confirm(t('common.delete_confirm'), t('common.warning'), {
+    confirmButtonText: t('common.confirm'),
+    cancelButtonText: t('common.cancel'),
+    type: 'warning'
+  }).then(async () => {
+    const res = await store.del(row.id)
+    if ($checkRes(res, true)) {
+      search()
+    }
+  })
+}
+// #endregion
+
+// #region 工具函数
+const toAddNext = (row) => {
+  const obj = { parent_id: row.id, is_use: '0' }
+  form.value = obj
+  dialog.value = true
+}
+const toUpdate = (row) => {
+  form.value = cloneDeep(row)
+  dialog.value = true
+}
+
+const toAdd = () => {
+  dialog.value = true
+  form.value = { is_use: '0' }
+}
+const toClose = () => {
+  form.value = {}
+  dialog.value = false
+}
+// #endregion
+
+// provide
+provide('menuTree', list)
+provide('form', form)
+provide('typeList', typeList)
+provide('toUpdate', toUpdate)
+provide('toAddNext', toAddNext)
+provide('toDelete', toDelete)
+provide('toSave', toSave)
+provide('iconList', iconList)
+</script>
+
+<style scoped></style>

+ 27 - 0
src/views/system/userMenus/parts/menu-info.vue

@@ -0,0 +1,27 @@
+<template>
+  <el-tabs v-model="tab" type="card">
+    <el-tab-pane :label="$t('pages.menus.baseInfo')" name="basic">
+      <info></info>
+    </el-tab-pane>
+
+    <el-tab-pane :label="$t('pages.menus.configInfo')" name="config" v-if="form.type && form.type !== '0'">
+      <!-- 不在功能列表的功能不能被使用 -->
+      <func></func>
+    </el-tab-pane>
+  </el-tabs>
+  <el-row type="flex" justify="space-around" style="margin-top: 10px">
+    <el-col :span="6" style="text-align: center">
+      <el-button @click="toSave" size="small" type="primary">{{ $t('common.save') }}</el-button>
+    </el-col>
+  </el-row>
+</template>
+
+<script setup>
+import info from './parts/info.vue'
+import func from './parts/func.vue'
+const form = inject('form')
+const tab = ref('basic')
+const toSave = inject('toSave')
+</script>
+
+<style scoped></style>

+ 69 - 0
src/views/system/userMenus/parts/menu-table.vue

@@ -0,0 +1,69 @@
+<template>
+  <div id="menu-table">
+    <el-table :data="data" row-key="id" border>
+      <el-table-column align="center" :label="$t('pages.menus.order_num')" sortable prop="order_num" width="80"></el-table-column>
+      <el-table-column align="center" :label="$t('pages.menus.icon')" width="80">
+        <template #default="{ row }"><span :class="['iconfont', row.icon]"></span></template>
+      </el-table-column>
+      <el-table-column align="center" :label="$t('pages.menus.name')" prop="name"></el-table-column>
+      <el-table-column align="center" :label="$t('pages.menus.route_name')" prop="route_name"></el-table-column>
+      <el-table-column align="center" :label="$t('pages.menus.i18n_code')" prop="i18n_code"></el-table-column>
+      <el-table-column align="center" :label="$t('pages.menus.parentName')" prop="parent_name"></el-table-column>
+
+      <el-table-column align="center" :label="$t('pages.menus.path')" prop="path"></el-table-column>
+      <el-table-column align="center" :label="$t('pages.menus.component')" prop="component"></el-table-column>
+      <el-table-column align="center" :label="$t('pages.menus.type')" prop="type" width="100">
+        <template #default="{ row }">{{ getType(row) }} </template>
+      </el-table-column>
+      <el-table-column align="center" :label="$t('pages.menus.is_use')" prop="is_use" width="80">
+        <template #default="{ row }">
+          <el-tag v-if="row.is_use === '0'" type="success"> {{ getStatus(row) }} </el-tag>
+          <el-tag v-else type="info"> {{ getStatus(row) }} </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column align="center" :label="$t('pages.menus.remark')" prop="remark" width="100"> </el-table-column>
+      <el-table-column align="center" :label="$t('common.opera')">
+        <template #default="{ row }">
+          <el-link :underline="false" type="primary" size="mini" @click="toUpdate(row)" style="margin-right: 10px">{{ $t('common.update') }}</el-link>
+          <!-- <el-link :underline="false" type="primary" size="mini" @click="toAddNext(row)" style="margin-right: 10px">
+            {{ $t('pages.menus.addNext') }}
+          </el-link> -->
+          <el-link v-if="row.is_default !== '0'" :underline="false" type="danger" size="mini" @click="toDelete(row)">
+            {{ $t('common.delete') }}
+          </el-link>
+        </template>
+      </el-table-column>
+    </el-table>
+  </div>
+</template>
+
+<script setup>
+const data = inject('menuTree', [])
+const typeList = inject('typeList', [])
+const toUpdate = inject('toUpdate')
+const toAddNext = inject('toAddNext')
+const toDelete = inject('toDelete')
+const { t } = useI18n()
+const getType = (row) => {
+  let word = ''
+  const r = typeList.find((f) => f.value === row.type)
+  if (r) word = r.label
+  return word
+}
+const getStatus = (row) => {
+  let word = ''
+  switch (row.is_use) {
+    case '0':
+      word = t('common.is_use_abled')
+      break
+    case '1':
+      word = t('common.is_use_disabled')
+      break
+
+    default:
+      break
+  }
+  return word
+}
+</script>
+<style scoped></style>

+ 79 - 0
src/views/system/userMenus/parts/parts/func.vue

@@ -0,0 +1,79 @@
+<template>
+  <el-row>
+    <el-col :span="24">
+      <el-collapse v-model="activeNames">
+        <el-collapse-item title="功能列表说明" name="1">
+          <el-table :data="descData">
+            <el-table-column align="center" label="字段" prop="label"></el-table-column>
+            <el-table-column align="center" label="描述" prop="desc"></el-table-column>
+            <el-table-column align="center" label="填写方式" prop="way"></el-table-column>
+            <el-table-column align="center" label="判断方式" prop="judge"></el-table-column>
+            <el-table-column align="center" label="注意" prop="notice"></el-table-column>
+          </el-table>
+          <el-row>
+            <el-col :span="24">ps:</el-col>
+            <el-col :span="24"> 1.多个按钮可以对应同一个方法: e.g.:修改数据 和 单修改数据的状态 可以是多个按钮,但是使用一个接口处理</el-col>
+            <el-col :span="24"> 2.按钮可以没有对应的接口: e.g.: 展示数据且不需要重新查询时,就不需要连接口 </el-col>
+            <el-col :span="24"> 3.按钮对应的接口可以交叉,例如目录的添加下一级功能,这个功能其实是和创建使用一个接口,如果权限中给了创建接口权限,那在接口编码这里,写不写都无所谓,因为会叠加</el-col>
+          </el-row>
+        </el-collapse-item>
+      </el-collapse>
+    </el-col>
+    <el-col :span="24" style="text-align: right; margin: 10px 0">
+      <el-button size="mini" type="primary" @click="toAddConfig()">{{ $t('pages.menus.add_config') }}</el-button>
+    </el-col>
+    <el-col :span="24">
+      <el-table :data="form.config">
+        <el-table-column align="center" :label="$t('pages.menus.config_zh')">
+          <template #default="{ row }">
+            <el-input v-model="row.zh"></el-input>
+          </template>
+        </el-table-column>
+        <el-table-column align="center" :label="$t('pages.menus.config_code')">
+          <template #default="{ row }">
+            <el-input v-model="row.code"></el-input>
+          </template>
+        </el-table-column>
+        <el-table-column align="center" :label="$t('pages.menus.config_controller_code')">
+          <template #default="{ row }">
+            <el-input v-model="row.controller_code"></el-input>
+          </template>
+        </el-table-column>
+        <el-table-column align="center" :label="$t('common.opera')">
+          <template #default="{ $index }">
+            <el-button size="mini" type="danger" @click="deleteConfig($index)">{{ $t('common.delete') }}</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-col>
+  </el-row>
+</template>
+
+<script setup>
+const activeNames = ref([])
+const form = inject('form')
+const toAddConfig = () => {
+  if (!form.value.config) form.value.config = []
+  form.value.config.push({})
+}
+const deleteConfig = (index) => {
+  form.value.config.splice(index, 1)
+}
+const descData = [
+  {
+    label: '按钮编码',
+    desc: '控制页面按钮显示',
+    notice: '填写的编码与函数名称一致',
+    way: '需要填写按钮对应的函数名称',
+    judge: '${基本信息的路由名称}.${页面函数名称} 与用户页面权限对比.有按钮就显示页面.没有就不显示按钮'
+  },
+  {
+    label: '接口编码',
+    desc: '控制接口编码使用',
+    notice: 'controllerCode与route_name保持一致 \n methodName与接口的函数名称一致 \n query查询不需要写methodName部分, 但是另起一个controller函数需要写',
+    way: '需要填写接口的${controllerCode}.${methodName}',
+    judge: '${controller中的controllerCode}.${methodName(函数名)}与权限对比,有就能用,没有就不能用'
+  }
+]
+</script>
+<style scoped></style>

+ 101 - 0
src/views/system/userMenus/parts/parts/info.vue

@@ -0,0 +1,101 @@
+<template>
+  <el-form label-position="left" label-width="100px">
+    <!-- 开发模式下显示该字段.即非开发模式下.所有的目录都是非默认的 -->
+    <el-form-item v-if="is_dev" :label="$t('pages.menus.is_default')">
+      <el-radio-group v-model="form.is_default">
+        <el-radio label="0">{{ $t('common.yes') }}</el-radio>
+        <el-radio label="1">{{ $t('common.no') }}</el-radio>
+      </el-radio-group>
+    </el-form-item>
+    <el-form-item :label="$t('pages.menus.name')">
+      <el-input v-model="form.name" :placeholder="$t('pages.menus.namePh')"></el-input>
+    </el-form-item>
+    <el-form-item :label="$t('pages.menus.route_name')">
+      <el-input v-model="form.route_name" :placeholder="$t('pages.menus.route_namePh')"></el-input>
+    </el-form-item>
+    <el-form-item :label="$t('pages.menus.i18n_code')">
+      <el-input v-model="form.i18n_code" :placeholder="$t('pages.menus.i18n_codePh')"></el-input>
+    </el-form-item>
+    <el-form-item :label="$t('pages.menus.type')">
+      <el-select v-model="form.type" :placeholder="$t('pages.menus.typePh')">
+        <el-option v-for="(i, index) in typeList" :key="`t${index}`" :label="i.label" :value="i.value"></el-option>
+      </el-select>
+    </el-form-item>
+    <el-form-item :label="$t('pages.menus.parentName')">
+      <el-select v-model="form.parent_id" placeholder="" :disabled="true">
+        <el-option v-for="(i, index) in getOneDimensionList()" :key="`m${index}`" :label="i.name" :value="i.id"></el-option>
+      </el-select>
+    </el-form-item>
+    <template v-if="form.type === '1' || form.type === '2'">
+      <el-form-item :label="$t('pages.menus.path')">
+        <el-input v-model="form.path" :placeholder="$t('pages.menus.pathPh')"></el-input>
+      </el-form-item>
+      <el-form-item :label="$t('pages.menus.component')">
+        <el-input v-model="form.component" :placeholder="$t('pages.menus.componentPh')"></el-input>
+      </el-form-item>
+    </template>
+    <el-form-item :label="$t('pages.menus.order_num')">
+      <el-input-number v-model="form.order_num"></el-input-number>
+    </el-form-item>
+    <el-form-item :label="$t('pages.menus.icon')">
+      <el-select v-model="form.icon" clearable filterable :placeholder="$t('pages.menus.iconPh')">
+        <el-option v-for="item in iconList" :key="item.label" :label="item.label" :value="item.label">
+          <div>
+            <component class="icon" :is="item.label"></component>
+            <span style="font-size: 12px">{{ item.label }}</span>
+          </div>
+        </el-option>
+      </el-select>
+    </el-form-item>
+    <el-form-item :label="$t('pages.menus.is_use')">
+      <el-radio-group v-model="form.is_use">
+        <el-radio label="0">{{ $t('common.is_use_abled') }}</el-radio>
+        <el-radio label="1">{{ $t('common.is_use_disabled') }}</el-radio>
+      </el-radio-group>
+    </el-form-item>
+    <el-form-item :label="$t('pages.menus.remark')">
+      <el-input v-model="form.remark" :placeholder="$t('pages.menus.remarkPh')" type="textarea" :autosize="{ minRows: 5, maxRows: 5 }"></el-input>
+    </el-form-item>
+  </el-form>
+</template>
+
+<script setup>
+import { cloneDeep } from 'lodash-es'
+const menuTree = inject('menuTree')
+const typeList = inject('typeList')
+const form = inject('form')
+const iconList = inject('iconList')
+const is_dev = import.meta.env.MODE === 'development'
+
+// #region 整理数组
+const getOneDimensionList = () => {
+  let dup = cloneDeep(menuTree.value)
+  let arr = getAllChild(dup)
+  return arr
+}
+
+const getAllChild = (children) => {
+  let arr = []
+  for (const i of children) {
+    const { children, ...others } = i
+    arr.push(others)
+    if (children) {
+      const marr = getAllChild(children)
+      arr.push(...marr)
+    }
+  }
+  return arr
+}
+// #endregion
+</script>
+<style scoped lang="scss">
+.icon {
+  display: inline-block;
+  width: 1em;
+  height: 1em;
+  overflow: hidden;
+  vertical-align: -0.15em;
+  outline: none;
+  margin-right: 5px;
+}
+</style>