Bläddra i källkod

feat: 上下文菜单迁移完成

qinlaiqiao 3 år sedan
förälder
incheckning
44591bf075

+ 9 - 9
package.json

@@ -19,7 +19,7 @@
     "@sc/util": "^1.0.9",
     "animate.css": "^4.1.1",
     "animejs": "^3.2.1",
-    "ant-design-vue": "^2.2.8",
+    "ant-design-vue": "2.2.7",
     "echarts": "^5.2.2",
     "element-plus": "^1.1.0-beta.24",
     "koa-bodyparser": "^4.3.0",
@@ -37,17 +37,17 @@
     "@types/jest": "^26.0.23",
     "@types/koa-bodyparser": "^4.3.1",
     "@types/lodash": "^4.14.176",
-    "@typescript-eslint/eslint-plugin": "^5.3.0",
-    "@typescript-eslint/parser": "^5.3.0",
+    "@typescript-eslint/eslint-plugin": "^4.18.0",
+    "@typescript-eslint/parser": "^4.18.0",
     "@vitejs/plugin-vue": "^1.9.3",
-    "@vue/cli-plugin-eslint": "^4.5.15",
-    "@vue/eslint-config-airbnb": "^5.3.0",
-    "@vue/eslint-config-prettier": "^6.0.0",
-    "@vue/eslint-config-typescript": "^9.0.1",
+    "@vue/cli-plugin-eslint": "~4.5.0",
+    "@vue/eslint-config-airbnb": "^5.0.2",
+    "@vue/eslint-config-typescript": "^7.0.0",
     "autoprefixer": "^10.4.0",
-    "eslint": "^8.1.0",
+    "eslint": "^6.7.2",
+    "eslint-plugin-import": "^2.20.2",
     "eslint-plugin-prettier": "^4.0.0",
-    "eslint-plugin-vue": "^8.0.3",
+    "eslint-plugin-vue": "^7.0.0",
     "jest": "^26.6.3",
     "postcss": "^8.3.11",
     "sass": "^1.43.4",

+ 1 - 0
src/apis/controller/index.ts

@@ -6,6 +6,7 @@ function useKoaContext() {
 }
 
 export default async () => {
+  console.log('index方法被调用')
   return {
     message: 'Hello World',
     method: useKoaContext().method,

+ 15 - 15
src/components/context-menu/ContextMenu.vue

@@ -1,17 +1,17 @@
 <template>
   <div class="context-menu" @click="visible = false">
     <a-dropdown
-        :trigger="['contextmenu']"
-        :overlayClassName="`context-menu-overlay ${randomClass}`"
-        v-model:visible="visible"
-        ref="dropdownRef"
+      :trigger="['contextmenu']"
+      :overlayClassName="`context-menu-overlay ${randomClass}`"
+      v-model:visible="visible"
+      ref="dropdownRef"
     >
       <div class="trigger-area" ref="triggerAreaRef">
         <slot></slot>
       </div>
       <template #overlay v-if="!disabled">
         <a-menu>
-          <context-sub-menu @hide="visible = false" :menu-groups="menuGroups"/>
+          <context-sub-menu @hide="visible = false" :menu-groups="menuGroups" />
         </a-menu>
       </template>
     </a-dropdown>
@@ -19,10 +19,10 @@
 </template>
 
 <script lang="ts">
-import {defineComponent, ref, PropType, onMounted, watchEffect} from 'vue';
-import {Dropdown, Menu} from 'ant-design-vue';
-import {isFunction} from 'lodash';
-import {on, query} from 'utils/frontend/dom';
+import { defineComponent, ref, PropType, onMounted, watchEffect } from 'vue';
+import { Dropdown, Menu } from 'ant-design-vue';
+import { isFunction } from 'lodash';
+import { on, query } from '@/utils/frontend/dom';
 import ContextSubMenu from './ContextSubMenu.vue';
 
 /*
@@ -48,7 +48,7 @@ interface IWidget {
   percent?: boolean;
 }
 
-interface IMenuItem extends Comp.ContextMenu.IMenuItem {
+interface IMenuItem extends ContextMenu.IMenuItem {
   content: IWidget[];
   submenu?: IMenuItem[][];
 }
@@ -60,14 +60,14 @@ export type MenuGroup = IMenuItem[];
 
 export default defineComponent({
   name: 'ContextMenu',
-  components: {ContextSubMenu, ADropdown: Dropdown, AMenu: Menu},
+  components: { ContextSubMenu, ADropdown: Dropdown, AMenu: Menu },
   props: {
     // 菜单分组列表
     itemGroups: {
       type: Array as PropType<MenuGroup[]>,
       required: true,
     },
-    // 是否禁用上下文菜单(不显示)
+    // 是否禁用上下文菜单(不显示上下文菜单
     disabled: {
       type: Boolean,
       default: false,
@@ -93,7 +93,7 @@ export default defineComponent({
         if (inputReg.test(item)) {
           // 匹配 input 模式字符串
           const attrs = item.match(
-              /(\[type=(text|number)\])|(\[value=.*?\])|(\[min=.*?\])|(\[max=.*?\])|(\[step=.*?\])|(\[percent=.*?\])|(\[precision=.*?\])/g
+            /(\[type=(text|number)\])|(\[value=.*?\])|(\[min=.*?\])|(\[max=.*?\])|(\[step=.*?\])|(\[percent=.*?\])|(\[precision=.*?\])/g
           );
           const metaArr: Common.IStringIndex[] = [];
           if (attrs) {
@@ -104,9 +104,9 @@ export default defineComponent({
               });
             });
           }
-          let widget: IWidget = {component: 'input'};
+          let widget: IWidget = { component: 'input' };
           metaArr.forEach((meta: any) => {
-            widget = Object.assign(widget, {...meta});
+            widget = Object.assign(widget, { ...meta });
           });
           if (widget.type === 'number') {
             widget.min = widget.min ? parseInt(`${widget.min}`, 10) : 1;

+ 11 - 8
src/components/context-menu/ContextSubMenu.vue

@@ -5,7 +5,9 @@
       <template v-if="item.submenu">
         <a-sub-menu :disabled="item.disable" v-if="!item.hidden" :key="item.text">
           <template #title>
-            <span><iconfont :class="item.icon" />{{ item.text }}</span>
+            <span>
+              <iconfont :class="item.icon" />{{ item.text }}
+            </span>
           </template>
           <context-sub-menu @hide="$emit('hide')" :menu-groups="item.submenu" />
         </a-sub-menu>
@@ -17,13 +19,15 @@
             <iconfont :class="item.icon" />
             <span v-for="(widget, index) in item.content" :key="index">
               <!-- 普通文本 -->
-              <template v-if="widget.component === 'text'">
-                {{ widget.value }}
-              </template>
+              <template v-if="widget.component === 'text'">{{ widget.value }}</template>
               <!-- 文本输入框 -->
               <template v-else-if="widget.component === 'input' && widget.type === 'text'">
                 <span @click.stop>
-                  <a-input v-model:value="widget.value" size="small" @pressEnter="handleMenuItemClick(item)" />
+                  <a-input
+                    v-model:value="widget.value"
+                    size="small"
+                    @pressEnter="handleMenuItemClick(item)"
+                  />
                 </span>
               </template>
               <!-- 数字输入框 -->
@@ -54,6 +58,7 @@
 <script lang="ts">
 import { defineComponent, PropType } from 'vue';
 import { Menu, Input, InputNumber } from 'ant-design-vue';
+
 import { MenuItem, MenuGroup } from './ContextMenu.vue';
 
 export default defineComponent({
@@ -71,9 +76,7 @@ export default defineComponent({
       required: true,
     },
   },
-  emits: {
-    hide: null,
-  },
+  emits: ['hide'],
   setup(props, context) {
     // 点击某一个菜单项时调用其回调函数
     const handleMenuItemClick = (item: MenuItem) => {

+ 5 - 0
src/constants/subject.ts

@@ -0,0 +1,5 @@
+export enum SubjectTreeNodeType {
+    CONSTRUCTION = 'construction',
+    SINGLE = 'single',
+    UNIT = 'unit'
+}

+ 0 - 0
src/examples/.gitkeep


+ 15 - 0
src/examples/Examples.vue

@@ -0,0 +1,15 @@
+<script setup lang="ts">
+import { ref, reactive, onMounted } from "vue";
+import { Input as AInput, InputNumber as AInputNumber } from 'ant-design-vue';
+
+</script>
+
+<template>
+    <article class="examples-page">
+        <h1>哈哈哈</h1>
+        <a-input />
+    </article>
+</template>
+
+<style lang="scss" scoped>
+</style>

+ 6 - 1
src/router/index.ts

@@ -1,4 +1,4 @@
-import {createRouter, createWebHistory, RouteRecordRaw} from 'vue-router';
+import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
 
 const routes: Array<RouteRecordRaw> = [
     {
@@ -55,6 +55,11 @@ const routes: Array<RouteRecordRaw> = [
         name: 'Login',
         component: () => import('@/views/login/Login.vue'),
     },
+    {
+        path: '/examples',
+        name: 'Examples',
+        component: () => import('@/examples/Examples.vue')
+    }
 ];
 
 const router = createRouter({

+ 4 - 0
src/styles/_element_plus.scss

@@ -26,3 +26,7 @@
   }
 }
 
+.el-loading-mask {
+  transition: opacity 0.1s;
+}
+

+ 3 - 4
src/styles/_mixin.scss

@@ -4,7 +4,7 @@
   @return ($px/$rem) + rem;
 }
 
-@mixin dropdown-menu($width:auto, $min-width:auto, $submenu-min-width:auto) {
+@mixin dropdown-menu($width: auto, $min-width: auto, $submenu-min-width: auto) {
   z-index: 2050; /* 没有这行则 抽屉无法显示右键菜单 */
   width: $width;
   min-width: $min-width !important;
@@ -147,7 +147,6 @@
   }
 
   .ant-dropdown-menu-submenu {
-
     &-disabled {
       .ant-dropdown-menu-submenu-title {
         color: #c0c4cc !important;
@@ -217,8 +216,8 @@
           }
 
           &::after {
-            font-family: 'iconfont';
-            content: '\e605';
+            font-family: "iconfont";
+            content: "\e630";
             font-size: 18px;
           }
 

+ 1 - 0
src/styles/index.scss

@@ -1,3 +1,4 @@
+@import "ant-design-vue/dist/antd.min.css";
 @import "reset.scss";
 @import "main.scss";
 @import "element_plus";

+ 9 - 0
src/types/common.d.ts

@@ -0,0 +1,9 @@
+declare namespace Common {
+    interface IStringIndex {
+        [key: string]: any;
+    }
+
+    interface IRender {
+        Render: () => void;
+    }
+}

+ 19 - 0
src/types/components.d.ts

@@ -0,0 +1,19 @@
+declare namespace ContextMenu {
+    interface IContextMenuComponent {
+        // 隐藏
+        Hide: () => void;
+    }
+
+    // 菜单项的每一个 item
+    interface IMenuItem {
+        text: (() => string) | string;
+        icon?: string;
+        hidden?: (() => boolean) | boolean;
+        disable?: (() => Promise<boolean> | boolean) | boolean;
+        callback?: (inputVal?: number | string) => void;
+        submenu?: IMenuItem[][];
+    }
+
+    // 菜单项分组
+    type MenuGroup = IMenuItem[];
+}

+ 11 - 0
src/types/subject.d.ts

@@ -0,0 +1,11 @@
+import { SubjectTreeNodeType } from '@/constants/subject'
+
+declare namespace Subject {
+    interface ISubjectTreeNode {
+        id: string;
+        label: string;
+        icon?: string;
+        type: SubjectTreeNodeType;
+        children?: ISubjectTreeNode[];
+    }
+}

+ 20 - 47
src/views/login/Login.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import {onMounted, ref} from "vue";
+import { onMounted, ref } from "vue";
 import useRotateCanvas from "./scripts/rotateCanvas";
 import usePreloadImg from "./scripts/preloadImg";
 import useModuleItem from "./scripts/moduleItem";
@@ -20,22 +20,22 @@ onMounted(() => {
 });
 
 // 底部旋转 canvas
-const {canvasRef} = useRotateCanvas();
-const {moduleRef} = useModuleItem();
+const { canvasRef } = useRotateCanvas();
+const { moduleRef } = useModuleItem();
 </script>
 
 <template>
   <article class="login-page">
     <!-- 视频背景 -->
     <video
-        ref="loginVideoRef"
-        class="video-bg"
-        poster="@/assets/login-bg-poster.jpg"
-        autoplay
-        loop
-        muted
+      ref="loginVideoRef"
+      class="video-bg"
+      poster="@/assets/login-bg-poster.jpg"
+      autoplay
+      loop
+      muted
     >
-      <source src="@/assets/login-bg-video.mp4" type="video/mp4"/>
+      <source src="@/assets/login-bg-video.mp4" type="video/mp4" />
     </video>
 
     <!-- 标题 -->
@@ -52,13 +52,13 @@ const {moduleRef} = useModuleItem();
       <div class="panel">
         <!-- 用户名 -->
         <div class="input-wrap username">
-          <input class="input" type="text" placeholder="用户名"/>
-          <i class="line"/>
+          <input class="input" type="text" placeholder="用户名" />
+          <i class="line" />
         </div>
         <!-- 密码 -->
         <div class="input-wrap password">
-          <input class="input" type="password" placeholder="密码"/>
-          <i class="line"/>
+          <input class="input" type="password" placeholder="密码" />
+          <i class="line" />
         </div>
         <!-- 登录按钮 -->
         <div class="login-btn">
@@ -76,39 +76,12 @@ const {moduleRef} = useModuleItem();
       </div>
       <!-- 功能模块 -->
       <ul class="module" ref="moduleRef">
-        <li
-            class="item budget animate__animated animate__bounceIn"
-            ref="budgetRef"
-        >
-          预算审核模块
-        </li>
-        <li
-            class="item estimate animate__animated animate__bounceIn"
-            ref="estimateRef"
-        >
-          估/概算模块
-        </li>
-        <li class="item util animate__animated animate__bounceIn" ref="utilRef">
-          基础工具模块
-        </li>
-        <li
-            class="item settlement animate__animated animate__bounceIn"
-            ref="settlementRef"
-        >
-          结算审核模块
-        </li>
-        <li
-            class="item final animate__animated animate__bounceIn"
-            ref="finalRef"
-        >
-          决算审核模块
-        </li>
-        <li
-            class="item check animate__animated animate__bounceIn"
-            ref="checkRef"
-        >
-          检测功能模块
-        </li>
+        <li class="item budget animate__animated animate__bounceIn" ref="budgetRef">预算审核模块</li>
+        <li class="item estimate animate__animated animate__bounceIn" ref="estimateRef">估/概算模块</li>
+        <li class="item util animate__animated animate__bounceIn" ref="utilRef">基础工具模块</li>
+        <li class="item settlement animate__animated animate__bounceIn" ref="settlementRef">结算审核模块</li>
+        <li class="item final animate__animated animate__bounceIn" ref="finalRef">决算审核模块</li>
+        <li class="item check animate__animated animate__bounceIn" ref="checkRef">检测功能模块</li>
       </ul>
     </main>
   </article>

+ 25 - 17
src/views/main-frame/MainFrame.vue

@@ -1,6 +1,5 @@
 <script setup lang="ts">
-import {onMounted, ref} from "vue";
-import {useRoute} from 'vue-router'
+import { useRoute } from 'vue-router'
 
 const route = useRoute()
 </script>
@@ -8,9 +7,9 @@ const route = useRoute()
 <template>
   <article class="main-frame-page">
     <header class="header">
-      <img src="@/assets/logo.png" class="logo" alt="logo">
+      <img src="@/assets/logo.png" class="logo" alt="logo" />
       <span class="refresh" title="刷新">
-        <iconfont class="icon dsk-undo"/>
+        <iconfont class="icon dsk-undo" />
       </span>
       <ul class="breadcrumb">
         <li class="item">首页</li>
@@ -18,50 +17,59 @@ const route = useRoute()
         <li class="item">莲花路电力迁改项目</li>
       </ul>
       <span class="data-big-screen">
-        <iconfont class="icon dsk-workbench"/>
+        <iconfont class="icon dsk-workbench" />
       </span>
       <span class="message">
-        <iconfont class="icon dsk-remind"/>
+        <iconfont class="icon dsk-remind" />
       </span>
       <span class="help">
-        <iconfont class="icon dsk-help"/>
+        <iconfont class="icon dsk-help" />
       </span>
       <span class="avatar">
         <el-avatar :size="30">张三</el-avatar>
       </span>
       <span class="fullscreen">
-        <iconfont class="icon dsk-full-screen-one"/>
+        <iconfont class="icon dsk-full-screen-one" />
       </span>
     </header>
     <aside class="aside">
       <ul class="menu">
         <router-link custom to="/" v-slot="{ navigate, isExactActive }">
           <li class="item" @click="navigate" :class="{ 'active': isExactActive }">
-            <iconfont class="icon dsk-dashboard-one"/>
+            <iconfont class="icon dsk-dashboard-one" />
           </li>
         </router-link>
 
-        <router-link custom to="/project-list" v-slot="{ navigate, isExactActive }">
-          <li class="item" @click="navigate" :class="{ 'active': route.fullPath.startsWith('/project') }">
-            <iconfont class="icon dsk-city-one"/>
+        <router-link custom to="/project-list" v-slot="{ navigate }">
+          <li
+            class="item"
+            @click="navigate"
+            :class="{ 'active': route.fullPath.startsWith('/project') }"
+          >
+            <iconfont class="icon dsk-city-one" />
           </li>
         </router-link>
         <router-link custom to="/ration" v-slot="{ navigate, isExactActive }">
           <li class="item" @click="navigate" :class="{ 'active': isExactActive }">
-            <iconfont class="icon dsk-data"/>
+            <iconfont class="icon dsk-data" />
+          </li>
+        </router-link>
+        <router-link custom to="/login" v-slot="{ navigate, isExactActive }">
+          <li class="item" @click="navigate" :class="{ 'active': isExactActive }">
+            <iconfont class="icon dsk-me" />
           </li>
         </router-link>
         <li class="item disabled">
-          <iconfont class="icon dsk-more-app"/>
+          <iconfont class="icon dsk-more-app" />
         </li>
         <li class="item disabled">
-          <iconfont class="icon dsk-tool"/>
+          <iconfont class="icon dsk-tool" />
         </li>
         <li class="item disabled">
-          <iconfont class="icon dsk-doc-detail"/>
+          <iconfont class="icon dsk-doc-detail" />
         </li>
         <li class="item disabled">
-          <iconfont class="icon dsk-application-one"/>
+          <iconfont class="icon dsk-application-one" />
         </li>
       </ul>
     </aside>

+ 16 - 36
src/views/project-list/ProjectList.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import {onMounted, reactive, ref} from "vue";
+import { reactive, ref } from "vue";
 
 const filterForm = reactive({
   name: '',
@@ -73,37 +73,37 @@ const projectList = reactive(list);
         <el-form-item label="项目名称" class="name-item">
           <el-input v-model="filterForm.name" placeholder="请输入项目名称"></el-input>
         </el-form-item>
-        <el-form-item label="工程类型" class="type-item" :class="{inline : inlineMode}">
+        <el-form-item label="工程类型" class="type-item" :class="{ inline: inlineMode }">
           <el-select v-model="filterForm.type" placeholder="请选择工程类型">
             <el-option label="Zone one" value="shanghai"></el-option>
             <el-option label="Zone two" value="beijing"></el-option>
           </el-select>
         </el-form-item>
-        <el-form-item label="项目流程" class="process-item" :class="{inline : inlineMode}">
+        <el-form-item label="项目流程" class="process-item" :class="{ inline: inlineMode }">
           <el-select v-model="filterForm.process" placeholder="请选择项目流程">
             <el-option label="Zone one" value="shanghai"></el-option>
             <el-option label="Zone two" value="beijing"></el-option>
           </el-select>
         </el-form-item>
-        <br v-if="!inlineMode"/>
-        <el-form-item label="审核步骤" class="step-item" :class="{inline : inlineMode}">
+        <br v-if="!inlineMode" />
+        <el-form-item label="审核步骤" class="step-item" :class="{ inline: inlineMode }">
           <el-select v-model="filterForm.step" placeholder="请选择审核步骤">
             <el-option label="Zone one" value="shanghai"></el-option>
             <el-option label="Zone two" value="beijing"></el-option>
           </el-select>
         </el-form-item>
-        <el-form-item label="流程时间" class="time-item" :class="{inline : inlineMode}">
+        <el-form-item label="流程时间" class="time-item" :class="{ inline: inlineMode }">
           <el-select v-model="filterForm.time" placeholder="请选择">
             <el-option label="Zone one" value="shanghai"></el-option>
             <el-option label="Zone two" value="beijing"></el-option>
           </el-select>
         </el-form-item>
-        <el-form-item class="submit-item" :class="{inline : inlineMode}">
+        <el-form-item class="submit-item" :class="{ inline: inlineMode }">
           <el-button type="primary" @click="onSubmit">查询</el-button>
           <el-button plain @click="onSubmit">重置</el-button>
           <el-button type="text" @click="inlineMode = !inlineMode">
             展开
-            <iconfont :class="inlineMode ? 'dsk-down' : 'dsk-up'"/>
+            <iconfont :class="inlineMode ? 'dsk-down' : 'dsk-up'" />
           </el-button>
         </el-form-item>
       </el-form>
@@ -115,37 +115,17 @@ const projectList = reactive(list);
             <router-link to="/project" class="link">{{ scope.row.name }}</router-link>
           </template>
         </el-table-column>
-        <el-table-column prop="process" label="项目流程" :min-width="80"/>
-        <el-table-column prop="step" label="当前步骤" :min-width="140"/>
-        <el-table-column
-            prop="submit"
-            label="送审金额(元)"
-            align="center"
-            :min-width="115"
-        />
-        <el-table-column
-            prop="increase"
-            label="审增金额(元)"
-            align="center"
-            :min-width="115"
-        />
-        <el-table-column
-            prop="decrease"
-            label="审减金额(元)"
-            align="center"
-            :min-width="115"
-        />
-        <el-table-column
-            prop="offset"
-            label="品迭金额(元)"
-            align="center"
-            :min-width="115"
-        />
+        <el-table-column prop="process" label="项目流程" :min-width="80" />
+        <el-table-column prop="step" label="当前步骤" :min-width="140" />
+        <el-table-column prop="submit" label="送审金额(元)" align="center" :min-width="115" />
+        <el-table-column prop="increase" label="审增金额(元)" align="center" :min-width="115" />
+        <el-table-column prop="decrease" label="审减金额(元)" align="center" :min-width="115" />
+        <el-table-column prop="offset" label="品迭金额(元)" align="center" :min-width="115" />
         <template #empty>
-          <el-empty :image-size="120" description="暂无项目"/>
+          <el-empty :image-size="120" description="暂无项目" />
         </template>
       </el-table>
-      <el-pagination background layout="total, prev, pager, next" :total="50"/>
+      <el-pagination background layout="total, prev, pager, next" :total="50" />
     </main>
   </article>
 </template>

+ 111 - 39
src/views/project/summary/Summary.vue

@@ -1,65 +1,137 @@
 <script setup lang="ts">
-import {onMounted, reactive, ref} from "vue";
-import ProjectExplorer from "./components/project-explorer/ProjectExplorer.vue";
+import { ref, reactive, onMounted } from "vue";
+import SubjectTree from "./components/subject-tree/SubjectTree.vue";
 import ToolBar from "./components/tool-bar/ToolBar.vue";
 import CostTable from "./components/cost-table/CostTable.vue";
 import BottomTabs from "./components/bottom-tabs/BottomTabs.vue";
 import StdBill from "./components/std-bill/StdBill.vue";
+import { SubjectTreeNodeType } from '@/constants/subject'
+import { Subject } from "@/types/subject";
+import useSummaryLayout from './scripts/useSummaryLayout'
 
-const projectExplorerSize = ref(60)
-const mainContentSize = ref(300)
-const mainLeftSize = ref(150)
-const mainRightSize = ref(50)
+// 布局相关代码
+const {
+  projectExplorerSize,
+  mainContentSize,
+  mainLeftSize,
+  mainRightSize,
+  mainLeftTopSize,
+  mainLeftBottomSize,
+  handleProjectExplorerSize,
+  handleMainContentSize,
+  handleMainLeftSize,
+  handleMainRightSize,
+  handleMainLeftTopSize,
+  handleMainLeftBottomSize,
+} = useSummaryLayout()
 
-const mainLeftTopSize = ref(300)
-const mainLeftBottomSize = ref(150)
+const loading = ref(true)
 
-const handleProjectExplorerSize = (size: number) => {
-  projectExplorerSize.value = size
-}
-const handleMainContentSize = (size: number) => {
-  mainContentSize.value = size
-}
-const handleMainLeftSize = (size: number) => {
-  mainLeftSize.value = size
-}
-const handleMainRightSize = (size: number) => {
-  mainRightSize.value = size
-}
-const handleMainLeftTopSize = (size: number) => {
-  mainLeftTopSize.value = size
-}
-const handleMainLeftBottomSize = (size: number) => {
-  mainLeftBottomSize.value = size
-}
+// 侧边树结构数据
+const treeData = reactive<Subject.ISubjectTreeNode[]>([])
+
+onMounted(async () => {
+  setTimeout(() => {
+    treeData.push({
+      id: '1',
+      label: '市妇幼保健院新址配套市政道路',
+      icon: 'dsk-city-one',
+      type: SubjectTreeNodeType.CONSTRUCTION,
+      children: [
+        {
+          id: '1.1',
+          label: '单项工程1',
+          icon: 'dsk-home-two',
+          type: SubjectTreeNodeType.SINGLE,
+          children: [
+            {
+              id: '1.1.1',
+              label: '单位工程1.1',
+              type: SubjectTreeNodeType.UNIT,
+            },
+            {
+              id: '1.1.2',
+              label: '单位工程1.2',
+              type: SubjectTreeNodeType.UNIT,
+            },
+          ],
+        },
+        {
+          id: '1.2',
+          label: '单项工程2',
+          icon: 'dsk-home-two',
+          type: SubjectTreeNodeType.SINGLE,
+          children: [
+            {
+              id: '1.2.1',
+              label: '单位工程2.1',
+              type: SubjectTreeNodeType.UNIT,
+            },
+            {
+              id: '1.2.2',
+              label: '单位工程2.2',
+              type: SubjectTreeNodeType.UNIT,
+            },
+          ],
+        },
+      ],
+    })
+
+    loading.value = false
+  }, 1000);
+})
 </script>
 
 <template>
-  <article class="summary-page">
+  <article class="summary-page" v-loading="loading">
     <resizable-layout>
-      <resizable-layout-item :weight="projectExplorerSize" :min-width="200" @resize="handleProjectExplorerSize">
-        <!-- 左侧项目结构浏览器 -->
-        <project-explorer/>
+      <resizable-layout-item
+        :weight="projectExplorerSize"
+        :min-width="200"
+        @resize="handleProjectExplorerSize"
+      >
+        <!-- 左侧项目结构树 -->
+        <subject-tree :tree-data="treeData" />
       </resizable-layout-item>
-      <resizable-layout-item :weight="mainContentSize" :min-width="400" @resize="handleMainContentSize">
+      <resizable-layout-item
+        :weight="mainContentSize"
+        :min-width="400"
+        @resize="handleMainContentSize"
+      >
         <!-- 工具栏 -->
-        <tool-bar/>
+        <tool-bar />
         <resizable-layout style="height: calc(100% - 41px)">
-          <resizable-layout-item :weight="mainLeftSize" :min-width="150" @resize="handleMainLeftSize">
+          <resizable-layout-item
+            :weight="mainLeftSize"
+            :min-width="150"
+            @resize="handleMainLeftSize"
+          >
             <resizable-layout direction="vertical">
-              <resizable-layout-item :weight="mainLeftTopSize" :min-width="400" @resize="handleMainLeftTopSize">
+              <resizable-layout-item
+                :weight="mainLeftTopSize"
+                :min-width="400"
+                @resize="handleMainLeftTopSize"
+              >
                 <!-- 造价书表格 -->
-                <cost-table/>
+                <cost-table />
               </resizable-layout-item>
-              <resizable-layout-item :weight="mainLeftBottomSize" :min-width="400" @resize="handleMainLeftBottomSize">
+              <resizable-layout-item
+                :weight="mainLeftBottomSize"
+                :min-width="400"
+                @resize="handleMainLeftBottomSize"
+              >
                 <!-- 底部 tabs -->
-                <bottom-tabs/>
+                <bottom-tabs />
               </resizable-layout-item>
             </resizable-layout>
           </resizable-layout-item>
           <!-- 右侧布局:标准清单 -->
-          <resizable-layout-item :weight="mainRightSize" :min-width="150" @resize="handleMainRightSize">
-            <std-bill/>
+          <resizable-layout-item
+            :weight="mainRightSize"
+            :min-width="150"
+            @resize="handleMainRightSize"
+          >
+            <std-bill />
           </resizable-layout-item>
         </resizable-layout>
       </resizable-layout-item>

+ 0 - 139
src/views/project/summary/components/project-explorer/ProjectExplorer.vue

@@ -1,139 +0,0 @@
-<script setup lang="ts">
-import {onMounted, reactive, ref, PropType} from "vue";
-
-const props = defineProps({
-  data: {
-    type: Array as PropType<SubjectTreeNode[]>,
-    required: true,
-  },
-})
-// 上下文菜单设置
-const menuGroup: Comp.ContextMenu.MenuGroup[] = [
-  [
-    {
-      text: '新建单项工程',
-      icon: 'dsk-home-two-add',
-      disable() {
-        return false;
-      },
-      callback() {
-        console.log('新建单项工程')
-      },
-    },
-    {
-      text: '新建单位工程',
-      icon: 'dsk-file-addition-one',
-      disable() {
-        return false;
-      },
-      callback() {
-        console.log('新建单位工程')
-      },
-    },
-    {
-      text: '重命名',
-      icon: 'dsk-edit',
-      disable() {
-        return false;
-      },
-      callback() {
-        console.log('重命名')
-      },
-    },
-    {
-      text: '删除',
-      icon: 'dsk-delete-one',
-      disable() {
-        return false;
-      },
-      callback() {
-        console.log('删除')
-      },
-    },
-    {
-      text: '分享协作',
-      icon: 'dsk-peoples',
-      disable() {
-        return false;
-      },
-      callback() {
-        console.log('分享协作')
-      },
-    },
-  ],
-];
-
-const readOnly = ref(false)
-const loading = ref(false)
-
-const data = [
-  {
-    label: '市妇幼保健院新址配套市政道路',
-    icon: 'dsk-city-one',
-    children: [
-      {
-        label: '单项工程1',
-        icon: 'dsk-home-two',
-        children: [
-          {
-            label: '单位工程1.1',
-          },
-          {
-            label: '单位工程1.2',
-          },
-        ],
-      },
-      {
-        label: '单项工程2',
-        icon: 'dsk-home-two',
-        children: [
-          {
-            label: '单位工程2.1',
-          },
-          {
-            label: '单位工程2.2',
-          },
-        ],
-      },
-    ],
-  },
-]
-</script>
-
-<template>
-  <section class="project-explorer">
-    <ul class="tabs">
-      <li class="tab active">原始结构</li>
-      <li class="tab">整理结构</li>
-    </ul>
-    <div class="tree-wrap">
-      <context-menu :item-groups="menuGroup" ref="contextMenuRef" :readOnly="readOnly" auto-size>
-        <el-tree
-            class="subject-tree"
-            v-loading="loading"
-            icon-class="el-icon-arrow-right"
-            :data="data"
-            default-expand-all
-            :expand-on-click-node="false"
-            node-key="id"
-            highlight-current
-            :current-node-key="currentNodeKey"
-            @node-click="handleNodeClick"
-            @node-contextmenu="handleNodeContextmenu"
-            ref="treeRef"
-        >
-          <template #default="{ data }">
-            <span class="custom-tree-node">
-              <iconfont class="subject-icon" :class="data.icon"/>
-              <span class="text-wrap">
-                <i class="text" :title="data.label">{{ data.label }}</i>
-              </span>
-            </span>
-          </template>
-        </el-tree>
-      </context-menu>
-    </div>
-  </section>
-</template>
-
-<style lang="scss" src="./style.scss" scoped></style>

+ 225 - 0
src/views/project/summary/components/subject-tree/SubjectTree.vue

@@ -0,0 +1,225 @@
+<script setup lang="ts">
+import { Subject } from "@/types/subject";
+import { ref, PropType, toRaw, onMounted } from "vue";
+
+const props = defineProps({
+  treeData: {
+    type: Array as PropType<Subject.ISubjectTreeNode[]>,
+    required: true,
+  },
+})
+
+// 上下文菜单设置
+const menuGroup: ContextMenu.MenuGroup[] = [
+  [
+    {
+      text: '菜单项 1{%input[type=text][value=张三]%}行',
+      icon: 'dsk-home-two-add',
+      disable() {
+        return false;
+      },
+      callback(inputVal: any) {
+        console.log('回调', inputVal);
+      },
+    },
+    {
+      text: '菜单项 2',
+      icon: 'dsk-file-addition-one',
+      disable() {
+        return Math.random() > 0.5;
+      },
+      callback() {
+        return true;
+      },
+    },
+    {
+      text: '菜单项 3{%input[type=number][value=1][min=1][max=4]%}列',
+      icon: 'dsk-edit',
+      disable() {
+        return false;
+      },
+      callback(inputVal: any) {
+        console.log('回调', inputVal);
+      },
+    },
+  ],
+  [
+    {
+      text: '菜单项 4',
+      hidden() {
+        return Math.random() > 0.5;
+      },
+      disable() {
+        return false;
+      },
+      callback() {
+        console.log('callback');
+      },
+    },
+    {
+      text: '菜单项 5',
+      disable() {
+        return false;
+      },
+      submenu: [
+        [
+          {
+            text: '菜单项 1',
+            hidden() {
+              return false;
+            },
+            disable() {
+              return false;
+            },
+            callback() {
+              console.log('callback');
+            },
+          },
+          {
+            text: '菜单项 2',
+            icon: 'dsk-home-two-add',
+            hidden() {
+              return false;
+            },
+            disable() {
+              return false;
+            },
+            callback() {
+              console.log('callback');
+            },
+          },
+        ],
+        [
+          {
+            text: '菜单项 3',
+            icon: 'dsk-home-two-add',
+            hidden() {
+              return false;
+            },
+            disable() {
+              return true;
+            },
+            callback() {
+              console.log('callback');
+            },
+          },
+          {
+            text: '菜单项 4',
+            hidden() {
+              return false;
+            },
+            disable() {
+              return false;
+            },
+            submenu: [
+              [
+                {
+                  text: '菜单项 1',
+                  icon: 'dsk-home-two-add',
+                  hidden() {
+                    return false;
+                  },
+                  disable() {
+                    return false;
+                  },
+                  submenu: [
+                    [
+                      {
+                        text: '菜单项 1',
+                        icon: 'dsk-home-two-add',
+                        hidden() {
+                          return false;
+                        },
+                        disable() {
+                          return false;
+                        }
+                      },
+                    ],
+                  ],
+                },
+              ],
+            ],
+          },
+        ],
+      ],
+    },
+  ],
+];
+
+const loading = ref(false)
+
+// el-tree 的最外层 dom
+const $subjectTree = ref<HTMLElement | null>();
+onMounted(() => {
+  $subjectTree.value = document.getElementById('subject-tree');
+});
+
+// 当前选中的节点
+const currentNodeKey = '1.1.1'
+
+// 点击某个节点的事件
+const handleNodeClick = (nodeData: Subject.ISubjectTreeNode) => {
+  // 当出现上下文菜单时,单击其他节点,菜单消失
+  if ($subjectTree.value) {
+    $subjectTree.value.click();
+  }
+
+  console.log('当前节点的值:', toRaw(nodeData))
+}
+
+// 右键点击某个节点的事件
+const handleNodeContextmenu = (event: MouseEvent, nodeData: Subject.ISubjectTreeNode) => {
+  console.log('右键事件对象:', event)
+  console.log('当前节点的值:', toRaw(nodeData))
+
+  // 由于 el-tree 默认阻止 contextmenu 事件冒泡,所以此处重新派发一个事件
+  const contextmenuEvent = new MouseEvent('contextmenu', {
+    bubbles: true,
+    clientX: event.clientX,
+    clientY: event.clientY,
+  });
+  if ($subjectTree.value) {
+    $subjectTree.value.dispatchEvent(contextmenuEvent);
+  }
+}
+
+</script>
+
+<template>
+  <section class="subject-tree-wrap">
+    <ul class="tabs">
+      <li class="tab active">原始结构</li>
+      <li class="tab">整理结构</li>
+    </ul>
+    <div class="tree-wrap">
+      <context-menu :item-groups="menuGroup" ref="contextMenuRef" auto-size>
+        <el-tree
+          class="subject-tree"
+          id="subject-tree"
+          v-loading="loading"
+          icon-class="el-icon-arrow-right"
+          :data="treeData"
+          default-expand-all
+          :expand-on-click-node="false"
+          node-key="id"
+          highlight-current
+          :current-node-key="currentNodeKey"
+          @node-click="handleNodeClick"
+          @node-contextmenu="handleNodeContextmenu"
+          ref="treeRef"
+        >
+          <template #default="{ data }">
+            <span class="custom-tree-node">
+              <iconfont class="subject-icon" :class="data.icon" />
+              <span class="text-wrap">
+                <i class="text" :title="data.label">{{ data.label }}</i>
+              </span>
+            </span>
+          </template>
+        </el-tree>
+      </context-menu>
+    </div>
+  </section>
+</template>
+
+<style lang="scss" src="./style.scss" scoped></style>

+ 2 - 2
src/views/project/summary/components/project-explorer/style.scss

@@ -1,4 +1,4 @@
-.project-explorer {
+.subject-tree-wrap {
   @apply h-full;
   background: #fff;
   border: 1px solid #e8eaec;
@@ -27,7 +27,7 @@
         @apply relative;
         &::after {
           @apply absolute;
-          content: '';
+          content: "";
           width: 1px;
           height: 24px;
           background: #e8eaec;

+ 43 - 0
src/views/project/summary/scripts/useSummaryLayout.ts

@@ -0,0 +1,43 @@
+import { ref } from "vue";
+
+export default function useSummaryLayout() {
+    const projectExplorerSize = ref(60)
+    const mainContentSize = ref(300)
+    const mainLeftSize = ref(150)
+    const mainRightSize = ref(50)
+    const mainLeftTopSize = ref(300)
+    const mainLeftBottomSize = ref(150)
+
+    const handleProjectExplorerSize = (size: number) => {
+        projectExplorerSize.value = size
+    }
+    const handleMainContentSize = (size: number) => {
+        mainContentSize.value = size
+    }
+    const handleMainLeftSize = (size: number) => {
+        mainLeftSize.value = size
+    }
+    const handleMainRightSize = (size: number) => {
+        mainRightSize.value = size
+    }
+    const handleMainLeftTopSize = (size: number) => {
+        mainLeftTopSize.value = size
+    }
+    const handleMainLeftBottomSize = (size: number) => {
+        mainLeftBottomSize.value = size
+    }
+    return {
+        projectExplorerSize,
+        mainContentSize,
+        mainLeftSize,
+        mainRightSize,
+        mainLeftTopSize,
+        mainLeftBottomSize,
+        handleProjectExplorerSize,
+        handleMainContentSize,
+        handleMainLeftSize,
+        handleMainRightSize,
+        handleMainLeftTopSize,
+        handleMainLeftBottomSize,
+    }
+}

+ 1 - 1
src/views/ration/Ration.vue

@@ -4,7 +4,7 @@ import { onMounted, reactive, ref } from "vue";
 
 <template>
   <article class="ration">
-    定额库
+    <h1 class="title">数据库,暂未实现。</h1>
   </article>
 </template>
 

+ 4 - 0
src/views/ration/style.scss

@@ -1,2 +1,6 @@
 .ration {
+    .title {
+        font-size: 20px;
+        margin: 50px;
+    }
 }

+ 29 - 16
src/views/workbench/Workbench.vue

@@ -1,9 +1,17 @@
 <script setup lang="ts">
-import {onMounted, reactive, ref} from "vue";
+import { onMounted, reactive, ref } from "vue";
+import getPath, { post } from '@/apis/controller/index';
+
+const handleClick = async () => {
+  console.log(await getPath())
+  console.log('----')
+  console.log(await post())
+}
 </script>
 
 <template>
   <article class="workbench-page">
+    <!-- <el-button @click="handleClick">按钮</el-button> -->
     <header class="header">
       <h1 class="title">工作台</h1>
       <section class="content">
@@ -14,15 +22,21 @@ import {onMounted, reactive, ref} from "vue";
         </div>
         <ul class="summary">
           <li class="item project-count">
-            <span class="text"><img src="@/assets/project-icon.svg" class="icon" alt="">项目数</span>
+            <span class="text">
+              <img src="@/assets/project-icon.svg" class="icon" alt />项目数
+            </span>
             <span class="number">12</span>
           </li>
           <li class="item todo">
-            <span class="text"><img src="@/assets/todo-icon.svg" class="icon" alt="">待办项</span>
+            <span class="text">
+              <img src="@/assets/todo-icon.svg" class="icon" alt />待办项
+            </span>
             <span class="number">4/10</span>
           </li>
           <li class="item enterprise">
-            <span class="text"><img src="@/assets/enterprise-icon.svg" class="icon" alt="">企业数</span>
+            <span class="text">
+              <img src="@/assets/enterprise-icon.svg" class="icon" alt />企业数
+            </span>
             <span class="number">12</span>
           </li>
         </ul>
@@ -35,7 +49,7 @@ import {onMounted, reactive, ref} from "vue";
           <template #header>
             <div class="card-header">
               <span class="text">
-                <img src="@/assets/project-icon.svg" alt="" class="icon">
+                <img src="@/assets/project-icon.svg" alt class="icon" />
                 我的项目
               </span>
               <router-link class="link" to="/project-list">全部项目</router-link>
@@ -57,7 +71,7 @@ import {onMounted, reactive, ref} from "vue";
           <template #header>
             <div class="card-header">
               <span class="text">
-                <img src="@/assets/comment-icon.svg" alt="" class="icon">
+                <img src="@/assets/comment-icon.svg" alt class="icon" />
                 动态
               </span>
             </div>
@@ -85,7 +99,7 @@ import {onMounted, reactive, ref} from "vue";
           <template #header>
             <div class="card-header">
               <span class="text">
-                <img src="@/assets/like-icon.svg" alt="" class="icon">
+                <img src="@/assets/like-icon.svg" alt class="icon" />
                 快捷操作
               </span>
             </div>
@@ -93,27 +107,27 @@ import {onMounted, reactive, ref} from "vue";
           <div class="card-body">
             <ul class="list">
               <li class="item">
-                <iconfont class="icon dsk-dashboard-one" style="color: rgb(82, 196, 26)"/>
+                <iconfont class="icon dsk-dashboard-one" style="color: rgb(82, 196, 26)" />
                 <span class="text">主控台</span>
               </li>
               <li class="item">
-                <iconfont class="icon dsk-me" style="color: rgb(24, 144, 255)"/>
+                <iconfont class="icon dsk-me" style="color: rgb(24, 144, 255)" />
                 <span class="text">个人中心</span>
               </li>
               <li class="item">
-                <iconfont class="icon dsk-config" style="color: rgb(250, 173, 20)"/>
+                <iconfont class="icon dsk-config" style="color: rgb(250, 173, 20)" />
                 <span class="text">设置</span>
               </li>
               <li class="item">
-                <iconfont class="icon dsk-robot-two" style="color: rgb(114, 46, 209)"/>
+                <iconfont class="icon dsk-robot-two" style="color: rgb(114, 46, 209)" />
                 <span class="text">定额库</span>
               </li>
               <li class="item">
-                <iconfont class="icon dsk-doc-detail" style="color: rgb(19, 194, 194)"/>
+                <iconfont class="icon dsk-doc-detail" style="color: rgb(19, 194, 194)" />
                 <span class="text">消息</span>
               </li>
               <li class="item">
-                <iconfont class="icon dsk-search" style="color: rgb(235, 47, 150)"/>
+                <iconfont class="icon dsk-search" style="color: rgb(235, 47, 150)" />
                 <span class="text">搜索</span>
               </li>
             </ul>
@@ -124,7 +138,7 @@ import {onMounted, reactive, ref} from "vue";
           <template #header>
             <div class="card-header">
               <span class="text">
-                <img src="@/assets/todo-icon.svg" alt="" class="icon">
+                <img src="@/assets/todo-icon.svg" alt class="icon" />
                 待办事项
               </span>
             </div>
@@ -147,7 +161,7 @@ import {onMounted, reactive, ref} from "vue";
           <template #header>
             <div class="card-header">
               <span class="text">
-                <img src="@/assets/enterprise-icon.svg" alt="" class="icon">
+                <img src="@/assets/enterprise-icon.svg" alt class="icon" />
                 成员
               </span>
             </div>
@@ -173,7 +187,6 @@ import {onMounted, reactive, ref} from "vue";
             </ul>
           </div>
         </el-card>
-
       </div>
     </main>
   </article>

+ 6 - 7
vite.config.ts

@@ -1,19 +1,18 @@
-import {defineConfig} from 'vite';
+import { defineConfig } from 'vite';
 import vue from '@vitejs/plugin-vue'
 import hooks from '@midwayjs/vite-plugin-hooks';
 
-const {resolve} = require('path')
+const { resolve } = require('path')
 
 // https://vitejs.dev/config/
 export default defineConfig({
-    plugins: [hooks(), vue()],
+    plugins: [
+        hooks(),
+        vue(),
+    ],
     resolve: {
         alias: {
             '@': resolve(__dirname, 'src'),
-            'controller': resolve(__dirname, 'src/apis/controller'),
-            'service': resolve(__dirname, 'src/apis/service'),
-            'constants': resolve(__dirname, 'src/constants'),
-            'utils': resolve(__dirname, 'src/utils'),
         }
     },
     css: {