@ -0,0 +1,3 @@ |
|||||||
|
VITE_BASE_API = '/remoteServer' |
||||||
|
VITE_SHOW_ONLINE_DEVICE = true |
||||||
|
VITE_APP_ENV = local |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
VITE_APP_ENV = cloud |
||||||
|
VITE_BASE_URL = 'http://192.168.1.3:8080' |
||||||
|
VITE_ZMQ_BASE_URL = '192.168.1.99' |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
VITE_APP_ENV = local |
||||||
|
VITE_BASE_URL = 'http://192.168.1.210:8080' |
||||||
|
VITE_ZMQ_BASE_URL = '192.168.1.210' |
||||||
@ -0,0 +1,29 @@ |
|||||||
|
.DS_Store |
||||||
|
|
||||||
|
.vscode |
||||||
|
|
||||||
|
# package manager log files |
||||||
|
npm-debug.log* |
||||||
|
yarn-debug.log* |
||||||
|
yarn-error.log* |
||||||
|
.npm |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# eslint cache |
||||||
|
.eslintcache |
||||||
|
# stylelint cache |
||||||
|
.stylelintcache |
||||||
|
|
||||||
|
|
||||||
|
# Log files |
||||||
|
npm-debug.log* |
||||||
|
yarn-debug.log* |
||||||
|
yarn-error.log* |
||||||
|
|
||||||
|
/dist |
||||||
|
/dist-local |
||||||
|
/dist-cloud |
||||||
|
/docker_output/ |
||||||
|
/node_modules/ |
||||||
|
/*.tar |
||||||
@ -0,0 +1,12 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<module type="WEB_MODULE" version="4"> |
||||||
|
<component name="NewModuleRootManager"> |
||||||
|
<content url="file://$MODULE_DIR$"> |
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.tmp" /> |
||||||
|
<excludeFolder url="file://$MODULE_DIR$/temp" /> |
||||||
|
<excludeFolder url="file://$MODULE_DIR$/tmp" /> |
||||||
|
</content> |
||||||
|
<orderEntry type="inheritedJdk" /> |
||||||
|
<orderEntry type="sourceFolder" forTests="false" /> |
||||||
|
</component> |
||||||
|
</module> |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<project version="4"> |
||||||
|
<component name="GitCommitMessageStorage"> |
||||||
|
<option name="messageStorage"> |
||||||
|
<MessageStorage /> |
||||||
|
</option> |
||||||
|
</component> |
||||||
|
</project> |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<project version="4"> |
||||||
|
<component name="AgentMigrationStateService"> |
||||||
|
<option name="migrationStatus" value="COMPLETED" /> |
||||||
|
</component> |
||||||
|
</project> |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<project version="4"> |
||||||
|
<component name="AskMigrationStateService"> |
||||||
|
<option name="migrationStatus" value="COMPLETED" /> |
||||||
|
</component> |
||||||
|
</project> |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<project version="4"> |
||||||
|
<component name="Ask2AgentMigrationStateService"> |
||||||
|
<option name="migrationStatus" value="COMPLETED" /> |
||||||
|
</component> |
||||||
|
</project> |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<project version="4"> |
||||||
|
<component name="EditMigrationStateService"> |
||||||
|
<option name="migrationStatus" value="COMPLETED" /> |
||||||
|
</component> |
||||||
|
</project> |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
<component name="ProjectDictionaryState"> |
||||||
|
<dictionary name="project"> |
||||||
|
<words> |
||||||
|
<w>antv</w> |
||||||
|
<w>mingcute</w> |
||||||
|
</words> |
||||||
|
</dictionary> |
||||||
|
</component> |
||||||
@ -0,0 +1,15 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<project version="4"> |
||||||
|
<component name="GitToolBoxProjectSettings"> |
||||||
|
<option name="commitMessageIssueKeyValidationOverride"> |
||||||
|
<BoolValueOverride> |
||||||
|
<option name="enabled" value="true" /> |
||||||
|
</BoolValueOverride> |
||||||
|
</option> |
||||||
|
<option name="commitMessageValidationEnabledOverride"> |
||||||
|
<BoolValueOverride> |
||||||
|
<option name="enabled" value="true" /> |
||||||
|
</BoolValueOverride> |
||||||
|
</option> |
||||||
|
</component> |
||||||
|
</project> |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
<component name="InspectionProjectProfileManager"> |
||||||
|
<profile version="1.0"> |
||||||
|
<option name="myName" value="Project Default" /> |
||||||
|
<inspection_tool class="D" enabled="false" level="WEAK WARNING" enabled_by_default="false" /> |
||||||
|
</profile> |
||||||
|
</component> |
||||||
@ -0,0 +1,12 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<project version="4"> |
||||||
|
<component name="MaterialThemeProjectNewConfig"> |
||||||
|
<option name="metadata"> |
||||||
|
<MTProjectMetadataState> |
||||||
|
<option name="migrated" value="true" /> |
||||||
|
<option name="pristineConfig" value="false" /> |
||||||
|
<option name="userId" value="-6d6b07ab:1980cbf3ff4:-7ffe" /> |
||||||
|
</MTProjectMetadataState> |
||||||
|
</option> |
||||||
|
</component> |
||||||
|
</project> |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<project version="4"> |
||||||
|
<component name="ProjectModuleManager"> |
||||||
|
<modules> |
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/EDFS-MCU.iml" filepath="$PROJECT_DIR$/.idea/EDFS-MCU.iml" /> |
||||||
|
</modules> |
||||||
|
</component> |
||||||
|
</project> |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<project version="4"> |
||||||
|
<component name="VcsDirectoryMappings"> |
||||||
|
<mapping directory="" vcs="Git" /> |
||||||
|
</component> |
||||||
|
</project> |
||||||
@ -0,0 +1,477 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<project version="4"> |
||||||
|
<component name="AutoImportSettings"> |
||||||
|
<option name="autoReloadType" value="SELECTIVE" /> |
||||||
|
</component> |
||||||
|
<component name="ChangeListManager"> |
||||||
|
<list default="true" id="1a5a317b-2539-4ca3-a13a-6db181725745" name="Changes" comment="feat: 样式优化" /> |
||||||
|
<option name="SHOW_DIALOG" value="false" /> |
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" /> |
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" /> |
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" /> |
||||||
|
</component> |
||||||
|
<component name="FileTemplateManagerImpl"> |
||||||
|
<option name="RECENT_TEMPLATES"> |
||||||
|
<list> |
||||||
|
<option value="TypeScript File" /> |
||||||
|
<option value="Vue Composition API Component" /> |
||||||
|
</list> |
||||||
|
</option> |
||||||
|
</component> |
||||||
|
<component name="Git.Settings"> |
||||||
|
<option name="RECENT_BRANCH_BY_REPOSITORY"> |
||||||
|
<map> |
||||||
|
<entry key="$PROJECT_DIR$" value="main-testG6" /> |
||||||
|
</map> |
||||||
|
</option> |
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" /> |
||||||
|
</component> |
||||||
|
<component name="GitToolBoxStore"> |
||||||
|
<option name="recentBranches"> |
||||||
|
<RecentBranches> |
||||||
|
<option name="branchesForRepo"> |
||||||
|
<list> |
||||||
|
<RecentBranchesForRepo> |
||||||
|
<option name="branches"> |
||||||
|
<list> |
||||||
|
<RecentBranch> |
||||||
|
<option name="branchName" value="main" /> |
||||||
|
<option name="lastUsedInstant" value="1762340990" /> |
||||||
|
</RecentBranch> |
||||||
|
<RecentBranch> |
||||||
|
<option name="branchName" value="main-testG6" /> |
||||||
|
<option name="lastUsedInstant" value="1762333705" /> |
||||||
|
</RecentBranch> |
||||||
|
</list> |
||||||
|
</option> |
||||||
|
<option name="repositoryRootUrl" value="file://$PROJECT_DIR$" /> |
||||||
|
</RecentBranchesForRepo> |
||||||
|
</list> |
||||||
|
</option> |
||||||
|
</RecentBranches> |
||||||
|
</option> |
||||||
|
</component> |
||||||
|
<component name="HighlightingSettingsPerFile"> |
||||||
|
<setting file="file://$USER_HOME$/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/wxid_0wujhfg3eau322_5d75/msg/file/2025-11/1.json" root0="FORCE_HIGHLIGHTING" /> |
||||||
|
</component> |
||||||
|
<component name="ProblemsViewState"> |
||||||
|
<option name="selectedTabId" value="CurrentFile" /> |
||||||
|
</component> |
||||||
|
<component name="ProjectColorInfo">{ |
||||||
|
"associatedIndex": 1 |
||||||
|
}</component> |
||||||
|
<component name="ProjectId" id="33Pn17EFffQH46OlqwOKF3K4cpA" /> |
||||||
|
<component name="ProjectViewState"> |
||||||
|
<option name="autoscrollFromSource" value="true" /> |
||||||
|
<option name="autoscrollToSource" value="true" /> |
||||||
|
<option name="hideEmptyMiddlePackages" value="true" /> |
||||||
|
<option name="openDirectoriesWithSingleClick" value="true" /> |
||||||
|
<option name="showLibraryContents" value="true" /> |
||||||
|
<option name="showMembers" value="true" /> |
||||||
|
<option name="showModules" value="false" /> |
||||||
|
<option name="showScratchesAndConsoles" value="false" /> |
||||||
|
</component> |
||||||
|
<component name="PropertiesComponent"><![CDATA[{ |
||||||
|
"keyToString": { |
||||||
|
"ASKED_MARK_IGNORED_FILES_AS_EXCLUDED": "true", |
||||||
|
"ModuleVcsDetector.initialDetectionPerformed": "true", |
||||||
|
"RunOnceActivity.ShowReadmeOnStart": "true", |
||||||
|
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true", |
||||||
|
"RunOnceActivity.git.unshallow": "true", |
||||||
|
"com.intellij.ml.llm.matterhorn.ej.ui.settings.DefaultAutoModeForALLUsers.v1": "true", |
||||||
|
"com.intellij.ml.llm.matterhorn.ej.ui.settings.DefaultModelSelectionForGA.v1": "true", |
||||||
|
"git-widget-placeholder": "main", |
||||||
|
"junie.onboarding.icon.badge.shown": "true", |
||||||
|
"last_opened_file_path": "/Users/taqibe/worker/EDFS-EPM", |
||||||
|
"node.js.detected.package.eslint": "true", |
||||||
|
"node.js.detected.package.tslint": "true", |
||||||
|
"node.js.selected.package.eslint": "(autodetect)", |
||||||
|
"node.js.selected.package.tslint": "(autodetect)", |
||||||
|
"nodejs_package_manager_path": "npm", |
||||||
|
"settings.editor.selected.configurable": "editing.templates", |
||||||
|
"to.speed.mode.migration.done": "true", |
||||||
|
"ts.external.directory.path": "/Users/taqibe/worker/EDFS-EPM/node_modules/typescript/lib", |
||||||
|
"vue.rearranger.settings.migration": "true" |
||||||
|
}, |
||||||
|
"keyToStringList": { |
||||||
|
"vue.recent.templates": [ |
||||||
|
"Vue Composition API Component" |
||||||
|
] |
||||||
|
} |
||||||
|
}]]></component> |
||||||
|
<component name="RecentsManager"> |
||||||
|
<key name="CopyFile.RECENT_KEYS"> |
||||||
|
<recent name="$PROJECT_DIR$/src/views" /> |
||||||
|
</key> |
||||||
|
<key name="MoveFile.RECENT_KEYS"> |
||||||
|
<recent name="$PROJECT_DIR$/src/components" /> |
||||||
|
</key> |
||||||
|
</component> |
||||||
|
<component name="SharedIndexes"> |
||||||
|
<attachedChunks> |
||||||
|
<set> |
||||||
|
<option value="bundled-js-predefined-d6986cc7102b-3aa1da707db6-JavaScript-WS-252.27397.92" /> |
||||||
|
</set> |
||||||
|
</attachedChunks> |
||||||
|
</component> |
||||||
|
<component name="TaskManager"> |
||||||
|
<task active="true" id="Default" summary="Default task"> |
||||||
|
<changelist id="1a5a317b-2539-4ca3-a13a-6db181725745" name="Changes" comment="" /> |
||||||
|
<created>1759227356966</created> |
||||||
|
<option name="number" value="Default" /> |
||||||
|
<option name="presentableId" value="Default" /> |
||||||
|
<updated>1759227356966</updated> |
||||||
|
<workItem from="1759227358304" duration="1023000" /> |
||||||
|
<workItem from="1759228390368" duration="40000" /> |
||||||
|
<workItem from="1759228549846" duration="11000" /> |
||||||
|
<workItem from="1759230772741" duration="89000" /> |
||||||
|
<workItem from="1759989220548" duration="176000" /> |
||||||
|
<workItem from="1759989551899" duration="170000" /> |
||||||
|
<workItem from="1759989746585" duration="292000" /> |
||||||
|
<workItem from="1759990048708" duration="78000" /> |
||||||
|
<workItem from="1759990136521" duration="3232000" /> |
||||||
|
<workItem from="1759993446079" duration="487000" /> |
||||||
|
<workItem from="1759994018535" duration="3043000" /> |
||||||
|
<workItem from="1759997133848" duration="14412000" /> |
||||||
|
<workItem from="1760336647412" duration="244000" /> |
||||||
|
<workItem from="1760431007053" duration="3049000" /> |
||||||
|
<workItem from="1760509810280" duration="10929000" /> |
||||||
|
<workItem from="1761284911195" duration="9696000" /> |
||||||
|
<workItem from="1761530640193" duration="3291000" /> |
||||||
|
<workItem from="1761558310679" duration="2000" /> |
||||||
|
<workItem from="1761632345080" duration="225000" /> |
||||||
|
<workItem from="1761632681106" duration="1485000" /> |
||||||
|
<workItem from="1761643503658" duration="19000" /> |
||||||
|
<workItem from="1761644243506" duration="709000" /> |
||||||
|
<workItem from="1761649848303" duration="1426000" /> |
||||||
|
<workItem from="1761710435976" duration="607000" /> |
||||||
|
<workItem from="1761789282293" duration="5005000" /> |
||||||
|
<workItem from="1761817047786" duration="316000" /> |
||||||
|
<workItem from="1761817569420" duration="353000" /> |
||||||
|
<workItem from="1761817939264" duration="2782000" /> |
||||||
|
<workItem from="1762136843058" duration="1324000" /> |
||||||
|
<workItem from="1762138186028" duration="1264000" /> |
||||||
|
<workItem from="1762139581166" duration="11368000" /> |
||||||
|
<workItem from="1762155421774" duration="11124000" /> |
||||||
|
<workItem from="1762227571451" duration="42233000" /> |
||||||
|
<workItem from="1762506557322" duration="7100000" /> |
||||||
|
<workItem from="1762840881699" duration="7799000" /> |
||||||
|
<workItem from="1762876079079" duration="617000" /> |
||||||
|
<workItem from="1762935965088" duration="57732000" /> |
||||||
|
<workItem from="1763346319714" duration="475000" /> |
||||||
|
<workItem from="1763347210197" duration="7708000" /> |
||||||
|
<workItem from="1763520819502" duration="1260000" /> |
||||||
|
<workItem from="1763537899229" duration="583000" /> |
||||||
|
<workItem from="1763539836768" duration="598000" /> |
||||||
|
<workItem from="1763543600806" duration="4000" /> |
||||||
|
<workItem from="1763543655623" duration="1880000" /> |
||||||
|
<workItem from="1763610355529" duration="670000" /> |
||||||
|
<workItem from="1763611329249" duration="1960000" /> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00001" summary="fix: 一些调整"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1759227435333</created> |
||||||
|
<option name="number" value="00001" /> |
||||||
|
<option name="presentableId" value="LOCAL-00001" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1759227435333</updated> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00002" summary="fix: 一些调整"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1759227945246</created> |
||||||
|
<option name="number" value="00002" /> |
||||||
|
<option name="presentableId" value="LOCAL-00002" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1759227945246</updated> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00003" summary="feat: 云端导入增加导入方式"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1759989710232</created> |
||||||
|
<option name="number" value="00003" /> |
||||||
|
<option name="presentableId" value="LOCAL-00003" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1759989710232</updated> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00004" summary="feat: 在线设备根据站点区分"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1759998954540</created> |
||||||
|
<option name="number" value="00004" /> |
||||||
|
<option name="presentableId" value="LOCAL-00004" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1759998954540</updated> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00005" summary="feat: 迁移drawer loading 逻辑修改"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1760178915059</created> |
||||||
|
<option name="number" value="00005" /> |
||||||
|
<option name="presentableId" value="LOCAL-00005" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1760178915059</updated> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00006" summary="feat: 增加设备类型并增加icon"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1761531297313</created> |
||||||
|
<option name="number" value="00006" /> |
||||||
|
<option name="presentableId" value="LOCAL-00006" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1761531297313</updated> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00007" summary="feat: emu 设备数据点位值保留小数修改"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1761632856036</created> |
||||||
|
<option name="number" value="00007" /> |
||||||
|
<option name="presentableId" value="LOCAL-00007" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1761632856036</updated> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00008" summary="feat: emu 设备批量选择增加全选"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1761792036623</created> |
||||||
|
<option name="number" value="00008" /> |
||||||
|
<option name="presentableId" value="LOCAL-00008" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1761792036623</updated> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00009" summary="feat: 点位数据不显示数据为空的"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1761817754637</created> |
||||||
|
<option name="number" value="00009" /> |
||||||
|
<option name="presentableId" value="LOCAL-00009" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1761817754637</updated> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00010" summary="feat: 使用时间对齐作为折线图x轴"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1762136923706</created> |
||||||
|
<option name="number" value="00010" /> |
||||||
|
<option name="presentableId" value="LOCAL-00010" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1762136923706</updated> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00011" summary="fix: 导入导出名称修正"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1762141831374</created> |
||||||
|
<option name="number" value="00011" /> |
||||||
|
<option name="presentableId" value="LOCAL-00011" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1762141831374</updated> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00012" summary="feat: wrap组件收缩功能"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1762154304120</created> |
||||||
|
<option name="number" value="00012" /> |
||||||
|
<option name="presentableId" value="LOCAL-00012" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1762154304120</updated> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00013" summary="feat: 设备数据侧边设备点位收缩功能"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1762154365707</created> |
||||||
|
<option name="number" value="00013" /> |
||||||
|
<option name="presentableId" value="LOCAL-00013" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1762154365707</updated> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00014" summary="feat: 样式修改"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1762156981920</created> |
||||||
|
<option name="number" value="00014" /> |
||||||
|
<option name="presentableId" value="LOCAL-00014" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1762156981920</updated> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00015" summary="feat: 任务列表状态实时更新逻辑"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1762162249095</created> |
||||||
|
<option name="number" value="00015" /> |
||||||
|
<option name="presentableId" value="LOCAL-00015" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1762162249095</updated> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00016" summary="fix: 导入导出名称修正"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1762227756908</created> |
||||||
|
<option name="number" value="00016" /> |
||||||
|
<option name="presentableId" value="LOCAL-00016" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1762227756908</updated> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00017" summary="fix: 平台名称修改"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1762321409388</created> |
||||||
|
<option name="number" value="00017" /> |
||||||
|
<option name="presentableId" value="LOCAL-00017" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1762321409388</updated> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00018" summary="fix: ZMQ 多个消息订阅统一个主题时回调函数被覆盖问题"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1762321502585</created> |
||||||
|
<option name="number" value="00018" /> |
||||||
|
<option name="presentableId" value="LOCAL-00018" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1762321502585</updated> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00019" summary="fix: 任务状态更新修正"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1762321564102</created> |
||||||
|
<option name="number" value="00019" /> |
||||||
|
<option name="presentableId" value="LOCAL-00019" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1762321564102</updated> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00020" summary="feat: 点位数据tooltip 增加单位"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1762325331961</created> |
||||||
|
<option name="number" value="00020" /> |
||||||
|
<option name="presentableId" value="LOCAL-00020" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1762325331961</updated> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00021" summary="feat: 数据精确到毫秒"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1762329140928</created> |
||||||
|
<option name="number" value="00021" /> |
||||||
|
<option name="presentableId" value="LOCAL-00021" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1762329140928</updated> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00022" summary="feat: 任务zmq回显调整"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1762481889492</created> |
||||||
|
<option name="number" value="00022" /> |
||||||
|
<option name="presentableId" value="LOCAL-00022" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1762481889492</updated> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00023" summary="feat: 任务zmq回显调整"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1762484638850</created> |
||||||
|
<option name="number" value="00023" /> |
||||||
|
<option name="presentableId" value="LOCAL-00023" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1762484638850</updated> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00024" summary="feat: 一些调整"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1762508208321</created> |
||||||
|
<option name="number" value="00024" /> |
||||||
|
<option name="presentableId" value="LOCAL-00024" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1762508208321</updated> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00025" summary="feat: 优化折线图性能"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1762942198582</created> |
||||||
|
<option name="number" value="00025" /> |
||||||
|
<option name="presentableId" value="LOCAL-00025" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1762942198582</updated> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00026" summary="feat: 增加查询最近"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1763013063694</created> |
||||||
|
<option name="number" value="00026" /> |
||||||
|
<option name="presentableId" value="LOCAL-00026" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1763013063694</updated> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00027" summary="feat: 点位数据增加并发请求"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1763110405491</created> |
||||||
|
<option name="number" value="00027" /> |
||||||
|
<option name="presentableId" value="LOCAL-00027" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1763110405491</updated> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00028" summary="feat: 请求进度增加滚动"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1763347443465</created> |
||||||
|
<option name="number" value="00028" /> |
||||||
|
<option name="presentableId" value="LOCAL-00028" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1763347443465</updated> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00029" summary="feat: 点位数据空值不不处理"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1763349111621</created> |
||||||
|
<option name="number" value="00029" /> |
||||||
|
<option name="presentableId" value="LOCAL-00029" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1763349111621</updated> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00030" summary="feat: topo 点位Total 调整"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1763349274662</created> |
||||||
|
<option name="number" value="00030" /> |
||||||
|
<option name="presentableId" value="LOCAL-00030" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1763349274662</updated> |
||||||
|
</task> |
||||||
|
<task id="LOCAL-00031" summary="feat: 样式优化"> |
||||||
|
<option name="closed" value="true" /> |
||||||
|
<created>1763358207970</created> |
||||||
|
<option name="number" value="00031" /> |
||||||
|
<option name="presentableId" value="LOCAL-00031" /> |
||||||
|
<option name="project" value="LOCAL" /> |
||||||
|
<updated>1763358207970</updated> |
||||||
|
</task> |
||||||
|
<option name="localTasksCounter" value="32" /> |
||||||
|
<servers /> |
||||||
|
</component> |
||||||
|
<component name="TypeScriptGeneratedFilesManager"> |
||||||
|
<option name="version" value="3" /> |
||||||
|
<option name="exactExcludedFiles"> |
||||||
|
<list> |
||||||
|
<option value="$PROJECT_DIR$/uno.config.js" /> |
||||||
|
</list> |
||||||
|
</option> |
||||||
|
</component> |
||||||
|
<component name="Vcs.Log.Tabs.Properties"> |
||||||
|
<option name="TAB_STATES"> |
||||||
|
<map> |
||||||
|
<entry key="MAIN"> |
||||||
|
<value> |
||||||
|
<State /> |
||||||
|
</value> |
||||||
|
</entry> |
||||||
|
</map> |
||||||
|
</option> |
||||||
|
</component> |
||||||
|
<component name="VcsManagerConfiguration"> |
||||||
|
<MESSAGE value="feat: 迁移drawer loading 逻辑修改" /> |
||||||
|
<MESSAGE value="feat: 增加设备类型并增加icon" /> |
||||||
|
<MESSAGE value="feat: emu 设备数据点位值保留小数修改" /> |
||||||
|
<MESSAGE value="feat: emu 设备批量选择增加全选" /> |
||||||
|
<MESSAGE value="feat: 点位数据不显示数据为空的" /> |
||||||
|
<MESSAGE value="feat: 使用时间对齐作为折线图x轴" /> |
||||||
|
<MESSAGE value="feat: wrap组件收缩功能" /> |
||||||
|
<MESSAGE value="feat: 设备数据侧边设备点位收缩功能" /> |
||||||
|
<MESSAGE value="feat: 样式修改" /> |
||||||
|
<MESSAGE value="feat: 任务列表状态实时更新逻辑" /> |
||||||
|
<MESSAGE value="fix: 导入导出名称修正" /> |
||||||
|
<MESSAGE value="fix: 平台名称修改" /> |
||||||
|
<MESSAGE value="fix: ZMQ 多个消息订阅统一个主题时回调函数被覆盖问题" /> |
||||||
|
<MESSAGE value="fix: 任务状态更新修正" /> |
||||||
|
<MESSAGE value="feat: 点位数据tooltip 增加单位" /> |
||||||
|
<MESSAGE value="feat: 数据精确到毫秒" /> |
||||||
|
<MESSAGE value="feat: 任务zmq回显调整" /> |
||||||
|
<MESSAGE value="feat: 一些调整" /> |
||||||
|
<MESSAGE value="feat: 优化折线图性能" /> |
||||||
|
<MESSAGE value="feat: 增加查询最近" /> |
||||||
|
<MESSAGE value="feat: 点位数据增加并发请求" /> |
||||||
|
<MESSAGE value="feat: 请求进度增加滚动" /> |
||||||
|
<MESSAGE value="feat: 点位数据空值不不处理" /> |
||||||
|
<MESSAGE value="feat: topo 点位Total 调整" /> |
||||||
|
<MESSAGE value="feat: 样式优化" /> |
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="feat: 样式优化" /> |
||||||
|
</component> |
||||||
|
<component name="github-copilot-workspace"> |
||||||
|
<instructionFileLocations> |
||||||
|
<option value=".github/instructions" /> |
||||||
|
</instructionFileLocations> |
||||||
|
<promptFileLocations> |
||||||
|
<option value=".github/prompts" /> |
||||||
|
</promptFileLocations> |
||||||
|
</component> |
||||||
|
</project> |
||||||
@ -0,0 +1,33 @@ |
|||||||
|
# vue-project |
||||||
|
|
||||||
|
This template should help get you started developing with Vue 3 in Vite. |
||||||
|
|
||||||
|
## Recommended IDE Setup |
||||||
|
|
||||||
|
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). |
||||||
|
|
||||||
|
## Type Support for `.vue` Imports in TS |
||||||
|
|
||||||
|
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types. |
||||||
|
|
||||||
|
## Customize configuration |
||||||
|
|
||||||
|
See [Vite Configuration Reference](https://vite.dev/config/). |
||||||
|
|
||||||
|
## Project Setup |
||||||
|
|
||||||
|
```sh |
||||||
|
npm install |
||||||
|
``` |
||||||
|
|
||||||
|
### Compile and Hot-Reload for Development |
||||||
|
|
||||||
|
```sh |
||||||
|
npm run dev |
||||||
|
``` |
||||||
|
|
||||||
|
### Type-Check, Compile and Minify for Production |
||||||
|
|
||||||
|
```sh |
||||||
|
npm run build |
||||||
|
``` |
||||||
@ -0,0 +1,78 @@ |
|||||||
|
/* eslint-disable */ |
||||||
|
/* prettier-ignore */ |
||||||
|
// @ts-nocheck
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
// Generated by unplugin-auto-import
|
||||||
|
// biome-ignore lint: disable
|
||||||
|
export {} |
||||||
|
declare global { |
||||||
|
const EffectScope: typeof import('vue')['EffectScope'] |
||||||
|
const ElLoading: typeof import('element-plus/es')['ElLoading'] |
||||||
|
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox'] |
||||||
|
const computed: typeof import('vue')['computed'] |
||||||
|
const createApp: typeof import('vue')['createApp'] |
||||||
|
const customRef: typeof import('vue')['customRef'] |
||||||
|
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] |
||||||
|
const defineComponent: typeof import('vue')['defineComponent'] |
||||||
|
const effectScope: typeof import('vue')['effectScope'] |
||||||
|
const getCurrentInstance: typeof import('vue')['getCurrentInstance'] |
||||||
|
const getCurrentScope: typeof import('vue')['getCurrentScope'] |
||||||
|
const h: typeof import('vue')['h'] |
||||||
|
const inject: typeof import('vue')['inject'] |
||||||
|
const isProxy: typeof import('vue')['isProxy'] |
||||||
|
const isReactive: typeof import('vue')['isReactive'] |
||||||
|
const isReadonly: typeof import('vue')['isReadonly'] |
||||||
|
const isRef: typeof import('vue')['isRef'] |
||||||
|
const markRaw: typeof import('vue')['markRaw'] |
||||||
|
const nextTick: typeof import('vue')['nextTick'] |
||||||
|
const onActivated: typeof import('vue')['onActivated'] |
||||||
|
const onBeforeMount: typeof import('vue')['onBeforeMount'] |
||||||
|
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave'] |
||||||
|
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate'] |
||||||
|
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] |
||||||
|
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] |
||||||
|
const onDeactivated: typeof import('vue')['onDeactivated'] |
||||||
|
const onErrorCaptured: typeof import('vue')['onErrorCaptured'] |
||||||
|
const onMounted: typeof import('vue')['onMounted'] |
||||||
|
const onRenderTracked: typeof import('vue')['onRenderTracked'] |
||||||
|
const onRenderTriggered: typeof import('vue')['onRenderTriggered'] |
||||||
|
const onScopeDispose: typeof import('vue')['onScopeDispose'] |
||||||
|
const onServerPrefetch: typeof import('vue')['onServerPrefetch'] |
||||||
|
const onUnmounted: typeof import('vue')['onUnmounted'] |
||||||
|
const onUpdated: typeof import('vue')['onUpdated'] |
||||||
|
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup'] |
||||||
|
const provide: typeof import('vue')['provide'] |
||||||
|
const reactive: typeof import('vue')['reactive'] |
||||||
|
const readonly: typeof import('vue')['readonly'] |
||||||
|
const ref: typeof import('vue')['ref'] |
||||||
|
const resolveComponent: typeof import('vue')['resolveComponent'] |
||||||
|
const shallowReactive: typeof import('vue')['shallowReactive'] |
||||||
|
const shallowReadonly: typeof import('vue')['shallowReadonly'] |
||||||
|
const shallowRef: typeof import('vue')['shallowRef'] |
||||||
|
const toRaw: typeof import('vue')['toRaw'] |
||||||
|
const toRef: typeof import('vue')['toRef'] |
||||||
|
const toRefs: typeof import('vue')['toRefs'] |
||||||
|
const toValue: typeof import('vue')['toValue'] |
||||||
|
const triggerRef: typeof import('vue')['triggerRef'] |
||||||
|
const unref: typeof import('vue')['unref'] |
||||||
|
const useAttrs: typeof import('vue')['useAttrs'] |
||||||
|
const useCssModule: typeof import('vue')['useCssModule'] |
||||||
|
const useCssVars: typeof import('vue')['useCssVars'] |
||||||
|
const useId: typeof import('vue')['useId'] |
||||||
|
const useLink: typeof import('vue-router')['useLink'] |
||||||
|
const useModel: typeof import('vue')['useModel'] |
||||||
|
const useRoute: typeof import('vue-router')['useRoute'] |
||||||
|
const useRouter: typeof import('vue-router')['useRouter'] |
||||||
|
const useSlots: typeof import('vue')['useSlots'] |
||||||
|
const useTemplateRef: typeof import('vue')['useTemplateRef'] |
||||||
|
const watch: typeof import('vue')['watch'] |
||||||
|
const watchEffect: typeof import('vue')['watchEffect'] |
||||||
|
const watchPostEffect: typeof import('vue')['watchPostEffect'] |
||||||
|
const watchSyncEffect: typeof import('vue')['watchSyncEffect'] |
||||||
|
} |
||||||
|
// for type re-export
|
||||||
|
declare global { |
||||||
|
// @ts-ignore
|
||||||
|
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue' |
||||||
|
import('vue') |
||||||
|
} |
||||||
@ -0,0 +1,32 @@ |
|||||||
|
/* eslint-disable */ |
||||||
|
// @ts-nocheck
|
||||||
|
// Generated by unplugin-vue-components
|
||||||
|
// Read more: https://github.com/vuejs/core/pull/3399
|
||||||
|
// biome-ignore lint: disable
|
||||||
|
export {} |
||||||
|
|
||||||
|
/* prettier-ignore */ |
||||||
|
declare module 'vue' { |
||||||
|
export interface GlobalComponents { |
||||||
|
EdfsButton: typeof import('./../src/components/Edfs-button.vue')['default'] |
||||||
|
EdfsDialog: typeof import('./../src/components/Edfs-dialog.vue')['default'] |
||||||
|
EdfsInput: typeof import('./../src/components/Edfs-Input.vue')['default'] |
||||||
|
EdfsNumberInput: typeof import('./../src/components/Edfs-number-input.vue')['default'] |
||||||
|
EdfsTable: typeof import('./../src/components/Edfs-table/index.vue')['default'] |
||||||
|
EdfsWrap: typeof import('./../src/components/Edfs-wrap.vue')['default'] |
||||||
|
ElAside: typeof import('element-plus/es')['ElAside'] |
||||||
|
ElButton: typeof import('element-plus/es')['ElButton'] |
||||||
|
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'] |
||||||
|
ElContainer: typeof import('element-plus/es')['ElContainer'] |
||||||
|
ElHeader: typeof import('element-plus/es')['ElHeader'] |
||||||
|
ElMenu: typeof import('element-plus/es')['ElMenu'] |
||||||
|
ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] |
||||||
|
ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] |
||||||
|
ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] |
||||||
|
RouterLink: typeof import('vue-router')['RouterLink'] |
||||||
|
RouterView: typeof import('vue-router')['RouterView'] |
||||||
|
} |
||||||
|
export interface GlobalDirectives { |
||||||
|
vInfiniteScroll: typeof import('element-plus/es')['ElInfiniteScroll'] |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
declare module '*.vue' { |
||||||
|
import type { DefineComponent } from 'vue' |
||||||
|
const component: DefineComponent<{}, {}, any> |
||||||
|
export default component |
||||||
|
} |
||||||
|
declare module "element-plus/dist/locale/zh-cn.mjs"; |
||||||
|
|
||||||
@ -0,0 +1,60 @@ |
|||||||
|
export { } |
||||||
|
declare global { |
||||||
|
interface Fn<T = any> { |
||||||
|
(...arg: T[]): T |
||||||
|
} |
||||||
|
|
||||||
|
type Nullable<T> = T | null |
||||||
|
|
||||||
|
type ElRef<T extends HTMLElement = HTMLDivElement> = Nullable<T> |
||||||
|
type Recordable<T = any> = Record<string, T> |
||||||
|
|
||||||
|
type ComponentRef<T extends abstract new (...args: any) => any> = InstanceType<T> |
||||||
|
|
||||||
|
type LocaleType = 'zh-CN' | 'en' |
||||||
|
|
||||||
|
type AxiosHeaders = |
||||||
|
| 'application/json' |
||||||
|
| 'application/x-www-form-urlencoded' |
||||||
|
| 'multipart/form-data' |
||||||
|
|
||||||
|
type AxiosMethod = 'get' | 'post' | 'delete' | 'put' | 'GET' | 'POST' | 'DELETE' | 'PUT' |
||||||
|
|
||||||
|
type AxiosResponseType = |
||||||
|
| 'arraybuffer' |
||||||
|
| 'blob' |
||||||
|
| 'document' |
||||||
|
| 'json' |
||||||
|
| 'text' |
||||||
|
| 'stream' |
||||||
|
|
||||||
|
interface AxiosConfig { |
||||||
|
params?: any |
||||||
|
data?: any |
||||||
|
url?: string |
||||||
|
method?: AxiosMethod |
||||||
|
headersType?: string |
||||||
|
responseType?: AxiosResponseType |
||||||
|
} |
||||||
|
|
||||||
|
interface IResponse<T = any> { |
||||||
|
code: string |
||||||
|
data: T extends any ? T : T & any |
||||||
|
} |
||||||
|
|
||||||
|
interface PageParam { |
||||||
|
pageSize?: number |
||||||
|
pageNo?: number |
||||||
|
} |
||||||
|
|
||||||
|
interface Tree { |
||||||
|
id: number |
||||||
|
name: string |
||||||
|
children?: Tree[] | any[] |
||||||
|
} |
||||||
|
// 分页数据公共返回
|
||||||
|
interface PageResult<T> { |
||||||
|
list: T // 数据
|
||||||
|
total: number // 总量
|
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,16 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html lang=""> |
||||||
|
|
||||||
|
<head> |
||||||
|
<meta charset="UTF-8"> |
||||||
|
<link rel="icon" href="/favicon.ico"> |
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||||
|
<title>EMU</title> |
||||||
|
</head> |
||||||
|
|
||||||
|
<body> |
||||||
|
<div id="app"></div> |
||||||
|
<script type="module" src="/src/main.ts"></script> |
||||||
|
</body> |
||||||
|
|
||||||
|
</html> |
||||||
@ -0,0 +1,57 @@ |
|||||||
|
{ |
||||||
|
"name": "vue-project", |
||||||
|
"version": "0.0.0", |
||||||
|
"private": true, |
||||||
|
"type": "module", |
||||||
|
"scripts": { |
||||||
|
"dev": "vite", |
||||||
|
"preview": "vite preview", |
||||||
|
"build-only": "vite build", |
||||||
|
"build": "vite build", |
||||||
|
"type-check": "vue-tsc --build" |
||||||
|
}, |
||||||
|
"dependencies": { |
||||||
|
"@antv/g": "^6.1.28", |
||||||
|
"@antv/g-svg": "^2.0.42", |
||||||
|
"@antv/g6": "^5.0.49", |
||||||
|
"@types/qs": "^6.9.18", |
||||||
|
"@unocss/reset": "^66.0.0", |
||||||
|
"axios": "^1.8.4", |
||||||
|
"dayjs": "^1.11.13", |
||||||
|
"dexie": "^4.0.11", |
||||||
|
"echarts": "^5.6.0", |
||||||
|
"element-plus": "^2.9.5", |
||||||
|
"g6-extension-vue": "^0.1.0", |
||||||
|
"jszmq": "^0.1.2", |
||||||
|
"lodash-es": "^4.17.21", |
||||||
|
"mockjs": "^1.1.0", |
||||||
|
"p-limit": "^7.2.0", |
||||||
|
"pinia": "^3.0.1", |
||||||
|
"qs": "^6.14.0", |
||||||
|
"uuid": "^11.1.0", |
||||||
|
"vue": "^3.5.13", |
||||||
|
"vue-echarts": "^7.0.3", |
||||||
|
"vue-router": "^4.5.0" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"@iconify/json": "^2.2.310", |
||||||
|
"@tsconfig/node22": "^22.0.0", |
||||||
|
"@types/node": "^24.2.1", |
||||||
|
"@unocss/preset-icons": "^66.0.0", |
||||||
|
"@unocss/preset-rem-to-px": "^66.0.0", |
||||||
|
"@vitejs/plugin-vue": "^6.0.1", |
||||||
|
"@vitejs/plugin-vue-jsx": "^4.2.0", |
||||||
|
"@vue/tsconfig": "^0.7.0", |
||||||
|
"npm-run-all2": "^7.0.2", |
||||||
|
"sass": "^1.85.0", |
||||||
|
"typescript": "~5.7.3", |
||||||
|
"unocss": "^66.0.0", |
||||||
|
"unocss-preset-scalpel": "^1.2.7", |
||||||
|
"unplugin-auto-import": "^19.1.0", |
||||||
|
"unplugin-icons": "^22.1.0", |
||||||
|
"unplugin-vue-components": "^28.4.0", |
||||||
|
"vite": "^6.3.5", |
||||||
|
"vite-plugin-vue-devtools": "^7.7.2", |
||||||
|
"vue-tsc": "^2.2.2" |
||||||
|
} |
||||||
|
} |
||||||
|
After Width: | Height: | Size: 4.2 KiB |
@ -0,0 +1,11 @@ |
|||||||
|
<script setup lang="ts"> |
||||||
|
import zhCn from 'element-plus/dist/locale/zh-cn.mjs' |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<el-config-provider :locale="zhCn"> |
||||||
|
<RouterView/> |
||||||
|
</el-config-provider> |
||||||
|
</template> |
||||||
|
|
||||||
|
<style scoped lang="scss"></style> |
||||||
@ -0,0 +1,14 @@ |
|||||||
|
export default class Keys { |
||||||
|
public static STORAGE_UUID = 'uuid' |
||||||
|
public static STORAGE_TENANT_ID = 'tenant' |
||||||
|
public static STORAGE_TOKEN = 'token' |
||||||
|
public static STORAGE_REFRESH_TOKEN = 'refreshToken' |
||||||
|
public static STORAGE_USER_INFO = 'userInfo' |
||||||
|
public static STORAGE_ROLE_ROUTERS = 'roleRouters' |
||||||
|
public static STORAGE_DICT_CACHE = 'dict' |
||||||
|
public static STORAGE_LANG = 'lang' |
||||||
|
public static STORAGE_STATIONID = 'stationId' |
||||||
|
public static STORAGE_THEME = 'EDFS-THEME' |
||||||
|
|
||||||
|
public static CODE_SUCCEED = 0 |
||||||
|
} |
||||||
@ -0,0 +1,5 @@ |
|||||||
|
export const errorCode: any = { |
||||||
|
401: '认证失败,无法访问系统资源', |
||||||
|
403: '当前操作没有权限', |
||||||
|
404: '访问资源不存在', |
||||||
|
} |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
export interface Result<T> { |
||||||
|
code: number |
||||||
|
msg: string |
||||||
|
status?: number |
||||||
|
data: T |
||||||
|
} |
||||||
@ -0,0 +1,27 @@ |
|||||||
|
const authErrMap: any = { |
||||||
|
'401': '认证失败,无法访问系统资源', |
||||||
|
'403': '当前操作没有权限', |
||||||
|
'404': '访问资源不存在', |
||||||
|
default: '系统未知错误,请反馈给管理员', |
||||||
|
} |
||||||
|
|
||||||
|
const networkErrMap: { [key: number]: string } = { |
||||||
|
400: '请求错误(400)', |
||||||
|
401: '未授权,请重新登录(401)', |
||||||
|
403: '拒绝访问(403)', |
||||||
|
404: '请求出错(404)', |
||||||
|
408: '请求超时(408)', |
||||||
|
500: '服务器错误(500)', |
||||||
|
501: '服务未实现(501)', |
||||||
|
502: '网络错误(502)', |
||||||
|
503: '服务不可用(503)', |
||||||
|
504: '网络超时(504)', |
||||||
|
505: 'HTTP版本不受支持(505)', |
||||||
|
} |
||||||
|
|
||||||
|
const ignoreMsgs = [ |
||||||
|
'无效的刷新令牌', // 刷新令牌被删除时,不用提示
|
||||||
|
'刷新令牌已过期', // 使用刷新令牌,刷新获取新的访问令牌时,结果因为过期失败,此时需要忽略。否则,会导致继续 401,无法跳转到登出界面
|
||||||
|
] |
||||||
|
|
||||||
|
export { authErrMap, networkErrMap, ignoreMsgs } |
||||||
@ -0,0 +1,5 @@ |
|||||||
|
export interface IEngineeringList { |
||||||
|
id: string, |
||||||
|
name: string, |
||||||
|
description: string, |
||||||
|
} |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
import axiosInstance from '@/api/server/axiosInstance' |
||||||
|
import { API_Config } from '@/api/server/config' |
||||||
|
|
||||||
|
const globalServer = axiosInstance('global', API_Config.global) |
||||||
|
|
||||||
|
export { globalServer } |
||||||
@ -0,0 +1,238 @@ |
|||||||
|
import type { |
||||||
|
AxiosInstance, |
||||||
|
AxiosRequestConfig, |
||||||
|
AxiosRequestHeaders, |
||||||
|
AxiosResponse, |
||||||
|
InternalAxiosRequestConfig, |
||||||
|
} from 'axios' |
||||||
|
|
||||||
|
import qs from 'qs' |
||||||
|
import Keys from '../Keys' |
||||||
|
import axios, { AxiosError, isCancel } from 'axios' |
||||||
|
// import {
|
||||||
|
// getRefreshToken,
|
||||||
|
// getTenantId,
|
||||||
|
// getToken,
|
||||||
|
// removeToken,
|
||||||
|
// setToken,
|
||||||
|
// } from '@/utils/auth'
|
||||||
|
import { authErrMap, ignoreMsgs, networkErrMap } from '../basic/utils' |
||||||
|
import type { Result } from '../basic/httpTypes' |
||||||
|
import type { APIConfigKeys } from './config' |
||||||
|
import { useRouter } from 'vue-router' |
||||||
|
import { useMessage } from '@/composables/useMessage' |
||||||
|
const router = useRouter() |
||||||
|
|
||||||
|
const message = useMessage() |
||||||
|
const basicHeader = { |
||||||
|
tenant() { |
||||||
|
// const tenant = getTenantId()
|
||||||
|
// if (tenant) return tenant
|
||||||
|
return '' |
||||||
|
}, |
||||||
|
token() { |
||||||
|
// const token = getToken()
|
||||||
|
// if (token) return 'Bearer ' + token
|
||||||
|
return '' |
||||||
|
}, |
||||||
|
} |
||||||
|
interface Config { |
||||||
|
baseURL?: string |
||||||
|
baseAPI: string |
||||||
|
} |
||||||
|
const instances: Record<string, AxiosInstance> = {} |
||||||
|
let isRefreshToken = false // 是否正在刷新中
|
||||||
|
let requestList: any[] = [] // 请求队列
|
||||||
|
|
||||||
|
const createAxiosInstance = (module: APIConfigKeys, config: Config) => { |
||||||
|
if (!config || !config.baseAPI) { |
||||||
|
throw new Error(`Invalid configuration for module: ${module}`) |
||||||
|
} |
||||||
|
const { baseAPI } = config |
||||||
|
|
||||||
|
if (!instances[module]) { |
||||||
|
const instance = axios.create({ |
||||||
|
baseURL: `${baseAPI}`, |
||||||
|
timeout: 10000, |
||||||
|
headers: { |
||||||
|
'Content-Type': 'application/json', |
||||||
|
}, |
||||||
|
}) |
||||||
|
instances[module] = instance |
||||||
|
} |
||||||
|
|
||||||
|
const service = instances[module] |
||||||
|
|
||||||
|
service.interceptors.request.use( |
||||||
|
(config: InternalAxiosRequestConfig): InternalAxiosRequestConfig => { |
||||||
|
// config.headers.Authorization = basicHeader.token()
|
||||||
|
// config.headers['tenant-id'] = basicHeader.tenant()
|
||||||
|
const params = config.params || {} |
||||||
|
const data = config.data || false |
||||||
|
|
||||||
|
if ( |
||||||
|
config.method?.toUpperCase() === 'POST' && |
||||||
|
(config.headers as AxiosRequestHeaders)['Content-Type'] === |
||||||
|
'application/x-www-form-urlencoded' |
||||||
|
) { |
||||||
|
config.data = qs.stringify(data) |
||||||
|
} |
||||||
|
|
||||||
|
if (config.method?.toUpperCase() === 'GET' && params) { |
||||||
|
config.params = {} |
||||||
|
const paramsStr = qs.stringify(params, { allowDots: true }) |
||||||
|
if (paramsStr) { |
||||||
|
config.url = config.url + '?' + paramsStr |
||||||
|
} |
||||||
|
} |
||||||
|
return config |
||||||
|
}, |
||||||
|
(error: AxiosError): Promise<AxiosError> => { |
||||||
|
return Promise.reject(error) |
||||||
|
} |
||||||
|
) |
||||||
|
|
||||||
|
service.interceptors.response.use( |
||||||
|
async (res: AxiosResponse) => { |
||||||
|
const config = res.config |
||||||
|
let { data } = res |
||||||
|
if (!res.data) { |
||||||
|
throw new Error('返回“[HTTP]请求没有返回值”') |
||||||
|
} |
||||||
|
if ( |
||||||
|
res.request.responseType === 'blob' || |
||||||
|
res.request.responseType === 'arraybuffer' |
||||||
|
) { |
||||||
|
// 注意:如果导出的响应为 json,说明可能失败了,不直接返回进行下载
|
||||||
|
if (res.data.type !== 'application/json') { |
||||||
|
return Promise.resolve({ code: res.status, data: res.data }) |
||||||
|
} |
||||||
|
data = await new Response(res.data).json() |
||||||
|
} |
||||||
|
const code = data.code || 200 |
||||||
|
const msg = data.msg || authErrMap[code] || authErrMap['default'] |
||||||
|
|
||||||
|
if (ignoreMsgs.indexOf(msg) !== -1) { |
||||||
|
// 如果是忽略的错误码,直接返回 msg 异常
|
||||||
|
return Promise.reject({ code: null, msg }) |
||||||
|
} else |
||||||
|
// if (code === 401) {
|
||||||
|
// if (!isRefreshToken) {
|
||||||
|
// isRefreshToken = true
|
||||||
|
// if (!getRefreshToken()) logout()
|
||||||
|
// try {
|
||||||
|
// const refreshTokenRes = await refreshToken()
|
||||||
|
// if (refreshTokenRes.data.code !== 0) {
|
||||||
|
// logout()
|
||||||
|
// return Promise.reject({ code, msg })
|
||||||
|
// }
|
||||||
|
// setToken(refreshTokenRes.data.data.accessToken)
|
||||||
|
// config.headers!.Authorization = 'Bearer ' + getToken()
|
||||||
|
// requestList.forEach((cb: any) => {
|
||||||
|
// cb()
|
||||||
|
// })
|
||||||
|
// requestList = []
|
||||||
|
// // 重连接socket
|
||||||
|
// // openSocket()
|
||||||
|
// return service(config)
|
||||||
|
// } catch (e) {
|
||||||
|
// logout()
|
||||||
|
// return Promise.reject({ code, msg })
|
||||||
|
// } finally {
|
||||||
|
// requestList = []
|
||||||
|
// isRefreshToken = false
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// return new Promise(resolve => {
|
||||||
|
// requestList.push(() => {
|
||||||
|
// config.headers!.Authorization = 'Bearer ' + getToken()
|
||||||
|
// resolve(service(config))
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// } else
|
||||||
|
|
||||||
|
if (code === 500) { |
||||||
|
return Promise.reject({ code, msg }) |
||||||
|
} else if (code !== 200) { |
||||||
|
// if (msg === '无效的刷新令牌') {
|
||||||
|
// // hard coding:忽略这个提示,直接登出
|
||||||
|
// console.log(msg)
|
||||||
|
// } else {
|
||||||
|
// ElMessage.error(msg)
|
||||||
|
// }
|
||||||
|
return Promise.reject({ code, msg }) |
||||||
|
} |
||||||
|
|
||||||
|
return data |
||||||
|
}, |
||||||
|
(error: any): Promise<any> => { |
||||||
|
if (isCancel(error)) { |
||||||
|
return Promise.resolve() |
||||||
|
} |
||||||
|
const message = '请求错误' |
||||||
|
if (error.code === 'ECONNABORTED') { |
||||||
|
return Promise.reject({ code: null, msg: '服务器响应超时' }) |
||||||
|
} |
||||||
|
if (!error.response) { |
||||||
|
return Promise.reject({ code: null, msg: message }) |
||||||
|
} |
||||||
|
|
||||||
|
const status = error.response?.status |
||||||
|
const unKnowError = `连接出错(${error.response.status})!` |
||||||
|
if (status === 401) { |
||||||
|
localStorage.removeItem(Keys.STORAGE_TOKEN) |
||||||
|
router.push('/login') |
||||||
|
} |
||||||
|
|
||||||
|
const msg = networkErrMap[status] ? networkErrMap[status] : unKnowError |
||||||
|
|
||||||
|
return Promise.reject({ code: status || null, msg: msg }) |
||||||
|
} |
||||||
|
) |
||||||
|
|
||||||
|
if (!service) { |
||||||
|
throw new Error(`Failed to create Axios instance for module: ${module}`) |
||||||
|
} |
||||||
|
|
||||||
|
// const refreshToken = async () => {
|
||||||
|
// // axios.defaults.headers.common['tenant-id'] = getTenantId()
|
||||||
|
// return await axios.post(
|
||||||
|
// `${VITE_BASE_API_SYSTEM}/auth/refresh-token?refreshToken=` + getRefreshToken()
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async function request<T = any>(config: AxiosRequestConfig): Promise<Result<T>> { |
||||||
|
try { |
||||||
|
const result = (await service(config)) as Result<T> |
||||||
|
return result |
||||||
|
} catch (err: any) { |
||||||
|
console.log(err) |
||||||
|
const result: Result<T> = { |
||||||
|
code: err?.code || -1, |
||||||
|
msg: err.msg || err.message, |
||||||
|
data: err.data || null, |
||||||
|
} |
||||||
|
return result |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return request |
||||||
|
} |
||||||
|
|
||||||
|
// let isShowLogout = false
|
||||||
|
// async function logout() {
|
||||||
|
// if (isShowLogout) return
|
||||||
|
// isShowLogout = true
|
||||||
|
|
||||||
|
// await message.forceConfirm('登录状态已失效,请重新登录', '系统提示', '重新登录')
|
||||||
|
// removeToken()
|
||||||
|
// isShowLogout = false
|
||||||
|
// window.location.href = '/login'
|
||||||
|
// }
|
||||||
|
|
||||||
|
export default createAxiosInstance |
||||||
@ -0,0 +1,18 @@ |
|||||||
|
export interface APIConfig { |
||||||
|
global: Config |
||||||
|
} |
||||||
|
|
||||||
|
interface Config { |
||||||
|
baseURL?: string |
||||||
|
baseAPI: string |
||||||
|
} |
||||||
|
|
||||||
|
const API_Config: APIConfig = { |
||||||
|
global: { |
||||||
|
baseAPI: import.meta.env.VITE_BASE_API, |
||||||
|
}, |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
export type APIConfigKeys = keyof typeof API_Config |
||||||
|
export { API_Config } |
||||||
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 878 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 878 B |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1,5 @@ |
|||||||
|
// 导出变量 |
||||||
|
:export { |
||||||
|
namespace: $namespace; |
||||||
|
elNamespace: $elNamespace; |
||||||
|
} |
||||||
@ -0,0 +1,15 @@ |
|||||||
|
@import 'element-plus/dist/index.css'; |
||||||
|
@import './theme-variables.css'; |
||||||
|
@import '@unocss/reset/tailwind.css'; |
||||||
|
|
||||||
|
html, |
||||||
|
body { |
||||||
|
@apply w-full h-full; |
||||||
|
} |
||||||
|
|
||||||
|
#app { |
||||||
|
@apply wh-full; |
||||||
|
} |
||||||
|
.el-progress-bar__innerText { |
||||||
|
@apply w-full text-center; |
||||||
|
} |
||||||
@ -0,0 +1,77 @@ |
|||||||
|
@use 'sass:math'; |
||||||
|
|
||||||
|
// 命名空间 |
||||||
|
$namespace: v; |
||||||
|
// el命名空间 |
||||||
|
$elNamespace: el; |
||||||
|
$W-scale-ratio-1440: math.div(1440, 1920); |
||||||
|
$H-scale-ratio-900: math.div(900, 1080); |
||||||
|
|
||||||
|
$W-scale-ratio-2560: math.div(2560, 1920); |
||||||
|
$H-scale-ratio-1440: math.div(1440, 1080); |
||||||
|
|
||||||
|
@mixin ResponsiveW($base-width) { |
||||||
|
width: $base-width + px !important; |
||||||
|
|
||||||
|
@media only screen and (max-width: 1440px) { |
||||||
|
width: calc($W-scale-ratio-1440 * #{$base-width}px) !important; |
||||||
|
} |
||||||
|
|
||||||
|
@media only screen and (min-width: 2560px) { |
||||||
|
width: calc($W-scale-ratio-2560 * #{$base-width}px) !important; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@mixin ResponsiveH($base-height) { |
||||||
|
height: $base-height + px; |
||||||
|
|
||||||
|
@media only screen and (max-width: 1440px) { |
||||||
|
height: calc($H-scale-ratio-900 * #{$base-height}px); |
||||||
|
} |
||||||
|
|
||||||
|
@media only screen and (min-width: 2560px) { |
||||||
|
height: calc($H-scale-ratio-1440 * #{$base-height}px); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@mixin ResponsiveFz($base-font-size) { |
||||||
|
font-size: $base-font-size + px; |
||||||
|
|
||||||
|
@media only screen and (max-width: 1440px) { |
||||||
|
font-size: calc($W-scale-ratio-1440 * #{$base-font-size}px); |
||||||
|
} |
||||||
|
|
||||||
|
@media only screen and (min-width: 2560px) { |
||||||
|
font-size: calc($W-scale-ratio-2560 * #{$base-font-size}px); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@mixin ResponsivePadding($top, $right, $bottom, $left) { |
||||||
|
padding: $top + px $right + px $bottom + px $left + px; |
||||||
|
|
||||||
|
@media only screen and (max-width: 1440px) { |
||||||
|
padding: calc($H-scale-ratio-900 * #{$top}px) calc($W-scale-ratio-1440 * #{$right}px) |
||||||
|
calc($H-scale-ratio-900 * #{$bottom}px) calc($W-scale-ratio-1440 * #{$left}px); |
||||||
|
} |
||||||
|
|
||||||
|
@media only screen and (min-width: 2560px) { |
||||||
|
padding: calc($H-scale-ratio-1440 * #{$top}px) calc($W-scale-ratio-2560 * #{$right}px) |
||||||
|
calc($H-scale-ratio-1440 * #{$bottom}px), |
||||||
|
calc($W-scale-ratio-2560 * #{$left}px); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@mixin ResponsiveMargin($top, $right, $bottom, $left) { |
||||||
|
margin: $top + px $right + px $bottom + px $left + px; |
||||||
|
|
||||||
|
@media only screen and (max-width: 1440px) { |
||||||
|
margin: calc($H-scale-ratio-900 * #{$top}px) calc($W-scale-ratio-1440 * #{$right}px) |
||||||
|
calc($H-scale-ratio-900 * #{$bottom}px) calc($W-scale-ratio-1440 * #{$left}px); |
||||||
|
} |
||||||
|
|
||||||
|
@media only screen and (min-width: 2560px) { |
||||||
|
margin: calc($H-scale-ratio-1440 * #{$top}px) calc($W-scale-ratio-2560 * #{$right}px) |
||||||
|
calc($H-scale-ratio-1440 * #{$bottom}px), |
||||||
|
calc($W-scale-ratio-2560 * #{$left}px); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,74 @@ |
|||||||
|
html[data-theme='dark'] { |
||||||
|
--bg: #0d0d0d; |
||||||
|
--text-color: #ffffff; |
||||||
|
--layout-header-bg: #1b1d23; |
||||||
|
--layout-menu-bg: #1b1d23; |
||||||
|
--menu-active-bg: #2c342c; |
||||||
|
--menu-hover-bg: #272b35; |
||||||
|
--warp-bg: #212327; |
||||||
|
--table-header-bg: #3b3d40; |
||||||
|
--table-header-text-color: #cdcecf; |
||||||
|
--pagination-bg: #101115; |
||||||
|
--pagination-border-color: rgba(255, 255, 255, 0.13); |
||||||
|
--select-header--text-color: #ffffff; |
||||||
|
--station-card-bg: #23272f; |
||||||
|
--station-card-border-color: #4b5361; |
||||||
|
--station-header-bg: #363a40; |
||||||
|
--station-header-text-color: #fff; |
||||||
|
--station-info-val-text: #fff; |
||||||
|
--label-color: #666; |
||||||
|
--text-desc: #8d9095; |
||||||
|
--mask-bg: #333; |
||||||
|
--icon-color: #aaa; |
||||||
|
--icon-hover-color: #606266; |
||||||
|
} |
||||||
|
|
||||||
|
html[data-theme='light'] { |
||||||
|
--bg: #f1f2f6; |
||||||
|
--text-color: #4d4d4d; |
||||||
|
--layout-header-bg: #ffffff; |
||||||
|
--layout-menu-bg: #fff; |
||||||
|
--menu-active-bg: #f5fcee; |
||||||
|
--menu-hover-bg: #f5f5f5; |
||||||
|
--warp-bg: #ffffff; |
||||||
|
--table-header-bg: #e8e9ee; |
||||||
|
--table-header-text-color: #030303; |
||||||
|
--pagination-bg: transparent; |
||||||
|
--pagination-border-color: rgba(217, 217, 217, 1); |
||||||
|
--select-header--text-color: #666; |
||||||
|
--station-card-bg: #ffffff; |
||||||
|
--station-card-border-color: #e9e9e9; |
||||||
|
--station-header-bg: #f1f1f1; |
||||||
|
--station-header-text-color: #030303; |
||||||
|
--station-info-val-text: #4d4d4d; |
||||||
|
--label-color: #666; |
||||||
|
--text-desc: #a8abb2; |
||||||
|
--mask-bg: #f5f5f5; |
||||||
|
--icon-color: #aaa; |
||||||
|
--icon-hover-color: #606266; |
||||||
|
} |
||||||
|
|
||||||
|
html.dark { |
||||||
|
--el-bg-color: #212327; |
||||||
|
--el-border-color: rgba(255, 255, 255, 0.13); |
||||||
|
--el-button-divide-border-color: rgba(255, 255, 255, 0.13); |
||||||
|
--el-bg-color-overlay: #212327; |
||||||
|
--el-text-color-regular: #c7c8cb; |
||||||
|
--el-color-primary: #619925; |
||||||
|
--el-color-primary-light-3: rgb(78.1, 141.8, 46.6); |
||||||
|
--el-color-primary-light-5: rgb(61.5, 107, 39); |
||||||
|
--el-color-primary-light-7: rgb(44.9, 72.2, 31.4); |
||||||
|
--el-color-primary-light-8: rgb(36.6, 54.8, 27.6); |
||||||
|
--el-color-primary-light-9: rgb(28.3, 37.4, 23.8); |
||||||
|
--el-color-primary-dark-2: rgb(133.4, 206.2, 97.4); |
||||||
|
} |
||||||
|
|
||||||
|
html.light { |
||||||
|
--el-color-primary: #619925; |
||||||
|
--el-color-primary-light-3: rgb(148.6, 212.3, 117.1); |
||||||
|
--el-color-primary-light-5: rgb(179, 224.5, 156.5); |
||||||
|
--el-color-primary-light-7: rgb(209.4, 236.7, 195.9); |
||||||
|
--el-color-primary-light-8: rgb(224.6, 242.8, 215.6); |
||||||
|
--el-color-primary-light-9: rgb(239.8, 248.9, 235.3); |
||||||
|
--el-color-primary-dark-2: rgb(82.4, 155.2, 46.4); |
||||||
|
} |
||||||
@ -0,0 +1,154 @@ |
|||||||
|
<template> |
||||||
|
<div class="additem-item-input-root"> |
||||||
|
<div class="label" :class="labelWidth"> |
||||||
|
<span v-if="props.require" class="require">*</span> |
||||||
|
{{ label }}: |
||||||
|
</div> |
||||||
|
<el-input |
||||||
|
v-model="localValue" |
||||||
|
:type="type" |
||||||
|
:disabled="!canEdit" |
||||||
|
:show-password="showPassword" |
||||||
|
class="input" |
||||||
|
:placeholder="placeholder" |
||||||
|
@input="inputChange" |
||||||
|
resize="none" |
||||||
|
:rows="rows" |
||||||
|
@blur="handleBlur" |
||||||
|
:formatter="handleFormatter" |
||||||
|
></el-input> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup lang="ts"> |
||||||
|
export interface Props { |
||||||
|
type?: string |
||||||
|
require?: boolean |
||||||
|
label?: string |
||||||
|
modelValue?: string | number |
||||||
|
canEdit?: boolean |
||||||
|
widthType?: number |
||||||
|
stringNum?: boolean //获取string类型的number,开启时,输入框只能输入数字,且value值为string,适用于id类型的编辑 |
||||||
|
placeholder?: string |
||||||
|
showPassword?: boolean |
||||||
|
rows?: number |
||||||
|
} |
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), { |
||||||
|
type: 'text', |
||||||
|
require: false, |
||||||
|
label: '', |
||||||
|
modelValue: '', |
||||||
|
canEdit: true, |
||||||
|
widthType: 0, |
||||||
|
stringNum: false, |
||||||
|
cmd: false, |
||||||
|
placeholder: '', |
||||||
|
showPassword: false, |
||||||
|
rows: 5, |
||||||
|
}) |
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue']) |
||||||
|
|
||||||
|
const localValue = ref<string | number>('') |
||||||
|
|
||||||
|
const labelWidth = computed(() => { |
||||||
|
switch (props.widthType) { |
||||||
|
case 0: |
||||||
|
return 'width-normal' |
||||||
|
case 1: |
||||||
|
return '' |
||||||
|
default: |
||||||
|
return 'width-normal' |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
const inputChange = (val: string | number) => { |
||||||
|
emit('update:modelValue', props.type === 'number' ? Number(val) : val) |
||||||
|
} |
||||||
|
|
||||||
|
const handleBlur = () => { |
||||||
|
if (props.type === 'number') { |
||||||
|
if ( |
||||||
|
String(localValue.value).length === 0 || |
||||||
|
String(localValue.value).match(/^[0]+$/) !== null |
||||||
|
) { |
||||||
|
localValue.value = 0 |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const handleFormatter = (value: string | number) => { |
||||||
|
if (props.stringNum && typeof value === 'string') { |
||||||
|
return value.replace(/([^\d])+/g, '') |
||||||
|
} |
||||||
|
return value |
||||||
|
} |
||||||
|
|
||||||
|
watch( |
||||||
|
() => props.modelValue, |
||||||
|
modelValue => { |
||||||
|
localValue.value = modelValue |
||||||
|
}, |
||||||
|
{ immediate: true } |
||||||
|
) |
||||||
|
</script> |
||||||
|
|
||||||
|
<style lang="scss" scoped> |
||||||
|
.additem-item-input-root { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
box-sizing: border-box; |
||||||
|
column-gap: 8px; |
||||||
|
// font-size: 0.18rem; |
||||||
|
// padding-right: 42px; |
||||||
|
|
||||||
|
.label { |
||||||
|
// font-size: 14px; |
||||||
|
color: var(--label-color); |
||||||
|
line-height: 33px; |
||||||
|
text-align: right; |
||||||
|
// margin-right: 24px; |
||||||
|
|
||||||
|
.require { |
||||||
|
color: red; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.input { |
||||||
|
flex-grow: 1; |
||||||
|
width: 0; |
||||||
|
height: 100%; |
||||||
|
line-height: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
.width-normal { |
||||||
|
width: 110px; |
||||||
|
} |
||||||
|
|
||||||
|
:deep(input::-webkit-outer-spin-button), |
||||||
|
:deep(input::-webkit-inner-spin-button) { |
||||||
|
-webkit-appearance: none; |
||||||
|
} |
||||||
|
|
||||||
|
:deep(input[type='number']) { |
||||||
|
-moz-appearance: textfield; |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.el-input) { |
||||||
|
font-size: inherit; |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.el-input__inner) { |
||||||
|
// background-color: transparent; |
||||||
|
height: 100%; |
||||||
|
line-height: 100%; |
||||||
|
// @include border_color("ws_dialog_input"); |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.el-input__inner::-webkit-input-placeholder) { |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,67 @@ |
|||||||
|
<template> |
||||||
|
<el-button |
||||||
|
:type="type" |
||||||
|
:circle="circle" |
||||||
|
:icon="icon" |
||||||
|
:loading="loading" |
||||||
|
:auto-insert-space="autoInsertSpace" |
||||||
|
:round="round" |
||||||
|
:size="size" |
||||||
|
:plain="plain" |
||||||
|
:disabled="disabled" |
||||||
|
:autofocus="autofocus" |
||||||
|
:color="color" |
||||||
|
:class="className" |
||||||
|
@click="onClick" |
||||||
|
:link="link" |
||||||
|
:text="text" |
||||||
|
class="edfs-button" |
||||||
|
> |
||||||
|
{{ innerText }} |
||||||
|
</el-button> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup lang="ts"> |
||||||
|
import { type ButtonProps } from 'element-plus' |
||||||
|
|
||||||
|
type Props = ButtonProps & { |
||||||
|
innerText: string |
||||||
|
} |
||||||
|
const props = defineProps<Partial<Props>>() |
||||||
|
const emit = defineEmits<{ |
||||||
|
click: [e: MouseEvent] |
||||||
|
}>() |
||||||
|
|
||||||
|
const className = computed(() => { |
||||||
|
let name = 'edfs-button-default' |
||||||
|
switch (props.size) { |
||||||
|
case 'default': |
||||||
|
name = 'edfs-button-default' |
||||||
|
break |
||||||
|
case 'large': |
||||||
|
name = 'edfs-button-large' |
||||||
|
break |
||||||
|
case 'small': |
||||||
|
name = 'edfs-button-small' |
||||||
|
break |
||||||
|
} |
||||||
|
return name |
||||||
|
}) |
||||||
|
|
||||||
|
function onClick(e: MouseEvent) { |
||||||
|
emit('click', e) |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped lang="scss"> |
||||||
|
.edfs-button-default { |
||||||
|
height: 32px; |
||||||
|
font-size: 14px; |
||||||
|
padding: 8px 15px; |
||||||
|
} |
||||||
|
.edfs-button-small { |
||||||
|
height: 24px; |
||||||
|
font-size: 14px; |
||||||
|
padding: 6px 11px; |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,108 @@ |
|||||||
|
<template> |
||||||
|
<div class="base-dialog"> |
||||||
|
<el-dialog |
||||||
|
:title="title" |
||||||
|
v-model="visible" |
||||||
|
:draggable="draggable" |
||||||
|
:overflow="overflow" |
||||||
|
:width="width" |
||||||
|
:close-on-click-modal="false" |
||||||
|
@close="onClose" |
||||||
|
> |
||||||
|
<slot></slot> |
||||||
|
<template #footer> |
||||||
|
<slot name="footer"></slot> |
||||||
|
</template> |
||||||
|
|
||||||
|
<template v-if="isShowFooter"> |
||||||
|
<div class="dialog-footer"> |
||||||
|
<edfs-button |
||||||
|
type="primary" |
||||||
|
@click="onSave" |
||||||
|
:loading="btnLoading" |
||||||
|
inner-text="确定" |
||||||
|
/> |
||||||
|
<edfs-button v-if="useDelBtn" type="danger" @click="onDel" inner-text="删除" /> |
||||||
|
<edfs-button @click="onClose" inner-text="关闭" /> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
</el-dialog> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup lang="ts"> |
||||||
|
import edfsButton from './Edfs-button.vue' |
||||||
|
|
||||||
|
interface Props { |
||||||
|
title: string |
||||||
|
isShow: boolean |
||||||
|
isShowFooter?: boolean |
||||||
|
draggable?: boolean |
||||||
|
overflow?: boolean |
||||||
|
width?: string |
||||||
|
btnLoading?: boolean |
||||||
|
useDelBtn?: boolean |
||||||
|
} |
||||||
|
|
||||||
|
const emits = defineEmits(['on-save', 'on-close', 'on-del']) |
||||||
|
|
||||||
|
const visible = ref(false) |
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), { |
||||||
|
title: '提示', |
||||||
|
isShow: false, |
||||||
|
isShowFooter: true, |
||||||
|
draggable: true, |
||||||
|
overflow: true, |
||||||
|
btnLoading: false, |
||||||
|
useDelBtn: false, |
||||||
|
}) |
||||||
|
|
||||||
|
watch( |
||||||
|
() => props.isShow, |
||||||
|
val => { |
||||||
|
visible.value = val |
||||||
|
} |
||||||
|
) |
||||||
|
|
||||||
|
function onSave() { |
||||||
|
emits('on-save') |
||||||
|
} |
||||||
|
|
||||||
|
function onClose() { |
||||||
|
emits('on-close') |
||||||
|
} |
||||||
|
|
||||||
|
function onDel() { |
||||||
|
emits('on-del') |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style lang="scss" scoped> |
||||||
|
.base-dialog { |
||||||
|
:deep(.el-dialog__header) { |
||||||
|
display: flex; |
||||||
|
padding-bottom: 16px; |
||||||
|
padding-right: 20px; |
||||||
|
} |
||||||
|
:deep(.el-dialog__title) { |
||||||
|
font-size: 18px; |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.el-dialog) { |
||||||
|
padding: 20px; |
||||||
|
} |
||||||
|
:deep(.el-dialog__footer) { |
||||||
|
padding: 0; |
||||||
|
} |
||||||
|
|
||||||
|
.dialog-footer { |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
column-gap: 4px; |
||||||
|
height: 60px; |
||||||
|
margin-top: 20px; |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,151 @@ |
|||||||
|
<template> |
||||||
|
<div class="edfs-number-input"> |
||||||
|
<div class="label" v-if="labelName"> |
||||||
|
<span v-if="props.require" class="require">*</span> |
||||||
|
{{ labelName }} |
||||||
|
</div> |
||||||
|
<el-tooltip |
||||||
|
popper-class="number-tips" |
||||||
|
:content="tipText" |
||||||
|
placement="top" |
||||||
|
:visible="isShowTip" |
||||||
|
:disabled="!isUseTip" |
||||||
|
> |
||||||
|
<el-input |
||||||
|
class="number-input" |
||||||
|
v-model="inputValue" |
||||||
|
type="text" |
||||||
|
@input="handleInput" |
||||||
|
@wheel="onWheel" |
||||||
|
:disabled="disabled" |
||||||
|
:placeholder="placeholder" |
||||||
|
@focus="onFocus" |
||||||
|
@blur="onBlur" |
||||||
|
/> |
||||||
|
</el-tooltip> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup lang="ts"> |
||||||
|
interface Props { |
||||||
|
tipText?: string |
||||||
|
isUseTip?: boolean |
||||||
|
labelName?: string |
||||||
|
require?: boolean |
||||||
|
placeholder?: string |
||||||
|
modelValue: number | string |
||||||
|
numMax?: number | undefined |
||||||
|
numMin?: number | undefined |
||||||
|
useWheel?: boolean |
||||||
|
disabled?: boolean |
||||||
|
} |
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), { |
||||||
|
labelName: '', |
||||||
|
modelValue: '', |
||||||
|
require: false, |
||||||
|
placeholder: '请输入', |
||||||
|
numMax: undefined, |
||||||
|
numMin: undefined, |
||||||
|
isUseTip: false, |
||||||
|
tipText: '', |
||||||
|
disabled: false, |
||||||
|
useWheel: false, |
||||||
|
}) |
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'change']) |
||||||
|
const inputValue = ref() |
||||||
|
|
||||||
|
watch( |
||||||
|
() => props.modelValue, |
||||||
|
newValue => { |
||||||
|
inputValue.value = newValue |
||||||
|
}, |
||||||
|
{ immediate: true } |
||||||
|
) |
||||||
|
|
||||||
|
const handleInput = (value: string) => { |
||||||
|
let newValue = value.replace(/[^0-9.-]/g, '').replace(/(\..*)\./g, '$1') |
||||||
|
|
||||||
|
if (newValue.indexOf('-') > 0) { |
||||||
|
newValue = newValue.replace(/-/g, '') |
||||||
|
} |
||||||
|
|
||||||
|
if (newValue.length === 0) { |
||||||
|
isShowTip.value = true |
||||||
|
} else { |
||||||
|
isShowTip.value = false |
||||||
|
} |
||||||
|
|
||||||
|
inputValue.value = newValue |
||||||
|
} |
||||||
|
|
||||||
|
const onWheel = (event: WheelEvent) => { |
||||||
|
event.preventDefault() |
||||||
|
if (!props.useWheel) return |
||||||
|
let currentValue = Number(inputValue.value) || 0 |
||||||
|
if (event.deltaY < 0) { |
||||||
|
currentValue++ |
||||||
|
} else { |
||||||
|
currentValue-- |
||||||
|
} |
||||||
|
inputValue.value = currentValue.toString() |
||||||
|
} |
||||||
|
|
||||||
|
watch(inputValue, newValue => { |
||||||
|
if (!newValue) { |
||||||
|
emit('update:modelValue', undefined) |
||||||
|
return |
||||||
|
} |
||||||
|
let numericValue = newValue |
||||||
|
// if (props.numMax !== undefined && numericValue > props.numMax) { |
||||||
|
// inputValue.value = props.numMax.toString() |
||||||
|
// numericValue = props.numMax |
||||||
|
// } |
||||||
|
// if (props.numMin !== undefined && numericValue < props.numMin) { |
||||||
|
// inputValue.value = props.numMin.toString() |
||||||
|
// numericValue = props.numMin |
||||||
|
// } |
||||||
|
emit('update:modelValue', numericValue) |
||||||
|
}) |
||||||
|
|
||||||
|
const isShowTip = ref(false) |
||||||
|
|
||||||
|
function onBlur() { |
||||||
|
onChangeTip(false) |
||||||
|
} |
||||||
|
|
||||||
|
function onFocus() { |
||||||
|
onChangeTip(true) |
||||||
|
} |
||||||
|
|
||||||
|
function onChangeTip(visible: boolean) { |
||||||
|
isShowTip.value = visible |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style lang="scss" scoped> |
||||||
|
.edfs-number-input { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
box-sizing: border-box; |
||||||
|
|
||||||
|
.label { |
||||||
|
color: var(--label-color); |
||||||
|
white-space: nowrap; |
||||||
|
text-align: right; |
||||||
|
line-height: 33px; |
||||||
|
width: 110px; |
||||||
|
|
||||||
|
.require { |
||||||
|
color: red; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.number-input { |
||||||
|
flex: 1; |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
import type { TableProps } from 'element-plus'; |
||||||
|
|
||||||
|
interface IPaging { |
||||||
|
currentPage?: number, |
||||||
|
pageSize?: number, |
||||||
|
pageTotal?: number, |
||||||
|
usePaging?: boolean |
||||||
|
loading?: boolean |
||||||
|
} |
||||||
|
|
||||||
|
export type Props<T> = TableProps<T> & IPaging |
||||||
@ -0,0 +1,213 @@ |
|||||||
|
<template> |
||||||
|
<div class="edfs-table-components"> |
||||||
|
<el-table :data="data" :fit="fit" :stripe="stripe" :border="border" v-loading="loading" |
||||||
|
:show-header="showHeader" |
||||||
|
:max-height="maxHeight" :highlight-current-row="highlightCurrentRow" |
||||||
|
:row-class-name="rowClassName" |
||||||
|
@current-change="onCurrentChange" ref="ELTableRef" @row-click="onRowClick" |
||||||
|
@row-dblclick="onRowDblclick" |
||||||
|
@selection-change="handleSelectionChange" @expand-change="expandChange" |
||||||
|
class="edfs-table" |
||||||
|
:span-method="spanMethod"> |
||||||
|
<slot></slot> |
||||||
|
</el-table> |
||||||
|
<template v-if="usePaging"> |
||||||
|
<div class="pagination-block"> |
||||||
|
<el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize" |
||||||
|
:width-type="1" |
||||||
|
layout="prev, pager, next,total, jumper" :total="pageTotal" background |
||||||
|
@current-change="onPageCurrentChange"/> |
||||||
|
|
||||||
|
<!-- @size-change="onPageSizeChange" --> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup lang="ts" generic="T"> |
||||||
|
import { ElTable } from 'element-plus' |
||||||
|
import { type Props } from './defaults' |
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props<T>>(), { |
||||||
|
fit: true, |
||||||
|
showHeader: true, |
||||||
|
usePaging: true, |
||||||
|
currentPage: 1, |
||||||
|
pageTotal: 15, |
||||||
|
loading: false, |
||||||
|
}) |
||||||
|
|
||||||
|
|
||||||
|
const emit = defineEmits<{ |
||||||
|
'current-change': [currentRow: any, oldCurrentRow: any] |
||||||
|
'row-click': [row: any, column: any, event: Event] |
||||||
|
'row-dblclick': [row: any, column: any, event: Event] |
||||||
|
// 'page-size-change': [pageSize: number] |
||||||
|
'page-current-change': [currentPage: number] |
||||||
|
'selection-change': [selection: any[]] |
||||||
|
'expand-change': [row: any, expandedRows: any[]] |
||||||
|
}>() |
||||||
|
|
||||||
|
function onCurrentChange(currentRow: any, oldCurrentRow: any) { |
||||||
|
emit('current-change', currentRow, oldCurrentRow) |
||||||
|
} |
||||||
|
|
||||||
|
function onRowClick(row: any, column: any, event: Event) { |
||||||
|
emit('row-click', row, column, event) |
||||||
|
} |
||||||
|
|
||||||
|
function onRowDblclick(row: any, column: any, event: Event) { |
||||||
|
emit('row-dblclick', row, column, event) |
||||||
|
} |
||||||
|
|
||||||
|
function handleSelectionChange(selection: any[]) { |
||||||
|
emit('selection-change', selection) |
||||||
|
} |
||||||
|
|
||||||
|
function expandChange(row: any, expandedRows: any[]) { |
||||||
|
emit('expand-change', row, expandedRows) |
||||||
|
} |
||||||
|
|
||||||
|
// 分页 |
||||||
|
const pageSize = ref() |
||||||
|
|
||||||
|
watch( |
||||||
|
() => props.pageSize, |
||||||
|
val => { |
||||||
|
pageSize.value = val |
||||||
|
} |
||||||
|
) |
||||||
|
|
||||||
|
const currentPage = ref() |
||||||
|
watch( |
||||||
|
() => props.currentPage, |
||||||
|
val => { |
||||||
|
currentPage.value = val |
||||||
|
} |
||||||
|
) |
||||||
|
|
||||||
|
// function onPageSizeChange(pageSize: number) { |
||||||
|
// emit('page-size-change', pageSize) |
||||||
|
// } |
||||||
|
function onPageCurrentChange(currentPage: number) { |
||||||
|
emit('page-current-change', currentPage) |
||||||
|
} |
||||||
|
|
||||||
|
function getSize() { |
||||||
|
const el = document.querySelector('.edfs-table-components') |
||||||
|
if (!el) return 18 |
||||||
|
const height = Math.floor(el.getBoundingClientRect().height) |
||||||
|
return Math.ceil(height / 40) |
||||||
|
} |
||||||
|
|
||||||
|
const ELTableRef = ref<InstanceType<typeof ElTable>>() |
||||||
|
|
||||||
|
function clearSelection() { |
||||||
|
ELTableRef.value?.clearSelection() |
||||||
|
} |
||||||
|
|
||||||
|
defineExpose({ |
||||||
|
getSize, |
||||||
|
clearSelection, |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<style lang="scss" scoped> |
||||||
|
.edfs-table-components { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
box-sizing: border-box; |
||||||
|
|
||||||
|
// background-color: #fff; |
||||||
|
.edfs-table { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
font-size: 14px; |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.el-table th.el-table__cell) { |
||||||
|
background-color: var(--table-header-bg); |
||||||
|
font-family: Alibaba-PuHuiTi-M; |
||||||
|
font-size: 14px; |
||||||
|
color: var(--table-header-text-color); |
||||||
|
font-weight: 500; |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.el-table__row .cell) { |
||||||
|
font-family: Alibaba-PuHuiTi-R; |
||||||
|
line-height: 16px; |
||||||
|
min-height: 20px; |
||||||
|
font-weight: 400; |
||||||
|
color: var(--text-color); |
||||||
|
} |
||||||
|
|
||||||
|
.pagination-block { |
||||||
|
user-select: none; |
||||||
|
display: flex; |
||||||
|
// place-content: center; |
||||||
|
align-items: center; |
||||||
|
justify-content: end; |
||||||
|
height: 60px; |
||||||
|
|
||||||
|
:deep(.el-pagination__editor.el-input) { |
||||||
|
width: 66px; |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.el-input__wrapper) { |
||||||
|
height: 30px; |
||||||
|
margin-left: 8px; |
||||||
|
padding: 0 10px; |
||||||
|
|
||||||
|
.el-input__inner { |
||||||
|
font-size: 14px; |
||||||
|
height: 100%; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.el-pagination) { |
||||||
|
// width: cvw(200); |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.el-pagination), |
||||||
|
:deep(.el-pagination .el-icon), |
||||||
|
:deep(.el-pagination li) { |
||||||
|
font-size: 14px; |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.el-pagination .btn-next), |
||||||
|
:deep(.el-pagination .btn-prev), |
||||||
|
:deep(.el-pagination li) { |
||||||
|
background-color: transparent; |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.el-pager) { |
||||||
|
height: 28px; |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.el-pagination li) { |
||||||
|
min-width: 28px; |
||||||
|
height: 28px; |
||||||
|
border-radius: 2px; |
||||||
|
border: 1px solid var(--pagination-border-color); |
||||||
|
background-color: var(--pagination-bg); |
||||||
|
|
||||||
|
&:hover { |
||||||
|
color: #619925; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.is-active) { |
||||||
|
color: #619925 !important; |
||||||
|
background: rgba(97, 153, 37, 0.06) !important; |
||||||
|
border: 1px solid rgba(97, 153, 37, 1) !important; |
||||||
|
border-radius: 2px; |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.el-input__wrapper) { |
||||||
|
background-color: var(--pagination-bg); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,224 @@ |
|||||||
|
<template> |
||||||
|
<div class="edfs-layout-wrap"> |
||||||
|
<div class="edfs-wrap"> |
||||||
|
<div class="wrap-title" v-if="title || customLeft"> |
||||||
|
<div class="title-left" v-if="!customLeft"> |
||||||
|
<template v-if="shape === 'rect'"> |
||||||
|
<div |
||||||
|
class="title-rect" |
||||||
|
v-if="!useLeftColorBar" |
||||||
|
:style="{ backgroundColor: shapeColor }" |
||||||
|
></div> |
||||||
|
</template> |
||||||
|
<template v-else-if="shape === 'circle'"> |
||||||
|
<div |
||||||
|
class="title-circle" |
||||||
|
v-if="!useLeftColorBar" |
||||||
|
:style="{ backgroundColor: shapeColor }" |
||||||
|
></div> |
||||||
|
</template> |
||||||
|
<div class="title-text"> |
||||||
|
{{ title }} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="title-left" v-else> |
||||||
|
<div class="title-rect" v-if="!useLeftColorBar"></div> |
||||||
|
<slot name="title-left"></slot> |
||||||
|
</div> |
||||||
|
<div class="title-right"> |
||||||
|
<slot name="title-right"></slot> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<el-scrollbar class="wrap-body" :class="{ 'is-title': title }" v-if="useScrollBar"> |
||||||
|
<slot></slot> |
||||||
|
</el-scrollbar> |
||||||
|
<div class="wrap-body" :class="{ 'is-title': title }" v-else> |
||||||
|
<slot></slot> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div |
||||||
|
class="toggle-bar" |
||||||
|
v-if="isCollapse" |
||||||
|
:class="{ collapsed: !collapsed }" |
||||||
|
@click="onCollapse" |
||||||
|
> |
||||||
|
<div class="top"></div> |
||||||
|
<div class="bottom"></div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup lang="ts"> |
||||||
|
const emit = defineEmits<{ |
||||||
|
collapse: [] |
||||||
|
}>() |
||||||
|
|
||||||
|
interface Props { |
||||||
|
title?: string |
||||||
|
useLeftColorBar?: boolean |
||||||
|
barColor?: string |
||||||
|
customLeft?: boolean |
||||||
|
shape?: 'circle' | 'rect' |
||||||
|
shapeColor?: string |
||||||
|
useScrollBar?: boolean |
||||||
|
isCollapse?: boolean |
||||||
|
collapsed?: boolean |
||||||
|
} |
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), { |
||||||
|
title: '', |
||||||
|
useLeftColorBar: false, |
||||||
|
barColor: '#fff', |
||||||
|
customLeft: false, |
||||||
|
shape: 'rect', |
||||||
|
shapeColor: '#619925', |
||||||
|
useScrollBar: false, |
||||||
|
isCollapse: false, |
||||||
|
collapsed: false, |
||||||
|
}) |
||||||
|
|
||||||
|
function onCollapse() { |
||||||
|
emit('collapse') |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style lang="scss" scoped> |
||||||
|
.edfs-layout-wrap { |
||||||
|
width: 100%; |
||||||
|
transition: width 0.3s; |
||||||
|
position: relative; |
||||||
|
|
||||||
|
.edfs-wrap { |
||||||
|
width: 100%; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
overflow: hidden; |
||||||
|
height: 100%; |
||||||
|
box-sizing: border-box; |
||||||
|
background-color: var(--warp-bg); |
||||||
|
border-radius: 4px; |
||||||
|
|
||||||
|
.wrap-title { |
||||||
|
height: 46px; |
||||||
|
width: 100%; |
||||||
|
display: flex; |
||||||
|
justify-content: space-between; |
||||||
|
padding: 0 16px; |
||||||
|
box-sizing: border-box; |
||||||
|
background-color: var(--warp-bg); |
||||||
|
white-space: nowrap; |
||||||
|
|
||||||
|
.title-left { |
||||||
|
height: 100%; |
||||||
|
display: flex; |
||||||
|
gap: 4px; |
||||||
|
align-items: center; |
||||||
|
|
||||||
|
.title-rect { |
||||||
|
width: 3px; |
||||||
|
height: 14px; |
||||||
|
} |
||||||
|
|
||||||
|
.title-circle { |
||||||
|
width: 8px; |
||||||
|
height: 8px; |
||||||
|
border-radius: 50%; |
||||||
|
} |
||||||
|
|
||||||
|
.title-text { |
||||||
|
font-size: 16px; |
||||||
|
color: var(--text-color); |
||||||
|
font-weight: 700; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.title-right { |
||||||
|
// flex: 1; |
||||||
|
height: 100%; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
box-sizing: border-box; |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.el-button) { |
||||||
|
height: 26px; |
||||||
|
font-size: 14px; |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.el-icon) { |
||||||
|
font-size: 14px; |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.icon) { |
||||||
|
font-size: 14px; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.wrap-body { |
||||||
|
width: 100%; |
||||||
|
padding: 16px; |
||||||
|
height: 100%; |
||||||
|
box-sizing: border-box; |
||||||
|
background-color: var(--warp-bg); |
||||||
|
} |
||||||
|
|
||||||
|
.is-title { |
||||||
|
height: calc(100% - 46px); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.toggle-bar.collapsed { |
||||||
|
&:hover { |
||||||
|
.top { |
||||||
|
transform: rotate(12deg) scale(1.15) translateY(-2px); |
||||||
|
} |
||||||
|
|
||||||
|
.bottom { |
||||||
|
transform: rotate(-12deg) scale(1.15) translateY(2px); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.toggle-bar { |
||||||
|
cursor: pointer; |
||||||
|
height: 72px; |
||||||
|
width: 32px; |
||||||
|
position: absolute; |
||||||
|
top: calc(50% - 36px); |
||||||
|
z-index: 888; |
||||||
|
right: -20px; |
||||||
|
|
||||||
|
&:hover { |
||||||
|
.top { |
||||||
|
transform: rotate(-12deg) scale(1.15) translateY(-2px); |
||||||
|
} |
||||||
|
|
||||||
|
.bottom { |
||||||
|
transform: rotate(12deg) scale(1.15) translateY(2px); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.top { |
||||||
|
background-color: #c7bebe; |
||||||
|
position: absolute; |
||||||
|
width: 4px; |
||||||
|
border-radius: 2px; |
||||||
|
height: 38px; |
||||||
|
left: 14px; |
||||||
|
transition: all 0.3s; |
||||||
|
} |
||||||
|
|
||||||
|
.bottom { |
||||||
|
position: absolute; |
||||||
|
top: 34px; |
||||||
|
background-color: #c7bebe; |
||||||
|
width: 4px; |
||||||
|
border-radius: 2px; |
||||||
|
height: 38px; |
||||||
|
left: 14px; |
||||||
|
transition: all 0.3s; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,140 @@ |
|||||||
|
import { ElMessage, ElMessageBox, ElNotification } from 'element-plus' |
||||||
|
export const useMessage = () => { |
||||||
|
return { |
||||||
|
// 消息提示
|
||||||
|
info(content: string) { |
||||||
|
ElMessage.info(content) |
||||||
|
}, |
||||||
|
// 错误消息
|
||||||
|
error(content: string) { |
||||||
|
ElMessage.error(content) |
||||||
|
}, |
||||||
|
// 成功消息
|
||||||
|
success(content: string) { |
||||||
|
ElMessage.success(content) |
||||||
|
}, |
||||||
|
// 警告消息
|
||||||
|
warning(content: string) { |
||||||
|
ElMessage.warning(content) |
||||||
|
}, |
||||||
|
// 弹出提示
|
||||||
|
alert(content: string) { |
||||||
|
ElMessageBox.alert(content, '系统提示') |
||||||
|
}, |
||||||
|
// 错误提示
|
||||||
|
alertError(content: string) { |
||||||
|
ElMessageBox.alert(content, '系统提示', { type: 'error' }) |
||||||
|
}, |
||||||
|
// 成功提示
|
||||||
|
alertSuccess(content: string) { |
||||||
|
ElMessageBox.alert(content, '系统提示', { type: 'success' }) |
||||||
|
}, |
||||||
|
// 警告提示
|
||||||
|
alertWarning(content: string) { |
||||||
|
ElMessageBox.alert(content, '系统提示', { type: 'warning' }) |
||||||
|
}, |
||||||
|
// 通知提示
|
||||||
|
notify(content: string) { |
||||||
|
ElNotification.info(content) |
||||||
|
}, |
||||||
|
// 错误通知
|
||||||
|
notifyError(content: string, tip?: string) { |
||||||
|
ElNotification.error({ |
||||||
|
title: tip ? tip : '系统提示', |
||||||
|
message: content, |
||||||
|
}) |
||||||
|
}, |
||||||
|
// 成功通知
|
||||||
|
notifySuccess(content: string) { |
||||||
|
ElNotification.success(content) |
||||||
|
}, |
||||||
|
// 警告通知
|
||||||
|
notifyWarning(content: string) { |
||||||
|
ElNotification.warning(content) |
||||||
|
}, |
||||||
|
// 确认窗体
|
||||||
|
confirm(content: string, tip?: string) { |
||||||
|
return ElMessageBox.confirm(content, tip ? tip : '系统提示', { |
||||||
|
confirmButtonText: '确定', |
||||||
|
cancelButtonText: '取消', |
||||||
|
confirmButtonClass: 'el-button--success', |
||||||
|
cancelButtonClass: 'el-button--default', |
||||||
|
type: 'warning', |
||||||
|
}) |
||||||
|
}, |
||||||
|
forceConfirm(content: string, tip?: string, buttonText?: string) { |
||||||
|
return ElMessageBox.confirm(content, tip ? tip : '系统提示', { |
||||||
|
confirmButtonText: buttonText ?? '确定', |
||||||
|
showCancelButton: false, |
||||||
|
closeOnClickModal: false, |
||||||
|
closeOnPressEscape: false, |
||||||
|
confirmButtonClass: 'el-button--success', |
||||||
|
cancelButtonClass: 'el-button--default', |
||||||
|
showClose: false, |
||||||
|
type: 'warning', |
||||||
|
}) |
||||||
|
}, |
||||||
|
// 删除窗体
|
||||||
|
delConfirm(content?: string, tip?: string) { |
||||||
|
return new Promise((resolve, reject) => { |
||||||
|
ElMessageBox.confirm( |
||||||
|
content ? content : '是否确认删除数据项?', |
||||||
|
tip ? tip : '系统提示', |
||||||
|
{ |
||||||
|
confirmButtonText: '确定', |
||||||
|
cancelButtonText: '取消', |
||||||
|
type: 'warning', |
||||||
|
confirmButtonClass: 'el-button--success', |
||||||
|
cancelButtonClass: 'el-button--default', |
||||||
|
} |
||||||
|
) |
||||||
|
.then(() => { |
||||||
|
resolve('') |
||||||
|
}) |
||||||
|
.catch(() => { |
||||||
|
reject('') |
||||||
|
}) |
||||||
|
}) |
||||||
|
}, |
||||||
|
// 导出窗体
|
||||||
|
exportConfirm(content?: string, tip?: string) { |
||||||
|
return ElMessageBox.confirm( |
||||||
|
content ? content : '是否确认导出数据项?', |
||||||
|
tip ? tip : '系统提示', |
||||||
|
{ |
||||||
|
confirmButtonText: '确定', |
||||||
|
cancelButtonText: '取消', |
||||||
|
confirmButtonClass: 'el-button--success', |
||||||
|
cancelButtonClass: 'el-button--default', |
||||||
|
type: 'warning', |
||||||
|
} |
||||||
|
) |
||||||
|
}, |
||||||
|
// 提交内容
|
||||||
|
prompt(content: string, tip: string) { |
||||||
|
return ElMessageBox.prompt(content, tip, { |
||||||
|
confirmButtonText: '确定', |
||||||
|
cancelButtonText: '取消', |
||||||
|
confirmButtonClass: 'el-button--success', |
||||||
|
cancelButtonClass: 'el-button--default', |
||||||
|
type: 'warning', |
||||||
|
}) |
||||||
|
}, |
||||||
|
|
||||||
|
promptVerify(content: string, tip: string, pattern: string, inputErrorMessage = '') { |
||||||
|
const PatternRegExp = new RegExp( |
||||||
|
`^${pattern.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}$` |
||||||
|
) |
||||||
|
return ElMessageBox.prompt(content, tip, { |
||||||
|
confirmButtonText: '确定', |
||||||
|
cancelButtonText: '取消', |
||||||
|
confirmButtonClass: 'el-button--success', |
||||||
|
cancelButtonClass: 'el-button--default', |
||||||
|
inputPattern: PatternRegExp, |
||||||
|
inputErrorMessage: inputErrorMessage, |
||||||
|
type: 'warning', |
||||||
|
}) |
||||||
|
}, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
@ -0,0 +1,41 @@ |
|||||||
|
|
||||||
|
const STORAGE_THEME = 'theme' |
||||||
|
|
||||||
|
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches |
||||||
|
const localTheme = 'light' |
||||||
|
// localStorage.getItem(STORAGE_THEME) || (prefersDark ? 'dark' : 'light')
|
||||||
|
|
||||||
|
const theme = ref<'light' | 'dark'>(localTheme as 'light' | 'dark') |
||||||
|
|
||||||
|
const chartGraphicTextColor = computed(() => |
||||||
|
theme.value === 'dark' ? '#ccc' : '#4d4d4d' |
||||||
|
) |
||||||
|
|
||||||
|
watchEffect(() => { |
||||||
|
const html = document.querySelector('html') |
||||||
|
if (theme.value === 'dark') { |
||||||
|
html?.classList.remove('light') |
||||||
|
html?.classList.add('dark') |
||||||
|
} else { |
||||||
|
html?.classList.remove('dark') |
||||||
|
html?.classList.add('light') |
||||||
|
} |
||||||
|
document.documentElement.setAttribute('data-theme', theme.value) |
||||||
|
localStorage.setItem(STORAGE_THEME, theme.value) |
||||||
|
}) |
||||||
|
|
||||||
|
function toggle() { |
||||||
|
theme.value = theme.value === 'light' ? 'dark' : 'light' |
||||||
|
} |
||||||
|
function setTheme(value: 'light' | 'dark') { |
||||||
|
theme.value = value |
||||||
|
} |
||||||
|
|
||||||
|
export function useTheme() { |
||||||
|
return { |
||||||
|
theme, |
||||||
|
toggle, |
||||||
|
setTheme, |
||||||
|
chartGraphicTextColor, |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,175 @@ |
|||||||
|
import type { |
||||||
|
ManualAction, |
||||||
|
PublishMsg, |
||||||
|
PubMsgData, |
||||||
|
SubMsgData, |
||||||
|
TimeoutMsg, |
||||||
|
ZmqMessage |
||||||
|
} from '@/utils/zmq' |
||||||
|
import { WorkerCMD, ZmqCMD, } from '@/utils/zmq' |
||||||
|
import webWorker from '@/utils/zmqJsonWorker?worker' |
||||||
|
|
||||||
|
let defaultHost = env.VITE_ZMQ_BASE_URL |
||||||
|
|
||||||
|
|
||||||
|
const SUBDEFAULTKEY = 'default' |
||||||
|
|
||||||
|
type Handler = (msg: SubMsgData | PubMsgData) => void |
||||||
|
|
||||||
|
class ZMQJsonWorker { |
||||||
|
private static instance: ZMQJsonWorker | null = null; // ➤ 单例实例
|
||||||
|
private worker: Worker; |
||||||
|
private scribeHandlers: Map<string, Map<string, Handler>> = new Map(); |
||||||
|
private pubTimeoutHandlers: Map<string, (msg: TimeoutMsg) => void> = new Map(); |
||||||
|
private readonly host: string; |
||||||
|
private statusCallback: ((status: string) => void) | null = null; |
||||||
|
private isAlwaysListenMsgMap: Map<string, PublishMsg<any>> = new Map(); |
||||||
|
|
||||||
|
private constructor(host: string = defaultHost) { |
||||||
|
this.host = host; |
||||||
|
this.worker = new webWorker(); |
||||||
|
this.worker.onmessage = this.handleMessage.bind(this); |
||||||
|
} |
||||||
|
|
||||||
|
public static getInstance(host: string = defaultHost): ZMQJsonWorker { |
||||||
|
if (!ZMQJsonWorker.instance) { |
||||||
|
ZMQJsonWorker.instance = new ZMQJsonWorker(host); |
||||||
|
} |
||||||
|
return ZMQJsonWorker.instance; |
||||||
|
} |
||||||
|
|
||||||
|
start() { |
||||||
|
this.worker.postMessage({ cmd: WorkerCMD.START, msg: this.host }); |
||||||
|
} |
||||||
|
|
||||||
|
stop() { |
||||||
|
this.worker.postMessage({ cmd: WorkerCMD.STOP }); |
||||||
|
this.worker.terminate(); |
||||||
|
ZMQJsonWorker.instance = null; // ➤ 释放实例,允许重新创建
|
||||||
|
} |
||||||
|
|
||||||
|
subscribe(topic: string, handler: (msg: any) => void, id?: string) { |
||||||
|
const key = id ?? SUBDEFAULTKEY; |
||||||
|
let topicMap = this.scribeHandlers.get(topic); |
||||||
|
if (!topicMap) { |
||||||
|
topicMap = new Map<string, Handler>(); |
||||||
|
this.scribeHandlers.set(topic, topicMap); |
||||||
|
} |
||||||
|
|
||||||
|
// 添加 handler,不会覆盖其他 id 的 handler
|
||||||
|
topicMap.set(key, handler); |
||||||
|
this.worker.postMessage({ cmd: WorkerCMD.SUBSCRIBE, topic }); |
||||||
|
} |
||||||
|
|
||||||
|
unsubscribe(topic: string, id?: string) { |
||||||
|
const topicMap = this.scribeHandlers.get(topic); |
||||||
|
if (!topicMap) return; |
||||||
|
|
||||||
|
if (id) { |
||||||
|
topicMap.delete(id); |
||||||
|
} else { |
||||||
|
topicMap.delete(SUBDEFAULTKEY); |
||||||
|
} |
||||||
|
|
||||||
|
if (topicMap.size === 0) { |
||||||
|
this.scribeHandlers.delete(topic); |
||||||
|
this.worker.postMessage({ cmd: WorkerCMD.UNSUBSCRIBE, topic }); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
publish<T extends ManualAction>(topic: string, msg: PublishMsg<T>, isTimeout: boolean = false, handler?: (msg: TimeoutMsg) => void, isAlwaysListen: boolean = false) { |
||||||
|
if (isTimeout) { |
||||||
|
const timeoutId = msg.id |
||||||
|
if (typeof handler !== 'function') { |
||||||
|
console.warn(`发布主题${topic}失败, 回调函数handler为空`) |
||||||
|
return |
||||||
|
} |
||||||
|
this.pubTimeoutHandlers.set(timeoutId, handler) |
||||||
|
} |
||||||
|
if (isAlwaysListen) { |
||||||
|
this.isAlwaysListenMsgMap.set(`${msg.id}`, msg) |
||||||
|
} |
||||||
|
this.worker.postMessage({ |
||||||
|
cmd: WorkerCMD.PUBLISH, |
||||||
|
topic, |
||||||
|
msg: JSON.stringify(msg), |
||||||
|
isTimeout, |
||||||
|
isAlwaysListen |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
setStatusCallback(callback: (status: string) => void) { |
||||||
|
this.statusCallback = callback; |
||||||
|
} |
||||||
|
|
||||||
|
private handleSubscribeMessage(topic: string, json: PubMsgData & SubMsgData) { |
||||||
|
const topicMap = this.scribeHandlers.get(topic); |
||||||
|
if (!topicMap) return; |
||||||
|
|
||||||
|
topicMap.forEach((handler, id) => { |
||||||
|
try { |
||||||
|
handler(json); |
||||||
|
} catch (error) { |
||||||
|
console.error(`主题: ${topic} 的 handler ${id} 执行失败:`, error); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private handleTimeoutMessage(timeoutTopic: string, timeoutId: string) { |
||||||
|
const handler = this.pubTimeoutHandlers.get(timeoutId); |
||||||
|
if (handler) { |
||||||
|
handler({ |
||||||
|
timeoutId, |
||||||
|
timeoutTopic |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 回收发布后订阅的回调
|
||||||
|
private GC_pubReleaseSub(key: string) { |
||||||
|
this.scribeHandlers.delete(key); |
||||||
|
} |
||||||
|
|
||||||
|
// 回收超时消息的回调
|
||||||
|
private GC_pubReleaseTimeout(key: string) { |
||||||
|
this.pubTimeoutHandlers.delete(key); |
||||||
|
} |
||||||
|
|
||||||
|
private handleMessage(e: MessageEvent<ZmqMessage>) { |
||||||
|
const { cmd, msg, topic, community } = e.data; |
||||||
|
// const now = dayjs().format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
// console.log(now, e.data)
|
||||||
|
if (cmd === ZmqCMD.STATUS) { |
||||||
|
const status = community ? 'disconnected' : 'connected'; |
||||||
|
if (this.statusCallback) { |
||||||
|
this.statusCallback(status); |
||||||
|
} |
||||||
|
} else if (cmd === ZmqCMD.JSON_MSG) { |
||||||
|
const json = JSON.parse(msg) as PubMsgData & SubMsgData; |
||||||
|
|
||||||
|
if (json.action) { |
||||||
|
// 处理订阅消息
|
||||||
|
this.handleSubscribeMessage(topic, json); |
||||||
|
} else { |
||||||
|
// 处理需要发布消息并有返回的订阅消息
|
||||||
|
this.handleSubscribeMessage(`${topic}-${json.id}`, json); |
||||||
|
// 删除发布消息的回调
|
||||||
|
if (Object.keys(json).includes('result') && json.result !== 'progress') { |
||||||
|
if (!this.isAlwaysListenMsgMap.has(`${json.id}`)) { |
||||||
|
this.GC_pubReleaseSub(`${topic}-${json.id}`) |
||||||
|
this.GC_pubReleaseTimeout(`${json.id}`) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} else if (cmd === ZmqCMD.TIMEOUT) { |
||||||
|
this.handleTimeoutMessage(topic, msg); |
||||||
|
this.GC_pubReleaseTimeout(msg) |
||||||
|
// console.log('pubTimeoutHandlers=>', this.pubTimeoutHandlers)
|
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default ZMQJsonWorker; |
||||||
|
|
||||||
@ -0,0 +1,103 @@ |
|||||||
|
import { merge } from "lodash-es"; |
||||||
|
import type { IUseG6Options } from "./useG6"; |
||||||
|
import { VueNode } from "g6-extension-vue"; |
||||||
|
import { GenerateGraphData, MyLineEdge } from "./utils"; |
||||||
|
import { |
||||||
|
ExtensionCategory, |
||||||
|
Graph, |
||||||
|
type GraphOptions, |
||||||
|
register, |
||||||
|
type PluginOptions |
||||||
|
} from "@antv/g6"; |
||||||
|
|
||||||
|
export function useG6<T = any>({ |
||||||
|
tree, |
||||||
|
nodeComponent, |
||||||
|
nodeSize, |
||||||
|
layoutType = 'TB' |
||||||
|
}: IUseG6Options<T>, |
||||||
|
plugins?: PluginOptions |
||||||
|
) { |
||||||
|
|
||||||
|
register(ExtensionCategory.NODE, 'vue-node', VueNode); |
||||||
|
register(ExtensionCategory.EDGE, 'my-line-edge', MyLineEdge); |
||||||
|
|
||||||
|
const container = ref<HTMLElement>() |
||||||
|
const graphData = new GenerateGraphData(tree) |
||||||
|
|
||||||
|
const LR = () => ({ |
||||||
|
layout: { |
||||||
|
type: 'compact-box', |
||||||
|
direction: 'LR', |
||||||
|
getHeight: function getHeight() { |
||||||
|
return 32; |
||||||
|
}, |
||||||
|
getWidth: function getWidth() { |
||||||
|
return 32; |
||||||
|
}, |
||||||
|
getVGap: function getVGap() { |
||||||
|
return 10; |
||||||
|
}, |
||||||
|
getHGap: function getHGap() { |
||||||
|
return 100; |
||||||
|
}, |
||||||
|
}, |
||||||
|
node: { |
||||||
|
style: { |
||||||
|
ports: [{ placement: 'right' }, { placement: 'left' }], |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
const TB = () => ({ |
||||||
|
layout: { |
||||||
|
type: 'antv-dagre', |
||||||
|
}, |
||||||
|
node: { |
||||||
|
style: { |
||||||
|
size: nodeSize, |
||||||
|
dx: -nodeSize[0] / 2, |
||||||
|
dy: -nodeSize[1] / 2, |
||||||
|
ports: [{ placement: 'top' }, { placement: 'bottom' }], |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
const options: GraphOptions = { |
||||||
|
container: container.value!, |
||||||
|
padding: 50, |
||||||
|
autoFit: 'view', |
||||||
|
node: { |
||||||
|
type: 'vue-node', |
||||||
|
style: { |
||||||
|
component: (data: any) => { |
||||||
|
return h(nodeComponent, { data: data }) |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
edge: { |
||||||
|
type: 'my-line-edge', |
||||||
|
style: { |
||||||
|
radius: 10, |
||||||
|
router: { |
||||||
|
type: 'orth', |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
data: { |
||||||
|
nodes: graphData.getNodes(), |
||||||
|
edges: graphData.getEdges() |
||||||
|
}, |
||||||
|
plugins, |
||||||
|
behaviors: ['drag-canvas', 'zoom-canvas'], |
||||||
|
} |
||||||
|
|
||||||
|
const mergeOption = merge({}, options, layoutType === 'LR' ? LR() : TB()) |
||||||
|
|
||||||
|
const graph = new Graph(Object.assign(options, mergeOption)); |
||||||
|
|
||||||
|
return { |
||||||
|
container, |
||||||
|
graph |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
import type { Component } from "vue"; |
||||||
|
|
||||||
|
|
||||||
|
interface IUseG6Options<T> { |
||||||
|
tree: T[], |
||||||
|
nodeComponent: Component, |
||||||
|
nodeSize: [number, number] |
||||||
|
layoutType?: 'LR' | 'TB' |
||||||
|
} |
||||||
@ -0,0 +1,49 @@ |
|||||||
|
import type { EdgeData, NodeData } from "@antv/g6"; |
||||||
|
import type { Device, MyNodeData } from "@/types/device"; |
||||||
|
|
||||||
|
export class GenerateGraphData { |
||||||
|
private readonly devices: Device[] = []; |
||||||
|
private nodes: MyNodeData[] = []; |
||||||
|
private edges: EdgeData[] = []; |
||||||
|
|
||||||
|
constructor(devices: Device[]) { |
||||||
|
this.devices = devices; |
||||||
|
this.generateNodeData() |
||||||
|
this.generateEdgeData(); |
||||||
|
} |
||||||
|
|
||||||
|
private generateNodeData() { |
||||||
|
for (const device of this.devices) { |
||||||
|
this.nodes.push({ |
||||||
|
id: String(device.name), |
||||||
|
label: device.name, |
||||||
|
data: Object.assign(device, { |
||||||
|
// typeString: DeviceType[device.type],
|
||||||
|
}), |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private generateEdgeData() { |
||||||
|
for (const node of this.nodes) { |
||||||
|
const customData: Device = node.data |
||||||
|
const findParentNode: Device = this.nodes.some((n: MyNodeData) => n.id === String(customData.parentName)); |
||||||
|
|
||||||
|
if (findParentNode) { |
||||||
|
this.edges.push({ |
||||||
|
source: String(customData.parentName), |
||||||
|
target: String(node.id), |
||||||
|
data: customData, |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
getNodes(): NodeData[] { |
||||||
|
return this.nodes as NodeData[]; |
||||||
|
} |
||||||
|
|
||||||
|
getEdges(): EdgeData[] { |
||||||
|
return this.edges; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,64 @@ |
|||||||
|
import { Polyline } from '@antv/g6'; |
||||||
|
import { Circle } from '@antv/g'; |
||||||
|
import { subStyleProps } from '@antv/g6'; |
||||||
|
import type { Device } from "@/types/device"; |
||||||
|
|
||||||
|
export class MyLineEdge extends Polyline { |
||||||
|
getMarkerStyle(attributes: object) { |
||||||
|
return { |
||||||
|
r: 4, |
||||||
|
fill: '#58C448', |
||||||
|
offsetPath: this.shapeMap.key, ...subStyleProps(attributes, 'marker') |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
onCreate() { |
||||||
|
const marker = this.upsert('marker', Circle, this.getMarkerStyle(this.attributes), this)!; |
||||||
|
|
||||||
|
const prev = this.context.model.getRelatedEdgesData(this.sourceNode.id) // 获取源节点的相关边数据
|
||||||
|
.find((edge) => edge.target === this.targetNode.id) as Device; |
||||||
|
|
||||||
|
|
||||||
|
const depth = prev!.data!.depth; |
||||||
|
const delay = depth * 3000; // 每级延迟 3 秒
|
||||||
|
marker.animate( |
||||||
|
[{ offsetDistance: 0 }, { offsetDistance: 1 }], |
||||||
|
{ |
||||||
|
duration: 3000, |
||||||
|
iterations: Infinity, |
||||||
|
delay, |
||||||
|
} |
||||||
|
); |
||||||
|
|
||||||
|
// 涟漪效果:在 marker 外套一个圆
|
||||||
|
const ripple = this.upsert( |
||||||
|
'ripple', |
||||||
|
Circle, |
||||||
|
{ |
||||||
|
r: 4, // 初始半径和 marker 一样
|
||||||
|
stroke: '#58C448', |
||||||
|
lineWidth: 2, |
||||||
|
fill: 'none', |
||||||
|
offsetPath: this.shapeMap.key, |
||||||
|
}, |
||||||
|
this |
||||||
|
)!; |
||||||
|
|
||||||
|
// 涟漪动画:半径变大 + 透明度变小
|
||||||
|
ripple.animate( |
||||||
|
[ |
||||||
|
{ offsetDistance: 0, r: 4, opacity: 0.7 }, |
||||||
|
{ offsetDistance: 1, r: 6, opacity: 0 }, |
||||||
|
], |
||||||
|
{ |
||||||
|
duration: 3000, |
||||||
|
iterations: Infinity, |
||||||
|
delay, |
||||||
|
} |
||||||
|
); |
||||||
|
// marker.animate([{ offsetDistance: 0 }, { offsetDistance: 1 }], {
|
||||||
|
// duration: 3000,
|
||||||
|
// iterations: Infinity,
|
||||||
|
// });
|
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,4 @@ |
|||||||
|
export * from './GenerateGraphData' |
||||||
|
export * from './MyLineEdge' |
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,14 @@ |
|||||||
|
import * as zmq from "jszmq"; |
||||||
|
export default class ZmqClient { |
||||||
|
sock: zmq.Dealer | zmq.Router | zmq.XSub | zmq.XPub | zmq.Pull | zmq.Push | zmq.Pair; |
||||||
|
constructor(type: string); |
||||||
|
zmqReq(host: string, request_type: string, dataStr: string, timeout: number): Promise<string>; |
||||||
|
zmqSub(host: string, callback: (...args: any[]) => void): void; |
||||||
|
zmqPub(host: string): void; |
||||||
|
subscribe(topic: string): void; |
||||||
|
unsubscribe(topic: string): void; |
||||||
|
publishHex(topic: string, dataStr: string): Promise<void>; |
||||||
|
publishStr(topic: string, dataStr: string): Promise<void>; |
||||||
|
close(host: string, callback?: (...args: any[]) => void): void; |
||||||
|
} |
||||||
|
//# sourceMappingURL=zmqClient.d.ts.map
|
||||||
@ -0,0 +1,10 @@ |
|||||||
|
{ |
||||||
|
"version": 3, |
||||||
|
"file": "zmqClient.d.ts", |
||||||
|
"sourceRoot": "", |
||||||
|
"sources": [ |
||||||
|
"../src/zmqClient.ts" |
||||||
|
], |
||||||
|
"names": [], |
||||||
|
"mappings": "AAAA,OAAO,KAAK,GAAG,MAAM,OAAO,CAAC;AAG7B,MAAM,CAAC,OAAO,OAAO,SAAS;IAC1B,IAAI,EACE,GAAG,CAAC,MAAM,GACV,GAAG,CAAC,MAAM,GACV,GAAG,CAAC,IAAI,GACR,GAAG,CAAC,IAAI,GACR,GAAG,CAAC,IAAI,GACR,GAAG,CAAC,IAAI,GACR,GAAG,CAAC,IAAI,CAAC;gBACH,IAAI,EAAE,MAAM;IAgBlB,MAAM,CACR,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,CAAC;IA6BlB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI;IAKvD,MAAM,CAAC,IAAI,EAAE,MAAM;IAInB,SAAS,CAAC,KAAK,EAAE,MAAM;IAIvB,WAAW,CAAC,KAAK,EAAE,MAAM;IAInB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;IAOzC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;IAO/C,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI;CAO1D" |
||||||
|
} |
||||||
@ -0,0 +1,118 @@ |
|||||||
|
var __awaiter = |
||||||
|
(this && this.__awaiter) || |
||||||
|
function (thisArg, _arguments, P, generator) { |
||||||
|
function adopt(value) { |
||||||
|
return value instanceof P |
||||||
|
? value |
||||||
|
: new P(function (resolve) { |
||||||
|
resolve(value) |
||||||
|
}) |
||||||
|
} |
||||||
|
return new (P || (P = Promise))(function (resolve, reject) { |
||||||
|
function fulfilled(value) { |
||||||
|
try { |
||||||
|
step(generator.next(value)) |
||||||
|
} catch (e) { |
||||||
|
reject(e) |
||||||
|
} |
||||||
|
} |
||||||
|
function rejected(value) { |
||||||
|
try { |
||||||
|
step(generator['throw'](value)) |
||||||
|
} catch (e) { |
||||||
|
reject(e) |
||||||
|
} |
||||||
|
} |
||||||
|
function step(result) { |
||||||
|
result.done |
||||||
|
? resolve(result.value) |
||||||
|
: adopt(result.value).then(fulfilled, rejected) |
||||||
|
} |
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next()) |
||||||
|
}) |
||||||
|
} |
||||||
|
import * as zmq from 'jszmq' |
||||||
|
import { Buffer } from 'buffer' |
||||||
|
export default class ZmqClient { |
||||||
|
constructor(type) { |
||||||
|
switch (type) { |
||||||
|
case 'req': |
||||||
|
this.sock = zmq.socket('req') |
||||||
|
break |
||||||
|
case 'sub': |
||||||
|
this.sock = zmq.socket('sub') |
||||||
|
break |
||||||
|
case 'pub': |
||||||
|
this.sock = zmq.socket('pub') |
||||||
|
break |
||||||
|
default: |
||||||
|
throw new Error('unsupported client type') |
||||||
|
} |
||||||
|
} |
||||||
|
zmqReq(host, request_type, dataStr, timeout) { |
||||||
|
return __awaiter(this, void 0, void 0, function* () { |
||||||
|
this.sock.connect(host) |
||||||
|
var request = new Array() |
||||||
|
request[0] = request_type |
||||||
|
request[1] = dataStr |
||||||
|
this.sock.send(request) |
||||||
|
return Promise.race([ |
||||||
|
new Promise((resolve, reject) => { |
||||||
|
this.sock.on('message', function (message) { |
||||||
|
resolve(message.toString()) |
||||||
|
}) |
||||||
|
}), |
||||||
|
new Promise((resolve, reject) => { |
||||||
|
setTimeout(() => { |
||||||
|
reject('timeout error') |
||||||
|
}, timeout) |
||||||
|
}), |
||||||
|
]) |
||||||
|
// var isMessageArrived = false;
|
||||||
|
// const result: string = "";
|
||||||
|
// var msgReceiveTimer = setTimeout(() => {
|
||||||
|
// if (!isMessageArrived) {
|
||||||
|
// console.log("msg receive timed out");
|
||||||
|
// }
|
||||||
|
// }, timeout); // 设置超时时间为5秒
|
||||||
|
}) |
||||||
|
} |
||||||
|
zmqSub(host, callback) { |
||||||
|
this.sock.connect(host) |
||||||
|
this.sock.on('message', callback) |
||||||
|
} |
||||||
|
zmqPub(host) { |
||||||
|
this.sock.connect(host) |
||||||
|
} |
||||||
|
subscribe(topic) { |
||||||
|
this.sock.subscribe(topic) |
||||||
|
} |
||||||
|
unsubscribe(topic) { |
||||||
|
this.sock.unsubscribe(topic) |
||||||
|
} |
||||||
|
publishHex(topic, dataStr) { |
||||||
|
return __awaiter(this, void 0, void 0, function* () { |
||||||
|
var request = new Array() |
||||||
|
request[0] = topic |
||||||
|
const buf = Buffer.from(dataStr, 'hex') |
||||||
|
request[1] = buf |
||||||
|
this.sock.send(request) |
||||||
|
}) |
||||||
|
} |
||||||
|
publishStr(topic, dataStr) { |
||||||
|
return __awaiter(this, void 0, void 0, function* () { |
||||||
|
var request = new Array() |
||||||
|
request[0] = topic |
||||||
|
request[1] = dataStr |
||||||
|
this.sock.send(request) |
||||||
|
}) |
||||||
|
} |
||||||
|
close(host, callback) { |
||||||
|
if (callback) { |
||||||
|
this.sock.removeListener('message', callback) |
||||||
|
} |
||||||
|
this.sock.disconnect(host) |
||||||
|
this.sock.close() |
||||||
|
} |
||||||
|
} |
||||||
|
//# sourceMappingURL=zmqClient.js.map
|
||||||
@ -0,0 +1,10 @@ |
|||||||
|
{ |
||||||
|
"version": 3, |
||||||
|
"file": "zmqClient.js", |
||||||
|
"sourceRoot": "", |
||||||
|
"sources": [ |
||||||
|
"../src/zmqClient.ts" |
||||||
|
], |
||||||
|
"names": [], |
||||||
|
"mappings": ";;;;;;;;;AAAA,OAAO,KAAK,GAAG,MAAM,OAAO,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,MAAM,CAAC,OAAO,OAAO,SAAS;IAS1B,YAAY,IAAY;QACpB,QAAQ,IAAI,EAAE;YACV,KAAK,KAAK;gBACN,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBAC9B,MAAM;YACV,KAAK,KAAK;gBACN,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBAC9B,MAAM;YACV,KAAK,KAAK;gBACN,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBAC9B,MAAM;YACV;gBACI,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;SAClD;IACL,CAAC;IAEK,MAAM,CACR,IAAY,EACZ,YAAoB,EACpB,OAAe,EACf,OAAe;;YAEf,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACxB,IAAI,OAAO,GAAG,IAAI,KAAK,EAAE,CAAC;YAC1B,OAAO,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC;YAC1B,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC;YACrB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAExB,OAAO,OAAO,CAAC,IAAI,CAAC;gBAChB,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;oBACpC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,UAAU,OAAO;wBACrC,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;oBAChC,CAAC,CAAC,CAAC;gBACP,CAAC,CAAC;gBACF,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;oBACpC,UAAU,CAAC,GAAG,EAAE;wBACZ,MAAM,CAAC,eAAe,CAAC,CAAC;oBAC5B,CAAC,EAAE,OAAO,CAAC,CAAC;gBAChB,CAAC,CAAC;aACL,CAAC,CAAC;YAEH,gCAAgC;YAChC,6BAA6B;YAC7B,2CAA2C;YAC3C,+BAA+B;YAC/B,gDAAgD;YAChD,QAAQ;YACR,4BAA4B;QAChC,CAAC;KAAA;IAED,MAAM,CAAC,IAAY,EAAE,QAAkC;QACnD,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IACtC,CAAC;IAED,MAAM,CAAC,IAAY;QACf,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAED,SAAS,CAAC,KAAa;QACnB,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAED,WAAW,CAAC,KAAa;QACrB,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC;IAEK,UAAU,CAAC,KAAa,EAAE,OAAe;;YAC3C,IAAI,OAAO,GAAG,IAAI,KAAK,EAAE,CAAC;YAC1B,OAAO,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;YACnB,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YACxC,OAAO,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC;YACjB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC5B,CAAC;KAAA;IACK,UAAU,CAAC,KAAa,EAAE,OAAe;;YAC3C,IAAI,OAAO,GAAG,IAAI,KAAK,EAAE,CAAC;YAC1B,OAAO,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;YACnB,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC;YACrB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC5B,CAAC;KAAA;IAED,KAAK,CAAC,IAAY,EAAE,QAAmC;QACnD,IAAI,QAAQ,EAAE;YACV,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;SACjD;QACD,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QAC3B,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;CACJ" |
||||||
|
} |
||||||
@ -0,0 +1,14 @@ |
|||||||
|
import './assets/styles/main.css' |
||||||
|
import 'virtual:uno.css' |
||||||
|
import { createApp } from 'vue' |
||||||
|
import { createPinia } from 'pinia' |
||||||
|
|
||||||
|
import App from './App.vue' |
||||||
|
import router from './router' |
||||||
|
|
||||||
|
const app = createApp(App) |
||||||
|
|
||||||
|
app.use(createPinia()) |
||||||
|
app.use(router) |
||||||
|
|
||||||
|
app.mount('#app') |
||||||
@ -0,0 +1,34 @@ |
|||||||
|
import { createRouter, createWebHistory } from 'vue-router' |
||||||
|
|
||||||
|
export const defaultRouter = [ |
||||||
|
{ |
||||||
|
path: '/', |
||||||
|
name: 'dashboard', |
||||||
|
redirect: '/engineering', |
||||||
|
component: () => import('@/views/layout/index.vue'), |
||||||
|
meta: { |
||||||
|
title: '首页', |
||||||
|
isShow: true, |
||||||
|
icon: 'i-mage:file-2', |
||||||
|
}, |
||||||
|
children: [ |
||||||
|
{ |
||||||
|
path: '/engineering', |
||||||
|
name: 'engineering', |
||||||
|
component: () => import('@/views/engineering/index.vue'), |
||||||
|
meta: { |
||||||
|
title: '工程管理', |
||||||
|
isShow: true, |
||||||
|
icon: 'i-icon-park-outline:data', |
||||||
|
}, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
] |
||||||
|
|
||||||
|
const router = createRouter({ |
||||||
|
history: createWebHistory(import.meta.env.BASE_URL), |
||||||
|
routes: defaultRouter, |
||||||
|
}) |
||||||
|
|
||||||
|
export default router |
||||||
@ -0,0 +1,270 @@ |
|||||||
|
import { ref, computed, reactive } from 'vue' |
||||||
|
import { defineStore } from 'pinia' |
||||||
|
import type { IOnlineDevice, IUpFirmwareStatus } from '@/views/stationData/type' |
||||||
|
import ZMQWorker from '@/composables/useZMQJsonWorker' |
||||||
|
import { getSubTopic, type SubMsgData } from '@/utils/zmq' |
||||||
|
import { getDeviceTopic } from '@/views/stationData/utils' |
||||||
|
|
||||||
|
export interface SiteInfo { |
||||||
|
id: string |
||||||
|
onlineCount: number |
||||||
|
offlineCount: number |
||||||
|
devices: Map<string, IOnlineDevice> |
||||||
|
} |
||||||
|
|
||||||
|
export const useTransferDataStore = defineStore('transfer', () => { |
||||||
|
const subDevices = getSubTopic('client', 'status', 'transfer') |
||||||
|
const worker = ZMQWorker.getInstance() |
||||||
|
const isConnected = ref(false) |
||||||
|
|
||||||
|
const connectSite = ref<any>(null) |
||||||
|
|
||||||
|
const checkDeviceStatusInterval = ref<NodeJS.Timeout>() |
||||||
|
|
||||||
|
const siteMap = reactive(new Map<string, SiteInfo>()) |
||||||
|
|
||||||
|
const devicesMap = reactive(new Map<string, IOnlineDevice>()) |
||||||
|
|
||||||
|
// =========== mock =================
|
||||||
|
// let i = 0
|
||||||
|
//
|
||||||
|
// function mockSubDeviceMsg() {
|
||||||
|
// // 随机生成 SN
|
||||||
|
// const sn = `SN-${Math.floor(Math.random() * 1000)}`
|
||||||
|
// // const siteId = `${i % 2 === 0 ? 'site1' : 'site2'}`
|
||||||
|
// const siteId = `siteId-${Math.floor(Math.random() * 1000)}`
|
||||||
|
// const clientIp = `192.168.0.${Math.floor(Math.random() * 255)}`
|
||||||
|
// const version = `v${(Math.random() * 2 + 1).toFixed(2)}`
|
||||||
|
// const footprint = (Math.random() * 50000).toFixed(2) // KB
|
||||||
|
//
|
||||||
|
// // const sn = 123
|
||||||
|
// // const siteId = 123123
|
||||||
|
// // const clientIp = `192.168.0.${Math.floor(Math.random() * 255)}`
|
||||||
|
// // const version = `v${(Math.random() * 2 + 1).toFixed(2)}`
|
||||||
|
// // const footprint = (Math.random() * 50000).toFixed(2) // KB
|
||||||
|
//
|
||||||
|
// const msg: any = {
|
||||||
|
// feedback: [clientIp, sn, siteId, version, footprint],
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// getSubDevicesCb(msg)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// let a = setInterval(() => {
|
||||||
|
// i++
|
||||||
|
// mockSubDeviceMsg()
|
||||||
|
// }, 1000)
|
||||||
|
|
||||||
|
// setTimeout(() => {
|
||||||
|
// clearInterval(a)
|
||||||
|
// }, 8000)
|
||||||
|
|
||||||
|
// =========== mock end =================
|
||||||
|
|
||||||
|
function startCheckStatus() { |
||||||
|
clearInterval(checkDeviceStatusInterval.value) |
||||||
|
checkDeviceStatusInterval.value = setInterval(checkDeviceStatusFn, 1000); |
||||||
|
} |
||||||
|
|
||||||
|
const checkDeviceStatusFn = () => { |
||||||
|
const now = Date.now(); |
||||||
|
devicesMap.forEach((device: IOnlineDevice, sn) => { |
||||||
|
// console.log(device, dayjs(now).format('HH:mm:ss'), dayjs(device.lastUpdated).format('HH:mm:ss'), now - device.lastUpdated)
|
||||||
|
if (now - device.lastUpdated > 5500) { |
||||||
|
device.status = '离线'; |
||||||
|
} |
||||||
|
}); |
||||||
|
reassignDevicesToSites() |
||||||
|
} |
||||||
|
|
||||||
|
function formatSizeFromKB(num: number): string { |
||||||
|
const sizeKB = Number(num) |
||||||
|
const units = ['KB', 'MB', 'GB', 'TB', 'PB'] |
||||||
|
let size = sizeKB |
||||||
|
let unitIndex = 0 |
||||||
|
|
||||||
|
while (size >= 1024 && unitIndex < units.length - 1) { |
||||||
|
size = size / 1024 |
||||||
|
unitIndex++ |
||||||
|
} |
||||||
|
|
||||||
|
return `${size.toFixed(2)} ${units[unitIndex]}` |
||||||
|
} |
||||||
|
|
||||||
|
function getSubDevicesCb(msg: SubMsgData) { |
||||||
|
const { feedback } = msg |
||||||
|
const sn = feedback[1] |
||||||
|
const hasDevice = devicesMap.get(sn) |
||||||
|
if (hasDevice) { |
||||||
|
hasDevice.lastUpdated = Date.now() |
||||||
|
hasDevice.status = '在线' |
||||||
|
hasDevice.footprint = formatSizeFromKB(Number(feedback[4] || 0)) |
||||||
|
hasDevice.clientIp = feedback[0] |
||||||
|
hasDevice.versions = feedback[3] ?? '--' |
||||||
|
} else { |
||||||
|
const num = feedback[4] || 0 |
||||||
|
const device: IOnlineDevice = { |
||||||
|
clientIp: feedback[0], |
||||||
|
sn: sn, |
||||||
|
site_id: feedback[2], |
||||||
|
versions: feedback[3] ?? '--', |
||||||
|
footprint: formatSizeFromKB(Number(num)), |
||||||
|
lastUpdated: Date.now(), |
||||||
|
status: '在线', // 初始状态为在线
|
||||||
|
isChecked: false, |
||||||
|
} |
||||||
|
devicesMap.set(sn, device) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function reassignDevicesToSites() { |
||||||
|
devicesMap.forEach((device) => { |
||||||
|
const siteId = device.site_id |
||||||
|
if (!siteId) return |
||||||
|
|
||||||
|
// 若该站点还未创建,则初始化
|
||||||
|
if (!siteMap.has(siteId)) { |
||||||
|
siteMap.set(siteId, { |
||||||
|
id: siteId, |
||||||
|
onlineCount: 0, |
||||||
|
offlineCount: 0, |
||||||
|
devices: reactive(new Map<string, IOnlineDevice>()), |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
// 获取站点信息并更新
|
||||||
|
const siteInfo = siteMap.get(siteId)! |
||||||
|
siteInfo.devices.set(device.sn, device) |
||||||
|
}) |
||||||
|
|
||||||
|
// 更新site 在线数量和离线数量
|
||||||
|
siteMap.forEach((siteInfo) => { |
||||||
|
const devices = Array.from(siteInfo.devices.values()) |
||||||
|
siteInfo.onlineCount = devices.filter(item => item.status === '在线').length |
||||||
|
siteInfo.offlineCount = devices.filter(item => item.status === '离线').length |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const onlineCount = computed(() => { |
||||||
|
return Array.from(devicesMap.values()).filter(item => item.status === '在线') |
||||||
|
.length |
||||||
|
}) |
||||||
|
|
||||||
|
const offlineCount = computed(() => { |
||||||
|
return Array.from(devicesMap.values()).filter(item => item.status === '离线') |
||||||
|
.length |
||||||
|
}) |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
worker.subscribe(getDeviceTopic, getSubDevicesCb) |
||||||
|
startCheckStatus() |
||||||
|
document.addEventListener('visibilitychange', handleVisibilityChange) |
||||||
|
}) |
||||||
|
|
||||||
|
const route = useRoute() |
||||||
|
|
||||||
|
watch(() => route.path, (val) => { |
||||||
|
if (!['/station/data-transfer', '/station'].includes(val)) { |
||||||
|
clearInterval(checkDeviceStatusInterval.value) |
||||||
|
document.removeEventListener('visibilitychange', handleVisibilityChange) |
||||||
|
worker.unsubscribe(subDevices) |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
function handleVisibilityChange() { |
||||||
|
if (document.hidden) { |
||||||
|
clearInterval(checkDeviceStatusInterval.value) |
||||||
|
} else { |
||||||
|
startCheckStatus() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function upFirmwareStatus(sn: string, feedback: any[]) { |
||||||
|
const device = devicesMap.get(sn) |
||||||
|
if (device) { |
||||||
|
device.upFirmware = 'updating' |
||||||
|
const step = feedback[1] |
||||||
|
const progress = feedback[2] || undefined |
||||||
|
const errMsg = feedback[3] || undefined |
||||||
|
if (step < (device.upFirmwareStatus?.step ?? -100)) return |
||||||
|
device.upFirmwareStatus = { |
||||||
|
step, |
||||||
|
progress: progress === -1 ? 100 : progress, |
||||||
|
errMsg, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function upFirmwareStatusReject(sn: string, feedback: any[]) { |
||||||
|
const device = devicesMap.get(sn) |
||||||
|
if (device) { |
||||||
|
device.upFirmware = 'rejected' |
||||||
|
const step = feedback[1] |
||||||
|
const progress = feedback[2] || undefined |
||||||
|
const errMsg = feedback[3] || undefined |
||||||
|
if (step < (device.upFirmwareStatus?.step ?? -100)) return |
||||||
|
device.upFirmwareStatus = { |
||||||
|
step, |
||||||
|
progress: progress === -1 ? 100 : progress, |
||||||
|
errMsg, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function upFirmwarePending(snList: string[]) { |
||||||
|
for (const sn of snList) { |
||||||
|
const device = devicesMap.get(sn) |
||||||
|
if (device) { |
||||||
|
device.upFirmware = 'pending' |
||||||
|
device.upFirmwareStatus = { |
||||||
|
step: 0, |
||||||
|
progress: undefined, |
||||||
|
errMsg: undefined, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function upFirmwareReset(snList: string[]) { |
||||||
|
for (const sn of snList) { |
||||||
|
const device = devicesMap.get(sn) |
||||||
|
if (device) { |
||||||
|
upFirmwareSucceed(sn) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function upFirmwareSucceed(sn: string) { |
||||||
|
const device = devicesMap.get(sn) |
||||||
|
if (device) { |
||||||
|
device.upFirmware = undefined |
||||||
|
device.upFirmwareStatus = undefined |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function upFirmwareTimeout(sn: string) { |
||||||
|
const device = devicesMap.get(sn) |
||||||
|
if (device) { |
||||||
|
device.upFirmware = 'timeout' |
||||||
|
device.upFirmwareStatus = undefined |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
return { |
||||||
|
siteMap, |
||||||
|
isConnected, |
||||||
|
devicesMap, |
||||||
|
connectSite, |
||||||
|
checkDeviceStatusInterval, |
||||||
|
onlineCount, |
||||||
|
offlineCount, |
||||||
|
upFirmwarePending, |
||||||
|
upFirmwareReset, |
||||||
|
upFirmwareStatus, |
||||||
|
upFirmwareSucceed, |
||||||
|
upFirmwareStatusReject, |
||||||
|
upFirmwareTimeout |
||||||
|
} |
||||||
|
}) |
||||||
@ -0,0 +1,5 @@ |
|||||||
|
import type { NodeData } from "@antv/g6"; |
||||||
|
export type Device = any |
||||||
|
export interface MyNodeData extends Omit<NodeData, 'data'> { |
||||||
|
data?: Device; |
||||||
|
} |
||||||
@ -0,0 +1,53 @@ |
|||||||
|
// @unocss-include
|
||||||
|
import type { Preset, PresetUnoTheme } from 'unocss' |
||||||
|
|
||||||
|
export function presetSoybeanAdmin(): Preset<PresetUnoTheme> { |
||||||
|
const preset: Preset<PresetUnoTheme> = { |
||||||
|
name: 'preset-soybean-admin', |
||||||
|
shortcuts: [ |
||||||
|
{ |
||||||
|
'flex-center': 'flex justify-center items-center', |
||||||
|
'flex-x-center': 'flex justify-center', |
||||||
|
'flex-y-center': 'flex items-center', |
||||||
|
'flex-col': 'flex flex-col', |
||||||
|
'flex-col-center': 'flex-center flex-col', |
||||||
|
'flex-col-stretch': 'flex-col items-stretch', |
||||||
|
'i-flex-center': 'inline-flex justify-center items-center', |
||||||
|
'i-flex-x-center': 'inline-flex justify-center', |
||||||
|
'i-flex-y-center': 'inline-flex items-center', |
||||||
|
'i-flex-col': 'flex-col inline-flex', |
||||||
|
'i-flex-col-center': 'flex-col i-flex-center', |
||||||
|
'i-flex-col-stretch': 'i-flex-col items-stretch', |
||||||
|
'flex-1-hidden': 'flex-1 overflow-hidden', |
||||||
|
}, |
||||||
|
{ |
||||||
|
'absolute-lt': 'absolute left-0 top-0', |
||||||
|
'absolute-lb': 'absolute left-0 bottom-0', |
||||||
|
'absolute-rt': 'absolute right-0 top-0', |
||||||
|
'absolute-rb': 'absolute right-0 bottom-0', |
||||||
|
'absolute-tl': 'absolute-lt', |
||||||
|
'absolute-tr': 'absolute-rt', |
||||||
|
'absolute-bl': 'absolute-lb', |
||||||
|
'absolute-br': 'absolute-rb', |
||||||
|
'absolute-center': 'absolute-lt flex-center size-full', |
||||||
|
'fixed-lt': 'fixed left-0 top-0', |
||||||
|
'fixed-lb': 'fixed left-0 bottom-0', |
||||||
|
'fixed-rt': 'fixed right-0 top-0', |
||||||
|
'fixed-rb': 'fixed right-0 bottom-0', |
||||||
|
'fixed-tl': 'fixed-lt', |
||||||
|
'fixed-tr': 'fixed-rt', |
||||||
|
'fixed-bl': 'fixed-lb', |
||||||
|
'fixed-br': 'fixed-rb', |
||||||
|
'fixed-center': 'fixed-lt flex-center size-full', |
||||||
|
}, |
||||||
|
{ |
||||||
|
'nowrap-hidden': 'overflow-hidden whitespace-nowrap', |
||||||
|
'ellipsis-text': 'nowrap-hidden text-ellipsis', |
||||||
|
}, |
||||||
|
], |
||||||
|
} |
||||||
|
|
||||||
|
return preset |
||||||
|
} |
||||||
|
|
||||||
|
export default presetSoybeanAdmin |
||||||
@ -0,0 +1,117 @@ |
|||||||
|
// copy to vben-admin
|
||||||
|
|
||||||
|
const toString = Object.prototype.toString |
||||||
|
|
||||||
|
export const is = (val: unknown, type: string) => { |
||||||
|
return toString.call(val) === `[object ${type}]` |
||||||
|
} |
||||||
|
|
||||||
|
export const isDef = <T = unknown>(val?: T): val is T => { |
||||||
|
return typeof val !== 'undefined' |
||||||
|
} |
||||||
|
|
||||||
|
export const isUnDef = <T = unknown>(val?: T): val is T => { |
||||||
|
return !isDef(val) |
||||||
|
} |
||||||
|
|
||||||
|
export const isObject = (val: any): val is Record<any, any> => { |
||||||
|
return val !== null && is(val, 'Object') |
||||||
|
} |
||||||
|
|
||||||
|
export const isEmpty = <T = unknown>(val: T): val is T => { |
||||||
|
if (val === null) { |
||||||
|
return true |
||||||
|
} |
||||||
|
if (isArray(val) || isString(val)) { |
||||||
|
return val.length === 0 |
||||||
|
} |
||||||
|
|
||||||
|
if (val instanceof Map || val instanceof Set) { |
||||||
|
return val.size === 0 |
||||||
|
} |
||||||
|
|
||||||
|
if (isObject(val)) { |
||||||
|
return Object.keys(val).length === 0 |
||||||
|
} |
||||||
|
|
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
export const isDate = (val: unknown): val is Date => { |
||||||
|
return is(val, 'Date') |
||||||
|
} |
||||||
|
|
||||||
|
export const isNull = (val: unknown): val is null => { |
||||||
|
return val === null |
||||||
|
} |
||||||
|
|
||||||
|
export const isNullAndUnDef = (val: unknown): val is null | undefined => { |
||||||
|
return isUnDef(val) && isNull(val) |
||||||
|
} |
||||||
|
|
||||||
|
export const isNullOrUnDef = (val: unknown): val is null | undefined => { |
||||||
|
return isUnDef(val) || isNull(val) |
||||||
|
} |
||||||
|
|
||||||
|
export const isNumber = (val: unknown): val is number => { |
||||||
|
return is(val, 'Number') |
||||||
|
} |
||||||
|
|
||||||
|
export const isPromise = <T = any>(val: unknown): val is Promise<T> => { |
||||||
|
return is(val, 'Promise') && isObject(val) && isFunction(val.then) && isFunction(val.catch) |
||||||
|
} |
||||||
|
|
||||||
|
export const isString = (val: unknown): val is string => { |
||||||
|
return is(val, 'String') |
||||||
|
} |
||||||
|
|
||||||
|
export const isFunction = (val: unknown): val is Function => { |
||||||
|
return typeof val === 'function' |
||||||
|
} |
||||||
|
|
||||||
|
export const isBoolean = (val: unknown): val is boolean => { |
||||||
|
return is(val, 'Boolean') |
||||||
|
} |
||||||
|
|
||||||
|
export const isRegExp = (val: unknown): val is RegExp => { |
||||||
|
return is(val, 'RegExp') |
||||||
|
} |
||||||
|
|
||||||
|
export const isArray = (val: any): val is Array<any> => { |
||||||
|
return val && Array.isArray(val) |
||||||
|
} |
||||||
|
|
||||||
|
export const isWindow = (val: any): val is Window => { |
||||||
|
return typeof window !== 'undefined' && is(val, 'Window') |
||||||
|
} |
||||||
|
|
||||||
|
export const isElement = (val: unknown): val is Element => { |
||||||
|
return isObject(val) && !!val.tagName |
||||||
|
} |
||||||
|
|
||||||
|
export const isMap = (val: unknown): val is Map<any, any> => { |
||||||
|
return is(val, 'Map') |
||||||
|
} |
||||||
|
|
||||||
|
export const isServer = typeof window === 'undefined' |
||||||
|
|
||||||
|
export const isClient = !isServer |
||||||
|
|
||||||
|
export const isUrl = (path: string): boolean => { |
||||||
|
const reg = |
||||||
|
/(((^https?:(?:\/\/)?)(?:[-:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&%@.\w_]*)#?(?:[\w]*))?)$/ |
||||||
|
return reg.test(path) |
||||||
|
} |
||||||
|
|
||||||
|
export const isDark = (): boolean => { |
||||||
|
return window.matchMedia('(prefers-color-scheme: dark)').matches |
||||||
|
} |
||||||
|
|
||||||
|
// 是否是图片链接
|
||||||
|
export const isImgPath = (path: string): boolean => { |
||||||
|
return /(https?:\/\/|data:image\/).*?\.(png|jpg|jpeg|gif|svg|webp|ico)/gi.test(path) |
||||||
|
} |
||||||
|
|
||||||
|
export const isEmptyVal = (val: any): boolean => { |
||||||
|
return val === '' || val === null || val === undefined |
||||||
|
} |
||||||
@ -0,0 +1,174 @@ |
|||||||
|
import { v4 as uuidv4 } from 'uuid'; |
||||||
|
export type ManualAction = |
||||||
|
'init' | 'release' | 'write' | 'report' | 'lock' | 'unlock' | |
||||||
|
'export' | 'cancel' | 'import' | 'upgrade' |
||||||
|
|
||||||
|
export type ZmqStatus = 'disconnected' | 'connected' |
||||||
|
|
||||||
|
export enum WorkerCMD { |
||||||
|
START, |
||||||
|
SUBSCRIBE, |
||||||
|
UNSUBSCRIBE, |
||||||
|
PUBLISH, |
||||||
|
STOP, |
||||||
|
SET_TIMEOUT |
||||||
|
} |
||||||
|
|
||||||
|
export enum ZmqMsgResultType { |
||||||
|
SUCCESS = 200, |
||||||
|
PROGRESS = 1002, |
||||||
|
ERROR = 1003, |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
export enum ZmqCMD { |
||||||
|
MSG, |
||||||
|
JSON_MSG, |
||||||
|
STATUS, |
||||||
|
TIMEOUT |
||||||
|
} |
||||||
|
|
||||||
|
export interface TimeoutMsg { |
||||||
|
timeoutId: string |
||||||
|
timeoutTopic: string |
||||||
|
} |
||||||
|
export interface ZmqMessage { |
||||||
|
cmd: ZmqCMD |
||||||
|
msg: string |
||||||
|
community: boolean |
||||||
|
topic: string |
||||||
|
} |
||||||
|
type TopicType = 'event' | 'status' |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// web端发布消息类型
|
||||||
|
export interface PublishMsg<A> { |
||||||
|
id: string // 每条消息的唯一标识
|
||||||
|
action: A // 消息的动作
|
||||||
|
reply: 'yes' | 'no' // 是否需要回复
|
||||||
|
params: any // 消息的参数
|
||||||
|
} |
||||||
|
// web端发布消息服务端返回的数据类型
|
||||||
|
export interface PubMsgData { |
||||||
|
code: number |
||||||
|
feedback: any, |
||||||
|
id: string |
||||||
|
result: 'success' | 'progress' | 'failure' | 'refuse' | 'error' |
||||||
|
} |
||||||
|
|
||||||
|
// 订阅消息服务端返回的数据类型
|
||||||
|
export interface SubMsgData { |
||||||
|
id: string |
||||||
|
action: 'report' |
||||||
|
feedback: any, |
||||||
|
reply: 'yes' | 'no' // 是否需要回复
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* |
||||||
|
* @Description: 获取发布的主题 |
||||||
|
* type: 主题类型 |
||||||
|
* module: 模块名称 |
||||||
|
* userid: 用户唯一标识 |
||||||
|
*/ |
||||||
|
|
||||||
|
export function getPubTopic(type: TopicType, module: string,) { |
||||||
|
return `web/${type}/${module}/` |
||||||
|
} |
||||||
|
|
||||||
|
export function getSubTopic(server: string, type: TopicType, module: string,) { |
||||||
|
return `${server}/${type}/${module}/` |
||||||
|
} |
||||||
|
|
||||||
|
// 获取随机id
|
||||||
|
|
||||||
|
export function getRandomId() { |
||||||
|
const uniqueId = `${Date.now()}-${uuidv4()}`; |
||||||
|
return uniqueId |
||||||
|
} |
||||||
|
|
||||||
|
export function generatePubMsg<A>(massage: Omit<PublishMsg<A>, 'id'>): PublishMsg<A> { |
||||||
|
return { |
||||||
|
id: getRandomId(), |
||||||
|
action: massage.action, |
||||||
|
reply: massage.reply, |
||||||
|
params: massage.params |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export function getLockPubMsg(action: 'lock' | 'unlock'): PublishMsg<'lock' | 'unlock'> { |
||||||
|
return { |
||||||
|
id: getRandomId(), |
||||||
|
action: action, |
||||||
|
params: [], |
||||||
|
reply: 'yes' |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export function pollingWithCallback(intervalInSeconds: number, callback: Function) { |
||||||
|
let counter = 0 |
||||||
|
const fps = 60 |
||||||
|
|
||||||
|
function poll() { |
||||||
|
counter++ |
||||||
|
if (counter >= intervalInSeconds * fps) { |
||||||
|
callback() |
||||||
|
counter = 0 |
||||||
|
} |
||||||
|
requestAnimationFrame(poll) |
||||||
|
} |
||||||
|
|
||||||
|
poll() |
||||||
|
} |
||||||
|
|
||||||
|
export function getPubInitData<T extends ManualAction>(action: T, params: any, reply: 'yes' | 'no' = 'yes') { |
||||||
|
return generatePubMsg<T>({ |
||||||
|
action, |
||||||
|
reply, |
||||||
|
params |
||||||
|
}) |
||||||
|
} |
||||||
|
export function isHexadecimal(text: string) { |
||||||
|
const hexRegex = /^[0-9A-Fa-f]+$/ |
||||||
|
return hexRegex.test(text) |
||||||
|
} |
||||||
|
|
||||||
|
export function stringToHex(str: string) { |
||||||
|
return Array.from(str) |
||||||
|
.map(char => char.charCodeAt(0).toString(16).toUpperCase()) |
||||||
|
.join('') |
||||||
|
} |
||||||
|
|
||||||
|
export function stringToDecimalismNumbers(str: string): number[] { |
||||||
|
return Array.from(str).map(char => char.charCodeAt(0)) |
||||||
|
} |
||||||
|
|
||||||
|
export function hexToArray(num: string) { |
||||||
|
const numStr = num.toString() |
||||||
|
let paddedNumStr = numStr |
||||||
|
if (numStr.length % 2 !== 0) { |
||||||
|
paddedNumStr = numStr.slice(0, -1) + '0' + numStr.slice(-1) |
||||||
|
} |
||||||
|
|
||||||
|
const result = [] |
||||||
|
|
||||||
|
for (let i = 0; i < paddedNumStr.length; i += 2) { |
||||||
|
result.push(paddedNumStr.slice(i, i + 2)) |
||||||
|
} |
||||||
|
|
||||||
|
return result |
||||||
|
} |
||||||
|
|
||||||
|
export function hexToDecimal(hex: string[]) { |
||||||
|
return hex.map(item => parseInt(item, 16)) |
||||||
|
} |
||||||
|
|
||||||
|
export function decimalToHexArray(decimalArray: number[]) { |
||||||
|
return decimalArray.map(num => num.toString(16).toUpperCase()).join('') |
||||||
|
} |
||||||
|
|
||||||
|
export function decimalToString(arr: number[]) { |
||||||
|
return arr.map(num => String.fromCharCode(num)).join('') |
||||||
|
} |
||||||
@ -0,0 +1,166 @@ |
|||||||
|
import ZmqClient from '@/lib/zmq/zmqClient' |
||||||
|
import { type PublishMsg, type PubMsgData, WorkerCMD, ZmqCMD, } from './zmq' |
||||||
|
|
||||||
|
|
||||||
|
const HEARTBEAT_TOPIC = 'HEARTBEAT' |
||||||
|
const HEARTBEAT_INTERVAL = 3000 |
||||||
|
const STATUS_CHECK_INTERVAL = 1000 |
||||||
|
let messageTimeout = 20000 |
||||||
|
|
||||||
|
let heartClient: ZmqClient | null, subClient: ZmqClient | null, pubClient: ZmqClient | null |
||||||
|
let subHost = '', pubHost = '' |
||||||
|
let lastHeartbeatTime = 0 |
||||||
|
let statusTimerId: ReturnType<typeof setInterval> | null |
||||||
|
let isConnectionError = false |
||||||
|
|
||||||
|
function decodeMessage(data: Uint8Array) { |
||||||
|
return new TextDecoder().decode(data) |
||||||
|
} |
||||||
|
|
||||||
|
function updateHeartbeat() { |
||||||
|
lastHeartbeatTime = Date.now() |
||||||
|
} |
||||||
|
|
||||||
|
function changeConnectionStatus(hasError: boolean) { |
||||||
|
if (isConnectionError !== hasError) { |
||||||
|
isConnectionError = hasError |
||||||
|
console.log('ZMQ连接状态更新:', hasError ? '断开' : '连接') |
||||||
|
postMessage({ cmd: ZmqCMD.STATUS, community: hasError }) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function monitorConnection() { |
||||||
|
if (statusTimerId) return |
||||||
|
|
||||||
|
lastHeartbeatTime = Date.now() |
||||||
|
statusTimerId = setInterval(() => { |
||||||
|
const currentTime = Date.now() |
||||||
|
const hasError = currentTime - lastHeartbeatTime > HEARTBEAT_INTERVAL |
||||||
|
changeConnectionStatus(hasError) |
||||||
|
}, STATUS_CHECK_INTERVAL) |
||||||
|
} |
||||||
|
|
||||||
|
function stopMonitoringConnection() { |
||||||
|
if (statusTimerId) { |
||||||
|
clearInterval(statusTimerId) |
||||||
|
statusTimerId = null |
||||||
|
} |
||||||
|
changeConnectionStatus(false) |
||||||
|
} |
||||||
|
|
||||||
|
function disconnect() { |
||||||
|
if (subClient) { |
||||||
|
subClient.close(subHost, handleZmqMessage) |
||||||
|
subClient = null |
||||||
|
} |
||||||
|
|
||||||
|
if (heartClient) { |
||||||
|
heartClient.unsubscribe(HEARTBEAT_TOPIC) |
||||||
|
heartClient.close(subHost, updateHeartbeat) |
||||||
|
heartClient = null |
||||||
|
} |
||||||
|
|
||||||
|
if (pubClient) { |
||||||
|
pubClient.close(pubHost) |
||||||
|
pubClient = null |
||||||
|
} |
||||||
|
|
||||||
|
stopMonitoringConnection() |
||||||
|
} |
||||||
|
|
||||||
|
function handleZmqMessage(topic: Uint8Array, msg: Uint8Array) { |
||||||
|
try { |
||||||
|
if (msg instanceof Uint8Array) { |
||||||
|
const jsonMessage = decodeMessage(msg) as string |
||||||
|
postMessage({ |
||||||
|
topic: decodeMessage(topic), |
||||||
|
cmd: ZmqCMD.JSON_MSG, |
||||||
|
msg: jsonMessage |
||||||
|
}) |
||||||
|
const parsedMessage = JSON.parse(jsonMessage) as PubMsgData |
||||||
|
// traceMessages.get(parsedMessage.id)
|
||||||
|
const curTraceMessages = traceMessages.get(parsedMessage.id) |
||||||
|
if (parsedMessage.id && !!curTraceMessages) { |
||||||
|
if (curTraceMessages.isAlwaysListen || parsedMessage.result === 'progress') { |
||||||
|
// 重置消息超时时间
|
||||||
|
const val = traceMessages.get(parsedMessage.id) |
||||||
|
if (val) { |
||||||
|
val.timestamp = Date.now() |
||||||
|
} |
||||||
|
traceMessages.set(parsedMessage.id, val) |
||||||
|
} else { |
||||||
|
traceMessages.delete(parsedMessage.id) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} catch (e) { |
||||||
|
console.error('handleZmqMessage error:', e) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function connect(host: string) { |
||||||
|
disconnect() |
||||||
|
|
||||||
|
subHost = `ws://${host}:15555` |
||||||
|
subClient = new ZmqClient('sub') |
||||||
|
subClient.zmqSub(subHost, handleZmqMessage) |
||||||
|
|
||||||
|
heartClient = new ZmqClient('sub') |
||||||
|
heartClient.zmqSub(subHost, updateHeartbeat) |
||||||
|
heartClient.subscribe(HEARTBEAT_TOPIC) |
||||||
|
monitorConnection() |
||||||
|
|
||||||
|
pubHost = `ws://${host}:15556` |
||||||
|
pubClient = new ZmqClient('pub') |
||||||
|
pubClient.zmqPub(pubHost) |
||||||
|
setInterval(() => { |
||||||
|
const now = Date.now() |
||||||
|
traceMessages.forEach((val, id) => { |
||||||
|
if (now - val.timestamp > messageTimeout) { |
||||||
|
console.warn(`Message ${id} timed out.`) |
||||||
|
postMessage({ |
||||||
|
cmd: ZmqCMD.TIMEOUT, |
||||||
|
topic: val.topic.replace(/^web\//, 'server/'), |
||||||
|
msg: id |
||||||
|
}) |
||||||
|
traceMessages.delete(id) |
||||||
|
} |
||||||
|
}) |
||||||
|
}, 1000) |
||||||
|
} |
||||||
|
|
||||||
|
const traceMessages = new Map<string, any>() |
||||||
|
|
||||||
|
self.onmessage = function (event) { |
||||||
|
const { cmd, topic, msg, isTimeout = false, isAlwaysListen } = event.data |
||||||
|
|
||||||
|
switch (cmd) { |
||||||
|
case WorkerCMD.START: |
||||||
|
connect(msg) |
||||||
|
break |
||||||
|
case WorkerCMD.SET_TIMEOUT: |
||||||
|
messageTimeout = msg |
||||||
|
break |
||||||
|
case WorkerCMD.SUBSCRIBE: |
||||||
|
subClient?.subscribe(topic) |
||||||
|
break |
||||||
|
case WorkerCMD.UNSUBSCRIBE: |
||||||
|
subClient?.unsubscribe(topic) |
||||||
|
break |
||||||
|
case WorkerCMD.PUBLISH: |
||||||
|
if (!msg) throw new Error('msg is required') |
||||||
|
if (isTimeout) { |
||||||
|
const parseMsg = JSON.parse(msg) as PublishMsg<string> |
||||||
|
traceMessages.set(parseMsg.id, { |
||||||
|
timestamp: Date.now(), |
||||||
|
topic: topic, |
||||||
|
isAlwaysListen, |
||||||
|
}) |
||||||
|
} |
||||||
|
pubClient?.publishStr(topic, msg) |
||||||
|
break |
||||||
|
case WorkerCMD.STOP: |
||||||
|
disconnect() |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,19 @@ |
|||||||
|
<template> |
||||||
|
<EdfsWrap class="wh-full" :title="'工程列表'"> |
||||||
|
<template #title-right> |
||||||
|
<EdfsButton |
||||||
|
inner-text="新增工程" |
||||||
|
type="primary" |
||||||
|
size="small" |
||||||
|
plain |
||||||
|
@click="addEngineering" |
||||||
|
/> |
||||||
|
</template> |
||||||
|
</EdfsWrap> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup lang="ts"> |
||||||
|
import EdfsWrap from '@/components/Edfs-wrap.vue' |
||||||
|
|
||||||
|
function addEngineering() {} |
||||||
|
</script> |
||||||
@ -0,0 +1,189 @@ |
|||||||
|
<template> |
||||||
|
<div class="common-layout"> |
||||||
|
<el-container> |
||||||
|
<el-aside class="aside-wrap"> |
||||||
|
<RouterLink to="/" class="layout-logo" :class="{ 'layout-logo-collapse': isCollapse }"> |
||||||
|
<svg class="inline-block text-32px" width="1em" height="1em" viewBox="0 0 160 160" |
||||||
|
xmlns="http://www.w3.org/2000/svg"> |
||||||
|
<path |
||||||
|
d="M81.28 55.9c-.1-11.67-2.93-22.55-9.37-32.38-1-1.5-2.14-2.86-2.5-4.71a8.1 8.1 0 014-8.61 7.89 7.89 0 019.3 1.23 35.999 35.999 0 015.9 8.83 75.18 75.18 0 018.44 28.58 83.211 83.211 0 01-5.23 36.74 102.983 102.983 0 01-3 7.28 1.2 1.2 0 000 1.41c9.58 13.3 21.76 23 37.85 27.24a54.37 54.37 0 0019.68 1.57 7.72 7.72 0 018.36 6.9 7.903 7.903 0 01-6.7 9 64.744 64.744 0 01-23-1.33 77.68 77.68 0 01-36.93-19.88 93.628 93.628 0 01-11.91-13.71 2.18 2.18 0 00-2.3-1.06 72.744 72.744 0 00-27.38 7.55c-11.6 6-20.67 14.58-26.4 26.45a10.134 10.134 0 01-3.7 4.7 8 8 0 01-9.19-.7 7.86 7.86 0 01-2.36-9.28 60.324 60.324 0 018.72-14.52c12.2-15.43 28.21-24.59 47.32-28.57A85.085 85.085 0 0173.07 87c.524.015 1-.307 1.18-.8a76.06 76.06 0 006.53-22.3c.351-2.652.518-5.325.5-8z" |
||||||
|
fill="currentColor"></path> |
||||||
|
<path |
||||||
|
d="M136.26 108.34a44.742 44.742 0 01-11.13-2.87 46.108 46.108 0 01-19.66-13.76 8 8 0 015.72-13.22 7.93 7.93 0 016.54 2.93 33.27 33.27 0 0018.87 10.75c1.546.155 3.058.553 4.48 1.18a8.08 8.08 0 013.84 9.21c-.92 3.52-4.13 5.81-8.66 5.78zm-80.6-75.02a7.61 7.61 0 016.64 5 49.139 49.139 0 013.64 17 46.33 46.33 0 01-2.46 17.28c-2 5.77-8.24 7.79-12.89 4.15a8.1 8.1 0 01-2.39-9 31.679 31.679 0 001.68-12.36 35.77 35.77 0 00-2.43-11c-2.1-5.45 1.75-11.07 8.21-11.07zm22.26 93.25a8 8 0 01-6.68 7.86 32.88 32.88 0 00-19.7 12.19 8.13 8.13 0 01-11.21 1.62 8 8 0 01-1.41-11.58A51.043 51.043 0 0154 123.81a45.842 45.842 0 0114-5.1c5.35-1.04 9.91 2.56 9.92 7.86z" |
||||||
|
fill="currentColor"></path> |
||||||
|
</svg> |
||||||
|
<h2 class="pl-8px text-16px font-bold" v-show="!isCollapse"> |
||||||
|
EPM-工程管理平台 |
||||||
|
</h2> |
||||||
|
</RouterLink> |
||||||
|
<el-menu class="layout-menu" :default-active="activeMenu" @select="menuSelect" router |
||||||
|
:collapse="isCollapse"> |
||||||
|
<template v-for="router in menuList"> |
||||||
|
<template v-if="router.meta?.isShow"> |
||||||
|
<el-sub-menu v-if="router?.children?.filter((item: any) => item.meta?.isShow).length" |
||||||
|
:index="router.path" |
||||||
|
:key="router.path"> |
||||||
|
<template #title> |
||||||
|
<div :class="router.meta.icon" class="menu-icon"></div> |
||||||
|
<span>{{ router.meta.title }}</span> |
||||||
|
</template> |
||||||
|
<template v-for="child in router?.children"> |
||||||
|
<el-menu-item v-if="child?.meta?.isShow" :key="child.path" |
||||||
|
:index="`${router.path}/${child.path}`"> |
||||||
|
<div :class="child.meta.icon" class="menu-icon"></div> |
||||||
|
<span>{{ child.meta.title }}</span> |
||||||
|
</el-menu-item> |
||||||
|
</template> |
||||||
|
</el-sub-menu> |
||||||
|
<el-menu-item v-else :index="router.path" :key="router?.path"> |
||||||
|
<div :class="router.meta.icon" class="menu-icon"></div> |
||||||
|
<span>{{ router.meta.title }}</span> |
||||||
|
</el-menu-item> |
||||||
|
</template> |
||||||
|
</template> |
||||||
|
</el-menu> |
||||||
|
</el-aside> |
||||||
|
<el-container> |
||||||
|
<el-header> |
||||||
|
<div class="flex items-center gap-col-2"> |
||||||
|
<el-button class="collapes-btn" @click="isCollapse = !isCollapse"> |
||||||
|
<div :class="isCollapse ? unfold : fold"></div> |
||||||
|
</el-button> |
||||||
|
<div> |
||||||
|
{{ currentTime }} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</el-header> |
||||||
|
<main class="main-wrap"> |
||||||
|
<RouterView/> |
||||||
|
</main> |
||||||
|
</el-container> |
||||||
|
</el-container> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup lang="ts"> |
||||||
|
import { useTheme } from '@/composables/useTheme' |
||||||
|
import { defaultRouter } from '@/router' |
||||||
|
import dayjs from 'dayjs' |
||||||
|
|
||||||
|
const unfold = 'i-icon-park-outline:menu-unfold' |
||||||
|
const fold = 'i-icon-park-outline:menu-fold' |
||||||
|
|
||||||
|
const { theme } = useTheme() |
||||||
|
|
||||||
|
const menuList = computed<any[]>(() => { |
||||||
|
let data = defaultRouter[0].children |
||||||
|
return data |
||||||
|
} |
||||||
|
) |
||||||
|
|
||||||
|
const circleUrl = ref( |
||||||
|
'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png' |
||||||
|
) |
||||||
|
const isCollapse = ref(false) |
||||||
|
|
||||||
|
const getIconClass = (icon: string) => { |
||||||
|
return icon |
||||||
|
} |
||||||
|
|
||||||
|
const { push, currentRoute } = useRouter() |
||||||
|
const activeMenu = computed(() => { |
||||||
|
const { meta, path } = unref(currentRoute) |
||||||
|
|
||||||
|
return path |
||||||
|
}) |
||||||
|
|
||||||
|
function menuSelect(path: string) { |
||||||
|
push(path) |
||||||
|
} |
||||||
|
|
||||||
|
const currentTime = ref('123') |
||||||
|
const updateTime = () => { |
||||||
|
currentTime.value = dayjs().format('YYYY-MM-DD HH:mm:ss') |
||||||
|
requestAnimationFrame(updateTime) |
||||||
|
} |
||||||
|
onMounted(() => { |
||||||
|
updateTime() |
||||||
|
}) |
||||||
|
</script> |
||||||
|
<style lang="scss" scoped> |
||||||
|
.common-layout { |
||||||
|
@apply w-full h-full bg-[#F2F3F5]; |
||||||
|
|
||||||
|
:deep(.el-container) { |
||||||
|
@apply w-full h-full; |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.el-header) { |
||||||
|
@apply h-56 w-full p0 flex items-center justify-between bg-white; |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.el-main) { |
||||||
|
@apply p0; |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.el-aside) { |
||||||
|
@apply w-auto; |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.el-menu) { |
||||||
|
@apply h-full; |
||||||
|
} |
||||||
|
|
||||||
|
.aside-wrap { |
||||||
|
@apply flex-col; |
||||||
|
border-radius: 4px; |
||||||
|
height: calc(100vh - 16px); |
||||||
|
} |
||||||
|
|
||||||
|
.layout-logo { |
||||||
|
@apply w-full flex-center nowrap-hidden h-56 bg-white; |
||||||
|
} |
||||||
|
|
||||||
|
.layout-menu { |
||||||
|
height: calc(100vh - 56px); |
||||||
|
@apply border-r-none; |
||||||
|
} |
||||||
|
|
||||||
|
.layout-menu:not(.el-menu--collapse) { |
||||||
|
width: 220px; |
||||||
|
height: calc(100vh - 56px); |
||||||
|
} |
||||||
|
|
||||||
|
.layout-logo { |
||||||
|
transition: width 0.3s ease-in-out; |
||||||
|
width: 220px; |
||||||
|
} |
||||||
|
|
||||||
|
.layout-logo-collapse { |
||||||
|
width: 64px; |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.el-header) { |
||||||
|
@apply p-x-12; |
||||||
|
} |
||||||
|
|
||||||
|
.collapes-btn { |
||||||
|
@apply border-none; |
||||||
|
|
||||||
|
&:hover { |
||||||
|
background-color: rgba(46, 51, 56, 0.09); |
||||||
|
color: rgb(51, 54, 57); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.avatar { |
||||||
|
@apply w-24 h-24; |
||||||
|
} |
||||||
|
|
||||||
|
.menu-icon { |
||||||
|
@apply m-r-8; |
||||||
|
} |
||||||
|
|
||||||
|
.main-wrap { |
||||||
|
height: calc(100vh - 56px); |
||||||
|
@apply p-16; |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,26 @@ |
|||||||
|
{ |
||||||
|
"extends": "@vue/tsconfig/tsconfig.dom.json", |
||||||
|
"include": [ |
||||||
|
"env.d.ts", |
||||||
|
"src/**/*", |
||||||
|
"src/**/*.ts", |
||||||
|
"src/**/*.vue", |
||||||
|
"src/**/**/*.vue", |
||||||
|
"src/**/**/*.ts", |
||||||
|
"global.types/**/*.d.ts", |
||||||
|
"*/*.d.ts", |
||||||
|
], |
||||||
|
"exclude": [ |
||||||
|
"src/**/__tests__/*" |
||||||
|
], |
||||||
|
"compilerOptions": { |
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", |
||||||
|
"esModuleInterop": true, |
||||||
|
"allowSyntheticDefaultImports": true, |
||||||
|
"paths": { |
||||||
|
"@/*": [ |
||||||
|
"./src/*" |
||||||
|
] |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,20 @@ |
|||||||
|
{ |
||||||
|
"files": [], |
||||||
|
"references": [ |
||||||
|
{ |
||||||
|
"path": "./tsconfig.node.json" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"path": "./tsconfig.app.json" |
||||||
|
} |
||||||
|
], |
||||||
|
"compilerOptions": { |
||||||
|
"types": [ |
||||||
|
"node" |
||||||
|
], |
||||||
|
"moduleResolution": "node", |
||||||
|
"typeRoots": [ |
||||||
|
"node_modules/@types" |
||||||
|
] |
||||||
|
}, |
||||||
|
} |
||||||
@ -0,0 +1,21 @@ |
|||||||
|
{ |
||||||
|
"extends": "@tsconfig/node22/tsconfig.json", |
||||||
|
"include": [ |
||||||
|
"vite.config.*", |
||||||
|
"vitest.config.*", |
||||||
|
"cypress.config.*", |
||||||
|
"nightwatch.conf.*", |
||||||
|
"playwright.config.*", |
||||||
|
"eslint.config.*" |
||||||
|
], |
||||||
|
"compilerOptions": { |
||||||
|
"composite": true, // 启用复合项目 |
||||||
|
"noEmit": true, |
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", |
||||||
|
"module": "ESNext", |
||||||
|
"moduleResolution": "Bundler", |
||||||
|
"types": [ |
||||||
|
"node" |
||||||
|
] |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1 @@ |
|||||||
|
{"root":["./src/app.vue","./src/main.ts","./src/api/keys.ts","./src/api/basic/errorcode.ts","./src/api/basic/httptypes.ts","./src/api/basic/utils.ts","./src/api/module/index.ts","./src/api/module/firmware/index.ts","./src/api/module/taks/index.ts","./src/api/module/transfer/index.ts","./src/api/server/axiosinstance.ts","./src/api/server/config.ts","./src/components/edfs-input.vue","./src/components/edfs-button.vue","./src/components/edfs-dialog.vue","./src/components/edfs-number-input.vue","./src/components/edfs-wrap.vue","./src/components/edfs-table/defaults.ts","./src/components/edfs-table/index.vue","./src/composables/usemessage.ts","./src/composables/usetheme.ts","./src/composables/usezmqjsonworker.ts","./src/hooks/useg6/index.ts","./src/hooks/useg6/useg6.d.ts","./src/hooks/useg6/utils/generategraphdata.ts","./src/hooks/useg6/utils/mylineedge.ts","./src/hooks/useg6/utils/index.ts","./src/lib/zmq/zmqclient.d.ts","./src/router/index.ts","./src/stores/transferdata.ts","./src/types/device.ts","./src/uno-preset/src/index.ts","./src/utils/is.ts","./src/utils/zmq.ts","./src/utils/zmqjsonworker.ts","./src/views/engineering/index.vue","./src/views/layout/index.vue"],"errors":true,"version":"5.7.3"} |
||||||
@ -0,0 +1,43 @@ |
|||||||
|
"use strict"; |
||||||
|
Object.defineProperty(exports, "__esModule", { value: true }); |
||||||
|
var preset_rem_to_px_1 = require("@unocss/preset-rem-to-px"); |
||||||
|
var unocss_preset_scalpel_1 = require("unocss-preset-scalpel"); |
||||||
|
var index_1 = require("./src/uno-preset/src/index"); |
||||||
|
var unocss_1 = require("unocss"); |
||||||
|
exports.default = (0, unocss_1.defineConfig)({ |
||||||
|
shortcuts: [], |
||||||
|
theme: { |
||||||
|
colors: {}, |
||||||
|
}, |
||||||
|
content: { |
||||||
|
pipeline: { |
||||||
|
include: [ |
||||||
|
//参考:https://unocss.dev/guide/extracting#extracting-from-build-tools-pipeline
|
||||||
|
/\.(vue|svelte|[jt]sx|mdx?|astro|elm|php|phtml|html)($|\?)/, |
||||||
|
'src/**/*.{js,ts}', |
||||||
|
'src/router/index.ts', |
||||||
|
'src/views/home/utils/menuConfig.ts', |
||||||
|
], |
||||||
|
}, |
||||||
|
}, |
||||||
|
rules: [['wh-full', { width: '100%', height: '100%' }]], |
||||||
|
presets: [ |
||||||
|
(0, preset_rem_to_px_1.default)(), |
||||||
|
(0, unocss_preset_scalpel_1.presetScalpel)(), |
||||||
|
(0, unocss_1.presetWind3)(), |
||||||
|
(0, index_1.presetSoybeanAdmin)(), |
||||||
|
(0, unocss_1.presetAttributify)({ |
||||||
|
prefix: 'uno-', |
||||||
|
prefixedOnly: true, |
||||||
|
}), |
||||||
|
(0, unocss_1.presetIcons)({ |
||||||
|
scale: 1.2, |
||||||
|
warn: true, |
||||||
|
}), |
||||||
|
(0, unocss_1.presetTypography)(), |
||||||
|
(0, unocss_1.presetWebFonts)({ |
||||||
|
fonts: {}, |
||||||
|
}), |
||||||
|
], |
||||||
|
transformers: [(0, unocss_1.transformerDirectives)(), (0, unocss_1.transformerVariantGroup)()], |
||||||
|
}); |
||||||
@ -0,0 +1,51 @@ |
|||||||
|
import presetRemToPx from '@unocss/preset-rem-to-px' |
||||||
|
import { presetScalpel } from 'unocss-preset-scalpel' |
||||||
|
import { presetSoybeanAdmin } from './src/uno-preset/src/index' |
||||||
|
import { |
||||||
|
defineConfig, |
||||||
|
presetAttributify, |
||||||
|
presetIcons, |
||||||
|
presetTypography, |
||||||
|
presetWind3, |
||||||
|
presetWebFonts, |
||||||
|
transformerDirectives, |
||||||
|
transformerVariantGroup, |
||||||
|
} from 'unocss' |
||||||
|
|
||||||
|
export default defineConfig({ |
||||||
|
shortcuts: [], |
||||||
|
theme: { |
||||||
|
colors: {}, |
||||||
|
}, |
||||||
|
content: { |
||||||
|
pipeline: { |
||||||
|
include: [ |
||||||
|
//参考:https://unocss.dev/guide/extracting#extracting-from-build-tools-pipeline
|
||||||
|
/\.(vue|svelte|[jt]sx|mdx?|astro|elm|php|phtml|html)($|\?)/, |
||||||
|
'src/**/*.{js,ts}', |
||||||
|
'src/router/index.ts', |
||||||
|
'src/views/home/utils/menuConfig.ts', |
||||||
|
], |
||||||
|
}, |
||||||
|
}, |
||||||
|
rules: [['wh-full', { width: '100%', height: '100%' }]], |
||||||
|
presets: [ |
||||||
|
presetRemToPx(), |
||||||
|
presetScalpel(), |
||||||
|
presetWind3(), |
||||||
|
presetSoybeanAdmin(), |
||||||
|
presetAttributify({ |
||||||
|
prefix: 'uno-', |
||||||
|
prefixedOnly: true, |
||||||
|
}), |
||||||
|
presetIcons({ |
||||||
|
scale: 1.2, |
||||||
|
warn: true, |
||||||
|
}), |
||||||
|
presetTypography(), |
||||||
|
presetWebFonts({ |
||||||
|
fonts: {}, |
||||||
|
}), |
||||||
|
], |
||||||
|
transformers: [transformerDirectives(), transformerVariantGroup()], |
||||||
|
}) |
||||||
@ -0,0 +1,82 @@ |
|||||||
|
import { fileURLToPath, URL } from 'node:url' |
||||||
|
import UnoCSS from 'unocss/vite' |
||||||
|
import { defineConfig, loadEnv } from 'vite' |
||||||
|
import vue from '@vitejs/plugin-vue' |
||||||
|
import vueDevTools from 'vite-plugin-vue-devtools' |
||||||
|
import AutoImport from 'unplugin-auto-import/vite' |
||||||
|
import Components from 'unplugin-vue-components/vite' |
||||||
|
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' |
||||||
|
import vueJsx from '@vitejs/plugin-vue-jsx' |
||||||
|
import Icons from 'unplugin-icons/vite' |
||||||
|
// Vite 配置文件
|
||||||
|
export default defineConfig(({ mode }) => { |
||||||
|
const env = loadEnv(mode, process.cwd()) |
||||||
|
console.log(env) |
||||||
|
console.log(env.PROD) |
||||||
|
return { |
||||||
|
plugins: [ |
||||||
|
vue(), |
||||||
|
vueJsx(), |
||||||
|
vueDevTools(), |
||||||
|
UnoCSS(), |
||||||
|
Icons({ |
||||||
|
autoInstall: true, |
||||||
|
}), |
||||||
|
AutoImport({ |
||||||
|
imports: ['vue', 'vue-router'], |
||||||
|
resolvers: [ElementPlusResolver()], |
||||||
|
dts: 'global.types/auto-imports.d.ts', |
||||||
|
}), |
||||||
|
Components({ |
||||||
|
dirs: ['src/components'], |
||||||
|
extensions: ['vue'], |
||||||
|
dts: 'global.types/components.d.ts', |
||||||
|
resolvers: [ElementPlusResolver()], |
||||||
|
}), |
||||||
|
], |
||||||
|
resolve: { |
||||||
|
alias: { |
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url)), |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
build: { |
||||||
|
outDir: env.VITE_APP_ENV === 'cloud' ? 'dist-cloud' : 'dist-local', |
||||||
|
minify: 'terser', |
||||||
|
terserOptions: { |
||||||
|
compress: { |
||||||
|
drop_console: true, |
||||||
|
drop_debugger: true, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
css: { |
||||||
|
preprocessorOptions: { |
||||||
|
scss: { |
||||||
|
additionalData: '@use "@/assets/styles/mixins.scss" as *;', |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
define: { |
||||||
|
'process.env': {} |
||||||
|
}, |
||||||
|
|
||||||
|
// 开发服务器配置
|
||||||
|
server: { |
||||||
|
// 启动时自动打开浏览器
|
||||||
|
// 开发服务器端口
|
||||||
|
port: 3000, |
||||||
|
// 允许局域网访问
|
||||||
|
host: '0.0.0.0', |
||||||
|
proxy: { |
||||||
|
'/remoteServer': { |
||||||
|
target: env.VITE_BASE_URL, |
||||||
|
changeOrigin: true, |
||||||
|
secure: false, |
||||||
|
ws: true, |
||||||
|
rewrite: path => path.replace(/^\/remoteServer/, ''), |
||||||
|
}, |
||||||
|
} |
||||||
|
}, |
||||||
|
} |
||||||
|
}) |
||||||