vue3中的增删改查和按钮的权限管理

配置search

page-search.vue

1.Form 表单
Form 属性

  • model:表单数据对象
  • label-width:标签的长度, 作为 Form 直接子元素的 form-item 会继承该值
  • size:用于控制该表单内组件的尺寸’large’ | ‘default’ | ‘small’

Form Item 属性

  • label:标签文本
  • prop:model 的键名。 在定义了 validate、resetFields 的方法时,该属性是必填的

2.Layout 布局
默认使用 Flex 布局,通过 row 和 col 组件,并通过 col 组件的 span 属性我们就可以自由地组合布局,默认 24 分栏
Row 属性:行

  • gutter:栅格间隔

Col 属性:列

  • span:栅格占据的列数

3.Input 输入框
Input 属性

  • v-model:绑定值,使标签可以输入
  • placeholder:输入框占位文本

4.DatePicker 日期选择器

  • v-model:选中项绑定值
  • type:显示类型year/month/date/dates/datetime/ week/datetimerange/daterange/ monthrange
  • range-separator:选择范围时的分隔符
  • start-placeholder:范围选择时开始日期的占位内容
  • end-placeholder:范围选择时结束日期的占位内容

5.Select 选择器
Select 属性

  • v-model:选中项绑定值

Option 属性

  • label:选项的标签,若不设置则默认与value相同
  • value:选项的值
//components/page-search/page-search.vue
<template>
  <div class="search" v-if="isQuery">
    <!-- 1.输入搜索关键字的表单 -->
    <el-form
      :model="searchForm"
      ref="formRef"
      :label-width="searchConfig.labelWidth ?? '80px'"
      size="large"
    >
      <el-row :gutter="20">
        <template v-for="item in searchConfig.formItems" :key="item.prop">
          <el-col :span="8">
            <el-form-item :label="item.label" :prop="item.prop">
              <template v-if="item.type === 'input'">
                <el-input
                  v-model="searchForm[item.prop]"
                  :placeholder="item.placeholder"
                />
              </template>
              <template v-if="item.type === 'date-picker'">
                <el-date-picker
                  v-model="searchForm[item.prop]"
                  type="daterange"
                  range-separator="-"
                  start-placeholder="开始时间"
                  end-placeholder="结束时间"
                />
              </template>
              <template v-if="item.type === 'select'">
                <el-select
                  v-model="searchForm[item.prop]"
                  :placeholder="item.placeholder"
                  style="width: 100%"
                >
                  <template v-for="option in item.options" :key="option.value">
                    <el-option :label="option.label" :value="option.value" />
                  </template>
                </el-select>
              </template>
            </el-form-item>
          </el-col>
        </template>
      </el-row>
    </el-form>

    <!-- 2.重置和搜索的按钮 -->
    <div class="btns">
      <el-button icon="Refresh" @click="handleResetClick">重置</el-button>
      <el-button icon="Search" type="primary" @click="handleQueryClick"
        >查询</el-button
      >
    </div>
  </div>
</template>

<script setup lang="ts">
import { reactive, ref } from 'vue'
import type { ElForm } from 'element-plus'
import usePermissions from '@/hooks/usePermissions'

// 定义自定义事件/接收的属性
interface IProps {
  searchConfig: {
    pageName: string
    labelWidth?: string
    formItems: any[]
  }
}
const emit = defineEmits(['queryClick', 'resetClick'])
const props = defineProps<IProps>()
//const props = defineProps({
//searchConfig:{
//type:Object,
//default
//}
//})

// 获取权限
const isQuery = usePermissions(`${props.searchConfig.pageName}:query`)

// 定义form的数据
const initialForm: any = {}
for (const item of props.searchConfig.formItems) {
  initialForm[item.prop] = item.initialValue ?? ''//初始化数据
}
const searchForm = reactive(initialForm)
//const searchForm=reactive({
//name:'',
//leader:''.
//createAt:''
//})

// 重置操作
const formRef = ref<InstanceType<typeof ElForm>>()
function handleResetClick() {
  // 1.form中的数据全部重置
  formRef.value?.resetFields()

  // 2.将事件出去, content内部重新发送网络请求
  emit('resetClick')
}

function handleQueryClick() {
  emit('queryClick', searchForm)
}
</script>

<style lang="less" scoped>
.search {
  background-color: #fff;
  padding: 20px;

  .el-form-item {
    padding: 20px 30px;
    margin-bottom: 0;
  }

  .btns {
    text-align: right;
    padding: 0 50px 10px 0;

    .el-button {
      height: 36px;
    }
  }
}
</style>

date picker日期选择器 转换为中文

  //App.vue
  <div class="app">
    <el-config-provider :locale="zhCn">
      <router-view></router-view>
    </el-config-provider>
  </div>
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
//  .mjs文件声明
//env.d.ts
declare module '*.mjs'

serch.config.ts

//  views/main/system/departent/config/search.config.ts
const searchConfig = {
  pageName: 'department',
  formItems: [
    {
      type: 'input',
      prop: 'name',
      label: '部门名称',
      placeholder: '请输入查询的部门名称',
      initialValue: 'bbb'
    },
    {
      type: 'input',
      prop: 'leader',
      label: '部门领导',
      placeholder: '请输入查询的领导名称'
    },
    {
      type: 'date-picker',
      prop: 'createAt',
      label: '创建时间'
    },{
     type: 'select',
      prop: 'enable',
      label: '状态',
     placeholder:"请选择查询的状态",
     options:[
    {label:'启动',value:1},
    {label:'禁用',value:0}
]
}
  ]
}

export default searchConfig

department.vue

<div class="department">
<page-search
      :search-config="searchConfig"
      @query-click="handleQueryClick"
      @reset-click="handleResetClick"
    />
    </div>
import searchConfig from './config/search.config'
import PageSearch from '@/components/page-search/page-search.vue'

配置content

page-content.vue

1.Table 表格
Table 属性

  • data:显示的数据
  • border:是否带有纵向边框

Table-column 属性

  • align:对齐方式
  • type:对应列的类型。 如果设置了selection则显示多选框; 如果设置了 index 则显示该行的索引(从 1 开始计算); 如果设置了 expand 则显示为一个可展开的按钮
  • label:column 的 key
  • prop:字段名称 对应列内容的字段名

Table-column 插槽

  • default:自定义列的内容
  • header:自定义表头的内容

2.Pagination 分页
属性

  • page-size:每页显示条目个数,支持 v-model 双向绑定
  • current-page:当前页数,支持 v-model 双向绑定
  • total:总条目数
  • layout:组件布局,子组件名用逗号分隔total/sizes / prev / pager / next / jumper /

事件

  • @size-change: pageSize 改变时触发
  • @current-change:current-change 改变时触发
//components/page-content/page-content.vue
<template>
  <div class="content">
  //头部
    <div class="header">
      <h3 class="title">{{ contentConfig?.header?.title ?? '数据列表' }}</h3>
      <el-button v-if="isCreate" type="primary" @click="handleNewUserClick">
        {{ contentConfig?.header?.btnTitle ?? '新建数据' }}
      </el-button>
    </div>
    //表格
    <div class="table">
      <el-table
        :data="pageList"
        border
        style="width: 100%"
        v-bind="contentConfig.childrenTree"
      >
        <template v-for="item in contentConfig.propsList" :key="item.prop">
          <template v-if="item.type === 'timer'">
          //v-bind="item"等价于 :label="item.label" :prop="item.prop" :width="item.width"
            <el-table-column align="center" v-bind="item">
              <template #default="scope">
                {{ formatUTC(scope.row[item.prop]) }}
              </template>
            </el-table-column>
          </template>
          <template v-else-if="item.type === 'handler'">
            <el-table-column align="center" v-bind="item">
              <template #default="scope">
                <el-button
                  v-if="isUpdate"
                  size="small"
                  icon="Edit"
                  type="primary"
                  text
                  @click="handleEditBtnClick(scope.row)"
                >
                  编辑
                </el-button>
                <el-button
                  v-if="isDelete"
                  size="small"
                  icon="Delete"
                  type="danger"
                  text
                  @click="handleDeleteBtnClick(scope.row.id)"
                >
                  删除
                </el-button>
              </template>
            </el-table-column>
          </template>
          //自定义类型
          <template v-else-if="item.type === 'custom'">
            <el-table-column align="center" v-bind="item">
              <template #default="scope">
                <slot
                  :name="item.slotName"//具名插槽
                  v-bind="scope"
                  :prop="item.prop"
                  hName="why"
                ></slot>
                //-{{scope.row[item.prop]}}-
              </template>
            </el-table-column>
          </template>
          <template v-else>
            <el-table-column align="center" v-bind="item" />
          </template>
          //状态
          // <el-table-column
         // align="center"
         // label="状态"
         // prop="enable"
         // width="100px"
       // >
        //  <!-- 作用域插槽 -->
         // <template #default="scope">
              //plain镂空效果
         //   <el-button
          //    size="small"
          //    :type="scope.row.enable ? 'primary' : 'danger'"
           //   plain
          //  >
          //    {{ scope.row.enable ? '启用' : '禁用' }}
         //   </el-button>
         // </template>
       // </el-table-column>
        </template>
      </el-table>
    </div>
    //分页器
    <div class="pagination">
      <el-pagination
        v-model:current-page="currentPage"
        v-model:page-size="pageSize"
        :page-sizes="[10, 20, 30]"
        layout="total, sizes, prev, pager, next, jumper"
        :total="pageTotalCount"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
      />
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { storeToRefs } from 'pinia'
import useSystemStore from '@/store/main/system/system'
import { formatUTC } from '@/utils/format'
import usePermissions from '@/hooks/usePermissions'

interface IProps {
  contentConfig: {
    pageName: string
    header?: {
      title?: string
      btnTitle?: string
    }
    propsList: any[]
    childrenTree?: any
  }
}

const props = defineProps<IProps>()

// 定义事件
const emit = defineEmits(['newClick', 'editClick'])

// 0.获取是否有对应的增删改查的权限
const isCreate = usePermissions(`${props.contentConfig.pageName}:create`)
const isDelete = usePermissions(`${props.contentConfig.pageName}:delete`)
const isUpdate = usePermissions(`${props.contentConfig.pageName}:update`)
const isQuery = usePermissions(`${props.contentConfig.pageName}:query`)

// 1.发起action,请求usersList的数据
const systemStore = useSystemStore()
const currentPage = ref(1)
const pageSize = ref(10)
//新建,编辑,删除都将分页器页数设置为第一页,监听store中的action事件
systemStore.$onAction(({ name, after }) => {
  after(() => {
    if (
      name === 'deletePageByIdAction' ||
      name === 'editPageDataAction' ||
      name === 'newPageDataAction'
    ) {
      currentPage.value = 1
    }
  })
})
fetchPageListData()

// 2.获取usersList数据,进行展示
const { pageList, pageTotalCount } = storeToRefs(systemStore)

// 3.页码相关的逻辑
function handleSizeChange() {
  fetchPageListData()
}
function handleCurrentChange() {
  fetchPageListData()
}

// 4.定义函数, 用于发送网络请求
function fetchPageListData(formData: any = {}) {
  if (!isQuery) return

  // 1.获取offset/size
  const size = pageSize.value
  const offset = (currentPage.value - 1) * size
  const pageInfo = { size, offset }

  // 2.发起网络请求
  const queryInfo = { ...pageInfo, ...formData }
  systemStore.postPageListAction(props.contentConfig.pageName, queryInfo)
}

// 5.删除/新建/编辑的操作
function handleDeleteBtnClick(id: number) {
  systemStore.deletePageByIdAction(props.contentConfig.pageName, id)
}
function handleNewUserClick() {
  emit('newClick')
}
function handleEditBtnClick(itemData: any) {
  emit('editClick', itemData)
}

// 6.监听systemStore中的actions被执行
defineExpose({ fetchPageListData })
</script>

<style lang="less" scoped>
.content {
  margin-top: 20px;
  padding: 20px;
  background-color: #fff;
}

.header {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  margin-bottom: 10px;

  .title {
    font-size: 22px;
  }
}

.table {
  :deep(.el-table__cell) {
    padding: 12px 0;
  }

  .el-button {
    margin-left: 0;
    padding: 5px 8px;
  }
}

.pagination {
  display: flex;
  justify-content: flex-end;
  margin-top: 10px;
}
</style>

封装API

// service/main/system/system.ts
import hyRequest from '@/service'

/** 用户列表API */
export function postUsersListData(queryInfo: any) {
  return hyRequest.post({
    url: '/users/list',
    data: queryInfo
    //data:{
    //offset:0,
    //size:10
    //}
  })
}

export function deleteUserById(id: number) {
  return hyRequest.delete({
    url: `/users/${id}`
  })
}

export function newUserData(userInfo: any) {
  return hyRequest.post({
    url: '/users',
    data: userInfo
  })
}

export function editUserData(id: number, userInfo: any) {
  return hyRequest.patch({
    url: `/users/${id}`,
    data: userInfo
  })
}

/** 针对页面的网络请求: 增删改查 */
export function postPageListData(pageName: string, queryInfo: any) {
  return hyRequest.post({
    url: `/${pageName}/list`,
    data: queryInfo
  })
}

export function deletePageById(pageName: string, id: number) {
  return hyRequest.delete({
    url: `/${pageName}/${id}`
  })
}

export function newPageData(pageName: string, pageInfo: any) {
  return hyRequest.post({
    url: `/${pageName}`,
    data: pageInfo
  })
}

export function editPageData(pageName: string, id: number, pageInfo: any) {
  return hyRequest.patch({
    url: `/${pageName}/${id}`,
    data: pageInfo
  })
}

发送请求

//store/main/system/system.ts
import {
  deletePageById,
  deleteUserById,
  editPageData,
  editUserData,
  newPageData,
  newUserData,
  postPageListData,
  postUsersListData
} from '@/service/main/system/system'
import { defineStore } from 'pinia'
import useMainStore from '../main'
import type { ISystemState } from './type'
//export interface IUser {
//  id: number
//  name: string
//  realname: string
//  cellphone: number
 // enable: number
//  departmentId: number
//  roleId: number
//  createAt: string
//  updateAt: string
//}

//export interface ISystemState {
 // usersList: IUser[]
 // usersTotalCount: number

 // pageList: any[]
 // pageTotalCount: number
//}


const useSystemStore = defineStore('system', {
  state: (): ISystemState => ({
    usersList: [],//用户列表数据
    usersTotalCount: 0,

    pageList: [],
    pageTotalCount: 0
  }),
  actions: {
  //获取用户列表数据
    async postUsersListAction(queryInfo: any) {
      const usersListResult = await postUsersListData(queryInfo)
      const { totalCount, list } = usersListResult.data
      this.usersTotalCount = totalCount
      this.usersList = list
    },
    //
    async deleteUserByIdAction(id: number) {
      // 1.删除数据操作
      const deleteResult = await deleteUserById(id)
      console.log(deleteResult)

      // 2.重新请求新的数据
      this.postUsersListAction({ offset: 0, size: 10 })
    },
    async newUserDataAction(userInfo: any) {
      // 1.创建新的用户
      const newResult = await newUserData(userInfo)
      console.log(newResult)

      // 2.重新请求新的数据
      this.postUsersListAction({ offset: 0, size: 10 })
    },
    async editUserDataAction(id: number, userInfo: any) {
      // 1.更新用户的数据
      const editResult = await editUserData(id, userInfo)
      console.log(editResult)

      // 2.重新请求新的数据
      this.postUsersListAction({ offset: 0, size: 10 })
    },

    /** 针对页面的数据: 增删改查 */
    async postPageListAction(pageName: string, queryInfo: any) {
      const pageListResult = await postPageListData(pageName, queryInfo)
      const { totalCount, list } = pageListResult.data

      this.pageList = list
      this.pageTotalCount = totalCount
    },
    async deletePageByIdAction(pageName: string, id: number) {
      const deleteResult = await deletePageById(pageName, id)
      console.log(deleteResult)
      this.postPageListAction(pageName, { offset: 0, size: 10 })

      // 获取完整的数据
      const mainStore = useMainStore()
      mainStore.fetchEntireDataAction()
    },
    async newPageDataAction(pageName: string, pageInfo: any) {
      const newResult = await newPageData(pageName, pageInfo)
      console.log(newResult)
      this.postPageListAction(pageName, { offset: 0, size: 10 })

      // 获取完整的数据
      const mainStore = useMainStore()
      mainStore.fetchEntireDataAction()
    },
    async editPageDataAction(pageName: string, id: number, pageInfo: any) {
      const editResult = await editPageData(pageName, id, pageInfo)
      console.log(editResult)
      this.postPageListAction(pageName, { offset: 0, size: 10 })

      // 获取完整的数据
      const mainStore = useMainStore()
      mainStore.fetchEntireDataAction()
    }
  }
})

export default useSystemStore

UTC时间格式化

npm install dayjs

//  utils/format.ts
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'

dayjs.extend(utc)

export function formatUTC(
  utcString: string,
  format: string = 'YYYY/MM/DD HH:mm:ss'
) {
  const resultTime = dayjs.utc(utcString).utcOffset(8).format(format)//+8小时:零时区转东八区时间
  return resultTime
}

content.config.ts

const contentConfig = {
  pageName: 'department',
  header: {
    title: '部门列表',
    btnTitle: '新建部门'
  },
  propsList: [
    // 1.selection 2.index
    { type: 'selection', label: '选择', width: '80px' },
    { type: 'index', label: '序号', width: '80px' },

    { type: 'normal', label: '部门名称', prop: 'name', width: '150px' },
    { type: 'normal', label: '部门领导', prop: 'leader', width: '150px' },
    { type: 'normal', label: '上级部门', prop: 'parentId', width: '150px' },
//自定义
    // {
    //   type: 'custom',
    //   label: '部门领导',
    //   prop: 'leader',
    //   width: '150px',
    //   slotName: 'leader'
    // },
    // {
    //   type: 'custom',
    //   label: '上级部门',
    //   prop: 'parentId',
    //   width: '150px',
    //   slotName: 'parent'
    // },

    { type: 'timer', label: '创建时间', prop: 'createAt' },
    { type: 'timer', label: '更新时间', prop: 'updateAt' },

    { type: 'handler', label: '操作', width: '150px' }
  ],
  childrenTree: {
    rowKey: 'id',  //子菜单,存在时不能用type
    treeProps: {
      children: 'children'
    }
  }
}

export default contentConfig

department.vue

 <page-content
      :content-config="contentConfig"
      ref="contentRef"
      @new-click="handleNewClick"
      @edit-click="handleEditClick"
    >
      <template #leader="scope">
        <span class="leader">哈哈哈: {{ scope.row[scope.prop] }}</span>
      </template>
      <template #parent="scope">
        <span class="parent">呵呵呵: {{ scope.row[scope.prop] }}</span>
      </template>
    </page-content>

配置modal

page-modal.vue

1.Dialog 对话框
属性
v-model:是否显示 Dialog
title:Dialog 对话框 的标题
插槽
header:对话框标题的内容;会替换标题部分,但不会移除关闭按钮。
footer:Dialog 按钮操作区的内容
2.Form 表单
Form 属性

  • model:表单数据对象
  • label-width:标签的长度, 作为 Form 直接子元素的 form-item 会继承该值
  • size:用于控制该表单内组件的尺寸’large’ | ‘default’ | ‘small’

Form Item 属性

  • label:标签文本
  • prop:model 的键名。 在定义了 validate、resetFields 的方法时,该属性是必填的

3.DatePicker 日期选择器

  • v-model:选中项绑定值
  • type:显示类型year/month/date/dates/datetime/ week/datetimerange/daterange/ monthrange
  • range-separator:选择范围时的分隔符
  • start-placeholder:范围选择时开始日期的占位内容
  • end-placeholder:范围选择时结束日期的占位内容

4.Select 选择器
Select 属性

  • v-model:选中项绑定值

Option 属性

  • label:选项的标签,若不设置则默认与value相同
  • value:选项的值
<template>
  <div class="modal">
    <el-dialog
      v-model="dialogVisible"
      :title="
        isNewRef ? modalConfig.header.newTitle : modalConfig.header.editTitle
      "
      width="30%"
      center
    >
      <div class="form">
        <el-form :model="formData" label-width="80px" size="large">
          <template v-for="item in modalConfig.formItems" :key="item.prop">
            <el-form-item :label="item.label" :prop="item.prop">
              <template v-if="item.type === 'input'">
                <el-input
                  v-model="formData[item.prop]"
                  :placeholder="item.placeholder"
                />
              </template>
              <template v-if="item.type === 'date-picker'">
                <el-date-picker
                  v-model="formData[item.prop]"
                  type="daterange"
                  range-separator="-"
                  start-placeholder="开始时间"
                  end-placeholder="结束时间"
                />
              </template>
              <template v-if="item.type === 'select'">
                <el-select
                  v-model="formData[item.prop]"
                  :placeholder="item.placeholder"
                  style="width: 100%"
                >
                  <template v-for="option in item.options" :key="option.value">
                    <el-option :label="option.label" :value="option.value" />
                  </template>
                </el-select>
              </template>
              <template v-if="item.type === 'custom'">
                <slot :name="item.slotName"></slot>
              </template>
            </el-form-item>
          </template>
        </el-form>
      </div>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="dialogVisible = false">取消</el-button>
          <el-button type="primary" @click="handleConfirmClick">
            确定
          </el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>

<script setup lang="ts">
import { reactive, ref } from 'vue'
import useSystemStore from '@/store/main/system/system'

interface IModalProps {
  modalConfig: {
    pageName: string
    header: {
      newTitle: string
      editTitle: string
    }
    formItems: any[]
  }
  otherInfo?: any//树结构数据
}

// 0.定义props
const props = defineProps<IModalProps>()

// 1.定义内部的属性
const dialogVisible = ref(false)
const initialData: any = {}
for (const item of props.modalConfig.formItems) {
  initialData[item.prop] = item.initialValue ?? ''
}
const formData = reactive<any>(initialData)
const isNewRef = ref(true)
const editData = ref()

// 2.获取roles/departments数据
const systemStore = useSystemStore()

// 2.定义设置dialogVisible方法
function setModalVisible(isNew: boolean = true, itemData?: any) {
  dialogVisible.value = true
  isNewRef.value = isNew
  if (!isNew && itemData) {
    // 编辑数据
    for (const key in formData) {
      formData[key] = itemData[key]
    }
    editData.value = itemData//用于记录状态
  } else {
    // 新建数据
    for (const key in formData) {
      const item = props.modalConfig.formItems.find((item) => item.prop === key)//初始化值
      formData[key] = item ? item.initialValue : ''
    }
    editData.value = null
  }
}

// 3.点击了确定的逻辑
function handleConfirmClick() {
  dialogVisible.value = false

  let infoData = formData
  if (props.otherInfo) {//合并树结构menulist数据
    infoData = { ...infoData, ...props.otherInfo }
  }

  if (!isNewRef.value && editData.value) {
    // 编辑用户的数据
    systemStore.editPageDataAction(
      props.modalConfig.pageName,
      editData.value.id,
      infoData
    )
  } else {
    // 创建新的部门
    systemStore.newPageDataAction(props.modalConfig.pageName, infoData)
  }
}

// 暴露的属性和方法
defineExpose({ setModalVisible })
</script>

<style lang="less" scoped>
.form {
  padding: 0 20px;
}
</style>

modal.config.ts

import type { IModalConfig } from '@/components/page-modal/type'
//interface IModalConfig {
 //   pageName: string
//    header: {
 //     newTitle: string
  //    editTitle: string
 //   }
 //   formItems: any[]
 // }
//  otherInfo?: any
// }

const modalConfig: IModalConfig = {
  pageName: 'department',
  header: {
    newTitle: '新建部门',
    editTitle: '编辑部门'
  },
  formItems: [
    {
      type: 'input',
      label: '部门名称',
      prop: 'name',
      placeholder: '请输入部门名称'
    },
    {
      type: 'input',
      label: '部门领导',
      prop: 'leader',
      placeholder: '请输入部门领导'
    },
    {
      type: 'select',
      label: '上级部门',
      prop: 'parentId',
      placeholder: '请选择上级部门',
      options: []
    }.
       {
      type: 'custom',
       slotName:'menulist'
    }
  ]
}

export default modalConfig

department.vue

<page-modal :modal-config="modalConfigRef" ref="modalRef" />

department.vue

Tree 树形控件
属性
data:展示数据
node-key:每个树节点用来作为唯一标识的属性,整棵树应该是唯一的
show-checkbox:节点是否可被选择
props:配置选项

  • label:指定节点标签为节点对象的某个属性值
  • children:指定子树为节点对象的某个属性值

@check:点击节点复选框之后触发
setCheckedKeys:设置目前选中的节点,使用此方法必须有node-key属性

<template>
  <div class="department">
    <page-search
      :search-config="searchConfig"
      @query-click="handleQueryClick"
      @reset-click="handleResetClick"
    />
    <page-content
      :content-config="contentConfig"
      ref="contentRef"
      @new-click="handleNewClick"
      @edit-click="handleEditClick"
    >
      <template #leader="scope">//scope获取插槽中的数据
        <span class="leader">哈哈哈: {{ scope.row[scope.prop] }}</span>
        <span>{{scope.hName}}</span>
      </template>
      <template #parent="scope">
        <span class="parent">呵呵呵: {{ scope.row[scope.prop] }}</span>
      </template>
    </page-content>
    <page-modal :modal-config="modalConfigRef" ref="modalRef" >
     <template #menulist>
        <el-tree
          ref="treeRef"
          :data="entireMenus"
          show-checkbox
          node-key="id"
          :props="{ children: 'children', label: 'name' }"
          @check="handleElTreeCheck"
        />
      </template>
    </page-modal>
  </div>
</template>

<script setup lang="ts" name="department">
import { computed } from 'vue'
import useMainStore from '@/store/main/main'

import PageSearch from '@/components/page-search/page-search.vue'
import PageContent from '@/components/page-content/page-content.vue'
import PageModal from '@/components/page-modal/page-modal.vue'

import searchConfig from './config/search.config'
import contentConfig from './config/content.config'
import modalConfig from './config/modal.config'
import usePageContent from '@/hooks/usePageContent'
import usePageModal from '@/hooks/usePageModal'

// 对modalConfig进行操作
//获取整个部门数据
const modalConfigRef = computed(() => {
  const mainStore = useMainStore()
  const departments = mainStore.entireDepartments.map((item) => {
    return { label: item.name, value: item.id }
  })
  modalConfig.formItems.forEach((item) => {
    if (item.prop === 'parentId') {
      item.options.push(...departments)
    }
  })

  return modalConfig
})

// setup相同的逻辑的抽取: hooks
// 点击search, content的操作
const { contentRef, handleQueryClick, handleResetClick } = usePageContent()

// 点击content, modal的操作
const { modalRef, handleNewClick, handleEditClick } = usePageModal()

// 获取完整的菜单(role.vue中需要)
const mainStore = useMainStore()
const { entireMenus } = storeToRefs(mainStore)
const otherInfo = ref({})//树结构的数据,未配置
function handleElTreeCheck(data1: any, data2: any) {
  const menuList = [...data2.checkedKeys, ...data2.halfCheckedKeys]//子节点id,父节点id
  console.log(data2.checkedKeys)
  otherInfo.value = { menuList }
}

const treeRef = ref<InstanceType<typeof ElTree>>()
//针对树结构数据的回调
function newCallback() {
  nextTick(() => {
    treeRef.value?.setCheckedKeys([])
  })
}
function editCallback(itemData: any) {
  nextTick(() => {
    const menuIds = mapMenuListToIds(itemData.menuList)
    treeRef.value?.setCheckedKeys(menuIds)
  })
}
</script>

<style scoped>
.leader {
  color: red;
}

.parent {
  color: blue;
}
</style>

mapMenuListToIds方法封装

//utils/map-menus.ts
export function mapMenuListToIds(menuList: any[]) {
  const ids: number[] = []

  function recurseGetId(menus: any[]) {
    for (const item of menus) {
      if (item.children) {
        recurseGetId(item.children)//递归
      } else {
        ids.push(item.id)
      }
    }
  }
  recurseGetId(menuList)

  return ids
}

获取选择器整个数据

选择器数据API封装

import hyRequest from '..'

export function getEntireRoles() {
  return hyRequest.post({
    url: '/role/list'
  })
}

export function getEntireDepartments() {
  return hyRequest.post({
    url: '/department/list'
  })
}

export function getEntireMenus() {
  return hyRequest.post({
    url: '/menu/list'
  })
}

调用

//store/main/main.ts
import {
  getEntireDepartments,
  getEntireMenus,
  getEntireRoles
} from '@/service/main/main'
import { defineStore } from 'pinia'

interface IMainState {
  entireRoles: any[]
  entireDepartments: any[]
  entireMenus: any[]
}

const useMainStore = defineStore('main', {
  state: (): IMainState => ({
    entireRoles: [],
    entireDepartments: [],
    entireMenus: []
  }),
  actions: {
    async fetchEntireDataAction() {
      const rolesResult = await getEntireRoles()
      const departmentsResult = await getEntireDepartments()
      const menuResult = await getEntireMenus()

      // 保存数据
      this.entireRoles = rolesResult.data.list
      this.entireDepartments = departmentsResult.data.list
      this.entireMenus = menuResult.data.list
    }
  }
})

export default useMainStore

页面相关逻辑

usePageContent.ts

//   hooks/usePageConten.ts
import { ref } from 'vue'
import type PageContent from '@/components/page-content/page-content.vue'

function usePageContent() {
  const contentRef = ref<InstanceType<typeof PageContent>>()
  function handleQueryClick(queryInfo: any) {
    contentRef.value?.fetchPageListData(queryInfo)
  }
  function handleResetClick() {
    contentRef.value?.fetchPageListData()
  }

  return {
    contentRef,
    handleQueryClick,
    handleResetClick
  }
}

export default usePageContent

usePageModal.ts

import { ref } from 'vue'
import type PageModal from '@/components/page-modal/page-modal.vue'

type CallbackFnType = (data?: any) => void

function usePageModal(
  newCallback?: CallbackFnType,
  editCallback?: CallbackFnType
) {
  const modalRef = ref<InstanceType<typeof PageModal>>()
  function handleNewClick() {
    modalRef.value?.setModalVisible()
    if (newCallback) newCallback()
  }
  function handleEditClick(itemData: any) {
    // 1.让modal显示出来
    modalRef.value?.setModalVisible(false, itemData)
    // 2.编辑的回调
    if (editCallback) editCallback(itemData)
  }

  return { modalRef, handleNewClick, handleEditClick }
}

export default usePageModal

按钮权限管理

封装映射方法

//  utils/map-menus.ts
/**
 * 从菜单映射到按钮的权限
 * @param menuList 菜单的列表
 * @returns 权限的数组(字符串数组)
 */
export function mapMenusToPermissions(menuList: any[]) {
  const permissions: string[] = []

  function recurseGetPermission(menus: any[]) {
    for (const item of menus) {
      if (item.type === 3) {
      //permission :"system:users:create"
        permissions.push(item.permission)
      } else {
        recurseGetPermission(item.children ?? [])
      }
    }
  }
  recurseGetPermission(menuList)

  return permissions
}

使用方法

//  store/login.ts
async loginAccountAction(account: IAccount) {
// 重要: 获取登录用户的所有按钮的权限
      const permissions = mapMenusToPermissions(userMenus)
      this.permissions = permissions
}
 loadLocalCacheAction() {
 // 2.获取按钮的权限
        const permissions = mapMenusToPermissions(userMenus)
        this.permissions = permissions
}

根据权限判断按钮是否展示

// hookes/usePermissions.ts
import useLoginStore from '@/store/login/login'
//permissionID  'development:create'
function usePermissions(permissionID: string) {
  const loginStore = useLoginStore()
  const { permissions } = loginStore
//转换为布尔类型
  return !!permissions.find((item) => item.includes(permissionID))
}

export default usePermissions

//  page-content.vue
V-if="iscreate"
import usePermissions from '@/hooks/usePermissions'
// 0.获取是否有对应的增删改查的权限
const isCreate = usePermissions(`${props.contentConfig.pageName}:create`)
const isDelete = usePermissions(`${props.contentConfig.pageName}:delete`)
const isUpdate = usePermissions(`${props.contentConfig.pageName}:update`)
const isQuery = usePermissions(`${props.contentConfig.pageName}:query`)
// 4.定义函数, 用于发送网络请求
function fetchPageListData(formData: any = {}) {
  if (!isQuery) return
...
}
  • 13
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Vue-element-admin 是一个基于 Vue.js 和 Element UI 的后台管理系统解决方案,其功能包括登录、权限管理、Dashboard、表单等常见的后台管理系统功能。 要进行增删查的操作,需要先了解 Vue-element-admin 的代码结构和数据流,以下是一些常用的操作指南: 1. 添加新页面:可以在 src/views 目录下新建一个.vue 文件,然后在 src/router/index.js 添加路由。 2. 修页面:直接修 src/views 目录下的相应.vue 文件即可。 3. 删除页面:删除 src/views 目录下的相应.vue 文件,并在 src/router/index.js 删除相应的路由即可。 4. 增加/修 API 接口:在 src/api 目录下添加/修对应的.js 文件即可。 5. 增加/修菜单项:在 src/layout/components/Sidebar/index.vue 文件找到对应的位置,添加/修菜单项。 6. 增加/修权限:在 src/permission.js 文件对应的权限即可。 需要注意的是,在修 Vue-element-admin 的代码时,建议先熟悉 Vue.js 和 Element UI 的使用,以及了解前端的基本知识。此外,修代码前最好先备份原有代码,以防止出现不可预料的错误。 ### 回答2: vue-element-admin是一个基于Vue.js和Element UI的后台管理系统,它提供了一套完整的后台管理模板和常用组件,方便开发者快速开发管理系统。 在vue-element-admin,实现增删查功能是非常常见的需求。下面就来具体介绍一下如何实现增删查。 增加数据 在vue-element-admin,我们通常使用表单来添加新的数据。首先需要定义一个表单组件,可以使用Element UI提供的组件,也可以自行编写。然后,在表单组件,需要监听表单的提交事件,将表单的数据发送到后端,以添加新的数据。 对于后端,需要提供一个API,用于接收前端传递过来的数据。通过API将数据保存到数据库。 删除数据 在vue-element-admin,删除数据通常是通过点击列表项的删除按来实现的。首先需要在列表项加入删除按,并定义一个删除方法。当用户点击删除按时,会调用该方法。 然后,在该方法,需要向后端发送一个请求,告诉后端需要删除的数据的ID。后端需要提供一个API,将指定的数据从数据库删除。 修数据 在vue-element-admin,修数据通常是通过点击列表项的编辑按来实现的。首先需要在列表项加入编辑按,并定义一个编辑方法。当用户点击编辑按时,会调用该方法。 然后,在该方法,需要获取需要编辑的数据的ID,并将其发送到后端。后端需要提供一个API,用于获取指定ID的数据,并将其返回给前端。 返回数据后,将其填充到表单组件,易于修。当用户修完数据后,需要将数据发送到后端,以更新到数据库。 查询数据 在vue-element-admin,实现查询数据通常是在页面顶部加入一个查询表单。当用户输入查询条件后,需要向后端发送一个请求,告诉后端需要查询的条件。后端需要提供一个API,接收查询条件,并返回查询结果给前端。 查询结果需要渲染到页面,以便用户查看。同时,如果查询结果很多,可以考虑进行分页,提高查询效率。 以上就是在vue-element-admin实现增删查的大致过程,需要注意的是,每个步骤都需要前后端协同,才能实现完整的功能。 ### 回答3: vue-element-admin是一个基于Vue.js的后台管理系统解决方案,它提供的各种组件和功能可以帮助我们快速搭建出一个完整的后台管理系统。其增删查是后台管理系统最常见的功能,下面就来详细介绍一下vue-element-admin的增删查。 一、增加数据 在vue-element-admin,增加数据分为两种情况:新增数据和批量增加数据。对于新增数据,我们需要引用element-ui的Dialog组件实现弹窗输入新数据,然后将数据通过api的方法传给后端实现数据的插入操作。对于批量增加数据,我们可以使用Vue.js的v-for指令和el-table组件展示已有数据,并通过上传Excel表格的方式批量增加数据。 二、删除数据 删除数据同样需要使用element-ui的Dialog组件来实现删除数据的确认框。在调用删除api接口时,我们需要传递一个或多个需要删除的数据的唯一标识符,并在后端进行相应的删除操作。不过需要注意的是,对于某些敏感数据,我们可以通过后端开发的方式做出逻辑删除而非物理删除的处理。 三、修数据 修数据也需要使用element-ui的Dialog组件实现弹窗,并将原有的数据展示在弹窗,让用户修之后提交保存。我们需要传递修后的数据和原有数据的唯一标识符给api接口,然后在后端进行相应的修操作。 四、查询数据 查询数据是后台管理系统最常用的功能之一,vue-element-admin通过使用el-form和el-input组件实现数据的模糊查询、精确查询等功能。同时,Vue.js的自定义过滤器可以帮助我们对查询出的数据进行格式化,以便展示给用户。 综上所述,vue-element-admin的增删查功能涉及到多个组件和功能,需要结合具体业务场景进行设计和开发。不过通过使用vue-element-admin提供的各种组件和api接口,我们可以快速实现一个完整的后台管理系统,并满足各种业务需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值