Merge branch 'dev' of github.com:jetlinks/jetlinks-ui-vue into dev

This commit is contained in:
wangshuaiswim 2023-03-27 17:15:01 +08:00
commit 4d41f82b75
28 changed files with 463 additions and 1081 deletions

View File

@ -1,170 +0,0 @@
<template>
<Tooltip ref="tooltipRef" placement="top" v-bind="props.tooltip">
<template v-if="props.tooltip" #title>
<slot></slot>
<slot name="tooltip"></slot>
</template>
<span
ref="triggerRef"
v-bind="triggerAttrs()"
@click="handleClickRef"
@mouseenter="
[
props.expandTrigger === 'click'
? getTooltipDisabled()
: undefined,
]
"
>
<slot></slot>
</span>
</Tooltip>
</template>
<script lang="ts" setup>
import { Tooltip, TooltipProps } from 'ant-design-vue';
import { computed, mergeProps, PropType, ref, useAttrs } from 'vue';
// define class name
const jEllipsis = 'j-ellipsis';
const jEllipsisCursorClass = 'j-ellipsis-cursor';
const jEllipsisLineClampClass = 'j-ellipsis-line-clamp';
const props = defineProps({
/** expand by */
expandTrigger: {
type: String as PropType<'click'>,
default: undefined,
},
/** multiline ellipsis */
lineClamp: {
type: [Number, String] as PropType<string | number>,
default: 1,
},
/** a-tooltip props */
tooltip: {
type: [Boolean, Object] as PropType<TooltipProps | boolean>,
default: true,
},
});
const attrs = useAttrs();
function triggerAttrs() {
return {
...mergeProps(attrs, {
class: [
jEllipsis,
props.lineClamp !== undefined
? jEllipsisLineClampClass
: undefined,
props.expandTrigger === 'click'
? jEllipsisCursorClass
: undefined,
],
style: ellipsisStyleRef.value,
}),
};
}
const expandedRef = ref(false);
const tooltipRef = ref<HTMLElement | null>(null);
const triggerRef = ref<HTMLElement | null>(null);
const ellipsisStyleRef = computed(() => {
const { lineClamp } = props;
const { value: expanded } = expandedRef;
if (lineClamp !== undefined) {
return {
textOverflow: '',
'-webkit-line-clamp': expanded ? '' : lineClamp,
};
} else {
return {
textOverflow: expanded ? '' : 'ellipsis',
'-webkit-line-clamp': '',
};
}
});
function syncCursorStyle(trigger: HTMLElement, tooltipDisabled: boolean): void {
if (props.expandTrigger === 'click' && !tooltipDisabled) {
syncTriggerClass(trigger, jEllipsisCursorClass, 'add');
} else {
syncTriggerClass(trigger, jEllipsisCursorClass, 'remove');
}
}
function getTooltipDisabled(): boolean {
let tooltipDisabled = false;
const { value: expanded } = expandedRef;
if (expanded) return true;
const { value: trigger } = triggerRef;
if (trigger) {
syncEllipsisStyle(trigger);
tooltipDisabled = trigger.scrollHeight <= trigger.offsetHeight;
syncCursorStyle(trigger, tooltipDisabled);
}
return tooltipDisabled;
}
const handleClickRef = computed(() => {
return props.expandTrigger === 'click'
? () => {
const { value: expanded } = expandedRef;
expandedRef.value = !expanded;
}
: undefined;
});
function syncEllipsisStyle(trigger: HTMLElement): void {
if (!trigger) return;
const latestStyle = ellipsisStyleRef.value;
const lineClampClass = jEllipsisLineClampClass;
if (props.lineClamp !== undefined) {
syncTriggerClass(trigger, lineClampClass, 'add');
} else {
syncTriggerClass(trigger, lineClampClass, 'remove');
}
for (const key in latestStyle) {
if ((trigger.style as any)[key] !== (latestStyle as any)[key]) {
(trigger.style as any)[key] = (latestStyle as any)[key];
}
}
}
function syncTriggerClass(
trigger: HTMLElement,
styleClass: string,
action: 'add' | 'remove',
): void {
if (action === 'add') {
if (!trigger.classList.contains(styleClass)) {
trigger.classList.add(styleClass);
}
} else {
if (trigger.classList.contains(styleClass)) {
trigger.classList.remove(styleClass);
}
}
}
</script>
<style scoped lang='less'>
.j-ellipsis {
overflow: hidden;
vertical-align: bottom;
}
.j-ellipsis-cursor {
cursor: pointer;
}
.j-ellipsis-line-clamp {
display: -webkit-box;
-webkit-box-orient: vertical;
word-break: break-all;
white-space: normal;
}
</style>

View File

@ -9,8 +9,8 @@ import NormalUpload from './NormalUpload/index.vue'
import FileFormat from './FileFormat/index.vue'
import JProUpload from './Upload/index.vue'
import { BasicLayoutPage, BlankLayoutPage } from './Layout'
import { PageContainer, AIcon } from 'jetlinks-ui-components'
import Ellipsis from './Ellipsis/index.vue'
import { PageContainer, AIcon, Ellipsis } from 'jetlinks-ui-components'
// import Ellipsis from './Ellipsis/index.vue'
import JEmpty from './Empty/index.vue'
import AMapComponent from './AMapComponent/index.vue'
import PathSimplifier from './AMapComponent/PathSimplifier.vue'

View File

@ -3,6 +3,8 @@ import { systemVersion } from '@/api/comm'
import { useMenuStore } from './menu'
import { getDetails_api } from '@/api/system/basis';
import type { ConfigInfoType } from '@/views/system/Basis/typing';
import { LocalStore } from '@/utils/comm'
import { SystemConst } from '@/utils/consts'
type SystemStateType = {
isCommunity: boolean;
@ -22,6 +24,7 @@ export const useSystem = defineStore('system', {
const resp = await systemVersion()
if (resp.success && resp.result) {
const isCommunity = resp.result.edition === 'community'
LocalStore.set(SystemConst.VERSION_CODE, resp.result.edition)
this.isCommunity = isCommunity
// 获取菜单
const menu = useMenuStore()

15
src/utils/setting.ts Normal file
View File

@ -0,0 +1,15 @@
import { isNoCommunity } from '@/utils/utils';
// 过滤网关类型
export const accessConfigTypeFilter = (data: any[], filterKey: string = 'id'): any[] => {
if (!data) return []
const filterKeys = !isNoCommunity ?
[
'mqtt-server-gateway',
'http-server-gateway',
'mqtt-client-gateway',
'tcp-server-gateway',
'plugin_gateway'
] : ['plugin_gateway']
return data.filter(item => !filterKeys.includes(item[filterKey])).map( item => ({ ...item, label: item.name, value: item.id}))
}

View File

@ -16,4 +16,12 @@ export const phoneRegEx = (value: string) => {
export const passwordRegEx = (value: string) => {
const password = new RegExp(/^\S*(?=\S{8,})(?=\S*\d)(?=\S*[A-Z])(?=\S*[a-z])\S*$/)
return password.test(value)
}
}
/**
* IP地址
*/
export const testIP = (value: string) => {
const ip =
/^([0-9]|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.([0-9]|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.([0-9]|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.([0-9]|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])$/;
return ip.test(value);
};

View File

@ -445,7 +445,7 @@ const setDevMesChartOption = (
type: 'line',
smooth: true,
symbolSize: 0, //
color: '#96ECE3',
color: '#ADC6FF',
areaStyle: {
color: {
type: 'linear',
@ -456,7 +456,7 @@ const setDevMesChartOption = (
colorStops: [
{
offset: 0,
color: '#96ECE3', // 100%
color: '#ADC6FF', // 100%
},
{
offset: 1,

View File

@ -309,6 +309,7 @@ import BadgeStatus from '@/components/BadgeStatus/index.vue';
import BatchDropdown from '@/components/BatchDropdown/index.vue';
import { BatchActionsType } from '@/components/BatchDropdown/types';
import {useRouterParams} from "@/utils/hooks/useParams";
import { accessConfigTypeFilter } from '@/utils/setting'
const instanceRef = ref<Record<string, any>>({});
const params = ref<Record<string, any>>({});
@ -417,12 +418,17 @@ const columns = [
options: () =>
new Promise((resolve) => {
getProviders().then((resp: any) => {
resolve(
resp.result.map((item: any) => ({
label: item.name,
value: `accessProvider is ${item.id}`,
})),
);
const data = resp.result || []
resolve(accessConfigTypeFilter(data).map(item => ({
...item,
value: `accessProvider is ${item.id}`
})))
// resolve(
// resp.result.map((item: any) => ({
// label: item.name,
// value: `accessProvider is ${item.id}`,
// })),
// );
});
}),
},

View File

@ -419,7 +419,7 @@ import { marked } from 'marked';
import type { TableColumnType } from 'ant-design-vue';
import { useMenuStore } from '@/store/menu';
import _ from 'lodash';
import encodeQuery from '@/utils/encodeQuery';
import { accessConfigTypeFilter } from '@/utils/setting'
const tableRef = ref();
const formRef = ref();
@ -434,7 +434,6 @@ marked.setOptions({
});
const simpleImage = ref(Empty.PRESENTED_IMAGE_SIMPLE);
const visible = ref<boolean>(false);
const listData = ref<string[]>([]);
const access = ref({});
const config = ref<any>({});
const metadata = ref<ConfigMetadata>({ properties: [] });
@ -501,35 +500,10 @@ const query = reactive({
search: {
type: 'select',
options: async () => {
return new Promise((res) => {
return new Promise((resolve) => {
getProviders().then((resp: any) => {
listData.value = [];
if (isNoCommunity) {
(resp?.result || []).map((item: any) => {
if (item.id != 'plugin_gateway') {
listData.value.push({
label: item.name,
value: item.id,
});
}
});
} else {
listData.value = (resp?.result || [])
.filter((i: any) =>
[
'mqtt-server-gateway',
'http-server-gateway',
'mqtt-client-gateway',
'tcp-server-gateway',
].includes(i.id),
)
.map((item: any) => ({
label: item.name,
value: item.id,
}));
// }
}
res(listData.value);
const data = resp.result || []
resolve(accessConfigTypeFilter(data))
});
});
},
@ -961,7 +935,8 @@ const getData = async (accessId?: string) => {
);
getProviders().then((resp) => {
if (resp.status === 200) {
dataSource.value = resp.result;
const data = resp.result || []
dataSource.value = accessConfigTypeFilter(data as any[]);
}
});
}

View File

@ -182,6 +182,7 @@ import Save from './Save/index.vue';
import { useMenuStore } from 'store/menu';
import { useRoute } from 'vue-router';
import {useRouterParams} from "@/utils/hooks/useParams";
import { accessConfigTypeFilter } from '@/utils/setting'
/**
* 表格数据
*/
@ -442,37 +443,11 @@ const query = reactive({
dataIndex: 'accessProvider',
search: {
type: 'select',
options: async () => {
return new Promise((res) => {
options: () => {
return new Promise((resolve) => {
getProviders().then((resp: any) => {
listData.value = [];
// const list = () => {
if (isNoCommunity) {
(resp?.result || []).map((item: any) => {
if (item.id != 'plugin_gateway') {
listData.value.push({
label: item.name,
value: item.id,
});
}
});
} else {
listData.value = (resp?.result || [])
.filter((i: any) =>
[
'mqtt-server-gateway',
'http-server-gateway',
'mqtt-client-gateway',
'tcp-server-gateway',
].includes(i.id),
)
.map((item: any) => ({
label: item.name,
value: item.id,
}));
// }
}
res(listData.value);
const data = resp.result || []
resolve(accessConfigTypeFilter(data))
});
});
},

View File

@ -10,7 +10,6 @@
>
<j-input
v-model:value="form.title"
:maxlength="64"
placeholder="请输入系统名称"
/>
</j-form-item>
@ -255,7 +254,7 @@
<script setup lang="ts">
import { modalState, formState, logoState } from '../data/interface';
import { getImage } from '@/utils/comm.ts';
import { Form, message } from 'jetlinks-ui-components';
import { Form, message } from 'jetlinks-ui-components';
import { FILE_UPLOAD } from '@/api/comm';
import {
getSystemPermission,
@ -276,7 +275,7 @@ import {
} from '@/api/initHome';
import { LocalStore } from '@/utils/comm';
import { TOKEN_KEY } from '@/utils/variable';
import { SystemConst } from '@/utils/consts'
import { SystemConst } from '@/utils/consts';
const formRef = ref();
const menuRef = ref();
const formBasicRef = ref();
@ -297,9 +296,16 @@ const form = ref<formState>({
basePath: `${window.location.origin}/api`,
logo: getImage('/logo.png'),
ico: getImage('/favicon.ico'),
background:getImage('/login.png')
background: getImage('/login.png'),
});
const rulesFrom = ref({
title: [
{
max: 64,
message: '最多可输入64位',
trigger: 'change',
},
],
headerTheme: [
{
required: true,
@ -322,49 +328,52 @@ const { resetFields, validate, validateInfos } = useForm(
/**
* 提交数据
*/
const saveBasicInfo = () =>{
return new Promise( async (resolve) => {
validate()
.then(async () => {
const item = [
{
scope: 'front',
properties: {
...form.value,
apikey: '',
'base-path': '',
const saveBasicInfo = () => {
return new Promise(async (resolve) => {
validate()
.then(async () => {
const item = [
{
scope: 'front',
properties: {
...form.value,
apikey: '',
'base-path': '',
},
},
},
{
scope: 'amap',
properties: {
apiKey: form.value.apikey,
{
scope: 'amap',
properties: {
apiKey: form.value.apikey,
},
},
},
{
scope: 'paths',
properties: {
'base-path': form.value.basePath,
{
scope: 'paths',
properties: {
'base-path': form.value.basePath,
},
},
},
];
const res = await save(item);
if (res.status === 200) {
resolve(true);
localStorage.setItem(SystemConst.AMAP_KEY,form.value.apikey);
const ico: any = document.querySelector('link[rel="icon"]');
if (ico !== null) {
ico.href = form.value.ico;
];
const res = await save(item);
if (res.status === 200) {
resolve(true);
localStorage.setItem(
SystemConst.AMAP_KEY,
form.value.apikey,
);
const ico: any = document.querySelector('link[rel="icon"]');
if (ico !== null) {
ico.href = form.value.ico;
}
} else {
resolve(false);
}
}else {
})
.catch(() => {
resolve(false);
}
})
.catch(() => {
resolve(false);
});
})
}
});
});
};
/**
* logo格式校验
*/
@ -397,22 +406,22 @@ const handleChangeLogo = (info: any) => {
/**
* 浏览器页签上传之前
*/
const beforeIconUpload = (file:any) => {
const beforeIconUpload = (file: any) => {
const isType = iconTypes.value.includes(file.type);
if(!isType){
if (!isType) {
message.error('请上传ico格式的图片');
return false;
}
const isSize = file.size / 1024 / 1024 < 1;
if(!isSize){
if (!isSize) {
message.error('支持1M以内的图片');
}
return isType && isSize;
}
};
/**
* 浏览器页签发生改变
*/
const changeIconUpload = (info: any) => {
const changeIconUpload = (info: any) => {
if (info.file.status === 'uploading') {
iconLoading.value = true;
}
@ -421,11 +430,11 @@ const beforeIconUpload = (file:any) => {
iconLoading.value = false;
form.value.ico = info.file.response?.result;
}
}
};
/**
* 背景图片上传之前
*/
const beforeBackUpload = (file: any) => {
const beforeBackUpload = (file: any) => {
const isType = imageTypes.value.includes(file.type);
if (!isType) {
message.error(`请上传.jpg.png.jfif.pjp.pjpeg.jpeg格式的图片`);

View File

@ -42,6 +42,8 @@ export default [
actions: ['query'],
},
],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
{
code: 'notice',
@ -182,6 +184,8 @@ export default [
],
},
],
accessSupport: { text: "支持", value: "support" },
supportDataAccess: true
},
{
code: 'notice/Template',
@ -301,6 +305,8 @@ export default [
],
},
],
accessSupport: { text: "支持", value: "support" },
supportDataAccess: true
},
],
},
@ -344,6 +350,8 @@ export default [
actions: ['find-geo'],
},
],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
{
code: 'device/Product',
@ -354,7 +362,6 @@ export default [
url: '/iot/device/Product',
icon: 'icon-chanpin',
sortIndex: 2,
accessSupport: 'support',
assetType: 'product',
showPage: ['device-product'],
permissions: [
@ -535,6 +542,8 @@ export default [
],
},
],
accessSupport: { text: "支持", value: "support" },
supportDataAccess: true
},
{
code: 'device/Instance',
@ -545,7 +554,8 @@ export default [
url: '/iot/device/Instance',
icon: 'icon-shebei',
sortIndex: 3,
accessSupport: 'support',
accessSupport: { text: "支持", value: "support" },
supportDataAccess: true,
assetType: 'device',
showPage: ['device-instance'],
permissions: [
@ -752,7 +762,8 @@ export default [
sortIndex: 4,
url: '/iot/device/Category',
icon: 'icon-chanpinfenlei',
accessSupport: 'support',
accessSupport: { text: "支持", value: "support" },
supportDataAccess: true,
assetType: 'deviceCategory',
showPage: ['device-category'],
permissions: [],
@ -832,6 +843,8 @@ export default [
actions: ['query'],
},
],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
{
code: 'link/AccessConfig',
@ -956,6 +969,8 @@ export default [
],
},
],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
{
code: 'link/Protocol',
@ -1026,6 +1041,8 @@ export default [
],
},
],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
{
code: 'Log',
@ -1048,6 +1065,8 @@ export default [
},
],
buttons: [],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
{
code: 'link/Type',
@ -1124,6 +1143,8 @@ export default [
],
},
],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
{
code: 'link/Certificate',
@ -1178,6 +1199,8 @@ export default [
],
},
],
accessSupport: { text: "支持", value: "support" },
supportDataAccess: true
},
{
code: 'media/Stream',
@ -1242,125 +1265,9 @@ export default [
],
},
],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
// {
// code: 'link/Channel',
// name: '通道配置',
// owner: 'iot',
// //parentId: '1-4',
// //id: '1-4-8',
// sortIndex: 8,
// url: '/iot/link/Channel',
// icon: 'icon-zidingyiguize',
// showPage: ['media-server'],
// permissions: [],
// children: [
// {
// code: 'link/Channel/Opcua',
// name: 'OPC UA',
// owner: 'iot',
// //parentId: '1-4-8',
// //id: '1-4-8-1',
// sortIndex: 1,
// url: '/iot/link/Channel/Opcua',
// icon: 'icon-zhilianshebei',
// showPage: ['opc-client'],
// permissions: [
// { permission: 'opc-device-bind', actions: ['query'] },
// { permission: 'opc-point', actions: ['query'] },
// { permission: 'opc-client', actions: ['query'] },
// ],
// buttons: [
// {
// id: 'view',
// name: '设备接入',
// permissions: [
// { permission: 'opc-point', actions: ['query'] },
// { permission: 'opc-device-bind', actions: ['query'] },
// { permission: 'opc-client', actions: ['query'] },
// ],
// },
// {
// id: 'action',
// name: '启/禁用',
// permissions: [
// { permission: 'opc-point', actions: ['query', 'save'] },
// { permission: 'opc-client', actions: ['query', 'save'] },
// ],
// },
// {
// id: 'update',
// name: '编辑',
// permissions: [
// { permission: 'opc-point', actions: ['query', 'save'] },
// { permission: 'opc-device-bind', actions: ['query', 'save'] },
// { permission: 'opc-client', actions: ['query', 'save'] },
// ],
// },
// {
// id: 'delete',
// name: '删除',
// permissions: [
// { permission: 'opc-point', actions: ['query', 'delete'] },
// { permission: 'opc-device-bind', actions: ['query', 'delete'] },
// { permission: 'opc-client', actions: ['query', 'delete'] },
// ],
// },
// {
// id: 'add',
// name: '新增',
// permissions: [
// { permission: 'opc-point', actions: ['query', 'save'] },
// { permission: 'opc-device-bind', actions: ['query', 'save'] },
// { permission: 'opc-client', actions: ['query', 'save'] },
// ],
// },
// ],
// },
// {
// code: 'link/Channel/Modbus',
// name: 'Modbus',
// owner: 'iot',
// //parentId: '1-4-8',
// //id: '1-4-8-2',
// sortIndex: 2,
// url: '/iot/link/Channel/Modbus',
// icon: 'icon-changjingliandong',
// showPage: ['modbus-master'],
// permissions: [
// { permission: 'modbus-point', actions: ['query', 'save', 'delete'] },
// { permission: 'modbus-master', actions: ['query', 'save', 'delete'] },
// ],
// buttons: [
// {
// id: 'update',
// name: '编辑',
// permissions: [{ permission: 'modbus-master', actions: ['query', 'save'] }],
// },
// {
// id: 'action',
// name: '启/禁用',
// permissions: [{ permission: 'modbus-master', actions: ['query', 'save'] }],
// },
// {
// id: 'view',
// name: '设备接入',
// permissions: [{ permission: 'modbus-master', actions: ['query', 'save'] }],
// },
// {
// id: 'delete',
// name: '删除',
// permissions: [{ permission: 'modbus-master', actions: ['query', 'delete'] }],
// },
// {
// id: 'add',
// name: '新增',
// permissions: [{ permission: 'modbus-master', actions: ['query', 'save'] }],
// },
// ],
// },
// ],
// },
{
code: 'device/Firmware',
name: '远程升级',
@ -1458,6 +1365,8 @@ export default [
],
},
],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
],
},
@ -1493,6 +1402,8 @@ export default [
{ permission: 'things-collector', actions: ['query'] },
],
buttons: [],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
{
code: 'DataCollect/Channel',
@ -1624,6 +1535,8 @@ export default [
],
},
],
accessSupport: { text: "支持", value: "support" },
supportDataAccess: true
},
{
code: 'DataCollect/Collector',
@ -1755,6 +1668,8 @@ export default [
],
},
],
accessSupport: { text: "支持", value: "support" },
supportDataAccess: true
},
],
},
@ -1787,6 +1702,8 @@ export default [
{ permission: 'alarm-record', actions: ['query'] },
],
buttons: [],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
{
code: 'rule-engine/Alarm/Config',
@ -1809,6 +1726,8 @@ export default [
],
},
],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
{
code: 'rule-engine/Alarm/Configuration',
@ -1920,6 +1839,8 @@ export default [
],
},
],
accessSupport: { text: "支持", value: "support" },
supportDataAccess: true
},
{
code: 'rule-engine/Alarm/Log',
@ -1970,6 +1891,8 @@ export default [
],
},
],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
],
},
@ -2061,6 +1984,8 @@ export default [
],
},
],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
{
code: 'Northbound/AliCloud',
@ -2137,6 +2062,8 @@ export default [
],
},
],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
],
},
@ -2295,6 +2222,8 @@ export default [
],
},
],
accessSupport: { text: "支持", value: "support" },
supportDataAccess: true
},
{
code: 'rule-engine/Scene',
@ -2445,6 +2374,8 @@ export default [
],
},
],
accessSupport: { text: "支持", value: "support" },
supportDataAccess: true
},
],
},
@ -2566,6 +2497,8 @@ export default [
],
},
],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
{
code: 'edge/Resource',
@ -2641,6 +2574,8 @@ export default [
],
},
],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
],
},
@ -2656,8 +2591,6 @@ export default [
url: '/media',
icon: 'icon-shipinwangguan',
sortIndex: 2,
accessSupport: 'indirect',
indirectMenus: ['1-3-3'],
permissions: [],
buttons: [],
children: [
@ -2673,6 +2606,8 @@ export default [
permissions: [],
buttons: [],
showPage: ['media-device'],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
{
code: 'media/DashBoard',
@ -2691,6 +2626,8 @@ export default [
],
buttons: [],
showPage: ['dashboard', 'media-device'],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
{
code: 'media/Device',
@ -2811,6 +2748,8 @@ export default [
],
},
],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
{
code: 'media/SplitScreen',
@ -2845,6 +2784,8 @@ export default [
],
},
],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
{
code: 'media/Cascade',
@ -2954,6 +2895,8 @@ export default [
],
},
],
accessSupport: { text: "支持", value: "support" },
supportDataAccess: true
},
],
},
@ -3007,6 +2950,8 @@ export default [
],
},
],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
{
code: 'system/User',
@ -3095,6 +3040,8 @@ export default [
],
},
],
accessSupport: { text: "支持", value: "support" },
supportDataAccess: true
},
{
code: 'system/Department',
@ -3291,6 +3238,8 @@ export default [
],
},
],
accessSupport: { text: "支持", value: "support" },
supportDataAccess: true
},
{
code: 'system/Role',
@ -3366,6 +3315,8 @@ export default [
],
},
],
accessSupport: { text: "支持", value: "support" },
supportDataAccess: true
},
{
code: 'system/Menu',
@ -3393,17 +3344,6 @@ export default [
},
],
},
// 超管才具备该权限
// {
// id: 'setting',
// name: '配置',
// permissions: [
// {
// permission: 'menu',
// actions: ['query', 'save', 'grant'],
// },
// ],
// },
{
id: 'update',
name: '编辑',
@ -3459,6 +3399,8 @@ export default [
],
},
],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
{
code: 'system/Permission',
@ -3547,44 +3489,9 @@ export default [
],
},
],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
// {
// code: 'system/Platforms',
// name: '第三方平台',
// owner: 'iot',
// //parentId: '3',
// //id: '3-7',
// sortIndex: 7,
// url: '/system/platforms',
// icon: 'icon-xitongguanli1',
// permissions: [{ permission: 'open-api', actions: ['query', 'save', 'delete'] }],
// buttons: [
// {
// id: 'empowerment',
// name: '赋权',
// permissions: [
// { permission: 'user-third-party-manager', actions: ['save'] },
// { permission: 'open-api', actions: ['save'] },
// ],
// },
// {
// id: 'password',
// name: '重置密码',
// permissions: [{ permission: 'open-api', actions: ['save'] }],
// },
// {
// id: 'delete',
// name: '删除',
// permissions: [{ permission: 'open-api', actions: ['delete'] }],
// },
// {
// id: 'update',
// name: '编辑',
// permissions: [{ permission: 'open-api', actions: ['save'] }],
// },
// { id: 'add', name: '新增', permissions: [{ permission: 'open-api', actions: ['save'] }] },
// ],
// },
{
code: 'system/Relationship',
name: '关系配置',
@ -3638,6 +3545,8 @@ export default [
],
},
],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
{
code: 'system/DataSource',
@ -3720,6 +3629,8 @@ export default [
],
},
],
accessSupport: { text: "支持", value: "support" },
supportDataAccess: true
},
{
code: 'system/Platforms/Setting',
@ -3767,6 +3678,8 @@ export default [
],
},
],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
{
code: 'system/Apply',
@ -3859,6 +3772,8 @@ export default [
],
},
],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
{
code: 'system/License',
@ -3955,6 +3870,8 @@ export default [
icon: 'icon-keshihua',
showPage: ['network-flow'],
permissions: [{ permission: 'network-flow', actions: ['query'] }],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
{
path: '5Hpl-O2m8',
@ -4115,6 +4032,8 @@ export default [
],
},
],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
{
path: '5Hpl-ZjAG',
@ -4159,6 +4078,8 @@ export default [
],
},
],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
{
path: '5Hpl-eS9h',
@ -4229,6 +4150,8 @@ export default [
],
},
],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
{
path: '5Hpl-cL34',
@ -4247,6 +4170,8 @@ export default [
actions: ['query'],
},
],
accessSupport: { text: "不支持", value: "unsupported" },
supportDataAccess: false
},
],
},

View File

@ -51,6 +51,7 @@ import Channel from '../components/Channel/index.vue';
import Edge from '../components/Edge/index.vue';
import Cloud from '../components/Cloud/index.vue';
import { getProviders, detail } from '@/api/link/accessConfig';
import { accessConfigTypeFilter } from '@/utils/setting'
const route = useRoute();
const id = route.params.id as string;
@ -140,7 +141,8 @@ const getTypeList = (result: Record<string, any>) => {
const queryProviders = async () => {
const resp: any = await getProviders();
if (resp.status === 200) {
dataSource.value = getTypeList(resp.result);
const data = resp.result || []
dataSource.value = getTypeList(accessConfigTypeFilter(data as any[]));
// dataSource.value = getTypeList(resp.result)[0].list.filter(
// (item) => item.name !== '',
// );
@ -151,7 +153,8 @@ const getProvidersData = async () => {
if (id !== ':id') {
getProviders().then((response: any) => {
if (response.status === 200) {
const list = getTypeList(response.result);
const data = response.result || []
const list = getTypeList(accessConfigTypeFilter(data as any[]));
dataSource.value = list.filter(
(item: any) =>
item.channel === 'network' ||

View File

@ -188,6 +188,7 @@ import {
} from '@/api/link/accessConfig';
import { onlyMessage } from '@/utils/comm';
import { useMenuStore } from 'store/menu';
import { accessConfigTypeFilter } from '@/utils/setting'
const menuStory = useMenuStore();
const tableRef = ref<Record<string, any>>({});
@ -318,12 +319,7 @@ const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
const getProvidersList = async () => {
const res: any = await getProviders();
providersList.value = res.result;
providersOptions.value = (res?.result || [])
?.map((item: any) => ({
label: item.name,
value: item.id,
}))
.filter((item: any) => item.value !== 'plugin_gateway'); // todo
providersOptions.value = accessConfigTypeFilter(res.result || [])
};
getProvidersList();

View File

@ -264,7 +264,7 @@ const getDashBoard = () => {
name: '告警数',
data: fifteenData.map((item) => item.value),
type: 'line',
color: '#2F54EB',
color: '#FF595E',
smooth: true,
symbolSize: 0,
areaStyle: {
@ -277,7 +277,7 @@ const getDashBoard = () => {
colorStops: [
{
offset: 0,
color: '#2F54EB', // 100%
color: '#FF595E', // 100%
},
{
offset: 1,
@ -458,7 +458,7 @@ const selectChange = () => {
data: sData.reverse(),
type: 'line',
smooth: true,
color: '#685DEB',
color: '#ADC6FF',
areaStyle: {
color: {
type: 'linear',
@ -469,7 +469,7 @@ const selectChange = () => {
colorStops: [
{
offset: 0,
color: '#685DEB', // 100%
color: '#ADC6FF', // 100%
},
{
offset: 1,

View File

@ -62,6 +62,7 @@ import { queryTree } from '@/api/device/category'
import { getTreeData_api } from '@/api/system/department'
import { isNoCommunity } from '@/utils/utils'
import { getImage } from '@/utils/comm'
import { accessConfigTypeFilter } from '@/utils/setting'
type Emit = {
(e: 'update:rowKey', data: string): void
@ -115,23 +116,7 @@ const columns = [
search: {
type: 'select',
options: () => getProviders().then((resp: any) => {
if (isNoCommunity) {
return (resp?.result || []).map((item: any) => ({
label: item.name,
value: item.id
}))
} else {
return (resp?.result || []).filter((item: any) => [
'mqtt-server-gateway',
'http-server-gateway',
'mqtt-client-gateway',
'tcp-server-gateway',
].includes(item.id))
.map((item: any) => ({
label: item.name,
value: item.id,
}))
}
return accessConfigTypeFilter(resp.result || [])
})
}
},

View File

@ -74,6 +74,7 @@ import { queryTree } from '@/api/device/category';
import { getTreeData_api } from '@/api/system/department';
import { isNoCommunity } from '@/utils/utils';
import { getImage } from '@/utils/comm';
import { accessConfigTypeFilter } from '@/utils/setting'
type Emit = {
(e: 'update:rowKey', data: string): void;
@ -127,26 +128,8 @@ const columns = [
type: 'select',
options: () =>
getProviders().then((resp: any) => {
if (isNoCommunity) {
return (resp?.result || []).map((item: any) => ({
label: item.name,
value: item.id,
}));
} else {
return (resp?.result || [])
.filter((item: any) =>
[
'mqtt-server-gateway',
'http-server-gateway',
'mqtt-client-gateway',
'tcp-server-gateway',
].includes(item.id),
)
.map((item: any) => ({
label: item.name,
value: item.id,
}));
}
const data = resp.result || []
return accessConfigTypeFilter(data)
}),
},
},

View File

@ -58,12 +58,14 @@ const checkDeviceDelete = async () => {
formTouchOff()
return
}
const deviceList = item!.selectorValues?.map(item => item.value) || []
const deviceResp = await deviceQuery({ terms: [{ terms: [{ column: 'id', termType: 'in', value: deviceList.toString() }]}]})
if (deviceResp.success && (deviceResp.result as any)?.total < (item!.selectorValues?.length || 0)) { //
_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].device!.selectorValues = undefined
formTouchOff()
return
if (item?.selector === 'fixed') {
const deviceList = item!.selectorValues?.map(item => item.value) || []
const deviceResp = await deviceQuery({ terms: [{ terms: [{ column: 'id', termType: 'in', value: deviceList.toString() }]}]})
if (deviceResp.success && (deviceResp.result as any)?.total < (item!.selectorValues?.length || 0)) { //
_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].device!.selectorValues = undefined
formTouchOff()
return
}
}
}

View File

@ -107,9 +107,11 @@ const onKeys: string[] = EventSubscribeKeys({
action: props.actionName
})
EventEmitter.subscribe(onKeys, (d: any) => {
const handleRequest = () => {
columnRequest()
})
}
EventEmitter.subscribe(onKeys, handleRequest)
provide('filter-params', columnOptions)
@ -241,6 +243,10 @@ nextTick(() => {
columnRequest()
})
onUnmounted(() => {
EventEmitter.unSubscribe(onKeys, handleRequest)
})
</script>
<style scoped>

View File

@ -572,44 +572,6 @@ const rules = [{
}
}]
const formTouchOff = () => {
console.log('formTouchOff')
formItemContext.onFieldChange()
}
/**
* 校验当前执行动作的设备或者产品是否删除
*/
const checkDeviceDelete = async () => {
const item = _data.value.branches![props.branchesName].then[props.thenName].actions[props.name].device
const proResp = await queryProductList({ terms: [{ terms: [{ column: 'id', termType: 'eq', value: item!.productId }]}]})
if (proResp.success && (proResp.result as any)?.total === 0) { //
_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].device!.productId = undefined
formTouchOff()
return
}
const deviceList = item!.selectorValues?.map(item => item.value) || []
const deviceResp = await deviceQuery({ terms: [{ terms: [{ column: 'id', termType: 'in', value: deviceList.toString() }]}]})
if (deviceResp.success && (deviceResp.result as any)?.total < (item!.selectorValues?.length || 0)) { //
_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].device!.selectorValues = undefined
formTouchOff()
return
}
}
/**
* 校验当前执行动作的通知配置消息模板是否删除
*/
const checkNoticeDelete = () => {
}
nextTick(() => {
if (_data.value.branches![props.branchesName].then[props.thenName].actions[props.name]?.executor === 'device') {
checkDeviceDelete()
}
})
</script>
<style lang="less" scoped>

View File

@ -221,7 +221,7 @@ const showDouble = computed(() => {
} else {
metricOption.value = []
}
return isRange && !isMetric
return isRange && !isMetric.value
})
const mouseover = () => {

View File

@ -1,3 +1,5 @@
import { BranchesThen } from '@/views/rule-engine/Scene/typings'
export const ContextKey = 'columnOptions'
export const handleParamsData = (data: any[], key: string = 'column'): any[] => {
@ -12,7 +14,7 @@ export const handleParamsData = (data: any[], key: string = 'column'): any[] =>
}
export const thenRules = [{
validator(_: string, value: any) {
validator(_: string, value: BranchesThen[]) {
if (!value || (value && !value.length) || !value.some(item => item.actions && item.actions.length)) {
return Promise.reject('至少配置一个执行动作')
}

View File

@ -540,7 +540,7 @@
:rules="[
{
required: true,
message: '该字段是必填字段',
message: '请输入用户名',
},
{
max: 64,
@ -567,7 +567,7 @@
:rules="[
{
required: true,
message: '该字段是必填字段',
message: '请输入密码',
},
{
max: 64,
@ -824,7 +824,15 @@
placeholder="请输入redirectUrl"
/>
</j-form-item>
<j-form-item label="IP白名单">
<j-form-item
label="IP白名单"
:name="['apiServer', 'ipWhiteList']"
:rules="[
{
validator: validateIP,
},
]"
>
<j-textarea
v-model:value="form.data.apiServer.ipWhiteList"
placeholder="请输入IP白名单多个地址回车分隔不填默认均可访问"
@ -1197,12 +1205,7 @@
<j-form-item
v-if="form.data.provider !== 'dingtalk-ent-app'"
class="resetLabel"
:name="[
'sso',
'configuration',
'oauth2',
'clientId',
]"
:name="['sso', 'configuration', 'appId']"
:rules="[
{
required: true,
@ -1223,7 +1226,7 @@
</template>
<j-input
v-model:value="
form.data.sso.configuration.oauth2.clientId
form.data.sso.configuration.appId
"
placeholder="请输入appId"
/>
@ -1232,12 +1235,7 @@
<j-form-item
v-if="form.data.provider !== 'wechat-webapp'"
class="resetLabel"
:name="[
'sso',
'configuration',
'oauth2',
'clientSecret',
]"
:name="['sso', 'configuration', 'appKey']"
:rules="[
{
required: true,
@ -1258,8 +1256,7 @@
</template>
<j-input
v-model:value="
form.data.sso.configuration.oauth2
.clientSecret
form.data.sso.configuration.appKey
"
placeholder="请输入appKey"
/>
@ -1443,6 +1440,7 @@
<script setup lang="ts">
import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable';
import { LocalStore, filterSelectNode } from '@/utils/comm';
import { testIP } from '@/utils/validate';
import {
getDepartmentList_api,
@ -1465,6 +1463,7 @@ import {
import { randomString } from '@/utils/utils';
import { cloneDeep, difference } from 'lodash';
import { useMenuStore } from '@/store/menu';
import { Rule } from 'ant-design-vue/lib/form';
const emit = defineEmits(['changeApplyType']);
const routeQuery = useRoute().query;
@ -1673,6 +1672,10 @@ function init() {
watch(
() => form.data.provider,
(n) => {
form.data.page.baseUrl = '';
form.data.page.parameters = [];
form.data.apiClient.baseUrl = '';
form.data.apiClient.parameters = [];
emit('changeApplyType', n);
if (routeQuery.id) return;
if (n === 'wechat-webapp' || n === 'dingtalk-ent-app') {
@ -1697,19 +1700,21 @@ function init() {
function getInfo(id: string) {
getAppInfo_api(id).then((resp: any) => {
// headersparameters, keylabel
resp.result.apiClient.headers = resp.result.apiClient.headers.map(
(m: any) => ({
...m,
label: m.key,
}),
);
resp.result.apiClient.parameters = resp.result.apiClient.parameters.map(
(m: any) => ({
...m,
label: m.key,
}),
);
if (resp.result.apiClient) {
resp.result.apiClient.headers = resp.result.apiClient.headers.map(
(m: any) => ({
...m,
label: m.key,
}),
);
resp.result.apiClient.parameters =
resp.result.apiClient.parameters.map((m: any) => ({
...m,
label: m.key,
}));
}
form.data = {
...initForm, // , . : bug#10892
...resp.result,
integrationModes: resp.result.integrationModes.map(
(item: any) => item.value,
@ -1749,7 +1754,6 @@ function clickAddItem(data: string[], target: string) {
}
//
function clickSave() {
console.log('headers: ', form.data.apiClient.headers);
formRef.value?.validate().then(() => {
const params = cloneDeep(form.data);
//
@ -1813,7 +1817,7 @@ function clickSave() {
if (resp.status === 200) {
const isPage = params.integrationModes.includes('page');
if (isPage) {
form.data = params;
// form.data = params;
dialog.selectId = routeQuery.id || resp.result.id;
dialog.selectProvider = form.data.provider;
dialog.visible = true;
@ -1842,7 +1846,7 @@ function getErrorNum(
}
}
const imageTypes = ref(['image/jpg', 'image/png']);
const imageTypes = ref(['image/jpg', 'image/png', 'image/jpeg']);
const beforeLogoUpload = (file: any) => {
const isType: any = imageTypes.value.includes(file.type);
if (!isType) {
@ -1863,7 +1867,6 @@ function changeBackUpload(info: UploadChangeParam<UploadFile<any>>) {
form.uploadLoading = false;
form.data.sso.configuration.oauth2.logoUrl = info.file.response?.result;
} else if (info.file.status === 'error') {
console.log(info.file);
form.uploadLoading = false;
message.error('logo上传失败请稍后再试');
}
@ -1878,6 +1881,23 @@ function clearNullProp(obj: object) {
}
}
}
/**
* 验证IP合法性
* @param _rule
* @param value
*/
const validateIP = (_rule: Rule, value: string) => {
const ipList = value?.split(/[\n,]/g).filter((i: string) => i && i.trim());
const errorIPList = ipList.filter(
(f: string) => !testIP(f.replace(/\s*/g, '')),
);
return new Promise((resolve, reject) => {
!errorIPList.length
? resolve('')
: reject(`[${errorIPList}]不是正确的IP地址`);
});
};
</script>
<style lang="less" scoped>

View File

@ -283,7 +283,7 @@ const queryParams = ref({});
const tableRef = ref();
const table = {
refresh: () => {
tableRef.value.reload();
tableRef.value.reload(queryParams.value);
},
toSave: (id?: string, view = false) => {
if (id) menuStory.jumpPage('system/Apply/Save', {}, { id, view });

View File

@ -1,388 +0,0 @@
<template>
<div class="setting-container">
<h5 class="top">
<AIcon type="ExclamationCircleOutlined" />
<span style="padding-left: 12px"
>基于系统源代码中的菜单数据配置系统菜单</span
>
</h5>
<div class="transfer">
<!-- 左侧树 -->
<div class="basic-tree left">
<div class="title">
<div class="title-label">
<span>源菜单</span>
<j-tooltip>
<template #title
>根据系统代码自动读取的菜单数据</template
>
<AIcon type="QuestionCircleOutlined" />
</j-tooltip>
</div>
<div class="title-func">
<j-button
type="primary"
@click="dialogShow = true"
ghost
>一键拷贝</j-button
>
</div>
</div>
<div class="content">
<j-input
v-model:value="transfer.data.leftSearchValue"
style="margin-bottom: 8px"
placeholder="请输入菜单名称"
@change="transfer.changeTreeLeft"
>
<template #prefix>
<AIcon
type="SearchOutlined"
style="color: #b3b3b3"
/>
</template>
</j-input>
<j-tree
v-if="transfer.data.leftTreeData.length !== 0"
:tree-data="transfer.data.leftTreeData"
draggable
defaultExpandAll
:height="550"
>
<template #title="row">
<div>{{ row.name }}</div>
</template>
</j-tree>
</div>
</div>
<div class="center">
<j-button>请拖动至右侧</j-button>
</div>
<!-- 右侧树 -->
<div class="basic-tree right">
<div class="title">
<div class="title-label">
<span>系统菜单</span>
<j-tooltip>
<template #title
>菜单管理页面配置的菜单数据</template
>
<AIcon type="QuestionCircleOutlined" />
</j-tooltip>
</div>
</div>
<div class="content">
<j-input
v-model:value="transfer.data.rightSearchValue"
style="margin-bottom: 8px"
placeholder="请输入菜单名称"
@change="transfer.changeTreeRight"
>
<template #prefix>
<AIcon
type="SearchOutlined"
style="color: #b3b3b3"
/>
</template>
</j-input>
<j-tree
v-if="transfer.data.rightTreeData.length !== 0"
draggable
defaultExpandAll
:tree-data="transfer.data.rightTreeData"
@drop="transfer.onRightDrop"
:height="550"
>
<template #title="row">
<div
style="
display: flex;
justify-content: space-between;
"
>
<span>{{ row.name }}</span>
<j-popconfirm
title="确认删除?"
ok-text="确定"
cancel-text="取消"
@confirm="transfer.removeItem(row)"
placement="topRight"
>
<j-tooltip>
<template #title>删除</template>
<j-button
style="
padding: 0;
margin-right: 12px;
"
type="link"
>
<AIcon type="CloseOutlined" />
</j-button>
</j-tooltip>
</j-popconfirm>
</div>
</template>
</j-tree>
</div>
</div>
</div>
<j-button type="primary" style="margin-top: 24px">保存</j-button>
<div class="dialogs">
<j-modal
v-model:visible="dialogShow"
title="一键拷贝"
@ok="transfer.copy"
cancelText="取消"
okText="确认"
>
<p>源数据将会覆盖当前的系统菜单数据确定要一键拷贝吗</p>
</j-modal>
</div>
</div>
</template>
<script setup lang="ts" name="MenuSetting">
import { getMenuTree_api } from '@/api/system/menu';
import { getSystemPermission as getSystemPermission_api } from '@/api/initHome';
import { filterMenu, getKeys, loop } from './utils';
import BaseTreeData from './baseMenu';
import type {
AntTreeNodeDropEvent,
TreeDataItem,
TreeProps,
} from 'ant-design-vue/es/tree';
import { treeFilter } from '@/utils/tree';
import { cloneDeep } from 'lodash';
const transfer = {
data: reactive({
//
leftSearchValue: '',
leftTreeData: [] as any[],
//
rightSearchValue: '',
rightTreeData: [] as any[],
}),
leftSourceData: [] as any[],
rightSourceData: [] as any[],
init: () => {
//
const sourceMenu = getSystemPermission_api().then((resp: any) => {
const newTree = filterMenu(
resp.result.map((item: any) => JSON.parse(item).id),
BaseTreeData,
);
transfer.leftSourceData = [...newTree];
transfer.data.leftTreeData = newTree;
});
const params = {
paging: false,
terms: [
{
terms: [
{
column: 'owner',
termType: 'eq',
value: 'iot',
},
{
column: 'owner',
termType: 'isnull',
value: '1',
type: 'or',
},
],
},
],
};
//
const systemMenu = getMenuTree_api(params).then((resp: any) => {
transfer.data.rightTreeData = resp.result;
transfer.rightSourceData = [...resp.result];
});
//
Promise.all([sourceMenu, systemMenu]).then(() => transfer.updateTree());
},
copy: () => {
transfer.data.rightTreeData = [...toRaw(transfer.data.leftTreeData)];
dialogShow.value = false;
},
removeItem: (row: any) => {
loop(
transfer.data.rightTreeData,
row.id,
(item: TreeDataItem, index: number, arr: TreeProps['treeData']) => {
arr?.splice(index, 1);
},
);
transfer.updateTree();
},
onRightDrop: (info: AntTreeNodeDropEvent) => {
const dropKey = info.node.id;
const dragKey = info.dragNode.id;
const dropPos = (info.node.pos && info.node.pos.split('-')) || [];
const dropPosition =
info.dropPosition - Number(dropPos[dropPos.length - 1]);
const data = [...transfer.data.rightTreeData];
let dragObj: TreeDataItem = { key: '' };
loop(
data,
dragKey,
(item: TreeDataItem, index: number, arr: TreeProps['treeData']) => {
arr?.splice(index, 1);
dragObj = item;
},
);
if (!info.dropToGap) {
// Drop on the content
loop(data, dropKey, (item: TreeDataItem) => {
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: TreeDataItem) => {
item.children = item.children || [];
// where to insert
item.children.unshift(dragObj);
});
} else {
let ar: TreeProps['treeData'] = [];
let i = 0;
loop(
data,
dropKey,
(
_item: TreeDataItem,
index: number,
arr: TreeProps['treeData'],
) => {
ar = arr;
i = index;
},
);
if (dropPosition === -1) {
ar.splice(i, 0, dragObj);
} else {
ar.splice(i + 1, 0, dragObj);
}
}
transfer.data.rightTreeData = data;
},
updateTree: () => {
console.log(getKeys(transfer.data.rightTreeData));
},
changeTreeLeft: (val: any) => {
const value = val.target.value;
if (value) {
transfer.data.leftTreeData = treeFilter(
transfer.leftSourceData,
value,
'name',
);
} else {
transfer.data.leftTreeData = cloneDeep(transfer.rightSourceData);
}
},
changeTreeRight: (val: any) => {
const value = val.target.value;
if (value) {
transfer.data.rightTreeData = treeFilter(
transfer.rightSourceData,
value,
'name',
);
} else {
transfer.data.rightTreeData = cloneDeep(transfer.rightSourceData);
}
},
};
transfer.init();
const dialogShow = ref<boolean>(false);
</script>
<style lang="less" scoped>
.setting-container {
padding: 24px;
margin: 24px;
background-color: #fff;
.top {
font-size: inherit;
margin-bottom: 24px;
padding: 10px 24px;
color: rgba(0, 0, 0, 0.55);
background-color: #f6f6f6;
}
.transfer {
display: flex;
justify-content: space-between;
.basic-tree {
flex: 1;
.title {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
padding: 14px 16px;
font-weight: 400;
font-size: 16px;
background-color: #f3f4f4;
.title-label {
span {
padding-right: 12px;
}
}
}
.content {
padding: 12px;
border: 1px solid #e0e0e0;
border-radius: 4px;
.ant-input-affix-wrapper {
width: 75%;
}
:deep(.ant-tree) {
height: 550px;
overflow: auto;
.ant-tree-list-holder-inner {
> .ant-tree-treenode {
width: 100%;
}
.ant-tree-node-content-wrapper {
width: 100%;
}
}
}
}
}
.center {
flex-basis: 120px;
display: flex;
align-items: center;
margin: 0 24px;
}
}
}
</style>

View File

@ -3,84 +3,84 @@
<j-card>
<div class="top">
<AIcon style="padding: 12px" type="ExclamationCircleOutlined" />
<span>基于系统源代码中的菜单数据配置系统菜单</span>
<span
>单击可切换菜单未选中/选中状态操作父级菜单时对应子菜单状态将默认与其同步可以单独操作调整支持拖拽菜单调整展示顺序
</span>
</div>
<div class="content">
<div class="title">
系统菜单
<j-tooltip>
<template #title
>根据系统代码自动读取的菜单数据</template
<j-card title="菜单配置" style="width: 80%">
<div class="tree">
<j-tree
v-if="treeData.length !== 0"
show-line
defaultExpandAll
multiple
draggable
:tree-data="treeData"
:height="520"
@select="onSelect"
:selectedKeys="selectedKeys"
@drop="onDrop"
>
<AIcon type="QuestionCircleOutlined" />
</j-tooltip>
</div>
<div class="tree">
<j-input
v-model="filterText"
placeholder="请输入"
@change="change"
style="margin-bottom: 12px"
/>
<j-tree
v-if="treeData.length !== 0"
show-line
defaultExpandAll
multiple
draggable
:tree-data="treeData"
:height="500"
@select="onSelect"
:selectedKeys="selectedKeys"
@drop="onDrop"
>
<template #title="row">
<div class="tree-content">
<div class="tree-content-title">
<AIcon type="HolderOutlined" />
<div style="margin-left: 8px">
{{ row.name }}
<template #title="row">
<div class="tree-content">
<div class="tree-content-title">
<AIcon type="HolderOutlined" />
<div style="margin-left: 8px">
{{ row.name }}
</div>
</div>
</div>
</div>
</template>
</j-tree>
</div>
<j-button type="primary">保存</j-button>
</template>
</j-tree>
</div>
</j-card>
</div>
<j-button
type="primary"
@click="() => (visible = true)"
style="margin-left: 10%"
>保存</j-button
>
</j-card>
<j-modal
:visible="visible"
@ok="handleOk"
@cancel="handleCancel"
modalType="message"
:confirmLoading="loading"
>
保存后当前系统菜单数据将被覆盖确认操作
</j-modal>
</page-container>
</template>
<script setup lang="ts" name="MenuSetting">
import { getMenuTree_api } from '@/api/system/menu';
import { getSystemPermission as getSystemPermission_api } from '@/api/initHome';
import {
getSystemPermission as getSystemPermission_api,
updateMenus,
} from '@/api/initHome';
import {
filterMenu,
mergeMapToArr,
developArrToMap,
drop,
select,
getMaxDepth,
} from './utils';
import BaseMenu from './baseMenu';
import type { AntTreeNodeDropEvent } from 'ant-design-vue/es/tree';
import { treeFilter } from '@/utils/tree';
import { cloneDeep } from 'lodash';
import { onlyMessage } from '@/utils/comm';
const selectedKeys: any = ref([]);
const treeData = ref<any>([]);
const filterText = ref('');
const systemMenu: any = ref([]);
const baseMenu: any = ref([]);
const AllMenu = ref([]);
const onSelect = (selecteds: Array<string>, e: any) => {
selectedKeys.value = select(selecteds, e);
};
const onDrop = (info: AntTreeNodeDropEvent) => {
treeData.value = drop(info, treeData.value);
};
const visible = ref(false);
const loading = ref(false);
const params = {
paging: false,
@ -103,8 +103,33 @@ const params = {
],
};
const change = (val: any) => {
treeData.value = treeFilter(AllMenu.value, val.target.value, 'name');
const handleOk = async () => {
loading.value = true;
const res = await updateMenus(treeData.value);
if (res.status === 200) {
onlyMessage('操作成功', 'success');
}
loading.value = false;
visible.value = false;
};
const handleCancel = () => {
visible.value = false;
};
const onSelect = (selecteds: Array<string>, e: any) => {
selectedKeys.value = select(selecteds, e);
};
const onDrop = (info: AntTreeNodeDropEvent) => {
const TreeData = cloneDeep(treeData.value);
const newTreeData = drop(info, treeData.value);
const maxDepth = getMaxDepth(newTreeData);
if (maxDepth > 3) {
onlyMessage('仅支持3级菜单', 'error');
treeData.value = TreeData;
} else {
treeData.value = newTreeData;
}
};
onMounted(() => {
@ -116,6 +141,7 @@ onMounted(() => {
getMenuTree_api(params).then((resp: any) => {
if (resp.status == 200) {
systemMenu.value = resp.result;
//
const baseMenuData = developArrToMap(baseMenu.value);
const systemMenuData = developArrToMap(systemMenu.value, true);
selectedKeys.value = systemMenuData.checkedKeys;
@ -136,19 +162,14 @@ onMounted(() => {
font-size: 14px;
line-height: 20px;
color: rgba(0, 0, 0, 0.55);
margin-bottom: 12px;
}
.content {
width: 50%;
margin-top: 24px;
.title {
font-style: normal;
font-weight: 800;
font-size: 16px;
height: 48px;
padding: 12px;
background: #f3f4f4;
color: rgba(0, 0, 0, 0.8);
}
width: 100%;
margin: 12px 0;
display: flex;
justify-content: center;
// flex-direction: row;
:deep(.ant-tree) {
.ant-tree-switcher {
display: flex;
@ -165,12 +186,16 @@ onMounted(() => {
width: 100%;
}
}
:deep(.ant-card-body) {
padding: 0;
}
.tree {
height: 580px;
// flex: 1;
height: 540px;
margin: 16px 0;
padding: 12px;
background: #ffffff;
border: 1px solid #e0e0e0;
border-radius: 4px;
overflow: hidden;
width: 100%;
@ -178,6 +203,8 @@ onMounted(() => {
&-content {
display: flex;
justify-content: space-between;
margin: 5px 0;
&-title {
flex: 1;
font-weight: 800;

View File

@ -61,20 +61,20 @@ export const developArrToMap = (Menu: any, checked = false) => {
const rootSet = new Set();
const arrMap = new Map();
const checkedKeys: any = [];
const getMap = (arr: any, parentCode = 'root', preKey = '0') => {
arr.forEach((item: any, index: number) => {
const key = preKey + `-${index}`; //初始化key
const getMap = (arr: any, parentCode = 'root') => {
arr.forEach((item: any) => {
item.title = item.code;
item.key = key;
if (checked) {
checkedKeys.push(key);
item.key = item.code;
if (checked || item?.checked) {
item.checked = item?.checked || checked;
checkedKeys.push(item.code);
}
arrMap.set(item.code, item);
if (parentCode === 'root') {
rootSet.add(item.code); //处理根菜单
}
if (item?.children) {
getMap(item?.children, item.code, key);
getMap(item?.children, item.code);
}
});
};
@ -91,17 +91,16 @@ export const developArrToMap = (Menu: any, checked = false) => {
export const select = (selecteds: Array<string>, e: any) => {
const { node } = e;
const childKeys: Array<string> = [];
const getChildKeys = (data: any, preKey = '0') => {
data.forEach((item: any, index: number) => {
const checkedKey = preKey + `-${index}`;
childKeys.push(checkedKey);
const getChildKeys = (data: any) => {
data.forEach((item: any) => {
childKeys.push(item.code);
if (item?.children) {
getChildKeys(item?.children, checkedKey);
getChildKeys(item?.children);
}
});
};
if (node?.children) {
getChildKeys(node.children, node.key);
getChildKeys(node.children);
}
const Keys = new Set(selecteds);
@ -185,4 +184,28 @@ export const drop = (info: AntTreeNodeDropEvent, treeData: any) => {
}
}
return data;
};
};
// 查找最深层级
export const getMaxDepth = (data: any) => {
let maxDepth = 0;
data.forEach((node: any) => {
const depth = getNodeDepth(node);
if (depth > maxDepth) {
maxDepth = depth;
}
});
return maxDepth;
};
export const getNodeDepth = (node: any) => {
let depth = 1;
if (node.children) {
node.children.forEach((child: any) => {
const childDepth = getNodeDepth(child) + 1;
if (childDepth > depth) {
depth = childDepth;
}
});
}
return depth;
};

View File

@ -61,24 +61,33 @@ const columns = [
},
];
const rowSelection = {
onSelect: (record: any) => {
const targetId = record.id;
let newKeys = [...props.selectedRowKeys];
// onSelect: (record: any) => {
// const targetId = record.id;
// let newKeys = [...props.selectedRowKeys];
if (props.selectedRowKeys.includes(targetId)) {
newKeys = newKeys.filter((id) => id !== targetId);
} else newKeys.push(targetId);
// if (props.selectedRowKeys.includes(targetId)) {
// newKeys = newKeys.filter((id) => id !== targetId);
// } else newKeys.push(targetId);
emits('update:selectedRowKeys', newKeys);
if (props.mode === 'appManger') {
emits('update:changedApis', {
...props.changedApis,
[record.id]: record,
});
}
},
// emits('update:selectedRowKeys', newKeys);
// if (props.mode === 'appManger') {
// emits('update:changedApis', {
// ...props.changedApis,
// [record.id]: record,
// });
// }
// },
onChange: (keys: string[]) => {
rowSelection.selectedRowKeys.value = keys;
// rowSelection.selectedRowKeys.value = keys;
emits('update:selectedRowKeys', keys);
keys.forEach((key: string) => {
if (props.mode === 'appManger') {
emits('update:changedApis', {
...props.changedApis,
[key]: props.tableData.find((f: any) => f.id === key),
});
}
});
},
selectedRowKeys: ref<string[]>([]),
};
@ -87,7 +96,9 @@ const save = async () => {
// id
const currenTableKeys = props.tableData.map((m: any) => m.id);
// id
const currentSelectedKeys = rowSelection.selectedRowKeys.value;
const currentSelectedKeys = rowSelection.selectedRowKeys.value.filter(
(key: string) => currenTableKeys.includes(key),
);
// , id
const oldSelectedKeys = currenTableKeys.filter((key) =>
props.sourceKeys.includes(key),
@ -124,11 +135,15 @@ const save = async () => {
} else if (props.mode === 'appManger') {
const removeItems = removeKeys.map((key) => ({
id: key,
permissions: props.changedApis[key]?.security,
// permissions: props.changedApis[key]?.security,
permissions: props.tableData.find((f: any) => f.id === key)
?.security,
}));
const addItems = addKeys.map((key) => ({
id: key,
permissions: props.changedApis[key]?.security,
// permissions: props.changedApis[key]?.security,
permissions: props.tableData.find((f: any) => f.id === key)
?.security,
}));
Promise.all([
updateOperations_api(code, '_delete', { operations: removeItems }),

View File

@ -3700,8 +3700,8 @@ jetlinks-store@^0.0.3:
jetlinks-ui-components@^1.0.5:
version "1.0.5"
resolved "http://47.108.170.157:9013/jetlinks-ui-components/-/jetlinks-ui-components-1.0.5.tgz#593185f6313895485b59e9a79bc920c63374d84d"
integrity sha512-dkSOmatSPLHlV91YdTcHWO2wfwriUIZKEuLd5bJF2GsO9SvDMyJ2YJ4n/3fkklOoL5albhY37iX2Ot3A+7QYwA==
resolved "http://47.108.170.157:9013/jetlinks-ui-components/-/jetlinks-ui-components-1.0.5.tgz#27312836506c4833dcaaef075e1d3c694d75ae4d"
integrity sha512-oum7zipoDUVkm/tPd7yu+mw9mR5NmfBcvBf49ebf55s+nz4zyArFOITzldQJ3Wx6BwaUUH/BiDwskHH+KgBVyg==
dependencies:
"@vueuse/core" "^9.12.0"
ant-design-vue "^3.2.15"