home.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. <template>
  2. <div class="home-page">
  3. <el-header>
  4. <myheader />
  5. </el-header>
  6. <el-main>
  7. <div class="home-page-container">
  8. <div class="home-page-header">
  9. <HeaderTabs @button-click="handleButtonClick" />
  10. </div>
  11. <div class="home-page-content">
  12. <router-view :key="activeProjectId" @save="handleSave" @copy="handleCopy" @cut="handleCut" @paste="handlePaste"/>
  13. </div>
  14. <!-- 底部项目标签栏 -->
  15. <div class="project-tabs">
  16. <el-tabs
  17. v-model="activeProjectId"
  18. type="card"
  19. closable
  20. class="project-tabs-bar"
  21. @tab-click="handleProjectTabClick"
  22. @tab-remove="handleProjectTabRemove"
  23. >
  24. <el-tab-pane
  25. v-for="project in projectStore.projects"
  26. :key="project.projectId"
  27. :name="project.projectId"
  28. >
  29. <template #label>
  30. <span class="project-tab-label">
  31. <img :src="modelIcon" alt="Project Icon" class="tab-icon" />
  32. {{ project.projectName }}
  33. </span>
  34. </template>
  35. </el-tab-pane>
  36. </el-tabs>
  37. <el-button class="add-project-btn" type="text" @click="addProjectdialog = true">
  38. <el-icon><Plus /></el-icon>
  39. </el-button>
  40. </div>
  41. </div>
  42. </el-main>
  43. </div>
  44. <el-dialog
  45. v-model="addProjectdialog"
  46. align-center
  47. :append-to-body="true"
  48. width="500"
  49. class="dialog_class"
  50. draggable
  51. >
  52. <template #header="{ titleId, titleClass }">
  53. <div class="my-header">
  54. <!-- <el-image :src="icon" fit="contain"></el-image> -->
  55. <h4 :id="titleId" :class="titleClass">{{ $t("dialog.new") }}</h4>
  56. </div>
  57. </template>
  58. <el-form
  59. :model="newproject"
  60. :rules="rules"
  61. ref="projectForm"
  62. :label-width="labelWidth"
  63. >
  64. <el-row :gutter="20">
  65. <el-col :span="24">
  66. <el-form-item :label="`${$t('project.name')}:`" prop="name">
  67. <el-input
  68. v-model="newproject.name"
  69. class="w-50 m-2"
  70. :placeholder="$t('project.inputProjectName')"
  71. maxlength="100"
  72. />
  73. </el-form-item>
  74. </el-col>
  75. <el-col :span="24">
  76. <el-form-item :label="`${$t('project.keywords')}:`" prop="keywords">
  77. <el-input
  78. v-model="newproject.keywords"
  79. class="w-50 m-2"
  80. :placeholder="$t('project.inputProjectKeywords')"
  81. maxlength="100"
  82. />
  83. </el-form-item>
  84. </el-col>
  85. <el-col :span="24">
  86. <el-form-item :label="`${$t('project.description')}:`" prop="description">
  87. <el-input
  88. v-model="newproject.description"
  89. class="w-50 m-2"
  90. :placeholder="$t('project.inputProjectDescription')"
  91. maxlength="500"
  92. />
  93. </el-form-item>
  94. </el-col>
  95. </el-row>
  96. </el-form>
  97. <template #footer>
  98. <span class="lastbtn">
  99. <el-button @click="addProjectdialog = false">{{
  100. $t("dialog.cancel")
  101. }}</el-button>
  102. <el-button type="primary" @click="submitForm">
  103. {{ $t("dialog.ok") }}
  104. </el-button>
  105. </span>
  106. </template>
  107. </el-dialog>
  108. <ProjectListDialog
  109. v-model:visible="dialog.projectListDialog"
  110. identify="importProject"
  111. @confirm="handleProjectConfirm"
  112. @refresh-flow="handleRefreshFlow"
  113. />
  114. </template>
  115. <script setup>
  116. import myheader from '@/components/layout/header.vue';
  117. import HeaderTabs from './HeaderButtonBar.vue';
  118. import { RouterView, useRouter, useRoute } from 'vue-router';
  119. import { ElMessage, ElIcon } from 'element-plus';
  120. import { Plus } from '@element-plus/icons-vue';
  121. import { useProjectStore } from '@/store/project';
  122. import { useI18n } from 'vue-i18n';
  123. import modelIcon from '@/assets/icons/model.png';
  124. import { ref, computed, watch, onMounted, nextTick } from 'vue';
  125. import { request } from "@/utils/request"
  126. import emitter from "@/utils/emitter"
  127. import FileUploadDialog from '@/components/dialog/FileUploadDialog.vue';
  128. import ProjectListDialog from "@/components/dialog/ProjectListDialog.vue";
  129. const { t, locale } = useI18n()
  130. const router = useRouter();
  131. const route = useRoute();
  132. const projectStore = useProjectStore();
  133. const addButtonLeft = ref(70); // 默认位置
  134. // 根据当前路由路径设置 activeTab
  135. const activeTab = ref(route.path.slice(1) || 'model');
  136. const activeProjectId = computed(() => projectStore.activeProjectId);
  137. // 控制添加项目对话框显示
  138. let addProjectdialog = ref(false);
  139. // 表单引用
  140. const projectForm = ref(null);
  141. // 控制 FileSelector 对话框显示
  142. const showFileSelector = ref(false);
  143. // 添加当前操作标识
  144. const currentIdentify = ref("openProject")
  145. let newproject = ref({
  146. name: "",
  147. description: "",
  148. keywords: ""
  149. })
  150. // 弹窗开关
  151. let dialog = ref({
  152. projectListDialog: false
  153. })
  154. const labelWidth = computed(() => {
  155. return locale.value === "zh-CN" ? "80px" : "120px"
  156. })
  157. // 监听路由变化,同步 activeTab
  158. watch(
  159. () => route.path,
  160. (newPath) => {
  161. const tabName = newPath.slice(1);
  162. if (tabName) {
  163. activeTab.value = tabName;
  164. }
  165. }
  166. );
  167. // 监听 activeTab 变化,导航到对应路由
  168. watch(activeTab, (newTab) => {
  169. if (newTab && route.path !== `/${newTab}`) {
  170. router.push(`/${newTab}`);
  171. }
  172. });
  173. // 更新 + 按钮位置
  174. const updateAddButtonPosition = () => {
  175. nextTick(() => {
  176. const navWrap = document.querySelector('.project-tabs-bar .el-tabs__nav-wrap');
  177. const nav = document.querySelector('.project-tabs-bar .el-tabs__nav');
  178. if (nav && navWrap) {
  179. const tabs = nav.querySelectorAll('.el-tabs__item');
  180. const lastTab = tabs[tabs.length - 1];
  181. if (lastTab) {
  182. const rect = lastTab.getBoundingClientRect();
  183. const navWrapRect = navWrap.getBoundingClientRect();
  184. addButtonLeft.value = rect.right - navWrapRect.left + 40;
  185. } else {
  186. addButtonLeft.value = 70; // 默认位置
  187. }
  188. } else {
  189. addButtonLeft.value = 70; // 默认位置
  190. }
  191. });
  192. };
  193. // 监听 projects 和 activeProjectId 变化,更新按钮位置
  194. watch(() => projectStore.projects.length, updateAddButtonPosition);
  195. watch(() => projectStore.activeProjectId, updateAddButtonPosition);
  196. onMounted(() => {
  197. updateAddButtonPosition();
  198. });
  199. // 提交表单
  200. const submitForm = () => {
  201. projectForm.value.validate((valid) => {
  202. if (valid) {
  203. addProject()
  204. } else {
  205. return false
  206. }
  207. })
  208. }
  209. // 表单校验规则
  210. const rules = ref({
  211. name: [
  212. { required: true, message: t('project.inputProjectName'), trigger: 'blur' }
  213. ],
  214. keywords: [
  215. { required: false, message: t('project.inputProjectKeywords'), trigger: 'blur' }
  216. ],
  217. description: [
  218. { required: false, message: t('project.inputProjectDescription'), trigger: 'blur' }
  219. ]
  220. })
  221. const handleButtonClick = (action) => {
  222. switch (action) {
  223. case 'copy':
  224. emitter.emit('copy');
  225. break;
  226. case 'cut':
  227. emitter.emit('cut');
  228. break;
  229. case 'paste':
  230. emitter.emit('paste');
  231. break;
  232. case 'save':
  233. emitter.emit('save');
  234. break;
  235. case 'importProject':
  236. dialog.value.projectListDialog = true;
  237. // const newProject = {
  238. // projectId: `proj-${Date.now()}`,
  239. // projectName: `Project ${projectStore.projects.length + 1}`,
  240. // };
  241. // projectStore.addProject(newProject);
  242. // ElMessage.success(t('message.importSuccess'));
  243. // nextTick(() => updateAddButtonPosition());
  244. break;
  245. default:
  246. console.log(`Button clicked: ${action}`);
  247. break;
  248. }
  249. };
  250. // 处理项目选择确认
  251. const handleProjectConfirm = (project) => {
  252. router.push({ path: "/home" });
  253. dialog.value.projectListDialog = false;
  254. };
  255. const handleProjectTabClick = (tab) => {
  256. projectStore.setActiveProject(tab.props.name);
  257. };
  258. const handleProjectTabRemove = (projectId) => {
  259. projectStore.removeProject(projectId);
  260. nextTick(() => {
  261. updateAddButtonPosition(); // 手动触发位置更新
  262. });
  263. };
  264. // 处理文件选择
  265. const handleFileSelected = (file) => {
  266. const formData = new FormData();
  267. formData.append('file', file);
  268. formData.append('transCode', 'ES0029');
  269. request(formData, {
  270. headers: {
  271. 'Content-Type': 'multipart/form-data',
  272. },
  273. })
  274. .then((res) => {
  275. // 假设后端返回项目信息
  276. const newProject = {
  277. projectId: res.pid, // 使用后端返回的 pid
  278. projectName: res.name || `Imported Project ${Date.now()}`,
  279. keywords: res.keywords || '',
  280. remark: res.remark || '',
  281. flow: res.flow || '{"nodes":[],"edges":[]}',
  282. };
  283. projectStore.addProject(newProject);
  284. projectStore.setActiveProject(newProject.projectId);
  285. ElMessage.success(t('message.importSuccess'));
  286. nextTick(() => updateAddButtonPosition());
  287. })
  288. .catch((err) => {
  289. ElMessage.error(err.returnMsg || t('message.importFailed'));
  290. });
  291. };
  292. // 添加项目
  293. const addProject = () => {
  294. const params = {
  295. transCode: "ES0002",
  296. name: newproject.value.name,
  297. remark: newproject.value.description,
  298. keywords: newproject.value.keywords
  299. }
  300. request(params)
  301. .then((res) => {
  302. projectStore.addProject({
  303. projectId: res.pid, // 使用后端返回的 pid
  304. projectName: params.name,
  305. keywords: params.keywords,
  306. remark: params.remark,
  307. flow: '{"nodes":[],"edges":[]}',
  308. });
  309. ElMessage.success("添加成功")
  310. addProjectdialog.value = false
  311. // 设置激活项目
  312. projectStore.setActiveProject(res.pid);
  313. })
  314. .catch((err) => {
  315. ElMessage.error(err.returnMsg)
  316. })
  317. }
  318. const handleSave = () => {
  319. emitter.emit('save');
  320. };
  321. const handleCopy = () => {
  322. emitter.emit('copy');
  323. };
  324. const handleCut = () => {
  325. emitter.emit('cut');
  326. };
  327. const handlePaste = () => {
  328. emitter.emit('paste');
  329. };
  330. </script>
  331. <style scoped>
  332. .el-header {
  333. background-color: #075679;
  334. margin: 0 2px 0 0 !important;
  335. }
  336. .home-page {
  337. width: 100%;
  338. height: 100vh;
  339. overflow: hidden;
  340. overflow: auto;
  341. }
  342. .home-page-container {
  343. width: 100%;
  344. height: 100%;
  345. overflow: hidden;
  346. overflow: auto;
  347. }
  348. .home-page-header {
  349. display: flex;
  350. flex-direction: column;
  351. justify-content: flex-start;
  352. align-items: flex-start;
  353. width: 100%;
  354. min-height: 80px;
  355. background: #ffffff;
  356. }
  357. .project-tabs {
  358. width: 100%;
  359. padding: 0 10px;
  360. background: #f5f7fa;
  361. border-top: 1px solid #e4e7ed;
  362. position: relative;
  363. }
  364. .home-page-content {
  365. display: flex;
  366. width: 100%;
  367. height: calc(100vh - 32px - 89px - 39px); /* 减去顶部和底部的高度 */
  368. }
  369. .add-project-btn {
  370. position: absolute;
  371. top: 6px;
  372. left: v-bind('`${addButtonLeft}px`');
  373. color: #000000;
  374. font-size: 16px;
  375. padding: 0;
  376. height: 32px;
  377. line-height: 32px;
  378. }
  379. :deep(.el-header) {
  380. padding: 0;
  381. margin: 0;
  382. height: auto;
  383. }
  384. :deep(.el-main) {
  385. padding: 0;
  386. margin: 0;
  387. flex: 1;
  388. }
  389. :deep(.project-tabs-bar .el-tabs__content) {
  390. display: none; /* 仅隐藏 project-tabs-bar 的标签内容 */
  391. }
  392. :deep(.project-tabs-bar .el-tabs__header) {
  393. margin: 3px 0 1px;
  394. background: #f5f7fa;
  395. position: relative;
  396. }
  397. :deep(.project-tabs-bar .el-tabs__item) {
  398. color: #000000;
  399. background: #e3e3e5;
  400. border: 1px solid #c5c7ca;
  401. height: 32px;
  402. line-height: 32px;
  403. padding: 0 12px;
  404. display: flex;
  405. align-items: center;
  406. }
  407. :deep(.project-tabs-bar .el-tabs__item.is-active) {
  408. background: #ffffff;
  409. color: #000000;
  410. }
  411. :deep(.el-tabs--card>.el-tabs__header .el-tabs__item.is-active.is-closable){
  412. padding: 0 3px 0 0 !important;
  413. margin: 0 !important;
  414. }
  415. :deep(.project-tabs-bar .el-tabs__nav-wrap) {
  416. overflow-x: auto;
  417. }
  418. </style>