Commit 723c20974c72bb50adb712b6144d98b6dc96b3ab
Merge branch 'main_dev' of http://git.yunteng.com/yunteng/thingskit-front into feat/add-rule-chain
Showing
39 changed files
with
1131 additions
and
124 deletions
| 1 | import { RuleChainPaginationItemType } from './model/type'; | 1 | import { RuleChainPaginationItemType } from './model/type'; |
| 2 | import { TBPaginationResult } from '/#/axios'; | 2 | import { TBPaginationResult } from '/#/axios'; |
| 3 | import { defHttp } from '/@/utils/http/axios'; | 3 | import { defHttp } from '/@/utils/http/axios'; |
| 4 | -import { RuleChainType } from '/@/views/rule/designer/types/ruleNode'; | 4 | +import { RuleChainDetail, RuleChainType } from '/@/views/rule/designer/types/ruleNode'; |
| 5 | 5 | ||
| 6 | enum Api { | 6 | enum Api { |
| 7 | + GET_RULE_CHAINS_DETAIL = '/ruleChain', | ||
| 7 | SAVE = '/ruleChain/metadata', | 8 | SAVE = '/ruleChain/metadata', |
| 8 | GET_RULE_CHAINES = '/ruleChains', | 9 | GET_RULE_CHAINES = '/ruleChains', |
| 9 | GET_RULE_NODE_EVENTS = '/events/RULE_NODE', | 10 | GET_RULE_NODE_EVENTS = '/events/RULE_NODE', |
| 10 | } | 11 | } |
| 11 | 12 | ||
| 13 | +export const getRuleChainDetail = (id: string) => { | ||
| 14 | + return defHttp.get<RuleChainDetail>( | ||
| 15 | + { | ||
| 16 | + url: `${Api.GET_RULE_CHAINS_DETAIL}/${id}`, | ||
| 17 | + }, | ||
| 18 | + { joinPrefix: false } | ||
| 19 | + ); | ||
| 20 | +}; | ||
| 21 | + | ||
| 12 | export const getRuleChainData = (id: string) => { | 22 | export const getRuleChainData = (id: string) => { |
| 13 | return defHttp.get<RuleChainType>( | 23 | return defHttp.get<RuleChainType>( |
| 14 | { | 24 | { |
| @@ -66,7 +66,7 @@ export interface RoleReqDTO { | @@ -66,7 +66,7 @@ export interface RoleReqDTO { | ||
| 66 | name?: string; | 66 | name?: string; |
| 67 | remark?: string; | 67 | remark?: string; |
| 68 | status: number; | 68 | status: number; |
| 69 | - menu: Array<string>; | 69 | + menu: Array<string | number>; |
| 70 | } | 70 | } |
| 71 | 71 | ||
| 72 | export interface ChangeAccountParams { | 72 | export interface ChangeAccountParams { |
| @@ -34,6 +34,7 @@ enum Api { | @@ -34,6 +34,7 @@ enum Api { | ||
| 34 | GetAllRoleList = '/role/find/list', | 34 | GetAllRoleList = '/role/find/list', |
| 35 | BaseUserUrl = '/user', | 35 | BaseUserUrl = '/user', |
| 36 | BaseOrganization = '/organization', | 36 | BaseOrganization = '/organization', |
| 37 | + RESET_USER_PASSWORD = '/user/reset_password/', | ||
| 37 | } | 38 | } |
| 38 | 39 | ||
| 39 | export const getAccountInfo = (userId: string) => | 40 | export const getAccountInfo = (userId: string) => |
| @@ -172,3 +173,12 @@ export const resetPassword = (params: ChangeAccountParams) => | @@ -172,3 +173,12 @@ export const resetPassword = (params: ChangeAccountParams) => | ||
| 172 | url: Api.BaseUserUrl + '/reset', | 173 | url: Api.BaseUserUrl + '/reset', |
| 173 | params: params, | 174 | params: params, |
| 174 | }); | 175 | }); |
| 176 | + | ||
| 177 | +/** | ||
| 178 | + * 清除密码 | ||
| 179 | + * @param params | ||
| 180 | + */ | ||
| 181 | +export const clearUserPassword = (userId: string) => | ||
| 182 | + defHttp.post({ | ||
| 183 | + url: Api.RESET_USER_PASSWORD + userId, | ||
| 184 | + }); |
| @@ -38,6 +38,11 @@ export const PLATFORM = 'PLATFORM'; | @@ -38,6 +38,11 @@ export const PLATFORM = 'PLATFORM'; | ||
| 38 | export const PLATFORM_INFO_CACHE_KEY = 'PLATFORM_INFO'; | 38 | export const PLATFORM_INFO_CACHE_KEY = 'PLATFORM_INFO'; |
| 39 | 39 | ||
| 40 | export const MENU_LIST = 'MENU_LIST'; | 40 | export const MENU_LIST = 'MENU_LIST'; |
| 41 | + | ||
| 42 | +export const RULE_NODE_LOCAL_CACHE_KEY = 'RULE__NODE__KEY__'; | ||
| 43 | + | ||
| 44 | +export const RULE_NODE_KEY = 'RULE_NODE'; | ||
| 45 | + | ||
| 41 | export enum CacheTypeEnum { | 46 | export enum CacheTypeEnum { |
| 42 | SESSION, | 47 | SESSION, |
| 43 | LOCAL, | 48 | LOCAL, |
| @@ -16,4 +16,6 @@ export const PageEnum = { | @@ -16,4 +16,6 @@ export const PageEnum = { | ||
| 16 | DEVICE_LIST: '/device/list', | 16 | DEVICE_LIST: '/device/list', |
| 17 | 17 | ||
| 18 | SHARE_PAGE: '/share/:viewType/:id/:publicId', | 18 | SHARE_PAGE: '/share/:viewType/:id/:publicId', |
| 19 | + | ||
| 20 | + RULE_CHAIN_DETAIL: '/rule/chain/:id', | ||
| 19 | }; | 21 | }; |
| @@ -102,7 +102,12 @@ | @@ -102,7 +102,12 @@ | ||
| 102 | <template> | 102 | <template> |
| 103 | <StepContainer> | 103 | <StepContainer> |
| 104 | <div class="">设备文件</div> | 104 | <div class="">设备文件</div> |
| 105 | - <Upload.Dragger :fileList="fileList" :customRequest="handleParseFile" accept=".csv" name="file"> | 105 | + <Upload.Dragger |
| 106 | + v-model:fileList="fileList" | ||
| 107 | + :customRequest="handleParseFile" | ||
| 108 | + accept=".csv" | ||
| 109 | + name="file" | ||
| 110 | + > | ||
| 106 | <section class="cursor-pointer flex flex-col justify-center items-center"> | 111 | <section class="cursor-pointer flex flex-col justify-center items-center"> |
| 107 | <InboxOutlined class="text-[4rem] !text-blue-400" /> | 112 | <InboxOutlined class="text-[4rem] !text-blue-400" /> |
| 108 | <div class="text-gray-500">点击上传或拖拽上传</div> | 113 | <div class="text-gray-500">点击上传或拖拽上传</div> |
| @@ -68,7 +68,7 @@ export const formSchemas: FormSchema[] = [ | @@ -68,7 +68,7 @@ export const formSchemas: FormSchema[] = [ | ||
| 68 | componentProps: { | 68 | componentProps: { |
| 69 | placeholder: '请输入标识符', | 69 | placeholder: '请输入标识符', |
| 70 | }, | 70 | }, |
| 71 | - colProps: { span: 6 }, | 71 | + colProps: { span: 7 }, |
| 72 | }, | 72 | }, |
| 73 | { | 73 | { |
| 74 | field: 'eventType', | 74 | field: 'eventType', |
| @@ -83,7 +83,7 @@ export const formSchemas: FormSchema[] = [ | @@ -83,7 +83,7 @@ export const formSchemas: FormSchema[] = [ | ||
| 83 | labelField: 'itemText', | 83 | labelField: 'itemText', |
| 84 | valueField: 'itemValue', | 84 | valueField: 'itemValue', |
| 85 | }, | 85 | }, |
| 86 | - colProps: { span: 6 }, | 86 | + colProps: { span: 7 }, |
| 87 | }, | 87 | }, |
| 88 | { | 88 | { |
| 89 | field: 'dateRange', | 89 | field: 'dateRange', |
| @@ -94,6 +94,6 @@ export const formSchemas: FormSchema[] = [ | @@ -94,6 +94,6 @@ export const formSchemas: FormSchema[] = [ | ||
| 94 | defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')], | 94 | defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')], |
| 95 | }, | 95 | }, |
| 96 | }, | 96 | }, |
| 97 | - colProps: { span: 6 }, | 97 | + colProps: { span: 7 }, |
| 98 | }, | 98 | }, |
| 99 | ]; | 99 | ]; |
| @@ -39,6 +39,7 @@ | @@ -39,6 +39,7 @@ | ||
| 39 | type?: string; | 39 | type?: string; |
| 40 | boolClose?: string; | 40 | boolClose?: string; |
| 41 | boolOpen?: string; | 41 | boolOpen?: string; |
| 42 | + name?: string; | ||
| 42 | detail: DeviceModelOfMatterAttrs; | 43 | detail: DeviceModelOfMatterAttrs; |
| 43 | } | 44 | } |
| 44 | 45 | ||
| @@ -61,23 +62,27 @@ | @@ -61,23 +62,27 @@ | ||
| 61 | showQuickJumper: true, | 62 | showQuickJumper: true, |
| 62 | hideOnSinglePage: false, | 63 | hideOnSinglePage: false, |
| 63 | showTotal: (total: number) => `共${total}条数据`, | 64 | showTotal: (total: number) => `共${total}条数据`, |
| 64 | - onChange: handleChange, | ||
| 65 | - onShowSizeChange: handleChange, | 65 | + onChange: handleFilterChange, |
| 66 | + onShowSizeChange: handleFilterChange, | ||
| 66 | }); | 67 | }); |
| 67 | 68 | ||
| 68 | const socketInfo = reactive({ | 69 | const socketInfo = reactive({ |
| 69 | cmdId: 0, | 70 | cmdId: 0, |
| 70 | origin: `${socketUrl}${token}`, | 71 | origin: `${socketUrl}${token}`, |
| 71 | attr: undefined as string | undefined, | 72 | attr: undefined as string | undefined, |
| 72 | - originData: [] as DataSource[], | ||
| 73 | dataSource: [] as DataSource[], | 73 | dataSource: [] as DataSource[], |
| 74 | message: {} as ReceiveMessage['data'], | 74 | message: {} as ReceiveMessage['data'], |
| 75 | attrKeys: [] as DeviceModelOfMatterAttrs[], | 75 | attrKeys: [] as DeviceModelOfMatterAttrs[], |
| 76 | + filterAttrKeys: [] as DeviceModelOfMatterAttrs[], | ||
| 76 | }); | 77 | }); |
| 77 | 78 | ||
| 78 | const getPaginationAttrkey = computed<DeviceModelOfMatterAttrs[]>(() => { | 79 | const getPaginationAttrkey = computed<DeviceModelOfMatterAttrs[]>(() => { |
| 79 | const { current = 1, pageSize = 10 } = pagination; | 80 | const { current = 1, pageSize = 10 } = pagination; |
| 80 | - return socketInfo.attrKeys.slice(current * pageSize - pageSize, current * pageSize); | 81 | + return ( |
| 82 | + socketInfo.filterAttrKeys && socketInfo.filterAttrKeys.length | ||
| 83 | + ? socketInfo.filterAttrKeys | ||
| 84 | + : socketInfo.attrKeys | ||
| 85 | + ).slice(current * pageSize - pageSize, current * pageSize); | ||
| 81 | }); | 86 | }); |
| 82 | 87 | ||
| 83 | function createUnsubscribeMessage(cmdId: number) { | 88 | function createUnsubscribeMessage(cmdId: number) { |
| @@ -107,6 +112,22 @@ | @@ -107,6 +112,22 @@ | ||
| 107 | }; | 112 | }; |
| 108 | }); | 113 | }); |
| 109 | 114 | ||
| 115 | + const getFilterSendValue = computed(() => { | ||
| 116 | + return { | ||
| 117 | + tsSubCmds: [ | ||
| 118 | + { | ||
| 119 | + entityType: 'DEVICE', | ||
| 120 | + entityId: props.deviceDetail!.tbDeviceId, | ||
| 121 | + scope: 'LATEST_TELEMETRY', | ||
| 122 | + cmdId: socketInfo.cmdId, | ||
| 123 | + keys: unref(getPaginationAttrkey) | ||
| 124 | + .map((item) => item.identifier) | ||
| 125 | + .join(','), | ||
| 126 | + }, | ||
| 127 | + ], | ||
| 128 | + }; | ||
| 129 | + }); | ||
| 130 | + | ||
| 110 | const [registerForm, { getFieldsValue }] = useForm({ | 131 | const [registerForm, { getFieldsValue }] = useForm({ |
| 111 | schemas: [ | 132 | schemas: [ |
| 112 | { | 133 | { |
| @@ -125,20 +146,29 @@ | @@ -125,20 +146,29 @@ | ||
| 125 | submitFunc: async () => { | 146 | submitFunc: async () => { |
| 126 | try { | 147 | try { |
| 127 | const { value } = getFieldsValue() || {}; | 148 | const { value } = getFieldsValue() || {}; |
| 128 | - if (!value) setTableData(socketInfo.originData); | ||
| 129 | - const data = unref(socketInfo.originData).filter( | ||
| 130 | - (item) => item.key?.includes(value) || item.value?.includes(value) | ||
| 131 | - ); | 149 | + |
| 150 | + pagination.current = 1; | ||
| 151 | + | ||
| 152 | + socketInfo.filterAttrKeys = value | ||
| 153 | + ? unref(socketInfo.attrKeys).filter( | ||
| 154 | + (item) => | ||
| 155 | + item.identifier?.toUpperCase().includes(value.toUpperCase()) || | ||
| 156 | + item.name?.toUpperCase().includes(value.toUpperCase()) | ||
| 157 | + ) | ||
| 158 | + : socketInfo.attrKeys; | ||
| 159 | + | ||
| 132 | await nextTick(); | 160 | await nextTick(); |
| 133 | - socketInfo.dataSource = data; | ||
| 134 | 161 | ||
| 135 | - setTableData(data); | 162 | + handleFilterChange(); |
| 163 | + | ||
| 164 | + unref(mode) === EnumTableCardMode.TABLE && setTableData(socketInfo.dataSource); | ||
| 136 | } catch (error) {} | 165 | } catch (error) {} |
| 137 | }, | 166 | }, |
| 138 | resetFunc: async () => { | 167 | resetFunc: async () => { |
| 139 | try { | 168 | try { |
| 140 | - socketInfo.dataSource = socketInfo.originData; | ||
| 141 | - setTableData(socketInfo.originData); | 169 | + socketInfo.filterAttrKeys = []; |
| 170 | + handleFilterChange(); | ||
| 171 | + unref(mode) === EnumTableCardMode.TABLE && setTableData(socketInfo.dataSource); | ||
| 142 | } catch (error) {} | 172 | } catch (error) {} |
| 143 | }, | 173 | }, |
| 144 | }); | 174 | }); |
| @@ -148,15 +178,19 @@ | @@ -148,15 +178,19 @@ | ||
| 148 | showTableSetting: true, | 178 | showTableSetting: true, |
| 149 | pagination: pagination as any, | 179 | pagination: pagination as any, |
| 150 | bordered: true, | 180 | bordered: true, |
| 181 | + resizeHeightOffset: 16, | ||
| 151 | showIndexColumn: false, | 182 | showIndexColumn: false, |
| 152 | }); | 183 | }); |
| 153 | 184 | ||
| 154 | - function handleChange(page: number, pageSize: number) { | 185 | + function handleFilterChange( |
| 186 | + page: number = pagination.current || 1, | ||
| 187 | + pageSize: number = pagination.pageSize || 10 | ||
| 188 | + ) { | ||
| 155 | pagination.current = page; | 189 | pagination.current = page; |
| 156 | pagination.pageSize = pageSize; | 190 | pagination.pageSize = pageSize; |
| 157 | send(JSON.stringify(createUnsubscribeMessage(socketInfo.cmdId))); | 191 | send(JSON.stringify(createUnsubscribeMessage(socketInfo.cmdId))); |
| 158 | socketInfo.cmdId = socketInfo.cmdId + 1; | 192 | socketInfo.cmdId = socketInfo.cmdId + 1; |
| 159 | - send(JSON.stringify(unref(getSendValue))); | 193 | + send(JSON.stringify(unref(getFilterSendValue))); |
| 160 | } | 194 | } |
| 161 | 195 | ||
| 162 | const [registerModal, { openModal }] = useModal(); | 196 | const [registerModal, { openModal }] = useModal(); |
| @@ -166,8 +200,8 @@ | @@ -166,8 +200,8 @@ | ||
| 166 | const switchMode = async (value: EnumTableCardMode) => { | 200 | const switchMode = async (value: EnumTableCardMode) => { |
| 167 | mode.value = value; | 201 | mode.value = value; |
| 168 | await nextTick(); | 202 | await nextTick(); |
| 169 | - setTableData(socketInfo.originData); | ||
| 170 | - socketInfo.dataSource = socketInfo.originData; | 203 | + unref(mode) === EnumTableCardMode.TABLE && setTableData(socketInfo.dataSource); |
| 204 | + socketInfo.filterAttrKeys = []; | ||
| 171 | }; | 205 | }; |
| 172 | 206 | ||
| 173 | const { createMessage } = useMessage(); | 207 | const { createMessage } = useMessage(); |
| @@ -190,10 +224,15 @@ | @@ -190,10 +224,15 @@ | ||
| 190 | }; | 224 | }; |
| 191 | 225 | ||
| 192 | const setDataSource = () => { | 226 | const setDataSource = () => { |
| 193 | - socketInfo.originData = socketInfo.dataSource = socketInfo.attrKeys.map((item) => { | 227 | + socketInfo.dataSource = ( |
| 228 | + socketInfo.filterAttrKeys && socketInfo.filterAttrKeys.length | ||
| 229 | + ? socketInfo.filterAttrKeys | ||
| 230 | + : socketInfo.attrKeys | ||
| 231 | + ).map((item) => { | ||
| 194 | const { identifier: key, name, detail, accessMode } = item; | 232 | const { identifier: key, name, detail, accessMode } = item; |
| 195 | const { unit, boolClose, boolOpen, type } = getUnit(detail); | 233 | const { unit, boolClose, boolOpen, type } = getUnit(detail); |
| 196 | - const dataInfo = socketInfo.attrKeys.find((item) => item.identifier === key); | 234 | + const dataInfo = socketInfo.filterAttrKeys.find((item) => item.identifier === key); |
| 235 | + | ||
| 197 | let time: number | undefined; | 236 | let time: number | undefined; |
| 198 | let value: any | undefined; | 237 | let value: any | undefined; |
| 199 | const message = socketInfo.message[key]; | 238 | const message = socketInfo.message[key]; |
| @@ -240,7 +279,8 @@ | @@ -240,7 +279,8 @@ | ||
| 240 | setDataSource(); | 279 | setDataSource(); |
| 241 | 280 | ||
| 242 | await nextTick(); | 281 | await nextTick(); |
| 243 | - setTableData(socketInfo.dataSource); | 282 | + |
| 283 | + unref(mode) === EnumTableCardMode.TABLE && setTableData(socketInfo.dataSource); | ||
| 244 | } | 284 | } |
| 245 | } catch (error) {} | 285 | } catch (error) {} |
| 246 | }, | 286 | }, |
| @@ -273,6 +273,7 @@ | @@ -273,6 +273,7 @@ | ||
| 273 | event: DropMenuEvent.SET_DEFAULT, | 273 | event: DropMenuEvent.SET_DEFAULT, |
| 274 | icon: 'ant-design:unordered-list-outlined', | 274 | icon: 'ant-design:unordered-list-outlined', |
| 275 | onClick: handleSetDefault.bind(null, item), | 275 | onClick: handleSetDefault.bind(null, item), |
| 276 | + disabled: item.default, | ||
| 276 | }, | 277 | }, |
| 277 | { | 278 | { |
| 278 | text: '删除', | 279 | text: '删除', |
| @@ -283,6 +284,7 @@ | @@ -283,6 +284,7 @@ | ||
| 283 | title: '是否确认删除操作?', | 284 | title: '是否确认删除操作?', |
| 284 | onConfirm: handleDelete.bind(null, [item.id]), | 285 | onConfirm: handleDelete.bind(null, [item.id]), |
| 285 | }, | 286 | }, |
| 287 | + disabled: item.default, | ||
| 286 | }, | 288 | }, |
| 287 | ]" | 289 | ]" |
| 288 | /> | 290 | /> |
src/views/rule/designer/enum/entity.ts
0 → 100644
| 1 | import { Node } from '@vue-flow/core'; | 1 | import { Node } from '@vue-flow/core'; |
| 2 | import { NodeTypeEnum } from '../enum'; | 2 | import { NodeTypeEnum } from '../enum'; |
| 3 | import { buildUUID } from '/@/utils/uuid'; | 3 | import { buildUUID } from '/@/utils/uuid'; |
| 4 | +import { NodeData } from '../types/node'; | ||
| 4 | 5 | ||
| 5 | export const useAddNodes = () => { | 6 | export const useAddNodes = () => { |
| 6 | const getAddNodesParams = ( | 7 | const getAddNodesParams = ( |
| 7 | position: Node['position'], | 8 | position: Node['position'], |
| 8 | - data: object, | 9 | + data: NodeData, |
| 9 | options?: Partial<Node> | 10 | options?: Partial<Node> |
| 10 | ): Node => { | 11 | ): Node => { |
| 11 | return { | 12 | return { |
| @@ -122,6 +122,7 @@ export function useBasicDataTransform() { | @@ -122,6 +122,7 @@ export function useBasicDataTransform() { | ||
| 122 | description, | 122 | description, |
| 123 | name, | 123 | name, |
| 124 | }, | 124 | }, |
| 125 | + created: !!id?.id, | ||
| 125 | }, | 126 | }, |
| 126 | { | 127 | { |
| 127 | id: id?.id || buildUUID(), | 128 | id: id?.id || buildUUID(), |
| 1 | +import { GraphEdge, GraphNode, VueFlowStore } from '@vue-flow/core'; | ||
| 2 | +import { getRuleNodeCache, setRuleNodeCache } from './useRuleCopyPaste'; | ||
| 3 | +import { RuleContextMenuEnum } from './useRuleChainContextMenu'; | ||
| 4 | +import { useAddNodes } from './useAddNodes'; | ||
| 5 | +import { buildUUID } from '/@/utils/uuid'; | ||
| 6 | +import { toRaw, unref } from 'vue'; | ||
| 7 | +import { useSaveAndRedo } from './useSaveAndRedo'; | ||
| 8 | +import { isUnDef } from '/@/utils/is'; | ||
| 9 | +import { useAddEdges } from './useAddEdges'; | ||
| 10 | +import { EdgeData } from '../types/node'; | ||
| 11 | + | ||
| 12 | +interface HandleContextMenuActionParamsType { | ||
| 13 | + menuType: RuleContextMenuEnum; | ||
| 14 | + event?: Event; | ||
| 15 | + flowActionType?: VueFlowStore; | ||
| 16 | + node?: GraphNode; | ||
| 17 | + edge?: GraphEdge; | ||
| 18 | + useSaveAndRedoActionType?: ReturnType<typeof useSaveAndRedo>; | ||
| 19 | +} | ||
| 20 | + | ||
| 21 | +export const NODE_WIDTH = 176; | ||
| 22 | +export const NODE_HEIGHT = 48; | ||
| 23 | + | ||
| 24 | +function getElementsCenter(nodes: GraphNode[]) { | ||
| 25 | + let leftTopX: number | undefined; | ||
| 26 | + let leftTopY: number | undefined; | ||
| 27 | + let rightBottomX: number | undefined; | ||
| 28 | + let rightBottomY: number | undefined; | ||
| 29 | + | ||
| 30 | + for (const node of nodes) { | ||
| 31 | + const { position } = node; | ||
| 32 | + const { x, y } = position; | ||
| 33 | + if (isUnDef(leftTopX)) { | ||
| 34 | + leftTopX = x; | ||
| 35 | + leftTopY = y; | ||
| 36 | + rightBottomX = x + NODE_WIDTH; | ||
| 37 | + rightBottomY = y + NODE_HEIGHT; | ||
| 38 | + | ||
| 39 | + continue; | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + if (x < leftTopX!) { | ||
| 43 | + leftTopX = x; | ||
| 44 | + if (y < leftTopY!) { | ||
| 45 | + leftTopY = y; | ||
| 46 | + } | ||
| 47 | + continue; | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + if (x + NODE_WIDTH > rightBottomX!) { | ||
| 51 | + rightBottomX = x + NODE_WIDTH; | ||
| 52 | + if (y + NODE_HEIGHT > rightBottomY!) { | ||
| 53 | + rightBottomY = y + NODE_HEIGHT; | ||
| 54 | + } | ||
| 55 | + } | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + return { | ||
| 59 | + originX: (rightBottomX! - leftTopX!) / 2 + leftTopX!, | ||
| 60 | + originY: (rightBottomY! - leftTopY!) / 2 + leftTopY!, | ||
| 61 | + }; | ||
| 62 | +} | ||
| 63 | + | ||
| 64 | +export function useContextMenuAction() { | ||
| 65 | + const copy = (params: HandleContextMenuActionParamsType) => { | ||
| 66 | + const { node } = params; | ||
| 67 | + if (!node) return; | ||
| 68 | + const { position, data } = node; | ||
| 69 | + const { getAddNodesParams } = useAddNodes(); | ||
| 70 | + const { x, y } = position; | ||
| 71 | + | ||
| 72 | + const value = getAddNodesParams(position, data, { id: buildUUID() }); | ||
| 73 | + | ||
| 74 | + setRuleNodeCache({ | ||
| 75 | + nodes: [value], | ||
| 76 | + originX: x + NODE_WIDTH / 2 + x, | ||
| 77 | + originY: y + NODE_HEIGHT / 2, | ||
| 78 | + }); | ||
| 79 | + }; | ||
| 80 | + | ||
| 81 | + const paste = (params: HandleContextMenuActionParamsType) => { | ||
| 82 | + const { event, flowActionType, useSaveAndRedoActionType } = params; | ||
| 83 | + const { triggerChange } = useSaveAndRedoActionType || {}; | ||
| 84 | + const { getAddNodesParams } = useAddNodes(); | ||
| 85 | + const { getAddedgesParams } = useAddEdges(); | ||
| 86 | + const clientX = (event as MouseEvent).offsetX; | ||
| 87 | + const clientY = (event as MouseEvent).offsetY; | ||
| 88 | + | ||
| 89 | + const { edges = [], nodes = [], originX, originY } = getRuleNodeCache(); | ||
| 90 | + | ||
| 91 | + const newNode = nodes.map((node) => { | ||
| 92 | + const { position, data, id } = node; | ||
| 93 | + const { x, y } = position; | ||
| 94 | + | ||
| 95 | + const newX = clientX - originX! + x + NODE_WIDTH / 2; | ||
| 96 | + const newY = clientY - originY! + y + NODE_HEIGHT / 2; | ||
| 97 | + | ||
| 98 | + return getAddNodesParams({ x: newX, y: newY }, { ...data, created: false }, { id }); | ||
| 99 | + }); | ||
| 100 | + | ||
| 101 | + const newEdges = edges.map((edge) => getAddedgesParams(edge, edge.data)); | ||
| 102 | + | ||
| 103 | + flowActionType?.addNodes(newNode); | ||
| 104 | + flowActionType?.addEdges(newEdges); | ||
| 105 | + | ||
| 106 | + triggerChange?.(); | ||
| 107 | + | ||
| 108 | + flowActionType?.removeSelectedElements(); | ||
| 109 | + }; | ||
| 110 | + | ||
| 111 | + const selectAll = (params: HandleContextMenuActionParamsType) => { | ||
| 112 | + const { flowActionType } = params; | ||
| 113 | + flowActionType?.addSelectedElements(unref(flowActionType.getElements)); | ||
| 114 | + }; | ||
| 115 | + | ||
| 116 | + const unselect = (params: HandleContextMenuActionParamsType) => { | ||
| 117 | + const { flowActionType } = params; | ||
| 118 | + flowActionType?.removeSelectedElements(); | ||
| 119 | + }; | ||
| 120 | + | ||
| 121 | + const deleteElement = (parmas: HandleContextMenuActionParamsType) => { | ||
| 122 | + const { useSaveAndRedoActionType, flowActionType, node, edge } = parmas; | ||
| 123 | + const { triggerChange } = useSaveAndRedoActionType || {}; | ||
| 124 | + | ||
| 125 | + node && flowActionType?.removeNodes(node); | ||
| 126 | + edge && flowActionType?.removeEdges(edge); | ||
| 127 | + | ||
| 128 | + triggerChange?.(); | ||
| 129 | + }; | ||
| 130 | + | ||
| 131 | + const deleteElements = (params: HandleContextMenuActionParamsType) => { | ||
| 132 | + const { flowActionType, useSaveAndRedoActionType } = params; | ||
| 133 | + flowActionType?.removeNodes(unref(flowActionType.getSelectedNodes)); | ||
| 134 | + | ||
| 135 | + useSaveAndRedoActionType?.triggerChange?.(); | ||
| 136 | + }; | ||
| 137 | + | ||
| 138 | + const selectCopy = (params: HandleContextMenuActionParamsType) => { | ||
| 139 | + const { flowActionType } = params; | ||
| 140 | + const { getAddNodesParams } = useAddNodes(); | ||
| 141 | + const { getAddedgesParams } = useAddEdges(); | ||
| 142 | + | ||
| 143 | + const edges = unref(flowActionType?.getSelectedEdges)?.map((edge) => | ||
| 144 | + getAddedgesParams( | ||
| 145 | + { | ||
| 146 | + source: edge.source, | ||
| 147 | + target: edge.target, | ||
| 148 | + sourceHandle: edge.sourceHandle, | ||
| 149 | + targetHandle: edge.targetHandle, | ||
| 150 | + }, | ||
| 151 | + toRaw(unref(edge.data as EdgeData)?.data) | ||
| 152 | + ) | ||
| 153 | + ); | ||
| 154 | + | ||
| 155 | + const nodes = unref(flowActionType?.getSelectedNodes)?.map((node) => { | ||
| 156 | + const { id: oldId } = node; | ||
| 157 | + const newId = buildUUID(); | ||
| 158 | + | ||
| 159 | + for (const connection of edges || []) { | ||
| 160 | + if (connection.source.includes(oldId)) { | ||
| 161 | + connection.source = newId; | ||
| 162 | + connection.sourceHandle = connection.sourceHandle?.replaceAll(oldId, newId); | ||
| 163 | + continue; | ||
| 164 | + } | ||
| 165 | + | ||
| 166 | + if (connection.target.includes(oldId)) { | ||
| 167 | + connection.target = newId; | ||
| 168 | + connection.targetHandle = connection.targetHandle?.replaceAll(oldId, newId); | ||
| 169 | + } | ||
| 170 | + } | ||
| 171 | + | ||
| 172 | + return getAddNodesParams(node.position, toRaw(unref(node.data)), { id: newId }); | ||
| 173 | + }); | ||
| 174 | + | ||
| 175 | + const originRect = getElementsCenter(unref(flowActionType?.getSelectedNodes) || []); | ||
| 176 | + | ||
| 177 | + setRuleNodeCache({ nodes, edges, ...originRect }); | ||
| 178 | + }; | ||
| 179 | + | ||
| 180 | + const applyChange = (params: HandleContextMenuActionParamsType) => { | ||
| 181 | + const { useSaveAndRedoActionType, flowActionType } = params; | ||
| 182 | + | ||
| 183 | + useSaveAndRedoActionType?.handleApplyChange(flowActionType!); | ||
| 184 | + }; | ||
| 185 | + | ||
| 186 | + const undoChange = (params: HandleContextMenuActionParamsType) => { | ||
| 187 | + const { useSaveAndRedoActionType, flowActionType } = params; | ||
| 188 | + | ||
| 189 | + useSaveAndRedoActionType?.handleRedoChange(flowActionType!); | ||
| 190 | + }; | ||
| 191 | + | ||
| 192 | + const handleContextMenuAction = (params: HandleContextMenuActionParamsType) => { | ||
| 193 | + const { menuType } = params; | ||
| 194 | + | ||
| 195 | + const handlerMapping = { | ||
| 196 | + [RuleContextMenuEnum.COPY]: copy, | ||
| 197 | + [RuleContextMenuEnum.PASTE]: paste, | ||
| 198 | + [RuleContextMenuEnum.SELECT_ALL]: selectAll, | ||
| 199 | + [RuleContextMenuEnum.UNSELECTED]: unselect, | ||
| 200 | + [RuleContextMenuEnum.DELETE]: deleteElement, | ||
| 201 | + [RuleContextMenuEnum.DELETE_SELECT]: deleteElements, | ||
| 202 | + [RuleContextMenuEnum.SELECT_COPY]: selectCopy, | ||
| 203 | + [RuleContextMenuEnum.APPLY_CHANGE]: applyChange, | ||
| 204 | + [RuleContextMenuEnum.UNDO_CHANGE]: undoChange, | ||
| 205 | + }; | ||
| 206 | + | ||
| 207 | + if (handlerMapping[menuType]) { | ||
| 208 | + handlerMapping[menuType]?.(params); | ||
| 209 | + } | ||
| 210 | + }; | ||
| 211 | + | ||
| 212 | + return { | ||
| 213 | + handleContextMenuAction, | ||
| 214 | + }; | ||
| 215 | +} |
| 1 | +import { NodeMouseEvent } from '@vue-flow/core'; | ||
| 2 | +import { useContextMenu } from '../src/components/RuleChainContextMenu'; | ||
| 3 | +import { RuleChainContextMenuItemType } from '../src/components/RuleChainContextMenu/index.type'; | ||
| 4 | +import { ElementsTypeEnum } from '../enum'; | ||
| 5 | +import { checkHasCacheRuleNode } from './useRuleCopyPaste'; | ||
| 6 | + | ||
| 7 | +export enum RuleContextMenuEnum { | ||
| 8 | + DETAIL = 'DETAIL', | ||
| 9 | + COPY = 'COPY', | ||
| 10 | + DELETE = 'DELETE', | ||
| 11 | + | ||
| 12 | + SELECT_COPY = 'SELECT_COPY', | ||
| 13 | + PASTE = 'PASTE', | ||
| 14 | + UNSELECTED = 'UNSELECTED', | ||
| 15 | + DELETE_SELECT = 'DELETE_SELECT', | ||
| 16 | + APPLY_CHANGE = 'APPLY_CHANGE', | ||
| 17 | + UNDO_CHANGE = 'UNDO_CHANGE', | ||
| 18 | + | ||
| 19 | + SELECT_ALL = 'SELECT_ALL', | ||
| 20 | +} | ||
| 21 | + | ||
| 22 | +export enum RuleContextMenuNameEnum { | ||
| 23 | + DETAIL = '详情', | ||
| 24 | + COPY = '复制', | ||
| 25 | + DELETE = '删除', | ||
| 26 | + | ||
| 27 | + SELECT_COPY = '选择副本', | ||
| 28 | + PASTE = '粘贴', | ||
| 29 | + UNSELECTED = '取消选择', | ||
| 30 | + DELETE_SELECT = '删除选定', | ||
| 31 | + APPLY_CHANGE = '应用更改', | ||
| 32 | + UNDO_CHANGE = '撤销更改', | ||
| 33 | + | ||
| 34 | + SELECT_ALL = '选择全部', | ||
| 35 | +} | ||
| 36 | + | ||
| 37 | +export enum RuleChainContextMenuIconEnum { | ||
| 38 | + DETAIL = 'material-symbols:menu', | ||
| 39 | + COPY = 'material-symbols:content-copy', | ||
| 40 | + DELETE = 'material-symbols:delete', | ||
| 41 | + | ||
| 42 | + SELECT_COPY = 'material-symbols:content-copy', | ||
| 43 | + PASTE = 'material-symbols:content-paste', | ||
| 44 | + UNSELECTED = 'material-symbols:tab-unselected', | ||
| 45 | + DELETE_SELECT = 'material-symbols:close', | ||
| 46 | + APPLY_CHANGE = 'material-symbols:done', | ||
| 47 | + UNDO_CHANGE = 'material-symbols:close', | ||
| 48 | + | ||
| 49 | + SELECT_ALL = 'material-symbols:select-all', | ||
| 50 | + | ||
| 51 | + // LINK = 'material-symbols:trending-flat', | ||
| 52 | +} | ||
| 53 | + | ||
| 54 | +export enum RuleChainContextMenuShortcutKeyEnum { | ||
| 55 | + DELETE = 'Ctrl(⌘) X', | ||
| 56 | + | ||
| 57 | + SELECT_COPY = 'Ctrl(⌘) C', | ||
| 58 | + PASTE = 'Ctrl(⌘) V', | ||
| 59 | + UNSELECTED = 'Esc', | ||
| 60 | + DELETE_SELECT = 'Del', | ||
| 61 | + APPLY_CHANGE = 'Ctrl(⌘) S', | ||
| 62 | + UNDO_CHANGE = 'Ctrl(⌘) Z', | ||
| 63 | + | ||
| 64 | + SELECT_ALL = 'Ctrl(⌘) A', | ||
| 65 | +} | ||
| 66 | + | ||
| 67 | +const getMenuItem = (key: RuleContextMenuEnum, handler: Fn, disabled = false) => { | ||
| 68 | + return { | ||
| 69 | + key, | ||
| 70 | + label: RuleContextMenuNameEnum[key], | ||
| 71 | + icon: RuleChainContextMenuIconEnum[key], | ||
| 72 | + shortcutKey: RuleChainContextMenuShortcutKeyEnum[key], | ||
| 73 | + handler: () => handler?.(key), | ||
| 74 | + disabled, | ||
| 75 | + } as RuleChainContextMenuItemType; | ||
| 76 | +}; | ||
| 77 | + | ||
| 78 | +const getDivider = (): RuleChainContextMenuItemType => ({ divider: true }); | ||
| 79 | + | ||
| 80 | +export function useCreateRuleChainContextMenu() { | ||
| 81 | + const [createContextMenu] = useContextMenu(); | ||
| 82 | + | ||
| 83 | + const createNodeContextMenu = (params: NodeMouseEvent): Promise<RuleContextMenuEnum | ''> => { | ||
| 84 | + return new Promise(async (resolve) => { | ||
| 85 | + await createContextMenu(params, { | ||
| 86 | + items: [ | ||
| 87 | + getMenuItem(RuleContextMenuEnum.DETAIL, resolve), | ||
| 88 | + getMenuItem(RuleContextMenuEnum.COPY, resolve), | ||
| 89 | + getMenuItem(RuleContextMenuEnum.DELETE, resolve), | ||
| 90 | + ], | ||
| 91 | + }); | ||
| 92 | + resolve(''); | ||
| 93 | + }); | ||
| 94 | + }; | ||
| 95 | + | ||
| 96 | + const createElementsSelectedContextMenu = ( | ||
| 97 | + params: NodeMouseEvent, | ||
| 98 | + changeMarker: boolean, | ||
| 99 | + elementsType: ElementsTypeEnum.NODE = ElementsTypeEnum.NODE | ||
| 100 | + ): Promise<RuleContextMenuEnum | ''> => { | ||
| 101 | + return new Promise(async (resolve) => { | ||
| 102 | + await createContextMenu(params, { | ||
| 103 | + items: [ | ||
| 104 | + ...(elementsType === ElementsTypeEnum.NODE | ||
| 105 | + ? [getMenuItem(RuleContextMenuEnum.SELECT_COPY, resolve)] | ||
| 106 | + : []), | ||
| 107 | + getMenuItem(RuleContextMenuEnum.PASTE, resolve, !checkHasCacheRuleNode()), | ||
| 108 | + getDivider(), | ||
| 109 | + getMenuItem(RuleContextMenuEnum.UNSELECTED, resolve), | ||
| 110 | + getMenuItem(RuleContextMenuEnum.DELETE_SELECT, resolve), | ||
| 111 | + getDivider(), | ||
| 112 | + getMenuItem(RuleContextMenuEnum.APPLY_CHANGE, resolve, !changeMarker), | ||
| 113 | + getMenuItem(RuleContextMenuEnum.UNDO_CHANGE, resolve, !changeMarker), | ||
| 114 | + ], | ||
| 115 | + }); | ||
| 116 | + resolve(''); | ||
| 117 | + }); | ||
| 118 | + }; | ||
| 119 | + | ||
| 120 | + const createPanelContextMenu = ( | ||
| 121 | + params: NodeMouseEvent, | ||
| 122 | + changeMarker: boolean | ||
| 123 | + ): Promise<RuleContextMenuEnum | ''> => { | ||
| 124 | + return new Promise(async (resolve) => { | ||
| 125 | + await createContextMenu(params, { | ||
| 126 | + items: [ | ||
| 127 | + getMenuItem(RuleContextMenuEnum.PASTE, resolve, !checkHasCacheRuleNode()), | ||
| 128 | + getMenuItem(RuleContextMenuEnum.SELECT_ALL, resolve), | ||
| 129 | + getMenuItem(RuleContextMenuEnum.APPLY_CHANGE, resolve, !changeMarker), | ||
| 130 | + getMenuItem(RuleContextMenuEnum.UNDO_CHANGE, resolve, !changeMarker), | ||
| 131 | + ], | ||
| 132 | + }); | ||
| 133 | + resolve(''); | ||
| 134 | + }); | ||
| 135 | + }; | ||
| 136 | + | ||
| 137 | + const createEdgeContextMenu = ( | ||
| 138 | + params: NodeMouseEvent, | ||
| 139 | + isInput = false | ||
| 140 | + ): Promise<RuleContextMenuEnum | ''> => { | ||
| 141 | + return new Promise(async (resolve) => { | ||
| 142 | + await createContextMenu(params, { | ||
| 143 | + items: [ | ||
| 144 | + ...(isInput ? [] : [getMenuItem(RuleContextMenuEnum.DETAIL, resolve)]), | ||
| 145 | + getMenuItem(RuleContextMenuEnum.DELETE, resolve), | ||
| 146 | + ], | ||
| 147 | + }); | ||
| 148 | + resolve(''); | ||
| 149 | + }); | ||
| 150 | + }; | ||
| 151 | + return { | ||
| 152 | + createNodeContextMenu, | ||
| 153 | + createElementsSelectedContextMenu, | ||
| 154 | + createPanelContextMenu, | ||
| 155 | + createEdgeContextMenu, | ||
| 156 | + }; | ||
| 157 | +} |
| 1 | +import { Edge, Node } from '@vue-flow/core'; | ||
| 2 | +import { RULE_NODE_KEY, RULE_NODE_LOCAL_CACHE_KEY } from '/@/enums/cacheEnum'; | ||
| 3 | +import { createLocalStorage } from '/@/utils/cache'; | ||
| 4 | + | ||
| 5 | +const ruleNodeStorage = createLocalStorage({ prefixKey: RULE_NODE_LOCAL_CACHE_KEY }); | ||
| 6 | + | ||
| 7 | +interface RuleNodeCacheType { | ||
| 8 | + nodes?: Node[]; | ||
| 9 | + edges?: Edge[]; | ||
| 10 | + originX?: number; | ||
| 11 | + originY?: number; | ||
| 12 | +} | ||
| 13 | + | ||
| 14 | +export const setRuleNodeCache = ({ | ||
| 15 | + nodes = [], | ||
| 16 | + edges = [], | ||
| 17 | + originX, | ||
| 18 | + originY, | ||
| 19 | +}: RuleNodeCacheType) => { | ||
| 20 | + ruleNodeStorage.set(RULE_NODE_KEY, { | ||
| 21 | + nodes, | ||
| 22 | + edges, | ||
| 23 | + originX, | ||
| 24 | + originY, | ||
| 25 | + }); | ||
| 26 | +}; | ||
| 27 | + | ||
| 28 | +export const getRuleNodeCache = (): RuleNodeCacheType => ruleNodeStorage.get(RULE_NODE_KEY); | ||
| 29 | + | ||
| 30 | +export const checkHasCacheRuleNode = () => !!getRuleNodeCache(); | ||
| 31 | + | ||
| 32 | +function initRuleNodeStorage() { | ||
| 33 | + const value = ruleNodeStorage.get(RULE_NODE_KEY); | ||
| 34 | + value && ruleNodeStorage.set(RULE_NODE_KEY, value); | ||
| 35 | +} | ||
| 36 | + | ||
| 37 | +initRuleNodeStorage(); |
| @@ -4,29 +4,38 @@ import type { | @@ -4,29 +4,38 @@ import type { | ||
| 4 | NodeComponent, | 4 | NodeComponent, |
| 5 | ValidConnectionFunc, | 5 | ValidConnectionFunc, |
| 6 | GraphNode, | 6 | GraphNode, |
| 7 | + NodeMouseEvent, | ||
| 8 | + GraphEdge, | ||
| 9 | + EdgeMouseEvent, | ||
| 7 | } from '@vue-flow/core'; | 10 | } from '@vue-flow/core'; |
| 8 | -import { ConnectionLineType, SelectionMode, useVueFlow } from '@vue-flow/core'; | ||
| 9 | import type { Ref } from 'vue'; | 11 | import type { Ref } from 'vue'; |
| 10 | -import { markRaw, toRaw, unref } from 'vue'; | ||
| 11 | -import { isFunction } from 'lodash-es'; | ||
| 12 | import type { CreateNodeModal } from '../src/components/CreateNodeModal'; | 12 | import type { CreateNodeModal } from '../src/components/CreateNodeModal'; |
| 13 | -import { EdgeTypeEnum, ElementsTypeEnum, NodeTypeEnum } from '../enum'; | ||
| 14 | -import { BasicEdge, BasicNode } from '../src/components'; | ||
| 15 | import type { EdgeData, NodeData } from '../types/node'; | 13 | import type { EdgeData, NodeData } from '../types/node'; |
| 16 | import type { CreateEdgeModal } from '../src/components/CreateEdgeModal'; | 14 | import type { CreateEdgeModal } from '../src/components/CreateEdgeModal'; |
| 15 | +import { BasicEdge, BasicNode } from '../src/components'; | ||
| 16 | +import { EdgeTypeEnum, ElementsTypeEnum, NodeTypeEnum } from '../enum'; | ||
| 17 | +import { markRaw, toRaw, unref } from 'vue'; | ||
| 18 | +import { isFunction } from 'lodash-es'; | ||
| 19 | +import { ConnectionLineType, SelectionMode, useVueFlow } from '@vue-flow/core'; | ||
| 17 | import { isInputHandle, isOutputHandle } from '../utils'; | 20 | import { isInputHandle, isOutputHandle } from '../utils'; |
| 18 | import { useAddEdges } from './useAddEdges'; | 21 | import { useAddEdges } from './useAddEdges'; |
| 19 | import { UpdateNodeDrawer } from '../src/components/UpdateNodeDrawer'; | 22 | import { UpdateNodeDrawer } from '../src/components/UpdateNodeDrawer'; |
| 20 | import { UpdateEdgeDrawer } from '../src/components/UpdateEdgeDrawer'; | 23 | import { UpdateEdgeDrawer } from '../src/components/UpdateEdgeDrawer'; |
| 21 | import { isNumber } from '/@/utils/is'; | 24 | import { isNumber } from '/@/utils/is'; |
| 25 | +import { RuleContextMenuEnum, useCreateRuleChainContextMenu } from './useRuleChainContextMenu'; | ||
| 26 | +import { RuleChainDetail } from '../types/ruleNode'; | ||
| 27 | +import { useContextMenuAction } from './useContextMenuAction'; | ||
| 28 | +import { useSaveAndRedo } from './useSaveAndRedo'; | ||
| 29 | +import { EntryCategoryComponentEnum } from '../enum/category'; | ||
| 22 | 30 | ||
| 23 | interface UseRuleFlowOptionsType { | 31 | interface UseRuleFlowOptionsType { |
| 24 | id: string; | 32 | id: string; |
| 33 | + ruleChainDetail: Ref<RuleChainDetail | undefined>; | ||
| 25 | createNodeModalActionType: Ref<Nullable<InstanceType<typeof CreateNodeModal>>>; | 34 | createNodeModalActionType: Ref<Nullable<InstanceType<typeof CreateNodeModal>>>; |
| 26 | createEdgeModalActionType: Ref<Nullable<InstanceType<typeof CreateEdgeModal>>>; | 35 | createEdgeModalActionType: Ref<Nullable<InstanceType<typeof CreateEdgeModal>>>; |
| 27 | updateNodeDrawerActionType: Ref<Nullable<InstanceType<typeof UpdateNodeDrawer>>>; | 36 | updateNodeDrawerActionType: Ref<Nullable<InstanceType<typeof UpdateNodeDrawer>>>; |
| 28 | updateEdgeDrawerActionType: Ref<Nullable<InstanceType<typeof UpdateEdgeDrawer>>>; | 37 | updateEdgeDrawerActionType: Ref<Nullable<InstanceType<typeof UpdateEdgeDrawer>>>; |
| 29 | - triggerChange: () => void; | 38 | + useSaveAndRedoActionType: ReturnType<typeof useSaveAndRedo>; |
| 30 | } | 39 | } |
| 31 | 40 | ||
| 32 | const validateInputAndOutput: ValidConnectionFunc = (connection: Connection) => { | 41 | const validateInputAndOutput: ValidConnectionFunc = (connection: Connection) => { |
| @@ -40,12 +49,15 @@ const validateInputAndOutput: ValidConnectionFunc = (connection: Connection) => | @@ -40,12 +49,15 @@ const validateInputAndOutput: ValidConnectionFunc = (connection: Connection) => | ||
| 40 | export function useRuleFlow(options: UseRuleFlowOptionsType) { | 49 | export function useRuleFlow(options: UseRuleFlowOptionsType) { |
| 41 | const { | 50 | const { |
| 42 | id, | 51 | id, |
| 52 | + ruleChainDetail, | ||
| 43 | createEdgeModalActionType, | 53 | createEdgeModalActionType, |
| 44 | updateEdgeDrawerActionType, | 54 | updateEdgeDrawerActionType, |
| 45 | updateNodeDrawerActionType, | 55 | updateNodeDrawerActionType, |
| 46 | - triggerChange, | 56 | + useSaveAndRedoActionType, |
| 47 | } = options; | 57 | } = options; |
| 48 | 58 | ||
| 59 | + const { triggerChange } = useSaveAndRedoActionType; | ||
| 60 | + | ||
| 49 | const flowActionType = useVueFlow({ | 61 | const flowActionType = useVueFlow({ |
| 50 | id, | 62 | id, |
| 51 | maxZoom: 1, | 63 | maxZoom: 1, |
| @@ -96,6 +108,9 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { | @@ -96,6 +108,9 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { | ||
| 96 | onNodeDoubleClick, | 108 | onNodeDoubleClick, |
| 97 | onEdgeDoubleClick, | 109 | onEdgeDoubleClick, |
| 98 | onNodeDragStop, | 110 | onNodeDragStop, |
| 111 | + onNodeContextMenu, | ||
| 112 | + onEdgeContextMenu, | ||
| 113 | + onPaneContextMenu, | ||
| 99 | } = flowActionType; | 114 | } = flowActionType; |
| 100 | 115 | ||
| 101 | const { getAddedgesParams } = useAddEdges(); | 116 | const { getAddedgesParams } = useAddEdges(); |
| @@ -125,6 +140,119 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { | @@ -125,6 +140,119 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { | ||
| 125 | }); | 140 | }); |
| 126 | 141 | ||
| 127 | onNodeDoubleClick(async ({ node }) => { | 142 | onNodeDoubleClick(async ({ node }) => { |
| 143 | + handleUpdateNode(node); | ||
| 144 | + }); | ||
| 145 | + | ||
| 146 | + onEdgeDoubleClick(async ({ edge }) => { | ||
| 147 | + handleUpdateEdge(edge); | ||
| 148 | + }); | ||
| 149 | + | ||
| 150 | + onNodeDragStop(() => { | ||
| 151 | + triggerChange(); | ||
| 152 | + }); | ||
| 153 | + | ||
| 154 | + const { | ||
| 155 | + createNodeContextMenu, | ||
| 156 | + createElementsSelectedContextMenu, | ||
| 157 | + createEdgeContextMenu, | ||
| 158 | + createPanelContextMenu, | ||
| 159 | + } = useCreateRuleChainContextMenu(); | ||
| 160 | + | ||
| 161 | + const { handleContextMenuAction } = useContextMenuAction(); | ||
| 162 | + | ||
| 163 | + onNodeContextMenu(async (params) => { | ||
| 164 | + const menuType = params.node.selected | ||
| 165 | + ? await createElementsSelectedContextMenu( | ||
| 166 | + params, | ||
| 167 | + unref(useSaveAndRedoActionType.changeMarker) | ||
| 168 | + ) | ||
| 169 | + : await createNodeContextMenu(params); | ||
| 170 | + | ||
| 171 | + if (menuType) { | ||
| 172 | + if (menuType === RuleContextMenuEnum.DETAIL) { | ||
| 173 | + handleUpdateNode(params.node); | ||
| 174 | + return; | ||
| 175 | + } | ||
| 176 | + | ||
| 177 | + handleContextMenuAction({ | ||
| 178 | + menuType, | ||
| 179 | + flowActionType, | ||
| 180 | + event: params.event, | ||
| 181 | + node: params.node, | ||
| 182 | + useSaveAndRedoActionType, | ||
| 183 | + }); | ||
| 184 | + } | ||
| 185 | + }); | ||
| 186 | + | ||
| 187 | + onEdgeContextMenu(async (params) => { | ||
| 188 | + const isInputNode = | ||
| 189 | + (params.edge.sourceNode.data as NodeData).config?.key === EntryCategoryComponentEnum.INPUT; | ||
| 190 | + const menuType = await createEdgeContextMenu( | ||
| 191 | + getCreateEdgeContextMenuParams(params), | ||
| 192 | + isInputNode | ||
| 193 | + ); | ||
| 194 | + | ||
| 195 | + if (menuType) { | ||
| 196 | + if (menuType === RuleContextMenuEnum.DETAIL) { | ||
| 197 | + handleUpdateEdge(params.edge); | ||
| 198 | + return; | ||
| 199 | + } | ||
| 200 | + | ||
| 201 | + handleContextMenuAction({ | ||
| 202 | + menuType, | ||
| 203 | + flowActionType, | ||
| 204 | + event: params.event, | ||
| 205 | + useSaveAndRedoActionType, | ||
| 206 | + edge: params.edge, | ||
| 207 | + }); | ||
| 208 | + } | ||
| 209 | + }); | ||
| 210 | + | ||
| 211 | + onPaneContextMenu(async (params) => { | ||
| 212 | + const menuType = unref(flowActionType.getSelectedElements).length | ||
| 213 | + ? await createElementsSelectedContextMenu( | ||
| 214 | + getCreatePanelContextMenuParams(params), | ||
| 215 | + unref(useSaveAndRedoActionType.changeMarker) | ||
| 216 | + ) | ||
| 217 | + : await createPanelContextMenu( | ||
| 218 | + getCreatePanelContextMenuParams(params), | ||
| 219 | + unref(useSaveAndRedoActionType.changeMarker) | ||
| 220 | + ); | ||
| 221 | + | ||
| 222 | + if (menuType) { | ||
| 223 | + handleContextMenuAction({ | ||
| 224 | + menuType, | ||
| 225 | + flowActionType, | ||
| 226 | + event: params, | ||
| 227 | + useSaveAndRedoActionType, | ||
| 228 | + }); | ||
| 229 | + } | ||
| 230 | + }); | ||
| 231 | + | ||
| 232 | + /** | ||
| 233 | + * @description 验证是否有连接label | ||
| 234 | + * @param sourceData | ||
| 235 | + * @returns | ||
| 236 | + */ | ||
| 237 | + function validateHasLabelConnection(sourceData: NodeData) { | ||
| 238 | + return !!sourceData?.config?.configurationDescriptor.nodeDefinition?.relationTypes?.length; | ||
| 239 | + } | ||
| 240 | + | ||
| 241 | + function handleMaxConnectionPoint(sourceNode?: GraphNode) { | ||
| 242 | + if (!sourceNode) return; | ||
| 243 | + | ||
| 244 | + const maxConnectionPoint = unref(sourceNode).data?.config?.maxConnectionPoint; | ||
| 245 | + | ||
| 246 | + if (!maxConnectionPoint || !isNumber(maxConnectionPoint)) return; | ||
| 247 | + | ||
| 248 | + const sourceId = sourceNode.id; | ||
| 249 | + const connectionPool = unref(getEdges).filter((item) => item.source === sourceId); | ||
| 250 | + if (connectionPool.length >= maxConnectionPoint && connectionPool[0]) { | ||
| 251 | + removeEdges(connectionPool[0].id); | ||
| 252 | + } | ||
| 253 | + } | ||
| 254 | + | ||
| 255 | + async function handleUpdateNode(node: GraphNode) { | ||
| 128 | if ((node.data as NodeData).config?.disableAction) return; | 256 | if ((node.data as NodeData).config?.disableAction) return; |
| 129 | const { flag, data } = | 257 | const { flag, data } = |
| 130 | (await unref(updateNodeDrawerActionType)?.open( | 258 | (await unref(updateNodeDrawerActionType)?.open( |
| @@ -137,9 +265,10 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { | @@ -137,9 +265,10 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { | ||
| 137 | 265 | ||
| 138 | const currentNode = findNode(node.id); | 266 | const currentNode = findNode(node.id); |
| 139 | (currentNode!.data as NodeData).data = data; | 267 | (currentNode!.data as NodeData).data = data; |
| 140 | - }); | 268 | + triggerChange(); |
| 269 | + } | ||
| 141 | 270 | ||
| 142 | - onEdgeDoubleClick(async ({ edge }) => { | 271 | + async function handleUpdateEdge(edge: GraphEdge) { |
| 143 | if (!validateHasLabelConnection(edge.sourceNode.data)) return; | 272 | if (!validateHasLabelConnection(edge.sourceNode.data)) return; |
| 144 | 273 | ||
| 145 | if ((edge.sourceNode.data as NodeData).config?.disableAction) return; | 274 | if ((edge.sourceNode.data as NodeData).config?.disableAction) return; |
| @@ -156,33 +285,48 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { | @@ -156,33 +285,48 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { | ||
| 156 | const currentEdge = findEdge(edge.id); | 285 | const currentEdge = findEdge(edge.id); |
| 157 | 286 | ||
| 158 | (currentEdge!.data as EdgeData).data = toRaw(unref(data)); | 287 | (currentEdge!.data as EdgeData).data = toRaw(unref(data)); |
| 159 | - }); | ||
| 160 | - | ||
| 161 | - onNodeDragStop(() => { | ||
| 162 | - triggerChange(); | ||
| 163 | - }); | ||
| 164 | 288 | ||
| 165 | - /** | ||
| 166 | - * @description 验证是否有连接label | ||
| 167 | - * @param sourceData | ||
| 168 | - * @returns | ||
| 169 | - */ | ||
| 170 | - function validateHasLabelConnection(sourceData: NodeData) { | ||
| 171 | - return !!sourceData?.config?.configurationDescriptor.nodeDefinition?.relationTypes?.length; | 289 | + triggerChange?.(); |
| 172 | } | 290 | } |
| 173 | 291 | ||
| 174 | - function handleMaxConnectionPoint(sourceNode?: GraphNode) { | ||
| 175 | - if (!sourceNode) return; | ||
| 176 | - | ||
| 177 | - const maxConnectionPoint = unref(sourceNode).data?.config?.maxConnectionPoint; | ||
| 178 | - | ||
| 179 | - if (!maxConnectionPoint || !isNumber(maxConnectionPoint)) return; | 292 | + function getCreatePanelContextMenuParams(params: Event) { |
| 293 | + return { | ||
| 294 | + event: params as MouseEvent, | ||
| 295 | + node: { | ||
| 296 | + data: { | ||
| 297 | + data: { name: '规则链' }, | ||
| 298 | + config: { | ||
| 299 | + name: unref(ruleChainDetail)?.name, | ||
| 300 | + backgroundColor: '#aac7e4', | ||
| 301 | + configurationDescriptor: { | ||
| 302 | + nodeDefinition: { | ||
| 303 | + icon: 'material-symbols:settings-ethernet', | ||
| 304 | + }, | ||
| 305 | + }, | ||
| 306 | + }, | ||
| 307 | + }, | ||
| 308 | + }, | ||
| 309 | + } as NodeMouseEvent; | ||
| 310 | + } | ||
| 180 | 311 | ||
| 181 | - const sourceId = sourceNode.id; | ||
| 182 | - const connectionPool = unref(getEdges).filter((item) => item.source === sourceId); | ||
| 183 | - if (connectionPool.length >= maxConnectionPoint && connectionPool[0]) { | ||
| 184 | - removeEdges(connectionPool[0].id); | ||
| 185 | - } | 312 | + function getCreateEdgeContextMenuParams(params: EdgeMouseEvent) { |
| 313 | + return { | ||
| 314 | + event: params.event as MouseEvent, | ||
| 315 | + node: { | ||
| 316 | + data: { | ||
| 317 | + data: { name: '链接' }, | ||
| 318 | + config: { | ||
| 319 | + name: unref(params.edge.data as EdgeData)?.data?.type?.join(' / '), | ||
| 320 | + backgroundColor: '#aac7e4', | ||
| 321 | + configurationDescriptor: { | ||
| 322 | + nodeDefinition: { | ||
| 323 | + icon: 'material-symbols:trending-flat', | ||
| 324 | + }, | ||
| 325 | + }, | ||
| 326 | + }, | ||
| 327 | + }, | ||
| 328 | + }, | ||
| 329 | + } as NodeMouseEvent; | ||
| 186 | } | 330 | } |
| 187 | 331 | ||
| 188 | return { flowActionType }; | 332 | return { flowActionType }; |
| @@ -3,11 +3,12 @@ import { ComputedRef, computed, ref, unref } from 'vue'; | @@ -3,11 +3,12 @@ import { ComputedRef, computed, ref, unref } from 'vue'; | ||
| 3 | import { BasicNodeBindData, EdgeData, NodeData } from '../types/node'; | 3 | import { BasicNodeBindData, EdgeData, NodeData } from '../types/node'; |
| 4 | import { EntryCategoryComponentEnum } from '../enum/category'; | 4 | import { EntryCategoryComponentEnum } from '../enum/category'; |
| 5 | import { useBasicDataTransform } from './useBasicDataTransform'; | 5 | import { useBasicDataTransform } from './useBasicDataTransform'; |
| 6 | -import { getRuleChainData, saveRuleChainData } from '/@/api/ruleDesigner'; | ||
| 7 | -import { ConnectionItemType, RuleChainType } from '../types/ruleNode'; | 6 | +import { getRuleChainData, getRuleChainDetail, saveRuleChainData } from '/@/api/ruleDesigner'; |
| 7 | +import { ConnectionItemType, RuleChainDetail, RuleChainType } from '../types/ruleNode'; | ||
| 8 | import { useInputNode } from './useInputNode'; | 8 | import { useInputNode } from './useInputNode'; |
| 9 | import { buildUUID } from '/@/utils/uuid'; | 9 | import { buildUUID } from '/@/utils/uuid'; |
| 10 | import { useRoute } from 'vue-router'; | 10 | import { useRoute } from 'vue-router'; |
| 11 | +import { RuleChainEntityType } from '../enum/entity'; | ||
| 11 | 12 | ||
| 12 | const ignoreNodeKeys: string[] = [EntryCategoryComponentEnum.INPUT]; | 13 | const ignoreNodeKeys: string[] = [EntryCategoryComponentEnum.INPUT]; |
| 13 | 14 | ||
| @@ -20,8 +21,12 @@ export function useSaveAndRedo() { | @@ -20,8 +21,12 @@ export function useSaveAndRedo() { | ||
| 20 | 21 | ||
| 21 | const route = useRoute(); | 22 | const route = useRoute(); |
| 22 | 23 | ||
| 24 | + const debugMarker = ref(false); | ||
| 25 | + | ||
| 23 | const getRuleChainId = computed(() => (route.params as Record<'id', string>).id); | 26 | const getRuleChainId = computed(() => (route.params as Record<'id', string>).id); |
| 24 | 27 | ||
| 28 | + const ruleChainDetail = ref<RuleChainDetail>(); | ||
| 29 | + | ||
| 25 | const { mergeData, deconstructionData } = useBasicDataTransform(); | 30 | const { mergeData, deconstructionData } = useBasicDataTransform(); |
| 26 | 31 | ||
| 27 | const triggerChange = () => { | 32 | const triggerChange = () => { |
| @@ -71,7 +76,16 @@ export function useSaveAndRedo() { | @@ -71,7 +76,16 @@ export function useSaveAndRedo() { | ||
| 71 | 76 | ||
| 72 | const data = nodeData.data; | 77 | const data = nodeData.data; |
| 73 | 78 | ||
| 74 | - nodes.push(mergeData(data, nodeData, node)); | 79 | + nodes.push( |
| 80 | + Object.assign( | ||
| 81 | + mergeData(data, nodeData, node), | ||
| 82 | + nodeData.created | ||
| 83 | + ? ({ | ||
| 84 | + id: { id: node.id, entityType: RuleChainEntityType.RULE_NODE }, | ||
| 85 | + } as BasicNodeBindData) | ||
| 86 | + : {} | ||
| 87 | + ) | ||
| 88 | + ); | ||
| 75 | } | 89 | } |
| 76 | 90 | ||
| 77 | return nodes; | 91 | return nodes; |
| @@ -116,6 +130,10 @@ export function useSaveAndRedo() { | @@ -116,6 +130,10 @@ export function useSaveAndRedo() { | ||
| 116 | resetChange(); | 130 | resetChange(); |
| 117 | }; | 131 | }; |
| 118 | 132 | ||
| 133 | + async function getCurrentRuleChainDetail() { | ||
| 134 | + ruleChainDetail.value = await getRuleChainDetail(unref(getRuleChainId)); | ||
| 135 | + } | ||
| 136 | + | ||
| 119 | async function handleSaveRuleChain( | 137 | async function handleSaveRuleChain( |
| 120 | connections: ConnectionItemType[], | 138 | connections: ConnectionItemType[], |
| 121 | nodes: BasicNodeBindData[], | 139 | nodes: BasicNodeBindData[], |
| @@ -123,12 +141,13 @@ export function useSaveAndRedo() { | @@ -123,12 +141,13 @@ export function useSaveAndRedo() { | ||
| 123 | ) { | 141 | ) { |
| 124 | try { | 142 | try { |
| 125 | loading.value = true; | 143 | loading.value = true; |
| 144 | + | ||
| 126 | const data = await saveRuleChainData({ | 145 | const data = await saveRuleChainData({ |
| 127 | connections, | 146 | connections, |
| 128 | nodes, | 147 | nodes, |
| 129 | firstNodeIndex, | 148 | firstNodeIndex, |
| 130 | ruleChainId: { | 149 | ruleChainId: { |
| 131 | - entityType: 'RULE_CHAIN', | 150 | + entityType: RuleChainEntityType.RULE_CHAIN, |
| 132 | id: unref(getRuleChainId), | 151 | id: unref(getRuleChainId), |
| 133 | }, | 152 | }, |
| 134 | }); | 153 | }); |
| @@ -175,12 +194,23 @@ export function useSaveAndRedo() { | @@ -175,12 +194,23 @@ export function useSaveAndRedo() { | ||
| 175 | return elements; | 194 | return elements; |
| 176 | } | 195 | } |
| 177 | 196 | ||
| 197 | + const handleRemoveDebug = (flowActionType: VueFlowStore) => { | ||
| 198 | + for (const item of unref(flowActionType.getNodes)) { | ||
| 199 | + (item.data as NodeData)!.data!.debugMode = false; | ||
| 200 | + } | ||
| 201 | + triggerChange(); | ||
| 202 | + }; | ||
| 203 | + | ||
| 178 | return { | 204 | return { |
| 179 | loading, | 205 | loading, |
| 206 | + debugMarker, | ||
| 180 | changeMarker, | 207 | changeMarker, |
| 208 | + ruleChainDetail, | ||
| 181 | triggerChange, | 209 | triggerChange, |
| 182 | handleApplyChange, | 210 | handleApplyChange, |
| 183 | handleRedoChange, | 211 | handleRedoChange, |
| 212 | + handleRemoveDebug, | ||
| 184 | getCurrentPageMetaData, | 213 | getCurrentPageMetaData, |
| 214 | + getCurrentRuleChainDetail, | ||
| 185 | }; | 215 | }; |
| 186 | } | 216 | } |
| @@ -22,6 +22,7 @@ | @@ -22,6 +22,7 @@ | ||
| 22 | import { Icon } from '/@/components/Icon'; | 22 | import { Icon } from '/@/components/Icon'; |
| 23 | import { UpdateNodeDrawer } from './src/components/UpdateNodeDrawer'; | 23 | import { UpdateNodeDrawer } from './src/components/UpdateNodeDrawer'; |
| 24 | import { UpdateEdgeDrawer } from './src/components/UpdateEdgeDrawer'; | 24 | import { UpdateEdgeDrawer } from './src/components/UpdateEdgeDrawer'; |
| 25 | + import { NodeData } from './types/node'; | ||
| 25 | 26 | ||
| 26 | const getId = Number(Math.random().toString().substring(2)).toString(16); | 27 | const getId = Number(Math.random().toString().substring(2)).toString(16); |
| 27 | 28 | ||
| @@ -39,22 +40,28 @@ | @@ -39,22 +40,28 @@ | ||
| 39 | 40 | ||
| 40 | const elements = ref([]); | 41 | const elements = ref([]); |
| 41 | 42 | ||
| 43 | + const useSaveAndRedoActionType = useSaveAndRedo(); | ||
| 44 | + | ||
| 42 | const { | 45 | const { |
| 43 | loading, | 46 | loading, |
| 44 | changeMarker, | 47 | changeMarker, |
| 48 | + ruleChainDetail, | ||
| 45 | getCurrentPageMetaData, | 49 | getCurrentPageMetaData, |
| 46 | triggerChange, | 50 | triggerChange, |
| 47 | handleApplyChange, | 51 | handleApplyChange, |
| 48 | handleRedoChange, | 52 | handleRedoChange, |
| 49 | - } = useSaveAndRedo(); | 53 | + handleRemoveDebug, |
| 54 | + getCurrentRuleChainDetail, | ||
| 55 | + } = useSaveAndRedoActionType; | ||
| 50 | 56 | ||
| 51 | const { flowActionType } = useRuleFlow({ | 57 | const { flowActionType } = useRuleFlow({ |
| 52 | id: getId, | 58 | id: getId, |
| 59 | + ruleChainDetail, | ||
| 53 | createNodeModalActionType, | 60 | createNodeModalActionType, |
| 54 | createEdgeModalActionType, | 61 | createEdgeModalActionType, |
| 55 | updateEdgeDrawerActionType, | 62 | updateEdgeDrawerActionType, |
| 56 | updateNodeDrawerActionType, | 63 | updateNodeDrawerActionType, |
| 57 | - triggerChange, | 64 | + useSaveAndRedoActionType, |
| 58 | }); | 65 | }); |
| 59 | 66 | ||
| 60 | const { handleOnDragOver, handleOnDrop } = useDragCreate({ | 67 | const { handleOnDragOver, handleOnDrop } = useDragCreate({ |
| @@ -72,13 +79,17 @@ | @@ -72,13 +79,17 @@ | ||
| 72 | 79 | ||
| 73 | const getDeleteDisplayState = computed(() => unref(flowActionType.getSelectedElements).length); | 80 | const getDeleteDisplayState = computed(() => unref(flowActionType.getSelectedElements).length); |
| 74 | 81 | ||
| 82 | + const getDebugMarker = computed(() => | ||
| 83 | + flowActionType.getNodes.value.some((item) => (item.data as NodeData).data?.debugMode) | ||
| 84 | + ); | ||
| 85 | + | ||
| 75 | const handleDeleteSelectionElements = () => { | 86 | const handleDeleteSelectionElements = () => { |
| 76 | - flowActionType.removeEdges(unref(flowActionType.getSelectedEdges)); | ||
| 77 | flowActionType.removeNodes(unref(flowActionType.getSelectedNodes)); | 87 | flowActionType.removeNodes(unref(flowActionType.getSelectedNodes)); |
| 78 | }; | 88 | }; |
| 79 | 89 | ||
| 80 | onMounted(() => { | 90 | onMounted(() => { |
| 81 | getCurrentPageMetaData(flowActionType); | 91 | getCurrentPageMetaData(flowActionType); |
| 92 | + getCurrentRuleChainDetail(); | ||
| 82 | }); | 93 | }); |
| 83 | 94 | ||
| 84 | createFlowContext({ | 95 | createFlowContext({ |
| @@ -127,7 +138,9 @@ | @@ -127,7 +138,9 @@ | ||
| 127 | <Icon class="!text-3xl !text-light-50" icon="mdi:delete" /> | 138 | <Icon class="!text-3xl !text-light-50" icon="mdi:delete" /> |
| 128 | </button> | 139 | </button> |
| 129 | <button | 140 | <button |
| 141 | + :class="getDebugMarker ? '!bg-orange-600 !opacity-100' : 'opacity-50'" | ||
| 130 | class="button-box-shadow w-14 h-14 flex justify-center items-center bg-gray-400 rounded-full opacity-50" | 142 | class="button-box-shadow w-14 h-14 flex justify-center items-center bg-gray-400 rounded-full opacity-50" |
| 143 | + @click="handleRemoveDebug(flowActionType)" | ||
| 131 | > | 144 | > |
| 132 | <Icon class="!text-3xl !text-light-50" icon="carbon:debug" /> | 145 | <Icon class="!text-3xl !text-light-50" icon="carbon:debug" /> |
| 133 | </button> | 146 | </button> |
| 1 | +import { RouteLocationNormalizedLoaded } from 'vue-router'; | ||
| 1 | import { RuleChainFieldsEnum, RuleChainFieldsNameEnum } from '../../../enum/formField/flow'; | 2 | import { RuleChainFieldsEnum, RuleChainFieldsNameEnum } from '../../../enum/formField/flow'; |
| 2 | import { getRuleChains } from '/@/api/ruleDesigner'; | 3 | import { getRuleChains } from '/@/api/ruleDesigner'; |
| 3 | import { FormSchema } from '/@/components/Form'; | 4 | import { FormSchema } from '/@/components/Form'; |
| 4 | 5 | ||
| 5 | -const fetch = async (params: Recordable) => { | 6 | +const fetch = async (params: Recordable, ruleChainId: string) => { |
| 6 | try { | 7 | try { |
| 7 | const result = await getRuleChains(params); | 8 | const result = await getRuleChains(params); |
| 8 | - const data = result.data.map((item) => ({ label: item.name, value: item.id.id })); | 9 | + const data = result.data |
| 10 | + .map((item) => ({ label: item.name, value: item.id.id })) | ||
| 11 | + .filter((item) => item.value !== ruleChainId); | ||
| 9 | return data; | 12 | return data; |
| 10 | } catch (err) { | 13 | } catch (err) { |
| 11 | console.error(err); | 14 | console.error(err); |
| @@ -13,24 +16,27 @@ const fetch = async (params: Recordable) => { | @@ -13,24 +16,27 @@ const fetch = async (params: Recordable) => { | ||
| 13 | } | 16 | } |
| 14 | }; | 17 | }; |
| 15 | 18 | ||
| 16 | -export const formSchemas: FormSchema[] = [ | ||
| 17 | - { | ||
| 18 | - field: RuleChainFieldsEnum.RULE_CHAIN_ID, | ||
| 19 | - label: RuleChainFieldsNameEnum.RULE_CHAIN_ID, | ||
| 20 | - component: 'ApiSearchSelect', | ||
| 21 | - componentProps: () => { | ||
| 22 | - return { | ||
| 23 | - placeholder: '请选择所属产品', | ||
| 24 | - showSearch: true, | ||
| 25 | - params: { | ||
| 26 | - pageSize: 50, | ||
| 27 | - page: 0, | ||
| 28 | - type: 'CORE', | ||
| 29 | - }, | ||
| 30 | - api: fetch, | ||
| 31 | - searchApi: fetch, | ||
| 32 | - getPopupContainer: () => document.body, | ||
| 33 | - }; | 19 | +export const getFormSchemas = (route: RouteLocationNormalizedLoaded): FormSchema[] => { |
| 20 | + const ruleChainId = (route.params as Record<'id', string>).id; | ||
| 21 | + return [ | ||
| 22 | + { | ||
| 23 | + field: RuleChainFieldsEnum.RULE_CHAIN_ID, | ||
| 24 | + label: RuleChainFieldsNameEnum.RULE_CHAIN_ID, | ||
| 25 | + component: 'ApiSearchSelect', | ||
| 26 | + componentProps: () => { | ||
| 27 | + return { | ||
| 28 | + placeholder: '请选择所属产品', | ||
| 29 | + showSearch: true, | ||
| 30 | + params: { | ||
| 31 | + pageSize: 50, | ||
| 32 | + page: 0, | ||
| 33 | + type: 'CORE', | ||
| 34 | + }, | ||
| 35 | + api: (params: Recordable) => fetch(params, ruleChainId), | ||
| 36 | + searchApi: (params: Recordable) => fetch(params, ruleChainId), | ||
| 37 | + getPopupContainer: () => document.body, | ||
| 38 | + }; | ||
| 39 | + }, | ||
| 34 | }, | 40 | }, |
| 35 | - }, | ||
| 36 | -]; | 41 | + ]; |
| 42 | +}; |
| 1 | <script lang="ts" setup> | 1 | <script lang="ts" setup> |
| 2 | import type { CreateModalDefineExposeType } from '../../../types'; | 2 | import type { CreateModalDefineExposeType } from '../../../types'; |
| 3 | import { BasicForm, useForm } from '/@/components/Form'; | 3 | import { BasicForm, useForm } from '/@/components/Form'; |
| 4 | - import { formSchemas } from './create.config'; | 4 | + import { getFormSchemas } from './create.config'; |
| 5 | import { NodeData } from '../../../types/node'; | 5 | import { NodeData } from '../../../types/node'; |
| 6 | + import { useRoute } from 'vue-router'; | ||
| 6 | 7 | ||
| 7 | defineProps<{ | 8 | defineProps<{ |
| 8 | config: NodeData; | 9 | config: NodeData; |
| 9 | }>(); | 10 | }>(); |
| 10 | 11 | ||
| 12 | + const ROUTE = useRoute(); | ||
| 13 | + | ||
| 11 | const [register, { validate, getFieldsValue, setFieldsValue, resetFields }] = useForm({ | 14 | const [register, { validate, getFieldsValue, setFieldsValue, resetFields }] = useForm({ |
| 12 | - schemas: formSchemas, | 15 | + schemas: getFormSchemas(ROUTE), |
| 13 | showActionButtonGroup: false, | 16 | showActionButtonGroup: false, |
| 14 | }); | 17 | }); |
| 15 | 18 |
| @@ -2,25 +2,32 @@ | @@ -2,25 +2,32 @@ | ||
| 2 | import { NodeProps } from '@vue-flow/core'; | 2 | import { NodeProps } from '@vue-flow/core'; |
| 3 | import { Icon } from '/@/components/Icon'; | 3 | import { Icon } from '/@/components/Icon'; |
| 4 | import { Tooltip } from 'ant-design-vue'; | 4 | import { Tooltip } from 'ant-design-vue'; |
| 5 | + import { useRouter } from 'vue-router'; | ||
| 6 | + import { NodeData } from '../../../types/node'; | ||
| 5 | 7 | ||
| 6 | - defineProps<{ | ||
| 7 | - nodeProps?: NodeProps; | 8 | + const props = defineProps<{ |
| 9 | + nodeProps?: NodeProps<NodeData>; | ||
| 8 | }>(); | 10 | }>(); |
| 9 | 11 | ||
| 12 | + const ROUTER = useRouter(); | ||
| 13 | + | ||
| 10 | const handleClick = () => { | 14 | const handleClick = () => { |
| 11 | - // event.stopPropagation(); | ||
| 12 | - // event.preventDefault(); | ||
| 13 | - // console.log(props); | 15 | + const { data } = props.nodeProps?.data || ({} as NodeData); |
| 16 | + const { configuration } = (data || {}) as { configuration: Record<'ruleChainId', string> }; | ||
| 17 | + if (configuration.ruleChainId) { | ||
| 18 | + ROUTER.push(`/rule/chain/${configuration.ruleChainId}`); | ||
| 19 | + } | ||
| 14 | }; | 20 | }; |
| 15 | </script> | 21 | </script> |
| 16 | 22 | ||
| 17 | <template> | 23 | <template> |
| 18 | - <div class="w-full h-6 flex justify-end" @click="handleClick"> | 24 | + <div class="w-full h-6 flex justify-end"> |
| 19 | <Tooltip color="#fff"> | 25 | <Tooltip color="#fff"> |
| 20 | <template #title> | 26 | <template #title> |
| 21 | <span class="text-slate-500 italic">打开规则链</span> | 27 | <span class="text-slate-500 italic">打开规则链</span> |
| 22 | </template> | 28 | </template> |
| 23 | <Icon | 29 | <Icon |
| 30 | + @click="handleClick" | ||
| 24 | icon="material-symbols:login" | 31 | icon="material-symbols:login" |
| 25 | class="cursor-pointer svg:text-lg svg:text-light-50 border-1 border-light-50 bg-purple-400 hover:bg-purple-500 rounded" | 32 | class="cursor-pointer svg:text-lg svg:text-light-50 border-1 border-light-50 bg-purple-400 hover:bg-purple-500 rounded" |
| 26 | /> | 33 | /> |
| @@ -29,6 +29,11 @@ | @@ -29,6 +29,11 @@ | ||
| 29 | return config?.name; | 29 | return config?.name; |
| 30 | }); | 30 | }); |
| 31 | 31 | ||
| 32 | + const getIconUrl = computed(() => { | ||
| 33 | + const { iconUrl } = unref(getNodeDefinition); | ||
| 34 | + return iconUrl; | ||
| 35 | + }); | ||
| 36 | + | ||
| 32 | const getIcon = computed(() => { | 37 | const getIcon = computed(() => { |
| 33 | const { icon } = unref(getNodeDefinition); | 38 | const { icon } = unref(getNodeDefinition); |
| 34 | const { category } = unref(getData); | 39 | const { category } = unref(getData); |
| @@ -64,10 +69,11 @@ | @@ -64,10 +69,11 @@ | ||
| 64 | </template> | 69 | </template> |
| 65 | <main | 70 | <main |
| 66 | class="basic-node-hover flex items-center w-44 h-12 rounded border px-4 py-2 border-gray-700 dark:text-light-50" | 71 | class="basic-node-hover flex items-center w-44 h-12 rounded border px-4 py-2 border-gray-700 dark:text-light-50" |
| 67 | - :style="{ backgroundColor: getBackgroundColor }" | 72 | + :style="{ backgroundColor: getBackgroundColor, outline: selected ? '3px solid red' : 'none' }" |
| 68 | > | 73 | > |
| 69 | <div> | 74 | <div> |
| 70 | - <Icon class="text-2xl dark:text-light-50" :icon="getIcon" /> | 75 | + <Icon v-if="!getIconUrl" class="text-2xl dark:text-light-50" :icon="getIcon" /> |
| 76 | + <img v-if="getIconUrl" :src="getIconUrl" class="w-4 h-4" /> | ||
| 71 | </div> | 77 | </div> |
| 72 | <BasicToolbar v-if="!getData.config?.disableAction" v-bind="$props" /> | 78 | <BasicToolbar v-if="!getData.config?.disableAction" v-bind="$props" /> |
| 73 | <div class="flex text-xs flex-col ml-2 text-left truncate"> | 79 | <div class="flex text-xs flex-col ml-2 text-left truncate"> |
| 1 | +import { NodeMouseEvent } from '@vue-flow/core'; | ||
| 2 | +import ContextMenuVue from './index.vue'; | ||
| 3 | +import { isClient } from '/@/utils/is'; | ||
| 4 | +import { createVNode, render, getCurrentInstance, onUnmounted, toRaw, unref } from 'vue'; | ||
| 5 | + | ||
| 6 | +const menuManager: { | ||
| 7 | + domList: Element[]; | ||
| 8 | + resolve: Fn; | ||
| 9 | +} = { | ||
| 10 | + domList: [], | ||
| 11 | + resolve: () => {}, | ||
| 12 | +}; | ||
| 13 | + | ||
| 14 | +const createContextMenu = function ( | ||
| 15 | + options: NodeMouseEvent, | ||
| 16 | + params?: InstanceType<typeof ContextMenuVue>['$props'] | ||
| 17 | +) { | ||
| 18 | + const { event } = options || {}; | ||
| 19 | + | ||
| 20 | + if (!(params?.items && params?.items.length)) return; | ||
| 21 | + | ||
| 22 | + event && event?.preventDefault(); | ||
| 23 | + | ||
| 24 | + if (!isClient) { | ||
| 25 | + return; | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + return new Promise((resolve) => { | ||
| 29 | + const body = document.body; | ||
| 30 | + | ||
| 31 | + const container = document.createElement('div'); | ||
| 32 | + const propsData: Partial<InstanceType<typeof ContextMenuVue>['$props']> = { | ||
| 33 | + ...params, | ||
| 34 | + }; | ||
| 35 | + | ||
| 36 | + if (options.event) { | ||
| 37 | + const { clientX, clientY } = options.event as MouseEvent; | ||
| 38 | + propsData.axis = { x: clientX, y: clientY }; | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + if (options.node) { | ||
| 42 | + propsData.nodeData = toRaw(unref(options.node.data)); | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + const vm = createVNode(ContextMenuVue, propsData); | ||
| 46 | + render(vm, container); | ||
| 47 | + | ||
| 48 | + const handleClick = function () { | ||
| 49 | + menuManager.resolve(''); | ||
| 50 | + }; | ||
| 51 | + | ||
| 52 | + menuManager.domList.push(container); | ||
| 53 | + | ||
| 54 | + const remove = function () { | ||
| 55 | + menuManager.domList.forEach((dom: Element) => { | ||
| 56 | + try { | ||
| 57 | + dom && body.removeChild(dom); | ||
| 58 | + } catch (error) {} | ||
| 59 | + }); | ||
| 60 | + body.removeEventListener('click', handleClick); | ||
| 61 | + body.removeEventListener('scroll', handleClick); | ||
| 62 | + }; | ||
| 63 | + | ||
| 64 | + menuManager.resolve = function (arg) { | ||
| 65 | + remove(); | ||
| 66 | + resolve(arg); | ||
| 67 | + }; | ||
| 68 | + remove(); | ||
| 69 | + body.appendChild(container); | ||
| 70 | + body.addEventListener('click', handleClick); | ||
| 71 | + body.addEventListener('scroll', handleClick); | ||
| 72 | + }); | ||
| 73 | +}; | ||
| 74 | + | ||
| 75 | +const destroyContextMenu = function () { | ||
| 76 | + if (menuManager) { | ||
| 77 | + menuManager.resolve(''); | ||
| 78 | + menuManager.domList = []; | ||
| 79 | + } | ||
| 80 | +}; | ||
| 81 | + | ||
| 82 | +export function useContextMenu(authRemove = true) { | ||
| 83 | + if (getCurrentInstance() && authRemove) { | ||
| 84 | + onUnmounted(() => { | ||
| 85 | + destroyContextMenu(); | ||
| 86 | + }); | ||
| 87 | + } | ||
| 88 | + return [createContextMenu, destroyContextMenu]; | ||
| 89 | +} |
| 1 | +<script setup lang="ts"> | ||
| 2 | + import { computed, CSSProperties, unref } from 'vue'; | ||
| 3 | + import { NodeData } from '../../../types/node'; | ||
| 4 | + import { RuleChainContextMenuItemType } from './index.type'; | ||
| 5 | + import { Icon } from '/@/components/Icon'; | ||
| 6 | + import { Divider } from 'ant-design-vue'; | ||
| 7 | + | ||
| 8 | + const props = withDefaults( | ||
| 9 | + defineProps<{ | ||
| 10 | + width?: number; | ||
| 11 | + itemHeight?: number; | ||
| 12 | + styles?: CSSProperties; | ||
| 13 | + axis?: Record<'x' | 'y', number>; | ||
| 14 | + items?: RuleChainContextMenuItemType[]; | ||
| 15 | + nodeData?: NodeData; | ||
| 16 | + }>(), | ||
| 17 | + { | ||
| 18 | + width: 320, | ||
| 19 | + itemHeight: 48, | ||
| 20 | + axis: () => ({ x: 0, y: 0 }), | ||
| 21 | + items: () => [], | ||
| 22 | + nodeData: () => ({}), | ||
| 23 | + } | ||
| 24 | + ); | ||
| 25 | + | ||
| 26 | + const getMenuHeight = computed(() => { | ||
| 27 | + const { items, itemHeight } = props; | ||
| 28 | + let dividerNumber = 0; | ||
| 29 | + let menuNumber = 1; | ||
| 30 | + for (const item of items) { | ||
| 31 | + if (item.divider) dividerNumber++; | ||
| 32 | + else menuNumber++; | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + return itemHeight * menuNumber + dividerNumber + 8; | ||
| 36 | + }); | ||
| 37 | + | ||
| 38 | + const getStyle = computed((): CSSProperties => { | ||
| 39 | + const { axis, styles, width } = props; | ||
| 40 | + const { x, y } = axis; | ||
| 41 | + const menuHeight = unref(getMenuHeight); | ||
| 42 | + const menuWidth = width; | ||
| 43 | + const body = document.body; | ||
| 44 | + | ||
| 45 | + const left = body.clientWidth < x + menuWidth ? x - menuWidth : x; | ||
| 46 | + const top = body.clientHeight < y + menuHeight ? y - menuHeight : y; | ||
| 47 | + | ||
| 48 | + return { | ||
| 49 | + ...styles, | ||
| 50 | + position: 'absolute', | ||
| 51 | + width: `${width}px`, | ||
| 52 | + left: `${left + 1}px`, | ||
| 53 | + top: `${top + 1}px`, | ||
| 54 | + }; | ||
| 55 | + }); | ||
| 56 | + | ||
| 57 | + const getNodeIconUrl = computed(() => { | ||
| 58 | + const { config } = props.nodeData; | ||
| 59 | + const { configurationDescriptor } = config || {}; | ||
| 60 | + const { nodeDefinition } = configurationDescriptor || {}; | ||
| 61 | + const { iconUrl } = nodeDefinition || {}; | ||
| 62 | + return iconUrl; | ||
| 63 | + }); | ||
| 64 | + | ||
| 65 | + const getNodeIcon = computed(() => { | ||
| 66 | + const { nodeData } = props; | ||
| 67 | + const { category, config } = nodeData; | ||
| 68 | + const { icon: categoryIcon } = category || {}; | ||
| 69 | + const { configurationDescriptor } = config || {}; | ||
| 70 | + const { nodeDefinition } = configurationDescriptor || {}; | ||
| 71 | + const { icon } = nodeDefinition || {}; | ||
| 72 | + | ||
| 73 | + return categoryIcon || icon; | ||
| 74 | + }); | ||
| 75 | + | ||
| 76 | + const getTitleBackgroundColor = computed(() => { | ||
| 77 | + const { category, config } = props.nodeData; | ||
| 78 | + const { backgroundColor: categoryBackgroundColor } = category || {}; | ||
| 79 | + const { backgroundColor } = config || {}; | ||
| 80 | + return categoryBackgroundColor || backgroundColor; | ||
| 81 | + }); | ||
| 82 | +</script> | ||
| 83 | + | ||
| 84 | +<template> | ||
| 85 | + <div | ||
| 86 | + :style="getStyle" | ||
| 87 | + class="bg-light-50 shadow-lg shadow-dark-50 z-50 rounded-md overflow-hidden pb-2" | ||
| 88 | + > | ||
| 89 | + <div | ||
| 90 | + v-if="nodeData" | ||
| 91 | + :style="{ backgroundColor: getTitleBackgroundColor, height: `${itemHeight}px` }" | ||
| 92 | + class="flex items-center p-2" | ||
| 93 | + > | ||
| 94 | + <Icon v-if="!getNodeIconUrl" :icon="getNodeIcon" class="svg:text-2xl" /> | ||
| 95 | + <img v-if="getNodeIconUrl" :src="getNodeIconUrl" class="w-6 h-6" /> | ||
| 96 | + <div class="ml-4"> | ||
| 97 | + <div class="font-medium">{{ nodeData.config?.name }}</div> | ||
| 98 | + <div class="text-xs">{{ nodeData.data?.name }}</div> | ||
| 99 | + </div> | ||
| 100 | + </div> | ||
| 101 | + <div> | ||
| 102 | + <template v-for="item in items" :key="item.key"> | ||
| 103 | + <div | ||
| 104 | + v-if="!item.divider" | ||
| 105 | + :style="{ height: `${itemHeight}px` }" | ||
| 106 | + class="px-4 flex items-center cursor-pointer hover:bg-neutral-100" | ||
| 107 | + :class="item.disabled && 'disables'" | ||
| 108 | + @click="(event) => !item.disabled && item?.handler?.(event)" | ||
| 109 | + > | ||
| 110 | + <Icon :icon="item.icon" class="svg:text-2xl" /> | ||
| 111 | + <div class="flex-auto px-4">{{ item.label }}</div> | ||
| 112 | + <div class="flex items-center"> {{ item.shortcutKey }} </div> | ||
| 113 | + </div> | ||
| 114 | + <Divider v-if="item.divider" class="!m-0" /> | ||
| 115 | + </template> | ||
| 116 | + </div> | ||
| 117 | + </div> | ||
| 118 | +</template> | ||
| 119 | + | ||
| 120 | +<style lang="less" scoped> | ||
| 121 | + .disables { | ||
| 122 | + @apply pointer-events-none text-gray-300; | ||
| 123 | + } | ||
| 124 | +</style> |
| @@ -113,7 +113,11 @@ | @@ -113,7 +113,11 @@ | ||
| 113 | <Empty v-if="!shadowComponent" description="未找到链接组件" /> | 113 | <Empty v-if="!shadowComponent" description="未找到链接组件" /> |
| 114 | </Spin> | 114 | </Spin> |
| 115 | </Tabs.TabPane> | 115 | </Tabs.TabPane> |
| 116 | - <Tabs.TabPane :tab="TabsPanelNameEnum[TabsPanelEnum.EVENT]" :key="TabsPanelEnum.EVENT"> | 116 | + <Tabs.TabPane |
| 117 | + v-if="nodeData?.created" | ||
| 118 | + :tab="TabsPanelNameEnum[TabsPanelEnum.EVENT]" | ||
| 119 | + :key="TabsPanelEnum.EVENT" | ||
| 120 | + > | ||
| 117 | <BasicEvents :elementInfo="elementInfo" /> | 121 | <BasicEvents :elementInfo="elementInfo" /> |
| 118 | </Tabs.TabPane> | 122 | </Tabs.TabPane> |
| 119 | <Tabs.TabPane :tab="TabsPanelNameEnum[TabsPanelEnum.HELP]" :key="TabsPanelEnum.HELP"> | 123 | <Tabs.TabPane :tab="TabsPanelNameEnum[TabsPanelEnum.HELP]" :key="TabsPanelEnum.HELP"> |
| @@ -137,6 +137,7 @@ export interface NodeData<T = BasicNodeFormData> { | @@ -137,6 +137,7 @@ export interface NodeData<T = BasicNodeFormData> { | ||
| 137 | category?: CategoryConfigType; | 137 | category?: CategoryConfigType; |
| 138 | config?: NodeItemConfigType; | 138 | config?: NodeItemConfigType; |
| 139 | data?: T; | 139 | data?: T; |
| 140 | + created?: boolean; | ||
| 140 | } | 141 | } |
| 141 | 142 | ||
| 142 | export interface EdgeData { | 143 | export interface EdgeData { |
| @@ -18,3 +18,20 @@ export interface ConnectionItemType { | @@ -18,3 +18,20 @@ export interface ConnectionItemType { | ||
| 18 | toIndex: number; | 18 | toIndex: number; |
| 19 | type: string; | 19 | type: string; |
| 20 | } | 20 | } |
| 21 | + | ||
| 22 | +export interface RuleChainDetail { | ||
| 23 | + id: Id; | ||
| 24 | + createdTime: number; | ||
| 25 | + additionalInfo: AdditionalInfo; | ||
| 26 | + tenantId: Id; | ||
| 27 | + name: Id; | ||
| 28 | + type: string; | ||
| 29 | + firstRuleNodeId: Id; | ||
| 30 | + root: boolean; | ||
| 31 | + debugMode: boolean; | ||
| 32 | + configuration: any; | ||
| 33 | +} | ||
| 34 | + | ||
| 35 | +export interface AdditionalInfo { | ||
| 36 | + description: string; | ||
| 37 | +} |
| @@ -47,12 +47,12 @@ | @@ -47,12 +47,12 @@ | ||
| 47 | {{ t('sys.login.loginButton') }} | 47 | {{ t('sys.login.loginButton') }} |
| 48 | </Button> | 48 | </Button> |
| 49 | </FormItem> | 49 | </FormItem> |
| 50 | - <ARow class="enter-x flex justify-between"> | ||
| 51 | - <ACol :md="11" :xs="24"> | 50 | + <ARow class="enter-x flex justify-center"> |
| 51 | + <!-- <ACol :md="11" :xs="24"> | ||
| 52 | <Button block @click="setLoginState(LoginStateEnum.LOGIN)"> | 52 | <Button block @click="setLoginState(LoginStateEnum.LOGIN)"> |
| 53 | {{ t('sys.login.userNameInFormTitle') }} | 53 | {{ t('sys.login.userNameInFormTitle') }} |
| 54 | </Button> | 54 | </Button> |
| 55 | - </ACol> | 55 | + </ACol> --> |
| 56 | <ACol :md="11" :xs="24"> | 56 | <ACol :md="11" :xs="24"> |
| 57 | <Button block @click="setLoginState(LoginStateEnum.MOBILE)"> | 57 | <Button block @click="setLoginState(LoginStateEnum.MOBILE)"> |
| 58 | {{ t('sys.login.mobileSignInFormTitle') }} | 58 | {{ t('sys.login.mobileSignInFormTitle') }} |
| @@ -81,6 +81,17 @@ | @@ -81,6 +81,17 @@ | ||
| 81 | confirm: handleDeleteOrBatchDelete.bind(null, record), | 81 | confirm: handleDeleteOrBatchDelete.bind(null, record), |
| 82 | }, | 82 | }, |
| 83 | }, | 83 | }, |
| 84 | + { | ||
| 85 | + label: '清除密码', | ||
| 86 | + auth: 'api:yt:user:resetPassword', | ||
| 87 | + icon: 'ant-design:delete-outlined', | ||
| 88 | + color: 'error', | ||
| 89 | + tooltip: '清除密码', | ||
| 90 | + popConfirm: { | ||
| 91 | + title: '是否确认清除密码', | ||
| 92 | + confirm: handleClearPassword.bind(null, record), | ||
| 93 | + }, | ||
| 94 | + }, | ||
| 84 | ]" | 95 | ]" |
| 85 | /> | 96 | /> |
| 86 | </template> | 97 | </template> |
| @@ -107,6 +118,8 @@ | @@ -107,6 +118,8 @@ | ||
| 107 | import { isAdmin } from '/@/enums/roleEnum'; | 118 | import { isAdmin } from '/@/enums/roleEnum'; |
| 108 | import { TenantListItemRecord } from '/@/api/tenant/tenantInfo'; | 119 | import { TenantListItemRecord } from '/@/api/tenant/tenantInfo'; |
| 109 | import { useFastEnter } from '/@/hooks/business/useFastEnter'; | 120 | import { useFastEnter } from '/@/hooks/business/useFastEnter'; |
| 121 | + import { clearUserPassword } from '/@/api/system/system'; | ||
| 122 | + import { useMessage } from '/@/hooks/web/useMessage'; | ||
| 110 | 123 | ||
| 111 | export default defineComponent({ | 124 | export default defineComponent({ |
| 112 | name: 'AccountManagement', | 125 | name: 'AccountManagement', |
| @@ -121,6 +134,7 @@ | @@ -121,6 +134,7 @@ | ||
| 121 | Popconfirm, | 134 | Popconfirm, |
| 122 | }, | 135 | }, |
| 123 | setup() { | 136 | setup() { |
| 137 | + const { createMessage } = useMessage(); | ||
| 124 | const userInfo: any = getAuthCache(USER_INFO_KEY); | 138 | const userInfo: any = getAuthCache(USER_INFO_KEY); |
| 125 | const role: string = userInfo?.roles[0]; | 139 | const role: string = userInfo?.roles[0]; |
| 126 | 140 | ||
| @@ -192,6 +206,13 @@ | @@ -192,6 +206,13 @@ | ||
| 192 | } | 206 | } |
| 193 | } | 207 | } |
| 194 | 208 | ||
| 209 | + const handleClearPassword = async (record: Recordable) => { | ||
| 210 | + const { id } = record; | ||
| 211 | + if (!id) return; | ||
| 212 | + const { message } = await clearUserPassword(id); | ||
| 213 | + createMessage.success(message); | ||
| 214 | + }; | ||
| 215 | + | ||
| 195 | return { | 216 | return { |
| 196 | handleLoginCustomAdmin, | 217 | handleLoginCustomAdmin, |
| 197 | registerTable, | 218 | registerTable, |
| @@ -206,6 +227,7 @@ | @@ -206,6 +227,7 @@ | ||
| 206 | handleDeleteOrBatchDelete, | 227 | handleDeleteOrBatchDelete, |
| 207 | isAdmin, | 228 | isAdmin, |
| 208 | role, | 229 | role, |
| 230 | + handleClearPassword, | ||
| 209 | }; | 231 | }; |
| 210 | }, | 232 | }, |
| 211 | }); | 233 | }); |
| @@ -5,14 +5,26 @@ export const formSchema: FormSchema[] = [ | @@ -5,14 +5,26 @@ export const formSchema: FormSchema[] = [ | ||
| 5 | field: 'passwordOld', | 5 | field: 'passwordOld', |
| 6 | label: '当前密码', | 6 | label: '当前密码', |
| 7 | component: 'InputPassword', | 7 | component: 'InputPassword', |
| 8 | + componentProps: { | ||
| 9 | + placeholder: '请输入当前密码', | ||
| 10 | + }, | ||
| 8 | required: true, | 11 | required: true, |
| 9 | }, | 12 | }, |
| 10 | { | 13 | { |
| 11 | field: 'passwordNew', | 14 | field: 'passwordNew', |
| 12 | label: '新密码', | 15 | label: '新密码', |
| 13 | - component: 'StrengthMeter', | ||
| 14 | - componentProps: { | ||
| 15 | - placeholder: '新密码', | 16 | + component: 'InputPassword', |
| 17 | + componentProps({ formModel, formActionType }) { | ||
| 18 | + return { | ||
| 19 | + placeholder: '请输入新密码', | ||
| 20 | + onInput({ target }) { | ||
| 21 | + const { value } = target; | ||
| 22 | + const { confirmPassword } = formModel; | ||
| 23 | + if (value === confirmPassword) { | ||
| 24 | + formActionType.clearValidate('confirmPassword'); | ||
| 25 | + } | ||
| 26 | + }, | ||
| 27 | + }; | ||
| 16 | }, | 28 | }, |
| 17 | rules: [ | 29 | rules: [ |
| 18 | { | 30 | { |
| @@ -25,7 +37,9 @@ export const formSchema: FormSchema[] = [ | @@ -25,7 +37,9 @@ export const formSchema: FormSchema[] = [ | ||
| 25 | field: 'confirmPassword', | 37 | field: 'confirmPassword', |
| 26 | label: '确认密码', | 38 | label: '确认密码', |
| 27 | component: 'InputPassword', | 39 | component: 'InputPassword', |
| 28 | - | 40 | + componentProps: { |
| 41 | + placeholder: '请输入确认密码', | ||
| 42 | + }, | ||
| 29 | dynamicRules: ({ values }) => { | 43 | dynamicRules: ({ values }) => { |
| 30 | return [ | 44 | return [ |
| 31 | { | 45 | { |
| @@ -37,7 +51,6 @@ export const formSchema: FormSchema[] = [ | @@ -37,7 +51,6 @@ export const formSchema: FormSchema[] = [ | ||
| 37 | if (value !== values.passwordNew) { | 51 | if (value !== values.passwordNew) { |
| 38 | return Promise.reject('两次输入的密码不一致!'); | 52 | return Promise.reject('两次输入的密码不一致!'); |
| 39 | } | 53 | } |
| 40 | - | ||
| 41 | const pwdRegex = new RegExp(InputRegExp.PASSWORD_INPUT); | 54 | const pwdRegex = new RegExp(InputRegExp.PASSWORD_INPUT); |
| 42 | if (!pwdRegex.test(value)) { | 55 | if (!pwdRegex.test(value)) { |
| 43 | return Promise.reject( | 56 | return Promise.reject( |
| @@ -175,7 +175,7 @@ export const formSchema: FormSchema[] = [ | @@ -175,7 +175,7 @@ export const formSchema: FormSchema[] = [ | ||
| 175 | required: true, | 175 | required: true, |
| 176 | defaultValue: '0', | 176 | defaultValue: '0', |
| 177 | 177 | ||
| 178 | - label: '默认存储天数(0-无限制)', | 178 | + label: '默认存储TTL天数(0-无限制)', |
| 179 | colProps: { span: 12 }, | 179 | colProps: { span: 12 }, |
| 180 | component: 'InputNumber', | 180 | component: 'InputNumber', |
| 181 | componentProps: { | 181 | componentProps: { |
| @@ -124,6 +124,8 @@ | @@ -124,6 +124,8 @@ | ||
| 124 | let ids = record?.id?.id; | 124 | let ids = record?.id?.id; |
| 125 | await deleteTenantProfileApi(ids); | 125 | await deleteTenantProfileApi(ids); |
| 126 | createMessage.success('删除成功'); | 126 | createMessage.success('删除成功'); |
| 127 | + selectedRowKeys.length = 0; | ||
| 128 | + disabled.value = true; | ||
| 127 | reload(); | 129 | reload(); |
| 128 | } else { | 130 | } else { |
| 129 | createMessage.warning(message); | 131 | createMessage.warning(message); |
| @@ -148,9 +150,6 @@ | @@ -148,9 +150,6 @@ | ||
| 148 | createMessage.success('删除成功'); | 150 | createMessage.success('删除成功'); |
| 149 | reload(); | 151 | reload(); |
| 150 | selectedRowKeys.length = 0; | 152 | selectedRowKeys.length = 0; |
| 151 | - setTimeout(() => { | ||
| 152 | - disabled.value = false; | ||
| 153 | - }, 3000); | ||
| 154 | }); | 153 | }); |
| 155 | }; | 154 | }; |
| 156 | 155 |
| @@ -46,7 +46,7 @@ | @@ -46,7 +46,7 @@ | ||
| 46 | label: '账号', | 46 | label: '账号', |
| 47 | component: 'Input', | 47 | component: 'Input', |
| 48 | componentProps: { | 48 | componentProps: { |
| 49 | - maxLength: 255, | 49 | + maxLength: 64, |
| 50 | placeholder: '请输入账号', | 50 | placeholder: '请输入账号', |
| 51 | }, | 51 | }, |
| 52 | dynamicRules: ({ values }) => { | 52 | dynamicRules: ({ values }) => { |
| @@ -87,7 +87,7 @@ | @@ -87,7 +87,7 @@ | ||
| 87 | required: true, | 87 | required: true, |
| 88 | component: 'Input', | 88 | component: 'Input', |
| 89 | componentProps: { | 89 | componentProps: { |
| 90 | - maxLength: 255, | 90 | + maxLength: 64, |
| 91 | placeholder: '请输入真实名字', | 91 | placeholder: '请输入真实名字', |
| 92 | }, | 92 | }, |
| 93 | rules: chineseAndEnlishRule, | 93 | rules: chineseAndEnlishRule, |
| @@ -97,12 +97,20 @@ | @@ -97,12 +97,20 @@ | ||
| 97 | label: '电话号码', | 97 | label: '电话号码', |
| 98 | required: true, | 98 | required: true, |
| 99 | component: 'Input', | 99 | component: 'Input', |
| 100 | + componentProps: { | ||
| 101 | + maxLength: 11, | ||
| 102 | + placeholder: '请输入电话号码', | ||
| 103 | + }, | ||
| 100 | rules: phoneRule, | 104 | rules: phoneRule, |
| 101 | }, | 105 | }, |
| 102 | { | 106 | { |
| 103 | field: 'email', | 107 | field: 'email', |
| 104 | label: '邮件', | 108 | label: '邮件', |
| 105 | component: 'Input', | 109 | component: 'Input', |
| 110 | + componentProps: { | ||
| 111 | + maxLength: 64, | ||
| 112 | + placeholder: '请输入邮件', | ||
| 113 | + }, | ||
| 106 | rules: emailRule, | 114 | rules: emailRule, |
| 107 | }, | 115 | }, |
| 108 | { | 116 | { |
| @@ -44,6 +44,7 @@ | @@ -44,6 +44,7 @@ | ||
| 44 | icon: 'ant-design:usergroup-add-outlined', | 44 | icon: 'ant-design:usergroup-add-outlined', |
| 45 | label: '租户管理员', | 45 | label: '租户管理员', |
| 46 | onClick: handleTenantAdminDrawer.bind(null, record), | 46 | onClick: handleTenantAdminDrawer.bind(null, record), |
| 47 | + disabled: record.enabled === false, | ||
| 47 | }, | 48 | }, |
| 48 | { | 49 | { |
| 49 | icon: 'clarity:note-edit-line', | 50 | icon: 'clarity:note-edit-line', |
| @@ -110,14 +110,20 @@ export const tenantFormSchema: FormSchema[] = [ | @@ -110,14 +110,20 @@ export const tenantFormSchema: FormSchema[] = [ | ||
| 110 | label: '租户角色', | 110 | label: '租户角色', |
| 111 | component: 'ApiSelect', | 111 | component: 'ApiSelect', |
| 112 | required: true, | 112 | required: true, |
| 113 | - componentProps: { | ||
| 114 | - mode: 'multiple', | ||
| 115 | - api: getAllRoleList, | ||
| 116 | - params: { | ||
| 117 | - roleType: RoleEnum.TENANT_ADMIN, | ||
| 118 | - }, | ||
| 119 | - labelField: 'name', | ||
| 120 | - valueField: 'id', | 113 | + componentProps: () => { |
| 114 | + return { | ||
| 115 | + api: async () => { | ||
| 116 | + const res = await getAllRoleList({ roleType: RoleEnum.TENANT_ADMIN }); | ||
| 117 | + return res; | ||
| 118 | + }, | ||
| 119 | + mode: 'multiple', | ||
| 120 | + showSearch: true, | ||
| 121 | + labelField: 'name', | ||
| 122 | + valueField: 'id', | ||
| 123 | + filterOption: (inputValue: string, options: Record<'label' | 'value', string>) => { | ||
| 124 | + return options.label.toLowerCase().includes(inputValue.toLowerCase()); | ||
| 125 | + }, | ||
| 126 | + }; | ||
| 121 | }, | 127 | }, |
| 122 | }, | 128 | }, |
| 123 | { | 129 | { |
| @@ -32,7 +32,7 @@ | @@ -32,7 +32,7 @@ | ||
| 32 | import { BasicForm, useForm } from '/@/components/Form/index'; | 32 | import { BasicForm, useForm } from '/@/components/Form/index'; |
| 33 | import { formSchema } from './role.data'; | 33 | import { formSchema } from './role.data'; |
| 34 | import { BasicDrawer, useDrawerInner } from '/@/components/Drawer'; | 34 | import { BasicDrawer, useDrawerInner } from '/@/components/Drawer'; |
| 35 | - import { BasicTree, CheckEvent, TreeActionType, TreeItem } from '/@/components/Tree'; | 35 | + import { BasicTree, CheckEvent, TreeActionType, TreeItem, CheckKeys } from '/@/components/Tree'; |
| 36 | const { t } = useI18n(); //加载国际化 | 36 | const { t } = useI18n(); //加载国际化 |
| 37 | // 加载菜单数据 | 37 | // 加载菜单数据 |
| 38 | import { getAdminMenuList, getMenuList, getMenusIdsByRoleId } from '/@/api/sys/menu'; | 38 | import { getAdminMenuList, getMenuList, getMenusIdsByRoleId } from '/@/api/sys/menu'; |
| @@ -60,7 +60,7 @@ | @@ -60,7 +60,7 @@ | ||
| 60 | const treeRef = ref<Nullable<TreeActionType>>(null); | 60 | const treeRef = ref<Nullable<TreeActionType>>(null); |
| 61 | const checked = ref<string[]>([]); //需要选中的节点 | 61 | const checked = ref<string[]>([]); //需要选中的节点 |
| 62 | const spinning = ref(false); | 62 | const spinning = ref(false); |
| 63 | - const checkedKeysWithHalfChecked = ref<string[]>([]); | 63 | + const checkedKeysWithHalfChecked = ref<(string | number)[]>([]); |
| 64 | 64 | ||
| 65 | const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({ | 65 | const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({ |
| 66 | labelWidth: 90, | 66 | labelWidth: 90, |
| @@ -137,7 +137,12 @@ | @@ -137,7 +137,12 @@ | ||
| 137 | async function handleSubmit() { | 137 | async function handleSubmit() { |
| 138 | setDrawerProps({ loading: true, confirmLoading: true }); | 138 | setDrawerProps({ loading: true, confirmLoading: true }); |
| 139 | const { createMessage } = useMessage(); | 139 | const { createMessage } = useMessage(); |
| 140 | - const treeCheckedKeys: string[] = (unref(treeRef)?.getCheckedKeys() as string[]) || []; | 140 | + let treeCheckedKeys: string[] | CheckKeys = |
| 141 | + (unref(treeRef)?.getCheckedKeys() as string[] | CheckKeys) || []; | ||
| 142 | + //fix 取消层级独立后(unref(treeRef)?.getCheckedKeys() as string[])的数据不是数组,是{checked:[],halfChecked:[]}对象,迭代报错 | ||
| 143 | + if (!Array.isArray(treeCheckedKeys)) { | ||
| 144 | + treeCheckedKeys = treeCheckedKeys?.checked; | ||
| 145 | + } | ||
| 141 | const menu = [...new Set([...unref(checkedKeysWithHalfChecked), ...treeCheckedKeys])]; | 146 | const menu = [...new Set([...unref(checkedKeysWithHalfChecked), ...treeCheckedKeys])]; |
| 142 | try { | 147 | try { |
| 143 | const values = await validate(); | 148 | const values = await validate(); |
| @@ -237,7 +242,12 @@ | @@ -237,7 +242,12 @@ | ||
| 237 | return needExcludeKeys; | 242 | return needExcludeKeys; |
| 238 | }; | 243 | }; |
| 239 | 244 | ||
| 240 | - const handleCheckClick = (selectedKeys: string[], event: CheckEvent) => { | 245 | + const handleCheckClick = (selectedKeys: CheckKeys, event: CheckEvent) => { |
| 246 | + //fix 取消层级独立后selectedKeys不是数组,是{checked:[],halfChecked:[]}对象 迭代报错 | ||
| 247 | + if (!Array.isArray(selectedKeys)) { | ||
| 248 | + selectedKeys = selectedKeys?.checked; | ||
| 249 | + event.halfCheckedKeys = []; | ||
| 250 | + } | ||
| 241 | checkedKeysWithHalfChecked.value = [ | 251 | checkedKeysWithHalfChecked.value = [ |
| 242 | ...selectedKeys, | 252 | ...selectedKeys, |
| 243 | ...(event.halfCheckedKeys as string[]), | 253 | ...(event.halfCheckedKeys as string[]), |