feat: 集成菜单新增菜单功能

* feat: api新增菜单

* feat: 集成菜单新增菜单功能

* feat: 集成菜单应用新增菜单

* fix: bug#20293
This commit is contained in:
qiaochuLei 2023-11-23 10:09:19 +08:00 committed by GitHub
parent 708078e03b
commit 6c8e8f3608
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 153 additions and 221 deletions

View File

@ -20,4 +20,6 @@ export const saveMenuInfo_api = (data: object) => server.patch(`/menu`, data);
// 新增菜单信息 // 新增菜单信息
export const addMenuInfo_api = (data: object) => server.post(`/menu`, data); export const addMenuInfo_api = (data: object) => server.post(`/menu`, data);
// 删除菜单信息 // 删除菜单信息
export const delMenuInfo_api = (id: string) => server.remove(`/menu/${id}`); export const delMenuInfo_api = (id: string) => server.remove(`/menu/${id}`);
//查询集成菜单
export const queryApp = (data:any) => server.post('/application/_query/no-paging',data)

View File

@ -221,6 +221,7 @@ const permission = 'system/Apply';
const typeOptions = ref<any[]>([]) const typeOptions = ref<any[]>([])
const visible = ref<boolean>(false) const visible = ref<boolean>(false)
const addMenuVisible = ref<boolean>(false)
onMounted(() => { onMounted(() => {
queryType().then((resp: any) => { queryType().then((resp: any) => {

View File

@ -4,209 +4,125 @@
<h3>基本信息</h3> <h3>基本信息</h3>
<j-form ref="basicFormRef" :model="form.data" class="basic-form"> <j-form ref="basicFormRef" :model="form.data" class="basic-form">
<div class="row" style="display: flex"> <div class="row" style="display: flex">
<j-form-item <j-form-item ref="uploadIcon" label="菜单图标" name="icon" :rules="[
ref="uploadIcon" {
label="菜单图标" required: true,
name="icon" message: '请上传图标',
:rules="[ trigger: 'change',
{ },
required: true, ]" style="flex: 0 0 186px">
message: '请上传图标',
trigger: 'change',
},
]"
style="flex: 0 0 186px"
>
<div class="icon-upload has-icon" v-if="form.data.icon"> <div class="icon-upload has-icon" v-if="form.data.icon">
<AIcon <AIcon :type="form.data.icon" style="font-size: 90px" />
:type="form.data.icon" <span class="mark" @click="dialogVisible = true">点击修改</span>
style="font-size: 90px"
/>
<span class="mark" @click="dialogVisible = true"
>点击修改</span
>
</div> </div>
<div <div v-else @click="dialogVisible = true" class="icon-upload no-icon">
v-else
@click="dialogVisible = true"
class="icon-upload no-icon"
>
<span> <span>
<AIcon <AIcon type="PlusOutlined" style="font-size: 30px" />
type="PlusOutlined"
style="font-size: 30px"
/>
<p>点击选择图标</p> <p>点击选择图标</p>
</span> </span>
</div> </div>
</j-form-item> </j-form-item>
<j-row :gutter="24" style="flex: 1 1 auto"> <j-row :gutter="24" style="flex: 1 1 auto">
<j-col :span="12"> <j-col :span="12">
<j-form-item <j-form-item label="名称" name="name" :rules="[
label="名称" {
name="name" required: true,
:rules="[ message: '请输入名称',
{ trigger: 'change',
required: true, },
message: '请输入名称', {
trigger: 'change', max: 64,
}, message: '最多可输入64个字符',
{ trigger: 'change',
max: 64, },
message: '最多可输入64个字符', ]">
trigger: 'change', <j-input v-model:value="form.data.name" placeholder="请输入名称" />
},
]"
>
<j-input
v-model:value="form.data.name"
placeholder="请输入名称"
/>
</j-form-item> </j-form-item>
</j-col> </j-col>
<j-col :span="12"> <j-col :span="12">
<j-form-item <j-form-item label="编码" name="code" :rules="[
label="编码" {
name="code" required: true,
:rules="[ message: '请输入编码',
{ trigger: 'change',
required: true, },
message: '请输入编码', {
trigger: 'change', max: 64,
}, message: '最多可输入64个字符',
{ trigger: 'change',
max: 64, },
message: '最多可输入64个字符', {
trigger: 'change', validator: form.checkCode,
}, trigger: 'blur',
{ },
validator: form.checkCode, ]">
trigger: 'blur', <j-input v-model:value="form.data.code" placeholder="请输入编码" />
},
]"
>
<j-input
v-model:value="form.data.code"
placeholder="请输入编码"
/>
</j-form-item> </j-form-item>
</j-col> </j-col>
<j-col :span="12"> <j-col :span="12">
<j-form-item <j-form-item label="页面地址" name="url" :rules="[
label="页面地址" {
name="url" required: true,
:rules="[ message: '请输入页面地址',
{ },
required: true, { max: 128, message: '最多可输入128字符' },
message: '请输入页面地址', { pattern: /^\//, message: '请正确填写地址,以/开头' },
}, ]">
{ max: 128, message: '最多可输入128字符' }, <j-input v-model:value="form.data.url" placeholder="请输入页面地址" />
{ pattern: /^\// ,message:'请正确填写地址,以/开头'},
]"
>
<j-input
v-model:value="form.data.url"
placeholder="请输入页面地址"
/>
</j-form-item> </j-form-item>
</j-col> </j-col>
<j-col :span="12"> <j-col :span="12">
<j-form-item <j-form-item label="排序" name="sortIndex" :rules="[
label="排序" {
name="sortIndex" pattern: /^[0-9]*[1-9][0-9]*$/,
:rules="[ message: '请输入大于0的整数',
{ },
pattern: /^[0-9]*[1-9][0-9]*$/, ]">
message: '请输入大于0的整数', <j-input-number v-model:value="form.data.sortIndex" placeholder="请输入排序"
}, style="width: 100%" />
]" </j-form-item>
> </j-col>
<j-input-number <j-col :span="12" v-if="!isChildren">
v-model:value="form.data.sortIndex" <j-form-item label="所属应用" name="appId">
placeholder="请输入排序" <j-select v-model:value="form.data.appId" :options="appOptions" :allowClear="!routeParams.id"
style="width: 100%" placeholder="请选择所属应用" style="width: 100%" @change="selectApp"/>
/>
</j-form-item> </j-form-item>
</j-col> </j-col>
<j-col :span="12">
<j-form-item
label="所属应用"
name="owner"
>
<j-select
v-model:value="form.data.owner"
:options="[{ label: 'Iot', value: 'iot' }]"
allowClear
placeholder="请选择所属应用"
style="width: 100%"
/>
</j-form-item>
</j-col>
</j-row> </j-row>
</div> </div>
<j-form-item label="说明" name="describe"> <j-form-item label="说明" name="describe">
<j-textarea <j-textarea v-model:value="form.data.describe" :rows="4" show-count :maxlength="200"
v-model:value="form.data.describe" placeholder="请输入说明" />
:rows="4"
show-count
:maxlength="200"
placeholder="请输入说明"
/>
</j-form-item> </j-form-item>
</j-form> </j-form>
</div> </div>
<div class="card"> <div class="card" v-if="!form.data.appId && !isChildren">
<h3>权限配置</h3> <h3>权限配置</h3>
<j-form <j-form ref="permissFormRef" :model="form.data" class="basic-form permiss-form">
ref="permissFormRef"
:model="form.data"
class="basic-form permiss-form"
>
<j-form-item name="accessSupport" required v-if="isNoCommunity"> <j-form-item name="accessSupport" required v-if="isNoCommunity">
<template #label> <template #label>
<span style="margin-right: 3px">数据权限控制</span> <span style="margin-right: 3px">数据权限控制</span>
<j-tooltip title="此菜单页面数据所对应的资产类型"> <j-tooltip title="此菜单页面数据所对应的资产类型">
<AIcon <AIcon type="QuestionCircleOutlined" class="img-style" style="color: #a6a6a6" />
type="QuestionCircleOutlined"
class="img-style"
style="color: #a6a6a6"
/>
</j-tooltip> </j-tooltip>
</template> </template>
<j-radio-group <j-radio-group v-model:value="form.data.accessSupport" name="radioGroup">
v-model:value="form.data.accessSupport"
name="radioGroup"
>
<j-radio value="unsupported">不支持</j-radio> <j-radio value="unsupported">不支持</j-radio>
<j-radio value="support">支持</j-radio> <j-radio value="support">支持</j-radio>
<j-radio value="indirect"> <j-radio value="indirect">
<span style="margin-right: 3px">间接控制</span> <span style="margin-right: 3px">间接控制</span>
<j-tooltip <j-tooltip title="此菜单内的数据基于其他菜单的数据权限控制">
title="此菜单内的数据基于其他菜单的数据权限控制" <AIcon type="QuestionCircleFilled" class="img-style" />
>
<AIcon
type="QuestionCircleFilled"
class="img-style"
/>
</j-tooltip> </j-tooltip>
</j-radio> </j-radio>
</j-radio-group> </j-radio-group>
<j-form-item <j-form-item name="assetType" v-if="form.data.accessSupport === 'support'"
name="assetType" :rules="[{ required: true, message: '请选择资产类型' }]" style="margin-top: 24px; margin-bottom: 0">
v-if="form.data.accessSupport === 'support'" <j-select v-model:value="form.data.assetType" style="width: 500px" placeholder="请选择资产类型" show-search
:rules="[{ required: true, message: '请选择资产类型' }]" :options="form.assetsType">
style="margin-top: 24px; margin-bottom: 0"
>
<j-select
v-model:value="form.data.assetType"
style="width: 500px"
placeholder="请选择资产类型"
show-search
:options="form.assetsType"
>
<!-- <j-select-option <!-- <j-select-option
v-for="item in form.assetsType" v-for="item in form.assetsType"
:value="item.value" :value="item.value"
@ -215,61 +131,32 @@
</j-select> </j-select>
</j-form-item> </j-form-item>
<j-form-item <j-form-item name="indirectMenus" v-if="form.data.accessSupport === 'indirect'"
name="indirectMenus" :rules="[{ required: true, message: '请选择关联菜单' }]" style="margin-top: 24px; margin-bottom: 0">
v-if="form.data.accessSupport === 'indirect'" <j-tree-select v-model:value="form.data.indirectMenus" style="width: 400px" :dropdown-style="{
:rules="[{ required: true, message: '请选择关联菜单' }]" maxHeight: '400px',
style="margin-top: 24px; margin-bottom: 0" overflow: 'auto',
> }" placeholder="请选择关联菜单" multiple show-search :tree-data="form.treeData" :field-names="{
<j-tree-select children: 'children',
v-model:value="form.data.indirectMenus" label: 'name',
style="width: 400px" value: 'id',
:dropdown-style="{ }">
maxHeight: '400px',
overflow: 'auto',
}"
placeholder="请选择关联菜单"
multiple
show-search
:tree-data="form.treeData"
:field-names="{
children: 'children',
label: 'name',
value: 'id',
}"
>
</j-tree-select> </j-tree-select>
</j-form-item> </j-form-item>
</j-form-item> </j-form-item>
<j-form-item label="权限"> <j-form-item label="权限">
<PermissChoose <PermissChoose :first-width="3" max-height="350px" v-model:value="form.data.permissions"
:first-width="3" :key="form.data.id || ''" />
max-height="350px"
v-model:value="form.data.permissions"
:key="form.data.id || ''"
/>
</j-form-item> </j-form-item>
</j-form> </j-form>
<PermissionButton
type="primary"
:hasPermission="`${permission}:${
route.params.id === ':id' ? 'add' : 'update'
}`"
@click="form.clickSave"
:loading='form.saveLoading'
>
保存
</PermissionButton>
</div> </div>
<PermissionButton type="primary" :hasPermission="`${permission}:${route.params.id === ':id' ? 'add' : 'update'
}`" @click="form.clickSave" :loading='form.saveLoading' class="saveBtn">
保存
</PermissionButton>
<!-- 弹窗 --> <!-- 弹窗 -->
<ChooseIconDialog <ChooseIconDialog v-if="dialogVisible" v-model:visible="dialogVisible" :icon="form.data.icon"
v-if="dialogVisible" @confirm="(typeStr: string) => choseIcon(typeStr)" />
v-model:visible="dialogVisible"
:icon="form.data.icon"
@confirm="(typeStr:string)=>choseIcon(typeStr)"
/>
</div> </div>
</template> </template>
@ -285,10 +172,12 @@ import {
saveMenuInfo_api, saveMenuInfo_api,
addMenuInfo_api, addMenuInfo_api,
validCode_api, validCode_api,
queryApp
} from '@/api/system/menu'; } from '@/api/system/menu';
import { Rule } from 'ant-design-vue/lib/form'; import { Rule } from 'ant-design-vue/lib/form';
import { isNoCommunity } from '@/utils/utils'; import { isNoCommunity } from '@/utils/utils';
import { onlyMessage } from '@/utils/comm'; import { onlyMessage } from '@/utils/comm';
import { applicationInfo } from '@/api/bind';
const permission = 'system/Menu'; const permission = 'system/Menu';
// //
@ -300,11 +189,13 @@ const routeParams = {
url: route.query.basePath, url: route.query.basePath,
parentId: route.query.pid, parentId: route.query.pid,
}; };
const isChildren = route.query?.isChildren
// //
const basicFormRef = ref<FormInstance>(); const basicFormRef = ref<FormInstance>();
const permissFormRef = ref<FormInstance>(); const permissFormRef = ref<FormInstance>();
const uploadIcon = ref<FormInstance>(); const uploadIcon = ref<FormInstance>();
//
const appOptions = ref<any>([])
const form = reactive({ const form = reactive({
data: { data: {
name: '', name: '',
@ -316,6 +207,8 @@ const form = reactive({
accessSupport: 'unsupported', accessSupport: 'unsupported',
assetType: undefined, assetType: undefined,
indirectMenus: [], indirectMenus: [],
appId: '',
application:'',
...routeParams, ...routeParams,
} as formType, } as formType,
treeData: [], // treeData: [], //
@ -383,20 +276,23 @@ const form = reactive({
const api = routeParams.id ? saveMenuInfo_api : addMenuInfo_api; const api = routeParams.id ? saveMenuInfo_api : addMenuInfo_api;
form.saveLoading = true; form.saveLoading = true;
const accessSupportValue = form.data.accessSupport; const accessSupportValue = form.data.accessSupport;
const params = { const params:any = {
...form.data, ...form.data,
owner: form.data?.owner ?? null, owner: 'iot',
options: form.data?.options || { show: true }, options: { show: true },
accessSupport: { accessSupport: {
value: accessSupportValue, value: accessSupportValue,
label: label:
accessSupportValue === 'unsupported' accessSupportValue === 'unsupported'
? '不支持' ? '不支持'
: accessSupportValue === 'support' : accessSupportValue === 'support'
? '支持' ? '支持'
: '间接控制', : '间接控制',
}, },
}; };
if(params?.isChildren){
delete params.isChildren
}
api(params) api(params)
.then((resp: any) => { .then((resp: any) => {
if (resp.status === 200) { if (resp.status === 200) {
@ -415,18 +311,41 @@ const form = reactive({
}) })
.finally(() => (form.saveLoading = false)); .finally(() => (form.saveLoading = false));
}) })
.catch((err) => {}); .catch((err) => { });
}, },
}); });
form.init(); form.init();
const choseIcon = (typeStr:string) =>{ const choseIcon = (typeStr: string) => {
form.data.icon = typeStr; form.data.icon = typeStr;
uploadIcon.value?.clearValidate(); uploadIcon.value?.clearValidate();
} }
const selectApp = (value:string,options:any) =>{
form.data.application = options?.label
}
// //
const dialogVisible = ref(false); const dialogVisible = ref(false);
onMounted(() => {
queryApp({
terms: [
{
"column": "integrationModes",
"termType": "in$any",
"value": "page"
}
],
paging: false
}).then((res:any)=>{
appOptions.value = res.result?.map((i:any)=>{
return {
label:i.name,
value:i.id
}
})
})
})
type formType = { type formType = {
id?: string; id?: string;
name: string; name: string;
@ -440,6 +359,8 @@ type formType = {
assetType: string | undefined; assetType: string | undefined;
indirectMenus: any[]; indirectMenus: any[];
parentId?: string; parentId?: string;
appId:string,
application:string
}; };
type assetType = { type assetType = {
@ -450,11 +371,11 @@ type assetType = {
<style lang="less" scoped> <style lang="less" scoped>
.basic-info-container { .basic-info-container {
background-color: #fff;
padding: 24px;
.card { .card {
margin-bottom: 24px; margin-bottom: 24px;
padding: 24px;
background-color: #fff;
h3 { h3 {
position: relative; position: relative;
display: flex; display: flex;
@ -479,15 +400,19 @@ type assetType = {
.basic-form { .basic-form {
.ant-form-item { .ant-form-item {
display: block; display: block;
:deep(.ant-form-item-label) { :deep(.ant-form-item-label) {
overflow: inherit; overflow: inherit;
.img-style { .img-style {
cursor: help; cursor: help;
} }
label::after { label::after {
display: none; display: none;
} }
} }
:deep(.ant-form-item-control-input-content) { :deep(.ant-form-item-control-input-content) {
.icon-upload { .icon-upload {
width: 160px; width: 160px;
@ -500,10 +425,12 @@ type assetType = {
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
transition: 0.5s; transition: 0.5s;
&:hover { &:hover {
border-color: #415ed1; border-color: #415ed1;
} }
} }
.has-icon { .has-icon {
position: relative; position: relative;
text-align: center; text-align: center;
@ -521,10 +448,12 @@ type assetType = {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
&:hover .mark { &:hover .mark {
display: flex; display: flex;
} }
} }
.no-icon { .no-icon {
background-color: rgba(0, 0, 0, 0.06); background-color: rgba(0, 0, 0, 0.06);
} }

View File

@ -236,12 +236,12 @@ const table = reactive({
}, },
addChildren: (row: any) => { addChildren: (row: any) => {
const sortIndex = row?.children?.length || 0; const sortIndex = row?.children?.length || 0;
router.push( router.push(
`/system/Menu/detail/:id?pid=${row.id}&basePath=${ `/system/Menu/detail/:id?pid=${row.id}&basePath=${
row.url || '' row.url || ''
}&sortIndex=${sortIndex + 1}`, }&sortIndex=${sortIndex + 1}&isChildren=${true}`,
); );
}, },
// //
toDetails: (row: any) => { toDetails: (row: any) => {