fix: bug#19771

* fix: bug#19771
This commit is contained in:
qiaochuLei 2023-11-09 15:34:31 +08:00 committed by GitHub
parent 8d941fd875
commit f9bc87a39d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 4317 additions and 543 deletions

View File

@ -1,2 +1,3 @@
ENV=develop
VITE_APP_BASE_API=/api
VITE_TOKEN_KEY = X-Access-Token

View File

@ -1,2 +1,3 @@
ENV=production
VITE_APP_BASE_API=/api
VITE_TOKEN_KEY = X-Access-Token

View File

@ -25,7 +25,7 @@
"event-source-polyfill": "^1.0.31",
"global": "^4.4.0",
"jetlinks-store": "^0.0.3",
"jetlinks-ui-components": "^1.0.34-4",
"jetlinks-ui-components": "^1.0.34-7",
"js-cookie": "^3.0.1",
"jsencrypt": "^3.3.2",
"less": "^4.1.3",

View File

@ -37,3 +37,5 @@ export const systemVersion = () => server.get<{edition?: string}>('/system/versi
export const queryDashboard = (data: Record<string, any>) => server.post(`/dashboard/_multi`, data)
export const fileUpload = (data: any) => server.post('/file/static', data)
export const lowCodeUrl = () => server.get('/system/config/low-code')

View File

@ -4,7 +4,8 @@ import server from '@/utils/request';
export const updateMenus = (data: any) => server.patch(`/menu/iot/_all`, data)
// 添加角色
export const addRole = (data: any) => server.post(`/role`, data)
//添加角色分组
export const addRoleGroup = (data:any) => server.patch('/role/group',data)
//更新权限菜单
export const getRoleMenu = (id: string) => server.get(`/menu/role/${id}/_grant/tree`)

View File

@ -0,0 +1,50 @@
import server, { request } from '@/utils/request';
/**
*
*/
export const getDicList = (data:any) => request.post('/dictionary/_query/no-paging',data)
/**
*
*/
export const getDic_page = (data:any) => request.post('/dictionary/_query',data)
/**
* ID是否重复
*/
export const verifyId = (id:string) => request.post(`/dictionary/_exists`,{where:`id is ${id}`})
/**
*
*/
export const addDictionary = (data:any) => request.patch('/dictionary',data)
/**
*
*/
export const deleteDictionary =(id:string) => request.delete(`/dictionary/${id}`)
/**
*
*/
export const queryDicItem = (data:any)=>request.post('/dictionary-item/_query',data)
/**
*
*/
export const saveDicItem = (data:any) => request.patch('/dictionary-item',data)
/**
*
*/
export const deleteDicItem = (id:string) => request.delete(`/dictionary-item/${id}`)
/**
* value唯一
*/
export const verifyValue = (data:any) => request.post('/dictionary-item/_exists',data)
/**
*
*/
export const downDic = (data:any) => request.post('/dictionary/detail/_query',data)

22
src/api/system/region.ts Normal file
View File

@ -0,0 +1,22 @@
import server from '@/utils/request';
// 获取全部地区(树结构)
export const getRegionTree = (): Promise<any> => server.post(`/area/_all/tree`);
// 校验名称是否存在
export const validateName = (name: string, id?: string): Promise<any> => server.get(`/area/name/_validate?name=${name}${id ? `&id=${id}` : ''}`);
//校验行政区划代码是否存在
export const validateCode = (code: string, id?: string): Promise<any> => server.get(`/area/code/_validate?code=${code}${id ? `&id=${id}` : ''}`);
// 删除
export const delRegion = (id: string): Promise<any> => server.remove(`/area/${id}`);
// 保存
export const saveRegion = (data: any): Promise<any> => server.post(`/area`, data);
// 更新
export const updateRegion = (data: any): Promise<any> => server.patch(`/area`, data);
// 获取全部内置地区(树结构)
export const getBuiltinRegionTree = (data: any): Promise<any> => server.post(`/area/builtin/_all/tree`, data);

View File

@ -1,3 +1,4 @@
import request from '@/utils/request';
import server from '@/utils/request';
// 获取角色列表
@ -26,3 +27,9 @@ export const getUserByRole_api = (data: any): Promise<any> => server.post(`/user
export const bindUser_api = (roleId:string, data: string[]): Promise<any> => server.post(`/role/${roleId}/users/_bind`, data);
// 将用户与角色解绑
export const unbindUser_api = (roleId:string, data: string[]): Promise<any> => server.post(`/role/${roleId}/users/_unbind`, data);
//查询分组
export const queryRoleGroup = (data:any) => request.post('/role/group/_query/no-paging',data)
//保存分组
export const saveRoleGroup = (data:any) => request.patch('/role/group',data)
//删除分组
export const deleteRoleGroup = (id:string) => request.remove(`/role/group/${id}`)

View File

@ -15,8 +15,11 @@ export const validateField_api = (type: 'username' | 'password', name: string) =
// 获取角色列表
export const getRoleList_api = () => server.get(`/role/_query/no-paging?paging=false`);
//获取角色列表
export const getRoleList = (data:any) => server.post('/role/group/detail/_query/tree',data)
// 获取组织列表
export const getDepartmentList_api = () => server.get(`/organization/_all/tree?paging=false`);
export const getDepartmentList_api = (data:any) => server.post(`/organization/_all/tree`,data);
// 获取用户信息
export const getUser_api = (id: string) => server.get(`/user/detail/${id}`);
@ -34,3 +37,5 @@ export const updatePassword_api = (data: { id: string, password: string }) => se
export const changeUserStatus_api = (data: object) => server.patch(`/user`,data);
// 删除用户
export const deleteUser_api = (id: string) => server.remove(`/user/${id}`);
// 查询角色不分页
export const queryRole_api = (data: any): Promise<any> => server.post(`/role/_query/no-paging`, data)

View File

@ -22,6 +22,8 @@ import '@vuemap/vue-amap/dist/style.css';
import { getAMapUiPromise } from './utils';
import { useSystem } from '@/store/system';
const emit = defineEmits('init')
const system = useSystem();
interface AMapProps {
style?: CSSProperties;
@ -65,6 +67,7 @@ const initMap = (e: any) => {
if (isOpenUi.value) {
getAMapUI();
}
emit('init', e)
};
</script>

View File

@ -0,0 +1,129 @@
<template>
<j-pro-layout
v-model:collapsed="basicLayout.collapsed"
v-model:openKeys="basicLayout.openKeys"
:breadcrumb="{ routes: breadcrumbs }"
:headerHeight='layout.headerHeight'
:pure="basicLayout.pure"
:selectedKeys="basicLayout.selectedKeys"
v-bind="layoutConf"
@backClick='routerBack'
>
<template #breadcrumbRender="slotProps">
<a
v-if="slotProps.route.index !== 0 && !slotProps.route.isLast"
@click='jump(slotProps.route)'
>
{{ slotProps.route.breadcrumbName }}
</a>
<span v-else style='cursor: default' >{{ slotProps.route.breadcrumbName }}</span>
</template>
<template #rightContentRender>
<div class="right-content">
<AIcon type="QuestionCircleOutlined" @click="toDoc" />
<Notice style="margin: 0 24px" />
<UserInfo />
</div>
</template>
<Iframe :key="route.path" />
</j-pro-layout>
</template>
<script lang="ts" name="SinglePage" setup>
import UserInfo from './components/UserInfo.vue';
import Notice from './components/Notice.vue';
import DefaultSetting from '../../../config/config';
import { useMenuStore } from '@/store/menu';
import { clearMenuItem } from 'jetlinks-ui-components/es/ProLayout/util';
import { AccountMenu } from '@/router/menu'
import { useSystem } from '@/store/system';
import { storeToRefs } from 'pinia';
import Iframe from '@/views/iframe/index.vue'
type StateType = {
collapsed: boolean;
openKeys: string[];
selectedKeys: string[];
pure: boolean;
};
const router = useRouter();
const route = useRoute();
const menu = useMenuStore();
const system = useSystem();
const {configInfo,layout, basicLayout} = storeToRefs(system);
const layoutConf = reactive({
theme: DefaultSetting.layout.theme,
siderWidth: layout.value.siderWidth,
logo: DefaultSetting.layout.logo,
title: DefaultSetting.layout.title,
menuData: menu.siderMenus,
// menuData: menu.siderMenus,
splitMenus: true,
});
watchEffect(() => {
layoutConf.theme = configInfo.value.front?.headerTheme || DefaultSetting.layout.theme;
layoutConf.title = configInfo.value.front?.title || DefaultSetting.layout.title;
layoutConf.logo = configInfo.value.front?.logo || DefaultSetting.layout.logo;
})
const components = computed(() => {
const componentName = route.matched[route.matched.length - 1]?.components?.default?.name
if (componentName !== 'BasicLayoutPage') {
return route.matched[route.matched.length - 1]?.components?.default
}
return undefined
})
/**
* 面包屑
*/
const breadcrumbs = computed(() =>
{
const paths = router.currentRoute.value.matched
return paths.map((item, index) => {
return {
index,
isLast: index === (paths.length -1),
path: item.path,
breadcrumbName: (item.meta as any).title || '',
}
})
}
);
const routerBack = () => {
router.go(-1)
}
const jump = (item: any) => {
router.push(item.path)
}
watchEffect(() => {
if (router.currentRoute) {
const paths = router.currentRoute.value.matched
basicLayout.value.selectedKeys = paths.map(item => item.path)
basicLayout.value.openKeys = paths.map(item => item.path)
console.log(paths) //
}
})
const toDoc = () => window.open('http://doc.v2.jetlinks.cn/');
</script>
<style scoped>
.right-content {
margin-right: 24px;
display: flex;
align-items: center;
}
</style>

View File

@ -175,11 +175,11 @@ watch(updateCount, () => getList());
const tabs = ref<any>([]);
const queryTypeList = async () => {
const queryTypeList = async (_tab: any[]) => {
const resp: any = await getAllNotice();
if (resp.status === 200) {
const provider = resp.result.map((i: any) => i.provider) || [];
const arr = tab.filter((item: any) => {
const arr = _tab.filter((item: any) => {
return item.type.some((i: any) => provider.includes(i))
});
tabs.value = arr;
@ -191,7 +191,15 @@ const queryTypeList = async () => {
};
onMounted(() => {
queryTypeList()
const _list: any[] = [...tab]
if(menuStory.hasMenu('process')){
_list.push({
key: 'workflow-notification',
tab: '工作流通知',
type: ['workflow-task-todo', 'workflow-task-reject', 'workflow-task-cc', 'workflow-process-finish', 'workflow-process-repealed'],
})
}
queryTypeList(_list)
})
</script>

View File

@ -64,12 +64,13 @@ import NoticeTab from './NoticeTab.vue';
const emits = defineEmits(['action']);
type DataType = 'alarm' | 'system-monitor' | 'system-business';
type DataType = 'alarm' | 'system-monitor' | 'system-business' | 'workflow-notification';
const refreshObj = ref({
'alarm': true,
'system-monitor': true,
'system-business': true,
'workflow-notification': true
});
const props = defineProps({

View File

@ -1,3 +1,4 @@
export { default as BasicLayoutPage } from './BasicLayoutPage.vue'
export { default as BlankLayoutPage } from './BlankLayoutPage.vue'
export { default as FullPage } from './FullPage.vue'
export { default as SinglePage } from './SinglePage.vue'

View File

@ -91,5 +91,5 @@ export default [
path: VideoSharePath,
component: () => import('@/views/media/Device/Channel/Share/index.vue')
},
AccountMenu
AccountMenu,
]

View File

@ -24,13 +24,23 @@ const defaultOwnParams = [
termType: 'eq',
value: 'iot'
},
// {
// column: 'owner',
// termType: 'isnull',
// value: '1',
// type: 'or'
// }
{
column: 'owner',
termType: 'isnull',
value: '1',
type: 'or'
},
]
},
{
terms: [
{
value: "%show\":true%",
termType: "like",
column: "options"
}
],
type:'or'
}
]
}
@ -94,9 +104,11 @@ export const useMenuStore = defineStore({
name, params, query, state: { params }
})
},
handleMenusMapById(item: { code: string, path: string}) {
handleMenusMapById(item: { name: string, path: string }) {
const { name, path } = item
if (name) {
this.menus[name] = { path }
}
},
queryMenuTree(isCommunity = false): Promise<any[]> {
return new Promise(async (res) => {
@ -105,9 +117,6 @@ export const useMenuStore = defineStore({
if (resp.success) {
const permission = usePermissionStore()
let resultData = resp.result
// if (!isNoCommunity) {
// resultData = filterCommunityMenus(resultData)
// }
const components = getAsyncRoutesMap()
const menusData = handleMenus(cloneDeep(resultData), components)
permission.handlePermission(resultData)

View File

@ -1,7 +1,7 @@
import { BlankLayoutPage, BasicLayoutPage } from 'components/Layout'
import { BlankLayoutPage, BasicLayoutPage, SinglePage } from 'components/Layout'
import { isNoCommunity } from '@/utils/utils'
import Iframe from '../views/iframe/index.vue'
import { shallowRef, defineAsyncComponent } from 'vue'
import { shallowRef, defineAsyncComponent, h } from 'vue'
const pagesComponent = import.meta.glob('../views/**/*.vue');
@ -158,10 +158,12 @@ const extraRouteObj = {
type Buttons = Array<{ id: string }>
const hasAppID = (item: { appId?: string, url?: string }): { isApp: boolean, appUrl: string } => {
const hasAppID = (item: any): { isApp: boolean, appUrl: string } => {
const isApp = !!item.appId
const isLowCode = !!item.options?.LowCode
return {
isApp: !!item.appId,
appUrl: `/${item.appId}${item.url}`
isApp: isApp || isLowCode,
appUrl: isApp ? `/${item.appId}${item.url}` : item.url
}
}
@ -170,17 +172,25 @@ const handleButtons = (buttons?: Buttons) => {
}
const handleMeta = (item: MenuItem, isApp: boolean) => {
const meta = item.meta
return {
icon: item.icon,
title: item.name,
hideInMenu: item.isShow === false,
title: meta?.title || item.name,
hideInMenu: meta?.hideInMenu ?? item.isShow === false,
buttons: handleButtons(item.buttons),
isApp
}
}
const findComponents = (code: string, level: number, isApp: boolean, components: any, mate: any, hasChildren: boolean) => {
const myComponents = components[code]
const findComponents = (code: string, level: number, isApp: boolean, components: any, mate: any, hasChildren: boolean, extraModules = {}) => {
const allComponents = {
...components,
...extraModules
}
const myComponents = allComponents[code]
if (level === 1) { // BasicLayoutPage
if (myComponents && !hasChildren) {
return mate?.hasLayout === false ? () => myComponents() : h(BasicLayoutPage, {}, () => [h(defineAsyncComponent(() => myComponents()), {})])
@ -195,6 +205,8 @@ const findComponents = (code: string, level: number, isApp: boolean, components:
return myComponents ? () => myComponents() : BlankLayoutPage
} else if(myComponents) { // components
return () => myComponents()
} else if(myComponents) { // components
return () => myComponents()
}
// return components['demo'] // 开发测试用
return undefined
@ -253,7 +265,7 @@ const findSaveRouteItem = (item: any, components: any) => {
return []
}
export const handleMenus = (menuData: any[], components: any, level: number = 1) => {
export const handleMenus = (menuData: any[], components: any, level: number = 1, extraModules = {}) => {
if (menuData && menuData.length) {
return menuData.map(item => {
const { isApp, appUrl } = hasAppID(item) // 是否为第三方程序
@ -266,7 +278,7 @@ export const handleMenus = (menuData: any[], components: any, level: number = 1)
children: item.children
}
route.component = findComponents(item.code, level, isApp, components, item.meta, !!item.chidlren?.length)
route.component = findComponents(item.code, level, isApp, components, item.meta, !!item.chidlren?.length, extraModules)
const extraRoute = hasExtraChildren(item, extraRouteObj)
const detail_components = findDetailRouteItem(item, components)
@ -274,12 +286,16 @@ export const handleMenus = (menuData: any[], components: any, level: number = 1)
route.children = route.children ? [...route.children, ...extraRoute] : extraRoute
}
if (item.options?.LowCode && level === 1) {
route.component = SinglePage
}
if (detail_components.length) {
route.children = route.children ? route.children.concat(detail_components) : detail_components
}
if (route.children && route.children.length) {
route.children = handleMenus(route.children, components, level + 1)
route.children = handleMenus(route.children, components, level + 1, extraModules)
}
const showChildren = route.children?.filter(r => !r.meta?.hideInMenu) || []
@ -312,8 +328,17 @@ const hideInMenu = (code: string) => {
export const handleSiderMenu = (menuData: any[]) => {
if (menuData && menuData.length) {
return menuData.map(item => {
return menuData.filter(item => {
if (('isShow' in item.options && item.options.isShow === false) || item.meta?.hideInMenu === true) {
return false
}
return true
}).map(item => {
const { isApp, appUrl } = hasAppID(item) // 是否为第三方程序
if ( item.options?.isShow !== undefined && item.isShow === undefined) {
item.isShow = item.options.isShow
}
const meta = handleMeta(item, isApp)
const route: any = {
path: isApp ? appUrl : `${item.url}`,
@ -327,7 +352,7 @@ export const handleSiderMenu = (menuData: any[]) => {
route.children = handleSiderMenu(route.children)
}
route.meta.hideInMenu = hideInMenu(item.code)
// route.meta.hideInMenu = hideInMenu(item.code)
return route
})

View File

@ -131,7 +131,7 @@ export const postStream = function(url: string, data = {}, params = {}) {
})
}
const showNotification = (message: string, description: string, key?: string, show: boolean = true) => {
export const showNotification = (message: string, description: string, key?: string, show: boolean = true) => {
if (show) {
Notification.error({
style: {
@ -144,6 +144,10 @@ const showNotification = (message: string, description: string, key?: string, sh
}
}
export const TokenLose = () => {
showNotification('Unauthorized', '用户未登录', '401')
}
/**
*
* @param {Object} error
@ -182,7 +186,7 @@ const errorHandler = (error: any) => {
} else if (status === 400) {
showNotification('Request Error', (data.message + '').substr(0, 90), '400')
} else if (status === 401) {
showNotification('Unauthorized', '用户未登录', '401')
TokenLose()
setTimeout(() => {
cleanToken()
router.replace({

View File

@ -107,7 +107,10 @@ const getType = computed(() => {
return ['device-transparent-codec'];
} else if (props.type === 'system-monitor') {
return ['system-event'];
} else {
} else if(props.type === 'workflow-notification'){
return ['workflow-task-cc','workflow-task-todo','workflow-task-reject', 'workflow-process-finish', 'workflow-process-repealed']
}
else {
return [
'alarm',
'alarm-product',

View File

@ -20,7 +20,7 @@
<script lang="ts" setup>
import NotificationRecord from './components/NotificationRecord/index.vue';
import { initData } from '../data';
import { getInitData } from '../data';
import { getAllNotice } from '@/api/account/center';
import { useRouterParams } from '@/utils/hooks/useParams';
import { useUserInfo } from '@/store/userInfo';
@ -28,7 +28,7 @@ import { useUserInfo } from '@/store/userInfo';
const tabs = ref<any[]>([]);
const router = useRouterParams();
const user = useUserInfo();
let initData:any[]
const queryTypeList = () => {
getAllNotice().then((resp: any) => {
if (resp.status === 200) {
@ -73,6 +73,7 @@ watchEffect(() => {
});
onMounted(() => {
initData = getInitData()
queryTypeList();
});
</script>

View File

@ -55,14 +55,15 @@
<script lang="ts" setup>
import { getAllNotice } from '@/api/account/center';
import { getNoticeList_api } from '@/api/account/notificationSubscription';
import { initData } from '../data';
import { getInitData } from '../data';
import Item from './components/Item.vue';
import { useMenuStore } from '@/store/menu';
const menuStore = useMenuStore();
const subscribe = ref<any[]>([]);
const dataSource = ref<any[]>([]);
const activeKey = ref<string[]>(['alarm', 'system-monitor', 'system-business']);
const activeKey = ref<string[]>();
const loading = ref<boolean>(false)
let initData:any[]
const handleSearch = () => {
loading.value = true
getAllNotice().then((resp: any) => {
@ -105,6 +106,12 @@ const handleSearch = () => {
};
onMounted(() => {
const keys = ['alarm', 'system-monitor', 'system-business']
if (menuStore.hasMenu('process')) {
keys.push('workflow-notification')
}
activeKey.value = keys
initData = getInitData()
handleSearch();
});
</script>

View File

@ -1,4 +1,6 @@
const initData: any[] = [
import { useMenuStore } from '@/store/menu';
const menuStore = useMenuStore();
const systemNotice = [
{
provider: 'alarm',
name: '告警',
@ -50,5 +52,34 @@ const initData: any[] = [
],
},
];
export { initData };
const workflowNotice = [
{
provider: 'workflow-notification',
name: '工作流通知',
children: [
{
provider: 'workflow-task-todo',
name: '待办通知',
},
{
provider: 'workflow-task-reject',
name: '驳回通知',
},
{
provider: 'workflow-task-cc',
name: '抄送通知',
},
{
provider: 'workflow-process-finish',
name: '办结通知',
},
{
provider: 'workflow-process-repealed',
name: '关闭通知',
},
],
},
]
export const getInitData = () =>{
return menuStore.hasMenu('process') ? [...systemNotice,...workflowNotice] : [...systemNotice]
}

View File

@ -1,21 +1,31 @@
<template>
<page-container>
<full-page>
<iframe
v-if="loading"
:src="iframeUrl"
scrolling="no"
frameBorder="0"
style="width: 100%; height: calc(100vh - 140px)"
style="width: 100%; height: 100%"
></iframe>
</full-page>
</page-container>
</template>
<script setup lang="ts">
<script lang="ts" name="IframgePage" setup>
import { TOKEN_KEY } from '@/utils/variable';
import { LocalStore } from '@/utils/comm';
import {LocalStore, getToken, cleanToken} from '@/utils/comm';
import { getAppInfo_api } from '@/api/system/apply';
import { lowCodeUrl } from '@/api/comm'
import FullPage from "components/Layout/FullPage.vue";
import {onUnmounted} from "vue";
import router from "@/router";
import {LoginPath} from "@/router/menu";
import { TokenLose} from "@/utils/request";
const iframeUrl = ref<string>('');
const route = useRoute()
const loading = ref(false)
const handle = async (appId: string, url: string) => {
const res = await getAppInfo_api(appId);
let menuUrl: any = url;
@ -53,14 +63,51 @@ const handle = async (appId: string, url: string) => {
}
};
const lowCode = () => {
lowCodeUrl().then(res => {
if (res.success && res.result) {
const url = res.result['ui-addr']
// const url = 'http://localhost:8080'
iframeUrl.value = url + '/#' + route.path + '?&token=' + getToken()
console.log(iframeUrl.value)
loading.value = true
}
})
}
const onMessage = (msg: any) => {
console.log('onMessage',msg)
if (msg?.data?.token === 'LOSE') {
TokenLose()
setTimeout(() => {
cleanToken()
router.replace({
path: LoginPath
})
}, 0)
}
}
onMounted(() => {
window.addEventListener('message', onMessage)
})
onUnmounted(() => {
window.removeEventListener('message', onMessage)
})
watchEffect(() => {
const matchedItem: any = route.matched?.[0]
if (matchedItem?.meta?.isApp) {
if (route.meta?.isApp) {
const params = route.path.split('/')?.[1];
if (params === 'preview') {
lowCode()
} else {
loading.value = true
const url = route.path.split('/').slice(2).join('/');
handle(params, url);
}
}
});
</script>

View File

@ -96,12 +96,26 @@ const menuCount = (menus: any[]) => {
return pre + _count;
}, 0);
};
/**
* 添加options show用于控制菜单是否显示函数
*/
const dealMenu = (data:any) =>{
data.forEach((item:any)=>{
item.options = Object.assign({
show: true
}, item?.options || {})
if(item.children){
dealMenu(item.children)
}
})
}
/**
* 初始化菜单
*/
const initMenu = async () => {
return new Promise(async (resolve) => {
//
dealMenu(menuDatas.current)
console.log([...menuDatas.current!, USER_CENTER_MENU_DATA]);
const res = await updateMenus([...menuDatas.current!, USER_CENTER_MENU_DATA]);
if (res.status === 200) {

View File

@ -1,15 +1,31 @@
<template>
<div class="init-home-role">
<div class="built_in_group">
<div>平台角色内置分组</div>
<div class="group">
<div v-for="(item, index) in group" class="group_item" @mouseover="() => showButton(item)"
@mouseleave="() => hiddenButton(item)">
<div class="group_name" :class="{ group_selected: item.selected }"
:type="item.selected ? 'primary' : 'default'">
<j-ellipsis style="max-width: 100%;"><span>{{ item.name }}</span>
<AIcon type="CloseOutlined" class="closeIcon" v-if="item.closeIcon"
@click="group.splice(index, 1)"></AIcon>
</j-ellipsis>
</div>
<div v-if="item.show">
<j-button block @click="() => selectGroup(item)">{{ item.selected ? '取消选中' : '选中' }}</j-button>
<j-button block @click="() => showEditGroup(item, index)">编辑</j-button>
</div>
</div>
<j-button type="text" @click="showAddGroup" :disabled="group.length >= 10">+ 自定义分组</j-button>
</div>
</div>
<j-checkbox-group @change="getCheckValue">
<div class="init-home-role-content">
<div
class="role-item role-item-1"
:style="
keys.includes('device')
<div class="role-item role-item-1" :style="keys.includes('device')
? 'background-color: #f5f5f5;'
: ''
"
>
">
<div class="role-item-title">
<j-checkbox :value="ROLEKEYS.device"></j-checkbox>
<div class="role-title">设备接入岗</div>
@ -19,14 +35,10 @@
该角色负责设备接入模块的维护管理
</div>
</div>
<div
class="role-item role-item-2"
:style="
keys.includes('link')
<div class="role-item role-item-2" :style="keys.includes('link')
? 'background-color: #f5f5f5;'
: ''
"
>
">
<div class="role-item-title">
<j-checkbox :value="ROLEKEYS.link"></j-checkbox>
<div class="role-title">运维管理岗</div>
@ -36,14 +48,10 @@
该角色负责系统运维模块的维护管理
</div>
</div>
<div
class="role-item role-item-3"
:style="
keys.includes('complex')
<div class="role-item role-item-3" :style="keys.includes('complex')
? 'background-color: #f5f5f5;'
: ''
"
>
">
<div class="role-item-title">
<j-checkbox :value="ROLEKEYS.complex"></j-checkbox>
<div class="role-title">综合管理岗</div>
@ -56,11 +64,28 @@
</div>
</j-checkbox-group>
</div>
<j-modal :visible="showAdd" title="自定义分组" @cancel="showAdd = false" @ok="addGroup">
<j-form layout="vertical" ref="formRef" :model="formData">
<j-form-item name="name" label="名称" :rules="[
{
required: true,
message: '请输入名称',
},
{
max: 64,
message: '最多可输入64个字符',
},
]">
<j-input v-model:value="formData.name"></j-input>
</j-form-item>
</j-form>
</j-modal>
</template>
<script lang="ts" setup>
import RoleMenuData, { ROLEKEYS, RoleData } from '../data/RoleData';
import { updateRoleMenu, addRole, getRoleMenu } from '@/api/initHome';
import { updateRoleMenu, addRole, getRoleMenu, addRoleGroup } from '@/api/initHome';
import { randomString } from '@/utils/utils';
/**
* 角色勾选数据
*/
@ -71,6 +96,34 @@ const keys = ref([]);
const getCheckValue = (val: any) => {
keys.value = val;
};
/**
* 获取分组数据
*/
const group = ref<any[]>([{
name: '默认',
show: false,
selected: true,
id: 'default_group'
}, {
name: '岗位',
show: false,
selected: false,
id: randomString()
}, {
name: '职位',
show: false,
selected: false,
id: randomString()
}])
const showAdd = ref(false)
const groupStatue = ref('add')
const selectedGroup = ref()
const formData = ref({
name: ''
})
const formRef = ref()
/**
* 根据菜单找角色
*/
@ -156,26 +209,134 @@ const addRoleData = async () => {
});
};
const showButton = (item: any) => {
if (item.name != '默认') {
item.show = true
}
}
const hiddenButton = (item: any) => {
item.show = false
}
const selectGroup = (item: any) => {
item.selected = !item.selected
}
/**
* 新增分组
*/
const showAddGroup = () => {
formData.value.name = ''
showAdd.value = true
groupStatue.value = 'add'
}
/**
* 分组编辑
*/
const showEditGroup = (item: any, index: number) => {
formData.value.name = item.name
showAdd.value = true
groupStatue.value = 'edit'
selectedGroup.value = index
}
const addGroup = () => {
formRef.value?.validate().then(() => {
groupStatue.value === 'add' ? group.value.push({
name: formData.value.name,
selected: true,
show: false,
closeIcon: true,
id: randomString()
}) : group.value[selectedGroup.value].name = formData.value.name
showAdd.value = false
})
}
/**
* 保存角色分组
*/
const saveGroup = async () => {
const roleGroup = group.value.filter((item: any) => {
return item.selected
})
return new Promise((resolve) => {
const allPromise = roleGroup.map(async (item) => {
return await addRoleGroup({ id: item.id, name: item.name });
});
Promise.all(allPromise).then((item) => {
resolve(
item.every((i) => {
return i;
}),
);
});
})
}
defineExpose({
submitRole: addRoleData,
submitRoleGroup: saveGroup
});
</script>
<style lang="less" scoped>
.init-home-role {
.built_in_group {
margin-bottom: 10px;
position: relative;
.group {
display: flex;
position: absolute;
z-index: 999;
.group_item {
width: 100px;
margin-right: 20px;
.button {
display: block;
}
.group_name {
border: .2px solid rgb(217, 217, 217);
text-align: center;
height: 32px;
line-height: 32px;
border-radius: 12%;
padding: 0 10px;
position: relative;
.closeIcon {
font-size: 12px;
position: absolute;
top: 4px;
right: 4px;
}
}
.group_selected {
background-color: rgb(190, 232, 251);
}
}
}
}
.init-home-role-content {
display: flex;
grid-gap: 24px;
gap: 24px;
margin-top: 40px;
}
.role-item-1 {
background-image: url(/images/init-home/role1.png);
}
.role-item-2 {
background-image: url(/images/init-home/role2.png);
}
.role-item-3 {
background-image: url(/images/init-home/role3.png);
}
.role-item {
position: relative;
display: flex;
@ -187,8 +348,10 @@ defineExpose({
background-position: 50%;
background-size: 370px;
border: 1px solid #f5f5f5;
.role-item-title {
display: flex;
.role-title {
flex: 1 1 auto;
font-weight: 700;
@ -196,11 +359,13 @@ defineExpose({
text-align: center;
}
}
.role-item-content {
width: 250px;
height: 260px;
margin-top: 24px;
}
.role-item-footer {
position: absolute;
bottom: -30px;
@ -211,5 +376,4 @@ defineExpose({
text-align: center;
}
}
}
</style>
}</style>

View File

@ -13,15 +13,18 @@ export const RoleData = {
[ROLEKEYS.device]: {
name: '设备接入岗',
description: '该角色负责设备接入模块的维护管理',
groupId:'default_group',
state: { text: '正常', value: 'enabled' },
},
[ROLEKEYS.link]: {
name: '运维管理岗',
groupId:'default_group',
description: '该角色负责系统运维模块的维护管理',
state: { text: '正常', value: 'enabled' },
},
[ROLEKEYS.complex]: {
name: '综合管理岗',
groupId:'default_group',
description: '该角色负责系统运维和设备接入模块的维护管理',
state: { text: '正常', value: 'enabled' },
},

View File

@ -13,6 +13,9 @@ export const USER_CENTER_MENU_DATA = {
sortIndex: 9999,
granted: true,
owner: 'iot',
options: {
isShow: false,
},
permissions: [
{
permission: 'system_config',
@ -3445,6 +3448,30 @@ export default [
permission: 'role',
actions: ['query'],
},
{
permission: 'role-group',
actions: ['query']
}
],
},
{
id: 'groupUpdate',
name: '角色组编辑',
permissions: [
{
permission: 'role-group',
actions: ['query','save']
}
],
},
{
id: 'groupDelete',
name: '角色组删除',
permissions: [
{
permission: 'role-group',
actions: ['query','delete']
}
],
},
],
@ -4013,6 +4040,144 @@ export default [
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
{
code: 'system/Dictionary',
name: '数据字典',
owner: 'iot',
id: 'b69782873cc24be8165c6ad292359092',
sortIndex: 12,
url: '/system/Dictionary',
icon: 'FormOutlined',
showPage: ['dictionary'],
permissions: [],
buttons: [
{
id: "add",
name: "新增",
permissions: [
{
permission: "dictionary",
actions: [
"save"
]
}
]
},
{
id: "delete",
name: "删除",
permissions: [
{
permission: "dictionary",
actions: [
"delete"
]
}
]
},
{
id: "update",
name: "编辑",
permissions: [
{
permission: "dictionary",
actions: [
"save"
]
}
]
},
{
id: "import",
name: "导入",
permissions: [
{
permission: "dictionary",
actions: [
"save"
]
}
]
},
{
id: "down",
name: "下载",
permissions: [
{
permission: "dictionary",
actions: [
"query"
]
}
]
},
{
id: "action",
name: "启用/禁用",
permissions: [
{
permission: "dictionary",
actions: [
"save"
]
}
]
}
],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
{
code: 'system/Region',
name: '地区管理',
owner: 'iot',
id: 'b69782873cc24be8165c6ad29233333',
sortIndex: 12,
url: '/system/region',
icon: 'FormOutlined',
showPage: ['area'],
permissions: [],
buttons: [
{
id: "add",
name: "新增",
permissions: [
{
permission: "area",
actions: [
"save"
]
}
]
},
{
id: "delete",
name: "删除",
permissions: [
{
permission: "area",
actions: [
"delete"
]
}
]
},
{
id: "update",
name: "编辑",
permissions: [
{
permission: "area",
actions: [
"save"
]
}
]
},
],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
// {
// code: 'system/License',
// name: 'License管理',
@ -4426,5 +4591,635 @@ export default [
supportDataAccess: false
},
],
},
{
"path": "3n23",
"sortIndex": 5,
"level": 1,
"owner": "iot",
"name": "WEB IDE",
"code": "web_ide",
"url": "/web_ide",
"icon": "MenuUnfoldOutlined",
showPage: ["low-code-info", "low-code-editor"],
"options": {
isShow: false,
},
"accessSupport": {
"text": "不支持",
"value": "unsupported"
},
"indirectMenus": [],
permissions: [
{
"permission": "low-code-info",
"actions": [
"add", "query", "save", "delete"
]
},
{
"permission": "low-code-editor",
"actions": [
"query", "deploy"
]
}
],
"creatorId": "1199596756811550720",
"createTime": 1698735482730,
"supportDataAccess": false
},
{
"path": "3nkE",
"sortIndex": 6,
"level": 1,
"owner": "iot",
"name": "工作流",
"code": "process",
"url": "/workflow",
"icon": "MenuUnfoldOutlined",
showPage: ["process-form","process-deployment", "process-runtime"],
"options": {
"show": true,
"isShow": false
},
"permissions": [
{
"permission": "low-code-info",
"actions": [
"query"
]
},
{
"permission": "process-runtime",
"actions": [
"reject",
"start",
"self",
"claim",
"complete",
"createAndStart",
"repeal"
]
}
],
"accessSupport": {
"text": "不支持",
"value": "unsupported"
},
"indirectMenus": [],
"children": [
// {
// "path": "3nkE-n7lJ",
// "sortIndex": 1,
// "level": 2,
// "name": "工作流",
// "code": "process/home",
// "describe": "",
// "url": "/workflow/home",
// "icon": "UpCircleOutlined",
// showPage: ["process-form"],
// "permissions": [
// {
// "permission": "process-form",
// "actions": [
// "query"
// ]
// },
// {
// "permission": "low-code-info",
// "actions": [
// "query"
// ]
// },
// ],
// "accessSupport": {
// "text": "不支持",
// "value": "unsupported"
// },
// "indirectMenus": [],
// "buttons": [],
// "options": {
// "show": true
// },
// "creatorId": "1199596756811550720",
// "createTime": 1699263818795,
// "supportDataAccess": false
// },
{
"path": "3nkE-n7lJ",
"sortIndex": 2,
"level": 2,
"name": "表单",
"code": "process/form",
"describe": "",
"url": "/workflow/form",
"icon": "UpCircleOutlined",
showPage: ["process-form"],
"permissions": [
{
"permission": "process-form",
"actions": [
"query"
]
}
],
"accessSupport": {
"text": "不支持",
"value": "unsupported"
},
"indirectMenus": [],
"buttons": [
{
"id": "add",
"name": "新增",
"permissions": [
{
"permission": "process-form",
"actions": [
"save"
]
}
]
},
{
"id": "save",
"name": "保存",
"permissions": [
{
"permission": "process-form",
"actions": [
"save"
]
}
]
},
{
"id": "update",
"name": "编辑",
"permissions": [
{
"permission": "process-form",
"actions": [
"save"
]
}
]
},
{
"id": "delete",
"name": "删除",
"permissions": [
{
"permission": "process-form",
"actions": [
"delete"
]
}
]
}
],
"options": {
"show": true
},
"creatorId": "1199596756811550720",
"createTime": 1699263818795,
"supportDataAccess": false
},
{
"path": "3nkE-ysKb",
"sortIndex": 3,
"level": 2,
"name": "流程模型",
"code": "process/model",
"describe": "",
"url": "/workflow/model",
"icon": "UpCircleOutlined",
showPage: ["process-definition"],
"permissions": [
{
"permission": "process-definition",
"actions": [
"query"
]
}
],
"accessSupport": {
"text": "不支持",
"value": "unsupported"
},
"indirectMenus": [],
"buttons": [
{
"id": "add",
"name": "新增",
"permissions": [
{
"permission": "process-definition",
"actions": [
"save"
]
}
]
},
{
"id": "delete",
"name": "删除",
"permissions": [
{
"permission": "process-definition",
"actions": [
"delete"
]
}
]
},
{
"id": "update",
"name": "编辑",
"permissions": [
{
"permission": "process-definition",
"actions": [
"save"
]
}
]
},
{
"id": "save",
"name": "保存",
"permissions": [
{
"permission": "process-definition",
"actions": [
"save"
]
}
]
},
{
"id": "deploy",
"name": "部署",
"permissions": [
{
"permission": "process-definition",
"actions": [
"save"
]
}
]
}
],
"options": {
"show": true
},
"creatorId": "1199596756811550720",
"createTime": 1699264341009,
"supportDataAccess": false
},
{
"path": "3nkE-fJkR",
"sortIndex": 4,
"level": 2,
"name": "流程实例",
"code": "process/instance",
"describe": "",
showPage: ["process-deployment"],
"url": "/workflow/instance",
"icon": "UpCircleOutlined",
"status": 1,
"permissions": [
{
"permission": "process-deployment",
"actions": [
"query"
]
}
],
"accessSupport": {
"text": "不支持",
"value": "unsupported"
},
"indirectMenus": [],
"buttons": [
{
"id": "action",
"name": "启/禁用",
"permissions": [
{
"permission": "process-deployment",
"actions": [
"save"
]
}
]
},
{
"id": "auth",
"name": "权限控制",
"permissions": [
{
"permission": "process-deployment",
"actions": [
"save"
]
}
]
},
{
"id": "delete",
"name": "删除",
"permissions": [
{
"permission": "process-deployment",
"actions": [
"delete"
]
}
]
}
],
"options": {
"show": true
},
"creatorId": "1199596756811550720",
"createTime": 1699264485954,
"supportDataAccess": false
},
{
"path": "3nkE-i2nG",
"sortIndex": 5,
"level": 2,
"name": "发起流程",
"code": "process/initiate",
"describe": "",
"url": "/workflow/initiate",
"icon": "UpOutlined",
showPage: ["process-runtime"],
"permissions": [
{
"permission": "process-definition",
"actions": [
"query"
]
},
{
"permission": "process-deployment",
"actions": [
"query"
]
},
{
"permission": "process-form",
"actions": [
"query"
]
},
{
"permission": "process-runtime",
"actions": [
"reject",
"start",
"self",
"claim",
"complete",
"createAndStart",
"repeal"
]
}
],
"accessSupport": {
"text": "不支持",
"value": "unsupported"
},
"indirectMenus": [],
"options": {
"show": true
},
"creatorId": "1199596756811550720",
"createTime": 1699269393605,
"supportDataAccess": false
},
{
"path": "3nkE-4T1D",
"sortIndex": 6,
"level": 2,
"name": "待办事项",
"code": "process/me/todo",
"describe": "",
"url": "/workflow/me/todo",
"icon": "UpCircleOutlined",
showPage: ["process-runtime"],
"permissions": [
{
"permission": "process-definition",
"actions": [
"query"
]
},
{
"permission": "process-deployment",
"actions": [
"query"
]
},
{
"permission": "process-form",
"actions": [
"query"
]
},
{
"permission": "process-runtime",
"actions": [
"reject",
"start",
"self",
"claim",
"complete",
"createAndStart",
"repeal"
]
}
],
"accessSupport": {
"text": "不支持",
"value": "unsupported"
},
"indirectMenus": [],
"options": {
"show": true
},
"creatorId": "1199596756811550720",
"createTime": 1699269425041,
"supportDataAccess": false
},
{
"path": "3nkE-RXP3",
"sortIndex": 7,
"level": 2,
"name": "已办事项",
"code": "process/me/finished",
"describe": "",
"url": "/workflow/me/finished",
"icon": "UpCircleOutlined",
showPage: ["process-runtime"],
"permissions": [
{
"permission": "process-definition",
"actions": [
"query"
]
},
{
"permission": "process-deployment",
"actions": [
"query"
]
},
{
"permission": "process-form",
"actions": [
"query"
]
},
{
"permission": "process-runtime",
"actions": [
"reject",
"start",
"self",
"claim",
"complete",
"createAndStart",
"repeal"
]
}
],
"accessSupport": {
"text": "不支持",
"value": "unsupported"
},
"indirectMenus": [],
"options": {
"show": true
},
"creatorId": "1199596756811550720",
"createTime": 1699269455412,
"supportDataAccess": false
},
{
"path": "3nkE-ibau",
"sortIndex": 8,
"level": 2,
"name": "我发起的",
"code": "process/me/initiate",
"describe": "",
"url": "/workflow/me/initiate",
"icon": "UpCircleOutlined",
showPage: ["process-runtime"],
"permissions": [
{
"permission": "process-definition",
"actions": [
"query"
]
},
{
"permission": "process-deployment",
"actions": [
"query"
]
},
{
"permission": "process-form",
"actions": [
"query"
]
},
{
"permission": "process-runtime",
"actions": [
"reject",
"start",
"self",
"claim",
"complete",
"createAndStart",
"repeal"
]
}
],
"accessSupport": {
"text": "不支持",
"value": "unsupported"
},
"indirectMenus": [],
"options": {
"show": true
},
"creatorId": "1199596756811550720",
"createTime": 1699269488928,
"supportDataAccess": false
},
{
"path": "3nkE-vCQb",
"sortIndex": 10,
"level": 2,
"name": "抄送给我",
"code": "process/me/cc",
"describe": "",
"url": "/workflow/me/cc",
"icon": "BorderInnerOutlined",
"status": 1,
showPage: ["process-runtime"],
"permissions": [
{
"permission": "process-definition",
"actions": [
"query"
]
},
{
"permission": "process-runtime",
"actions": [
"reject",
"start",
"self",
"claim",
"complete",
"createAndStart",
"repeal"
]
},
{
"permission": "process-deployment",
"actions": [
"query"
]
},
{
"permission": "process-form",
"actions": [
"query"
]
}
],
"accessSupport": {
"text": "不支持",
"value": "unsupported"
},
"indirectMenus": [],
"options": {
"show": true
},
"creatorId": "1199596756811550720",
"createTime": 1699348851779,
"supportDataAccess": false
}
],
"creatorId": "8004214f8141f29cb951befdca3615f0",
"createTime": 1699262480040,
"supportDataAccess": false
}
];

View File

@ -112,6 +112,11 @@ const submitData = async () => {
loading.value = false;
return;
}
const roleGroupRes = await roleRef.value.submitRoleGroup();
if (!roleGroupRes) {
loading.value = false;
return;
}
const initDataRes = await initDataRef.value.save();
if (!initDataRes) {
loading.value = false;
@ -119,7 +124,7 @@ const submitData = async () => {
}
loading.value = false;
//
if (basicRes && menuRes && roleRes && initDataRes) {
if (basicRes && menuRes && roleRes && roleGroupRes && initDataRes) {
onlyMessage('保存成功');
// //
const res = await saveInit();

View File

@ -128,6 +128,15 @@ function getMenus(id: string) {
column: 'appId',
value: id,
},
{
terms:[
{
value:"%show\":true%",
termType:"like",
column:"options"
}
]
}
],
};
getMenuTree_api(params).then((resp: any) => {

View File

@ -332,7 +332,6 @@ import { save_api } from '@/api/system/basis';
import { usePermissionStore } from '@/store/permission';
import { useSystem } from '@/store/system';
import { settingDetail } from '@/api/login';
const action = `${BASE_API_PATH}/file/static`;
const headers = { [TOKEN_KEY]: LocalStore.get(TOKEN_KEY) };
const formRef = ref();

View File

@ -0,0 +1,98 @@
<template>
<j-modal visible title="下载" @cancel="close" @ok="downLoad" :maskClosable="false"
:confirmLoading="loading" :width="900">
<JProTable
:columns="columns"
model="CARD"
:request="getDic_page"
:gridColumn="2"
:defaultParams="{sorts: [{ name: 'createTime', order: 'desc' }]}"
>
<template #headerTitle>
请选择需要下载的字典
</template>
<template #card="slotProps">
<CardBox :value="slotProps" :showStatus="false" :active="_selectedRowKeys.includes(slotProps.id)" @click="onSelectChange">
<template #content>
<j-row>
<j-col :span="12">
<Ellipsis style="width: 100%">
<div>字典名称{{ slotProps.name }}</div>
</Ellipsis>
</j-col>
<j-col :span="12">
<Ellipsis style="width: 100%">
字典ID{{ slotProps.id }}
</Ellipsis>
</j-col>
</j-row>
</template>
</CardBox>
</template>
</JProTable>
</j-modal>
</template>
<script lang="ts" setup>
import { getDic_page , downDic} from '@/api/system/dictionary';
import { onlyMessage } from '@/utils/comm';
import { downloadFileByUrl } from '@/utils/utils';
import dayjs from 'dayjs';
const emit = defineEmits(['closeDown'])
const loading = ref(false)
const columns = [ {
title:'name',
dataIndex:'name',
key:'name',
ellipsis: true,
},
{
title: 'name',
dataIndex: 'name',
key: 'name',
ellipsis: true,
}]
const _selectedRowKeys:any = ref([])
const onSelectChange = (dt: any) => {
if (_selectedRowKeys.value.includes(dt.id)) {
const _index = _selectedRowKeys.value.findIndex((i:any) => i === dt.id);
_selectedRowKeys.value.splice(_index, 1);
} else {
_selectedRowKeys.value = [..._selectedRowKeys.value, dt.id];
}
};
const close = () =>{
emit('closeDown')
}
const downLoad = async() =>{
if(_selectedRowKeys.value.length){
const res:any = await downDic({
terms:[
{
terms:[
{
value:_selectedRowKeys.value,
termType:'in',
column:'id'
}
]
}
]
})
if(res.status === 200 && res.result){
const json = JSON.stringify(res.result)
if (json) {
const blob = new Blob([json], { type: 'application/json' });
const url = URL.createObjectURL(blob);
downloadFileByUrl(url, `数据字典${dayjs().format('YYYY-MM-DD HH:mm:ss')}`, 'json');
emit('closeDown');
}
emit('closeDown')
}
}else{
onlyMessage('至少选择一条数据!')
}
}
</script>
<style lang="less" scoped>
</style>

View File

@ -0,0 +1,218 @@
<template>
<div class="left-contain">
<j-input placeholder="字典名称" v-model:value="searchValue" @pressEnter="search" @change="searchChange">
<template #suffix>
<AIcon type="SearchOutlined" @click="search" />
</template>
</j-input>
<div class="controls">
<PermissionButton type="primary" hasPermission="system/Dictionary:add" @click="showSave" style="width: 160px">
新增字典
</PermissionButton>
<PermissionButton type="text" hasPermission="system/Dictionary:down" @click="downVisible = true">
下载
</PermissionButton>
<j-upload :before-upload="beforeUpload" accept=".json" :show-upload-list="false"
:disabled="!hasPermission('system/Dictionary:import')">
<PermissionButton type="text" hasPermission="system/Dictionary:import">
导入
</PermissionButton>
</j-upload>
</div>
<div class="tree">
<j-tree :tree-data="listData" v-if="listData.length" :fieldNames="{ title: 'name', key: 'id' }" blockNode
:selectedKeys="selectedKeys">
<template #title="item">
<div class="treeItem" @click="() => selectDic(item.data)">
<div class="itemText">
<Ellipsis style="width: calc(100%-100px)">{{ item.name }}</Ellipsis>
</div>
<div @click="(e) => e.stopPropagation()">
<j-popconfirm v-if="hasPermission('system/Dictionary:action')"
:title="item.data.status === 1 ? '确定禁用?' : '确定启用?'" @confirm="() => updateDic(item.data)">
<j-switch :checked="item.status" :disabled="!hasPermission('system/Dictionary:action')"
:checkedValue="1" :unCheckedValue="0"></j-switch>
</j-popconfirm>
<j-tooltip v-else placement="top" title="暂无权限,请联系管理员">
<j-switch :checked="item.status" :disabled="!hasPermission('system/Dictionary:action')"
:checkedValue="1" :unCheckedValue="0"></j-switch>
</j-tooltip>
<PermissionButton type="text" hasPermission="system/Dictionary:delete" :popConfirm="{
title: `确定要删除?`,
onConfirm: () => deleteDic(item.id),
}">
删除
</PermissionButton>
<PermissionButton type="text" hasPermission="system/Dictionary:update"
@click="showEdit(item.data)">
编辑
</PermissionButton>
</div>
</div>
</template>
</j-tree>
<j-empty v-else style="margin-top: 100px;" />
</div>
</div>
<Save v-if="saveShow" :type="addType" @close-save="saveShow = false" @success="saveSuccess" :data="editData" />
<Export v-if="downVisible" @closeDown="closeDown" />
<Import />
</template>
<script lang="ts" setup>
import { getDicList, deleteDictionary, addDictionary } from '@/api/system/dictionary';
import Save from './save/index.vue'
import { onlyMessage } from '@/utils/comm';
import Export from './Export/index.vue'
import { usePermissionStore } from '@/store/permission';
const emit = defineEmits(['selectData'])
const hasPermission = usePermissionStore().hasPermission;
const saveShow = ref(false)
const addType = ref('add')
const listData = ref<any[]>([])
const editData = ref()
const selectedKeys: any = ref([])
const showSave = () => {
saveShow.value = true
addType.value = 'add'
}
const downVisible = ref(false)
const searchValue = ref()
const queryData = (first?: Boolean, searchName?: any) => {
const params = searchName ? { paging:false ,sorts: [{ name: 'createTime', order: 'desc' }, { name: 'name', order: 'desc' }], terms: [{ terms: [{ value: '%' + searchName + '%', termType: 'like', column: 'name' }] }] } : { sorts: [{ name: 'createTime', order: 'desc' }, { name: 'name', order: 'desc' }], paging:false }
getDicList(params).then((res: any) => {
if (res.status === 200) {
listData.value = res.result
if (first && res.result.length) {
selectDic(res.result[0])
}
}
})
}
const search = () => {
queryData(true, searchValue.value)
}
const searchChange = () => {
console.log(searchValue.value === '')
if (searchValue.value === '') {
queryData(true)
}
}
const showEdit = (data: any) => {
saveShow.value = true
addType.value = 'edit'
editData.value = data
}
/**
* 重新请求数据
*/
const reload = () => {
queryData()
}
const saveSuccess = () => {
saveShow.value = false
reload()
}
/**
*
* @param id 字典id
* 删除字典
*/
const deleteDic = (id: string) => {
deleteDictionary(id).then((res: any) => {
if (res.status === 200) {
onlyMessage('操作成功!')
queryData(true)
} else {
onlyMessage('操作失败!', 'error')
}
})
}
/**
* 更新字典
*/
const updateDic = (data: any) => {
data.status = data.status === 1 ? 0 : 1
addDictionary(data).then((res: any) => {
if (res.status === 200) {
onlyMessage('操作成功!')
reload()
} else {
onlyMessage('操作失败!', 'error')
}
})
}
/**
* 切换选中字典
*/
const selectDic = (selectKeys: any) => {
selectedKeys.value = [selectKeys.id]
emit('selectData', selectKeys)
}
/**
* 导入字典
*/
const beforeUpload = (file: any) => {
if (file.type === 'application/json') {
const reader = new FileReader();
reader.readAsText(file);
reader.onload = async (json: any) => {
if (json?.target?.result) {
const text = json.target.result
let data
try {
data = JSON.parse(text);
} catch {
onlyMessage('请上传json格式的文件', 'error')
return false
}
const res = await addDictionary(data)
if (res.status === 200) {
reload()
onlyMessage('操作成功!')
}
} else {
onlyMessage('文件内容不能为空', 'error')
}
};
} else {
onlyMessage('请上传json格式的文件', 'error')
}
};
const closeDown = () => {
downVisible.value = false
}
onMounted(() => {
queryData(true)
})
</script>
<style lang="less" scoped>
.left-contain {
width: 300px;
height: 100%;
}
:deep(.ant-tree-switcher) {
display: none;
}
.tree {
height: calc(100% - 110px);
overflow-y: auto;
}
.controls {
margin: 10px 0;
}
.treeItem {
display: flex;
justify-content: space-between;
.itemText {
line-height: 32px;
max-width: 40%;
}
}
</style>

View File

@ -0,0 +1,125 @@
<template>
<j-modal
:title="type==='add'?'新增字典':'编辑字典'"
visible
@cancel="closeModal"
@ok="submitData"
width="650px"
:maskClosable="false"
:confirmLoading="loading"
>
<j-form layout="vertical" :rules="rules" ref="formRef" :model="form">
<j-form-item label="字典ID" name="id">
<j-input v-model:value="form.id" :disabled="type ==='edit'"></j-input>
</j-form-item>
<j-form-item label="字典名称" name="name">
<j-input v-model:value="form.name"></j-input>
</j-form-item>
<j-form-item label="状态" name="status">
<j-radio-group v-model:value="form.status">
<j-radio-button :value="1">启用</j-radio-button>
<j-radio-button :value="0">禁用</j-radio-button>
</j-radio-group>
</j-form-item>
<j-form-item label="说明" name="describe">
<j-textarea :rows="4" :maxlength="200" v-model:value="form.describe"></j-textarea>
</j-form-item>
</j-form>
</j-modal>
</template>
<script lang="ts" setup>
import { isInput } from '@/utils/regular';
import type { Rule } from 'ant-design-vue/es/form';
import { verifyId,addDictionary } from '@/api/system/dictionary'
import { onlyMessage } from '@/utils/comm';
const props = defineProps({
type:{
type:String,
default:'add'
},
data:{
type:Object,
default:{}
}
})
const emit = defineEmits(['closeSave','success'])
const loading = ref(false)
const formRef = ref()
const form = reactive({
id:'',
name:'',
describe:'',
status:1,
})
/**
* 校验id
*/
const validateInput = async (_rule: Rule, value: string) => {
if (value) {
if (!isInput(value)) {
return Promise.reject('请输入英文或者数字或者-或者_');
} else {
if (props.type === 'add') {
const res:any = await verifyId(value);
if (res.status === 200 && res.result) {
return Promise.reject('该字典ID已存在');
} else {
return Promise.resolve();
}
}
}
} else {
return Promise.resolve();
}
};
const rules = {
id: [
{ required:true,message:'请输入ID'},
{ validator: validateInput, trigger: 'blur' },
{ max: 64, message: '最多可输入64位字符', trigger: 'change' },
],
name: [
{ required: true, message: '请输入名称', trigger: 'blur' },
{ max: 64, message: '最多可输入64位字符', trigger: 'change' },
],
status: [
{
required: true,
message:'请选择状态',
trigger: 'blur',
},
],
description: [
{ max: 200, message: '最多可输入200位字符', trigger: 'blur' },
],
}
const submitData = () =>{
formRef.value.validate().then(async()=>{
loading.value = true
const res = await addDictionary(form)
if(res.status === 200){
onlyMessage('保存成功!')
emit('success')
}else{
onlyMessage('操作失败!','error')
}
loading.value = false
})
}
const closeModal = ()=>{
emit('closeSave')
}
onMounted(()=>{
if(props.type==='edit' && props.data){
form.describe = props.data.describe
form.id = props.data.id
form.name = props.data.name
form.status = props.data.status
}
})
</script>
<style lang="less" scoped>
</style>

View File

@ -0,0 +1,143 @@
<template>
<j-modal visible :title="type === 'add' ? '新增' : '编辑'" @cancel="close" @ok="submitData" :maskClosable="false"
:confirmLoading="loading">
<j-form :model="form" layout="vertical" :rules="rules" ref="formRef">
<j-form-item label="name" name="name">
<j-input placeholder="con_type" v-model:value="form.name"></j-input>
</j-form-item>
<j-form-item label="value" name="value">
<j-input placeholder="con_type" v-model:value="form.value"></j-input>
</j-form-item>
<j-form-item label="text" name="text">
<j-input placeholder="连接失败" v-model:value="form.text"></j-input>
</j-form-item>
</j-form>
</j-modal>
</template>
<script lang="ts" setup>
import { isInput } from '@/utils/regular';
import type { Rule } from 'ant-design-vue/es/form';
import { saveDicItem, verifyValue } from '@/api/system/dictionary';
import { onlyMessage } from '@/utils/comm';
import { validateValueType } from '@/views/device/components/Metadata/Base/Edit/validator';
import { cloneDeep } from 'lodash-es';
const props = defineProps({
type: {
type: String,
default: 'add'
},
dicId: {
type: String,
default: ''
},
sort: {
type: Number,
default: 1
},
data: {
type: Object,
default: {}
}
})
const emit = defineEmits(['closeModal', 'refresh'])
const form: any = ref({
dictId: '',
name: '',
value: '',
text: '',
ordinal: 0,
searchCode:''
})
const lastValue = ref()
const loading = ref(false)
const formRef = ref()
/*
* 校验name
*/
const validateInput = async (_rule: Rule, value: string) => {
if (value) {
if (!isInput(value)) {
return Promise.reject('请输入英文或者数字或者-或者_');
}
} else {
return Promise.resolve();
}
};
/**
* 校验value唯一
*/
const validateValue = async (_rule: Rule, value: string) => {
if (value && lastValue.value !== value) {
const res:any = await verifyValue({
terms: [
{
terms: [
{
value: value,
termType: "eq",
column: "value"
},
{
value: form.value.dictId,
termType: "eq",
column: "dictId"
}
]
}
]
})
if (res.status === 200 && res.result) {
return Promise.reject('value重复');
} else {
return Promise.resolve();
}
} else {
return Promise.resolve();
}
}
const rules = {
name: [
{ required: true, message: '请输入name' },
{ validator: validateInput, trigger: 'change' },
{ max: 64, message: '最多可输入64位字符', trigger: 'change' },
],
value: [
{ required: true, message: '请输入value', trigger: 'blur' },
{ max: 64, message: '最多可输入64位字符', trigger: 'change' },
{ validator: validateValue, trigger: 'blur' }
],
text: [
{ required: true, message: '请输入text', trigger: 'blur' },
{ max: 64, message: '最多可输入64位字符', trigger: 'change' },
]
}
const submitData = () => {
formRef.value.validate().then(async () => {
loading.value = true
form.value.searchCode = form.value.name + ':' + form.value.value + ':' + form.value.text
const res = await saveDicItem(form.value)
if (res.status === 200) {
onlyMessage('操作成功!')
emit('refresh')
} else {
onlyMessage('操作失败!', 'error')
}
loading.value = false
})
}
const close = () => {
emit('closeModal')
}
onMounted(() => {
if (props.type === 'add') {
form.value.dictId = props.dicId
form.value.ordinal = props.sort
} else {
form.value = cloneDeep(props.data)
lastValue.value = props.data.value
}
})
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,225 @@
<template>
<div class="des">
<div class="des_head">
<div>字典ID<span>{{ data.id }}</span></div>
<div>说明<span>{{ data.describe }}</span></div>
<div>创建日期<span> {{
dayjs(
data?.createTime,
).format(
'YYYY-MM-DD HH:mm:ss',
)
}}</span></div>
</div>
<div class="contain">
<pro-search :columns="columns" @search="handleSearch" target="system_dictionary" />
<JProTable :columns="columns" model="TABLE" :request="queryItem" :params="params" ref="tableRef">
<template #headerTitle>
<PermissionButton type="primary" @click="add" hasPermission="system/Dictionary:add">
新增
</PermissionButton>
</template>
<template #action="slotProps">
<j-space>
<template v-for="i in getActions(slotProps, 'table')" :key="i.key">
<PermissionButton :disabled="i.disabled" :popConfirm="i.popConfirm" :tooltip="{
...i.tooltip,
}" @click="i.onClick" type="link" style="padding: 0 5px" :danger="i.key === 'delete'"
:hasPermission="'system/Dictionary:' + i.key
">
<template #icon>
<AIcon :type="i.icon" />
</template>
</PermissionButton>
</template>
</j-space>
</template>
</JProTable>
</div>
</div>
<Save v-if="saveVisible" :dicId='data.id' :type="modalType" :data="current" :sort=sort @closeModal="closeModal"
@refresh="refresh" />
</template>
<script lang="ts" setup>
import { queryDicItem, deleteDicItem } from '@/api/system/dictionary'
import Save from './Save/index.vue'
import type { ActionsType } from './typings';
import { onlyMessage } from '@/utils/comm';
import { cloneDeep } from 'lodash-es';
import dayjs from 'dayjs';
const props = defineProps({
data: {
type: Object,
default: {}
},
})
const params = ref()
const tableRef = ref()
const saveVisible = ref(false)
const sort = ref(0)
const modalType = ref('add')
const current = ref()
const columns = [
{
title: '序号',
dataIndex: 'ordinal',
key: 'ordinal',
},
{
title: '检索码',
dataIndex: 'searchCode',
hideInTable: true,
search: {
type: 'string'
}
},
{
title: 'name',
dataIndex: 'name',
key: 'name',
ellipsis: true,
search: {
type: 'string'
}
},
{
title: 'value',
dataIndex: 'value',
key: 'value',
ellipsis: true,
search: {
type: 'string'
}
},
{
title: 'text',
dataIndex: 'text',
key: 'text',
ellipsis: true,
search: {
type: 'string'
}
}, {
title: '操作',
dataIndex: 'action',
key: 'action',
scopedSlots: true,
}
];
const getActions = (
data: Partial<Record<string, any>>,
type: 'card' | 'table',
): ActionsType[] => {
if (!data) return [];
const actions = [
{
key: 'update',
text: '编辑',
tooltip: {
title: '编辑',
},
icon: 'EditOutlined',
onClick: () => {
saveVisible.value = true;
modalType.value = 'edit';
current.value = data
},
},
{
key: 'delete',
text: '删除',
tooltip: {
title: '删除',
},
popConfirm: {
title: '确认删除?',
onConfirm: async () => {
const res = await deleteDicItem(data.id)
if (res.status === 200) {
onlyMessage('操作成功!')
tableRef.value.reload()
} else {
onlyMessage('操作失败!', 'error')
}
},
},
icon: 'DeleteOutlined',
},
];
if (type === 'card')
return actions.filter((i: ActionsType) => i.key !== 'view');
return actions;
};
const add = () => {
modalType.value = 'add'
current.value = {}
saveVisible.value = true
}
const closeModal = () => {
saveVisible.value = false
}
const handleSearch = (i: any) => {
params.value = i
}
const refresh = () => {
saveVisible.value = false
tableRef.value.reload()
}
const queryItem = async (_params: any) => {
if (props.data?.id) {
const params = {
..._params,
sorts: [{ name: 'ordinal', order: 'asc' }],
terms: [
..._params.terms,
{
column: 'dictId',
termType: 'eq',
value: props.data?.id
},
],
};
const resp: any = await queryDicItem(params);
if (resp.status === 200) {
const arr = cloneDeep(resp.result.data)
arr?.sort((a: any, b: any) => {
return b.ordinal - a.ordinal
})
sort.value = arr.length ? arr[0].ordinal + 1 : 1
return {
code: resp.status,
result: resp.result,
status: resp.status,
};
}
} else {
sort.value = 1
return {
code: 200,
result: {
data: [],
pageIndex: 0,
pageSize: 0,
total: 0,
},
status: 200,
};
}
}
watch(() => props?.data?.id, () => {
tableRef.value.reload()
})
</script>
<style lang="less" scoped>
.des_head {
padding: 10px 20px;
background-color: rgb(242, 242, 242);
span {
color: rgb(127, 127, 127)
}
}
</style>

View File

@ -0,0 +1,45 @@
<template>
<page-container>
<FullPage>
<div class="dictionary_contain">
<div class="dictionary_left">
<Left @selectData="selectData" />
</div>
<div class="dictionary_right">
<Right :data="data" />
</div>
</div>
</FullPage>
</page-container>
</template>
<script lang="ts" setup>
import Left from './components/Left/index.vue'
import Right from './components/Right/index.vue'
const data = ref()
const selectData = (i: any) => {
data.value = i
}
</script>
<style lang="less" scoped>
.dictionary_contain {
background-color: #fff;
padding: 24px;
padding-bottom: 0;
position: relative;
height: 100%;
}
.dictionary_left {
position: absolute;
border-right: 1px solid #f0f0f0;
padding-right: 24px;
width: 310px;
height: 100%;
}
.dictionary_right {
margin-left: 317px;
width: calc(100% - 317px);
}
</style>

View File

@ -329,6 +329,7 @@ const form = reactive({
getMenuInfo_api(routeParams.id).then((resp: any) => {
form.data = {
...(resp.result as formType),
permissions: resp.result?.permissions ? resp.result.permissions : [],
accessSupport:
resp.result?.accessSupport?.value || 'unsupported',
};
@ -337,7 +338,15 @@ const form = reactive({
if (isNoCommunity) {
//
getMenuTree_api({ paging: false }).then((resp: any) => {
getMenuTree_api({ paging: false,terms:[{terms:[{
terms:[
{
value:"%show\":true%",
termType:"like",
column:"options"
}
]
}]}]}).then((resp: any) => {
form.treeData = resp.result;
});
//
@ -377,7 +386,7 @@ const form = reactive({
const params = {
...form.data,
owner: form.data?.owner ?? null,
options: { show: true },
options: form.data?.options || { show: true },
accessSupport: {
value: accessSupportValue,
label:

View File

@ -65,6 +65,7 @@ import {
import {
filterMenu,
initData,
inItSelected,
drop,
select,
getMaxDepth,
@ -128,31 +129,70 @@ const getProvidersFn = async () => {
}
}
getProvidersFn();
function filterTree(nodes: Array<any>, selectedKeys: Array<any>) {
/**
* 作用过滤掉非选中菜单重新组成新的数组
*/
// function filterTree(nodes: Array<any>, selectedKeys: Array<any>,parentId?:string) {
// const filtered = [];
// for (let i = 0; i < nodes.length; i++) {
// const node = nodes[i];
// if (!node.code) {
// continue;
// }
// node.parentId = parentId ? undefined : parentId
// if (selectedKeys.indexOf(node.code) !== -1) {
// filtered.push(node);
// if (node.children) {
// node.children = filterTree(node.children, selectedKeys,node.id);
// }
// } else if (node.children) {
// node.children = filterTree(node.children, selectedKeys,node.id);
// if (node.children.length > 0) {
// filtered.push(node);
// }
// }
// }
// return filtered;
// }
/**
*
* @param nodes 菜单数据
* @param selectedKeys 选中的菜单
* 选中和非选中改变show的值
*/
const dealTree = (nodes: Array<any>, selectedKeys: Array<any>,parentId?:string) =>{
const filtered = [];
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
if (!node.code) {
continue;
}
node.parentId = parentId ? undefined : parentId
node?.options ? node.options.show = false : node.options = { show : false }
if (selectedKeys.indexOf(node.code) !== -1) {
filtered.push(node);
node.options.show = true
if (node.children) {
node.children = filterTree(node.children, selectedKeys);
node.children = dealTree(node.children, selectedKeys,node.id);
}
} else if (node.children) {
node.children = filterTree(node.children, selectedKeys);
if (node.children.length > 0) {
filtered.push(node);
node.children = dealTree(node.children, selectedKeys,node.id);
const children =node.children.filter((item:any)=>{
item.options.show === true
})
if (children.length > 0) {
node.options.show = true
}
}else{
node.options.show = false
}
filtered.push(node)
}
return filtered;
}
const handleOk = async () => {
const _dataArr = filterTree(cloneDeep(treeData.value), selectedKeys.value);
// const _dataArr = filterTree(cloneDeep(treeData.value), selectedKeys.value);
const _dataArr = dealTree(cloneDeep(treeData.value),selectedKeys.value)
const _dataSorts = handleSorts(_dataArr)
loading.value = true;
const res = await updateMenus(_dataSorts).catch(() => {});
@ -203,13 +243,13 @@ const onDragend = (info: AntTreeNodeDropEvent) => {
onMounted(() => {
getSystemPermission_api().then((resp: any) => {
const filterBaseMenu = BaseMenu.filter(item => ![
USER_CENTER_MENU_CODE,messageSubscribe
].includes(item.code))
baseMenu.value = filterMenu(
resp.result.map((item: any) => JSON.parse(item).id),
filterBaseMenu,
);
// const filterBaseMenu = BaseMenu.filter(item => ![
// USER_CENTER_MENU_CODE,messageSubscribe
// ].includes(item.code))
// baseMenu.value = filterMenu(
// resp.result.map((item: any) => JSON.parse(item).id),
// filterBaseMenu,
// );
getMenuTree_api(params).then((resp: any) => {
if (resp.status == 200) {
systemMenu.value = resp.result?.filter(
@ -219,17 +259,16 @@ onMounted(() => {
].includes(item.code),
);
//
initData(baseMenu.value); // keyname
const systemMenuData = initData(systemMenu.value);
// initData(baseMenu.value); // keyname
const systemMenuData = inItSelected(systemMenu.value);
selectedKeys.value = systemMenuData.checkedKeys;
const AllMenu = filterMenus(mergeArr(
cloneDeep(baseMenu.value),
cloneDeep(systemMenu.value),
))
console.log(AllMenu);
// const AllMenu = filterMenus(mergeArr(
// cloneDeep(baseMenu.value),
// cloneDeep(systemMenu.value),
// ))
// console.log(AllMenu);
//
treeData.value = handleSortsArr(AllMenu);
treeData.value = handleSortsArr(systemMenu.value);
}
});
});

View File

@ -115,9 +115,7 @@ export const initData = (Menu: any) => {
arr.forEach((item: any) => {
item.title = item.code;
item.key = item.code; // treeData需要唯一key
checkedKeys.push(item.code);
if (item?.children) {
getMap(item?.children);
}
@ -127,6 +125,23 @@ export const initData = (Menu: any) => {
return { checkedKeys };
};
/**通过options判断选中菜单 */
export const inItSelected = (Menu:any) =>{
const checkedKeys: any = [];
const getMap = (arr: any) => {
arr.forEach((item: any) => {
item.title = item.code;
item.key = item.code; // treeData需要唯一key
item?.options?.show ? checkedKeys.push(item.code) : '';
if (item?.children) {
getMap(item?.children);
}
});
};
getMap(Menu);
return { checkedKeys };
}
/**
* code
* @param data

View File

@ -62,6 +62,7 @@ const direction = [
'RadiusUpright',
'Fullscreen',
'FullscreenExit',
'EnvironmentOutlined',
];
const suggestion = [

View File

@ -41,11 +41,12 @@
<template #action="slotProps">
<j-space :size="16">
<j-tooltip>
<template #title>编辑</template>
<template #title>{{ slotProps?.options?.LowCode ? '低码创建的菜单不支持编辑' : '编辑' }}</template>
<j-button
style="padding: 0"
type="link"
@click="table.toDetails(slotProps)"
:disabled="slotProps?.options?.LowCode"
>
<AIcon type="EditOutlined" />
</j-button>
@ -53,8 +54,8 @@
<PermissionButton
type="link"
:hasPermission="`${permission}:add`"
:tooltip="{ title: '新增子菜单' }"
:disabled="slotProps.level >= 3"
:tooltip="{ title: slotProps.level >= 3 ? '仅支持3级菜单' : '新增子菜单' }"
:disabled="slotProps.level >= 3 || slotProps?.options?.LowCode"
@click="table.addChildren(slotProps)"
>
<AIcon type="PlusCircleOutlined" />
@ -178,6 +179,8 @@ const table = reactive({
getList: async (_params: any) => {
//
const item = {
terms: [
{
terms: [
{
column: 'owner',
@ -191,6 +194,21 @@ const table = reactive({
type: 'or',
},
],
},
{
terms:[
{
terms:[
{
value:"%show\":true%",
termType:"like",
column:"options"
}
]
}
]
}
],
};
const params = {
..._params,

View File

@ -54,8 +54,10 @@
<script lang="ts" setup>
import { queryChannelConfig } from '@/api/system/noticeRule';
import Item from './components/Item/index.vue';
const dataSource = [
import { useMenuStore } from '@/store/menu';
const menuStore = useMenuStore();
let dataSource:any[] =[]
const systemNotice = [
{
provider: 'alarm',
name: '告警',
@ -99,7 +101,36 @@ const dataSource = [
],
},
];
const activeKey = ref<string[]>(['alarm', 'system-monitor', 'system-business']);
const lowCodeNotice = [
{
provider: 'workflow-notification',
name: '工作流通知',
children: [
{
provider: 'workflow-task-todo',
name: '待办通知',
},
{
provider: 'workflow-task-reject',
name: '驳回通知',
},
{
provider: 'workflow-task-cc',
name: '抄送通知',
},
{
provider: 'workflow-process-finish',
name: '办结通知',
},
{
provider: 'workflow-process-repealed',
name: '关闭通知',
},
],
},
]
const activeKey = ref<string[]>();
const dataMap = new Map();
@ -159,6 +190,13 @@ onMounted(() => {
// data.value = Array.from(dataMap).map((item) => {
// return item?.[1];
// });
if(menuStore.hasMenu('process')){
dataSource = [...systemNotice,...lowCodeNotice]
activeKey.value = ['alarm', 'system-monitor', 'system-business','workflow-notification']
}else{
dataSource = [...systemNotice]
activeKey.value = ['alarm', 'system-monitor', 'system-business']
}
handleSearch();
});
</script>

View File

@ -0,0 +1,240 @@
<template>
<j-input
placeholder="请输入区域名称或行政区划代码"
class="search-input"
v-model:value="searchValue"
@change="(e) => onSearch(e.target.value)"
>
<template #prefix>
<AIcon type="SearchOutlined" style="color: rgba(0, 0, 0, 0.45)" />
</template>
</j-input>
<j-button @click="onAdd" type="primary" class="btn">新增区域</j-button>
<j-tree
class="draggable-tree"
draggable
block-node
v-if="treeData.length"
:tree-data="_treeData"
@drop="onDrop"
:defaultExpandAll="true"
:height="700"
:show-line="{ showLeafIcon: false }"
:show-icon="true"
>
<template #title="_data">
<div class="tree-box">
<div class="name">
<j-ellipsis>{{ _data?.name }}</j-ellipsis>
</div>
<div class="actions">
<j-space :size="8">
<j-tooltip title="重命名">
<j-button
@click.stop="onEdit(_data?.data)"
class="actions-btn"
type="link"
><AIcon type="EditOutlined"
/></j-button>
</j-tooltip>
<j-tooltip title="新增子区域">
<j-button
@click.stop="onAdd(_data?.data)"
class="actions-btn"
type="link"
><AIcon type="PlusCircleOutlined"
/></j-button>
</j-tooltip>
<j-tooltip title="删除">
<j-popconfirm @confirm="onRemove(_data?.id)">
<j-button
@click.stop
class="actions-btn"
type="link"
danger
><AIcon type="DeleteOutlined"
/></j-button>
</j-popconfirm>
</j-tooltip>
</j-space>
</div>
</div>
</template>
</j-tree>
<j-empty v-else style="margin-top: 150px" />
<Save
:mode="mode"
v-if="visible"
:data="current"
@save="onSave"
@close="onClose"
/>
</template>
<script lang="ts" setup>
import { onlyMessage } from '@/utils/comm';
import { debounce } from 'lodash-es';
import { onMounted, ref, watch } from 'vue';
import Save from '../Save/index.vue';
import { getRegionTree, delRegion } from '@/api/system/region';
const treeData = ref<any[]>([]);
const _treeData = ref<any[]>([]);
const visible = ref<boolean>(false);
const current = ref<any>({});
const mode = ref<'add' | 'edit'>('add');
const searchValue = ref<string>();
const filterTreeNodes = (tree: any[], condition: string) => {
return tree.filter((item) => {
if (item?.title && item.title.includes(condition)) {
return true;
}
if (item?.code && item.code.includes(condition)) {
return true;
}
if (item.children) {
item.children = filterTreeNodes(item.children, condition);
return !!item.children.length;
}
return false;
});
};
const onSearch = debounce((v: string) => {
_treeData.value = filterTreeNodes(treeData.value, v);
});
const onSave = () => {
visible.value = false;
handleSearch()
};
const onClose = () => {
visible.value = false;
};
const onEdit = (_data: any) => {
mode.value = 'edit';
current.value = _data;
visible.value = true;
};
const onRemove = async (id: string) => {
const resp = await delRegion(id);
if (resp.success) {
onlyMessage('操作成功!');
handleSearch();
}
};
const onAdd = (_data?: any) => {
mode.value = 'add';
current.value = _data ? _data : {};
visible.value = true;
};
const onDrop = (info: any) => {
const dropKey = info.node.key;
const dragKey = info.dragNode.key;
const dropPos = info.node.pos.split('-');
const dropPosition =
info.dropPosition - Number(dropPos[dropPos.length - 1]);
const loop = (data: any, key: string | number, callback: any) => {
data.forEach((item: any, index: number) => {
if (item.key === key) {
return callback(item, index, data);
}
if (item.children) {
return loop(item.children, key, callback);
}
});
};
const data = [...treeData.value];
// // Find dragObject
let dragObj: any;
loop(data, dragKey, (item: any, index: number, arr: any[]) => {
arr.splice(index, 1);
dragObj = item;
});
if (!info.dropToGap) {
// Drop on the content
loop(data, dropKey, (item: any) => {
item.children = item.children || [];
/// where to insert
item.children.unshift(dragObj);
});
} else if (
(info.node.children || []).length > 0 && // Has children
info.node.expanded && // Is expanded
dropPosition === 1 // On the bottom gap
) {
loop(data, dropKey, (item: any) => {
item.children = item.children || [];
// where to insert
item.children.unshift(dragObj);
});
} else {
let ar: any[] = [];
let i = 0;
loop(data, dropKey, (_item: any, index: number, arr: any[]) => {
ar = arr;
i = index;
});
if (dropPosition === -1) {
ar.splice(i, 0, dragObj);
} else {
ar.splice(i + 1, 0, dragObj);
}
}
treeData.value = data;
};
watch(
() => treeData.value,
() => {
if (searchValue.value) {
onSearch(searchValue.value);
} else {
_treeData.value = treeData.value;
}
},
{
deep: true,
immediate: true,
},
);
const handleSearch = async () => {
const resp = await getRegionTree();
if (resp.success) {
treeData.value = resp?.result || [];
}
};
onMounted(() => {
handleSearch();
});
</script>
<style lang="less" scoped>
.btn {
width: 100%;
margin: 18px 0;
}
.tree-box {
display: flex;
justify-content: space-between;
align-items: center;
.actions {
.actions-btn {
margin: 0;
padding: 0;
}
}
}
</style>

View File

View File

@ -0,0 +1,304 @@
<template>
<div class="region-map">
<AMapComponent
@init="initMap"
>
<el-amap-polygon
v-if="overlay.type === 'polygon'"
:key="JSON.stringify(overlay.path || [])"
:editable="overlay.editable"
:path="overlay.path"
:visible="visible"
@addnode="polygonDraw"
@adjust="polygonDraw"
@init="overlaysInit"
@removenode="polygonDraw"
/>
<el-amap-circle
v-else-if="overlay.type === 'circle'"
:key="`${overlay.radius}_${JSON.stringify(overlay.path || [])}`"
:center="overlay.path"
:editable="overlay.editable"
:radius="overlay.radius"
:visible="visible"
@adjust="circleDraw"
@init="overlaysInit"
/>
<el-amap-rectangle
v-else-if="overlay.type === 'rectangle'"
:key="JSON.stringify(overlay.path || [])"
:bounds="overlay.path"
:editable="overlay.editable"
:visible="visible"
@adjust="rectangleDraw"
@init="overlaysInit"
/>
<el-amap-mouse-tool
v-else-if="overlay.type === 'create' && toolType"
:type="toolType"
@draw="toolDraw"
/>
</AMapComponent>
<div v-show="overlay.editable || overlay.type === 'create'" class="map-tool">
<div class="map-tool-content">
<div class="tool-item-group">
<div class="tool-item">
<j-tooltip title="双击保存描点" >
<AIcon type="QuestionCircleOutlined" />
</j-tooltip>
</div>
</div>
<div class="tool-item-group">
<div :class="{'tool-item': true, 'active': toolType === 'rectangle'}" @click="() => { drawOverlays('rectangle') }">
<j-tooltip title="矩形" >
<AIcon type="icon-huajuxing" />
</j-tooltip>
</div>
<div :class="{'tool-item': true, 'active': toolType === 'circle'}" @click="() => { drawOverlays('circle') }">
<j-tooltip title="圆" >
<AIcon type="icon-draw-circle" />
</j-tooltip>
</div>
<div :class="{'tool-item': true, 'active': toolType === 'polygon'}" @click="() => { drawOverlays('polygon') }">
<j-tooltip title="多边形" >
<AIcon type="icon-huaduobianxing" />
</j-tooltip>
</div>
</div>
<!-- <div class="tool-item-group">-->
<!-- <div class="tool-item">-->
<!-- <j-tooltip title="缩放" >-->
<!-- <AIcon type="GetwayOutlined" />-->
<!-- </j-tooltip>-->
<!-- </div>-->
<!-- <div class="tool-item">-->
<!-- <j-tooltip title="旋转" >-->
<!-- <AIcon type="GetwayOutlined" />-->
<!-- </j-tooltip>-->
<!-- </div>-->
<!-- </div>-->
<div class="tool-item-group">
<div :class="{'tool-item': true, 'disabled': historyList.length <= 1 }" @click="cancel">
<j-tooltip title="撤销" >
<AIcon type="RollbackOutlined" />
</j-tooltip>
</div>
<div class="tool-item">
<j-tooltip title="删除" @click="deleteOverlays">
<AIcon type="DeleteOutlined" />
</j-tooltip>
</div>
</div>
</div>
</div>
</div>
</template>
<script name="RegionMap" setup>
const props = defineProps({
path: {
type: Array,
default: () => []
},
radius: {
type: Number,
default: 0
},
type: {
type: String,
default: 'polygon'
},
})
const emit = defineEmits('dragend')
const MapRef = ref()
const toolType = ref('')
const historyList = ref([])
const overlay = reactive({
type: props.type,
path: props.path,
center: [],
editable: false,
status: 0 // 0 1
})
const visible = computed(() => {
return true
})
const setHistory = (data) => {
if (historyList.value.length > 10) {
historyList.value.shift()
}
historyList.value.push(data)
}
const initMap = (e) => {
MapRef.value = e
}
const polygonDraw = (e) => {
const target = e.target
const path = target.getPath()
setHistory({
type: 'polygon',
toolType: toolType.value,
editable: true,
path: path.map(item => [item.lng, item.lat]),
})
}
const circleDraw = (e) => {
setHistory({
type: 'circle',
toolType: toolType.value,
editable: true,
path: [e.lnglat.lng, e.lnglat.lat],
radius: e.radius
})
}
const rectangleDraw = (e) => {
const northEast = e.bounds.getNorthEast()
const southWest = e.bounds.getSouthWest()
const path = [
[southWest.lng, southWest.lat],
[northEast.lng, northEast.lat]
]
setHistory({
type: 'rectangle',
toolType: toolType.value,
editable: true,
path: path,
})
}
const overlaysInit = (e) => {
if (MapRef.value) {
MapRef.value.setFitView(e)
}
}
const drawOverlays = (e) => {
overlay.type = 'create'
toolType.value = e
}
const toolDraw = (e) => {
if (toolType.value === 'circle') {
overlay.path = e.center
overlay.radius = e.radius
} else {
overlay.path = e
}
overlay.editable = true
overlay.type = toolType.value
setHistory({
type: toolType.value,
toolType: toolType.value,
editable: overlay.editable,
path: overlay.path,
radius: overlay.radius
})
}
const deleteOverlays = () => {
overlay.type = 'create'
overlay.editable = false
toolType.value = ''
setHistory({
type: 'create',
toolType: '',
editable: false,
path: [],
radius: 0
})
}
const cancel = () => {
if (historyList.value.length > 1) {
historyList.value.pop()
}
const oldData = historyList.value[historyList.value.length - 1]
if (oldData) {
overlay.type = oldData.type
overlay.editable = oldData.editable
overlay.path = oldData.path
overlay.radius = oldData.radius
toolType.value = oldData.toolType
}
}
</script>
<style lang="less" scoped>
.region-map {
position: relative;
height: 100%;
.map-tool{
position: absolute;
top: 20%;
right: 20px;
.map-tool-content {
display: flex;
gap: 24px;
flex-direction: column;
.tool-item-group {
display: flex;
flex-direction: column;
border: 1px solid #e3e3e3;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 0 16px rgba(#000, .15);
.tool-item {
padding: 4px 6px;
color: #333;
font-size: 16px;
&:first-child {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
&:last-child {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
}
&:not(:first-child) {
border-top: 1px solid #e3e3e3;
}
&.active {
background-color: var(--ant-primary-color);
color: #fff;
}
&.disabled {
cursor: not-allowed !important;
background-color: #efefef;
> span {
cursor: not-allowed !important;
color: #666;
}
}
}
}
}
}
}
</style>

View File

@ -0,0 +1,124 @@
<template>
<j-tree-select
showSearch
placeholder="1级区域不需要选择"
:tree-data="builtInAreaList"
:value="_value"
:field-names="{
children: 'children',
label: 'name',
value: 'code',
}"
tree-node-filter-prop="name"
@select="onSelect"
>
<template #title="{ name, code }">
<span v-if="code">{{ name }} | {{ code }}</span>
</template>
</j-tree-select>
<j-checkbox
@change="onCheckChange"
v-model:checked="_checked"
style="margin-top: 5px"
>同步添加下一级区域</j-checkbox
>
</template>
<script lang="ts" setup>
import { getBuiltinRegionTree } from '@/api/system/region';
import { onMounted, ref, watch } from 'vue';
const props = defineProps({
value: {
type: String,
default: '',
},
name: {
type: String,
default: '',
},
children: {
type: Array,
default: () => [],
},
});
const emits = defineEmits(['update:value', 'update:name', 'update:children']);
const features = ref<any>({});
const _value = ref<string>();
const builtInAreaList = ref<Record<string, any>[]>([]);
const _checked = ref<boolean>(false);
const queryBuiltinRegionTree = async () => {
const resp = await getBuiltinRegionTree({
paging: false,
sorts: [{ name: 'sortIndex', order: 'asc' }],
});
if (resp.success) {
builtInAreaList.value = resp?.result || [];
}
};
const onCheckChange = (e: any) => {
if (e.target.checked) {
emits('update:children', features.value?.children || []);
} else {
emits('update:children', []);
}
};
const getObj = (node: any): any => {
const _children = (node?.children || []).map((item: any) => {
return {
code: item.code,
name: item.name,
parentId: item.parentId,
};
});
return {
code: node.code,
name: node.name,
parentId: node.parentId,
children: _children,
};
};
const onSelect = (val: string, node: any) => {
features.value = getObj(node);
_value.value = val;
emits('update:name', features.value?.name);
emits('update:value', features.value?.code);
};
onMounted(() => {
queryBuiltinRegionTree();
});
watch(
() => props.value,
() => {
if (props.value) {
_value.value = props.value
} else {
emits('update:name', '中国');
emits('update:value', 100000);
}
},
{
deep: true,
immediate: true,
},
);
watch(
() => props.children,
() => {
_checked.value = !!props.children?.length
},
{
deep: true,
immediate: true,
},
);
</script>

View File

@ -0,0 +1,45 @@
<template>
<j-button @click="onClick" v-if="!_value.length" type="link"
>请在地图上描点</j-button
>
<div v-else>已完成描点<j-button type="link" @click="onEdit">编辑</j-button></div>
</template>
<script lang="ts" setup>
import { ref, watch, inject } from 'vue';
const props = defineProps({
value: {
type: Array,
default: () => [],
},
});
const emits = defineEmits(['update:value', 'close']);
const _value = ref<any[]>([]);
const __data: any = inject('system-region')
const onClick = () => {
console.log(__data)
__data.type.value = 'edit'
emits('close')
};
const onEdit = () => {
// __data.type.value = 'edit'
// emits('close')
}
watch(
() => props.value,
() => {
console.log(props.value)
},
{
deep: true,
immediate: true,
},
);
</script>

View File

@ -0,0 +1,234 @@
<template>
<j-modal
:maskClosable="false"
width="650px"
:visible="true"
:title="mode === 'edit' ? '编辑区域' : '新增区域'"
@ok="handleSave"
@cancel="handleCancel"
:confirmLoading="loading"
>
<div style="margin-top: 10px">
<j-form :layout="'vertical'" ref="formRef" :model="modelRef">
<j-form-item name="parentId" label="上级区域">
<j-tree-select
showSearch
v-model:value="modelRef.parentId"
placeholder="1级区域不需要选择"
:tree-data="areaList"
allowClear
:field-names="{
children: 'children',
label: 'name',
value: 'id',
}"
tree-node-filter-prop="name"
/>
</j-form-item>
<j-form-item :name="['properties', 'type']" label="添加方式">
<j-radio-group
v-model:value="modelRef.properties.type"
button-style="solid"
@change="onChange"
>
<j-radio-button value="builtin"
>内置行政区</j-radio-button
>
<j-radio-button value="Custom"
>自定义数据</j-radio-button
>
</j-radio-group>
</j-form-item>
<j-form-item v-if="modelRef.properties.type === 'builtin'">
<BuildIn
v-model:value="modelRef.code"
v-model:children="modelRef.children"
v-model:name="modelRef.name"
/>
</j-form-item>
<j-form-item
label="区域名称"
name="name"
required
:rules="[
{
required: true,
message: '请输入区域名称',
},
{
max: 64,
message: '最多输入64个字符',
},
{
validator: vailName,
trigger: 'blur',
},
]"
>
<j-input
v-model:value="modelRef.name"
placeholder="请输入区域名称"
/>
</j-form-item>
<j-form-item
label="行政区划代码"
name="code"
required
:rules="[
{
required: true,
message: '请输入行政区划代码',
},
{
validator: vailCode,
trigger: 'blur',
},
]"
>
<j-input-number
v-model:value="modelRef.code"
style="width: 100%"
placeholder="请输入行政区划代码"
/>
</j-form-item>
<j-form-item
v-if="modelRef.properties.type !== 'builtin'"
label="区划划分"
required
name="features"
>
<TracePoint @close="emit('close')" v-model:value="modelRef.features" />
</j-form-item>
</j-form>
</div>
</j-modal>
</template>
<script lang="ts" setup>
import { ref, watch, reactive, PropType, onMounted } from 'vue';
import TracePoint from './TracePoint.vue';
import BuildIn from './BuildIn.vue';
import {
validateName,
getRegionTree,
validateCode,
updateRegion,
} from '@/api/system/region';
import { onlyMessage } from '@/utils/comm';
const emit = defineEmits(['close', 'save']);
const props = defineProps({
data: {
type: Object,
default: () => {},
},
mode: {
type: String as PropType<'add' | 'edit'>,
default: 'add',
},
});
const areaList = ref<Record<string, any>[]>([]);
const loading = ref<boolean>(false);
const formRef = ref();
const init = {
parentId: undefined,
id: undefined,
name: undefined,
code: undefined,
features: undefined,
children: [],
properties: {
type: 'builtin',
},
};
const modelRef = reactive(init);
watch(
() => props.data,
() => {
Object.assign(modelRef, {});
if (props.mode === 'add' && props.data?.id) {
//
Object.assign(modelRef, {
...init,
parentId: props.data.id,
});
} else if (props.mode === 'edit') {
//
Object.assign(modelRef, props.data);
} else {
Object.assign(modelRef, init);
}
},
{ immediate: true, deep: true },
);
const handleCancel = () => {
emit('close');
};
const handleSave = () => {
formRef.value
.validate()
.then(async (_data: any) => {
loading.value = true;
const resp = await updateRegion({
...props.data,
...modelRef,
}).finally(() => {
loading.value = false;
});
if (resp.status === 200) {
onlyMessage('操作成功!');
emit('save');
}
})
.catch((err: any) => {
console.log('error', err);
});
};
const vailName = async (_: Record<string, any>, value: string) => {
if (!props?.data?.id && value) {
const resp = await validateName(value, props.data.id);
if (resp.status === 200 && !resp.result?.passed) {
return Promise.reject(resp.result?.reason || '区域名称重复');
} else {
return Promise.resolve();
}
} else {
return Promise.resolve();
}
};
const vailCode = async (_: Record<string, any>, value: string) => {
if (!props?.data?.id && value) {
const resp = await validateCode(value, props.data.id);
if (resp.status === 200 && !resp.result?.passed) {
return Promise.reject(resp.result?.reason || '行政区域代码重复');
} else {
return Promise.resolve();
}
} else {
return Promise.resolve();
}
};
const onChange = () => {
modelRef.features = undefined;
};
const handleSearch = async () => {
const resp = await getRegionTree();
if (resp.success) {
areaList.value = resp?.result || [];
}
};
onMounted(() => {
handleSearch();
});
</script>

View File

@ -0,0 +1,65 @@
<template>
<page-container>
<full-page>
<div class="region">
<div class="left">
<LeftTree />
<div class="mask" v-if="type === 'edit'"></div>
</div>
<div class="right">
<Map :path="path" :type="mapType" />
</div>
</div>
</full-page>
</page-container>
</template>
<script setup lang="ts" name="RegionMange">
import LeftTree from './LeftTree/index.vue'
import Map from './MapTool/map.vue'
import FullPage from "components/Layout/FullPage.vue";
import { provide } from 'vue'
const path = ref([[121.5273285, 31.21515044], [121.5293285, 31.21515044], [121.5293285, 31.21915044], [121.5273285, 31.21515044]])
const type = ref<'view' | 'edit'>('view')
const mapType = ref<string>('create')
provide('system-region', {
type,
mapType: '',
path
})
</script>
<style lang="less" scoped>
.region {
display: flex;
gap: 24px;
height: 100%;
padding: 16px;
.left {
width: 300px;
position: relative;
.btn {
width: 100%;
margin: 18px 0;
}
.mask {
width: 100%;
position: absolute;
height: 100%;
background-color: lightgray;
opacity: .5;
left: 0;
top: 0;
z-index: 10;
}
}
.right {
flex: 1;
}
}
</style>

View File

@ -10,22 +10,9 @@
class="edit-dialog-container"
>
<j-form ref="formRef" :model="form.data" layout="vertical">
<j-form-item
label="名称"
name="name"
:rules="[
{ required: true, message: '请输入名称' },
{ max: 64, message: '最多可输入64个字符' },
]"
>
<j-input
v-model:value="form.data.name"
placeholder="请输入名称"
/>
</j-form-item>
<j-form-item
name="relation"
label="标识"
label="关系标识"
:rules="[
{ required: true, message: '请输入标识' },
{ max: 64, message: '最多可输入64个字符' },
@ -83,6 +70,37 @@
</j-form-item>
</j-col>
</j-row>
<j-form-item
label="正向关系名称"
name="name"
:rules="[
{ required: true, message: '请输入名称' },
{ max: 64, message: '最多可输入64个字符' },
{ required: true , validator:validateName, trigger: 'blur',}
]"
>
<j-input
v-model:value="form.data.name"
placeholder="请输入名称"
/>
<span class="example">正向关系示例用户张三是001号视频设备的管理员</span>
</j-form-item>
<j-form-item
label="反向关系名称"
name="reverseName"
:rules="[
{ required: true, message: '请输入名称' },
{ max: 64, message: '最多可输入64个字符' },
{ required: true , validator:validateName, trigger: 'blur',}
]"
>
<j-input
v-model:value="form.data.reverseName"
placeholder="请输入名称"
/>
<span class="example">反向关系示例001号视频设备是用户张三的管辖设备</span>
</j-form-item>
<j-form-item name="description" label="说明">
<j-textarea
v-model:value="form.data.description"
@ -115,6 +133,7 @@ const props = defineProps<{
}>();
//
const loading = ref(false);
const targetList = ref([])
const dialogTitle = computed(() => (props.data.id ? '编辑' : '新增'));
const confirm = () => {
loading.value = true;
@ -180,6 +199,7 @@ const form = reactive({
getObjectList: () => {
getObjectList_api().then((resp: any) => {
form.objectList = resp.result;
targetList.value = resp.result
});
},
submit: () => {
@ -196,13 +216,17 @@ const form = reactive({
return api(params);
},
});
const targetList = computed(() =>
form.data.objectType === 'device' ? [{ id: 'user', name: '用户' }] : [],
);
const validateName = async(_:any,value:any)=>{
if(!value){
return Promise.resolve()
}
return form.data.reverseName === form.data.name ? Promise.reject('不能使用相同的关系名称') : Promise.resolve()
}
form.getObjectList();
type formType = {
name: string;
reverseName: string;
relation: string;
objectType: string | undefined;
targetType: string | undefined;
@ -210,3 +234,9 @@ type formType = {
id?: string;
};
</script>
<style scoped lang="less">
.example {
color: rgb(192, 192, 192);
font-size: 12px;
}
</style>

View File

@ -80,7 +80,7 @@ const permission = 'system/Relationship';
const columns = [
{
title: '名称',
title: '正向关系名称',
dataIndex: 'name',
key: 'name',
ellipsis: true,
@ -89,6 +89,16 @@ const columns = [
type: 'string',
},
},
{
title: '反向关系名称',
dataIndex: 'reverseName',
key: 'reverseName',
ellipsis: true,
fixed: 'left',
search: {
type: 'string',
},
},
{
title: '关联方',
dataIndex: 'objectTypeName',

View File

@ -1,6 +1,6 @@
<template>
<div class="role-permiss-container">
<section class="card">
<!-- <section class="card">
<h5>基本信息</h5>
<j-form
ref="formRef"
@ -30,7 +30,7 @@
/>
</j-form-item>
</j-form>
</section>
</section> -->
<section class="card">
<h5>权限分配</h5>
@ -38,7 +38,7 @@
<j-button
type="primary"
:disabled="form.loading"
:confirm-loading="form.loading"
@click="form.clickSave"
style="margin-top: 24px"
>保存</j-button
@ -70,33 +70,33 @@ const roleId = route.params.id as string;
const formRef = ref<FormInstance>();
const form = reactive({
loading: false,
data: {
name: '',
description: '',
},
// data: {
// name: '',
// description: '',
// },
menus: [], // USER_CENTER_MENU_DATA
getForm: () => {
getRoleDetails_api(roleId).then((resp) => {
if (resp.status) {
form.data = resp.result;
}
});
},
// getForm: () => {
// getRoleDetails_api(roleId).then((resp) => {
// if (resp.status) {
// form.data = resp.result;
// }
// });
// },
clickSave: () => {
formRef.value?.validate().then(() => {
const updateRole = editRole_api(roleId, form.data);
// formRef.value?.validate().then(() => {
// const updateRole = editRole_api(roleId, form.data);
const updateTree = updatePrimissTree_api(roleId, {
menus: form.menus,
});
Promise.all([updateRole, updateTree]).then((resp) => {
Promise.all([ updateTree]).then((resp) => {
onlyMessage('操作成功');
// jumpPage(`system/Role`);
});
});
})
// });
},
});
form.getForm();
// form.getForm();
</script>
<style lang="less" scoped>

View File

@ -0,0 +1,175 @@
<template>
<div class="left-contain">
<j-input placeholder="分组名称" v-model:value="searchValue" @pressEnter="search" @change="searchChange">
<template #suffix>
<AIcon type="SearchOutlined" @click="search" />
</template>
</j-input>
<div class="controls" v-if="admin">
<j-button type="primary" @click="addGroup" style="width: 100%">
新增分组
</j-button>
</div>
<div class="listBox">
<j-tree :tree-data="listData" v-if="listData.length" :fieldNames="{ title: 'name', key: 'id', children: 'children' }"
blockNode :selectedKeys="selectedKeys" :default-expanded-keys="['global_role']"
:showLine="{ showLeafIcon: false }">
<template #title="item">
<div v-if="selectId === item.data.id">
<j-input v-model:value="addName" @blur="() => saveGroup(item.data)" ref="inputRef"
:maxlength="64"></j-input>
</div>
<div class="treeItem" @click="() => selectGroup(item.data.id)" v-else>
<template v-if="!item?.children">
<div class="itemText">
<Ellipsis style="width: calc(100%-100px)">{{ item.name }}</Ellipsis>
</div>
<div @click="(e) => e.stopPropagation()" v-if="item.id !== 'default_group'">
<PermissionButton type="text" hasPermission="system/Role:groupDelete" :popConfirm="{
title: `确定要删除?`,
onConfirm: () => deleteGroup(item.id),
}" :disabled="item.id === 'default_group'">
删除
</PermissionButton>
<PermissionButton type="text" hasPermission="system/Role:groupUpdate"
@click="editGroup(item.data)" :disabled="item.id === 'default_group'">
编辑
</PermissionButton>
</div>
</template>
<template v-else>
<Ellipsis style="width: calc(100%-100px)">{{ item.name }}</Ellipsis>
</template>
</div>
</template>
</j-tree>
<j-empty v-else style="margin-top: 100px;" />
</div>
</div>
</template>
<script lang="ts" setup>
import { onlyMessage } from '@/utils/comm';
import { queryRoleGroup, saveRoleGroup, deleteRoleGroup } from '@/api/system/role';
import { randomString } from '@/utils/utils';
import { useUserInfo } from '@/store/userInfo';
import { storeToRefs } from 'pinia';
const emit = defineEmits(['selectData'])
const userInfoStore = useUserInfo()
const { userInfos } = storeToRefs(userInfoStore)
const admin = computed(() => {
return userInfos.value?.username === 'admin';
})
const listData: any = ref([{
name: '全局角色',
id: 'global_role',
children: []
}])
const selectedKeys = ref<string[]>(['global_role'])
const searchValue = ref()
const inputRef = ref()
const addName = ref()
const selectId = ref()
const addStatus = ref(false) //
const queryGroup = async (select?: Boolean, searchName?: string) => {
const params = searchName ? { sorts: [{ name: 'createTime', order: 'desc' }], terms: [{ terms: [{ value: '%' + searchName + '%', termType: 'like', column: 'name' }] }] } : { sorts: [{ name: 'createTime', order: 'desc' }] }
const req: any = await queryRoleGroup(params)
if (req.status === 200) {
listData.value[0].children = req.result
// if(req.result.length && select){
// selectGroup(req.result[0].id)
// }
}
}
const addGroup = () => {
addName.value = ''
const newId = randomString()
listData.value[0].children.unshift({
name: '',
id: newId
})
selectId.value = newId
nextTick(() => {
inputRef.value.focus()
})
}
const saveGroup = async (data: any) => {
if (addName.value === '') {
listData.value[0].children.shift()
} else {
const saveData = {
name: addName.value,
id: data.id
}
const res = await saveRoleGroup(saveData)
if (res.status === 200) {
onlyMessage('操作成功!')
queryGroup()
} else {
onlyMessage('操作失败!')
}
}
setTimeout(()=>{
selectId.value = ''
},300)
}
const search = () => {
queryGroup(true, searchValue.value)
}
const searchChange = () => {
if (searchValue.value === '') {
queryGroup()
}
}
const selectGroup = (id: string) => {
selectedKeys.value = [id]
id === 'global_role' ? emit('selectData', '') : emit('selectData', selectedKeys.value)
}
const deleteGroup = async (id: string) => {
const res: any = await deleteRoleGroup(id)
if (res.status === 200) {
onlyMessage('操作成功!')
queryGroup(true)
} else {
onlyMessage('操作失败!')
}
}
const editGroup = (data: any) => {
if(!selectId.value){
selectId.value = data.id
addName.value = data.name
listData.value[0].children.forEach((item: any) => {
if (item.id === data.id) {
item.edit = true
}
})
nextTick(() => {
inputRef.value.focus()
})
}
}
onMounted(() => {
queryGroup(true)
})
</script>
<style lang="less" scoped>
.controls {
margin: 10px 0;
}
.treeItem {
display: flex;
justify-content: space-between;
.itemText {
line-height: 32px;
max-width: 40%
}
}
.listBox {
margin: 10px 0;
}
</style>

View File

@ -0,0 +1,139 @@
<template>
<j-modal
visible
:title="modalType ==='add' ? '新增' : '编辑'"
width="670px"
@cancel="emits('update:visible', false)"
@ok="confirm"
:confirm-loading="loading"
>
<j-form ref="formRef" :model="form" layout="vertical">
<j-form-item
name="name"
label="名称"
:rules="[
{ required: true, message: '请输入名称' },
{ max: 64, message: '最多可输入64个字符' },
]"
>
<j-input
v-model:value="form.name"
placeholder="请输入角色名称"
allow-clear
/>
</j-form-item>
<j-form-item
name="groupId"
label="分组"
:rules="[
{ required: true, message: '请选择分组' },
]"
>
<j-select
v-model:value="form.groupId"
placeholder="请选择分组"
:options="groupOptions"
/>
</j-form-item>
<j-form-item name="name" label="说明">
<j-textarea
v-model:value="form.description"
placeholder="请输入说明"
allow-clear
:maxlength="200"
show-count
/>
</j-form-item>
</j-form>
</j-modal>
</template>
<script setup lang="ts">
import { FormInstance } from 'ant-design-vue';
import { saveRole_api , queryRoleGroup , updateRole_api} from '@/api/system/role';
import { useMenuStore } from '@/store/menu';
import { onlyMessage } from '@/utils/comm';
const route = useRoute();
const { jumpPage } = useMenuStore();
const emits = defineEmits(['update:visible']);
const props = defineProps({
visible: {
type:Boolean,
default:false
},
groupId:{
type:String,
default:""
},
modalType:{
type:String,
default:'add'
},
current:{
type:Object,
default:{}
}
})
//
const loading = ref(false);
const form = ref<any>({
name:'',
groupId:'',
description:''
});
const formRef = ref<FormInstance>();
const groupOptions = ref<any>([])
const confirm = async() => {
loading.value = true;
formRef.value
?.validate()
.then(() => {
if(props.modalType === 'add'){
saveRole_api(form.value).then((resp:any)=>{
if (resp.status === 200) {
onlyMessage('操作成功');
emits('update:visible', false);
if (route.query.save) {
// @ts-ignore
if((window as any).onTabSaveSuccess){
(window as any).onTabSaveSuccess(resp.result.id);
setTimeout(() => window.close(), 300);
}
} else jumpPage(`system/Role/Detail`, { id: resp.result.id });
}
}).catch(() => (loading.value = false));
}else{
updateRole_api(form.value).then((resp:any)=>{
if (resp.status === 200) {
onlyMessage('操作成功');
emits('update:visible', false);
}
}).catch(() => (loading.value = false));
}
})
.catch(() => (loading.value = false));
};
//
const getGroupOptions = ()=>{
queryRoleGroup({sorts: [{ name: 'createTime', order: 'desc' }]}).then((res:any)=>{
if(res.status ===200){
groupOptions.value = res.result.map((item:any)=>{
return {
label:item.name,
value:item.id
}
})
}
})
}
onMounted(()=>{
getGroupOptions()
form.value.groupId = props.groupId
if(props.modalType === 'edit'){
form.value = props.current
}
})
</script>
<style scoped></style>

View File

@ -0,0 +1,193 @@
<template>
<page-container>
<div class="role-container">
<pro-search :columns="columns" target="system-role" @search="handelSearch" />
<FullPage>
<j-pro-table ref="tableRef" :columns="columns" :request="getRoleList_api" model="TABLE"
:params="queryParams" :defaultParams="{
sorts: [
{ name: 'createTime', order: 'desc' },
{ name: 'id', order: 'desc' },
]
}">
<template #headerTitle>
<PermissionButton type="primary" :hasPermission="`${permission}:add`" @click="addRole">
<AIcon type="PlusOutlined" />新增
</PermissionButton>
</template>
<template #action="slotProps">
<j-space>
<template v-for="i in getActions(slotProps, 'table')" :key="i.key">
<PermissionButton :disabled="i.disabled" :popConfirm="i.popConfirm" :tooltip="{
...i.tooltip,
}" @click="i.onClick" type="link" style="padding: 0 5px" :danger="i.key === 'delete'"
:hasPermission="'system/Role:' + i.key
">
<template #icon>
<AIcon :type="i.icon" />
</template>
</PermissionButton>
</template>
</j-space>
</template>
</j-pro-table>
</FullPage>
<AddDialog v-if="dialogVisible" v-model:visible="dialogVisible" :groupId="groupId" :modalType="modalType"
:current="current" />
</div>
</page-container>
</template>
<script setup lang="ts" name="Role">
import PermissionButton from '@/components/PermissionButton/index.vue';
import AddDialog from './components/AddDialog.vue';
import { getRoleList_api, delRole_api } from '@/api/system/role';
import type { ActionsType } from './typings';
import { useMenuStore } from '@/store/menu';
import { onlyMessage } from '@/utils/comm';
const props = defineProps({
groupId: {
type: String,
default: ''
}
})
const permission = 'system/Role';
const { jumpPage } = useMenuStore();
const modalType = ref('add')
const current = ref()
const isSave = !!useRoute().query.save;
const queryParams = ref<any>({terms:[]});
//
const tableRef = ref<Record<string, any>>();
const dialogVisible = ref(isSave);
const columns = [
{
title: '标识',
dataIndex: 'id',
key: 'id',
ellipsis: true,
fixed: 'left',
search: {
type: 'string',
},
},
{
title: '名称',
dataIndex: 'name',
key: 'name',
ellipsis: true,
search: {
type: 'string',
},
},
{
title: '说明',
key: 'description',
ellipsis: true,
dataIndex: 'description',
search: {
type: 'string',
},
},
{
title: '操作',
dataIndex: 'action',
key: 'action',
width: 120,
fixed: 'right',
scopedSlots: true,
},
];
const getActions = (
data: Partial<Record<string, any>>,
type: 'card' | 'table',
): ActionsType[] => {
if (!data) return [];
const actions = [
{
key: 'update',
text: '编辑',
tooltip: {
title: '编辑',
},
icon: 'EditOutlined',
onClick: () => {
dialogVisible.value = true;
modalType.value = 'edit';
current.value = data
},
},
{
key: 'update',
text: '权限配置',
tooltip: {
title: '权限配置'
},
onClick: () => {
jumpPage(`system/Role/Detail`, {
id: data.id,
})
},
icon: 'FormOutlined'
},
{
key: 'delete',
text: '删除',
tooltip: {
title: '删除',
},
popConfirm: {
title: '确认删除?',
onConfirm: async () => {
const res = await delRole_api(data.id)
if (res.status === 200) {
onlyMessage('操作成功!')
tableRef.value?.reload()
} else {
onlyMessage('操作失败!', 'error')
}
},
},
icon: 'DeleteOutlined',
},
];
if (type === 'card')
return actions.filter((i: ActionsType) => i.key !== 'view');
return actions;
};
const addRole = () => {
dialogVisible.value = true
modalType.value = 'add'
}
const handelSearch = (search: any) => {
queryParams.value.terms =props.groupId ? [{
value: props.groupId,
termType: 'eq',
column: 'groupId'
}, ...search.terms] : [...search.terms]
}
watch(() => props.groupId, (value) => {
queryParams.value = value ? {
terms: [{
value: props.groupId,
termType: 'eq',
column: 'groupId'
}]
} : {
terms: []
}
})
</script>
<style lang="less" scoped>
.role-container {
:deep(.ant-table-cell) {
.ant-btn-link {
padding: 0;
}
}
}
</style>

View File

@ -1,79 +0,0 @@
<template>
<j-modal
visible
title="新增"
width="670px"
@cancel="emits('update:visible', false)"
@ok="confirm"
:confirm-loading="loading"
>
<j-form ref="formRef" :model="form" layout="vertical">
<j-form-item
name="name"
label="名称"
:rules="[
{ required: true, message: '请输入名称' },
{ max: 64, message: '最多可输入64个字符' },
]"
>
<j-input
v-model:value="form.name"
placeholder="请输入角色名称"
allow-clear
/>
</j-form-item>
<j-form-item name="name" label="说明">
<j-textarea
v-model:value="form.description"
placeholder="请输入说明"
allow-clear
:maxlength="200"
show-count
/>
</j-form-item>
</j-form>
</j-modal>
</template>
<script setup lang="ts">
import { FormInstance } from 'ant-design-vue';
import { saveRole_api } from '@/api/system/role';
import { useMenuStore } from '@/store/menu';
import { onlyMessage } from '@/utils/comm';
const route = useRoute();
const { jumpPage } = useMenuStore();
const emits = defineEmits(['update:visible']);
const props = defineProps<{
visible: boolean;
}>();
//
const loading = ref(false);
const form = ref<any>({});
const formRef = ref<FormInstance>();
const confirm = () => {
loading.value = true;
formRef.value
?.validate()
.then(() => saveRole_api(form.value))
.then((resp) => {
if (resp.status === 200) {
onlyMessage('操作成功');
emits('update:visible', false);
if (route.query.save) {
// @ts-ignore
if((window as any).onTabSaveSuccess){
(window as any).onTabSaveSuccess(resp.result.id);
setTimeout(() => window.close(), 300);
}
} else jumpPage(`system/Role/Detail`, { id: resp.result.id });
}
})
.finally(() => (loading.value = false));
};
//
</script>
<style scoped></style>

View File

@ -1,142 +1,40 @@
<template>
<page-container>
<div class="role-container">
<pro-search
:columns="columns"
target="system-role"
@search="(params:any)=>queryParams = {...params}"
/>
<FullPage>
<j-pro-table
ref="tableRef"
:columns="columns"
:request="getRoleList_api"
model="TABLE"
:params="queryParams"
:defaultParams="{
sorts: [
{ name: 'createTime', order: 'desc' },
{ name: 'id', order: 'desc' },
],
}"
>
<template #headerTitle>
<PermissionButton
type="primary"
:hasPermission="`${permission}:add`"
@click="dialogVisible = true"
>
<AIcon type="PlusOutlined" />新增
</PermissionButton>
</template>
<template #action="slotProps">
<j-space :size="16">
<PermissionButton
:hasPermission="`${permission}:update`"
type="link"
:tooltip="{
title: '编辑',
}"
@click="
jumpPage(`system/Role/Detail`, {
id: slotProps.id,
})
"
>
<AIcon type="EditOutlined" />
</PermissionButton>
<PermissionButton
type="link"
:hasPermission="`${permission}:delete`"
:tooltip="{ title: '删除' }"
:popConfirm="{
title: `确定要删除吗`,
onConfirm: () => clickDel(slotProps),
}"
>
<AIcon type="DeleteOutlined" />
</PermissionButton>
</j-space>
</template>
</j-pro-table>
</FullPage>
<AddDialog v-if="dialogVisible" v-model:visible="dialogVisible" />
<div class="dictionary_contain">
<div class="dictionary_left">
<Left @select-data="selectData"/>
</div>
<div class="dictionary_right">
<Right :groupId="groupId"/>
</div>
</div>
</FullPage>
</page-container>
</template>
<script setup lang="ts" name="Role">
import PermissionButton from '@/components/PermissionButton/index.vue';
import AddDialog from './components/AddDialog.vue';
import { getRoleList_api, delRole_api } from '@/api/system/role';
import { useMenuStore } from '@/store/menu';
import { onlyMessage } from '@/utils/comm';
const permission = 'system/Role';
const { jumpPage } = useMenuStore();
const isSave = !!useRoute().query.save;
const columns = [
{
title: '标识',
dataIndex: 'id',
key: 'id',
ellipsis: true,
fixed: 'left',
search: {
type: 'string',
},
},
{
title: '名称',
dataIndex: 'name',
key: 'name',
ellipsis: true,
search: {
type: 'string',
},
},
{
title: '说明',
key: 'description',
ellipsis: true,
dataIndex: 'description',
search: {
type: 'string',
},
},
{
title: '操作',
dataIndex: 'action',
key: 'action',
width: 120,
fixed: 'right',
scopedSlots: true,
},
];
const queryParams = ref({});
//
const tableRef = ref<Record<string, any>>();
const clickDel = (row: any) => {
delRole_api(row.id).then((resp: any) => {
if (resp.status === 200) {
tableRef.value?.reload();
onlyMessage('操作成功!');
<script lang="ts" setup>
import Left from './RoleLeft/index.vue'
import Right from './RoleRight/index.vue'
const groupId = ref()
const selectData = (data:any)=>{
groupId.value = data[0]
}
});
};
const dialogVisible = ref(isSave);
</script>
<style lang="less" scoped>
.role-container {
:deep(.ant-table-cell) {
.ant-btn-link {
padding: 0;
.dictionary_contain{
display: flex;
background-color: #fff;
padding: 24px;
height: 100%;
}
.dictionary_left{
border-right: 1px solid #f0f0f0;
padding-right: 24px;
flex:1;
height:100%
}
.dictionary_right{
flex:4
}
</style>

View File

@ -12,8 +12,9 @@
okText="确定"
>
<j-form ref="formRef" :model="form.data" layout="vertical">
<div class="formName" v-if="form.IsShow('add', 'edit')">基础信息</div>
<j-row :gutter="24" v-if="form.IsShow('add', 'edit')">
<j-col :span="12">
<j-col :span="24">
<j-form-item
name="name"
label="姓名"
@ -31,7 +32,113 @@
/>
</j-form-item>
</j-col>
</j-row>
<j-row :gutter="24" v-if="form.IsShow('add', 'edit')">
<j-col :span="12">
<j-form-item
name="telephone"
label="手机号"
:rules="[
{
pattern: /^1[3456789]\d{9}$/,
message: '请输入正确的手机号',
},
]"
>
<j-input
v-model:value="form.data.telephone"
placeholder="请输入手机号"
:maxlength="64"
/>
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
name="email"
label="邮箱"
:rules="[
{
pattern:
/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/,
message: '请输入正确的邮箱',
},
]"
>
<j-input
v-model:value="form.data.email"
placeholder="请输入邮箱"
:maxlength="64"
/>
</j-form-item>
</j-col>
</j-row>
<j-row :gutter="24" v-if="form.IsShow('add', 'edit')">
<j-col :span="12">
<j-form-item name="roleIdList" label="角色" class="flex"
:rules="[
{ required: form.data.username !== 'admin', message: '请选择角色' },
]"
>
<j-tree-select
v-model:value="form.data.roleIdList"
multiple
show-search
style="width: calc(100% - 40px)"
placeholder="请选择角色"
:tree-data="form.roleOptions"
:fieldNames="{ label: 'name', value: 'id', children:'children' }"
:disabled="form.data.username === 'admin'"
:filterTreeNode="
(v: string, node: any) => filterSelectNode(v, node, 'name')
"
>
<template #title="{ name }">
<div style="width: calc(100% - 10px) ">
<Ellipsis>{{ name }}</Ellipsis>
</div>
</template>
</j-tree-select>
<PermissionButton
:hasPermission="`${rolePermission}:add`"
@click="form.clickAddItem('roleIdList', 'Role')"
v-if="form.data.username !== 'admin'"
>
<AIcon type="PlusOutlined" />
</PermissionButton>
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item name="orgIdList" label="组织" class="flex">
<j-tree-select
v-model:value="form.data.orgIdList"
show-search
style="width: calc(100% - 40px)"
placeholder="请选择组织"
:tree-data="form.departmentOptions"
:fieldNames="{ label: 'name', value: 'id' }"
multiple
:filterTreeNode="
(v: string, node: any) => filterSelectNode(v, node, 'name')
"
>
<template #title="{ name }">
{{ name }}
</template>
</j-tree-select>
<PermissionButton
:hasPermission="`${deptPermission}:add`"
@click="
form.clickAddItem('orgIdList', 'Department')
"
>
<AIcon type="PlusOutlined" />
</PermissionButton>
</j-form-item>
</j-col>
</j-row>
<div class="formName" v-if="form.IsShow('add', 'edit')">账号信息</div>
<j-row :gutter="24" v-if="form.IsShow('add', 'edit')">
<j-col :span="24">
<j-form-item
name="username"
label="用户名"
@ -92,99 +199,6 @@
</j-form-item>
</j-col>
</j-row>
<j-row :gutter="24" v-if="form.IsShow('add', 'edit')">
<j-col :span="12">
<j-form-item name="roleIdList" label="角色" class="flex"
:rules="[
{ required: form.data.username !== 'admin', message: '请选择角色' },
]"
>
<j-select
v-model:value="form.data.roleIdList"
mode="multiple"
style="width: calc(100% - 40px)"
placeholder="请选择角色"
:options="_roleOptions"
:disabled="form.data.username === 'admin'"
></j-select>
<PermissionButton
:hasPermission="`${rolePermission}:add`"
@click="form.clickAddItem('roleIdList', 'Role')"
v-if="!admin"
>
<AIcon type="PlusOutlined" />
</PermissionButton>
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item name="orgIdList" label="组织" class="flex">
<j-tree-select
v-model:value="form.data.orgIdList"
show-search
style="width: calc(100% - 40px)"
placeholder="请选择组织"
:tree-data="_departmentOptions"
:fieldNames="{ label: 'name', value: 'id' }"
multiple
:filterTreeNode="
(v: string, node: any) => filterSelectNode(v, node, 'name')
"
>
<template #title="{ name }">
{{ name }}
</template>
</j-tree-select>
<PermissionButton
:hasPermission="`${deptPermission}:add`"
@click="
form.clickAddItem('orgIdList', 'Department')
"
>
<AIcon type="PlusOutlined" />
</PermissionButton>
</j-form-item>
</j-col>
</j-row>
<j-row :gutter="24" v-if="form.IsShow('add', 'edit')">
<j-col :span="12">
<j-form-item
name="telephone"
label="手机号"
:rules="[
{
pattern: /^1[3456789]\d{9}$/,
message: '请输入正确的手机号',
},
]"
>
<j-input
v-model:value="form.data.telephone"
placeholder="请输入手机号"
:maxlength="64"
/>
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
name="email"
label="邮箱"
:rules="[
{
pattern:
/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/,
message: '请输入正确的邮箱',
},
]"
>
<j-input
v-model:value="form.data.email"
placeholder="请输入邮箱"
:maxlength="64"
/>
</j-form-item>
</j-col>
</j-row>
</j-form>
</j-modal>
</template>
@ -200,6 +214,7 @@ import {
updateUser_api,
updatePassword_api,
getUser_api,
getRoleList
} from '@/api/system/user';
import { Rule } from 'ant-design-vue/es/form';
import { DefaultOptionType } from 'ant-design-vue/es/vc-tree-select/TreeSelect';
@ -207,16 +222,8 @@ import { AxiosResponse } from 'axios';
import { passwordRegEx } from '@/utils/validate';
import { filterSelectNode, onlyMessage } from '@/utils/comm';
import { uniqBy } from 'lodash-es';
import { useUserInfo } from '@/store/userInfo';
import { storeToRefs } from 'pinia';
const userInfoStore = useUserInfo()
const { userInfos } = storeToRefs(userInfoStore)
const admin = computed(() => {
return userInfos.value?.username === 'admin';
})
const deptPermission = 'system/Department';
const rolePermission = 'system/Role';
@ -287,10 +294,9 @@ const form = reactive({
},
},
roleOptions: [] as optionType[],
roleOptions: [],
departmentOptions: [] as DefaultOptionType[],
_roleOptions: [] as optionType[],
_departmentOptions: [] as DefaultOptionType[],
init: () => {
@ -314,8 +320,8 @@ const form = reactive({
(item: dictType) => item.id,
),
};
form._roleOptions = resp.result?.roleList?.map((i: any) => {
return {label: i.name, value: i.id}
form.data.roleIdList = resp.result?.roleList?.map((i: any) => {
return i.id
});
form._departmentOptions = resp.result?.orgList
nextTick(() => {
@ -353,16 +359,22 @@ const form = reactive({
return api(params);
},
getRoleList: () => {
getRoleList_api().then((resp: any) => {
form.roleOptions = resp.result.map((item: dictType) => ({
label: item.name,
value: item.id,
}));
getRoleList({ sorts: [{ name: 'createTime', order: 'desc' }] }).then((resp: any) => {
if(resp.status === 200){
form.roleOptions = dealRoleList(resp.result)
}
});
},
getDepartmentList: () => {
getDepartmentList_api().then((resp: any) => {
form.departmentOptions = resp.result;
getDepartmentList_api({
paging: false,
sorts: [{ name: 'sortIndex', order: 'asc' }],
}).then((resp: any) => {
form.departmentOptions = resp.result.sort((a: any, b: any) =>
a.sortIndex === b.sortIndex
? b.createTime - a.createTime
: a.sortIndex - b.sortIndex,
); // ;
});
},
IsShow: (...typeList: modalType[]) => typeList.includes(props.type),
@ -376,13 +388,25 @@ const form = reactive({
},
});
const _roleOptions = computed(() => {
return uniqBy([...form.roleOptions, ...form._roleOptions], 'value')
})
const _departmentOptions = computed(() => {
return uniqBy([...form.departmentOptions, ...form._departmentOptions], 'id')
const dealRoleList = (data:any) =>{
return data.map((item:any)=>{
return {
name: item.groupName,
id: item.groupId,
disabled: true,
children: item?.roles ? item.roles.map((i:any)=>{
return {
name:i.name,
id:i.id
}
}) : []
}
})
}
//
// const _departmentOptions = computed(() => {
// return uniqBy([...form.departmentOptions, ...form._departmentOptions], 'id')
// })
form.init();
@ -447,4 +471,16 @@ type optionType = {
}
}
}
.formName{
margin-bottom: 10px;
font-size: 16px;
&::before{
width: 2px;
background-color: rgb(184, 184, 184);
display: inline-block;
height: 13px;
margin-right: 3px;
content:''
}
}
</style>

View File

@ -29,6 +29,13 @@
<template #type="slotProps">
{{ slotProps.type?.name }}
</template>
<template #roleList="slotProps">
<j-ellipsis>
{{ slotProps?.roleList?.map((item)=>{
return item.name
}).join(',') }}
</j-ellipsis>
</template>
<template #status="slotProps">
<BadgeStatus
:status="slotProps.status"
@ -94,10 +101,11 @@
: '删除',
}"
:popConfirm="{
title: `确认删除`,
title:'确认删除?',
onConfirm: () =>
table.clickDel(slotProps.id),
}"
:disabled="slotProps.status"
>
<AIcon type="DeleteOutlined" />
@ -126,6 +134,7 @@ import {
getUserList_api,
changeUserStatus_api,
deleteUser_api,
queryRole_api
} from '@/api/system/user';
import { onlyMessage } from '@/utils/comm';
@ -171,6 +180,35 @@ const columns = [
},
scopedSlots: true,
},
{
title: '角色',
dataIndex: 'roleList',
key: 'roleList',
search:{
type:'select',
rename:'id$in-dimension$role',
options:() =>
new Promise((resolve)=>{
queryRole_api(
{
paging:false,
sorts: [
{ name: 'createTime', order: 'desc' },
{ name: 'id', order: 'desc' },
]
}
).then((resp:any)=>{
resolve(
resp.result.map((item: dictType) => ({
label: item.name,
value: item.id,
})),
);
})
})
},
scopedSlots: true,
},
{
title: '状态',
dataIndex: 'status',
@ -215,7 +253,7 @@ const columns = [
dataIndex: 'action',
key: 'action',
fixed: 'right',
width: 150,
width: 200,
scopedSlots: true,
},
];

View File

@ -98,6 +98,7 @@ export default defineConfig(({ mode}) => {
// target: 'http://120.77.179.54:8844', // 120测试
target: 'http://192.168.33.46:8844', // 本地开发环境
// target: 'http://192.168.32.5:8848', // 刘本地
// target: 'http://192.168.32.187:8844', // 谭本地
ws: 'ws://192.168.33.46:8844',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')

View File

@ -3738,10 +3738,10 @@ jetlinks-store@^0.0.3:
resolved "https://registry.npmjs.org/jetlinks-store/-/jetlinks-store-0.0.3.tgz"
integrity sha512-AZf/soh1hmmwjBZ00fr1emuMEydeReaI6IBTGByQYhTmK1Zd5pQAxC7WLek2snRAn/HHDgJfVz2hjditKThl6Q==
jetlinks-ui-components@^1.0.34-4:
version "1.0.34-4"
resolved "https://registry.npmjs.org/jetlinks-ui-components/-/jetlinks-ui-components-1.0.34-4.tgz#92cfc6be685988385a489f3e924383c4831f3ed5"
integrity sha512-td+RgaBC5lQxRuDsHkCg9UEzCcSy4XikufnabVGz5IxU+UmXu+PJUjz2wo9vDe8sPSctyk/5jQU+N6GBPlp8JA==
jetlinks-ui-components@^1.0.34-7:
version "1.0.34-7"
resolved "https://registry.npmjs.org/jetlinks-ui-components/-/jetlinks-ui-components-1.0.34-7.tgz#3a14e85edb4c5d11427d30f3925dc5f498478940"
integrity sha512-Rgbjig3QYP8CDVHLbco20Cf7sArYralO8yWtH5E5zylYAN2lINLUsgOlIVf9aweszZR/Ps+z/NLP0CoRQf1Xtw==
dependencies:
"@vueuse/core" "^9.12.0"
"@vueuse/router" "^9.13.0"