Prechádzať zdrojové kódy

feat: add modal component

xingyu4j 2 rokov pred
rodič
commit
d79b23c8a2

+ 3 - 0
yudao-ui-admin-vue3/src/components/XModal/index.ts

@@ -0,0 +1,3 @@
+import XModal from './src/XModal.vue'
+
+export { XModal }

+ 62 - 0
yudao-ui-admin-vue3/src/components/XModal/src/XModal.vue

@@ -0,0 +1,62 @@
+<script setup lang="ts">
+import { propTypes } from '@/utils/propTypes'
+import { computed, useAttrs, useSlots } from 'vue'
+
+const slots = useSlots()
+
+const props = defineProps({
+  id: propTypes.string.def('model_1'),
+  modelValue: propTypes.bool.def(false),
+  fullscreen: propTypes.bool.def(false),
+  loading: propTypes.bool.def(false),
+  title: propTypes.string.def('弹窗'),
+  width: propTypes.string.def('800'),
+  height: propTypes.string.def('480'),
+  minWidth: propTypes.string.def('460'),
+  minHeight: propTypes.string.def('320'),
+  showFooter: propTypes.bool.def(true)
+})
+
+const getBindValue = computed(() => {
+  const delArr: string[] = ['title']
+  const attrs = useAttrs()
+  const obj = { ...attrs, ...props }
+  for (const key in obj) {
+    if (delArr.indexOf(key) !== -1) {
+      delete obj[key]
+    }
+  }
+  return obj
+})
+</script>
+
+<template>
+  <vxe-modal
+    v-bind="getBindValue"
+    :width="width"
+    :height="height"
+    :title="title"
+    min-width="460"
+    min-height="320"
+    :loading="loading"
+    :fullscreen="fullscreen"
+    destroy-on-close
+    show-zoom
+    resize
+    transfer
+    :show-footer="showFooter"
+  >
+    <template v-if="slots.header" #header>
+      <slot name="header"></slot>
+    </template>
+    <template v-if="slots.default" #default>
+      <slot name="default"></slot>
+    </template>
+    <template v-if="slots.corner" #corner>
+      <slot name="corner"></slot>
+    </template>
+    <template v-if="slots.footer" #footer>
+      <slot name="footer"></slot>
+    </template>
+  </vxe-modal>
+</template>

+ 2 - 0
yudao-ui-admin-vue3/src/components/index.ts

@@ -4,6 +4,7 @@ import { Form } from '@/components/Form'
 import { Table } from '@/components/Table'
 import { Search } from '@/components/Search'
 import { Dialog } from '@/components/Dialog'
+import { XModal } from '@/components/XModal'
 import { DictTag } from '@/components/DictTag'
 import { ContentWrap } from '@/components/ContentWrap'
 import { Descriptions } from '@/components/Descriptions'
@@ -14,6 +15,7 @@ export const setupGlobCom = (app: App<Element>): void => {
   app.component('Table', Table)
   app.component('Search', Search)
   app.component('Dialog', Dialog)
+  app.component('XModal', XModal)
   app.component('DictTag', DictTag)
   app.component('ContentWrap', ContentWrap)
   app.component('Descriptions', Descriptions)

+ 1 - 0
yudao-ui-admin-vue3/src/hooks/web/useVxeGrid.ts

@@ -4,6 +4,7 @@ import { VxeGridProps } from 'vxe-table'
 export const useVxeGrid = (allSchemas, getPageApi) => {
   const gridOptions = reactive<VxeGridProps>({
     loading: false,
+    height: 800,
     rowConfig: {
       keyField: 'id',
       isHover: true

+ 4 - 2
yudao-ui-admin-vue3/src/plugins/vxeTable/index.ts

@@ -114,8 +114,10 @@ VXETable.setup({
     titleColon: true // 是否显示标题冒号
   },
   modal: {
-    width: 600, // 窗口的宽度
-    height: 400, // 窗口的高度
+    width: 800, // 窗口的宽度
+    height: 600, // 窗口的高度
+    minWidth: 460,
+    minHeight: 320,
     showZoom: true, // 标题是否标显示最大化与还原按钮
     resize: true, // 是否允许窗口边缘拖动调整窗口大小
     marginSize: 0, // 只对 resize 启用后有效,用于设置可拖动界限范围,如果为负数则允许拖动超出屏幕边界

+ 172 - 184
yudao-ui-admin-vue3/src/views/system/menu/index.vue

@@ -1,169 +1,3 @@
-<script setup lang="ts">
-import * as MenuApi from '@/api/system/menu'
-import { MenuVO } from '@/api/system/menu/types'
-import { useI18n } from '@/hooks/web/useI18n'
-import { useMessage } from '@/hooks/web/useMessage'
-import { IconSelect } from '@/components/Icon'
-import { Tooltip } from '@/components/Tooltip'
-import { required } from '@/utils/formRules.js'
-import { onMounted, reactive, ref } from 'vue'
-import { VxeTableInstance } from 'vxe-table'
-import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
-import { SystemMenuTypeEnum, CommonStatusEnum } from '@/utils/constants'
-import {
-  ElRow,
-  ElCol,
-  ElForm,
-  ElFormItem,
-  ElInput,
-  ElInputNumber,
-  ElSelect,
-  ElTreeSelect,
-  ElOption,
-  ElRadioGroup,
-  ElRadioButton
-} from 'element-plus'
-import { handleTree } from '@/utils/tree'
-const { t } = useI18n() // 国际化
-const message = useMessage()
-const xTable = ref<VxeTableInstance>()
-const tableLoading = ref(false)
-const tableData = ref()
-const actionLoading = ref(false) // 遮罩层
-const actionType = ref('') // 操作按钮的类型
-const dialogVisible = ref(false) // 是否显示弹出层
-const dialogTitle = ref('edit') // 弹出层标题
-const statusOption = ref() // 状态选项
-const menuForm = ref<MenuVO>({
-  id: 0,
-  name: '',
-  permission: '',
-  type: SystemMenuTypeEnum.DIR,
-  sort: 1,
-  parentId: 0,
-  path: '',
-  icon: '',
-  component: '',
-  status: CommonStatusEnum.ENABLE,
-  visible: true,
-  keepAlive: true,
-  createTime: ''
-})
-const menuProps = {
-  checkStrictly: true,
-  children: 'children',
-  label: 'name',
-  value: 'id'
-}
-interface Tree {
-  id: number
-  name: string
-  children?: Tree[] | any[]
-}
-const menuOptions = ref<any[]>([]) // 树形结构
-const getTree = async () => {
-  menuOptions.value = []
-  const res = await MenuApi.listSimpleMenusApi()
-  let menu: Tree = { id: 0, name: '主类目', children: [] }
-  menu.children = handleTree(res)
-  menuOptions.value.push(menu)
-}
-// ========== 查询 ==========
-const queryParams = reactive({
-  name: null,
-  status: null
-})
-const getList = async () => {
-  statusOption.value = getIntDictOptions(DICT_TYPE.COMMON_STATUS)
-  tableLoading.value = true
-  const res = await MenuApi.getMenuListApi(queryParams)
-  tableData.value = res
-  tableLoading.value = false
-}
-// 设置标题
-const setDialogTile = (type: string) => {
-  dialogTitle.value = t('action.' + type)
-  actionType.value = type
-  dialogVisible.value = true
-}
-// 新建操作
-const handleCreate = () => {
-  setDialogTile('create')
-}
-// 修改操作
-const handleUpdate = async (row: MenuVO) => {
-  // 设置数据
-  const res = await MenuApi.getMenuApi(row.id)
-  console.log(res)
-  menuForm.value = res
-  setDialogTile('update')
-}
-// 删除操作
-const handleDelete = async (row: MenuVO) => {
-  message.confirm(t('common.delDataMessage'), t('common.confirmTitle')).then(async () => {
-    await MenuApi.deleteMenuApi(row.id)
-    message.success(t('common.delSuccess'))
-    await getList()
-  })
-}
-// 表单校验
-const rules = reactive({
-  name: [required],
-  sort: [required],
-  path: [required],
-  status: [required]
-})
-// 查询操作
-const handleQuery = async () => {
-  await getList()
-}
-// 重置操作
-const resetQuery = async () => {
-  queryParams.name = null
-  queryParams.status = null
-  await getList()
-}
-// 保存操作
-const isExternal = (path: string) => {
-  return /^(https?:|mailto:|tel:)/.test(path)
-}
-const submitForm = async () => {
-  actionLoading.value = true
-  // 提交请求
-  try {
-    if (
-      menuForm.value.type === SystemMenuTypeEnum.DIR ||
-      menuForm.value.type === SystemMenuTypeEnum.MENU
-    ) {
-      if (!isExternal(menuForm.value.path)) {
-        if (menuForm.value.parentId === 0 && menuForm.value.path.charAt(0) !== '/') {
-          message.error('路径必须以 / 开头')
-          return
-        } else if (menuForm.value.parentId !== 0 && menuForm.value.path.charAt(0) === '/') {
-          message.error('路径不能以 / 开头')
-          return
-        }
-      }
-    }
-    if (actionType.value === 'create') {
-      await MenuApi.createMenuApi(menuForm.value)
-      message.success(t('common.createSuccess'))
-    } else {
-      await MenuApi.updateMenuApi(menuForm.value)
-      message.success(t('common.updateSuccess'))
-    }
-    // 操作成功,重新加载列表
-    dialogVisible.value = false
-    await getList()
-  } finally {
-    actionLoading.value = false
-  }
-}
-onMounted(async () => {
-  await getList()
-  getTree()
-})
-</script>
 <template>
   <ContentWrap>
     <el-form :model="queryParams" ref="queryForm" :inline="true">
@@ -211,6 +45,7 @@ onMounted(async () => {
       :print-config="{}"
       :export-config="{}"
       :data="tableData"
+      class="xtable"
     >
       <vxe-column title="菜单名称" field="name" width="200" tree-node>
         <template #default="{ row }">
@@ -255,19 +90,7 @@ onMounted(async () => {
       </vxe-column>
     </vxe-table>
   </ContentWrap>
-  <vxe-modal
-    v-model="dialogVisible"
-    id="menuModel"
-    :title="dialogTitle"
-    width="800"
-    height="6f00"
-    min-width="460"
-    min-height="320"
-    show-zoom
-    resize
-    transfer
-    show-footer
-  >
+  <XModal v-model="dialogVisible" id="menuModel" :title="dialogTitle">
     <template #default>
       <!-- 对话框(添加 / 修改) -->
       <el-form
@@ -410,10 +233,175 @@ onMounted(async () => {
         type="primary"
         :loading="actionLoading"
         @click="submitForm"
-      >
-        {{ t('action.save') }}
-      </el-button>
-      <el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
+        :content="t('action.save')"
+      />
+      <el-button @click="dialogVisible = false" :content="t('dialog.close')" />
     </template>
-  </vxe-modal>
+  </XModal>
 </template>
+<script setup lang="ts">
+import * as MenuApi from '@/api/system/menu'
+import { MenuVO } from '@/api/system/menu/types'
+import { useI18n } from '@/hooks/web/useI18n'
+import { useMessage } from '@/hooks/web/useMessage'
+import { IconSelect } from '@/components/Icon'
+import { Tooltip } from '@/components/Tooltip'
+import { required } from '@/utils/formRules.js'
+import { onMounted, reactive, ref } from 'vue'
+import { VxeTableInstance } from 'vxe-table'
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { SystemMenuTypeEnum, CommonStatusEnum } from '@/utils/constants'
+import {
+  ElRow,
+  ElCol,
+  ElForm,
+  ElFormItem,
+  ElInput,
+  ElInputNumber,
+  ElSelect,
+  ElTreeSelect,
+  ElOption,
+  ElRadioGroup,
+  ElRadioButton
+} from 'element-plus'
+import { handleTree } from '@/utils/tree'
+const { t } = useI18n() // 国际化
+const message = useMessage()
+const xTable = ref<VxeTableInstance>()
+const tableLoading = ref(false)
+const tableData = ref()
+const actionLoading = ref(false) // 遮罩层
+const actionType = ref('') // 操作按钮的类型
+const dialogVisible = ref(false) // 是否显示弹出层
+const dialogTitle = ref('edit') // 弹出层标题
+const statusOption = ref() // 状态选项
+const menuForm = ref<MenuVO>({
+  id: 0,
+  name: '',
+  permission: '',
+  type: SystemMenuTypeEnum.DIR,
+  sort: 1,
+  parentId: 0,
+  path: '',
+  icon: '',
+  component: '',
+  status: CommonStatusEnum.ENABLE,
+  visible: true,
+  keepAlive: true,
+  createTime: ''
+})
+const menuProps = {
+  checkStrictly: true,
+  children: 'children',
+  label: 'name',
+  value: 'id'
+}
+interface Tree {
+  id: number
+  name: string
+  children?: Tree[] | any[]
+}
+const menuOptions = ref<any[]>([]) // 树形结构
+const getTree = async () => {
+  menuOptions.value = []
+  const res = await MenuApi.listSimpleMenusApi()
+  let menu: Tree = { id: 0, name: '主类目', children: [] }
+  menu.children = handleTree(res)
+  menuOptions.value.push(menu)
+}
+// ========== 查询 ==========
+const queryParams = reactive({
+  name: null,
+  status: null
+})
+const getList = async () => {
+  statusOption.value = getIntDictOptions(DICT_TYPE.COMMON_STATUS)
+  tableLoading.value = true
+  const res = await MenuApi.getMenuListApi(queryParams)
+  tableData.value = res
+  tableLoading.value = false
+}
+// 设置标题
+const setDialogTile = (type: string) => {
+  dialogTitle.value = t('action.' + type)
+  actionType.value = type
+  dialogVisible.value = true
+}
+// 新建操作
+const handleCreate = () => {
+  setDialogTile('create')
+}
+// 修改操作
+const handleUpdate = async (row: MenuVO) => {
+  // 设置数据
+  const res = await MenuApi.getMenuApi(row.id)
+  console.log(res)
+  menuForm.value = res
+  setDialogTile('update')
+}
+// 删除操作
+const handleDelete = async (row: MenuVO) => {
+  message.confirm(t('common.delDataMessage'), t('common.confirmTitle')).then(async () => {
+    await MenuApi.deleteMenuApi(row.id)
+    message.success(t('common.delSuccess'))
+    await getList()
+  })
+}
+// 表单校验
+const rules = reactive({
+  name: [required],
+  sort: [required],
+  path: [required],
+  status: [required]
+})
+// 查询操作
+const handleQuery = async () => {
+  await getList()
+}
+// 重置操作
+const resetQuery = async () => {
+  queryParams.name = null
+  queryParams.status = null
+  await getList()
+}
+// 保存操作
+const isExternal = (path: string) => {
+  return /^(https?:|mailto:|tel:)/.test(path)
+}
+const submitForm = async () => {
+  actionLoading.value = true
+  // 提交请求
+  try {
+    if (
+      menuForm.value.type === SystemMenuTypeEnum.DIR ||
+      menuForm.value.type === SystemMenuTypeEnum.MENU
+    ) {
+      if (!isExternal(menuForm.value.path)) {
+        if (menuForm.value.parentId === 0 && menuForm.value.path.charAt(0) !== '/') {
+          message.error('路径必须以 / 开头')
+          return
+        } else if (menuForm.value.parentId !== 0 && menuForm.value.path.charAt(0) === '/') {
+          message.error('路径不能以 / 开头')
+          return
+        }
+      }
+    }
+    if (actionType.value === 'create') {
+      await MenuApi.createMenuApi(menuForm.value)
+      message.success(t('common.createSuccess'))
+    } else {
+      await MenuApi.updateMenuApi(menuForm.value)
+      message.success(t('common.updateSuccess'))
+    }
+    // 操作成功,重新加载列表
+    dialogVisible.value = false
+    await getList()
+  } finally {
+    actionLoading.value = false
+  }
+}
+onMounted(async () => {
+  await getList()
+  getTree()
+})
+</script>

+ 73 - 86
yudao-ui-admin-vue3/src/views/system/post/index.vue

@@ -1,3 +1,76 @@
+<template>
+  <ContentWrap>
+    <vxe-grid ref="xGrid" v-bind="gridOptions" class="xtable-scrollbar">
+      <template #toolbar_buttons>
+        <el-button type="primary" v-hasPermi="['system:post:create']" @click="handleCreate">
+          <Icon icon="ep:zoom-in" class="mr-5px" /> {{ t('action.add') }}
+        </el-button>
+      </template>
+      <template #status_default="{ row }">
+        <DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
+      </template>
+      <template #action_default="{ row }">
+        <el-button
+          link
+          type="primary"
+          v-hasPermi="['system:post:update']"
+          @click="handleUpdate(row.id)"
+        >
+          <Icon icon="ep:edit" class="mr-1px" /> {{ t('action.edit') }}
+        </el-button>
+        <el-button
+          link
+          type="primary"
+          v-hasPermi="['system:post:update']"
+          @click="handleDetail(row)"
+        >
+          <Icon icon="ep:view" class="mr-1px" /> {{ t('action.detail') }}
+        </el-button>
+        <el-button
+          link
+          type="primary"
+          v-hasPermi="['system:post:delete']"
+          @click="handleDelete(row.id)"
+        >
+          <Icon icon="ep:delete" class="mr-1px" /> {{ t('action.del') }}
+        </el-button>
+      </template>
+    </vxe-grid>
+  </ContentWrap>
+  <XModal id="postModel" v-model="dialogVisible" :title="dialogTitle">
+    <template #default>
+      <!-- 对话框(添加 / 修改) -->
+      <vxe-form
+        ref="xForm"
+        v-if="['create', 'update'].includes(actionType)"
+        :data="formData"
+        :items="formItems"
+        :rules="rules"
+      />
+      <Descriptions
+        v-if="actionType === 'detail'"
+        :schema="allSchemas.detailSchema"
+        :data="detailRef"
+      >
+        <template #status="{ row }">
+          <DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
+        </template>
+        <template #createTime="{ row }">
+          <span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
+        </template>
+      </Descriptions>
+    </template>
+    <template #footer>
+      <vxe-button
+        v-if="['create', 'update'].includes(actionType)"
+        status="primary"
+        @click="submitForm"
+        :content="t('action.save')"
+      />
+      <vxe-button @click="dialogVisible = false" :content="t('dialog.close')" />
+    </template>
+  </XModal>
+</template>
 <script setup lang="ts">
 import { ref } from 'vue'
 import dayjs from 'dayjs'
@@ -82,89 +155,3 @@ const submitForm: VxeFormEvents.Submit = async () => {
   }
 }
 </script>
-<template>
-  <ContentWrap>
-    <vxe-grid ref="xGrid" v-bind="gridOptions">
-      <template #toolbar_buttons>
-        <el-button type="primary" v-hasPermi="['system:post:create']" @click="handleCreate">
-          <Icon icon="ep:zoom-in" class="mr-5px" /> {{ t('action.add') }}
-        </el-button>
-      </template>
-      <template #status_default="{ row }">
-        <DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
-      </template>
-      <template #action_default="{ row }">
-        <el-button
-          link
-          type="primary"
-          v-hasPermi="['system:post:update']"
-          @click="handleUpdate(row.id)"
-        >
-          <Icon icon="ep:edit" class="mr-1px" /> {{ t('action.edit') }}
-        </el-button>
-        <el-button
-          link
-          type="primary"
-          v-hasPermi="['system:post:update']"
-          @click="handleDetail(row)"
-        >
-          <Icon icon="ep:view" class="mr-1px" /> {{ t('action.detail') }}
-        </el-button>
-        <el-button
-          link
-          type="primary"
-          v-hasPermi="['system:post:delete']"
-          @click="handleDelete(row.id)"
-        >
-          <Icon icon="ep:delete" class="mr-1px" /> {{ t('action.del') }}
-        </el-button>
-      </template>
-    </vxe-grid>
-  </ContentWrap>
-  <vxe-modal
-    v-model="dialogVisible"
-    id="postModel"
-    :title="dialogTitle"
-    width="800"
-    height="400"
-    min-width="460"
-    min-height="320"
-    show-zoom
-    resize
-    transfer
-    show-footer
-  >
-    <template #default>
-      <!-- 对话框(添加 / 修改) -->
-      <vxe-form
-        ref="xForm"
-        v-if="['create', 'update'].includes(actionType)"
-        :data="formData"
-        :items="formItems"
-        :rules="rules"
-      />
-      <Descriptions
-        v-if="actionType === 'detail'"
-        :schema="allSchemas.detailSchema"
-        :data="detailRef"
-      >
-        <template #status="{ row }">
-          <DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
-        </template>
-        <template #createTime="{ row }">
-          <span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
-        </template>
-      </Descriptions>
-    </template>
-    <template #footer>
-      <vxe-button
-        v-if="['create', 'update'].includes(actionType)"
-        status="primary"
-        @click="submitForm"
-      >
-        {{ t('action.save') }}
-      </vxe-button>
-      <vxe-button @click="dialogVisible = false">{{ t('dialog.close') }}</vxe-button>
-    </template>
-  </vxe-modal>
-</template>