Quellcode durchsuchen

Merge branch 'dev' of http://59.110.6.97:3000/root/BoundLink-Plate-UI into dev

wangzs vor 4 Monaten
Ursprung
Commit
5c5816adad

+ 26 - 2
src/components/BsUi/Descriptions/index.vue

@@ -16,11 +16,23 @@
         <a-descriptions-item v-for="(item, index) in items" :key="index" v-bind="item.extraProps">
           <template #label>
             <slot v-if="item.labelSlot" :name="item.labelSlot"></slot>
-            <span v-else class="dsc-label">{{ item.label }}</span>
+
+            <div v-else class="help-config">
+              <a-tooltip placement="top" v-if="item?.helpConfig?.enable">
+                <template #title>
+                  <span>{{ item?.helpConfig.helpTip }}</span>
+                </template>
+                <QuestionCircleOutlined />
+              </a-tooltip>
+              <span class="dsc-label">{{ item.label }}</span>
+            </div>
+
           </template>
 
           <template v-if="item.valueSlot">
-            <slot :name="item.valueSlot"></slot>
+           <div class="value_slot">
+             <slot :name="item.valueSlot"></slot>
+           </div>
           </template>
           <template v-if="!item.valueSlot">
             <div class="dsc-value">
@@ -103,6 +115,11 @@
       color: #000;
       font-weight: 500;
     }
+    .help-config {
+      display: flex;
+      gap: 5px;
+      align-items: center;
+    }
     :deep(.ant-descriptions-item-container) {
       display: flex;
       align-items: flex-start;
@@ -114,8 +131,15 @@
       margin: 0;
       padding: 10px 0 0 0;
     }
+    :deep(.ant-descriptions-item-content) {
+      position: relative;
+    }
     .default_slot {
       padding: 10px 0;
     }
+    .value_slot {
+      position: absolute;
+      top: -5px;
+    }
   }
 </style>

+ 1 - 0
src/components/BsUi/Tabs/index.vue

@@ -37,6 +37,7 @@ watch(
     activeKey,
     (val) => {
       emits('change', val);
+      emits('update:tabActiveKey', val);
     },
     { immediate: true }
 );

+ 19 - 4
src/components/business/page-detail-layout/index.vue

@@ -22,7 +22,9 @@
         </div>
       </div>
 
-      <div class="header-flow" v-if="false"></div>
+      <div class="header-flow" v-if="!isEmpty(stepsData)">
+        <steps :doing-node-index="doingNodeIndex" @change-doing-node-index="handleChangeDoingNodeIndex" :steps="stepsData"></steps>
+      </div>
     </div>
 
     <div class="header-tabs">
@@ -38,9 +40,9 @@
 <script setup>
   import { BsTabs } from '/@/components/BsUi/index.js';
   import { useSlots } from 'vue';
-
+  import steps from './steps/index.vue';
+  import { isEmpty } from 'lodash';
   const slots = useSlots();
-
   const props = defineProps({
     title: {
       required: false,
@@ -66,13 +68,25 @@
         valueKey: 'value',
       },
     },
+    doingNodeIndex: {
+      required: false,
+      default: 0,
+    },
+    stepsData: {
+      required: false,
+      default: [],
+    },
   });
 
-  const emits = defineEmits(['update:tabActiveKey']);
+  const emits = defineEmits(['update:tabActiveKey', 'update:doingNodeIndex']);
 
   const handleChangeTabActiveKey = (val) => {
     emits('update:tabActiveKey', val);
   };
+
+  const handleChangeDoingNodeIndex = (val) => {
+    emits('update:doingNodeIndex', val);
+  };
 </script>
 
 <style lang="scss" scoped>
@@ -153,6 +167,7 @@
         margin-top: 20px;
         width: 100%;
         height: 100%;
+        overflow-x: auto;
       }
     }
 

+ 170 - 0
src/components/business/page-detail-layout/steps/index.vue

@@ -0,0 +1,170 @@
+<template>
+  <div class="steps-progress-container">
+    <div v-for="(step, index) in steps" :key="index" class="step-item">
+      <div class="step-item-part" @click="handleClickNode(step)">
+        <!-- 步骤圆圈及勾选/数字状态 -->
+        <div
+          :class="[
+            'step-circle',
+            {
+              completed: step.status === 'completed',
+              current: step.status === 'current',
+            },
+          ]"
+        >
+          <span v-if="step.status === 'completed'">✔</span>
+          <span v-else-if="step.status === 'current'">{{ step.number }}</span>
+          <span v-else>{{ step.number }}</span>
+        </div>
+        <!-- 步骤文字及时间 -->
+        <div class="step-text">
+          <div :class="`step-title ${step.status === 'completed' ? 'step-title_completed' : ''}`">{{ step.title }}</div>
+        </div>
+
+        <div class="step-time" v-if="step.time">{{ step.time }}</div>
+      </div>
+
+      <!-- 连接线条,最后一个步骤不需要线条 -->
+      <div
+        class="step-line"
+        v-if="index < steps.length - 1"
+        :style="{
+          width: '100%',
+          background: step.status === 'completed' ? lineColor_complete : lineColor_un_complete,
+        }"
+      ></div>
+
+      <div class="line-top" v-if="index < steps.length - 1">{{ step.lineTopNum }}天</div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import { onMounted, ref, watch } from 'vue';
+
+  const props = defineProps({
+    doingNodeIndex: {
+      required: false,
+      default: 0,
+    },
+    steps: {
+      required: false,
+      default: [],
+    },
+  });
+
+  const doingIndex = ref(0);
+
+  // 可根据需求动态调整线条宽度和颜色等样式
+  const lineColor_complete = '#1677ff';
+  const lineColor_un_complete = '#e5e6eb';
+
+  const emits = defineEmits(['clickNode', 'changeDoingNodeIndex']);
+
+  const handleClickNode = (currentNode) => {
+    emits('clickNode', { node: currentNode });
+  };
+
+  onMounted(() => {
+    doingIndex.value = props.doingNodeIndex;
+    //   TODO 动态来处理steps中的complete和current,todo的状态
+  });
+
+  watch(doingIndex, (val) => {
+    emits('changeDoingNodeIndex', val);
+  });
+</script>
+
+<style scoped>
+  .steps-progress-container {
+    min-width: 1250px;
+    display: flex;
+    align-items: flex-start;
+    padding: 10px 0;
+    justify-content: center;
+  }
+
+  .step-item {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    position: relative;
+    flex: 1;
+    //width: 150px;
+  }
+
+  .step-circle {
+    width: 30px;
+    height: 30px;
+    border-radius: 50%;
+    border: 2px solid #ccc;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    font-size: 14px;
+    color: #fff;
+    background-color: #ccc;
+  }
+
+  .step-circle.completed {
+    background-color: #fff;
+    border-color: var(--vxe-ui-font-primary-color);
+    color: var(--vxe-ui-font-primary-color);
+  }
+
+  .step-circle.current {
+    background-color: var(--vxe-ui-font-primary-color);
+    border-color: var(--vxe-ui-font-primary-color);
+  }
+
+  .step-text {
+    text-align: center;
+    margin-top: 5px;
+    font-size: 16px;
+    color: #86909c;
+    position: relative;
+  }
+
+  .step-time {
+    margin-top: 2px;
+    font-size: 14px;
+    color: #86909c;
+    position: absolute;
+    bottom: 0;
+    padding-left: 40px;
+  }
+
+  .step-line {
+    width: 100%;
+    height: 2px;
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    margin-left: 50px;
+    color: var(--vxe-ui-font-primary-color);
+  }
+
+  .step-item-part {
+    display: flex;
+    z-index: 100;
+    background: #fff;
+    gap: 10px;
+    padding: 20px;
+    align-items: center;
+    cursor: pointer;
+    position: relative;
+  }
+
+  .line-top {
+    position: absolute;
+    right: 0;
+    top: 10px;
+    transform: translateX(50%);
+    z-index: 100;
+    color: var(--vxe-ui-font-primary-color);
+  }
+
+  .step-title_completed {
+    color: #1d2129;
+  }
+</style>

+ 8 - 0
src/views/customer-manage/customer-detail/components/BasinInfo.vue

@@ -200,10 +200,18 @@
   const manageInfoItems = ref([
     {
       label: '集团公司',
+      helpConfig: {
+        enable: true,
+        helpTip: "测试"
+      },
       valueSlot: 'company_value_slot',
     },
     {
       label: '客户等级',
+      helpConfig: {
+        enable: true,
+        helpTip: "测试"
+      },
       value: 'A级',
     },
     {

+ 48 - 11
src/views/customer-manage/customer-detail/index.vue

@@ -32,8 +32,54 @@
 <script setup>
   import { reactive, ref } from 'vue';
   import PageDetailLayout from '/@/components/business/page-detail-layout/index.vue';
-  import BasicInfo from "./components/BasinInfo.vue"
-
+  import BasicInfo from './components/BasinInfo.vue';
+  const stepsData = [
+    {
+      title: '标段备案',
+      time: '2024-09-18',
+      status: 'completed',
+      number: 1,
+      lineTopNum: 100,
+    },
+    {
+      title: '商机管理',
+      time: '2024-09-20',
+      status: 'completed',
+      number: 2,
+      lineTopNum: 100,
+    },
+    {
+      title: '项目立项',
+      time: '2024-10-11',
+      status: 'completed',
+      number: 3,
+      lineTopNum: 100,
+    },
+    {
+      title: '投标评审',
+      status: 'current',
+      number: 4,
+      lineTopNum: 0,
+    },
+    {
+      title: '投标管理',
+      status: 'todo',
+      number: 5,
+      lineTopNum: 0,
+    },
+    {
+      title: '中标捷报',
+      status: 'todo',
+      number: 6,
+      lineTopNum: 0,
+    },
+    {
+      title: '合同签订',
+      status: 'todo',
+      number: 7,
+      lineTopNum: 0,
+    },
+  ];
   const getImgUrl = (name) => {
     return new URL('/src/assets/images/page-detail-layout/customer/' + name + '.svg', import.meta.url).href;
   };
@@ -137,15 +183,6 @@
     },
   ]);
 
-  const bsDescriptionItems = ref([
-    {
-      label: '姓名',
-      field: 'name',
-      value: '韩晓辉',
-      extraProps: {},
-    },
-  ]);
-
   const indexConfig = reactive({
     sourceData: [
       {