Commit d79b23c8a242432990be431a8c43633bd212c47d

Authored by xingyu4j
1 parent 1c1cafdc

feat: add modal component

yudao-ui-admin-vue3/src/components/XModal/index.ts 0 → 100644
  1 +import XModal from './src/XModal.vue'
  2 +
  3 +export { XModal }
... ...
yudao-ui-admin-vue3/src/components/XModal/src/XModal.vue 0 → 100644
  1 +<script setup lang="ts">
  2 +import { propTypes } from '@/utils/propTypes'
  3 +import { computed, useAttrs, useSlots } from 'vue'
  4 +
  5 +const slots = useSlots()
  6 +
  7 +const props = defineProps({
  8 + id: propTypes.string.def('model_1'),
  9 + modelValue: propTypes.bool.def(false),
  10 + fullscreen: propTypes.bool.def(false),
  11 + loading: propTypes.bool.def(false),
  12 + title: propTypes.string.def('弹窗'),
  13 + width: propTypes.string.def('800'),
  14 + height: propTypes.string.def('480'),
  15 + minWidth: propTypes.string.def('460'),
  16 + minHeight: propTypes.string.def('320'),
  17 + showFooter: propTypes.bool.def(true)
  18 +})
  19 +
  20 +const getBindValue = computed(() => {
  21 + const delArr: string[] = ['title']
  22 + const attrs = useAttrs()
  23 + const obj = { ...attrs, ...props }
  24 + for (const key in obj) {
  25 + if (delArr.indexOf(key) !== -1) {
  26 + delete obj[key]
  27 + }
  28 + }
  29 + return obj
  30 +})
  31 +</script>
  32 +
  33 +<template>
  34 + <vxe-modal
  35 + v-bind="getBindValue"
  36 + :width="width"
  37 + :height="height"
  38 + :title="title"
  39 + min-width="460"
  40 + min-height="320"
  41 + :loading="loading"
  42 + :fullscreen="fullscreen"
  43 + destroy-on-close
  44 + show-zoom
  45 + resize
  46 + transfer
  47 + :show-footer="showFooter"
  48 + >
  49 + <template v-if="slots.header" #header>
  50 + <slot name="header"></slot>
  51 + </template>
  52 + <template v-if="slots.default" #default>
  53 + <slot name="default"></slot>
  54 + </template>
  55 + <template v-if="slots.corner" #corner>
  56 + <slot name="corner"></slot>
  57 + </template>
  58 + <template v-if="slots.footer" #footer>
  59 + <slot name="footer"></slot>
  60 + </template>
  61 + </vxe-modal>
  62 +</template>
... ...
yudao-ui-admin-vue3/src/components/index.ts
... ... @@ -4,6 +4,7 @@ import { Form } from &#39;@/components/Form&#39;
4 4 import { Table } from '@/components/Table'
5 5 import { Search } from '@/components/Search'
6 6 import { Dialog } from '@/components/Dialog'
  7 +import { XModal } from '@/components/XModal'
7 8 import { DictTag } from '@/components/DictTag'
8 9 import { ContentWrap } from '@/components/ContentWrap'
9 10 import { Descriptions } from '@/components/Descriptions'
... ... @@ -14,6 +15,7 @@ export const setupGlobCom = (app: App&lt;Element&gt;): void =&gt; {
14 15 app.component('Table', Table)
15 16 app.component('Search', Search)
16 17 app.component('Dialog', Dialog)
  18 + app.component('XModal', XModal)
17 19 app.component('DictTag', DictTag)
18 20 app.component('ContentWrap', ContentWrap)
19 21 app.component('Descriptions', Descriptions)
... ...
yudao-ui-admin-vue3/src/hooks/web/useVxeGrid.ts
... ... @@ -4,6 +4,7 @@ import { VxeGridProps } from &#39;vxe-table&#39;
4 4 export const useVxeGrid = (allSchemas, getPageApi) => {
5 5 const gridOptions = reactive<VxeGridProps>({
6 6 loading: false,
  7 + height: 800,
7 8 rowConfig: {
8 9 keyField: 'id',
9 10 isHover: true
... ...
yudao-ui-admin-vue3/src/plugins/vxeTable/index.ts
... ... @@ -114,8 +114,10 @@ VXETable.setup({
114 114 titleColon: true // 是否显示标题冒号
115 115 },
116 116 modal: {
117   - width: 600, // 窗口的宽度
118   - height: 400, // 窗口的高度
  117 + width: 800, // 窗口的宽度
  118 + height: 600, // 窗口的高度
  119 + minWidth: 460,
  120 + minHeight: 320,
119 121 showZoom: true, // 标题是否标显示最大化与还原按钮
120 122 resize: true, // 是否允许窗口边缘拖动调整窗口大小
121 123 marginSize: 0, // 只对 resize 启用后有效,用于设置可拖动界限范围,如果为负数则允许拖动超出屏幕边界
... ...
yudao-ui-admin-vue3/src/views/system/menu/index.vue
1   -<script setup lang="ts">
2   -import * as MenuApi from '@/api/system/menu'
3   -import { MenuVO } from '@/api/system/menu/types'
4   -import { useI18n } from '@/hooks/web/useI18n'
5   -import { useMessage } from '@/hooks/web/useMessage'
6   -import { IconSelect } from '@/components/Icon'
7   -import { Tooltip } from '@/components/Tooltip'
8   -import { required } from '@/utils/formRules.js'
9   -import { onMounted, reactive, ref } from 'vue'
10   -import { VxeTableInstance } from 'vxe-table'
11   -import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
12   -import { SystemMenuTypeEnum, CommonStatusEnum } from '@/utils/constants'
13   -import {
14   - ElRow,
15   - ElCol,
16   - ElForm,
17   - ElFormItem,
18   - ElInput,
19   - ElInputNumber,
20   - ElSelect,
21   - ElTreeSelect,
22   - ElOption,
23   - ElRadioGroup,
24   - ElRadioButton
25   -} from 'element-plus'
26   -import { handleTree } from '@/utils/tree'
27   -const { t } = useI18n() // 国际化
28   -const message = useMessage()
29   -const xTable = ref<VxeTableInstance>()
30   -const tableLoading = ref(false)
31   -const tableData = ref()
32   -const actionLoading = ref(false) // 遮罩层
33   -const actionType = ref('') // 操作按钮的类型
34   -const dialogVisible = ref(false) // 是否显示弹出层
35   -const dialogTitle = ref('edit') // 弹出层标题
36   -const statusOption = ref() // 状态选项
37   -const menuForm = ref<MenuVO>({
38   - id: 0,
39   - name: '',
40   - permission: '',
41   - type: SystemMenuTypeEnum.DIR,
42   - sort: 1,
43   - parentId: 0,
44   - path: '',
45   - icon: '',
46   - component: '',
47   - status: CommonStatusEnum.ENABLE,
48   - visible: true,
49   - keepAlive: true,
50   - createTime: ''
51   -})
52   -const menuProps = {
53   - checkStrictly: true,
54   - children: 'children',
55   - label: 'name',
56   - value: 'id'
57   -}
58   -interface Tree {
59   - id: number
60   - name: string
61   - children?: Tree[] | any[]
62   -}
63   -const menuOptions = ref<any[]>([]) // 树形结构
64   -const getTree = async () => {
65   - menuOptions.value = []
66   - const res = await MenuApi.listSimpleMenusApi()
67   - let menu: Tree = { id: 0, name: '主类目', children: [] }
68   - menu.children = handleTree(res)
69   - menuOptions.value.push(menu)
70   -}
71   -// ========== 查询 ==========
72   -const queryParams = reactive({
73   - name: null,
74   - status: null
75   -})
76   -const getList = async () => {
77   - statusOption.value = getIntDictOptions(DICT_TYPE.COMMON_STATUS)
78   - tableLoading.value = true
79   - const res = await MenuApi.getMenuListApi(queryParams)
80   - tableData.value = res
81   - tableLoading.value = false
82   -}
83   -// 设置标题
84   -const setDialogTile = (type: string) => {
85   - dialogTitle.value = t('action.' + type)
86   - actionType.value = type
87   - dialogVisible.value = true
88   -}
89   -// 新建操作
90   -const handleCreate = () => {
91   - setDialogTile('create')
92   -}
93   -// 修改操作
94   -const handleUpdate = async (row: MenuVO) => {
95   - // 设置数据
96   - const res = await MenuApi.getMenuApi(row.id)
97   - console.log(res)
98   - menuForm.value = res
99   - setDialogTile('update')
100   -}
101   -// 删除操作
102   -const handleDelete = async (row: MenuVO) => {
103   - message.confirm(t('common.delDataMessage'), t('common.confirmTitle')).then(async () => {
104   - await MenuApi.deleteMenuApi(row.id)
105   - message.success(t('common.delSuccess'))
106   - await getList()
107   - })
108   -}
109   -// 表单校验
110   -const rules = reactive({
111   - name: [required],
112   - sort: [required],
113   - path: [required],
114   - status: [required]
115   -})
116   -// 查询操作
117   -const handleQuery = async () => {
118   - await getList()
119   -}
120   -// 重置操作
121   -const resetQuery = async () => {
122   - queryParams.name = null
123   - queryParams.status = null
124   - await getList()
125   -}
126   -// 保存操作
127   -const isExternal = (path: string) => {
128   - return /^(https?:|mailto:|tel:)/.test(path)
129   -}
130   -const submitForm = async () => {
131   - actionLoading.value = true
132   - // 提交请求
133   - try {
134   - if (
135   - menuForm.value.type === SystemMenuTypeEnum.DIR ||
136   - menuForm.value.type === SystemMenuTypeEnum.MENU
137   - ) {
138   - if (!isExternal(menuForm.value.path)) {
139   - if (menuForm.value.parentId === 0 && menuForm.value.path.charAt(0) !== '/') {
140   - message.error('路径必须以 / 开头')
141   - return
142   - } else if (menuForm.value.parentId !== 0 && menuForm.value.path.charAt(0) === '/') {
143   - message.error('路径不能以 / 开头')
144   - return
145   - }
146   - }
147   - }
148   - if (actionType.value === 'create') {
149   - await MenuApi.createMenuApi(menuForm.value)
150   - message.success(t('common.createSuccess'))
151   - } else {
152   - await MenuApi.updateMenuApi(menuForm.value)
153   - message.success(t('common.updateSuccess'))
154   - }
155   - // 操作成功,重新加载列表
156   - dialogVisible.value = false
157   - await getList()
158   - } finally {
159   - actionLoading.value = false
160   - }
161   -}
162   -onMounted(async () => {
163   - await getList()
164   - getTree()
165   -})
166   -</script>
167 1 <template>
168 2 <ContentWrap>
169 3 <el-form :model="queryParams" ref="queryForm" :inline="true">
... ... @@ -211,6 +45,7 @@ onMounted(async () =&gt; {
211 45 :print-config="{}"
212 46 :export-config="{}"
213 47 :data="tableData"
  48 + class="xtable"
214 49 >
215 50 <vxe-column title="菜单名称" field="name" width="200" tree-node>
216 51 <template #default="{ row }">
... ... @@ -255,19 +90,7 @@ onMounted(async () =&gt; {
255 90 </vxe-column>
256 91 </vxe-table>
257 92 </ContentWrap>
258   - <vxe-modal
259   - v-model="dialogVisible"
260   - id="menuModel"
261   - :title="dialogTitle"
262   - width="800"
263   - height="6f00"
264   - min-width="460"
265   - min-height="320"
266   - show-zoom
267   - resize
268   - transfer
269   - show-footer
270   - >
  93 + <XModal v-model="dialogVisible" id="menuModel" :title="dialogTitle">
271 94 <template #default>
272 95 <!-- 对话框(添加 / 修改) -->
273 96 <el-form
... ... @@ -410,10 +233,175 @@ onMounted(async () =&gt; {
410 233 type="primary"
411 234 :loading="actionLoading"
412 235 @click="submitForm"
413   - >
414   - {{ t('action.save') }}
415   - </el-button>
416   - <el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
  236 + :content="t('action.save')"
  237 + />
  238 + <el-button @click="dialogVisible = false" :content="t('dialog.close')" />
417 239 </template>
418   - </vxe-modal>
  240 + </XModal>
419 241 </template>
  242 +<script setup lang="ts">
  243 +import * as MenuApi from '@/api/system/menu'
  244 +import { MenuVO } from '@/api/system/menu/types'
  245 +import { useI18n } from '@/hooks/web/useI18n'
  246 +import { useMessage } from '@/hooks/web/useMessage'
  247 +import { IconSelect } from '@/components/Icon'
  248 +import { Tooltip } from '@/components/Tooltip'
  249 +import { required } from '@/utils/formRules.js'
  250 +import { onMounted, reactive, ref } from 'vue'
  251 +import { VxeTableInstance } from 'vxe-table'
  252 +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
  253 +import { SystemMenuTypeEnum, CommonStatusEnum } from '@/utils/constants'
  254 +import {
  255 + ElRow,
  256 + ElCol,
  257 + ElForm,
  258 + ElFormItem,
  259 + ElInput,
  260 + ElInputNumber,
  261 + ElSelect,
  262 + ElTreeSelect,
  263 + ElOption,
  264 + ElRadioGroup,
  265 + ElRadioButton
  266 +} from 'element-plus'
  267 +import { handleTree } from '@/utils/tree'
  268 +const { t } = useI18n() // 国际化
  269 +const message = useMessage()
  270 +const xTable = ref<VxeTableInstance>()
  271 +const tableLoading = ref(false)
  272 +const tableData = ref()
  273 +const actionLoading = ref(false) // 遮罩层
  274 +const actionType = ref('') // 操作按钮的类型
  275 +const dialogVisible = ref(false) // 是否显示弹出层
  276 +const dialogTitle = ref('edit') // 弹出层标题
  277 +const statusOption = ref() // 状态选项
  278 +const menuForm = ref<MenuVO>({
  279 + id: 0,
  280 + name: '',
  281 + permission: '',
  282 + type: SystemMenuTypeEnum.DIR,
  283 + sort: 1,
  284 + parentId: 0,
  285 + path: '',
  286 + icon: '',
  287 + component: '',
  288 + status: CommonStatusEnum.ENABLE,
  289 + visible: true,
  290 + keepAlive: true,
  291 + createTime: ''
  292 +})
  293 +const menuProps = {
  294 + checkStrictly: true,
  295 + children: 'children',
  296 + label: 'name',
  297 + value: 'id'
  298 +}
  299 +interface Tree {
  300 + id: number
  301 + name: string
  302 + children?: Tree[] | any[]
  303 +}
  304 +const menuOptions = ref<any[]>([]) // 树形结构
  305 +const getTree = async () => {
  306 + menuOptions.value = []
  307 + const res = await MenuApi.listSimpleMenusApi()
  308 + let menu: Tree = { id: 0, name: '主类目', children: [] }
  309 + menu.children = handleTree(res)
  310 + menuOptions.value.push(menu)
  311 +}
  312 +// ========== 查询 ==========
  313 +const queryParams = reactive({
  314 + name: null,
  315 + status: null
  316 +})
  317 +const getList = async () => {
  318 + statusOption.value = getIntDictOptions(DICT_TYPE.COMMON_STATUS)
  319 + tableLoading.value = true
  320 + const res = await MenuApi.getMenuListApi(queryParams)
  321 + tableData.value = res
  322 + tableLoading.value = false
  323 +}
  324 +// 设置标题
  325 +const setDialogTile = (type: string) => {
  326 + dialogTitle.value = t('action.' + type)
  327 + actionType.value = type
  328 + dialogVisible.value = true
  329 +}
  330 +// 新建操作
  331 +const handleCreate = () => {
  332 + setDialogTile('create')
  333 +}
  334 +// 修改操作
  335 +const handleUpdate = async (row: MenuVO) => {
  336 + // 设置数据
  337 + const res = await MenuApi.getMenuApi(row.id)
  338 + console.log(res)
  339 + menuForm.value = res
  340 + setDialogTile('update')
  341 +}
  342 +// 删除操作
  343 +const handleDelete = async (row: MenuVO) => {
  344 + message.confirm(t('common.delDataMessage'), t('common.confirmTitle')).then(async () => {
  345 + await MenuApi.deleteMenuApi(row.id)
  346 + message.success(t('common.delSuccess'))
  347 + await getList()
  348 + })
  349 +}
  350 +// 表单校验
  351 +const rules = reactive({
  352 + name: [required],
  353 + sort: [required],
  354 + path: [required],
  355 + status: [required]
  356 +})
  357 +// 查询操作
  358 +const handleQuery = async () => {
  359 + await getList()
  360 +}
  361 +// 重置操作
  362 +const resetQuery = async () => {
  363 + queryParams.name = null
  364 + queryParams.status = null
  365 + await getList()
  366 +}
  367 +// 保存操作
  368 +const isExternal = (path: string) => {
  369 + return /^(https?:|mailto:|tel:)/.test(path)
  370 +}
  371 +const submitForm = async () => {
  372 + actionLoading.value = true
  373 + // 提交请求
  374 + try {
  375 + if (
  376 + menuForm.value.type === SystemMenuTypeEnum.DIR ||
  377 + menuForm.value.type === SystemMenuTypeEnum.MENU
  378 + ) {
  379 + if (!isExternal(menuForm.value.path)) {
  380 + if (menuForm.value.parentId === 0 && menuForm.value.path.charAt(0) !== '/') {
  381 + message.error('路径必须以 / 开头')
  382 + return
  383 + } else if (menuForm.value.parentId !== 0 && menuForm.value.path.charAt(0) === '/') {
  384 + message.error('路径不能以 / 开头')
  385 + return
  386 + }
  387 + }
  388 + }
  389 + if (actionType.value === 'create') {
  390 + await MenuApi.createMenuApi(menuForm.value)
  391 + message.success(t('common.createSuccess'))
  392 + } else {
  393 + await MenuApi.updateMenuApi(menuForm.value)
  394 + message.success(t('common.updateSuccess'))
  395 + }
  396 + // 操作成功,重新加载列表
  397 + dialogVisible.value = false
  398 + await getList()
  399 + } finally {
  400 + actionLoading.value = false
  401 + }
  402 +}
  403 +onMounted(async () => {
  404 + await getList()
  405 + getTree()
  406 +})
  407 +</script>
... ...
yudao-ui-admin-vue3/src/views/system/post/index.vue
  1 +<template>
  2 + <ContentWrap>
  3 + <vxe-grid ref="xGrid" v-bind="gridOptions" class="xtable-scrollbar">
  4 + <template #toolbar_buttons>
  5 + <el-button type="primary" v-hasPermi="['system:post:create']" @click="handleCreate">
  6 + <Icon icon="ep:zoom-in" class="mr-5px" /> {{ t('action.add') }}
  7 + </el-button>
  8 + </template>
  9 + <template #status_default="{ row }">
  10 + <DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
  11 + </template>
  12 + <template #action_default="{ row }">
  13 + <el-button
  14 + link
  15 + type="primary"
  16 + v-hasPermi="['system:post:update']"
  17 + @click="handleUpdate(row.id)"
  18 + >
  19 + <Icon icon="ep:edit" class="mr-1px" /> {{ t('action.edit') }}
  20 + </el-button>
  21 + <el-button
  22 + link
  23 + type="primary"
  24 + v-hasPermi="['system:post:update']"
  25 + @click="handleDetail(row)"
  26 + >
  27 + <Icon icon="ep:view" class="mr-1px" /> {{ t('action.detail') }}
  28 + </el-button>
  29 + <el-button
  30 + link
  31 + type="primary"
  32 + v-hasPermi="['system:post:delete']"
  33 + @click="handleDelete(row.id)"
  34 + >
  35 + <Icon icon="ep:delete" class="mr-1px" /> {{ t('action.del') }}
  36 + </el-button>
  37 + </template>
  38 + </vxe-grid>
  39 + </ContentWrap>
  40 + <XModal id="postModel" v-model="dialogVisible" :title="dialogTitle">
  41 + <template #default>
  42 + <!-- 对话框(添加 / 修改) -->
  43 + <vxe-form
  44 + ref="xForm"
  45 + v-if="['create', 'update'].includes(actionType)"
  46 + :data="formData"
  47 + :items="formItems"
  48 + :rules="rules"
  49 + />
  50 + <Descriptions
  51 + v-if="actionType === 'detail'"
  52 + :schema="allSchemas.detailSchema"
  53 + :data="detailRef"
  54 + >
  55 + <template #status="{ row }">
  56 + <DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
  57 + </template>
  58 + <template #createTime="{ row }">
  59 + <span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
  60 + </template>
  61 + </Descriptions>
  62 + </template>
  63 + <template #footer>
  64 + <vxe-button
  65 + v-if="['create', 'update'].includes(actionType)"
  66 + status="primary"
  67 + @click="submitForm"
  68 + :content="t('action.save')"
  69 + />
  70 + <vxe-button @click="dialogVisible = false" :content="t('dialog.close')" />
  71 + </template>
  72 + </XModal>
  73 +</template>
1 74 <script setup lang="ts">
2 75 import { ref } from 'vue'
3 76 import dayjs from 'dayjs'
... ... @@ -82,89 +155,3 @@ const submitForm: VxeFormEvents.Submit = async () =&gt; {
82 155 }
83 156 }
84 157 </script>
85   -<template>
86   - <ContentWrap>
87   - <vxe-grid ref="xGrid" v-bind="gridOptions">
88   - <template #toolbar_buttons>
89   - <el-button type="primary" v-hasPermi="['system:post:create']" @click="handleCreate">
90   - <Icon icon="ep:zoom-in" class="mr-5px" /> {{ t('action.add') }}
91   - </el-button>
92   - </template>
93   - <template #status_default="{ row }">
94   - <DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
95   - </template>
96   - <template #action_default="{ row }">
97   - <el-button
98   - link
99   - type="primary"
100   - v-hasPermi="['system:post:update']"
101   - @click="handleUpdate(row.id)"
102   - >
103   - <Icon icon="ep:edit" class="mr-1px" /> {{ t('action.edit') }}
104   - </el-button>
105   - <el-button
106   - link
107   - type="primary"
108   - v-hasPermi="['system:post:update']"
109   - @click="handleDetail(row)"
110   - >
111   - <Icon icon="ep:view" class="mr-1px" /> {{ t('action.detail') }}
112   - </el-button>
113   - <el-button
114   - link
115   - type="primary"
116   - v-hasPermi="['system:post:delete']"
117   - @click="handleDelete(row.id)"
118   - >
119   - <Icon icon="ep:delete" class="mr-1px" /> {{ t('action.del') }}
120   - </el-button>
121   - </template>
122   - </vxe-grid>
123   - </ContentWrap>
124   - <vxe-modal
125   - v-model="dialogVisible"
126   - id="postModel"
127   - :title="dialogTitle"
128   - width="800"
129   - height="400"
130   - min-width="460"
131   - min-height="320"
132   - show-zoom
133   - resize
134   - transfer
135   - show-footer
136   - >
137   - <template #default>
138   - <!-- 对话框(添加 / 修改) -->
139   - <vxe-form
140   - ref="xForm"
141   - v-if="['create', 'update'].includes(actionType)"
142   - :data="formData"
143   - :items="formItems"
144   - :rules="rules"
145   - />
146   - <Descriptions
147   - v-if="actionType === 'detail'"
148   - :schema="allSchemas.detailSchema"
149   - :data="detailRef"
150   - >
151   - <template #status="{ row }">
152   - <DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
153   - </template>
154   - <template #createTime="{ row }">
155   - <span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
156   - </template>
157   - </Descriptions>
158   - </template>
159   - <template #footer>
160   - <vxe-button
161   - v-if="['create', 'update'].includes(actionType)"
162   - status="primary"
163   - @click="submitForm"
164   - >
165   - {{ t('action.save') }}
166   - </vxe-button>
167   - <vxe-button @click="dialogVisible = false">{{ t('dialog.close') }}</vxe-button>
168   - </template>
169   - </vxe-modal>
170   -</template>
... ...