소스 검색

Merge branch 'flow' of ruiqigogs/yf-portal-vue into master

yanghao 2 주 전
부모
커밋
9c56f3f21d
5개의 변경된 파일628개의 추가작업 그리고 163개의 파일을 삭제
  1. BIN
      src/assets/images/logotext.png
  2. BIN
      src/assets/images/鸿盘.jpg
  3. 297 64
      src/components/home/header.vue
  4. 190 54
      src/views/flow/index.vue
  5. 141 45
      src/views/index.vue

BIN
src/assets/images/logotext.png


BIN
src/assets/images/鸿盘.jpg


+ 297 - 64
src/components/home/header.vue

@@ -3,41 +3,90 @@
     class="fixed w-full top-0 z-100 bg-white border-b border-[#f0f2f5] shadow-sm"
   >
     <div
-      class="max-w-[1200px] mx-auto flex items-center justify-between px-10 pr-0 h-20"
+      class="max-w-[1200px] mx-auto flex items-center justify-between px-5 h-15"
     >
       <div class="flex items-center gap-2 cursor-pointer" @click="goHome">
-        <img :src="logo" alt="logo" class="w-9 h-9 rounded-sm" />
-        <span class="text-[#02409b] text-[20px] font-bold">DeepOil</span>
+        <img :src="logo" alt="logo" class="w-9 h-9 rounded-md" />
+        <span class="text-[#02409b] text-[20px] font-bold hidden lg:flex"
+          >KERUI DEEPOIL</span
+        >
+        <span class="text-[#828182] text-[14px]">智慧经营平台</span>
       </div>
 
-      <nav class="hidden lg:flex flex-1 mx-4 ml-10 text-sm">
-        <ul class="flex items-center gap-6 text-[#303133] text-lg">
+      <nav class="hidden lg:flex flex-1 mx-4 ml-80 text-sm">
+        <ul class="flex items-center gap-6 text-[#303133] text-md">
+          <!-- 首页 -->
           <li>
             <a
-              class="hover:text-[#02409b] cursor-pointer"
+              class="cursor-pointer px-3 py-1.5 rounded-md transition-all duration-300"
+              :class="
+                router.currentRoute.value.path === '/'
+                  ? 'nav-item-active'
+                  : 'nav-item-default'
+              "
+              style="text-wrap-mode: nowrap"
               @click="router.push('/')"
-              >首页</a
             >
+              首页
+            </a>
           </li>
 
+          <!-- 流程门户 -->
           <li>
-            <a class="hover:text-[#02409b] cursor-pointer" @click="goFlow"
-              >流程门户</a
+            <a
+              style="text-wrap-mode: nowrap"
+              class="cursor-pointer px-3 py-1.5 rounded-md transition-all duration-300"
+              :class="
+                router.currentRoute.value.path.startsWith('/flow')
+                  ? 'nav-item-active'
+                  : 'nav-item-default'
+              "
+              @click="goFlow"
             >
+              流程门户
+            </a>
           </li>
+
+          <!-- 驾驶舱门户 -->
+          <li>
+            <a
+              class="cursor-pointer px-3 py-1.5 rounded-md transition-all duration-300"
+              :class="
+                router.currentRoute.value.path.startsWith('/drive')
+                  ? 'nav-item-active'
+                  : 'nav-item-default'
+              "
+              @click="goDrive"
+              style="text-wrap-mode: nowrap"
+            >
+              驾驶舱门户
+            </a>
+          </li>
+
+          <!-- 报表门户 (假设路径为 /report,请根据实际路由调整) -->
           <li>
-            <a class="hover:text-[#02409b] cursor-pointer" @click="goDrive"
-              >驾驶舱门户</a
+            <a
+              class="cursor-pointer px-3 py-1.5 rounded-md transition-all duration-300"
+              :class="
+                router.currentRoute.value.path.startsWith('/report')
+                  ? 'nav-item-active'
+                  : 'nav-item-default'
+              "
+              style="text-wrap-mode: nowrap"
             >
+              报表门户
+            </a>
           </li>
-          <li><a class="hover:text-[#02409b] cursor-pointer">报表门户</a></li>
         </ul>
       </nav>
 
-      <div class="hidden lg:flex items-center gap-3 h-full">
+      <div
+        style="border-left: 1px solid #a5bbdb"
+        class="hidden lg:flex items-center gap-3 h-[40%] pl-4"
+      >
         <!-- 消息中心 -->
         <el-dropdown trigger="click" placement="bottom-end">
-          <div class="flex items-center gap-2 cursor-pointer pr-6 pt-2">
+          <div class="flex items-center gap-2 cursor-pointer pr-6 pt-1">
             <el-badge
               :value="unreadMessageCount + oaUnreadCount"
               class="item"
@@ -45,106 +94,106 @@
             >
               <Icon
                 icon="mdi:bell"
-                class="w-5 h-5 text-gray-600 hover:text-[#409EFF]"
+                class="w-5 h-5 text-[#507698] hover:text-[#409EFF]"
               />
             </el-badge>
             <Icon
               v-else
               icon="mdi:bell"
-              class="w-5 h-5 text-gray-600 hover:text-[#409EFF]"
+              class="w-5 h-5 text-[#507698] hover:text-[#409EFF]"
             />
           </div>
           <template #dropdown>
             <el-dropdown-menu class="notification-dropdown">
               <div class="notification-tabs pl-2">
                 <el-tabs v-model="activeTab" class="demo-tabs">
-                  <el-tab-pane label="CRM" name="messages">
+                  <el-tab-pane label="OA" name="tasks">
                     <template #label>
                       <span class="custom-tabs-label">
-                        <span>CRM</span>
+                        <span>OA</span>
                         <el-badge
-                          :value="unreadMessageCount"
+                          :value="oaUnreadCount"
                           class="item ml-1"
-                          v-if="hasUnreadMessages"
+                          v-if="oaHasUnreadCount"
                         ></el-badge>
                       </span>
                     </template>
                     <div class="tab-content">
-                      <!-- 消息中心内容 -->
                       <div>
                         <span
-                          v-if="hasUnreadMessages"
+                          v-if="oaHasUnreadCount"
                           class="cursor-pointer text-blue-500"
-                          @click="markAllAsRead"
+                          @click="oaMarkAllAsRead"
                           >全部标为已读</span
                         >
                       </div>
+                      <!-- OA消息 -->
                       <div
-                        class="message-item"
-                        v-for="(item, index) in messages"
+                        class="task-item"
+                        v-for="(task, index) in oaMessagesList"
                         :key="index"
                       >
-                        <div class="message-icon"></div>
-                        <div class="message-text">
-                          <!-- 未读就显示小红点 -->
-
-                          <p class="message-title flex items-center gap-5">
+                        <div class="task-info">
+                          <p class="task-title">
                             <span
-                              v-if="item.status === '0'"
-                              class="w-2 h-2 bg-[#f56c6c] rounded-full"
-                            ></span
-                            >{{ item.contentMajor }}
+                              v-if="task.status === '0'"
+                              class="inline-block h-2 w-2 bg-[#f56c6c] rounded-full"
+                            ></span>
+                            {{ task.title }}
                           </p>
                           <p class="message-desc">
-                            {{ timestampToDateTime(item.createTime) }}
+                            <span>{{ task.oaCreateTime }}</span>
                           </p>
                         </div>
                       </div>
-                      <div v-if="!messages.length" class="no-messages">
+                      <div v-if="!oaMessagesList.length" class="no-tasks">
                         暂无新消息
                       </div>
                     </div>
                   </el-tab-pane>
-                  <el-tab-pane label="OA" name="tasks">
+                  <el-tab-pane label="CRM" name="messages">
                     <template #label>
                       <span class="custom-tabs-label">
-                        <span>OA</span>
+                        <span>CRM</span>
                         <el-badge
-                          :value="oaUnreadCount"
+                          :value="unreadMessageCount"
                           class="item ml-1"
-                          v-if="oaHasUnreadCount"
+                          v-if="hasUnreadMessages"
                         ></el-badge>
                       </span>
                     </template>
                     <div class="tab-content">
+                      <!-- 消息中心内容 -->
                       <div>
                         <span
-                          v-if="oaHasUnreadCount"
+                          v-if="hasUnreadMessages"
                           class="cursor-pointer text-blue-500"
-                          @click="oaMarkAllAsRead"
+                          @click="markAllAsRead"
                           >全部标为已读</span
                         >
                       </div>
-                      <!-- OA消息 -->
                       <div
-                        class="task-item"
-                        v-for="(task, index) in oaMessagesList"
+                        class="message-item"
+                        v-for="(item, index) in messages"
                         :key="index"
                       >
-                        <div class="task-info">
-                          <p class="task-title">
+                        <div class="message-icon"></div>
+                        <div class="message-text">
+                          <!-- 未读就显示小红点 -->
+
+                          <p class="message-title flex items-center gap-5">
                             <span
-                              v-if="task.status === '0'"
-                              class="inline-block h-2 w-2 bg-[#f56c6c] rounded-full"
-                            ></span>
-                            {{ task.title }}
+                              v-if="item.status === '0'"
+                              class="w-2 h-2 bg-[#f56c6c] rounded-full"
+                            ></span
+                            >{{ item.contentMajor }}
                           </p>
                           <p class="message-desc">
-                            <span>{{ task.oaCreateTime }}</span>
+                            {{ timestampToDateTime(item.createTime) }}
                           </p>
                         </div>
                       </div>
-                      <div v-if="!oaMessagesList.length" class="no-tasks">
+                      <div v-if="!messages.length" class="no-messages">
                         暂无新消息
                       </div>
                     </div>
@@ -278,7 +327,8 @@
         </template>
         <template v-else>
           <div
-            class="bg-[#0050b3] hover:bg-[#0050b3]/90 text-white text-sm flex items-center justify-center cursor-pointer h-full px-10 py-4"
+            style="text-wrap: nowrap"
+            class="text-md flex items-center justify-center cursor-pointer h-full py-4"
             @click="login"
           >
             登 录
@@ -287,6 +337,124 @@
       </div>
 
       <div class="lg:hidden">
+        <el-dropdown trigger="click" placement="bottom-end">
+          <div class="flex items-center gap-2 cursor-pointer pr-6 pt-1">
+            <el-badge
+              :value="unreadMessageCount + oaUnreadCount"
+              class="item"
+              v-if="hasUnreadMessages || oaHasUnreadCount"
+            >
+              <Icon
+                icon="mdi:bell"
+                class="w-5 h-5 text-gray-600 hover:text-[#409EFF]"
+              />
+            </el-badge>
+            <Icon
+              v-else
+              icon="mdi:bell"
+              class="w-5 h-5 text-gray-600 hover:text-[#409EFF]"
+            />
+          </div>
+          <template #dropdown>
+            <el-dropdown-menu class="notification-dropdown">
+              <div class="notification-tabs pl-2">
+                <el-tabs v-model="activeTab" class="demo-tabs">
+                  <el-tab-pane label="OA" name="tasks">
+                    <template #label>
+                      <span class="custom-tabs-label">
+                        <span>OA</span>
+                        <el-badge
+                          :value="oaUnreadCount"
+                          class="item ml-1"
+                          v-if="oaHasUnreadCount"
+                        ></el-badge>
+                      </span>
+                    </template>
+                    <div class="tab-content">
+                      <div>
+                        <span
+                          v-if="oaHasUnreadCount"
+                          class="cursor-pointer text-blue-500"
+                          @click="oaMarkAllAsRead"
+                          >全部标为已读</span
+                        >
+                      </div>
+                      <!-- OA消息 -->
+                      <div
+                        class="task-item"
+                        v-for="(task, index) in oaMessagesList"
+                        :key="index"
+                      >
+                        <div class="task-info">
+                          <p class="task-title">
+                            <span
+                              v-if="task.status === '0'"
+                              class="inline-block h-2 w-2 bg-[#f56c6c] rounded-full"
+                            ></span>
+                            {{ task.title }}
+                          </p>
+                          <p class="message-desc">
+                            <span>{{ task.oaCreateTime }}</span>
+                          </p>
+                        </div>
+                      </div>
+                      <div v-if="!oaMessagesList.length" class="no-tasks">
+                        暂无新消息
+                      </div>
+                    </div>
+                  </el-tab-pane>
+                  <el-tab-pane label="CRM" name="messages">
+                    <template #label>
+                      <span class="custom-tabs-label">
+                        <span>CRM</span>
+                        <el-badge
+                          :value="unreadMessageCount"
+                          class="item ml-1"
+                          v-if="hasUnreadMessages"
+                        ></el-badge>
+                      </span>
+                    </template>
+                    <div class="tab-content">
+                      <!-- 消息中心内容 -->
+                      <div>
+                        <span
+                          v-if="hasUnreadMessages"
+                          class="cursor-pointer text-blue-500"
+                          @click="markAllAsRead"
+                          >全部标为已读</span
+                        >
+                      </div>
+                      <div
+                        class="message-item"
+                        v-for="(item, index) in messages"
+                        :key="index"
+                      >
+                        <div class="message-icon"></div>
+                        <div class="message-text">
+                          <!-- 未读就显示小红点 -->
+
+                          <p class="message-title flex items-center gap-5">
+                            <span
+                              v-if="item.status === '0'"
+                              class="w-2 h-2 bg-[#f56c6c] rounded-full"
+                            ></span
+                            >{{ item.contentMajor }}
+                          </p>
+                          <p class="message-desc">
+                            {{ timestampToDateTime(item.createTime) }}
+                          </p>
+                        </div>
+                      </div>
+                      <div v-if="!messages.length" class="no-messages">
+                        暂无新消息
+                      </div>
+                    </div>
+                  </el-tab-pane>
+                </el-tabs>
+              </div>
+            </el-dropdown-menu>
+          </template>
+        </el-dropdown>
         <el-button link @click="drawer = true">
           <i class="el-icon" />
           <Icon icon="fa:bars" class="icon" />
@@ -304,23 +472,58 @@
         <ul class="flex flex-col gap-3 text-[#303133]">
           <li>
             <a
-              class="hover:text-[#02409b] cursor-pointer"
-              @click="router.push('/')"
-              >首页</a
+              class="block px-3 py-2 rounded-md transition-all duration-300"
+              :class="
+                router.currentRoute.value.path === '/'
+                  ? 'nav-item-active'
+                  : 'nav-item-default'
+              "
+              @click="
+                router.push('/');
+                drawer = false;
+              "
             >
+              首页
+            </a>
           </li>
-
           <li>
-            <a class="hover:text-[#02409b] cursor-pointer" @click="goFlow"
-              >流程门户</a
+            <a
+              class="block px-3 py-2 rounded-md transition-all duration-300"
+              :class="
+                router.currentRoute.value.path.startsWith('/flow')
+                  ? 'nav-item-active'
+                  : 'nav-item-default'
+              "
+              @click="goFlow"
             >
+              流程门户
+            </a>
           </li>
           <li>
-            <a class="hover:text-[#02409b] cursor-pointer" @click="goDrive"
-              >驾驶舱门户</a
+            <a
+              class="block px-3 py-2 rounded-md transition-all duration-300"
+              :class="
+                router.currentRoute.value.path.startsWith('/drive')
+                  ? 'nav-item-active'
+                  : 'nav-item-default'
+              "
+              @click="goDrive"
             >
+              驾驶舱门户
+            </a>
+          </li>
+          <li>
+            <a
+              class="block px-3 py-2 rounded-md transition-all duration-300"
+              :class="
+                router.currentRoute.value.path.startsWith('/report')
+                  ? 'nav-item-active'
+                  : 'nav-item-default'
+              "
+            >
+              报表门户
+            </a>
           </li>
-          <li><a class="hover:text-[#02409b] cursor-pointer">报表门户</a></li>
         </ul>
         <div class="flex items-center gap-3 mt-3">
           <template v-if="isLoggedIn">
@@ -389,7 +592,7 @@ import { deleteUserCache } from "@hooks/useCache";
 import { manualLogoutKey, reloginCancelKey } from "@/config/axios/service";
 
 // 新增消息中心状态
-const activeTab = ref("messages");
+const activeTab = ref("tasks");
 const messages = ref([]);
 
 const isLoggedIn = computed(
@@ -702,4 +905,34 @@ const onUserCommand = async (command: string) => {
   font-size: 12px;
   color: #c0c4cc;
 }
+
+.nav-item-default {
+  background-color: #eeeeef; /* 浅灰色背景 */
+  color: #5f6f83; /*  Slate 500 灰色文字 */
+  border-radius: 100px;
+}
+
+.nav-item-default:hover {
+  background-color: #e2e8f0; /* 悬停时稍深一点的灰色 */
+  color: #02409b; /* 品牌蓝文字 */
+}
+
+/* 激活状态:深蓝色背景,白色文字 */
+.nav-item-active {
+  background-color: #063e8d !important; /* 指定的深蓝色背景 */
+  color: #ffffff !important; /* 白色文字 */
+  font-weight: 500; /* 稍微加粗 */
+  box-shadow: 0 2px 4px rgba(6, 62, 141, 0.2); /* 可选:轻微阴影 */
+  border-radius: 100px;
+}
+
+/* 确保移动端也生效,因为上面用了 block,可能需要调整一下内边距或显示方式 */
+@media (max-width: 1024px) {
+  .nav-item-active,
+  .nav-item-default {
+    display: block;
+    width: 100%;
+    text-align: left;
+  }
+}
 </style>

+ 190 - 54
src/views/flow/index.vue

@@ -80,28 +80,54 @@
         </span>
       </div>
 
-      <div class="tabs-container" role="tablist" aria-label="EHR模块">
+      <div class="tabs-wrapper">
+        <!-- 左侧箭头 -->
         <button
-          class="el-tab-item"
-          type="button"
-          role="tab"
-          :class="{ 'is-active': activeKey === 'all' }"
-          :aria-selected="activeKey === 'all'"
-          @click="setAll"
+          class="scroll-arrow left"
+          @click="scrollTabs('left')"
+          :disabled="isLeftDisabled"
         >
-          <span class="tab-label">全部</span>
+          <Icon icon="mdi:chevron-left" />
         </button>
+
+        <!-- Tab 列表容器 -->
+        <div
+          class="tabs-container"
+          ref="tabsContainerRef"
+          role="tablist"
+          aria-label="EHR模块"
+        >
+          <button
+            class="el-tab-item"
+            type="button"
+            role="tab"
+            :class="{ 'is-active': activeKey === 'all' }"
+            :aria-selected="activeKey === 'all'"
+            @click="setAll"
+          >
+            <span class="tab-label">全部</span>
+          </button>
+          <button
+            v-for="tab in tabs"
+            :key="tab.groupName"
+            class="el-tab-item"
+            :class="{ 'is-active': tab.groupName === activeKey }"
+            type="button"
+            role="tab"
+            :aria-selected="tab.groupName === activeKey"
+            @click="getById(tab)"
+          >
+            <span class="tab-label">{{ tab.groupName }}</span>
+          </button>
+        </div>
+
+        <!-- 右侧箭头 -->
         <button
-          v-for="tab in tabs"
-          :key="tab.groupName"
-          class="el-tab-item"
-          :class="{ 'is-active': tab.groupName === activeKey }"
-          type="button"
-          role="tab"
-          :aria-selected="tab.groupName === activeKey"
-          @click="getById(tab)"
+          class="scroll-arrow right"
+          @click="scrollTabs('right')"
+          :disabled="!isLeftDisabled"
         >
-          <span class="tab-label">{{ tab.groupName }}</span>
+          <Icon icon="mdi:chevron-right" />
         </button>
       </div>
 
@@ -142,34 +168,6 @@
               </h3>
               <p class="item-desc">{{ item.remark || "暂无描述" }}</p>
             </div>
-
-            <!-- <div class="flex justify-between">
-              <div class="item-time flex items-center gap-2">
-                <svg
-                  xmlns="http://www.w3.org/2000/svg"
-                  width="1em"
-                  height="1em"
-                  viewBox="0 0 24 24"
-                >
-                  <g
-                    fill="none"
-                    stroke="#f97316"
-                    stroke-linecap="round"
-                    stroke-linejoin="round"
-                    stroke-width="1.5"
-                  >
-                    <path
-                      d="M15.362 5.214A8.252 8.252 0 0 1 12 21A8.25 8.25 0 0 1 6.038 7.047A8.3 8.3 0 0 0 9 9.601a8.98 8.98 0 0 1 3.361-6.867a8.2 8.2 0 0 0 3 2.48"
-                    ></path>
-                    <path
-                      d="M12 18a3.75 3.75 0 0 0 .495-7.468a6 6 0 0 0-1.925 3.547a6 6 0 0 1-2.133-1.001A3.75 3.75 0 0 0 12 18"
-                    ></path>
-                  </g>
-                </svg>
-                <span class="text-[12px] text-[#babdd1]">提交流程</span>
-              </div>
-              <Icon icon="mdi-light:chevron-right" class="item-arrow w-6 h-6" />
-            </div> -->
           </div>
         </div>
         <div v-else class="empty-state">
@@ -206,6 +204,47 @@ const pieChartInstance = ref(null);
 let chartResizeObserver = null;
 let chartInitTimer = null;
 
+// 1. 定义 Ref
+const tabsContainerRef = ref(null);
+const isLeftDisabled = ref(true);
+const isRightDisabled = ref(true);
+
+// 2. 滚动逻辑
+const scrollTabs = (direction) => {
+  const container = tabsContainerRef.value;
+  if (!container) return;
+
+  // 每次滚动的距离,可以根据需求调整,例如 200px 或 container.clientWidth / 2
+  const scrollAmount = 200;
+
+  if (direction === "left") {
+    container.scrollBy({ left: -scrollAmount, behavior: "smooth" });
+  } else {
+    container.scrollBy({ left: scrollAmount, behavior: "smooth" });
+  }
+};
+
+// 3. 更新箭头禁用状态
+const updateArrowState = () => {
+  const container = tabsContainerRef.value;
+  if (!container) return;
+
+  const { scrollLeft, scrollWidth, clientWidth } = container;
+
+  // 如果滚动条在最左边(容差1px),禁用左箭头
+  isLeftDisabled.value = scrollLeft <= 1;
+
+  // 如果滚动条在最右边(容差1px),禁用右箭头
+  // Math.ceil 处理小数像素问题
+  isRightDisabled.value =
+    Math.ceil(scrollLeft + clientWidth) >= scrollWidth - 1;
+};
+
+// 4. 监听滚动事件
+const handleTabScroll = () => {
+  updateArrowState();
+};
+
 const initChartsSafe = (attempt = 0) => {
   const lineDom = lineChartRef.value;
   const pieDom = pieChartRef.value;
@@ -650,6 +689,11 @@ onMounted(async () => {
   getAll();
   // 等待 DOM 与样式生效,避免移动端首屏尺寸为 0
   await nextTick();
+  updateArrowState();
+  // 添加滚动监听
+  if (tabsContainerRef.value) {
+    tabsContainerRef.value.addEventListener("scroll", handleTabScroll);
+  }
   requestAnimationFrame(() => {
     initChartsSafe();
     // 添加监听
@@ -764,6 +808,10 @@ onBeforeUnmount(() => {
   // 可选:销毁 echarts 实例
   lineChartInstance.value?.dispose();
   pieChartInstance.value?.dispose();
+
+  if (tabsContainerRef.value) {
+    tabsContainerRef.value.removeEventListener("scroll", handleTabScroll);
+  }
 });
 </script>
 
@@ -878,15 +926,6 @@ onBeforeUnmount(() => {
   color: #64748b;
 }
 
-.tabs-container {
-  display: flex;
-  align-items: center;
-  border-bottom: 1px solid #e4e7ed; /* Element Plus 标准的分割线颜色 */
-  margin-bottom: 20px;
-  padding-left: 0;
-  overflow-x: auto; /* 防止Tab过多时溢出 */
-}
-
 .el-tab-item {
   position: relative;
   display: inline-flex;
@@ -1333,4 +1372,101 @@ onBeforeUnmount(() => {
   color: #02409b; /* 变蓝 (使用你主题中的蓝色) */
   transform: translateX(4px); /* 向右平移 4px */
 }
+
+.tabs-wrapper {
+  position: relative;
+  width: 100%;
+  display: flex;
+  align-items: center;
+  margin-bottom: 20px;
+}
+
+/* 修改:原有的 .tabs-container 去掉 margin-bottom,因为现在由 wrapper 控制间距 */
+.tabs-container {
+  display: flex;
+  align-items: center;
+  border-bottom: 1px solid #e4e7ed;
+  padding-left: 0;
+  overflow-x: auto;
+  scroll-behavior: smooth;
+  scrollbar-width: none; /* Firefox */
+  -ms-overflow-style: none; /* IE 10+ */
+  flex: 1; /* 占据中间剩余空间 */
+  mask-image: linear-gradient(
+    to right,
+    transparent,
+    black 20px,
+    black 98%,
+    transparent
+  ); /* 可选:添加两侧渐变遮罩效果 */
+  -webkit-mask-image: linear-gradient(
+    to right,
+    transparent,
+    black 20px,
+    black 98%,
+    transparent
+  );
+}
+
+.tabs-container::-webkit-scrollbar {
+  display: none;
+}
+
+/* 新增:箭头按钮样式 */
+.scroll-arrow {
+  position: absolute;
+  top: 50%;
+  transform: translateY(-50%);
+  z-index: 10;
+  width: 32px;
+  height: 32px;
+  border-radius: 50%;
+  background-color: #ffffff;
+  border: 1px solid #e2e8f0;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  color: #64748b;
+  transition: all 0.2s ease;
+  opacity: 0; /* 默认隐藏,鼠标悬停容器或需要时显示,或者始终显示但禁用时变灰 */
+  visibility: hidden;
+}
+
+/* 当容器被悬停时显示箭头,或者你可以去掉这个限制让它始终显示 */
+.tabs-wrapper:hover .scroll-arrow {
+  opacity: 1;
+  visibility: visible;
+}
+
+.scroll-arrow.left {
+  left: 0;
+}
+
+.scroll-arrow.right {
+  right: 0;
+}
+
+.scroll-arrow:hover:not(:disabled) {
+  background-color: #f1f5f9;
+  color: #02409b;
+  border-color: #cbd5e1;
+}
+
+.scroll-arrow:disabled {
+  background-color: #f8fafc;
+  color: #cbd5e1;
+  cursor: not-allowed;
+  box-shadow: none;
+  border-color: #f1f5f9;
+  opacity: 0.6;
+  visibility: visible; /* 禁用时也要可见以展示状态 */
+}
+
+/* 确保 Tab 项不会被箭头遮挡太多,可以在容器两侧加一点 padding */
+.tabs-container {
+  /* 在原有样式基础上增加左右 padding,防止第一个和最后一个 Tab 被箭头完全遮住 */
+  padding: 0 40px;
+}
 </style>

+ 141 - 45
src/views/index.vue

@@ -2,7 +2,7 @@
   <div class="portal-home min-h-screen bg-[#eef3f9] text-[#17345f]">
     <Header />
 
-    <main class="mx-auto max-w-[1200px] px-6 pb-8 pt-24">
+    <main class="mx-auto max-w-[1200px] px-6 pb-8 pt-20">
       <section class="hero-banner overflow-hidden rounded-[6px] relative">
         <div class="">
           <!-- 轮播容器 -->
@@ -97,7 +97,7 @@
                     v-if="app.image"
                     :src="app.image"
                     :alt="app.label"
-                    class="h-7 w-7 object-contain"
+                    class="h-7 w-7 object-contain rounded-md"
                   />
                   <Icon
                     v-else
@@ -137,28 +137,51 @@
           >
             <div class="side-card__header w-[91%] ml-[15px]">
               <div class="notice-badge px-2">{{ todoPanelTitle }}</div>
-              <button type="button" class="side-card__more">全部任务</button>
+              <button
+                type="button"
+                class="side-card__more"
+                @click="router.push('/todo-list')"
+              >
+                全部任务
+              </button>
             </div>
             <div class="space-y-2 p-4 pt-2">
+              <div
+                v-if="!userStore.getUser.username"
+                class="flex h-full items-center justify-center"
+              >
+                <div class="text-[#8a9ab0] pt-10">登录后查看</div>
+              </div>
+
+              <div
+                v-if="!oaTasks.length && userStore.getUser.username"
+                class="flex h-full items-center justify-center"
+              >
+                <div class="text-[#8a9ab0]">暂无数据</div>
+              </div>
+
               <article
-                v-for="task in todoTasks"
-                :key="task.title"
-                class="todo-item rounded-md"
+                v-if="userStore.getUser.username"
+                v-for="task in oaTasks"
+                :key="task.requestId"
+                @click="handleTask(task)"
+                class="todo-item rounded-md cursor-pointer"
               >
                 <div class="flex min-w-0 items-start justify-between gap-3">
                   <div class="min-w-0">
                     <div
                       class="truncate text-[14px] font-semibold text-[#0d4a9d]"
                     >
-                      {{ task.title }}
+                      {{ task.requestName }}
                     </div>
                     <div class="mt-1 text-[12px] text-[#8a9ab0]">
-                      {{ task.meta }}
+                      创建人:{{ task.creatorName }} · {{ task.createTime }}
                     </div>
                   </div>
-                  <span :class="['todo-item__tag', task.tagClass]">{{
-                    task.tag
-                  }}</span>
+                  <span
+                    :class="['todo-item__tag', getTagClass(task.requesLevel)]"
+                    >{{ getTagName(task.requesLevel) }}</span
+                  >
                 </div>
               </article>
             </div>
@@ -232,7 +255,13 @@ import Footer from "@components/home/Footer.vue";
 import { useRouter } from "vue-router";
 import axios from "axios";
 import { Icon } from "@iconify/vue";
-import { getMCSsoToken, ssoLogin, zentaoSsoLogin } from "@/api/user";
+import { ElLoading } from "element-plus";
+import {
+  getMCSsoToken,
+  ssoLogin,
+  zentaoSsoLogin,
+  getOATasks,
+} from "@/api/user";
 import { useUserStore } from "@/stores/useUserStore";
 import { getAccessToken } from "@/utils/auth";
 import banner1 from "@/assets/images/banner1.png";
@@ -265,6 +294,7 @@ import zhanlueimage from "@/assets/images/zhanlue.jpg"; // 战略解码
 import safeimage from "@/assets/images/safe.png"; // 安全合规管理
 import zuzhiimage from "@/assets/images/zuzhi.jpg";
 import youimage from "@/assets/images/youcnag.png"; // ai智能体
+import hongpan from "@/assets/images/鸿盘.jpg"; // ai智能体
 
 type PortalApp = {
   label: string;
@@ -306,7 +336,7 @@ type NewsItem = {
 
 const router = useRouter();
 const userStore = useUserStore();
-const heroGreeting = "早上好,保持热爱,奔赴目标!";
+
 const noticeLabel = "公告";
 const todoPanelTitle = "待办中心";
 const newsPanelTitle = "新闻";
@@ -334,6 +364,7 @@ const portalSections: PortalSection[] = [
       { label: "组织资产管理", image: zuzhiimage },
       { label: "风控合规管理", image: safeimage },
       { label: "研发需求管理", image: jishuimage2 },
+      { label: "鸿盘", image: hongpan },
     ],
   },
   {
@@ -354,7 +385,7 @@ const portalSections: PortalSection[] = [
     ],
   },
   {
-    code: "CB",
+    code: "AI",
     title: "Chat BI平台",
     subtitle: "高效协同 · 战略洞察",
     height: "160px",
@@ -382,11 +413,18 @@ let boldLabes = ref([
   "经营驾驶舱(MC)",
 ]);
 
+const getGreeting = () => {
+  const hour = new Date().getHours();
+  if (hour < 12) return "早上好";
+  if (hour < 18) return "下午好";
+  return "晚上好";
+};
+
 // 添加轮播数据
 const slides = ref([
   {
     image: banner1,
-    text: "早上好,保持热爱,奔赴目标!",
+    text: getGreeting() + ",保持热爱,奔赴目标!",
   },
   {
     image: banner2,
@@ -407,12 +445,6 @@ const nextSlide = () => {
   currentIndex.value = (currentIndex.value + 1) % slides.value.length;
 };
 
-// 上一页
-const prevSlide = () => {
-  currentIndex.value =
-    (currentIndex.value - 1 + slides.value.length) % slides.value.length;
-};
-
 const notices: NoticeItem[] = [
   {
     title: "集团总部关于办公楼设备维保的说明",
@@ -424,27 +456,6 @@ const notices: NoticeItem[] = [
   },
 ];
 
-const todoTasks: TodoTask[] = [
-  {
-    title: "项目立项审批 - QHSE 升级",
-    meta: "发起人:李智慧 · 2小时前",
-    tag: "紧急",
-    tagClass: "bg-[#ffe1e3] text-[#ff5d66]",
-  },
-  {
-    title: "合同会签 - 油服工程中心",
-    meta: "发起人:王志刚 · 4小时前",
-    tag: "普通",
-    tagClass: "bg-[#dbe8ff] text-[#3f74ff]",
-  },
-  {
-    title: "报销单待审批",
-    meta: "来自:财务管理系统 · 昨天",
-    tag: "待办",
-    tagClass: "bg-[#dfe8f3] text-[#6f7f94]",
-  },
-];
-
 const newsList: NewsItem[] = [
   {
     title: "科瑞石油成功交付首套自动化钻机",
@@ -463,8 +474,6 @@ const newsList: NewsItem[] = [
   },
 ];
 
-const sidePanels: SidePanel[] = [];
-
 const protectedOpen = (url: string) => {
   if (userStore.getUser.username && getAccessToken()) {
     window.open(url, "_blank");
@@ -559,6 +568,14 @@ const handlePortalAppClick = async (app: PortalApp) => {
       router.push({ path: "/login" });
     }
   }
+
+  if (app.label === "鸿盘") {
+    if (userStore.getUser.username && getAccessToken()) {
+      window.open(`https://pan.keruioil.com:52180`, "_blank");
+    } else {
+      router.push({ path: "/login" });
+    }
+  }
 };
 
 async function loginWithDingTalk() {
@@ -609,11 +626,90 @@ function dingTalkAutoLogin() {
   }
 }
 
-onMounted(() => {
+const getTagClass = (tag: string) => {
+  return tag === "0"
+    ? "bg-[#dbe8ff] text-[#3f74ff]"
+    : tag === "1"
+      ? "bg-[#ffe1e3] text-[#ff5d66]"
+      : tag === "2"
+        ? "bg-[#dfe8f3] text-[#6f7f94]"
+        : "bg-[#dbe8ff] text-[#3f74ff]";
+};
+
+const getTagName = (tag: string) => {
+  return tag === "0"
+    ? "正常"
+    : tag === "1"
+      ? "重要"
+      : tag === "2"
+        ? "紧急"
+        : "正常";
+};
+
+let oaTasks = ref([]);
+onMounted(async () => {
   dingTalkAutoLogin();
   slideTimer = setInterval(nextSlide, 5000);
+
+  if (userStore.getUser.username) {
+    try {
+      const res = await getOATasks({
+        id: userStore.getUser.username,
+        pageNum: 1,
+        pageSize: 10,
+      });
+      oaTasks.value = res.todoList.slice(0, 3);
+    } finally {
+    }
+  }
 });
 
+const handleTask = async (row) => {
+  const res = await ssoLogin({
+    username: userStore.getUser.username,
+  });
+
+  if (res) {
+    const ua = window.navigator.userAgent.toLowerCase();
+    if (ua.includes("dingtalk") || ua.includes("dingtalkwork")) {
+      dd.biz.util.openLink({
+        url:
+          "https://yfoa.keruioil.com/wui/index.html" +
+          "?ssoToken=" +
+          res +
+          "#/main", // 先跳你的 SSO 链接
+        onSuccess: () => {
+          // 延迟跳目标业务地址(和你原来 setTimeout 逻辑一致)
+          setTimeout(() => {
+            dd.biz.util.openLink({
+              url: `https://yfoa.keruioil.com/spa/workflow/static4form/index.html?_rdm=1776063595284#/main/workflow/req?requestid=${row.requestId}`,
+            });
+          }, 100);
+        },
+      });
+    } else {
+      const loading = ElLoading.service({
+        lock: true,
+        text: "正在跳转,请稍候...",
+        background: "rgba(0, 0, 0, 0.7)",
+      });
+      const newTab = window.open("", "_blank");
+      newTab.location.href =
+        "https://yfoa.keruioil.com/wui/index.html" +
+        "?ssoToken=" +
+        res +
+        "#/main";
+
+      setTimeout(function () {
+        newTab.location.href = `https://yfoa.keruioil.com/spa/workflow/static4form/index.html?_rdm=1776063595284#/main/workflow/req?requestid=${row.requestId}`;
+        setTimeout(() => {
+          loading.close();
+        }, 500);
+      }, 100);
+    }
+  }
+};
+
 onUnmounted(() => {
   if (slideTimer) {
     clearInterval(slideTimer);