Skip to content

Vue3+TS-项目:mr_vue3_ts_cms2

[TOC]

Department

组件:PageSearch

image-20230610103920876

image-20240523172110193

使用组件

html
<template>
  <div class="department">
+    <PageSearch @search-form="hdlSearchForm" />
  </div>
</template>
<script setup lang="ts">
+ import PageSearch from './cpns/PageSearch/PageSearch.vue'
</script>

页面布局

html
  <div class="page-search">
    <el-form label-width="80px" :model="searchForm" ref="searchFormRef">
      <el-row :gutter="120">
        <el-col :span="8">
+          <el-form-item label="部门名称" prop="name">
            <el-input v-model="searchForm.name" placeholder="请输入部门名称" />
          </el-form-item>
        </el-col>
        <el-col :span="8">
+          <el-form-item label="部门领导" prop="leader">
            <el-input v-model="searchForm.leader" placeholder="请输入部门领导" />
          </el-form-item>
        </el-col>
        <el-col :span="8">
+          <el-form-item label="创建时间" prop="createAt">
            <el-date-picker
              v-model="searchForm.createAt"
              type="daterange"
              range-separator="-"
              start-placeholder="开始时间"
              end-placeholder="结束时间"
            />
          </el-form-item>
        </el-col>
      </el-row>
      <el-form-item class="btns">
        <el-button icon="Refresh" @click="hdlReset">重置</el-button>
        <el-button icon="Search" type="primary" @click="hdlQuery">查询</el-button>
      </el-form-item>
    </el-form>
  </div>

组件:PageContent

使用组件

html
<template>
  <div class="department">
    <PageSearch @search-form="hdlSearchForm" />
+    <PageContent ref="contentRef" @change-visiable="hdlChangeVisiable" @edit-click="hdlEditClick" />
  </div>
</template>
<script setup lang="ts">
import PageSearch from './cpns/PageSearch/PageSearch.vue'
+ import PageContent from './cpns/PageContent/PageContent.vue'
</script>

提取page的请求方法

1、service

ts
/* 请求页面列表数据 */
export function postPageList(pageName: string, query: any) {
  return mrRequest.post({
    url: `/${pageName}/list`,
    data: query
  })
}

2、store

ts
interface ISystemState {
  userList: any[]
  totalCount: number
+  pageList: any[]
+  pageTotalCount: number
}
ts
  state: (): ISystemState => ({
    userList: [],
    totalCount: 0,

+    pageList: [],
+    pageTotalCount: 0
  }),
  actions: {
    /* 统一接口 */
    /* 请求页面列表数据 */
+    async postPageListAction(pageName: string, query: any) {
      const res = await postPageList(pageName, query)
      this.pageList = res.data.list
      this.pageTotalCount = res.data.totalCount
    },
  }

3、组件

ts
/* 发送网络请求 */
function fetchPageList(searchForm: any = {}) {
  const offset = (currentPage.value - 1) * pageSize.value
  const size = pageSize.value
  const query = { offset, size }
  const finalQuery = { ...query, ...searchForm }
+  systemStore.postPageListAction('department', finalQuery)
}
fetchPageList()

4、组件获取store数据

ts
/* 获取用户列表 */
const { pageList, pageTotalCount } = storeToRefs(systemStore)

5、组件展示数据

html
    <div class="form">
+      <el-table :data="pageList" border style="width: 100%">
        <el-table-column align="center" type="selection" />
        <el-table-column align="center" type="index" label="序号" width="60px" />
+        <el-table-column align="center" prop="name" label="部门名称" width="180px" />
+        <el-table-column align="center" prop="leader" label="部门领导" width="180px" />
+        <el-table-column align="center" prop="parentId" label="上级部门" width="150px" />
        <el-table-column align="center" prop="createAt" label="创建时间">
          <template #default="scope">{{ formatUTC(scope.row.createAt) }}</template>
        </el-table-column>
        <el-table-column align="center" prop="updateAt" label="更新时间">
          <template #default="scope">{{ formatUTC(scope.row.updateAt) }}</template>
        </el-table-column>
        <el-table-column align="center" label="操作" width="140px">
          <template #default="scope">
            <div class="btns">
              <el-button type="primary" text @click="() => hdlEditItem(scope.row)">
                <el-icon><Edit /></el-icon>
                <span>编辑</span>
              </el-button>
              <el-button type="danger" text @click="() => hdlDeletePage(scope.row.id)">
                <el-icon><Delete /></el-icon>
                <span>删除</span>
              </el-button>
            </div>
          </template>
        </el-table-column>
      </el-table>
    </div>
    <div class="navigation">
      <el-pagination
        v-model:current-page="currentPage"
        v-model:page-size="pageSize"
        :page-sizes="[5, 10, 20]"
        small="small"
        layout="total, sizes, prev, pager, next, jumper"
+        :total="pageTotalCount"
        @size-change="hdlSizeChange"
        @current-change="hdlPageChange"
      />

查询功能

在父组件中监听查询按钮点击事件,并调用子组件的方法执行查询

html
  <!-- @search-form="hdlSearchForm" -->
  <!-- ref="contentRef" -->
  <div class="department">
+    <PageSearch @search-form="hdlSearchForm" />
+    <PageContent ref="contentRef"/>
  </div>
ts
/* 调用UserContent组件中的方法,查询数据 */
const contentRef = ref<InstanceType<typeof PageContent>>()
function hdlSearchForm(searchForm: any) {
  if (contentRef.value) contentRef.value.fetchPageList(searchForm)
}

重置功能

1、在子组件中发送事件

ts
/* 重置搜索表单 */
function hdlReset() {
  emits('search-form', {})
  searchFormRef.value?.resetFields()
}

2、在父组件中监听重置按钮点击事件,并调用子组件的方法执行重置

ts
<PageSearch @search-form="hdlSearchForm" />
ts
/* 调用UserContent组件中的方法,查询数据 */
const contentRef = ref<InstanceType<typeof PageContent>>()
function hdlSearchForm(searchForm: any) {
  if (contentRef.value) contentRef.value.fetchPageList(searchForm)
}

删除功能

1、service

ts
/* 根据id删除用户 */
export function delPageById(pageName: string, id: number) {
  return mrRequest.delete({
    url: `/${pageName}/${id}`
  })
}

2、store

ts
    /* 根据id删除 */
    async delPageByIdAction(pageName: string, id: number) {
      await delPageById(pageName, id)
      ElMessage.success('哈哈,删除成功~')
      this.postPageListAction(pageName, { offset: 0, size: 5 })
    },

3、组件

html
  <el-button type="danger" text @click="() => hdlDeletePage(scope.row.id)">
    <el-icon><Delete /></el-icon>
    <span>删除</span>
  </el-button>
ts
/* 根据id删除用户 */
function hdlDeletePage(id: number) {
  systemStore.delPageByIdAction('department', id)
}

新增功能

1、在父组件中监听PageContent中的点击事件

html
  <PageContent ref="contentRef" @change-visiable="hdlChangeVisiable" />
  <PageModal ref="modalRef" />
ts
/* 修改对话框是否显示 */
const modalRef = ref<InstanceType<typeof PageModal>>()
function hdlChangeVisiable() {
  if (modalRef.value) modalRef.value.changeModalVisiable()
}

2、见:组件PageModal

组件:PageModal

使用组件

html
  <div class="department">
    <PageSearch @search-form="hdlSearchForm" />
    <PageContent ref="contentRef" @change-visiable="hdlChangeVisiable" @edit-click="hdlEditClick" />
+    <PageModal ref="modalRef" />
  </div>

修改PageModal

1、表单数据

ts
/* 表单数据 */
const pageForm = reactive<any>({
  name: '',
  leader: '',
  parentId: ''
})

2、模板

html
  <div class="user-modal">
    <el-dialog v-model="modalVisiable" :title="isEdit ? '编辑用户' : '新增用户'" width="30%" center>
      <div class="form">
        <el-form
+          :model="pageForm"
          :rules="formRules"
          label-position="right"
          label-width="100px"
          size="large"
          ref="formRef"
        >
+          <el-form-item label="部门名称" prop="name">
            <el-input v-model="pageForm.name" placeholder="请输入部门名称" />
          </el-form-item>
+          <el-form-item label="部门领导" prop="realname">
            <el-input v-model="pageForm.leader" placeholder="请输入部门领导" />
          </el-form-item>
+          <el-form-item label="上级部门" prop="parentId">
            <el-select
              v-model="pageForm.parentId"
              class="m-2"
              placeholder="请选择上级部门"
              style="width: 100%"
            >
              <el-option
                v-for="item in departmentLists"
                :key="item.id"
                :label="item.name"
                :value="item.id"
              />
            </el-select>
          </el-form-item>
        </el-form>
      </div>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="modalVisiable = false">取消</el-button>
          <el-button type="primary" @click="hdlSubmitUser">确定</el-button>
        </span>
      </template>
    </el-dialog>
  </div>

3、从store获取数据

ts
/* 获取store中数据 */
const { departmentLists } = storeToRefs(mainStore)

点击确定创建部门

1、service

ts
/* 新增用户 */
export function addPage(pageName: string, pageInfo: any) {
  return mrRequest.post({
    url: `/${pageName}`,
    data: pageInfo
  })
}

2、store

ts
    /* 新增 */
    async addPageAction(pageName: string, pageInfo: any) {
      await addPage(pageName, pageInfo)
      this.postPageListAction(pageName, { offset: 0, size: 5 })
    },

3、组件

html
<el-button type="primary" @click="hdlSubmitUser">确定</el-button>
ts
/* 添加、编辑用户 */
const formRef = ref<InstanceType<typeof ElForm>>()
function hdlSubmitUser() {
  modalVisiable.value = false
  pageInfo.value = pageForm
  // 验证表单
  formRef.value?.validate((valid: any) => {
    if (valid) {
      // 验证成功
      if (isEdit.value) {
        console.log('pageId', pageId.value, 'pageInfo', pageInfo.value)
        systemStore.editPageAction('department', pageId.value, pageInfo.value)
        ElMessage.success('哈哈,修改用户成功~')
      } else {
        console.log('pageInfo', pageInfo.value)
+        systemStore.addPageAction('department', pageInfo.value)
        ElMessage.success('哈哈,新增用户成功~')
      }
    } else {
      // 验证失败
      ElMessage.error('呜呼,验证失败,请重新来过~')
    }
  })
}

编辑功能

1、service

ts
/* 编辑用户 */
export function editPage(pageName: string, id: number, pageInfo: any) {
  return mrRequest.patch({
    url: `/${pageName}/${id}`,
    data: pageInfo
  })
}

2、store

ts
    /* 编辑 */
    async editPageAction(pageName: string, id: number, pageInfo: any) {
      await editPage(pageName, id, pageInfo)
      this.postPageListAction(pageName, { offset: 0, size: 5 })
    }

3、组件

ts
/* 添加、编辑用户 */
const formRef = ref<InstanceType<typeof ElForm>>()
function hdlSubmitUser() {
  modalVisiable.value = false
  pageInfo.value = pageForm
  // 验证表单
  formRef.value?.validate((valid: any) => {
    if (valid) {
      // 验证成功
      if (isEdit.value) {
+        systemStore.editPageAction('department', pageId.value, pageInfo.value)
        ElMessage.success('哈哈,修改用户成功~')
      } else {
        systemStore.addPageAction('department', pageInfo.value)
        ElMessage.success('哈哈,新增用户成功~')
      }
    } else {
      // 验证失败
      ElMessage.error('呜呼,验证失败,请重新来过~')
    }
  })
}

抽取

抽取:PageSearch

使用组件

ts
import PageSearch from '@/components/PageSearch/PageSearch.vue'

配置

1、定义配置

/deparment/config/search.config.ts

ts
const searchConfig = {
  formItems: [
    { type: 'input', prop: 'name', label: '部门名称', placeholder: '请输入部门名称' },
    { type: 'input', prop: 'leader', label: '部门领导', placeholder: '请输入部门领导' },
    { type: 'date-picker', prop: 'createAt', label: '创建时间' }
  ]
}

export default searchConfig

2、传递配置

ts
import searchConfig from './config/search.config'
html
  <div class="department">
    <PageSearch :search-config="searchConfig" @search-form="hdlSearchForm" />
  </div>

3、在组件内部接收searchConfig

ts
export interface IProps {
  searchConfig: {
    formItems: any[]
  }
}
defineProps<IProps>()

根据配置渲染模板

1、根据配置,初始化search表单列表

ts
/* 表单数据 */
const initForm: any = {}
for (const item of props.searchConfig.formItems) {
  initForm[item.prop] = item.initValue ?? ''
}
const searchForm = reactive<any>(initForm)

2、遍历配置项,渲染search

html
    <el-form label-width="80px" :model="searchForm" ref="searchFormRef">
      <el-row :gutter="120">
+        <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>
            </el-form-item>
          </el-col>
        </template>
      </el-row>
      <el-form-item class="btns">
        <el-button icon="Refresh" @click="hdlReset">重置</el-button>
        <el-button icon="Search" type="primary" @click="hdlQuery">查询</el-button>
      </el-form-item>
    </el-form>

2、select类型

ts
    {
      type: 'select',
      prop: 'enable',
      label: '状态',
      placeholder: '请选择状态',
      options: [
        { label: '启用', value: 1 },
        { label: '禁用', value: 0 }
      ]
    }
html
  <template v-else-if="item.type === 'select'">
    <el-select
      v-model="searchForm[item.prop]"
      class="m-2"
      :placeholder="item.placeholder"
      style="width: 100%"
    >
+      <el-option v-for="value in item.options" :key="value.value" v-bind="value" />
    </el-select>
  </template>

可选配置

1、labelWidth

image-20230610121629261

image-20230610121653582

抽取:PageContent

使用组件

ts
import PageContent from '@/components/PageContent/PageContent.vue'

header-配置

1、定义配置

ts
const contentConfig = {
  pageName: 'department',
  header: {
    title: '部门列表',
    btnTitle: '新增部门'
  }
}

export default contentConfig

2、传递配置

html
<template>
  <div class="department">
    <PageSearch :search-config="searchConfig" @search-form="hdlSearchForm" />
+    <PageContent
+      ref="contentRef"
+      :content-config="contentConfig"
+      @change-visiable="hdlChangeVisiable"
+      @edit-click="hdlEditClick"
+    />
  </div>
</template>
<script setup lang="ts">
+  import contentConfig from './config/content.config'
</script>

3、组件内部接收配置

ts
export interface IProps {
  contentConfig: {
    pageName: string
    header: {
      title: string
      btnTitle: string
    }
  }
}
const props = defineProps<IProps>()

header-渲染模板

html
    <div class="header">
+      <h3 class="title">{{ contentConfig.header.title }}</h3>
      <el-button class="btn" type="primary" @click="hdlAddItem">
+        {{ contentConfig.header.btnTitle }}
      </el-button>
    </div>

table-配置

1、定义配置

ts
const contentConfig = {
  pageName: 'department',
  header: {
    title: '部门列表',
    btnTitle: '新增部门'
  },
+  formItems: [
    { type: 'selection', label: '选择', width: '50px' },
    { type: 'index', label: '序号', width: '60px' },
    { type: 'normal', label: '部门名称', prop: 'name', width: '180px' },
    { type: 'normal', label: '部门领导', prop: 'leader', width: '180px' },
    { type: 'normal', label: '上级部门', prop: 'parentId', width: '150px' },
    { type: 'timer', label: '创建时间', prop: 'createAt' },
    { type: 'timer', label: '更新时间', prop: 'updateAt' },
    { type: 'handler', label: '操作', width: '140px' }
  ]
}

export default contentConfig

带插槽的时间

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

操作类型

ts
{ type: 'handler', label: '操作', width: '140px' }

2、传递配置

html
    <PageContent
      ref="contentRef"
+      :content-config="contentConfig"
      @change-visiable="hdlChangeVisiable"
      @edit-click="hdlEditClick"
    />

3、组件内部接收配置

ts
export interface IProps {
  contentConfig: {
    pageName: string
    header: {
      title: string
      btnTitle: string
    }
    formItems: any[]
  }
}
const props = defineProps<IProps>()

table-渲染模板

1、基础模板

html
    <div class="form">
      <el-table :data="pageList" border style="width: 100%">
        <template v-for="item in contentConfig.formItems" :key="item.prop">
+          <template v-if="item.type === 'timer'">
            <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">
                <div class="btns">
                  <el-button type="primary" text @click="() => hdlEditItem(scope.row)">
                    <el-icon><Edit /></el-icon>
                    <span>编辑</span>
                  </el-button>
                  <el-button type="danger" text @click="() => hdlDeletePage(scope.row.id)">
                    <el-icon><Delete /></el-icon>
                    <span>删除</span>
                  </el-button>
                </div>
              </template>
            </el-table-column>
          </template>
+          <template v-else>
            <el-table-column align="center" v-bind="item" />
          </template>
        </template>
      </el-table>
    </div>

2、时间类型-timer

html
+          <template v-if="item.type === 'timer'">
            <el-table-column align="center" v-bind="item">
              <template #default="scope">{{ formatUTC(scope.row[item.prop]) }}</template>
            </el-table-column>
          </template>

3、操作类型-handler

html
+          <template v-else-if="item.type === 'handler'">
            <el-table-column align="center" v-bind="item">
              <template #default="scope">
                <div class="btns">
                  <el-button type="primary" text @click="() => hdlEditItem(scope.row)">
                    <el-icon><Edit /></el-icon>
                    <span>编辑</span>
                  </el-button>
                  <el-button type="danger" text @click="() => hdlDeletePage(scope.row.id)">
                    <el-icon><Delete /></el-icon>
                    <span>删除</span>
                  </el-button>
                </div>
              </template>
            </el-table-column>
          </template>

定制插槽

1、修改类型为custom,并指定插槽名

ts
    { type: 'custom', label: '上级部门', prop: 'parentId', width: '150px', slotName: 'parent' },
    { type: 'custom', label: '上级领导', prop: 'leader', width: '150px', slotName: 'leader' },

2、在模板中添加具名插槽

html
  <!-- 定制插槽 -->
  <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"></slot>
      </template>
    </el-table-column>
  </template>

3、使用插槽,自定义数据

html
    <PageContent
      ref="contentRef"
      :content-config="contentConfig"
      @change-visiable="hdlChangeVisiable"
      @edit-click="hdlEditClick"
    >
+      <template #parent="scope">
+        <div style="color: red">{{ scope.row[scope.prop] }}</div>
      </template>
+      <template #leader="scope">
+        <div style="color: green">{{ scope.row[scope.prop] }}</div>
      </template>
    </PageContent>

4、动态决定插槽中的数据

html
<template #default="scope">
   <slot :name="item.slotName"
         v-bind="scope"
+         :prop="item.prop">
   </slot>
</template>
html
      <template #parent="scope">
+        <div style="color: red">{{ scope.row[scope.prop] }}</div>
      </template>

动态pageName

1、传递的配置中包含pageName

ts
const contentConfig = {
+  pageName: 'department',
  header: {
    title: '部门列表',
    btnTitle: '新增部门'
  },
}
ts
export interface IProps {
  contentConfig: {
+    pageName: string
    header: {
      title: string
      btnTitle: string
    }
    formItems: any[]
  }
}
const props = defineProps<IProps>()

2、根据传递的pageName发送请求

ts
/* 发送网络请求 */
function fetchPageList(searchForm: any = {}) {
  const offset = (currentPage.value - 1) * pageSize.value
  const size = pageSize.value
  const query = { offset, size }
  const finalQuery = { ...query, ...searchForm }
+  systemStore.postPageListAction(props.contentConfig.pageName, finalQuery)
}
fetchPageList()
ts
/* 根据id删除用户 */
function hdlDeletePage(id: number) {
+  systemStore.delPageByIdAction(props.contentConfig.pageName, id)
}

抽取:PageModal

使用组件

ts
import PageModal from '@/components/PageModal/PageModal.vue'

配置

1、定义配置

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

export default modalConfig

2、传递配置

html
  <div class="department">
    <PageSearch :search-config="searchConfig" @search-form="hdlSearchForm" />
    <PageContent
      ref="contentRef"
      :content-config="contentConfig"
      @change-visiable="hdlChangeVisiable"
      @edit-click="hdlEditClick"
    >
    </PageContent>
+    <PageModal :modal-config="modalConfig" ref="modalRef" />
  </div>
<script setup lang="ts">
+  import modalConfig from './config/modal.config'
</script>

3、接收配置

ts
export interface IProps {
  modalConfig: {
    pageName: string
    header: {
      addTitle: string
      editTitle: string
    }
    formItems: any[]
  }
}
const props = defineProps<IProps>()

渲染模板

1、header

html
    <el-dialog
      v-model="modalVisiable"
+      :title="isEdit ? modalConfig.header.editTitle : modalConfig.header.addTitle"
      width="30%"
      center
    >
      <div class="form">
          ...
    </el-dialog>

2、表单

html
  <el-form
    :model="pageForm"
    :rules="formRules"
    label-position="right"
    label-width="100px"
    size="large"
    ref="formRef"
  >
+    <template v-for="item in modalConfig.formItems" :key="item.prop">
      <el-form-item v-bind="item">
+        <template v-if="item.type === 'input'">
          <el-input v-model="pageForm[item.prop]" :placeholder="item.placeholder" />
        </template>
+        <template v-else-if="item.type === 'select'">
          <el-select
            v-model="pageForm[item.prop]"
            class="m-2"
            :placeholder="item.placeholder"
            style="width: 100%"
          >
            <el-option
++              v-for="value in item?.options"
++              :key="value.value"
++              :label="value.label"
++              :value="value.value"
            />
          </el-select>
        </template>
      </el-form-item>
    </template>
  </el-form>

初始化formData

ts
/* 表单数据 */
const initForm: any = {}
for (const item of props.modalConfig.formItems) {
  initForm[item.prop] = item.initValue ?? ''
}
const pageForm = reactive<any>(initForm)

设置初始化值

image-20230610153310223

动态options数据

0、数据

entireDepartments:

image-20240524151649259

1、初始化config中的options为空数组

ts
    {
      type: 'select',
      label: '上级部门',
      prop: 'parentId',
      placeholder: '请选择上级部门',
+      options: []
    }

2、在传递config过程中对config进行修改

注意: 需要对entireDepartments数据进行转换后才能使用

ts
/* 为modalConfig添加动态options数据 */
const mainStore = useMainStore()
const modalConfigRef = computed(() => {
  const { departmentLists } = mainStore
  // 1. 整理departmentLists数据
  const departments = departmentLists.map((item) => {
    return { label: item.name, value: item.id }
  })

  // 2. 动态添加departments到item.options中
  for (const item of modalConfig.formItems) {
    if (item.prop === 'parentId') {
      item.options.push(...departments)
    }
  }

  return modalConfig
})
html
<PageModal :modal-config="modalConfigRef" ref="modalRef" />

动态pageName

1、传递的配置中包含pageName

ts
const modalConfig: IModalConfig = {
+  pageName: 'department',
  header: {
    addTitle: '新建部门',
    editTitle: '编辑部门'
  },
}
ts
export interface IModalConfig {
+  pageName: string
  header: {
    addTitle: string
    editTitle: string
  }
  formItems: any[]
}

/* define函数 */
export interface IModalProps {
+  modalConfig: IModalConfig
}
const props = defineProps<IModalProps>()

2、根据传递的pageName发送请求

ts
/* 添加、编辑用户 */
const formRef = ref<InstanceType<typeof ElForm>>()
function hdlSubmitUser() {
  modalVisiable.value = false
  pageInfo.value = pageForm
  // 验证表单
  formRef.value?.validate((valid: any) => {
    if (valid) {
      // 验证成功
      if (isEdit.value) {
+        systemStore.editPageAction(props.modalConfig.pageName, pageId.value, pageInfo.value)
        ElMessage.success('哈哈,修改用户成功~')
      } else {
+        systemStore.addPageAction(props.modalConfig.pageName, pageInfo.value)
        ElMessage.success('哈哈,新增用户成功~')
      }
    } else {
      // 验证失败
      ElMessage.error('呜呼,验证失败,请重新来过~')
    }
  })
}

Hooks抽取

将不需要修改的代码逻辑部分抽取到Hooks中

重置、查询功能

ts
import { ref } from 'vue'
// import type PageContent from '@/components/PageContent/PageContent.vue'

function usePageContent() {
  /* 调用UserContent组件中的方法,查询、重置数据 */
  const contentRef = ref<any>()
  function hdlSearchForm(searchForm: any) {
    contentRef.value?.fetchPageList(searchForm)
  }

  return {
    contentRef,
    hdlSearchForm
  }
}

export default usePageContent

使用hooks

ts
const { contentRef, hdlSearchForm } = usePageContent()

新增、修改功能

ts
import { ref } from 'vue'
// import type PageModal from '@/components/PageModal/PageModal.vue'

function usePageModal() {
  /* 修改对话框是否显示 */
  // const modalRef = ref<InstanceType<typeof PageModal>>()
  const modalRef = ref<any>()
  function hdlChangeVisiable() {
    modalRef.value?.changeModalVisiable()
  }

  /* 调用模态组件内函数,修改用户 */
  function hdlEditClick(pageItem: any) {
    modalRef.value?.changeModalVisiable(pageItem)
  }

  return {
    modalRef,
    hdlChangeVisiable,
    hdlEditClick
  }
}

export default usePageModal

使用hooks

ts
const { modalRef, hdlChangeVisiable, hdlEditClick } = usePageModal()

Role

组件:PageSearch

image-20230610120943946

使用组件

html
  <div class="role">
+    <PageSearch />
  </div>
<script setup lang="ts">
+   import PageSearch from '@/components/PageSearch/PageSearch.vue'
</script>

搜索配置

ts
const searchConfig = {
  pageName: 'role',
  formItems: [
    { type: 'input', label: '角色名称', prop: 'name', placeholder: '请输入角色名称' },
    { type: 'input', label: '权限介绍', prop: 'intro', placeholder: '请输入权限介绍' },
    { type: 'date-picker', label: '创建时间', prop: 'createAt' }
  ]
}

export default searchConfig

使用配置

html
<template>
  <div class="role">
+    <PageSearch :search-config="searchConfig" />
  </div>
<script setup lang="ts">
import PageSearch from '@/components/PageSearch/PageSearch.vue'
+   import searchConfig from './config/search.config'
</script>

组件:PageContent

使用组件

html
<template>
  <div class="role">
    <PageSearch :search-config="searchConfig" @search-form="hdlSearchForm" />
+    <PageContent/>
  </div>
</template>
<script setup lang="ts">
import PageSearch from '@/components/PageSearch/PageSearch.vue'
+  import PageContent from '@/components/PageContent/PageContent.vue'
</script>

内容配置

ts
const contentConfig = {
  pageName: 'role',
  header: {
    title: '角色列表',
    btnTitle: '新增角色'
  },
  formItems: [
    { type: 'selection', label: '选择', width: '50px' },
    { type: 'index', label: '序号', width: '60px' },
    { type: 'normal', label: '角色名称', prop: 'name', width: '180px' },
    { type: 'normal', label: '权限介绍', prop: 'intro', width: '180px' },
    { type: 'timer', label: '创建时间', prop: 'createAt' },
    { type: 'timer', label: '更新时间', prop: 'updateAt' },
    { type: 'handler', label: '操作', width: '140px' }
  ]
}

export default contentConfig

使用配置

html
<template>
  <div class="role">
    <PageSearch :search-config="searchConfig" @search-form="hdlSearchForm" />
    <PageContent
+      :content-config="contentConfig"
    />
  </div>
</template>
<script setup lang="ts">
import PageSearch from '@/components/PageSearch/PageSearch.vue'
import PageContent from '@/components/PageContent/PageContent.vue'
    
import searchConfig from './config/search.config'
+  import contentConfig from './config/content.config'
</script>

组件:PageModal

使用组件

html
<template>
  <div class="role">
    <PageSearch :search-config="searchConfig" @search-form="hdlSearchForm" />
    <PageContent
      :content-config="contentConfig"
      ref="contentRef"
      @change-visiable="hdlChangeVisiable"
      @edit-click="hdlEditClick"
    />
+    <PageModal />
  </div>
</template>
<script setup lang="ts">
import PageSearch from '@/components/PageSearch/PageSearch.vue'
import PageContent from '@/components/PageContent/PageContent.vue'
+  import PageModal from '@/components/PageModal/PageModal.vue'
</script>

配置

ts
const modalConfig = {
  pageName: 'role',
  header: {
    addTitle: '新增角色',
    editTitle: '修改角色'
  },
  formItems: [
    { type: 'input', label: '角色名称', prop: 'name', placeholder: '请输入角色名称' },
    { type: 'input', label: '权限介绍', prop: 'intro', placeholder: '请输入权限介绍' }
  ]
}

export default modalConfig

使用配置

html
<template>
  <div class="role">
+    <PageModal :modal-config="modalConfig" ref="modalRef" />
  </div>
</template>
<script setup lang="ts">
import PageModal from '@/components/PageModal/PageModal.vue'
+  import modalConfig from './config/modal.config'
</script>

事件逻辑

ts
// 事件函数
const { contentRef, hdlSearchForm } = usePageContent()
const { modalRef, hdlChangeVisiable, hdlEditClick } = usePageModal()
html
  <div class="role">
+    <PageSearch :search-config="searchConfig" @search-form="hdlSearchForm" />
    <PageContent
      :content-config="contentConfig"
+      ref="contentRef"
+      @change-visiable="hdlChangeVisiable"
+      @edit-click="hdlEditClick"
    />
+    <PageModal :modal-config="modalConfig" ref="modalRef" />
  </div>

组件:PageContent

image-20240524171349239

数据:

image-20240524170531118

使用组件

html
<template>
  <div class="menu">
+    <PageContent />
  </div>
</template>

<script setup lang="ts">
+ import PageContent from '@/components/PageContent/PageContent.vue'
</script>

配置

ts
const contentConfig = {
  pageName: 'menu',
  header: {
    title: '菜单列表',
    btnTitle: '新建菜单'
  },
  formItems: [
    { type: 'normal', label: '菜单名称', prop: 'name', width: '150px' },
    { type: 'normal', label: '级别', prop: 'type', width: '80px' },
    { type: 'normal', label: '菜单url', prop: 'url', width: '180px' },
    { type: 'normal', label: '菜单icon', prop: 'icon', width: '190px' },
    { type: 'normal', label: '排序', prop: 'sort', width: '80px' },
    { type: 'normal', label: '权限', prop: 'permission', width: '180px' },
    { type: 'timer', label: '创建时间', prop: 'createAt' },
    { type: 'timer', label: '更新时间', prop: 'updateAt' },
    { type: 'handler', label: '操作', width: '140px' }
  ]
}

export default contentConfig

使用配置

html
<template>
  <div class="menu">
+    <PageContent :content-config="contentConfig" />
  </div>
</template>

<script setup lang="ts">
import PageContent from '@/components/PageContent/PageContent.vue'

+  import contentConfig from './config/content.config'
</script>

菜单子树展开

1、基本使用

注意: 当数据中子数据通过children区分时,tree-props 属性可以省略

html
  <el-table
    :data="pageList"
    border
    style="width: 100%"
+    row-key="id"
+    :tree-props="{ children: 'chidren', hasChildren: 'hasChildren' }"
  >

2、*注意:*如果想让子菜单树显示,在配置时,不能添加type: 'normal'

ts
  formItems: [
+    { label: '菜单名称', prop: 'name', width: '150px' },
+    { label: '级别', prop: 'type', width: '80px' },
+    { label: '菜单url', prop: 'url', width: '180px' },
+    { label: '菜单icon', prop: 'icon', width: '190px' },
+    { label: '排序', prop: 'sort', width: '80px' },
+    { label: '权限', prop: 'permission', width: '180px' },
      
    { type: 'timer', label: '创建时间', prop: 'createAt' },
    { type: 'timer', label: '更新时间', prop: 'updateAt' },
    { type: 'handler', label: '操作', width: '140px' }
  ],

3、动态定义row-key

ts
const contentConfig: IContentConfig = {
  pageName: 'menu',
  header: {
    title: '菜单列表',
    btnTitle: '新建菜单'
  },
  formItems: [
      ...
  ],
+  childrenTree: {
+    rowKey: 'id',
+    treeProps: {
+      children: 'children',
+      hasChildren: 'hasChildren'
+    }
+  }
}
html
  <el-table
    :data="pageList"
    border
    style="width: 100%"
+    v-bind="contentConfig.childrenTree"
  >

权限管理

image-20240524173303418

分配权限

新建角色时分配权限

定义配置

ts
  formItems: [
    { type: 'input', label: '角色名称', prop: 'name', placeholder: '请输入角色名称' },
    { type: 'input', label: '权限介绍', prop: 'intro', placeholder: '请输入权限介绍' },
+    { type: 'custom', label: '分配权限', slotName: 'menuList' }
  ]

自定义插槽

html
  <template v-else-if="item.type === 'custom'">
    <slot :name="item.slotName"></slot>
  </template>

请求完整菜单树数据

1、service

ts
/* 获取权限列表 */
export function postMenuLists() {
  return mrRequest.post({
    url: '/menu/list'
  })
}

2、store

ts
    async postMenuListsAction() {
      const res = await postMenuLists()
      this.menuLists = res.data.list
    }

3、组件Role中

ts
const mainStore = useMainStore()
const { menuLists } = storeToRefs(mainStore)
const defaultProps = {
  children: 'children',
  label: 'name'
}
html
    <PageModal :modal-config="modalConfig" :other-info="otherInfo" ref="modalRef">
+      <template #menuList>
        <el-tree
+          :data="menuLists"
+          show-checkbox
+          node-key="id"
+          :props="defaultProps"
          ref="treeRef"
          @check="hdlSelectChecked"
        />
      </template>
    </PageModal>

创建角色时带权限

1、点击权限树后获取选中项的id

html
        <el-tree
          :data="menuLists"
          show-checkbox
          node-key="id"
          :props="defaultProps"
          ref="treeRef"
+          @check="hdlSelectChecked"
        />
ts
/* 获取选中的权限节点 */
const otherInfo = ref({})
function hdlSelectChecked(param1: any, param2: any) {
  const menuList = [...param2.checkedKeys, ...param2.halfCheckedKeys]
  otherInfo.value = { menuList }
}

hdlSelectChecked的2个参数:

image-20240524180919888

2、传递额外的数据otherInfo到PageModal组件中

html
<PageModal :modal-config="modalConfig"
+           :other-info="otherInfo"
           ref="modalRef">

3、在PageModal组件中接收数据

ts
export interface IModalProps {
  modalConfig: IModalConfig
+  otherInfo?: any
}
const props = defineProps<IModalProps>()

4、合并otherInfo和formData

ts
function hdlSubmitUser() {
  modalVisiable.value = false
+  pageInfo.value = { ...pageForm, ...props.otherInfo }
  console.log('pageInfo', pageInfo.value)
  // 验证表单
  formRef.value?.validate((valid: any) => {
    if (valid) {
      // 验证成功
      if (isEdit.value) {
+        systemStore.editPageAction(props.modalConfig.pageName, pageId.value, pageInfo.value)
        ElMessage.success('哈哈,修改用户成功~')
      } else {
+        systemStore.addPageAction(props.modalConfig.pageName, pageInfo.value)
        ElMessage.success('哈哈,新增用户成功~')
      }
    } else {
      // 验证失败
      ElMessage.error('呜呼,验证失败,请重新来过~')
    }
  })
}

权限菜单回显

1、绑定ElTree的ref

html
  <el-tree
    :data="menuLists"
    show-checkbox
    node-key="id"
    :props="defaultProps"
+    ref="treeRef"
    @check="hdlSelectChecked"
  />

2、在Role组件中定义回调函数,并将其传递给usePageModal中,目的是为了获取数据itemData

ts
+  const { modalRef, hdlChangeVisiable, hdlEditClick } = usePageModal(editCB)
/* 点击编辑,回显权限 */
const treeRef = ref<InstanceType<typeof ElTree>>()
+  function editCB(pageItem: any) {
  // console.log('pageItem: ', pageItem.menuList)
  nextTick(() => {
    const checkedKeys = mapMenuListToIds(pageItem.menuList)
    treeRef.value?.setCheckedKeys(checkedKeys)
  })
}

3、在Hook中接收回调函数

ts
function usePageModal(editCB: (pageItem: any) => void) {
  /* 修改对话框是否显示 */
  // const modalRef = ref<InstanceType<typeof PageModal>>()
  const modalRef = ref<any>()
  function hdlChangeVisiable() {
    modalRef.value?.changeModalVisiable()
  }

  /* 调用模态组件内函数,修改用户 */
  function hdlEditClick(pageItem: any) {
    modalRef.value?.changeModalVisiable(pageItem)
+    if (editCB) editCB(pageItem)
  }

  return {
    modalRef,
    hdlChangeVisiable,
    hdlEditClick
  }
}

4、定义一个获取数组中id的工具函数

ts
/**
 * 根据权限列表获取其所有根节点id
 * @param menuList 权限列表
 * @returns 权限列表的所有根节点id
 */
export function mapMenuListToIds(menuList: any[]) {
  const ids: number[] = []

  function recurseGetId(menuList: any[]) {
    for (const item of menuList) {
      if (item.children) {
        recurseGetId(item.children)
      } else {
        ids.push(item.id)
      }
    }
  }
  recurseGetId(menuList)
  console.log('ids', ids)
  return ids
}

5、使用获取到的id,调用ElTree的setCheckedKeys方法,给控件添加初始值

ts
/* 点击编辑,回显权限 */
const treeRef = ref<InstanceType<typeof ElTree>>()
function editCB(pageItem: any) {
  // console.log('pageItem: ', pageItem.menuList)
+  nextTick(() => {
+    const checkedKeys = mapMenuListToIds(pageItem.menuList)
+    treeRef.value?.setCheckedKeys(checkedKeys)
  })
}

新增重置权限菜单

思路: 在新增角色时,同样添加一个callback,并在setCheckedKeys 时传递一个空数组

image-20230615103119219

image-20230615103433959

image-20230615103422203

按钮权限

获取用户所有按钮权限

1、定义获取userMenus中所有permissions的工具函数

image-20230614175904783

2、使用工具函数,在@store/login/login.ts中的loginActionloadLocalCacheAction中都获取permissions

image-20230614175447396

根据权限展示按钮

1、在PageContent中获取对应的增删改查的权限

image-20230614181340039

2、根据权限展示、隐藏按钮

image-20230614180728804

image-20230614180732693

image-20230614180735483

image-20230614180842408

3、封装权限判断

image-20230614181956584

4、使用封装的hook

image-20230614181915980

5、如果没有query权限,不展示PageSearch组件

PageSearch.vue

image-20230614182251390

image-20230614182325703

为user页面添加按钮权限

image-20230615104101707

image-20230615104116018

image-20230615104132917

image-20230615104204481

image-20230615104208611

新建编辑删除后重置page

思路: 由于新建、编辑、删除都会在systemStore中执行action,因此可以通过在PageContent页面中监听store中是否有执行这些对应的action来决定是否要重置page

image-20230615112854402

action执行成功之后再重置page

image-20230615113222637

Dashboard

顶部数字展示

image-20240525143035007

1、布局

image-20230615114501492

组件:CountCard

使用组件

image-20230615114811709

页面布局

image-20240525143448734

悬浮提示

image-20230615115521393

动态渲染数据

1、组件内定义类型,并设置默认值

image-20230615115818846

2、渲染组件

image-20230615115927587

请求数据

1、service

image-20230615120151239

2、main

image-20230615120636700

3、组件-发起请求

image-20230615120618116

4、组件-从store中获取数据

image-20230615120721442

5、遍历数据

image-20230615120809434

数据动画效果

1、动画插件:npm i countup.js

2、使用countup添加动画

image-20230615122630807

3、添加人民币符号

image-20230615123017431

组件:ChartCard

使用组件

image-20240525153331905

页面布局

ECharts

API

  • echarts
  • echarts.init(dom, theme?, opt?)
  • echarts.registerMap(mapName, opt | geoJSON)
  • echartsInstance
  • echartsInstance.setOption()

安装

依赖包: echarts

安装: pnpm add echarts

基本使用

1、指定echarts的容器

image-20240525154835097

image-20240525154853524

2、引入echarts

image-20230615124105848

image-20240525155723490

封装Echarts

1、目录结构

image-20230615124452436

组件:BaseEchart

image-20230615125126415

image-20230615125420240

使用组件

image-20230615125334347

image-20230615125354875

组件:PieEchart

1、使用组件

image-20230615130452802

image-20230615130519004

2、使用base-echart组件,并传入option

image-20230615130128920

3、option配置

image-20240525170835619

4、修改BaseEchart,使用传递进来的option

image-20230615130028473

5、统一导出

image-20240525163717360

组件:LineEchart

1、使用组件

image-20230615133840237

2、配置

image-20240525175317738

image-20240525174559657

3、请求商品销量数据

见:动态渲染数据

4、转化请求的商品销量数据

image-20230615134159958

5、传递数据

image-20240525175202646

组件:RoseEchart

1、使用组件

image-20240525173222944

2、页面布局

image-20230615132809567

3、修改BaseEchart

image-20230615131911311

组件:BarEchart

1、store

image-20230615134353162

2、组件

image-20240525175502358

3、渲染数据

image-20230615134517788

4、配置

image-20240525175310459

image-20240525174518446

组件:MapEchart

1、注册map

image-20230615153525832

2、页面布局

image-20240525181415572

image-20240525182118808

3、获取城市经纬度工具函数

image-20240525182147366

4、经纬度数据

image-20240525182151115

动态渲染数据

1、service

image-20230615132116191

image-20230615132131889

2、store

image-20230615132249583

3、组件Dashboard,获取到的数据需要通过map转化一下

image-20230615132440910

image-20230615132534753

4、组件PieEchart

image-20240525164944896

pie-echart组件中,可以使用computed监听props.pieData数据的变化

image-20240525165621979

image-20240525164959372

5、组件BaseEchart中,使用watchEffect()监听options数据的变化,重新设置echart

image-20240525170041294

页面缩放

1、window缩放时,echart也同时跟随缩放

image-20230615161130390

2、页面组件为响应式布局

image-20230615161603692