Forráskód Böngészése

feat: 标准清单和定额库结构完成

qinlaiqiao 3 éve
szülő
commit
9ab5bc498a

+ 15 - 0
src/store/modules/summaryLayout.ts

@@ -10,6 +10,11 @@ export class SummaryLayout extends VuexModule {
   mainLeftTopSize = 300
   mainLeftBottomSize = 50
 
+  // 右侧标准清单可见性
+  rightStdBillVisible = false
+  // 右侧定额库可见性
+  rightLibRationVisible = false
+
   @Mutation
   setSubjectTreeSize(size: number) {
     this.subjectTreeSize = size
@@ -39,6 +44,16 @@ export class SummaryLayout extends VuexModule {
   setMainLeftBottomSize(size: number) {
     this.mainLeftBottomSize = size
   }
+
+  @Mutation
+  setRightStdBillVisible(visible: boolean) {
+    this.rightStdBillVisible = visible
+  }
+
+  @Mutation
+  setRightLibRationVisible(visible: boolean) {
+    this.rightLibRationVisible = visible
+  }
 }
 
 export default getModule<SummaryLayout>(SummaryLayout);

+ 73 - 0
src/styles/_mixin.scss

@@ -311,3 +311,76 @@
     background-color: rgba(0, 0, 0, 0.4);
   }
 }
+
+@mixin summary-right-pane() {
+  height: 100%;
+  background: #fff;
+  border: 1px solid #e8eaec;
+
+  .filter-bar {
+    height: 36px;
+    flex: 0 36px;
+    display: flex;
+    padding: 0 3px;
+    justify-content: space-around;
+    align-items: center;
+
+    ::v-deep .el-select {
+      display: block;
+      margin-right: 6px;
+      height: 30px !important;
+      line-height: 30px !important;
+    }
+
+    ::v-deep .el-input {
+      display: flex;
+      align-items: center;
+      height: 30px;
+      line-height: 30px;
+
+      > .el-input__inner {
+        line-height: 30px;
+        border-radius: 2px;
+      }
+
+      > .el-input__prefix,
+      > .el-input__suffix {
+        line-height: 30px;
+      }
+    }
+  }
+  .search-result-nav {
+    display: flex;
+    height: 32px;
+    flex: 0;
+    justify-content: space-between;
+    align-items: center;
+    padding: 0 6px 4px;
+
+    > .count {
+    }
+
+    > .nav-button-group {
+    }
+
+    > .close-button {
+      cursor: pointer;
+      font-size: 18px;
+      color: #777;
+      padding: 2px;
+      border-radius: 50%;
+      transition: all 0.2s;
+
+      &:hover {
+        color: $black;
+        background-color: #ddd;
+      }
+    }
+  }
+
+  .table-area {
+    position: relative;
+    flex: 1;
+    border-top: 1px solid #ddd;
+  }
+}

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

@@ -39,7 +39,6 @@ declare namespace ResizableLayout {
 }
 
 
-
 type Renderer = (
     instance: Handsontable,
     TD: HTMLElement,

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

@@ -8,4 +8,13 @@ declare namespace Subject {
         type: SubjectTreeNodeType;
         children?: ISubjectTreeNode[];
     }
+
+    interface IRightVisible {
+        stdBillVisible: boolean;
+        libRationVisible: boolean;
+
+        // searchLocationVisible: boolean;
+        // priceTemplateVisible: boolean;
+        // bookmarkVisible: boolean;
+    }
 }

+ 32 - 7
src/views/project/summary/Summary.vue

@@ -1,10 +1,11 @@
 <script setup lang="ts">
-import { ref, reactive, onMounted } from "vue";
+import { ref, reactive, onMounted, nextTick } 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 LibRation from "./components/lib-ration/LibRation.vue";
 import { SubjectTreeNodeType } from '@/constants/subject'
 import { Subject } from "@/types/subject";
 import useSummaryLayout from './scripts/useSummaryLayout'
@@ -17,6 +18,13 @@ const {
   mainRightSize,
   mainLeftTopSize,
   mainLeftBottomSize,
+  rightStdBillVisible,
+  rightLibRationVisible,
+
+  costTableRef,
+  bottomTabsRef,
+  stdBillRef,
+  libRationRef,
 
   handleSubjectTreeSize,
   handleMainContentSize,
@@ -24,10 +32,6 @@ const {
   handleMainRightSize,
   handleMainLeftTopSize,
   handleMainLeftBottomSize,
-
-  costTableRef,
-  bottomTabsRef,
-  stdBillRef
 } = useSummaryLayout()
 
 const loading = ref(true)
@@ -85,6 +89,17 @@ onMounted(async () => {
     loading.value = false
   }, 1000);
 })
+
+const mainLayout = ref<ResizableLayout.IResizableLayoutComponent>()
+const handleRightVisibleChange = async (needRender: boolean) => {
+  if (needRender) {
+    setTimeout(() => {
+      costTableRef.value && costTableRef.value.Render();
+      bottomTabsRef.value && bottomTabsRef.value.Render();
+    }, 100)
+  }
+  mainLayout.value && mainLayout.value.Refresh()
+}
 </script>
 
 <template>
@@ -104,8 +119,8 @@ onMounted(async () => {
         @resize="handleMainContentSize"
       >
         <!-- 工具栏 -->
-        <tool-bar />
-        <resizable-layout style="height: calc(100% - 41px)">
+        <tool-bar @right-visible-change="handleRightVisibleChange" />
+        <resizable-layout style="height: calc(100% - 41px)" ref="mainLayout">
           <resizable-layout-item
             :weight="mainLeftSize"
             :min-width="150"
@@ -135,9 +150,19 @@ onMounted(async () => {
             :weight="mainRightSize"
             :min-width="150"
             @resize="handleMainRightSize"
+            v-if="rightStdBillVisible"
           >
             <std-bill ref="stdBillRef" />
           </resizable-layout-item>
+          <!-- 右侧布局:定额库 -->
+          <resizable-layout-item
+            :weight="mainRightSize"
+            :min-width="150"
+            @resize="handleMainRightSize"
+            v-if="rightLibRationVisible"
+          >
+            <lib-ration ref="libRationRef" />
+          </resizable-layout-item>
         </resizable-layout>
       </resizable-layout-item>
     </resizable-layout>

+ 5 - 1
src/views/project/summary/components/bottom-tabs/BottomTabs.vue

@@ -13,7 +13,11 @@ defineExpose({
 </script>
 
 <template>
-  <section class="bottom-tabs">底部 tabs</section>
+  <el-tabs type="border-card" class="bottom-tabs">
+    <el-tab-pane label="定额/工料机">定额/工料机</el-tab-pane>
+    <el-tab-pane label="清单对比">清单对比</el-tab-pane>
+    <el-tab-pane label="价格曲线图">价格曲线图</el-tab-pane>
+  </el-tabs>
 </template>
 
 <style lang="scss" src="./style.scss" scoped></style>

+ 29 - 1
src/views/project/summary/components/bottom-tabs/style.scss

@@ -1,5 +1,33 @@
 .bottom-tabs {
-  @apply h-full ;
+  @apply h-full;
   background: #fff;
   border: 1px solid #e8eaec;
+  box-shadow: none;
+
+  ::v-deep .el-tabs__header {
+    background-color: #f4f5f7;
+
+    .el-tabs__nav-prev,
+    .el-tabs__nav-next {
+      line-height: 36px;
+    }
+
+    .el-tabs__item {
+      height: 36px;
+      line-height: 36px;
+      color: #515a6e;
+      transition: none;
+      &.is-active {
+        color: #409eff;
+      }
+    }
+  }
+
+  ::v-deep .el-tabs__content {
+    padding: 0;
+    height: calc(100% - 35px);
+    .el-tab-pane {
+      @apply w-full h-full;
+    }
+  }
 }

+ 150 - 0
src/views/project/summary/components/lib-ration/LibRation.vue

@@ -0,0 +1,150 @@
+<script setup lang="ts">
+import { onMounted, reactive, ref } from "vue";
+import { ElInput } from "element-plus";
+
+const libID = ref('1')
+const options = reactive([
+  {
+    ID: '1',
+    name: 'Option1',
+  },
+  {
+    ID: '2',
+    name: 'Option2',
+  },
+])
+
+const showSearchResult = ref(false)
+const searchCount = ref(0)
+
+// 搜索框的模板引用
+const searchRef = ref<typeof ElInput>();
+
+// 搜索结果页面定额类型
+const rationType = ref('selectedLib');
+
+const searchValue = ref('')
+const handleSearch = () => {
+  console.log('搜索')
+
+  searchRef.value && searchRef.value.blur();
+  searchCount.value = 0;
+  // searchResults.length = 0;
+  // currentIndex = -1;
+  showSearchResult.value = true;
+  // const searchKey = searchValue.value.toLowerCase();
+}
+
+const topSize = ref(60)
+const bottomSize = ref(40)
+
+const handleTopResize = (size: number) => {
+  topSize.value = size
+}
+
+const handleBottomResize = (size: number) => {
+  bottomSize.value = size
+}
+
+
+const showRationTable = ref(false)
+
+const Render = () => {
+  // 重新渲染
+  // hotRef.value && hotRef.value.Render();
+  console.log('lib ration render')
+}
+
+defineExpose({
+  Render
+})
+</script>
+
+<template>
+  <section class="lib-ration">
+    <!-- 过滤栏 -->
+    <div class="filter-bar">
+      <el-select size="mini" style="width: 100%" v-model="libID">
+        <el-option v-for="item in options" :key="item.ID" :label="item.name" :value="item.ID" />
+      </el-select>
+      <el-input
+        size="mini"
+        placeholder="输入编码或名称"
+        v-model="searchValue"
+        @keypress.enter="handleSearch"
+        ref="searchRef"
+      >
+        <template #prefix>
+          <iconfont class="dsk-search" style="color: #6d7679" />
+        </template>
+        <template #suffix>
+          <iconfont class="dsk-enter-key" style="color: #37599f" />
+        </template>
+      </el-input>
+    </div>
+    <!-- 搜索结果栏 -->
+    <div class="search-result-nav" v-show="showSearchResult">
+      <p class="count">搜索结果: {{ searchCount }}</p>
+      <el-radio-group class="nav-button-group" v-model="rationType" size="mini">
+        <el-radio-button label="selectedLib">本定额</el-radio-button>
+        <el-radio-button label="all">全部定额</el-radio-button>
+      </el-radio-group>
+      <i class="el-icon-close close-button" @click="showSearchResult = false" />
+    </div>
+    <div
+      class="table-area"
+      :style="{ height: showSearchResult ? 'calc(100% - 68px)' : 'calc(100% - 36px)' }"
+    >
+      <resizable-layout direction="vertical" ref="layoutRef">
+        <!-- 章节 -->
+        <resizable-layout-item
+          :weight="topSize"
+          :min-height="200"
+          @resize="handleTopResize"
+          v-if="!showSearchResult"
+        >
+          <!-- <handsontable
+            :data="chapterTableTreeData"
+            :settings="chapterTableSettings"
+            tree
+            border="bottom"
+            :loading="chapterLoading"
+            ref="chapterHotRef"
+            @dblclick="handleChapterDbClick"
+          />-->
+          <span
+            class="临时待删"
+            style="display: block; height:100%; width:100%; background: #eee;"
+          >上面表格区域</span>
+        </resizable-layout-item>
+        <!-- 定额 -->
+        <resizable-layout-item :weight="bottomSize" :min-height="200" @resize="handleBottomResize">
+          <!-- <context-menu :item-groups="contextMenuGroups" :read-only="unitReadOnly"> -->
+          <!-- <handsontable
+              v-if="showRationTable"
+              :data="rationTableData"
+              :settings="rationTableSettings"
+              :loading="rationLoading"
+              border="top"
+              ref="rationHotRef"
+              @dblclick="handleRationDbClick"
+          />-->
+          <span
+            class="临时wrap,待删"
+            style="display: block; height:100%; width:100%; background: #eee;"
+            v-if="showRationTable"
+          >下面表格区域</span>
+          <el-empty
+            v-else
+            :image-size="80"
+            style="border-top: 1px solid #ddd"
+            description="请选择具体章节"
+          />
+          <!-- </context-menu> -->
+        </resizable-layout-item>
+      </resizable-layout>
+    </div>
+  </section>
+</template>
+
+<style lang="scss" src="./style.scss" scoped></style>

+ 3 - 0
src/views/project/summary/components/lib-ration/style.scss

@@ -0,0 +1,3 @@
+.lib-ration {
+  @include summary-right-pane;
+}

+ 97 - 4
src/views/project/summary/components/std-bill/StdBill.vue

@@ -1,19 +1,112 @@
 <script setup lang="ts">
-import { onMounted, reactive, ref } from "vue";
+import { ElInput } from "element-plus";
+import { reactive, ref, nextTick, watch } from "vue";
+import useHotRef from '@/composables/useHotRef';
 
+const libID = ref('1')
+const options = reactive([
+  {
+    ID: '1',
+    name: 'Option1',
+  },
+  {
+    ID: '2',
+    name: 'Option2',
+  },
+])
+
+const showSearchResult = ref(false)
+const searchCount = ref(0)
+
+// 搜索框的模板引用
+const searchRef = ref<typeof ElInput>();
+
+const searchValue = ref('')
+const handleSearch = () => {
+  console.log('搜索')
+
+  searchRef.value && searchRef.value.blur();
+  searchCount.value = 0;
+  // searchResults.length = 0;
+  // currentIndex = -1;
+  showSearchResult.value = true;
+  // const searchKey = searchValue.value.toLowerCase();
+}
+
+
+const preMatch = () => {
+  console.log('上一个')
+}
+
+const nextMatch = () => {
+  console.log('下一个')
+}
+
+const { getHotRef, notNull } = useHotRef();
+const hotRef = getHotRef();
+
+// 重新渲染表格
 const Render = () => {
-  // 重新渲染
-  // hotRef.value && hotRef.value.Render();
+  hotRef.value && hotRef.value.Render();
   console.log('std bill render')
 }
 
+// const tableData = []
+
+// const tableSettings = {
+// }
+
+// 显示或隐藏搜索结果栏,都重新 Render hot
+watch(showSearchResult, async () => {
+  await nextTick();
+  Render()
+});
+
 defineExpose({
   Render
 })
 </script>
 
 <template>
-  <section class="std-bill">标准清单</section>
+  <section class="std-bill">
+    <!-- 过滤栏 -->
+    <div class="filter-bar">
+      <el-select size="mini" style="width: 100%" v-model="libID">
+        <el-option v-for="item in options" :key="item.ID" :label="item.name" :value="item.ID" />
+      </el-select>
+      <el-input
+        size="mini"
+        placeholder="输入编码或名称"
+        v-model="searchValue"
+        @keypress.enter="handleSearch"
+        ref="searchRef"
+      >
+        <template #prefix>
+          <iconfont class="dsk-search" style="color: #6d7679" />
+        </template>
+        <template #suffix>
+          <iconfont class="dsk-enter-key" style="color: #37599f" />
+        </template>
+      </el-input>
+    </div>
+    <!-- 搜索结果栏 -->
+    <div class="search-result-nav" v-show="showSearchResult">
+      <p class="count">搜索结果: {{ searchCount }}</p>
+      <el-button-group class="nav-button-group">
+        <el-button type="primary" size="mini" @click="preMatch">上一个</el-button>
+        <el-button type="primary" size="mini" @click="nextMatch">下一个</el-button>
+      </el-button-group>
+      <i class="el-icon-close close-button" @click="showSearchResult = false" />
+    </div>
+    <!-- 表格区域 -->
+    <div
+      class="table-area"
+      :style="{ height: showSearchResult ? 'calc(100% - 68px)' : 'calc(100% - 36px)' }"
+    >
+      <!-- <handsontable :data="tableData" :settings="tableSettings" border="none" ref="hotRef" /> -->
+      表格区域
+    </div>
+  </section>
 </template>
 
 <style lang="scss" src="./style.scss" scoped></style>

+ 1 - 3
src/views/project/summary/components/std-bill/style.scss

@@ -1,5 +1,3 @@
 .std-bill {
-  @apply h-full;
-  background: #fff;
-  border: 1px solid #e8eaec;
+  @include summary-right-pane;
 }

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

@@ -1,4 +1,5 @@
 <script setup lang="ts">
+// import { ContextMenu } from "@/types/components";
 import { Subject } from "@/types/subject";
 import { ref, PropType, toRaw, onMounted } from "vue";
 

+ 58 - 4
src/views/project/summary/components/tool-bar/ToolBar.vue

@@ -1,5 +1,53 @@
 <script setup lang="ts">
-import { onMounted, reactive, ref } from "vue";
+import { onMounted, reactive, ref, computed } from "vue";
+import summaryLayoutStore from '@/store/modules/summaryLayout'
+import { Subject } from "@/types/subject";
+
+const { setRightStdBillVisible, setRightLibRationVisible } = summaryLayoutStore
+
+const rightStdBillVisible = computed({
+  get: () => summaryLayoutStore.rightStdBillVisible,
+  set: val => setRightStdBillVisible(val),
+});
+
+const rightLibRationVisible = computed({
+  get: () => summaryLayoutStore.rightLibRationVisible,
+  set: val => setRightLibRationVisible(val),
+});
+
+const collapseIconDisabled = computed(() => !rightStdBillVisible.value && !rightLibRationVisible.value)
+
+const emit = defineEmits<{
+  (e: 'right-visible-change', needRender: boolean): void
+}>()
+
+const handleStdBillClick = () => {
+  if (rightStdBillVisible.value)
+    return
+  emit('right-visible-change', collapseIconDisabled.value)
+
+  rightStdBillVisible.value = true
+  rightLibRationVisible.value = false
+}
+
+const handleLibRationClick = () => {
+  if (rightLibRationVisible.value)
+    return
+  emit('right-visible-change', collapseIconDisabled.value)
+
+  rightLibRationVisible.value = true
+  rightStdBillVisible.value = false
+}
+
+const hide = () => {
+  if (collapseIconDisabled.value)
+    return
+  emit('right-visible-change', true)
+
+  rightStdBillVisible.value = false
+  rightLibRationVisible.value = false
+}
+
 </script>
 
 <template>
@@ -45,11 +93,17 @@ import { onMounted, reactive, ref } from "vue";
       </el-tooltip>
     </div>
     <div class="menus">
-      <span class="menu-item active">
+      <span class="menu-item" :class="{ active: rightStdBillVisible }" @click="handleStdBillClick">
         <i class="text">标准清单</i>
       </span>
-      <span class="menu-item"><i class="text">定额库</i></span>
-      <span class="menu-item toggle-icon">
+      <span
+        class="menu-item"
+        :class="{ active: rightLibRationVisible }"
+        @click="handleLibRationClick"
+      >
+        <i class="text">定额库</i>
+      </span>
+      <span class="menu-item" @click="hide" :class="{ disabled: collapseIconDisabled }">
         <iconfont class="icon dsk-menu-unfold" />
       </span>
     </div>

+ 5 - 1
src/views/project/summary/components/tool-bar/style.scss

@@ -39,7 +39,7 @@
     line-height: 34px;
 
     .menu-item {
-      @apply relative inline-block align-bottom cursor-pointer;
+      @apply relative inline-block align-bottom cursor-pointer select-none;
       margin-left: 10px;
       padding: 0 8px;
 
@@ -59,6 +59,10 @@
         }
         color: #409eff;
       }
+      &.disabled {
+        color: #ccc;
+        cursor: not-allowed
+      }
       .text {
         @apply relative;
         z-index: 1;

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

@@ -12,6 +12,8 @@ export default function useSummaryLayout() {
         setMainRightSize,
         setMainLeftTopSize,
         setMainLeftBottomSize,
+        setRightStdBillVisible,
+        setRightLibRationVisible
     } = summaryLayoutStore
 
     const subjectTreeSize = computed({
@@ -44,13 +46,25 @@ export default function useSummaryLayout() {
         set: val => setMainLeftBottomSize(val),
     });
 
+    const rightStdBillVisible = computed({
+        get: () => summaryLayoutStore.rightStdBillVisible,
+        set: val => setRightStdBillVisible(val),
+    });
+
+    const rightLibRationVisible = computed({
+        get: () => summaryLayoutStore.rightLibRationVisible,
+        set: val => setRightLibRationVisible(val),
+    });
+
     const costTableRef = ref<NeedRenderComponent>()
     const bottomTabsRef = ref<NeedRenderComponent>()
     const stdBillRef = ref<NeedRenderComponent>()
+    const libRationRef = ref<NeedRenderComponent>()
 
     const handleSubjectTreeSize = (size: number) => {
         subjectTreeSize.value = size
     }
+
     const handleMainContentSize = (size: number) => {
         mainContentSize.value = size
 
@@ -58,7 +72,9 @@ export default function useSummaryLayout() {
         costTableRef.value && costTableRef.value.Render()
         bottomTabsRef.value && bottomTabsRef.value.Render()
         stdBillRef.value && stdBillRef.value.Render()
+        libRationRef.value && libRationRef.value.Render()
     }
+
     const handleMainLeftSize = (size: number) => {
         mainLeftSize.value = size
 
@@ -66,16 +82,20 @@ export default function useSummaryLayout() {
         costTableRef.value && costTableRef.value.Render()
         bottomTabsRef.value && bottomTabsRef.value.Render()
     }
+
     const handleMainRightSize = (size: number) => {
         mainRightSize.value = size
 
         stdBillRef.value && stdBillRef.value.Render()
+        libRationRef.value && libRationRef.value.Render()
     }
+
     const handleMainLeftTopSize = (size: number) => {
         mainLeftTopSize.value = size
 
         costTableRef.value && costTableRef.value.Render()
     }
+
     const handleMainLeftBottomSize = (size: number) => {
         mainLeftBottomSize.value = size
 
@@ -89,10 +109,14 @@ export default function useSummaryLayout() {
         mainRightSize,
         mainLeftTopSize,
         mainLeftBottomSize,
+        rightStdBillVisible,
+        rightLibRationVisible,
 
         costTableRef,
         bottomTabsRef,
         stdBillRef,
+        libRationRef,
+
         handleSubjectTreeSize,
         handleMainContentSize,
         handleMainLeftSize,