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

This commit is contained in:
wangshuaiswim 2023-03-24 09:52:06 +08:00
commit 0c4c93c386
76 changed files with 990 additions and 516 deletions

View File

@ -48,7 +48,7 @@ export const category = (data: any) => server.post('/device/category/_tree', dat
/** /**
* *
*/ */
export const getProviders = () => server.get('/gateway/device/providers') export const getProviders = (terms?:any) => server.get('/gateway/device/providers',terms)
/** /**
* *

View File

@ -10,4 +10,7 @@ export const addRelation_api = (data: object) => server.post(`/relation`, data);
// 保存关系 // 保存关系
export const editRelation_api = (data: object) => server.patch(`/relation`, data); export const editRelation_api = (data: object) => server.patch(`/relation`, data);
// 删除关系 // 删除关系
export const delRelation_api = (id: string) => server.remove(`/relation/${id}`); export const delRelation_api = (id: string) => server.remove(`/relation/${id}`);
// 验证标识唯一性
export const validateField = (params: any) => server.get<any>(`/relation/_validate`, params);

View File

@ -75,11 +75,9 @@ const handleMenuClick = (e: any) => {
if(!(val?.popConfirm || val?.onClick)){ if(!(val?.popConfirm || val?.onClick)){
emits('update:isCheck', true); emits('update:isCheck', true);
emits('change', true); emits('change', true);
}
if (val?.popConfirm) {
visible.value = false;
} else {
visible.value = true; visible.value = true;
} else {
visible.value = false;
} }
_item.value = (val || {}) as any; _item.value = (val || {}) as any;
}; };

View File

@ -15,7 +15,7 @@
> >
{{ slotProps.route.breadcrumbName }} {{ slotProps.route.breadcrumbName }}
</a> </a>
<span v-else style='cursor: pointer' >{{ slotProps.route.breadcrumbName }}</span> <span v-else style='cursor: default' >{{ slotProps.route.breadcrumbName }}</span>
</template> </template>
<template #rightContentRender> <template #rightContentRender>
<div class="right-content"> <div class="right-content">

26
src/store/department.ts Normal file
View File

@ -0,0 +1,26 @@
import { defineStore } from "pinia";
type DepartmentStateType = {
productId: string;
optType: string | undefined;
}
export const useDepartmentStore = defineStore({
id: 'department',
state: (): DepartmentStateType => ({
productId: '',
// 设备资产分配弹窗操作类型:
// 1. optType === 'handle': 手动点击资产分配按钮;
// 2. optType === ': 产品资产分配后, 自动弹出设备资产分配
optType: ''
}),
actions: {
setProductId(value: string) {
this.productId = value
},
setType(value: string | undefined) {
this.optType = value
}
}
})

View File

@ -32,14 +32,20 @@ export const defaultBranches = [
{ {
terms: [ terms: [
{ {
column: undefined, terms: [
value: { {
source: 'fixed', column: undefined,
value: undefined value: {
}, source: 'fixed',
termType: undefined, value: undefined
key: 'params_1', },
termType: undefined,
key: 'params_1',
type: 'and',
},
],
type: 'and', type: 'and',
key: 'terms_1_terms_1',
}, },
], ],
type: 'and', type: 'and',
@ -79,9 +85,22 @@ export const useSceneStore = defineStore('scene', () => {
name: '', name: '',
id: undefined id: undefined
}) })
const productCache = {} const productCache = {}
const refresh = () => {
data.value = {
trigger: { type: ''},
options: cloneDeep(defaultOptions),
branches: cloneDeep(defaultBranches),
description: '',
name: '',
id: undefined
}
}
const getDetail = async (id: string) => { const getDetail = async (id: string) => {
refresh()
const resp = await detail(id) const resp = await detail(id)
if (resp.success) { if (resp.success) {
const result = resp.result as any const result = resp.result as any
@ -109,22 +128,11 @@ export const useSceneStore = defineStore('scene', () => {
...result, ...result,
trigger: result.trigger || {}, trigger: result.trigger || {},
branches: cloneDeep(assignmentKey(branches)), branches: cloneDeep(assignmentKey(branches)),
options: result.options ? {...defaultOptions, ...result.options } : defaultOptions, options: result.options ? {...cloneDeep(defaultOptions), ...result.options } : cloneDeep(defaultOptions),
} }
} }
} }
const refresh = () => {
data.value = {
trigger: { type: ''},
options: cloneDeep(defaultOptions),
branches: cloneDeep(defaultBranches),
description: '',
name: '',
id: undefined
}
}
return { return {
data, data,
productCache, productCache,

View File

@ -108,4 +108,29 @@ export const onlyMessage = (msg: string, type: 'success' | 'error' | 'warning' =
content: msg, content: msg,
key: type key: type
}) })
}
export interface SearchItemData {
column: any;
value: any;
termType: string;
type?: string;
}
export const handleParamsToString = (terms:SearchItemData[] = []) => {
const _terms: any[] = [
{ terms: [null,null,null]},
{ terms: [null,null,null]}
]
let termsIndex = 0
let termsStar = 0
terms.forEach((item, index) => {
if (index > 2) {
termsIndex = 1
termsStar = 4
}
_terms[termsIndex].terms[index - termsStar ] = item
})
return JSON.stringify({ terms: _terms})
} }

View File

@ -148,6 +148,9 @@ const extraRouteObj = {
'edge/Device': { 'edge/Device': {
children: [{ code: 'Remote', name: '远程控制' }], children: [{ code: 'Remote', name: '远程控制' }],
}, },
'rule-engine/Alarm/Log': {
children: [{ code: 'Record', name: '处理记录' }]
}
}; };
@ -206,7 +209,7 @@ const findDetailRoutes = (routes: any[]): any[] => {
export const findCodeRoute = (asyncRouterMap: any[]) => { export const findCodeRoute = (asyncRouterMap: any[]) => {
const routeMeta = {} const routeMeta = {}
function getDetail( code: string, url: string) { function getDetail(code: string, url: string) {
const detail = findDetailRouteItem(code, url) const detail = findDetailRouteItem(code, url)
if (!detail) return if (!detail) return
routeMeta[(detail as MenuItem).code] = { routeMeta[(detail as MenuItem).code] = {
@ -217,7 +220,7 @@ export const findCodeRoute = (asyncRouterMap: any[]) => {
} }
} }
function findChildren (data: any[], code: string = '') { function findChildren(data: any[], code: string = '') {
data.forEach(route => { data.forEach(route => {
routeMeta[route.code] = { routeMeta[route.code] = {
path: route.url || route.path, path: route.url || route.path,
@ -254,7 +257,7 @@ export const findCodeRoute = (asyncRouterMap: any[]) => {
return routeMeta return routeMeta
} }
export function filterAsyncRouter(asyncRouterMap: any, parentCode = '', level = 1): { menusData: any, silderMenus: any} { export function filterAsyncRouter(asyncRouterMap: any, parentCode = '', level = 1): { menusData: any, silderMenus: any } {
const _asyncRouterMap = cloneDeep(asyncRouterMap) const _asyncRouterMap = cloneDeep(asyncRouterMap)
const menusData: any[] = [] const menusData: any[] = []
const silderMenus: any[] = [] const silderMenus: any[] = []
@ -270,7 +273,7 @@ export function filterAsyncRouter(asyncRouterMap: any, parentCode = '', level =
}, },
} }
const silder = {..._route} const silder = { ..._route }
// 查看是否有隐藏子路由 // 查看是否有隐藏子路由
route.children = findChildrenRoute(route.code, route.url, route.children) route.children = findChildrenRoute(route.code, route.url, route.children)

View File

@ -118,6 +118,16 @@ export const postStream = function(url: string, data={}, params = {}) {
}) })
} }
const showNotification = ( message: string, description: string, key?: string, show: boolean = true ) => {
if (show) {
Notification.error({
key,
message,
description
})
}
}
/** /**
* *
* @param {Object} error * @param {Object} error
@ -128,47 +138,20 @@ const errorHandler = (error: any) => {
const data = error.response.data const data = error.response.data
const status = error.response.status const status = error.response.status
if (status === 403) { if (status === 403) {
Notification.error({ showNotification( 'Forbidden', (data.message + '').substr(0, 90), '403')
key: '403',
message: 'Forbidden',
description: (data.message + '').substr(0, 90)
})
setTimeout(() => {
router.push({
name: 'Exception403'
})
}, 0)
} else if (status === 500) { } else if (status === 500) {
Notification.error({ showNotification( 'Server Side Error', (data.message + '').substr(0, 90), '500')
key: '500',
message: 'Server Side Error',
description: (data.message + '').substr(0, 90)
})
} else if (status === 400) { } else if (status === 400) {
Notification.error({ showNotification( 'Request Error', (data.message + '').substr(0, 90), '400')
key: '400',
message: 'Request Error',
description: (data.message + '').substr(0, 90)
})
} else if (status === 401) { } else if (status === 401) {
Notification.error({ showNotification( 'Unauthorized', '用户未登录', '401')
key: '401', console.log('showNotification')
message: 'Unauthorized',
description: '用户未登录'
})
setTimeout(() => { setTimeout(() => {
debugger location.href = `/#${LoginPath}`
router.push({
path: LoginPath
})
}, 0) }, 0)
} }
} else if (error.response === undefined) { } else if (error.response === undefined) {
Notification.error({ showNotification( error.message, (error.stack + '').substr(0, 90), undefined)
message: error.message,
description: (error.stack + '').substr(0, 90)
})
} }
return Promise.reject(error) return Promise.reject(error)
} }
@ -180,10 +163,13 @@ request.interceptors.request.use(config => {
const token = LocalStore.get(TOKEN_KEY) const token = LocalStore.get(TOKEN_KEY)
// const token = store.$state.tokenAlias // const token = store.$state.tokenAlias
if (!token) { if (!token) {
// setTimeout(() => {
// router.replace({
// path: LoginPath
// })
// }, 0)
setTimeout(() => { setTimeout(() => {
router.replace({ location.href = `/#${LoginPath}`
path: LoginPath
})
}, 0) }, 0)
return config return config
} }

View File

@ -37,6 +37,7 @@
:status="getState(slotProps).value" :status="getState(slotProps).value"
:statusText="getState(slotProps).text" :statusText="getState(slotProps).text"
:statusNames="StatusColorEnum" :statusNames="StatusColorEnum"
@click="handlEye(slotProps.id)"
> >
<template #img> <template #img>
<slot name="img"> <slot name="img">
@ -48,8 +49,9 @@
<Ellipsis style="width: calc(100% - 100px)"> <Ellipsis style="width: calc(100% - 100px)">
<span <span
style=" style="
font-size: 16px; font-size: 18px;
font-weight: 600; font-weight: 800;
line-height: 22px;
" "
> >
{{ slotProps.name }} {{ slotProps.name }}
@ -276,6 +278,9 @@ const handlEdit = (data: object) => {
current.value = _.cloneDeep(data); current.value = _.cloneDeep(data);
visible.value = true; visible.value = true;
}; };
const handlEye = (id: string) => {
menuStory.jumpPage(`DataCollect/Collector`, {}, { channelId: id });
};
const saveChange = (value: object) => { const saveChange = (value: object) => {
visible.value = false; visible.value = false;
current.value = {}; current.value = {};

View File

@ -81,6 +81,7 @@
? '正常的采集器不能删除' ? '正常的采集器不能删除'
: '删除', : '删除',
}" }"
:danger="data?.state?.value === 'disabled'"
hasPermission="DataCollect/Collector:delete" hasPermission="DataCollect/Collector:delete"
:popConfirm="{ :popConfirm="{
title: `该操作将会删除下属点位,确定删除?`, title: `该操作将会删除下属点位,确定删除?`,
@ -253,7 +254,7 @@ watch(
<style lang="less" scoped> <style lang="less" scoped>
.tree-container { .tree-container {
padding-right: 24px; padding-right: 24px;
width: 300px; width: 350px;
.add-btn { .add-btn {
margin: 10px 0; margin: 10px 0;

View File

@ -36,7 +36,7 @@ const changeTree = (row: any) => {
min-height: calc(100vh - 180px); min-height: calc(100vh - 180px);
width: 100%; width: 100%;
.left { .left {
width: 300px; width: 350px;
border-right: 1px #eeeeee solid; border-right: 1px #eeeeee solid;
margin: 10px; margin: 10px;
} }

View File

@ -265,6 +265,7 @@ const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
const column = { const column = {
username: 'context.username', username: 'context.username',
description: 'action',
}; };
/** /**

View File

@ -31,10 +31,11 @@
<script setup lang="ts" name="FileUpload"> <script setup lang="ts" name="FileUpload">
import { LocalStore } from '@/utils/comm'; import { LocalStore } from '@/utils/comm';
import { TOKEN_KEY } from '@/utils/variable'; import { TOKEN_KEY } from '@/utils/variable';
import { FIRMWARE_UPLOAD, querySystemApi } from '@/api/device/firmware'; import { FIRMWARE_UPLOAD } from '@/api/device/firmware';
import { onlyMessage } from '@/utils/comm'; import { onlyMessage } from '@/utils/comm';
import type { UploadChangeParam } from 'ant-design-vue'; import type { UploadChangeParam } from 'ant-design-vue';
import { notification as Notification } from 'ant-design-vue'; import { notification as Notification } from 'ant-design-vue';
import { useSystem } from '@/store/system';
const emit = defineEmits(['update:modelValue', 'update:extraValue', 'change']); const emit = defineEmits(['update:modelValue', 'update:extraValue', 'change']);
@ -45,6 +46,10 @@ const props = defineProps({
}, },
}); });
const paths: string = useSystem().$state.configInfo.paths?.[
'base-path'
] as string;
const fileValue = ref(props.modelValue); const fileValue = ref(props.modelValue);
const loading = ref(false); const loading = ref(false);
@ -53,11 +58,9 @@ const handleChange = async (info: UploadChangeParam) => {
if (info.file.status === 'done') { if (info.file.status === 'done') {
loading.value = false; loading.value = false;
const result = info.file.response?.result; const result = info.file.response?.result;
const api: any = await querySystemApi(['paths']); // todo base-pathpinia const f = `${paths || ''}/file/${result.id}?accessKey=${
const path = api.result[0]?.properties result.others.accessKey
? api.result[0]?.properties['base-path'] }`;
: '';
const f = `${path}/file/${result.id}?accessKey=${result.others.accessKey}`;
onlyMessage('上传成功!', 'success'); onlyMessage('上传成功!', 'success');
fileValue.value = f; fileValue.value = f;
emit('update:modelValue', f); emit('update:modelValue', f);

View File

@ -38,8 +38,8 @@
selectedRowKeys: _selectedRowKeys, selectedRowKeys: _selectedRowKeys,
onSelect: onSelectChange, onSelect: onSelectChange,
onSelectAll: onSelectAllChange, onSelectAll: onSelectAllChange,
onChange: onChange,
}" }"
@cancelSelect="cancelSelect"
:params="params" :params="params"
> >
<template #headerTitle> <template #headerTitle>
@ -125,7 +125,7 @@ const defaultParams = {
}; };
const statusMap = new Map(); const statusMap = new Map();
statusMap.set('online', 'success'); statusMap.set('online', 'processing');
statusMap.set('offline', 'error'); statusMap.set('offline', 'error');
statusMap.set('notActive', 'warning'); statusMap.set('notActive', 'warning');
@ -223,8 +223,10 @@ const getSelectedRowsKey = (selectedRows: T[]) =>
const getSetRowKey = (selectedRows: T[]) => const getSetRowKey = (selectedRows: T[]) =>
new Set([..._selectedRowKeys.value, ...getSelectedRowsKey(selectedRows)]); new Set([..._selectedRowKeys.value, ...getSelectedRowsKey(selectedRows)]);
const cancelSelect = () => { const onChange = (selectedRowKeys: T[]) => {
_selectedRowKeys.value = []; if (selectedRowKeys.length === 0) {
_selectedRowKeys.value = [];
}
}; };
const handleOk = () => { const handleOk = () => {
@ -249,7 +251,7 @@ const onVisible = () => {
const handleCancel = () => { const handleCancel = () => {
visible.value = false; visible.value = false;
cancelSelect(); _selectedRowKeys.value = [];
}; };
onMounted(() => { onMounted(() => {

View File

@ -51,6 +51,7 @@
style="padding: 0px" style="padding: 0px"
@click="i.onClick" @click="i.onClick"
type="link" type="link"
:danger="i.key === 'delete'"
:hasPermission="'device/Firmware:' + i.key" :hasPermission="'device/Firmware:' + i.key"
> >
<template #icon <template #icon
@ -68,7 +69,6 @@
<script lang="ts" setup name="FirmwarePage"> <script lang="ts" setup name="FirmwarePage">
import type { ActionsType } from '@/components/Table/index'; import type { ActionsType } from '@/components/Table/index';
import { query, queryProduct, remove } from '@/api/device/firmware'; import { query, queryProduct, remove } from '@/api/device/firmware';
import { message } from 'ant-design-vue';
import moment from 'moment'; import moment from 'moment';
import _ from 'lodash'; import _ from 'lodash';
import Save from './Save/index.vue'; import Save from './Save/index.vue';

View File

@ -162,7 +162,7 @@ import {
} from '@/api/device/instance'; } from '@/api/device/instance';
import MSelect from './MSelect.vue'; import MSelect from './MSelect.vue';
import PatchMapping from './PatchMapping.vue'; import PatchMapping from './PatchMapping.vue';
import { message } from 'ant-design-vue/es'; import { onlyMessage } from '@/utils/comm';
const columns = [ const columns = [
{ {
@ -280,7 +280,7 @@ const unbind = async (id: string) => {
}, },
); );
if (resp.status === 200) { if (resp.status === 200) {
message.success('操作成功!'); onlyMessage('操作成功!', 'success');
handleSearch(); handleSearch();
} }
} }
@ -313,7 +313,7 @@ const onSave = () => {
submitData, submitData,
); );
if (resp.status === 200) { if (resp.status === 200) {
message.success('操作成功!'); onlyMessage('操作成功!', 'success');
handleSearch(); handleSearch();
} }
} }
@ -342,7 +342,7 @@ const onAction = async (record: any) => {
submitData, submitData,
); );
if (resp.status === 200) { if (resp.status === 200) {
message.success('操作成功!'); onlyMessage('操作成功!', 'success');
handleSearch(); handleSearch();
} }
}; };

View File

@ -13,6 +13,7 @@
<j-image :src="value?.formatValue" /> <j-image :src="value?.formatValue" />
</template> </template>
<template v-else-if="['.flv', '.m3u8', '.mp4'].includes(type)"> <template v-else-if="['.flv', '.m3u8', '.mp4'].includes(type)">
<LivePlayer :url="value?.formatValue" autoplay />
</template> </template>
<template v-else> <template v-else>
<JsonViewer <JsonViewer
@ -25,6 +26,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import JsonViewer from 'vue-json-viewer'; import JsonViewer from 'vue-json-viewer';
import LivePlayer from '@/components/Player/index.vue';
const _data = defineProps({ const _data = defineProps({
type: { type: {

View File

@ -13,7 +13,7 @@
</div> </div>
<div v-else> <div v-else>
<p>{{ type === 'active' ? '启用' : '同步' }}成功{{ count }}</p> <p>{{ type === 'active' ? '启用' : '同步' }}成功{{ count }}</p>
<p v-if="type === 'active'">启用失败:{{ errCount }}条<j-tooltip title="实例信息页面中的配置项未完善"><AIcon type="QuestionCircleOutlined" /></j-tooltip></p> <p v-if="type === 'active'">启用失败:{{ errCount }}条<j-tooltip title="实例信息页面中的配置项未完善"><AIcon style="margin-left: 5px" type="QuestionCircleOutlined" /></j-tooltip></p>
</div> </div>
</div> </div>
<template #footer> <template #footer>
@ -56,13 +56,13 @@ const getData = (api: string) => {
source.value = _source; source.value = _source;
_source.onmessage = (e: any) => { _source.onmessage = (e: any) => {
const res = JSON.parse(e.data); const res = JSON.parse(e.data);
// console.log(res)
switch (props.type) { switch (props.type) {
case 'active': case 'active':
if (res.success) { if (res.success) {
_source.close(); _source.close();
dt += res.total; dt += res.total;
count.value = dt; count.value = dt;
flag.value = false;
} else { } else {
if (res.source) { if (res.source) {
errCount.value = 1 errCount.value = 1
@ -76,6 +76,7 @@ const getData = (api: string) => {
case 'sync': case 'sync':
dt += res; dt += res;
count.value = dt; count.value = dt;
flag.value = false;
break; break;
default: default:
break; break;

View File

@ -328,6 +328,7 @@ const columns = [
title: 'ID', title: 'ID',
dataIndex: 'id', dataIndex: 'id',
key: 'id', key: 'id',
ellipsis: true,
search: { search: {
type: 'string', type: 'string',
defaultTermType: 'eq', defaultTermType: 'eq',
@ -337,6 +338,7 @@ const columns = [
title: '设备名称', title: '设备名称',
dataIndex: 'name', dataIndex: 'name',
key: 'name', key: 'name',
ellipsis: true,
search: { search: {
type: 'string', type: 'string',
first: true, first: true,
@ -346,6 +348,7 @@ const columns = [
title: '产品名称', title: '产品名称',
dataIndex: 'productName', dataIndex: 'productName',
key: 'productName', key: 'productName',
ellipsis: true,
search: { search: {
type: 'select', type: 'select',
rename: 'productId', rename: 'productId',
@ -367,6 +370,7 @@ const columns = [
dataIndex: 'createTime', dataIndex: 'createTime',
key: 'createTime', key: 'createTime',
scopedSlots: true, scopedSlots: true,
width: 200,
search: { search: {
type: 'date', type: 'date',
}, },

View File

@ -33,7 +33,7 @@
" "
> >
<j-button <j-button
style="margin: 0 0 0 20px" class="changeBtn"
size="small" size="small"
:disabled=" :disabled="
productStore.current?.count && productStore.current?.count &&
@ -418,6 +418,8 @@ import { marked } from 'marked';
import type { TableColumnType } from 'ant-design-vue'; import type { TableColumnType } from 'ant-design-vue';
import { useMenuStore } from '@/store/menu'; import { useMenuStore } from '@/store/menu';
import _ from 'lodash'; import _ from 'lodash';
import encodeQuery from '@/utils/encodeQuery';
const tableRef = ref(); const tableRef = ref();
const formRef = ref(); const formRef = ref();
const menuStore = useMenuStore(); const menuStore = useMenuStore();
@ -501,14 +503,15 @@ const query = reactive({
return new Promise((res) => { return new Promise((res) => {
getProviders().then((resp: any) => { getProviders().then((resp: any) => {
listData.value = []; listData.value = [];
console.log(description.value);
if (isNoCommunity) { if (isNoCommunity) {
listData.value = (resp?.result || []).map( (resp?.result || []).map((item: any) => {
(item: any) => ({ if (item.id != 'plugin_gateway') {
label: item.name, listData.value.push({
value: item.id, label: item.name,
}), value: item.id,
); });
}
});
} else { } else {
listData.value = (resp?.result || []) listData.value = (resp?.result || [])
.filter((i: any) => .filter((i: any) =>
@ -568,17 +571,7 @@ const query = reactive({
}); });
const param = ref<Record<string, any>>({ const param = ref<Record<string, any>>({
pageSize: 4, pageSize: 4,
terms: [ terms: [],
{
terms: [
{
column: 'channel',
termType: 'nin',
value: 'plugin',
},
],
},
],
}); });
const queryParams = ref<Record<string, any>>({}); const queryParams = ref<Record<string, any>>({});
/** /**
@ -1085,4 +1078,10 @@ nextTick(() => {
font-weight: 400; font-weight: 400;
font-size: 12px; font-size: 12px;
} }
.changeBtn {
margin: 0 0 0 20px;
color: #315efb;
background: #ffffff;
border: 1px solid #315efb;
}
</style> </style>

View File

@ -1,7 +1,6 @@
<template> <template>
<page-container <page-container
:tabList="list" :tabList="list"
@back="onBack"
:tabActiveKey="productStore.tabActiveKey" :tabActiveKey="productStore.tabActiveKey"
@tabChange="onTabChange" @tabChange="onTabChange"
showBack="true" showBack="true"
@ -49,9 +48,21 @@
</div> </div>
</div> </div>
</div> </div>
</template>
<template #content>
<div style="padding-top: 10px"> <div style="padding-top: 10px">
<j-descriptions size="small" :column="4"> <j-descriptions size="small" :column="4">
<j-descriptions-item label="设备数量" <j-descriptions-item
label="设备数量"
:labelStyle="{
fontSize: '14px',
opacity: 0.55,
}"
:contentStyle="{
fontSize: '14px',
color: '#092EE7',
cursor: 'pointer',
}"
><span @click="jumpDevice">{{ ><span @click="jumpDevice">{{
productStore.current?.count productStore.current?.count
? productStore.current?.count ? productStore.current?.count
@ -62,18 +73,6 @@
</div> </div>
</template> </template>
<template #extra> <template #extra>
<!-- <j-popconfirm
title="确认应用配置"
@confirm="handleCofig"
okText="确定"
cancelText="取消"
>
<j-button
:disabled="productStore.current.state === 0"
type="primary"
>应用配置</j-button
>
</j-popconfirm> -->
<PermissionButton <PermissionButton
type="primary" type="primary"
:popConfirm="{ :popConfirm="{
@ -112,7 +111,7 @@ import {
getProtocolDetail, getProtocolDetail,
} from '@/api/device/product'; } from '@/api/device/product';
import { message } from 'jetlinks-ui-components'; import { message } from 'jetlinks-ui-components';
import { getImage } from '@/utils/comm'; import { getImage, handleParamsToString } from '@/utils/comm'
import encodeQuery from '@/utils/encodeQuery'; import encodeQuery from '@/utils/encodeQuery';
import { useMenuStore } from '@/store/menu'; import { useMenuStore } from '@/store/menu';
@ -172,7 +171,9 @@ watch(
getProtocol(); getProtocol();
}, },
); );
const onBack = () => {}; const onBack = () => {
history.back();
};
const onTabChange = (e: string) => { const onTabChange = (e: string) => {
productStore.tabActiveKey = e; productStore.tabActiveKey = e;
@ -250,7 +251,7 @@ const getProtocol = async () => {
tab: '数据解析', tab: '数据解析',
}, },
]; ];
}else{ } else {
list.value = [ list.value = [
{ {
key: 'Info', key: 'Info',
@ -264,7 +265,8 @@ const getProtocol = async () => {
{ {
key: 'Device', key: 'Device',
tab: '设备接入', tab: '设备接入',
},] },
];
} }
} }
} }
@ -284,7 +286,7 @@ const jumpDevice = () => {
{}, {},
{ {
target: 'device-instance', target: 'device-instance',
q: JSON.stringify({ terms: [{ terms: [{ searchParams }] }] }), q: handleParamsToString([searchParams]),
}, },
); );
}; };

View File

@ -84,7 +84,7 @@
> >
<template #title="item"> <template #title="item">
<span>{{ item.title }}</span> <span>{{ item.title }}</span>
<a-tooltip :title="item" <a-tooltip :title="item.option.tooltip"
><AIcon ><AIcon
type="QuestionCircleOutlined" type="QuestionCircleOutlined"
style="margin-left: 2px" style="margin-left: 2px"

View File

@ -447,12 +447,14 @@ const query = reactive({
listData.value = []; listData.value = [];
// const list = () => { // const list = () => {
if (isNoCommunity) { if (isNoCommunity) {
listData.value = (resp?.result || []).map( (resp?.result || []).map((item: any) => {
(item: any) => ({ if (item.id != 'plugin_gateway') {
label: item.name, listData.value.push({
value: item.id, label: item.name,
}), value: item.id,
); });
}
});
} else { } else {
listData.value = (resp?.result || []) listData.value = (resp?.result || [])
.filter((i: any) => .filter((i: any) =>

View File

@ -267,26 +267,26 @@ const columns = [
}), }),
}, },
}, },
{ // {
key: 'productId$product-info', // key: 'productId$product-info',
dataIndex: 'productId$product-info', // dataIndex: 'productId$product-info',
title: '接入方式', // title: '',
hideInTable: true, // hideInTable: true,
search: { // search: {
type: 'select', // type: 'select',
options: () => // options: () =>
new Promise((resolve) => { // new Promise((resolve) => {
queryGatewayList({}).then((resp: any) => { // queryGatewayList({}).then((resp: any) => {
resolve( // resolve(
resp.result.map((item: any) => ({ // resp.result.map((item: any) => ({
label: item.name, // label: item.name,
value: `accessId is ${item.id}`, // value: `accessId is ${item.id}`,
})), // })),
); // );
}); // });
}), // }),
}, // },
}, // },
{ {
dataIndex: 'deviceType', dataIndex: 'deviceType',
title: '设备类型', title: '设备类型',

View File

@ -20,7 +20,7 @@
<j-col :span="8">已下发数量{{ countErr + count }}</j-col> <j-col :span="8">已下发数量{{ countErr + count }}</j-col>
</j-row> </j-row>
<div v-if="!flag"> <div v-if="!flag">
<j-textarea :rows="20" :value="JSON.stringify(errMessage)" /> <j-textarea :rows="10" :value="JSON.stringify(errMessage)" />
</div> </div>
</j-modal> </j-modal>
</template> </template>
@ -86,7 +86,7 @@ const getData = () => {
et += 1; et += 1;
countErr.value = et; countErr.value = et;
flag.value = false; flag.value = false;
if (errMessages.length <= 5) { if (errMessages.length < 5) {
errMessages.push({ ...res }); errMessages.push({ ...res });
errMessage.value = [...errMessages]; errMessage.value = [...errMessages];
} }

View File

@ -169,7 +169,8 @@ const onCancel = () => {
<style lang="less" scoped> <style lang="less" scoped>
.search { .search {
padding: 0 0 0 24px; padding: 0px;
margin: 0px;
} }
.alert { .alert {
height: 40px; height: 40px;

View File

@ -2,7 +2,7 @@
<template> <template>
<j-form layout="vertical" :model="form" ref="formBasicRef"> <j-form layout="vertical" :model="form" ref="formBasicRef">
<j-row :span="24" :gutter="24"> <j-row :span="24" :gutter="24">
<j-col :span="10"> <j-col :span="12">
<j-form-item <j-form-item
label="系统名称" label="系统名称"
name="title" name="title"
@ -193,7 +193,7 @@
</j-col> </j-col>
</j-row> </j-row>
</j-col> </j-col>
<j-col :span="14"> <j-col :span="12">
<j-form-item label="登录背景图"> <j-form-item label="登录背景图">
<div class="upload-image-warp-back"> <div class="upload-image-warp-back">
<div class="upload-image-border-back"> <div class="upload-image-border-back">

View File

@ -183,6 +183,7 @@ defineExpose({
flex: 1 1 auto; flex: 1 1 auto;
font-weight: 700; font-weight: 700;
font-size: 16px; font-size: 16px;
text-align: center;
} }
} }
.role-item-content { .role-item-content {

View File

@ -572,7 +572,7 @@ const columns = [
search: { search: {
type: 'select', type: 'select',
options: [ options: [
{ label: '正常', value: 'using' }, { label: '激活', value: 'using' },
{ label: '未激活', value: 'toBeActivated' }, { label: '未激活', value: 'toBeActivated' },
{ label: '停机', value: 'deactivate' }, { label: '停机', value: 'deactivate' },
{ label: '其它', value: 'using,toBeActivated,deactivate' }, { label: '其它', value: 'using,toBeActivated,deactivate' },

View File

@ -482,6 +482,7 @@
type="primary" type="primary"
style="margin-right: 8px" style="margin-right: 8px"
@click="saveData" @click="saveData"
:loading="loading"
:hasPermission="`link/AccessConfig:${ :hasPermission="`link/AccessConfig:${
id === ':id' ? 'add' : 'update' id === ':id' ? 'add' : 'update'
}`" }`"
@ -534,7 +535,7 @@ const props = defineProps({
const route = useRoute(); const route = useRoute();
const view = route.query.view as string; const view = route.query.view as string;
const id = route.params.id as string; const id = route.params.id as string;
const loading = ref(false);
const activeKey: any = ref([]); const activeKey: any = ref([]);
const clientHeight = document.body.clientHeight; const clientHeight = document.body.clientHeight;
@ -638,10 +639,17 @@ const saveData = () => {
transport: 'SIP', transport: 'SIP',
channel: 'gb28181', channel: 'gb28181',
}; };
params.configuration.sipId = Number(params.configuration?.sipId); loading.value = true;
const resp = const resp =
id === ':id' ? await save(params) : await update({ ...params, id }); id === ':id'
if (resp.status === 200) { ? await save(params).catch(() => {
loading.value = false;
})
: await update({ ...params, id }).catch(() => {
loading.value = false;
});
if (resp?.status === 200) {
onlyMessage('操作成功', 'success'); onlyMessage('操作成功', 'success');
history.back(); history.back();
} }
@ -667,12 +675,14 @@ const next = async () => {
...data1, ...data1,
...data2, ...data2,
}; };
} else {
return onlyMessage('请新增或完善配置', 'error');
} }
current.value = current.value + 1; current.value = current.value + 1;
params.configuration = data1; params.configuration = data1;
}) })
.catch((err) => { .catch((err) => {
err.errorFields.forEach((item: any) => { err.errorFields?.forEach((item: any) => {
const activeId: any = const activeId: any =
dynamicValidateForm.cluster[item.name[1]].id; dynamicValidateForm.cluster[item.name[1]].id;
if (!activeKey.value.includes(activeId)) { if (!activeKey.value.includes(activeId)) {
@ -680,10 +690,24 @@ const next = async () => {
} }
}); });
}); });
} else {
current.value = current.value + 1;
params.configuration = data1;
} }
}; };
const prev = () => { const prev = () => {
current.value = current.value - 1; current.value = current.value - 1;
develop();
};
const develop = () => {
if (dynamicValidateForm.cluster.length !== 0) {
dynamicValidateForm.cluster.forEach((item) => {
const id: any = JSON.stringify(Date.now() + Math.random());
item.id = id;
activeKey.value.push(id);
});
}
}; };
onMounted(() => { onMounted(() => {
@ -723,21 +747,13 @@ onMounted(() => {
const { configuration, name, description = '' } = props.data; const { configuration, name, description = '' } = props.data;
formData.value = { name, description }; formData.value = { name, description };
if (configuration?.shareCluster) { formState.value = {
formState.value = { ...formState.value,
...formState.value, ...props.data.configuration,
...props.data.configuration, };
}; if (!configuration?.shareCluster) {
} else {
formState.value = {
...formState.value,
...props.data.configuration,
};
dynamicValidateForm.cluster = configuration.cluster; dynamicValidateForm.cluster = configuration.cluster;
if (dynamicValidateForm.cluster.length === 1) { develop();
activeKey.value = ['1'];
dynamicValidateForm.cluster[0].id = 1;
}
} }
} }
}); });

View File

@ -13,6 +13,17 @@
:request="list" :request="list"
:defaultParams="{ :defaultParams="{
sorts: [{ name: 'createTime', order: 'desc' }], sorts: [{ name: 'createTime', order: 'desc' }],
terms: [
{
terms: [
{
termType: 'nin',
column: 'provider',
value: 'plugin_gateway', //todo
},
],
},
],
}" }"
gridColumn="2" gridColumn="2"
:gridColumns="[1, 2]" :gridColumns="[1, 2]"
@ -40,6 +51,7 @@
enabled: 'processing', enabled: 'processing',
disabled: 'error', disabled: 'error',
}" }"
@click="handlEye(slotProps.id)"
> >
<template #img> <template #img>
<slot name="img"> <slot name="img">
@ -52,13 +64,9 @@
style=" style="
width: calc(100% - 100px); width: calc(100% - 100px);
margin-bottom: 20px; margin-bottom: 20px;
color: #2f54eb;
" "
> >
<span <span class="card-title">
class="card-title"
@click.stop="handlEye(slotProps.id)"
>
{{ slotProps.name }} {{ slotProps.name }}
</span> </span>
</Ellipsis> </Ellipsis>
@ -310,10 +318,12 @@ const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
const getProvidersList = async () => { const getProvidersList = async () => {
const res: any = await getProviders(); const res: any = await getProviders();
providersList.value = res.result; providersList.value = res.result;
providersOptions.value = (res?.result || [])?.map((item: any) => ({ providersOptions.value = (res?.result || [])
label: item.name, ?.map((item: any) => ({
value: item.id, label: item.name,
})); value: item.id,
}))
.filter((item: any) => item.value !== 'plugin_gateway'); // todo
}; };
getProvidersList(); getProvidersList();

View File

@ -41,7 +41,7 @@
<CertificateFile <CertificateFile
name="cert" name="cert"
v-model:modelValue="formData.configs.cert" v-model:modelValue="formData.configs.cert"
placeholder='证书格式以"-----BEGIN CERTIFICATE-----"开头,以"-----END CERTIFICATE-----"结尾"' placeholder="请输入证书文件"
/> />
</j-form-item> </j-form-item>
<j-form-item <j-form-item
@ -51,7 +51,7 @@
<CertificateFile <CertificateFile
name="key" name="key"
v-model:modelValue="formData.configs.key" v-model:modelValue="formData.configs.key"
placeholder='证书私钥格式以"-----BEGIN (RSA|EC) PRIVATE KEY-----"开头,以"-----END(RSA|EC) PRIVATE KEY-----"结尾。' placeholder="请输入证书私钥"
/> />
</j-form-item> </j-form-item>
<j-form-item label="说明" name="description"> <j-form-item label="说明" name="description">

View File

@ -44,6 +44,7 @@
style="padding: 0px" style="padding: 0px"
@click="i.onClick" @click="i.onClick"
type="link" type="link"
:danger="i.key === 'delete'"
:hasPermission="'link/Certificate:' + i.key" :hasPermission="'link/Certificate:' + i.key"
> >
<template #icon <template #icon
@ -96,6 +97,7 @@ const columns = [
ellipsis: true, ellipsis: true,
search: { search: {
type: 'string', type: 'string',
first: true,
}, },
}, },
{ {

View File

@ -75,8 +75,6 @@ const pickerTimeChange = () => {
const getCPUEcharts = async (val: any) => { const getCPUEcharts = async (val: any) => {
loading.value = true; loading.value = true;
console.log(224, val);
const res: any = await dashboard(defulteParamsData('cpu', val)); const res: any = await dashboard(defulteParamsData('cpu', val));
if (res.success) { if (res.success) {
const _cpuOptions = {}; const _cpuOptions = {};

View File

@ -114,8 +114,8 @@ const getNetworkEcharts = async (val: any) => {
loading.value = false; loading.value = false;
}, 300); }, 300);
}; };
const networkValueRender = (obj: any) => {
const { value } = obj; const formatterData = (value: any) => {
let _data = ''; let _data = '';
if (value >= 1024 && value < 1024 * 1024) { if (value >= 1024 && value < 1024 * 1024) {
_data = `${Number((value / 1024).toFixed(2))}KB`; _data = `${Number((value / 1024).toFixed(2))}KB`;
@ -124,7 +124,14 @@ const networkValueRender = (obj: any) => {
} else { } else {
_data = `${value}B`; _data = `${value}B`;
} }
return `${obj?.axisValueLabel}<br />${obj?.marker}${obj?.seriesName}: ${_data}`; return _data;
};
const networkValueRender = (obj: any) => {
const { value } = obj;
return `${obj?.axisValueLabel}<br />${obj?.marker}${
obj?.seriesName
} &nbsp; ${formatterData(value)}`;
}; };
const setOptions = (data: any, key: string) => ({ const setOptions = (data: any, key: string) => ({
@ -149,9 +156,12 @@ const handleNetworkOptions = (optionsData: any, xAxis: any) => {
}, },
yAxis: { yAxis: {
type: 'value', type: 'value',
axisLabel: {
formatter: (_value: any) => formatterData(_value),
},
}, },
grid: { grid: {
left: '100px', left: '70px',
right: '50px', right: '50px',
}, },
tooltip: { tooltip: {

View File

@ -27,10 +27,11 @@
<script setup lang="ts" name="FileUpload"> <script setup lang="ts" name="FileUpload">
import { LocalStore } from '@/utils/comm'; import { LocalStore } from '@/utils/comm';
import { TOKEN_KEY } from '@/utils/variable'; import { TOKEN_KEY } from '@/utils/variable';
import { PROTOCOL_UPLOAD, querySystemApi } from '@/api/link/protocol'; import { PROTOCOL_UPLOAD } from '@/api/link/protocol';
import { onlyMessage } from '@/utils/comm'; import { onlyMessage } from '@/utils/comm';
import type { UploadChangeParam, UploadProps } from 'ant-design-vue'; import type { UploadChangeParam, UploadProps } from 'ant-design-vue';
import { notification as Notification } from 'ant-design-vue'; import { notification as Notification } from 'ant-design-vue';
import { useSystem } from '@/store/system';
const emit = defineEmits(['update:modelValue', 'change']); const emit = defineEmits(['update:modelValue', 'change']);
@ -41,6 +42,10 @@ const props = defineProps({
}, },
}); });
const paths: string = useSystem().$state.configInfo.paths?.[
'base-path'
] as string;
const value = ref(props.modelValue); const value = ref(props.modelValue);
const loading = ref(false); const loading = ref(false);
@ -58,11 +63,9 @@ const handleChange = async (info: UploadChangeParam) => {
if (info.file.status === 'done') { if (info.file.status === 'done') {
loading.value = false; loading.value = false;
const result = info.file.response?.result; const result = info.file.response?.result;
const api: any = await querySystemApi(['paths']); // todo base-pathpinia const f = `${paths || ''}/file/${result.id}?accessKey=${
const path = api.result[0]?.properties result.others.accessKey
? api.result[0]?.properties['base-path'] }`;
: '';
const f = `${path}/file/${result.id}?accessKey=${result.others.accessKey}`;
onlyMessage('上传成功!', 'success'); onlyMessage('上传成功!', 'success');
value.value = f; value.value = f;
emit('update:modelValue', f); emit('update:modelValue', f);

View File

@ -37,6 +37,7 @@
:disabled="!!id" :disabled="!!id"
v-model:value="formData.type" v-model:value="formData.type"
:options="options" :options="options"
:column="2"
@change="changeType" @change="changeType"
/> />
</j-form-item> </j-form-item>

View File

@ -103,18 +103,6 @@
:key="cluster.id" :key="cluster.id"
:show-arrow="!formData.shareCluster" :show-arrow="!formData.shareCluster"
> >
<!-- <j-collapse-panel
:key="cluster.id"
:header="
cluster.serverId
? cluster.serverId
: !formData.shareCluster
? `#${index + 1}.配置信息`
: ''
"
collapsible="header"
:show-arrow="!formData.shareCluster"
> -->
<template #header v-if="!shareCluster"> <template #header v-if="!shareCluster">
<div class="collapse-header"> <div class="collapse-header">
{{ {{
@ -1136,10 +1124,6 @@ const filterConfigByType = (data: any, type: string) => {
}); });
}; };
const changeheader = (value: string) => {
console.log(22, value);
};
const getPortOptions = (portOptions: object, index = 0) => { const getPortOptions = (portOptions: object, index = 0) => {
if (!portOptions) return; if (!portOptions) return;
const type = formData.value.type; const type = formData.value.type;
@ -1224,6 +1208,10 @@ const saveData = async () => {
}); });
}); });
if (!formRef2Data?.cluster) {
return onlyMessage('请新增或完善配置', 'error');
}
const { configuration } = formRef2Data?.cluster[0]; const { configuration } = formRef2Data?.cluster[0];
const params = shareCluster.value const params = shareCluster.value
? { ...formData.value, configuration } ? { ...formData.value, configuration }
@ -1418,5 +1406,6 @@ watch(
background: #ffffff; background: #ffffff;
border: 1px solid #e50012; border: 1px solid #e50012;
border-radius: 2px; border-radius: 2px;
cursor: pointer;
} }
</style> </style>

View File

@ -113,6 +113,19 @@ export const Validator = {
regOnlyNumber: new RegExp(/^\d+$/), regOnlyNumber: new RegExp(/^\d+$/),
}; };
const validateAddress = (_rule: any, value: string): Promise<any> =>
new Promise(async (resolve, reject) => {
if (
Validator.regIpv4.test(value) ||
Validator.regIPv6.test(value) ||
Validator.regDomain.test(value)
) {
return resolve('');
} else {
return reject('请输入正确的IP地址或者域名');
}
});
export const Rules = { export const Rules = {
name: [ name: [
{ {
@ -160,10 +173,14 @@ export const Rules = {
message: '请输入公网地址', message: '请输入公网地址',
}, },
{ {
pattern: validator: validateAddress,
Validator.regIpv4 || Validator.regIPv6 || Validator.regDomain, message: '请输入正确的IP地址或者域名',
message: '请输入正确格式的域名或ip',
}, },
// {
// pattern:
// Validator.regIpv4 || Validator.regIPv6 || Validator.regDomain,
// message: '请输入正确格式的域名或ip',
// },
], ],
publicPort: [ publicPort: [
{ {
@ -181,9 +198,7 @@ export const Rules = {
message: '请输入远程地址', message: '请输入远程地址',
}, },
{ {
pattern: validator: validateAddress,
Validator.regIpv4 || Validator.regIPv6 || Validator.regDomain,
message: '请输入正确格式的域名或ip', message: '请输入正确格式的域名或ip',
}, },
], ],

View File

@ -39,6 +39,7 @@
enabled: 'processing', enabled: 'processing',
disabled: 'error', disabled: 'error',
}" }"
@click="handlEye(slotProps.id)"
> >
<template #img> <template #img>
<slot name="img"> <slot name="img">
@ -51,15 +52,14 @@
style=" style="
width: calc(100% - 100px); width: calc(100% - 100px);
margin-bottom: 20px; margin-bottom: 20px;
color: #2f54eb;
" "
> >
<span <span
style=" style="
font-size: 16px; font-size: 18px;
font-weight: 600; font-weight: 800;
line-height: 22px;
" "
@click.stop="handlEye(slotProps.id)"
> >
{{ slotProps.name }} {{ slotProps.name }}
</span> </span>
@ -136,6 +136,7 @@
style="padding: 0px" style="padding: 0px"
@click="i.onClick" @click="i.onClick"
type="link" type="link"
:danger="i.key === 'delete'"
:hasPermission="'link/Type:' + i.key" :hasPermission="'link/Type:' + i.key"
> >
<template #icon <template #icon

View File

@ -76,10 +76,7 @@
message: '请输入API Host', message: '请输入API Host',
}, },
{ {
pattern: validator: validateAddress,
Validator.regIpv4 ||
Validator.regIPv6 ||
Validator.regDomain,
message: '请输入正确的IP地址或者域名', message: '请输入正确的IP地址或者域名',
}, },
]" ]"
@ -133,10 +130,7 @@
message: '请输入RTP IP', message: '请输入RTP IP',
}, },
{ {
pattern: validator: validateAddress,
Validator.regIpv4 ||
Validator.regIPv6 ||
Validator.regDomain,
message: '请输入正确的IP地址或者域名', message: '请输入正确的IP地址或者域名',
}, },
]" ]"
@ -197,15 +191,8 @@
style="width: 100%" style="width: 100%"
:min="1" :min="1"
:max=" :max="
Number( formData.configuration
formData.configuration .dynamicRtpPortRange1 || 65535
.dynamicRtpPortRange1,
) < 65535
? Number(
formData.configuration
.dynamicRtpPortRange1,
)
: 65535
" "
:precision="0" :precision="0"
placeholder="起始端口" placeholder="起始端口"
@ -231,7 +218,8 @@
<j-input-number <j-input-number
style="width: 100%" style="width: 100%"
:min=" :min="
formData.configuration.dynamicRtpPortRange0 formData.configuration
.dynamicRtpPortRange0 || 1
" "
:max="65535" :max="65535"
:precision="0" :precision="0"
@ -305,6 +293,19 @@ const Validator = {
regOnlyNumber: new RegExp(/^\d+$/), regOnlyNumber: new RegExp(/^\d+$/),
}; };
const validateAddress = (_rule: any, value: string): Promise<any> =>
new Promise(async (resolve, reject) => {
if (
Validator.regIpv4.test(value) ||
Validator.regIPv6.test(value) ||
Validator.regDomain.test(value)
) {
return resolve('');
} else {
return reject('请输入正确的IP地址或者域名');
}
});
const formData = ref<FormDataType>({ const formData = ref<FormDataType>({
name: '', name: '',
provider: undefined, provider: undefined,

View File

@ -38,9 +38,10 @@
:status="slotProps.state.value" :status="slotProps.state.value"
:statusText="slotProps.state.text" :statusText="slotProps.state.text"
:statusNames="{ :statusNames="{
enabled: 'success', enabled: 'processing',
disabled: 'error', disabled: 'error',
}" }"
@click="handlEye(slotProps.id)"
> >
<template #img> <template #img>
<slot name="img"> <slot name="img">
@ -53,15 +54,14 @@
style=" style="
width: calc(100% - 100px); width: calc(100% - 100px);
margin-bottom: 10px; margin-bottom: 10px;
color: #2f54eb;
" "
> >
<span <span
style=" style="
font-size: 16px; font-size: 18px;
font-weight: 600; font-weight: 800;
line-height: 22px;
" "
@click.stop="handlEye(slotProps.id)"
> >
{{ slotProps.name }} {{ slotProps.name }}
</span> </span>

View File

@ -20,7 +20,7 @@
</j-col> </j-col>
</j-row> </j-row>
<j-form-item label="级别" name="level"> <j-form-item label="级别" name="level">
<j-radio-group v-model:value="form.level"> <j-radio-group v-model:value="form.level" class="levelSelect">
<j-radio-button <j-radio-button
v-for="(item, index) in levelOption" v-for="(item, index) in levelOption"
:key="index" :key="index"
@ -199,7 +199,11 @@ queryData();
<style lang="less" scoped> <style lang="less" scoped>
.ant-radio-button-wrapper { .ant-radio-button-wrapper {
margin: 10px 15px 0 0; margin: 10px 15px 0 0;
width: 125px; width: 20%;
height: 100%; height: 100%;
} }
.levelSelect{
display: flex;
width: 100%;
}
</style> </style>

View File

@ -1,13 +1,5 @@
<template> <template>
<j-modal <page-container>
visible
title="处理记录"
:width="1200"
cancelText="取消"
okText="确定"
@ok="clsoeModal"
@cancel="clsoeModal"
>
<pro-search <pro-search
:columns="columns" :columns="columns"
target="bind-channel" target="bind-channel"
@ -30,7 +22,7 @@
<span> <span>
{{ {{
dayjs(slotsProps.handleTime).format( dayjs(slotsProps.handleTime).format(
'YYYY-MM-DD HH:mm:ss' 'YYYY-MM-DD HH:mm:ss',
) )
}} }}
</span> </span>
@ -41,29 +33,25 @@
<template #alarmTime="slotProps"> <template #alarmTime="slotProps">
<span> <span>
{{ {{
dayjs(slotProps.alarmTime).format( dayjs(slotProps.alarmTime).format('YYYY-MM-DD HH:mm:ss')
'YYYY-MM-DD HH:mm:ss',
)
}} }}
</span> </span>
</template> </template>
</JProTable> </JProTable>
</j-modal> </page-container>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { queryHandleHistory } from '@/api/rule-engine/log'; import { queryHandleHistory } from '@/api/rule-engine/log';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
const props = defineProps({ import { useRoute } from 'vue-router';
data: { const route = useRoute();
type: Object, const id = route.query?.id;
},
});
const terms = [ const terms = [
{ {
column: 'alarmRecordId', column: 'alarmRecordId',
termType: 'eq', termType: 'eq',
value: props.data.id, value: id,
type: 'and', type: 'and',
}, },
]; ];
@ -119,9 +107,6 @@ const emit = defineEmits(['closeLog']);
/** /**
* 关闭弹窗 * 关闭弹窗
*/ */
const clsoeModal = () => {
emit('closeLog');
};
const handleSearch = (e: any) => { const handleSearch = (e: any) => {
params.value = e; params.value = e;

View File

@ -62,8 +62,8 @@
</span> </span>
</Ellipsis> </Ellipsis>
<j-row :gutter="24"> <j-row :gutter="24">
<j-col :span="8"> <j-col :span="8" class="content-left">
<div class="content-des-title"> <div class="content-left-title">
{{ titleMap.get(slotProps.targetType) }} {{ titleMap.get(slotProps.targetType) }}
</div> </div>
<Ellipsis <Ellipsis
@ -73,7 +73,7 @@
> >
</j-col> </j-col>
<j-col :span="8"> <j-col :span="8">
<div class="content-des-title"> <div class="content-right-title">
最近告警时间 最近告警时间
</div> </div>
<Ellipsis <Ellipsis
@ -87,7 +87,7 @@
> >
</j-col> </j-col>
<j-col :span="8"> <j-col :span="8">
<div class="content-des-title">状态</div> <div class="content-right-title">状态</div>
<BadgeStatus <BadgeStatus
:status="slotProps.state.value" :status="slotProps.state.value"
:statusName="{ :statusName="{
@ -136,11 +136,6 @@
v-if="data.solveVisible" v-if="data.solveVisible"
@closeSolve="closeSolve" @closeSolve="closeSolve"
/> />
<SolveLog
:data="data.current"
v-if="data.logVisible"
@closeLog="closeLog"
/>
</div> </div>
</template> </template>
@ -292,7 +287,7 @@ const orgCol = [
}, },
]; ];
let params = ref({ let params:any = ref({
sorts: [{ name: 'alarmTime', order: 'desc' }], sorts: [{ name: 'alarmTime', order: 'desc' }],
terms: [], terms: [],
}); });
@ -412,8 +407,11 @@ const getActions = (
}, },
icon: 'FileTextOutlined', icon: 'FileTextOutlined',
onClick: () => { onClick: () => {
data.value.current = currentData; menuStory.jumpPage(
data.value.logVisible = true; 'rule-engine/Alarm/Log/Record',
{},
{ id: currentData.id },
);
}, },
}, },
]; ];
@ -434,4 +432,14 @@ const closeLog = () => {
}; };
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.content-left{
border-right: .2px solid rgba(0,0,0,0.2);
}
.content-right-title{
color: #666;
font-size: 12px
}
.content-left-title{
font-size: 18px
}
</style> </style>

View File

@ -188,11 +188,18 @@ const handOptionByColumn = (option: any) => {
} }
} }
watchEffect(() => { watch(() => [columnOptions.value, paramsValue.column], () => {
if (!props.value.error && props.value.column) { // option if (paramsValue.column) {
const option = getOption(columnOptions.value, paramsValue.column, 'id') const option = getOption(columnOptions.value, paramsValue.column, 'id')
if (option) { if (option && Object.keys(option).length) {
handOptionByColumn(option) handOptionByColumn(option)
if (props.value.error) {
emit('update:value', {
...props.value,
error: false
})
formItemContext.onFieldChange()
}
} else { } else {
emit('update:value', { emit('update:value', {
...props.value, ...props.value,

View File

@ -213,7 +213,7 @@ const rules = [
return Promise.reject(new Error('请选择操作符')) return Promise.reject(new Error('请选择操作符'))
} }
if (v.value === undefined) { if (v.value.value === undefined) {
return Promise.reject(new Error('请选择或输入参数值')) return Promise.reject(new Error('请选择或输入参数值'))
} else { } else {
if ( if (

View File

@ -29,7 +29,7 @@
<div class='actions-terms-list-content'> <div class='actions-terms-list-content'>
<template v-if='showWhen'> <template v-if='showWhen'>
<TermsItem <WhenItem
v-for='(item, index) in whenData' v-for='(item, index) in whenData'
:key='item.key' :key='item.key'
:name='index' :name='index'
@ -37,9 +37,7 @@
:isFirst='index === 0' :isFirst='index === 0'
:isLast='index === whenData.length -1' :isLast='index === whenData.length -1'
:branchName='name' :branchName='name'
:whenName='index'
:data='item' :data='item'
:isFrist='index === 0'
/> />
</template> </template>
<span v-else class='when-add' @click='addWhen' :style='{ padding: isFirst ? "16px 0" : 0 }'> <span v-else class='when-add' @click='addWhen' :style='{ padding: isFirst ? "16px 0" : 0 }'>
@ -67,10 +65,11 @@
<script lang='ts' setup name='Branches'> <script lang='ts' setup name='Branches'>
import type { PropType } from 'vue' import type { PropType } from 'vue'
import type { ActionBranchesProps } from '@/views/rule-engine/Scene/typings' import type { ActionBranchesProps } from '@/views/rule-engine/Scene/typings'
import TermsItem from './TermsItem.vue' import WhenItem from './WhenItem.vue'
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useSceneStore } from 'store/scene' import { useSceneStore } from 'store/scene'
import Action from '../../action/index.vue' import Action from '../../action/index.vue'
import { randomString } from '@/utils/utils'
const sceneStore = useSceneStore() const sceneStore = useSceneStore()
const { data: FormModel } = storeToRefs(sceneStore) const { data: FormModel } = storeToRefs(sceneStore)
@ -110,7 +109,11 @@ const whenData = computed(() => {
}) })
const onDelete = () => { const onDelete = () => {
FormModel.value.branches?.splice(props.name, 1) if (FormModel.value.branches?.length == 2) {
FormModel.value.branches?.splice(props.name, 1, null)
} else {
FormModel.value.branches?.splice(props.name, 1)
}
} }
const onDeleteAll = () => { const onDeleteAll = () => {
@ -133,24 +136,31 @@ const mouseout = () => {
} }
const addWhen = () => { const addWhen = () => {
const whenItem = { const terms = {
key: `when_${new Date().getTime()}`,
type: 'and', type: 'and',
terms: [ terms: [
{ {
column: undefined, terms: [
value: { {
source: 'fixed', column: undefined,
value: undefined value: {
}, source: 'fixed',
termType: undefined, value: undefined
key: 'params_1', },
termType: undefined,
key: `params_${randomString()}`,
type: 'and',
}
],
key: `terms_2_${randomString()}`,
type: 'and', type: 'and',
} }
] ],
key: `terms_${randomString()}`
} }
FormModel.value.branches?.[props.name].when.push(whenItem) FormModel.value.branches?.[props.name].when?.push(terms)
FormModel.value.branches?.push(null) FormModel.value.branches?.push(null as any)
FormModel.value.options!.when[props.name]?.terms.push({ termType: '并且', terms: [['','eq','','and']]})
} }
const optionsClass = computed(() => { const optionsClass = computed(() => {

View File

@ -81,6 +81,7 @@ import { ContextKey } from './util'
import { useSceneStore } from 'store/scene' import { useSceneStore } from 'store/scene'
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { Form } from 'jetlinks-ui-components' import { Form } from 'jetlinks-ui-components'
import { pick } from 'lodash-es'
const sceneStore = useSceneStore() const sceneStore = useSceneStore()
const { data: formModel } = storeToRefs(sceneStore) const { data: formModel } = storeToRefs(sceneStore)
@ -187,11 +188,18 @@ const handOptionByColumn = (option: any) => {
} }
} }
watchEffect(() => { watch(() => [columnOptions.value, paramsValue.column], () => {
if (!props.value.error && props.value.column) { // option if (paramsValue.column) {
const option = getOption(columnOptions.value, paramsValue.column, 'column') const option = getOption(columnOptions.value, paramsValue.column, 'column')
if (option) { if (option && Object.keys(option).length) {
handOptionByColumn(option) handOptionByColumn(option)
if (props.value.error) {
emit('update:value', {
...props.value,
error: false
})
formItemContext.onFieldChange()
}
} else { } else {
emit('update:value', { emit('update:value', {
...props.value, ...props.value,
@ -200,7 +208,7 @@ watchEffect(() => {
formItemContext.onFieldChange() formItemContext.onFieldChange()
} }
} }
}) }, { immediate: true, deep: true })
const showDouble = computed(() => { const showDouble = computed(() => {
const isRange = paramsValue.termType ? arrayParamsKey.includes(paramsValue.termType) : false const isRange = paramsValue.termType ? arrayParamsKey.includes(paramsValue.termType) : false
@ -225,15 +233,18 @@ const mouseout = () => {
} }
const columnSelect = (option: any) => { const columnSelect = (option: any) => {
paramsValue.termType = 'eq' const termTypes = option.termTypes
paramsValue.termType = termTypes?.length ? termTypes[0].id : 'eq'
paramsValue.value = { paramsValue.value = {
source: tabsOptions.value[0].key, source: tabsOptions.value[0].key,
value: undefined value: undefined
} }
handOptionByColumn(option)
emit('update:value', { ...paramsValue }) emit('update:value', { ...paramsValue })
formItemContext.onFieldChange() formItemContext.onFieldChange()
formModel.value.options!.when[props.whenName].terms[props.termsName].terms[props.name][0] = option.name formModel.value.options!.when[props.branchName].terms[props.whenName].terms[props.name][0] = option.name
formModel.value.options!.when[props.whenName].terms[props.termsName].terms[props.name][1] = paramsValue.termType formModel.value.options!.when[props.branchName].terms[props.whenName].terms[props.name][1] = paramsValue.termType
} }
const termsTypeSelect = (e: { key: string, name: string }) => { const termsTypeSelect = (e: { key: string, name: string }) => {
@ -244,16 +255,18 @@ const termsTypeSelect = (e: { key: string, name: string }) => {
} }
emit('update:value', { ...paramsValue }) emit('update:value', { ...paramsValue })
formItemContext.onFieldChange() formItemContext.onFieldChange()
formModel.value.options!.when[props.whenName].terms[props.termsName].terms[props.name][1] = e.name formModel.value.options!.when[props.branchName].terms[props.whenName].terms[props.name][1] = e.name
} }
const valueSelect = (_: any, label: string, labelObj: Record<number, any>) => { const valueSelect = (_: any, label: string, labelObj: Record<number, any>) => {
emit('update:value', { ...paramsValue })
formItemContext.onFieldChange() formItemContext.onFieldChange()
formModel.value.options!.when[props.whenName].terms[props.termsName].terms[props.name][2] = labelObj formModel.value.options!.when[props.branchName].terms[props.whenName].terms[props.name][2] = labelObj
} }
const typeSelect = (e: any) => { const typeSelect = (e: any) => {
formModel.value.options!.when[props.whenName].terms[props.termsName].terms[props.name][3] = e.label formModel.value.options!.when[props.branchName].terms[props.whenName].terms[props.name][3] = e.label
} }
const termAdd = () => { const termAdd = () => {
@ -267,17 +280,17 @@ const termAdd = () => {
type: 'and', type: 'and',
key: `params_${new Date().getTime()}` key: `params_${new Date().getTime()}`
} }
formModel.value.branches?.[props.branchName]?.when?.[props.whenName]?.terms?.push(terms) formModel.value.branches?.[props.branchName]?.when?.[props.whenName]?.terms?.[props.termsName]?.terms?.push(terms)
formModel.value.options!.when[props.whenName].terms[props.termsName].terms[props.name].push(['', '', '', '并且']) formModel.value.options!.when[props.branchName].terms[props.whenName].terms[props.termsName].push(['', '', '', '并且'])
} }
const onDelete = () => { const onDelete = () => {
formModel.value.branches?.[props.branchName]?.when?.[props.whenName]?.terms?.splice(props.name, 1) formModel.value.branches?.[props.branchName]?.when?.[props.whenName]?.terms?.[props.termsName]?.terms?.splice(props.name, 1)
formModel.value.options!.when[props.whenName].terms[props.termsName].terms.splice(props.name, 1) formModel.value.options!.when[props.branchName].terms[props.whenName].terms.splice(props.name, 1)
} }
nextTick(() => { nextTick(() => {
Object.assign(paramsValue, props.value) Object.assign(paramsValue, pick(props.value, ['column', 'options', 'termType', 'terms', 'type', 'value']))
}) })
</script> </script>

View File

@ -1,67 +1,46 @@
<template> <template>
<div class='terms-params'> <div
<div class='terms-params-warp'> class='terms-params-content'
<div v-if='!isFirst' class='term-type-warp'> @mouseover='mouseover'
<DropdownButton @mouseout='mouseout'
:options='[ >
{ label: "并且", value: "and" }, <j-popconfirm
{ label: "或者", value: "or" }, title='确认删除?'
]' @confirm='onDelete'
type='type' >
v-model:value='formModel.branches[branchName].when[whenName].type' <div v-show='showDelete' class='terms-params-delete'>
@select='typeChange' <AIcon type='CloseOutlined' />
/>
</div> </div>
<div </j-popconfirm>
class='terms-params-content'
@mouseover='mouseover'
@mouseout='mouseout'
>
<j-popconfirm
title='确认删除?'
@confirm='onDelete'
>
<div v-show='showDelete' class='terms-params-delete'>
<AIcon type='CloseOutlined' />
</div>
</j-popconfirm>
<j-form-item <j-form-item
v-for='(item, index) in termsData' v-for='(item, index) in termsData'
:key='item.key' :key='item.key'
:name='["branches", branchName, "when", whenName, "terms", index]' :name='["branches", branchName, "when", whenName, "terms", props.name, "terms", index]'
:rules='rules' :rules='rules'
> >
<ParamsItem <ParamsItem
v-model:value='formModel.branches[branchName].when[whenName].terms[index]' v-model:value='formModel.branches[branchName].when[whenName].terms[props.name].terms[index]'
:isFirst='index === 0' :isFirst='index === 0'
:isLast='index === termsData.length - 1' :isLast='index === termsData.length - 1'
:showDeleteBtn='termsData.length !== 1' :showDeleteBtn='termsData.length !== 1'
:name='index' :name='index'
:termsName='name' :termsName='name'
:whenName='whenName' :whenName='whenName'
:branchName='branchName' :branchName='branchName'
/> />
</j-form-item> </j-form-item>
</div>
<div class='terms-group-add' @click='addTerms' v-if='isLast'>
<div class='terms-content'>
<AIcon type='PlusOutlined' />
<span>分组</span>
</div>
</div>
</div>
</div> </div>
</template> </template>
<script setup lang='ts' name='TermsItem'> <script setup lang='ts' name='TermsItem'>
import type { PropType } from 'vue' import type { PropType } from 'vue'
import type { TermsType } from '@/views/rule-engine/Scene/typings' import type { TermsType } from '@/views/rule-engine/Scene/typings'
import DropdownButton from '../DropdownButton'
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useSceneStore } from 'store/scene' import { useSceneStore } from 'store/scene'
import ParamsItem from './ParamsItem.vue' import ParamsItem from './ParamsItem.vue'
import { isArray } from 'lodash-es' import { isArray } from 'lodash-es'
import { randomString } from '@/utils/utils'
const sceneStore = useSceneStore() const sceneStore = useSceneStore()
const { data: formModel } = storeToRefs(sceneStore) const { data: formModel } = storeToRefs(sceneStore)
@ -107,7 +86,7 @@ const props = defineProps({
const rules = [ const rules = [
{ {
validator(_: any, v: any) { validator: async (_: any, v: any) => {
if (v !== undefined && !v.error) { if (v !== undefined && !v.error) {
if (!Object.keys(v).length) { if (!Object.keys(v).length) {
return Promise.reject(new Error('该数据已发生变更,请重新配置')); return Promise.reject(new Error('该数据已发生变更,请重新配置'));
@ -115,22 +94,17 @@ const rules = [
if (!v.column) { if (!v.column) {
return Promise.reject(new Error('请选择参数')); return Promise.reject(new Error('请选择参数'));
} }
if (!v.termType) { if (!v.termType) {
return Promise.reject(new Error('请选择操作符')); return Promise.reject(new Error('请选择操作符'));
} }
if (!v.value?.value) {
if (v.value === undefined) { return Promise.reject(new Error('请选择或输入参数值'));
}
if (
isArray(v.value.value) &&
v.value.value.some((_v: any) => _v === undefined)
) {
return Promise.reject(new Error('请选择或输入参数值')); return Promise.reject(new Error('请选择或输入参数值'));
} else {
if (
isArray(v.value.value) &&
v.value.value.some((_v: any) => _v === undefined)
) {
return Promise.reject(new Error('请选择或输入参数值'));
} else if (v.value.value === undefined) {
return Promise.reject(new Error('请选择或输入参数值'));
}
} }
} else { } else {
return Promise.reject(new Error('请选择参数')); return Promise.reject(new Error('请选择参数'));
@ -159,12 +133,8 @@ const mouseout = () => {
} }
const onDelete = () => { const onDelete = () => {
formModel.value.branches?.[props.branchName]?.when?.splice(props.name, 1) formModel.value.branches?.[props.branchName]?.when?.splice(props.whenName, 1)
formModel.value.options!.when[props.whenName].terms.splice(props.name, 1) formModel.value.options!.when[props.branchName].terms.splice(props.whenName, 1)
}
const typeChange = (e: any) => {
formModel.value.options!.when[props.whenName].terms[props.name].termType = e.label
} }
const addTerms = () => { const addTerms = () => {
@ -178,16 +148,14 @@ const addTerms = () => {
value: undefined value: undefined
}, },
termType: undefined, termType: undefined,
key: 'params_1', key: `params_${randomString()}`,
type: 'and', type: 'and',
} }
], ],
key: `terms_${new Date().getTime()}` key: `terms_${randomString()}`
} }
formModel.value.branches?.[props.branchName]?.when?.push(terms) formModel.value.branches?.[props.branchName]?.when?.[props.whenName].terms?.push(terms)
formModel.value.options!.when[props.whenName].push({ formModel.value.options!.when[props.branchName].terms[props.whenName].terms.push(['','eq','','and'])
terms: [{ termType: '并且', terms: [['','eq','','and']]}]
})
} }
</script> </script>

View File

@ -0,0 +1,122 @@
<template>
<div class='terms-params'>
<div class='terms-params-warp'>
<div v-if='!isFirst' class='term-type-warp'>
<DropdownButton
:options='[
{ label: "并且", value: "and" },
{ label: "或者", value: "or" },
]'
type='type'
v-model:value='formModel.branches[branchName].when[name].type'
@select='typeChange'
/>
</div>
<TermsItem
v-for='(item, index) in termsData'
:key='item.key'
:branchName='branchName'
:whenName='props.name'
:name='index'
:showDeleteBtn='showDeleteBtn'
:isFirst='index === 0'
:isLast='index === termsData.length -1'
:data='item'
/>
<div class='terms-group-add' @click='addWhen' v-if='isLast'>
<div class='terms-content'>
<AIcon type='PlusOutlined' />
<span>分组</span>
</div>
</div>
</div>
</div>
</template>
<script setup lang='ts' name='WhenItem'>
import type { PropType } from 'vue'
import TermsItem from './TermsItem.vue'
import { TermsType } from '@/views/rule-engine/Scene/typings'
import { useSceneStore } from 'store/scene'
import DropdownButton from '../DropdownButton'
import { storeToRefs } from 'pinia';
import { randomString } from '@/utils/utils'
const sceneStore = useSceneStore()
const { data: formModel } = storeToRefs(sceneStore)
const props = defineProps({
isFirst: {
type: Boolean,
default: true
},
data: {
type: Object as PropType<TermsType>,
default: () => ({
when: [],
shakeLimit: {},
then: []
})
},
showDeleteBtn: {
type: Boolean,
default: true
},
class: {
type: String,
default: ''
},
name: {
type: Number,
default: 0
},
branchName: {
type: Number,
default: 0
},
isLast: {
type: Boolean,
default: true
}
})
const termsData = computed(() => {
return props.data.terms
})
const typeChange = (e: any) => {
formModel.value.options!.when[props.name].terms[props.name].termType = e.label
}
const addWhen = () => {
const terms = {
type: 'and',
terms: [
{
terms: [
{
column: undefined,
value: {
source: 'fixed',
value: undefined
},
termType: undefined,
key: `params_${randomString()}`,
type: 'and',
}
],
key: `terms_2_${randomString()}`,
type: 'and',
}
],
key: `terms_${randomString()}`
}
formModel.value.branches?.[props.branchName]?.when?.push(terms)
formModel.value.options?.when?.[props.branchName]?.terms.push({ termType: '并且', terms: [['','eq','','and']]})
}
</script>
<style scoped>
</style>

View File

@ -113,6 +113,7 @@
} }
.terms-params { .terms-params {
position: relative;
display: flex; display: flex;
flex-shrink: 0; flex-shrink: 0;

View File

@ -65,7 +65,8 @@ const save = async () => {
const formData = await sceneForm.value.validateFields() const formData = await sceneForm.value.validateFields()
if (formData) { if (formData) {
loading.value = true loading.value = true
const resp = await modify(data.value.id!, data.value).then(res => res) const branches = data.value.branches?.filter(item => item)
const resp = await modify(data.value.id!, { ...data.value, branches }).then(res => res)
loading.value = false loading.value = false
if (resp.success) { if (resp.success) {
menuStore.jumpPage('rule-engine/Scene') menuStore.jumpPage('rule-engine/Scene')

View File

@ -311,7 +311,7 @@ const getActions = (
}, },
icon: 'LikeOutlined', icon: 'LikeOutlined',
popConfirm: { popConfirm: {
title: '', title: '确认手动触发?',
onConfirm: async () => { onConfirm: async () => {
const resp = await _execute(data.id); const resp = await _execute(data.id);
if (resp.status === 200) { if (resp.status === 200) {

View File

@ -143,6 +143,9 @@ import {
} from '@/api/system/department'; } from '@/api/system/department';
import { message } from 'jetlinks-ui-components'; import { message } from 'jetlinks-ui-components';
import { dictType } from '../typing'; import { dictType } from '../typing';
import { useDepartmentStore } from '@/store/department';
const departmentStore = useDepartmentStore();
const emits = defineEmits(['confirm', 'update:visible']); const emits = defineEmits(['confirm', 'update:visible']);
const props = defineProps<{ const props = defineProps<{
@ -154,6 +157,9 @@ const props = defineProps<{
}>(); }>();
// //
const loading = ref(false); const loading = ref(false);
// , , id
const queryCount = ref(0);
const confirm = () => { const confirm = () => {
if (table.selectedRows.length < 1) { if (table.selectedRows.length < 1) {
return message.warning('请先勾选数据'); return message.warning('请先勾选数据');
@ -167,6 +173,11 @@ const confirm = () => {
permission: item.selectPermissions, permission: item.selectPermissions,
})); }));
if (params.length === 1) {
// , ,
departmentStore.setProductId(params[0].assetIdList[0]);
}
loading.value = true; loading.value = true;
bindDeviceOrProductList_api(props.assetType, params) bindDeviceOrProductList_api(props.assetType, params)
.then(() => { .then(() => {
@ -259,8 +270,10 @@ const table: any = {
// //
onSelectChange: (row: any) => { onSelectChange: (row: any) => {
// //
if (!row.permissionList.find((item: any) => item.value === 'share')) if (!row.permissionList.find((item: any) => item.value === 'share')) {
message.warning('该资产不支持共享');
return; return;
}
const selectedRowKeys = table._selectedRowKeys.value; const selectedRowKeys = table._selectedRowKeys.value;
const index = selectedRowKeys.indexOf(row.id); const index = selectedRowKeys.indexOf(row.id);
@ -330,7 +343,9 @@ const table: any = {
resolve({ resolve({
code: 200, code: 200,
result: { result: {
data: data.sort((a, b) => a.createTime - b.createTime), data: data.sort(
(a, b) => a.createTime - b.createTime,
),
pageIndex, pageIndex,
pageSize, pageSize,
total, total,
@ -394,6 +409,7 @@ const table: any = {
}), }),
// //
requestFun: async (oParams: any) => { requestFun: async (oParams: any) => {
queryCount.value += 1;
if (props.parentId) { if (props.parentId) {
const terms = [ const terms = [
{ {
@ -411,9 +427,24 @@ const table: any = {
], ],
}, },
}, },
{
column: 'productId$product-info',
type: 'and',
value: `id is ${departmentStore.productId}`,
},
], ],
}, },
]; ];
if (
props.assetType !== 'device' ||
!departmentStore.productId ||
queryCount.value > 1 ||
departmentStore.optType === 'handle'
) {
// |id|(queryCount+1)|, id
terms[0].terms.pop();
}
if (oParams.terms && oParams.terms.length > 0) if (oParams.terms && oParams.terms.length > 0)
terms.unshift({ terms: oParams.terms }); terms.unshift({ terms: oParams.terms });
const params = { const params = {

View File

@ -18,6 +18,7 @@
placeholder="请选择上级组织" placeholder="请选择上级组织"
:tree-data="treeData" :tree-data="treeData"
:field-names="{ value: 'id' }" :field-names="{ value: 'id' }"
@change="handleTreeSelectChange"
> >
<template #title="{ name }"> {{ name }} </template> <template #title="{ name }"> {{ name }} </template>
</j-tree-select> </j-tree-select>
@ -60,6 +61,21 @@ import {
updateDepartment_api, updateDepartment_api,
} from '@/api/system/department'; } from '@/api/system/department';
type treeType = {
id: string;
parentId?: string;
name: string;
sortIndex: string | number;
children?: treeType[];
disabled?: boolean;
};
type formType = {
id?: string;
parentId?: string;
name: string;
sortIndex: string | number;
};
const emits = defineEmits(['refresh', 'update:visible']); const emits = defineEmits(['refresh', 'update:visible']);
const props = defineProps<{ const props = defineProps<{
treeData: any[]; treeData: any[];
@ -91,8 +107,8 @@ const treeData = computed(() => {
}); });
/** /**
* 在给定的树中通过id匹配 * 在给定的树中通过id匹配
* @param node * @param node
* @param id * @param id
*/ */
const findItemById = (node: treeType[], id: string): treeType | null => { const findItemById = (node: treeType[], id: string): treeType | null => {
let result = null; let result = null;
@ -107,7 +123,7 @@ const findItemById = (node: treeType[], id: string): treeType | null => {
}; };
/** /**
* 将此树下的所有节点禁用 * 将此树下的所有节点禁用
* @param treeNode * @param treeNode
*/ */
const filterTree = (treeNode: treeType[]) => { const filterTree = (treeNode: treeType[]) => {
if (treeNode.length < 1) return; if (treeNode.length < 1) return;
@ -160,18 +176,15 @@ const form = reactive({
}); });
form.init(); form.init();
type treeType = { /**
id: string; * 上级组织选择改变
parentId?: string; */
name: string; const handleTreeSelectChange = () => {
sortIndex: string | number; //
children?: treeType[]; const parent = treeData.value.find((f: any) => f.id === form.data.parentId);
disabled?: boolean; // , +1, , 1
}; form.data.sortIndex = parent?.children
type formType = { ? parent.children[parent.children.length - 1].sortIndex + 1
id?: string; : 1;
parentId?: string;
name: string;
sortIndex: string | number;
}; };
</script> </script>

View File

@ -8,7 +8,12 @@
visible visible
@cancel="emits('update:visible', false)" @cancel="emits('update:visible', false)"
> >
<div> <a-alert
message="只能分配有'共享'权限的资产数据"
type="warning"
show-icon
/>
<div style="margin-top: 5px;">
<span>资产权限</span> <span>资产权限</span>
<j-checkbox-group <j-checkbox-group
v-model:value="form.permission" v-model:value="form.permission"

View File

@ -27,6 +27,7 @@
v-if="treeData.length > 0" v-if="treeData.length > 0"
:tree-data="treeData" :tree-data="treeData"
v-model:selected-keys="selectedKeys" v-model:selected-keys="selectedKeys"
v-model:expandedKeys="expandedKeys"
:fieldNames="{ key: 'id' }" :fieldNames="{ key: 'id' }"
> >
<template #title="{ name, data }"> <template #title="{ name, data }">
@ -105,6 +106,7 @@ const sourceTree = ref<any[]>([]); // 源数据
const treeMap = new Map(); // map const treeMap = new Map(); // map
const treeData = ref<any[]>([]); // const treeData = ref<any[]>([]); //
const selectedKeys = ref<string[]>([]); // const selectedKeys = ref<string[]>([]); //
const expandedKeys = ref<string[] | number[]>([]);
function getTree(cb?: Function) { function getTree(cb?: Function) {
loading.value = true; loading.value = true;
@ -149,6 +151,7 @@ const search = debounce(() => {
treeData.value = ArrayToTree(cloneDeep([...treeArray.values()])); treeData.value = ArrayToTree(cloneDeep([...treeArray.values()]));
} else { } else {
treeData.value = ArrayToTree(cloneDeep([...treeMap.values()])); treeData.value = ArrayToTree(cloneDeep([...treeMap.values()]));
expandedKeys.value = [];
} }
function dig(_data: any[]): any { function dig(_data: any[]): any {

View File

@ -22,7 +22,7 @@
<PermissionButton <PermissionButton
:hasPermission="`${permission}:assert`" :hasPermission="`${permission}:assert`"
type="primary" type="primary"
@click="table.clickAdd" @click="table.clickAdd('handle')"
> >
<AIcon type="PlusOutlined" />资产分配 <AIcon type="PlusOutlined" />资产分配
</PermissionButton> </PermissionButton>
@ -211,6 +211,9 @@ import { intersection } from 'lodash-es';
import type { dictType, optionsType } from '../typing'; import type { dictType, optionsType } from '../typing';
import { message } from 'jetlinks-ui-components'; import { message } from 'jetlinks-ui-components';
import { useDepartmentStore } from '@/store/department';
const departmentStore = useDepartmentStore();
const permission = 'system/Department'; const permission = 'system/Department';
@ -248,7 +251,7 @@ const columns = [
rename: 'productId$product-info', rename: 'productId$product-info',
type: 'select', type: 'select',
handleValue(value: string) { handleValue(value: string) {
return `id is ${value}` return `id is ${value}`;
}, },
options: () => options: () =>
new Promise((resolve) => { new Promise((resolve) => {
@ -291,9 +294,9 @@ const columns = [
search: { search: {
type: 'select', type: 'select',
options: [ options: [
{ label: '禁用', value: 'notActive' }, { label: '禁用', value: 'notActive' },
{ label: '离线', value: 'offline' }, { label: '离线', value: 'offline' },
{ label: '在线', value: 'online' }, { label: '在线', value: 'online' },
], ],
}, },
scopedSlots: true, scopedSlots: true,
@ -465,7 +468,9 @@ const table = {
}; };
} }
}, },
clickAdd: () => { clickAdd: (type?: string) => {
// : type = 'handle': , !type,
departmentStore.setType(type)
dialogs.addShow = true; dialogs.addShow = true;
}, },
clickEdit: (row?: any) => { clickEdit: (row?: any) => {
@ -518,7 +523,7 @@ const dialogs = reactive({
}); });
table.init(); table.init();
nextTick(() => { watchEffect(() => {
props.bindBool && table.clickAdd(); props.bindBool && table.clickAdd();
emits('update:bindBool', false); emits('update:bindBool', false);
}); });

View File

@ -27,8 +27,13 @@
@cancelSelect="table.cancelSelect" @cancelSelect="table.cancelSelect"
model="TABLE" model="TABLE"
:defaultParams="{ :defaultParams="{
pageSize: 10,
sorts: [{ name: 'createTime', order: 'desc' }], sorts: [{ name: 'createTime', order: 'desc' }],
}" }"
:pagination="{
showSizeChanger: true,
pageSizeOptions: ['10', '20', '50', '100'],
}"
/> />
</div> </div>
</j-modal> </j-modal>

View File

@ -16,6 +16,13 @@
}" }"
@cancelSelect="table.cancelSelect" @cancelSelect="table.cancelSelect"
model="TABLE" model="TABLE"
:defaultParams="{
pageSize: 10,
}"
:pagination="{
showSizeChanger: true,
pageSizeOptions: ['10', '20', '50', '100'],
}"
> >
<template #headerTitle> <template #headerTitle>
<PermissionButton <PermissionButton

View File

@ -11,6 +11,7 @@
{ {
required: true, required: true,
message: '请上传图标', message: '请上传图标',
trigger: 'change',
}, },
]" ]"
style="flex: 0 0 186px" style="flex: 0 0 186px"
@ -45,11 +46,22 @@
label="名称" label="名称"
name="name" name="name"
:rules="[ :rules="[
{ required: true, message: '请输入名称',trigger: 'change', }, {
{ max: 64, message: '最多可输入64个字符', trigger: 'change', }, required: true,
message: '请输入名称',
trigger: 'change',
},
{
max: 64,
message: '最多可输入64个字符',
trigger: 'change',
},
]" ]"
> >
<j-input v-model:value="form.data.name" /> <j-input
v-model:value="form.data.name"
placeholder="请输入名称"
/>
</j-form-item> </j-form-item>
</j-col> </j-col>
<j-col :span="12"> <j-col :span="12">
@ -57,15 +69,26 @@
label="编码" label="编码"
name="code" name="code"
:rules="[ :rules="[
{ required: true, message: '请输入编码', trigger: 'change', }, {
{ max: 64, message: '最多可输入64个字符', trigger: 'change', }, required: true,
message: '请输入编码',
trigger: 'change',
},
{
max: 64,
message: '最多可输入64个字符',
trigger: 'change',
},
{ {
validator: form.checkCode, validator: form.checkCode,
trigger: 'blur', trigger: 'blur',
}, },
]" ]"
> >
<j-input v-model:value="form.data.code" /> <j-input
v-model:value="form.data.code"
placeholder="请输入编码"
/>
</j-form-item> </j-form-item>
</j-col> </j-col>
<j-col :span="12"> <j-col :span="12">
@ -80,7 +103,10 @@
{ max: 128, message: '最多可输入128字符' }, { max: 128, message: '最多可输入128字符' },
]" ]"
> >
<j-input v-model:value="form.data.url" /> <j-input
v-model:value="form.data.url"
placeholder="请输入页面地址"
/>
</j-form-item> </j-form-item>
</j-col> </j-col>
<j-col :span="12"> <j-col :span="12">
@ -94,7 +120,11 @@
}, },
]" ]"
> >
<j-input v-model:value="form.data.sortIndex" /> <j-input-number
v-model:value="form.data.sortIndex"
placeholder="请输入排序"
style="width: 100%"
/>
</j-form-item> </j-form-item>
</j-col> </j-col>
</j-row> </j-row>
@ -104,6 +134,8 @@
<j-textarea <j-textarea
v-model:value="form.data.describe" v-model:value="form.data.describe"
:rows="4" :rows="4"
show-count
:maxlength="200"
placeholder="请输入说明" placeholder="请输入说明"
/> />
</j-form-item> </j-form-item>
@ -277,7 +309,7 @@ const form = reactive({
accessSupport: accessSupport:
resp.result?.accessSupport?.value || 'unsupported', resp.result?.accessSupport?.value || 'unsupported',
}; };
form.sourceCode = resp.result.code form.sourceCode = resp.result.code;
}); });
// //
getMenuTree_api({ paging: false }).then((resp: any) => { getMenuTree_api({ paging: false }).then((resp: any) => {
@ -292,10 +324,11 @@ const form = reactive({
}); });
}, },
checkCode: async (_rule: Rule, value: string): Promise<any> => { checkCode: async (_rule: Rule, value: string): Promise<any> => {
if (!value) return Promise.reject('请输入编码'); if (!value) return Promise.reject('');
else if (value.length > 64) return Promise.reject('最多可输入64个字符'); else if (value.length > 64) return Promise.reject('最多可输入64个字符');
// //
else if (routeParams.id && value === form.sourceCode) return Promise.resolve(''); else if (routeParams.id && value === form.sourceCode)
return Promise.resolve('');
else { else {
const resp: any = await validCode_api({ const resp: any = await validCode_api({
code: value, code: value,
@ -372,6 +405,13 @@ type assetType = {
label: string; label: string;
value: string; value: string;
}; };
watch(
() => form.data.icon,
() => {
basicFormRef.value?.validate();
},
);
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -32,9 +32,7 @@
</template> </template>
<template #createTime="slotProps"> <template #createTime="slotProps">
{{ {{
moment(slotProps.createTime).format( dayjs(slotProps.createTime).format('YYYY-MM-DD HH:mm:ss')
'YYYY-MM-DD HH:mm:ss',
)
}} }}
</template> </template>
<template #action="slotProps"> <template #action="slotProps">
@ -81,7 +79,7 @@ import PermissionButton from '@/components/PermissionButton/index.vue';
import { getMenuTree_api, delMenuInfo_api } from '@/api/system/menu'; import { getMenuTree_api, delMenuInfo_api } from '@/api/system/menu';
import { message } from 'jetlinks-ui-components'; import { message } from 'jetlinks-ui-components';
import moment from 'moment'; import dayjs from 'dayjs';
const permission = 'system/Menu'; const permission = 'system/Menu';
@ -143,6 +141,7 @@ const columns = [
type: 'date', type: 'date',
}, },
width: 180, width: 180,
scopedSlots: true,
}, },
{ {
title: '操作', title: '操作',

View File

@ -13,7 +13,14 @@
:request="getPermission_api" :request="getPermission_api"
model="TABLE" model="TABLE"
:params="queryParams" :params="queryParams"
:defaultParams="{ sorts: [{ name: 'id', order: 'asc' }] }" :defaultParams="{
pageSize: 10,
sorts: [{ name: 'id', order: 'asc' }],
}"
:pagination="{
showSizeChanger: true,
pageSizeOptions: ['10', '20', '50', '100'],
}"
> >
<template #headerTitle> <template #headerTitle>
<PermissionButton <PermissionButton

View File

@ -19,6 +19,7 @@
:dataSource="paramsTable" :dataSource="paramsTable"
:pagination="false" :pagination="false"
size="small" size="small"
bordered
> >
<template #bodyCell="{ column, record, index }"> <template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'name'"> <template v-if="column.key === 'name'">
@ -65,8 +66,7 @@
</template> </template>
<template v-else-if="column.key === 'action'"> <template v-else-if="column.key === 'action'">
<PermissionButton <PermissionButton
type="link" type="text"
:hasPermission="`{permission}:delete`"
:popConfirm="{ :popConfirm="{
title: `确定删除`, title: `确定删除`,
onConfirm: () => onConfirm: () =>
@ -80,16 +80,35 @@
</j-table> </j-table>
</j-form> </j-form>
<j-pagination <div
:pageSize="requestBody.pageSize" class="pager"
v-model:current="requestBody.pageNum" v-if="
:total="requestBody.params.paramsTable.length" requestBody.params.paramsTable.length &&
hideOnSinglePage requestBody.pageSize
style="text-align: center" "
/> >
<j-select
v-model:value="requestBody.pageNum"
style="width: 60px"
>
<j-select-option
v-for="(val, i) in pageArr"
:value="i + 1"
>{{ i + 1 }}</j-select-option
>
</j-select>
<j-pagination
:pageSize="requestBody.pageSize"
v-model:current="requestBody.pageNum"
:total="requestBody.params.paramsTable.length"
hideOnSinglePage
style="text-align: center"
/>
</div>
<j-button <j-button
type="dashed"
@click="requestBody.addRow" @click="requestBody.addRow"
style="width: 100%; text-align: center" style="width: 100%; text-align: center; margin-top: 5px"
> >
<AIcon type="PlusOutlined" />新增 <AIcon type="PlusOutlined" />新增
</j-button> </j-button>
@ -250,6 +269,12 @@ type requestObj = {
name: string; name: string;
value: string; value: string;
}; };
const pageArr = computed(() => {
const maxPageNum = Math.ceil(
requestBody.params.paramsTable.length / requestBody.pageSize,
);
return new Array(maxPageNum).fill(1);
});
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@ -305,10 +330,25 @@ type requestObj = {
} }
} }
.table { .table {
margin-bottom: 22px;
:deep(.ant-table-cell) { :deep(.ant-table-cell) {
padding: 0 8px; padding: 0 8px;
height: 56px; height: 56px;
} }
.pager {
display: flex;
justify-content: center;
margin: 8px 0;
.ant-pagination {
margin-left: 8px;
:deep(.ant-pagination-item) {
display: none;
}
}
}
}
:deep(.ant-form-item) {
margin-bottom: 0;
} }
} }
} }

View File

@ -77,9 +77,12 @@ const rowSelection = {
}); });
} }
}, },
onChange: (keys: string[]) => {
rowSelection.selectedRowKeys.value = keys;
},
selectedRowKeys: ref<string[]>([]), selectedRowKeys: ref<string[]>([]),
}; };
const save = () => { const save = async () => {
const keys = props.selectedRowKeys; const keys = props.selectedRowKeys;
const removeKeys = props.sourceKeys.filter((key) => !keys.includes(key)); const removeKeys = props.sourceKeys.filter((key) => !keys.includes(key));
@ -87,13 +90,20 @@ const save = () => {
if (props.mode === 'api') { if (props.mode === 'api') {
// api // api
removeKeys.length && // removeKeys.length &&
delOperations_api(removeKeys) // delOperations_api(removeKeys)
.finally(() => addOperations_api(addKeys)) // .finally(() => addOperations_api(addKeys))
.then(() => { // .then(() => {
message.success('操作成功'); // message.success('');
emits('refresh') // emits('refresh');
}); // });
// fix: bug#10829
removeKeys.length && (await delOperations_api(removeKeys));
const res = await addOperations_api(addKeys);
if (res.success) {
message.success('操作成功');
emits('refresh');
}
} else if (props.mode === 'appManger') { } else if (props.mode === 'appManger') {
const removeItems = removeKeys.map((key) => ({ const removeItems = removeKeys.map((key) => ({
id: key, id: key,

View File

@ -1,15 +1,20 @@
<template> <template>
<j-tree <j-spin :spinning="spinning">
:tree-data="treeData" <template #indicator>
@select="clickSelectItem" <AIcon type="LoadingOutlined" />
v-model:selected-keys="selectedKeys"
showLine
class="left-tree-container"
>
<template #title="{ name }">
{{ name }}
</template> </template>
</j-tree> <j-tree
:tree-data="treeData"
@select="clickSelectItem"
v-model:selected-keys="selectedKeys"
showLine
class="left-tree-container"
>
<template #title="{ name }">
{{ name }}
</template>
</j-tree>
</j-spin>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -32,8 +37,10 @@ const props = defineProps<{
const treeData = ref<TreeProps['treeData']>([]); const treeData = ref<TreeProps['treeData']>([]);
const selectedKeys = ref<string[]>([]); const selectedKeys = ref<string[]>([]);
const spinning = ref(false);
const getTreeData = () => { const getTreeData = () => {
let tree: treeNodeTpye[] = []; let tree: treeNodeTpye[] = [];
spinning.value = true;
getTreeOne_api().then((resp: any) => { getTreeOne_api().then((resp: any) => {
tree = resp.urls.map((item: any) => ({ tree = resp.urls.map((item: any) => ({
...item, ...item,
@ -44,32 +51,36 @@ const getTreeData = () => {
if (props.mode === 'appManger') allPromise.push(apiOperations_api()); if (props.mode === 'appManger') allPromise.push(apiOperations_api());
else if (props.mode === 'home') else if (props.mode === 'home')
allPromise.push(getApiGranted_api(props.code as string)); allPromise.push(getApiGranted_api(props.code as string));
Promise.all(allPromise).then((values) => { Promise.all(allPromise)
values.forEach((item: any, i) => { .then((values) => {
if (props.mode === 'api') { values.forEach((item: any, i) => {
tree[i].schemas = item.components.schemas; if (props.mode === 'api') {
tree[i].children = combData(item.paths); tree[i].schemas = item.components.schemas;
} else if (i < values.length - 2) { tree[i].children = combData(item.paths);
const paths = filterPath( } else if (i < values.length - 2) {
item.paths, const paths = filterPath(
values[values.length - 1].result as string[], item.paths,
); values[values.length - 1].result as string[],
tree[i].children = combData(paths); );
tree[i].schemas = item.components.schemas; tree[i].children = combData(paths);
} tree[i].schemas = item.components.schemas;
}); }
if (props.hasHome) {
tree.unshift({
key: 'home',
name: '首页',
schemas: {},
children: [],
}); });
selectedKeys.value = ['home']; if (props.hasHome) {
} tree.unshift({
key: 'home',
name: '首页',
schemas: {},
children: [],
});
selectedKeys.value = ['home'];
}
treeData.value = tree; treeData.value = tree;
}); })
.finally(() => {
spinning.value = false;
});
}); });
}; };
const clickSelectItem: TreeProps['onSelect'] = (key: any[], node: any) => { const clickSelectItem: TreeProps['onSelect'] = (key: any[], node: any) => {

View File

@ -49,13 +49,14 @@
<j-select <j-select
v-model:value="form.data.objectType" v-model:value="form.data.objectType"
:disabled="!!form.data.id" :disabled="!!form.data.id"
@change="() => (form.data.targetType = undefined)" @change="form.handleObjectTypeChange"
placeholder="请选择关联方"
> >
<j-select-option <j-select-option
v-for="item in form.objectList" v-for="item in form.objectList"
:value="item.id" :value="item.id"
> >
{{ item.name }}. {{ item.name }}
</j-select-option> </j-select-option>
</j-select> </j-select>
</j-form-item> </j-form-item>
@ -69,6 +70,8 @@
<j-select <j-select
v-model:value="form.data.targetType" v-model:value="form.data.targetType"
:disabled="!!form.data.id" :disabled="!!form.data.id"
@change="form.rules.checkUnique"
placeholder="请选择关联方"
> >
<j-select-option <j-select-option
v-for="item in targetList" v-for="item in targetList"
@ -100,6 +103,7 @@ import {
getObjectList_api, getObjectList_api,
addRelation_api, addRelation_api,
editRelation_api, editRelation_api,
validateField,
} from '@/api/system/relationship'; } from '@/api/system/relationship';
import { dictItemType } from '../../DataSource/typing'; import { dictItemType } from '../../DataSource/typing';
@ -130,15 +134,44 @@ const formRef = ref<FormInstance>();
const form = reactive({ const form = reactive({
data: props.data, data: props.data,
rules: { rules: {
checkRelation: (_rule: Rule, value: string): any => { /**
if (!value) return Promise.reject(''); * 验证标识
else if (value.length > 64) return Promise.reject(''); * @param _rule
* @param value
*/
checkRelation: async (_rule: Rule, value: string) => {
const reg = new RegExp('^[0-9a-zA-Z_\\\\-]+$'); const reg = new RegExp('^[0-9a-zA-Z_\\\\-]+$');
if (!value) return Promise.reject('');
return reg.test(value) if (!reg.test(value))
? Promise.resolve() return Promise.reject(
: Promise.reject('标识只能由数字、字母、下划线、中划线组成'); '标识只能由数字、字母、下划线、中划线组成',
);
return form.rules.checkUnique();
}, },
/**
* 验证标识唯一性
* @param value
*/
checkUnique: () => {
if (
!form.data.relation ||
!form.data.objectType ||
!form.data.targetType
)
return;
return new Promise(async (resolve, reject) => {
const { result } = await validateField({
relation: form.data.relation,
objectType: form.data.objectType,
targetType: form.data.targetType,
});
result.passed ? resolve('') : reject(result.reason);
});
},
},
handleObjectTypeChange: () => {
form.data.targetType = undefined;
form.rules.checkUnique();
}, },
objectList: [] as any[], objectList: [] as any[],
@ -169,7 +202,7 @@ form.getObjectList();
type formType = { type formType = {
name: string; name: string;
relation: string; relation: string;
objectType: string; objectType: string | undefined;
targetType: string | undefined; targetType: string | undefined;
description: string; description: string;
id?: string; id?: string;

View File

@ -14,8 +14,13 @@
model="TABLE" model="TABLE"
:params="queryParams" :params="queryParams"
:defaultParams="{ :defaultParams="{
pageSize: 10,
sorts: [{ name: 'createTime', order: 'desc' }], sorts: [{ name: 'createTime', order: 'desc' }],
}" }"
:pagination="{
showSizeChanger: true,
pageSizeOptions: ['10', '20', '50', '100'],
}"
> >
<template #headerTitle> <template #headerTitle>
<PermissionButton <PermissionButton

View File

@ -78,12 +78,16 @@ const form = reactive({
}); });
}, },
clickSave: () => { clickSave: () => {
const updateRole = updateRole_api(form.data); formRef.value?.validate().then(() => {
const updateTree = updatePrimissTree_api(roleId, { menus: form.menus }); const updateRole = updateRole_api(form.data);
const updateTree = updatePrimissTree_api(roleId, {
menus: form.menus,
});
Promise.all([updateRole, updateTree]).then((resp) => { Promise.all([updateRole, updateTree]).then((resp) => {
message.success('操作成功'); message.success('操作成功');
router.push('/system/Role'); router.push('/system/Role');
});
}); });
}, },
}); });
@ -123,7 +127,7 @@ form.getForm();
:deep(.ant-form-item-required) { :deep(.ant-form-item-required) {
padding-right: 12px; padding-right: 12px;
&::before{ &::before {
right: 0; right: 0;
} }
} }

View File

@ -18,6 +18,13 @@
}" }"
@cancelSelect="selectedRowKeys = []" @cancelSelect="selectedRowKeys = []"
size="small" size="small"
:defaultParams="{
pageSize: 10,
}"
:pagination="{
showSizeChanger: true,
pageSizeOptions: ['10', '20', '50', '100'],
}"
> >
<template #headerTitle> <template #headerTitle>
<j-button type="primary" @click="dialogVisible = true"> <j-button type="primary" @click="dialogVisible = true">
@ -35,6 +42,9 @@
}" }"
></BadgeStatus> ></BadgeStatus>
</template> </template>
<template #createTime="slotProps">
{{ dayjs(slotProps.createTime).format('YYYY-MM-DD HH:mm:ss') }}
</template>
<template #action="slotProps"> <template #action="slotProps">
<j-space :size="16"> <j-space :size="16">
@ -66,6 +76,7 @@ import PermissionButton from '@/components/PermissionButton/index.vue';
import AddUserDialog from '../components/AddUserDialog.vue'; import AddUserDialog from '../components/AddUserDialog.vue';
import { getUserByRole_api, unbindUser_api } from '@/api/system/role'; import { getUserByRole_api, unbindUser_api } from '@/api/system/role';
import { message } from 'jetlinks-ui-components'; import { message } from 'jetlinks-ui-components';
import dayjs from 'dayjs';
const roleId = useRoute().params.id as string; const roleId = useRoute().params.id as string;
@ -93,6 +104,7 @@ const columns = [
search: { search: {
type: 'date', type: 'date',
}, },
scopedSlots: true,
}, },
{ {
title: '状态', title: '状态',

View File

@ -28,6 +28,7 @@
placeholder="请输入说明" placeholder="请输入说明"
allow-clear allow-clear
:maxlength="200" :maxlength="200"
show-count
/> />
</j-form-item> </j-form-item>
</j-form> </j-form>

View File

@ -14,11 +14,16 @@
model="TABLE" model="TABLE"
:params="queryParams" :params="queryParams"
:defaultParams="{ :defaultParams="{
pageSize: 10,
sorts: [ sorts: [
{ name: 'createTime', order: 'desc' }, { name: 'createTime', order: 'desc' },
{ name: 'id', order: 'desc' }, { name: 'id', order: 'desc' },
], ],
}" }"
:pagination="{
showSizeChanger: true,
pageSizeOptions: ['10', '20', '50', '100'],
}"
> >
<template #headerTitle> <template #headerTitle>
<PermissionButton <PermissionButton
@ -99,7 +104,7 @@ const columns = [
}, },
}, },
{ {
title: '描述', title: '说明',
key: 'description', key: 'description',
ellipsis: true, ellipsis: true,
dataIndex: 'description', dataIndex: 'description',

View File

@ -93,7 +93,7 @@ export default defineConfig(({ mode}) => {
// target: 'http://192.168.33.22:8800', // target: 'http://192.168.33.22:8800',
// target: 'http://192.168.32.244:8881', // target: 'http://192.168.32.244:8881',
target: 'http://120.77.179.54:8844', // 120测试 target: 'http://120.77.179.54:8844', // 120测试
// target: 'http://192.168.33.46:8844', // 本地开发环境 // target: 'http://192.168.33.46:8844', // 本地开发环境
ws: 'ws://192.168.33.46:8844', ws: 'ws://192.168.33.46:8844',
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '') rewrite: (path) => path.replace(/^\/api/, '')

View File

@ -3700,8 +3700,8 @@ jetlinks-store@^0.0.3:
jetlinks-ui-components@^1.0.5: jetlinks-ui-components@^1.0.5:
version "1.0.5" version "1.0.5"
resolved "http://47.108.170.157:9013/jetlinks-ui-components/-/jetlinks-ui-components-1.0.5.tgz#77bd58156212d793fdebe41fc8864eeed5db7182" resolved "http://47.108.170.157:9013/jetlinks-ui-components/-/jetlinks-ui-components-1.0.5.tgz#8cb5c9e68e46e6e7eebc0d96b1cdaab24828779f"
integrity sha512-VXuCMJlMV/bbmBhPtBzY/BUBIvGebQbguLPE06xps79i5Pjq46+HBW8VPsJ9MwvyMhYkhzPlQJu9Ft7v5uo+yA== integrity sha512-yIbmplK+twekevr7n+dGMvO8tvyIqguC60TWeJCmx2mUqpwv8dEnr/cwwpJee4PBLWohvGPywsYgmm7KxVBbcw==
dependencies: dependencies:
"@vueuse/core" "^9.12.0" "@vueuse/core" "^9.12.0"
ant-design-vue "^3.2.15" ant-design-vue "^3.2.15"