feat: add modal component

plp
xingyu4j 3 years ago
parent 1c1cafdc7f
commit d79b23c8a2

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

@ -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>

@ -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)

@ -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

@ -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 启用后有效,用于设置可拖动界限范围,如果为负数则允许拖动超出屏幕边界

@ -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>

@ -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>

Loading…
Cancel
Save