فهرست منبع

流程门户初版

yanghao 1 روز پیش
والد
کامیت
d61c239294
8فایلهای تغییر یافته به همراه571 افزوده شده و 76 حذف شده
  1. 4 0
      components.d.ts
  2. 21 0
      src/api/user.ts
  3. 128 45
      src/components/home/header.vue
  4. 18 0
      src/router/index.ts
  5. 169 0
      src/views/flow/crmTodoList.vue
  6. 17 4
      src/views/flow/index.vue
  7. 199 0
      src/views/flow/todoList.vue
  8. 15 27
      src/views/index.vue

+ 4 - 0
components.d.ts

@@ -12,6 +12,7 @@ export {}
 declare module 'vue' {
   export interface GlobalComponents {
     CardItem: typeof import('./src/components/home/CardItem.vue')['default']
+    ElBadge: typeof import('element-plus/es')['ElBadge']
     ElButton: typeof import('element-plus/es')['ElButton']
     ElCard: typeof import('element-plus/es')['ElCard']
     ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
@@ -21,8 +22,11 @@ declare module 'vue' {
     ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
     ElForm: typeof import('element-plus/es')['ElForm']
     ElFormItem: typeof import('element-plus/es')['ElFormItem']
+    ElIcon: typeof import('element-plus/es')['ElIcon']
     ElInput: typeof import('element-plus/es')['ElInput']
     ElPopover: typeof import('element-plus/es')['ElPopover']
+    ElTable: typeof import('element-plus/es')['ElTable']
+    ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
     ElTabPane: typeof import('element-plus/es')['ElTabPane']
     ElTabs: typeof import('element-plus/es')['ElTabs']
     ElTag: typeof import('element-plus/es')['ElTag']

+ 21 - 0
src/api/user.ts

@@ -105,3 +105,24 @@ export const getCRMTasks = async (id) => {
     url: "/admin-api/portal/todo/crm?workcode=" + id,
   });
 };
+
+// 消息通知
+export const getNotifyMessages = async (id) => {
+  return await request.get({
+    url: "/admin-api/portal/todo/crm/notice?workcode=" + id,
+  });
+};
+
+// 消息列表
+export const getNotifyMessageList = async (id) => {
+  return await request.get({
+    url: "/admin-api/portal/todo/crm/notice/self?workcode=" + id,
+  });
+};
+
+// 标记消息为已读
+export const markMessageAsRead = async (id) => {
+  return await request.get({
+    url: "/admin-api/portal/todo/crm/notice/readed?workcode=" + id,
+  });
+};

+ 128 - 45
src/components/home/header.vue

@@ -30,36 +30,71 @@
       <div class="hidden lg:flex items-center gap-3 h-full">
         <!-- 消息中心 -->
         <el-dropdown trigger="click" placement="bottom-end">
-          <div class="flex items-center gap-2 cursor-pointer pr-2">
+          <div class="flex items-center gap-2 cursor-pointer pr-6 pt-2">
+            <el-badge
+              :value="unreadMessageCount"
+              class="item"
+              v-if="hasUnreadMessages"
+            >
+              <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]"
             />
-            <span class="text-sm text-gray-600">消息代办</span>
           </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="消息中心" name="messages">
+                  <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
+                        >
+
+                        <span v-else class="cursor-pointer text-[#b2aaaa]"
+                          >全部已读</span
+                        >
+                      </div>
                       <div
                         class="message-item"
                         v-for="(item, index) in messages"
                         :key="index"
                       >
-                        <div class="message-icon">
-                          <Icon
-                            :icon="item.icon"
-                            class="w-5 h-5"
-                            :class="item.typeClass"
-                          />
-                        </div>
+                        <div class="message-icon"></div>
                         <div class="message-text">
-                          <p class="message-title">{{ item.title }}</p>
-                          <p class="message-desc">{{ item.desc }}</p>
-                          <p class="message-time">{{ item.time }}</p>
+                          <!-- 未读就显示小红点 -->
+
+                          <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">
@@ -67,25 +102,38 @@
                       </div>
                     </div>
                   </el-tab-pane>
-                  <el-tab-pane label="待办任务" name="tasks">
+                  <el-tab-pane label="OA" name="tasks">
                     <div class="tab-content">
-                      <!-- 待办任务内容 -->
+                      <!-- <div>
+                        <span
+                          class="cursor-pointer text-blue-500"
+                          @click="markAllAsRead"
+                          >全部标为已读</span
+                        >
+                      </div> -->
+                      <!-- OA消息 -->
                       <div
                         class="task-item"
-                        v-for="(task, index) in tasks"
+                        v-for="(task, index) in oaMessagesList"
                         :key="index"
                       >
                         <div class="task-info">
-                          <p class="task-title">{{ task.title }}</p>
-                          <p class="task-desc">{{ task.desc }}</p>
-                          <p class="task-time">截止时间: {{ task.dueTime }}</p>
+                          <p class="task-title">
+                            <span
+                              class="w-3 h-3 bg-[#f56c6c] rounded-full"
+                            ></span
+                            >{{ task.contentMajor }}
+                          </p>
+                          <p class="message-desc">
+                            {{ timestampToDateTime(task.createTime) }}
+                          </p>
                         </div>
                         <el-tag :type="task.priorityTag" size="small">{{
                           task.priorityText
                         }}</el-tag>
                       </div>
-                      <div v-if="!tasks.length" class="no-tasks">
-                        暂无待办任务
+                      <div v-if="!oaMessagesList.length" class="no-tasks">
+                        暂无新消息
                       </div>
                     </div>
                   </el-tab-pane>
@@ -289,12 +337,17 @@
 
 <script setup lang="ts">
 import { Icon } from "@iconify/vue";
-import { ref, computed } from "vue";
+import { ref, computed, onMounted } from "vue";
 import { useRouter } from "vue-router";
 import logo from "@/assets/images/logo.png";
 import person from "@/assets/images/person.png";
 import { useUserStoreWithOut } from "@/stores/useUserStore";
 const userStore = useUserStoreWithOut();
+import {
+  getNotifyMessages,
+  getNotifyMessageList,
+  markMessageAsRead,
+} from "@api/user";
 
 import {
   getAccessToken,
@@ -307,29 +360,7 @@ import { deleteUserCache } from "@hooks/useCache";
 
 // 新增消息中心状态
 const activeTab = ref("messages");
-const messages = ref([
-  {
-    title: "系统通知",
-    desc: "您的账户已成功激活",
-    time: "10分钟前",
-    icon: "mdi:message-text-outline",
-    typeClass: "text-blue-500",
-  },
-  {
-    title: "安全提醒",
-    desc: "检测到异地登录行为",
-    time: "30分钟前",
-    icon: "mdi:security",
-    typeClass: "text-red-500",
-  },
-  {
-    title: "更新提示",
-    desc: "系统将于今晚进行维护升级",
-    time: "1小时前",
-    icon: "mdi:update",
-    typeClass: "text-green-500",
-  },
-]);
+const messages = ref([]);
 
 const tasks = ref([
   {
@@ -361,9 +392,61 @@ const isLoggedIn = computed(
 const userAvatar = computed(() => userStore.user?.avatar || "");
 const userName = computed(() => userStore.user?.nickname || "");
 
+// 是否有未读消息
+const hasUnreadMessages = computed(() => {
+  return messages.value.some((msg) => msg.status === "0");
+});
+
+// 未读消息数量
+const unreadMessageCount = computed(() => {
+  return messages.value.filter((msg) => msg.status === "0").length;
+});
+
+// oa未读
+const oaMessagesList = ref([]);
+onMounted(async () => {
+  if (isLoggedIn) {
+    await getNotifyMessages(userStore.getUser.username);
+
+    const messageList = await getNotifyMessageList(userStore.getUser.username);
+    messages.value = messageList;
+
+    console.log("消息中心数据:", messages.value);
+  }
+});
+
+function timestampToDateTime(timestamp) {
+  // 兼容 10位(秒) / 13位(毫秒)
+  const len = String(timestamp).length;
+  const date = new Date(Number(timestamp) * (len === 10 ? 1000 : 1));
+
+  // 年
+  const year = date.getFullYear();
+  // 月(0~11 → +1)
+  const month = String(date.getMonth() + 1).padStart(2, "0");
+  // 日
+  const day = String(date.getDate()).padStart(2, "0");
+  // 时
+  const hours = String(date.getHours()).padStart(2, "0");
+  // 分
+  const minutes = String(date.getMinutes()).padStart(2, "0");
+  // 秒
+  const seconds = String(date.getSeconds()).padStart(2, "0");
+
+  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+}
+
 const router = useRouter();
 const drawer = ref(false);
 
+// 全部标为已读
+const markAllAsRead = async () => {
+  await markMessageAsRead(userStore.getUser.username);
+  // 刷新消息列表
+  const messageList = await getNotifyMessageList(userStore.getUser.username);
+  messages.value = messageList;
+};
+
 const goHome = () => {
   router.push({ path: "/" });
 };

+ 18 - 0
src/router/index.ts

@@ -38,6 +38,24 @@ const routes: RouteRecordRaw[] = [
       title: "DeepOil 智慧经营平台 | 流程门户",
     },
   },
+
+  {
+    path: "/todo-list",
+    name: "TodoList",
+    component: () => import("@/views/flow/todoList.vue"),
+    meta: {
+      title: "DeepOil 智慧经营平台 | 待办列表",
+    },
+  },
+
+  {
+    path: "/crm-todo-list",
+    name: "CRMTodoList",
+    component: () => import("@/views/flow/crmTodoList.vue"),
+    meta: {
+      title: "DeepOil 智慧经营平台 | CRM待办列表",
+    },
+  },
 ];
 
 const router = createRouter({

+ 169 - 0
src/views/flow/crmTodoList.vue

@@ -0,0 +1,169 @@
+<template>
+  <div class="todo-list">
+    <Header />
+
+    <div class="content mt-15">
+      <div class="flex gap-5 items-center mb-4 bg-[#f9f9f9] py-2">
+        <p class="flex items-center">
+          <Icon
+            icon="mynaui:arrow-up-down"
+            class="icon pr-1 h-6 w-6"
+            color="#014099"
+          />CRM待办任务列表
+        </p>
+
+        <el-button
+          type="primary"
+          round
+          size="default"
+          color="#02409b"
+          @click="router.back()"
+          ><Icon
+            icon="mynaui:corner-up-left"
+            class="icon pr-1"
+            width="20"
+            height="20"
+          />返回</el-button
+        >
+      </div>
+      <el-table
+        :data="oaTasks"
+        style="width: 100%"
+        :empty-text="loading ? '加载中...' : '暂无数据'"
+        height="80vh"
+        :header-cell-style="{
+          backgroundColor: '#e9f7ff',
+          color: 'black',
+          fontWeight: '400',
+        }"
+        :cell-style="{
+          color: 'black',
+        }"
+      >
+        <el-table-column
+          type="index"
+          label="序号"
+          width="80"
+          fixed="left"
+          align="center"
+        />
+        <el-table-column
+          prop="instTitle"
+          label="流程标题"
+          min-width="220"
+          fixed="left"
+          align="center"
+        />
+        <el-table-column
+          prop="entityTypeId"
+          label="业务类型"
+          width="140"
+          align="center"
+        />
+        <el-table-column
+          prop="priority"
+          label="优先级别"
+          width="100"
+          align="center"
+        />
+
+        <el-table-column
+          prop="createAt"
+          label="任务创建时间"
+          width="180"
+          align="center"
+        />
+
+        <el-table-column
+          prop="endAt"
+          label="任务完成时间"
+          width="180"
+          align="center"
+        />
+
+        <el-table-column
+          prop="submitterId"
+          label="任务提交人"
+          width="120"
+          align="center"
+        />
+
+        <el-table-column
+          prop="status"
+          label="任务状态"
+          width="100"
+          align="center"
+        />
+
+        <el-table-column label="操作" width="120" fixed="right" align="center">
+          <template #default="scope">
+            <span
+              class="text-[#02409b] cursor-pointer"
+              @click="goBackPage(scope.row)"
+              >处理</span
+            >
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+
+    <Footer />
+  </div>
+</template>
+
+<script setup>
+import Header from "@components/home/header.vue";
+import { ref, onMounted } from "vue";
+import { getCRMTasks, ssoLogin } from "@/api/user";
+import { useUserStore } from "@/stores/useUserStore";
+import { Icon } from "@iconify/vue";
+import router from "@/router";
+const userStore = useUserStore();
+
+const oaTasks = ref([]);
+const loading = ref(false);
+
+const goBackPage = async (row) => {
+  const res = await ssoLogin({
+    username: userStore.getUser.username,
+  });
+
+  if (res) {
+    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}`;
+    }, 0);
+  }
+};
+onMounted(async () => {
+  if (userStore.getUser.username) {
+    loading.value = true;
+    try {
+      const res = await getCRMTasks(userStore.getUser.username);
+      oaTasks.value = res.todoList;
+    } finally {
+      loading.value = false;
+    }
+  }
+});
+</script>
+
+<style scoped>
+.todo-list {
+  display: flex;
+  flex-direction: column;
+  min-height: 100%;
+}
+
+.content {
+  padding: 16px 20px;
+  margin-top: 100px;
+}
+</style>

+ 17 - 4
src/views/flow/index.vue

@@ -5,7 +5,8 @@
       <div class="hero-inner">
         <h1 class="hero-title">下午好,{{ userStore.getUser.nickname }}</h1>
         <p class="hero-desc">
-          今天是 {{ new Date().toLocaleDateString() }}。您有 5 条流程待处理。
+          今天是 {{ new Date().toLocaleDateString() }}。您有
+          {{ stats[0].number }}条流程待处理。
         </p>
       </div>
       <div class="opration">
@@ -132,6 +133,8 @@ import { getFlows, ssoLogin, getOATasks, getCRMTasks } from "@/api/user";
 import { useUserStore } from "@/stores/useUserStore";
 import { getAccessToken } from "@/utils/auth";
 import * as echarts from "echarts";
+import { useRouter } from "vue-router";
+const router = useRouter();
 
 const userStore = useUserStore();
 
@@ -385,9 +388,19 @@ const go = async (item) => {
 const handleDetailClick = (task, categoryTitle) => {
   console.log(`点击了 ${categoryTitle} 中的 ${task.name}: ${task.value}`);
   // 示例:根据类型跳转
-  // if (categoryTitle === '我的待办') {
-  //   router.push({ path: '/flow/todo', query: { type: task.name.toLowerCase() } });
-  // }
+  if (task.name === "OA" && categoryTitle === "我的待办") {
+    router.push({
+      path: "/todo-list",
+      query: { type: task.name.toLowerCase() },
+    });
+  }
+
+  if (task.name === "CRM" && categoryTitle === "我的待办") {
+    router.push({
+      path: "/crm-todo-list",
+      query: { type: task.name.toLowerCase() },
+    });
+  }
 };
 
 let oaTasks = ref([]);

+ 199 - 0
src/views/flow/todoList.vue

@@ -0,0 +1,199 @@
+<template>
+  <div class="todo-list">
+    <Header />
+
+    <div class="content mt-15">
+      <div class="flex gap-5 items-center mb-4 bg-[#f9f9f9] py-2">
+        <p class="flex items-center">
+          <Icon
+            icon="mynaui:arrow-up-down"
+            class="icon pr-1 h-6 w-6"
+            color="#014099"
+          />待办任务列表
+        </p>
+
+        <el-button
+          type="primary"
+          round
+          size="default"
+          color="#02409b"
+          @click="router.back()"
+          ><Icon
+            icon="mynaui:corner-up-left"
+            class="icon pr-1"
+            width="20"
+            height="20"
+          />返回</el-button
+        >
+      </div>
+      <el-table
+        :data="oaTasks"
+        style="width: 100%"
+        :empty-text="loading ? '加载中...' : '暂无数据'"
+        height="80vh"
+        :header-cell-style="{
+          backgroundColor: '#e9f7ff',
+          color: 'black',
+          fontWeight: '400',
+        }"
+        :cell-style="{
+          color: 'black',
+        }"
+      >
+        <el-table-column
+          type="index"
+          label="序号"
+          width="80"
+          fixed="left"
+          align="center"
+        />
+        <el-table-column
+          prop="requestName"
+          label="请求标题"
+          min-width="220"
+          fixed="left"
+          align="center"
+        />
+        <el-table-column
+          prop="workflowBaseInfo.workflowTypeName"
+          label="流程类型"
+          width="140"
+          align="center"
+        />
+        <el-table-column
+          prop="requestLevel"
+          label="紧急程度"
+          width="100"
+          align="center"
+        />
+        <el-table-column
+          prop="status"
+          label="路径状态"
+          width="100"
+          align="center"
+        />
+        <el-table-column
+          prop="sysName"
+          label="系统名称"
+          width="140"
+          align="center"
+        />
+        <el-table-column
+          prop="createTime"
+          label="创建时间"
+          width="180"
+          align="center"
+        />
+
+        <el-table-column
+          prop="creatorName"
+          label="创建人名称"
+          width="120"
+          align="center"
+        />
+
+        <el-table-column
+          prop="currentNodeName"
+          label="当前节点名称"
+          width="140"
+          align="center"
+        />
+        <el-table-column
+          prop="lastOperateTime"
+          label="最后操作时间"
+          width="180"
+          align="center"
+        />
+
+        <el-table-column
+          prop="lastOperatorName"
+          label="最后操作人名称"
+          width="140"
+          align="center"
+        />
+        <el-table-column
+          prop="receiveTime"
+          label="接收时间"
+          width="180"
+          align="center"
+        />
+
+        <el-table-column
+          prop="workflowBaseInfo.workflowName"
+          label="流程名称"
+          min-width="200"
+          align="center"
+        />
+
+        <el-table-column label="操作" width="120" fixed="right" align="center">
+          <template #default="scope">
+            <span
+              class="text-[#02409b] cursor-pointer"
+              @click="goBackPage(scope.row)"
+              >处理</span
+            >
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+
+    <Footer />
+  </div>
+</template>
+
+<script setup>
+import Header from "@components/home/header.vue";
+import { ref, onMounted } from "vue";
+import { getOATasks, ssoLogin } from "@/api/user";
+import { useUserStore } from "@/stores/useUserStore";
+import { Icon } from "@iconify/vue";
+import router from "@/router";
+const userStore = useUserStore();
+
+const oaTasks = ref([]);
+const loading = ref(false);
+
+const goBackPage = async (row) => {
+  const res = await ssoLogin({
+    username: userStore.getUser.username,
+  });
+
+  if (res) {
+    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}`;
+    }, 0);
+  }
+};
+onMounted(async () => {
+  if (userStore.getUser.username) {
+    loading.value = true;
+    try {
+      const res = await getOATasks(userStore.getUser.username);
+      oaTasks.value = res.todoList;
+    } finally {
+      loading.value = false;
+    }
+  }
+});
+</script>
+
+<style scoped>
+.todo-list {
+  display: flex;
+  flex-direction: column;
+  min-height: 100%;
+}
+
+.content {
+  padding: 16px 20px;
+  margin-top: 100px;
+}
+</style>

+ 15 - 27
src/views/index.vue

@@ -9,39 +9,27 @@
 
       <div
         class="absolute inset-0 flex transition-transform duration-700 ease-in-out"
-        :style="{ transform: `translateX(-${currentBgIndex * 100}%)` }"
       >
-        <div
-          v-for="(img, index) in bgImages"
-          :key="index"
-          class="min-w-full h-full bg-cover bg-center"
-          :style="{ backgroundImage: `url(${img})` }"
-        ></div>
+        <div class="min-w-full h-full relative">
+          <video
+            class="absolute inset-0 w-full h-full object-cover"
+            autoplay
+            muted
+            loop
+            playsinline
+            :src="bgvideo"
+          >
+            您的浏览器不支持 HTML5 视频。
+          </video>
+        </div>
       </div>
 
-      <!-- 指示器 -->
       <div
-        class="absolute bottom-4 left-1/2 transform -translate-x-1/2 z-20 flex gap-2"
-      >
-        <span
-          v-for="(img, index) in bgImages"
-          :key="index"
-          class="block w-2 h-2 rounded-full cursor-pointer transition-all duration-300"
-          :class="
-            currentBgIndex === index
-              ? 'bg-[#0644a1] w-6'
-              : 'bg-white/50 hover:bg-white/80'
-          "
-          @click="currentBgIndex = index"
-        ></span>
-      </div>
-
-      <!-- <div
         class="relative z-10 w-full mx-auto h-full px-20 flex items-center mt-5 pt-10"
       >
         <div class="max-w-4xl">
           <h1
-            class="text-2xl md:text-4xl pt-2 font-bold leading-tight slide-up-fade-in"
+            class="text-2xl md:text-4xl text-white/90 pt-2 font-bold leading-tight slide-up-fade-in"
           >
             山东科瑞石油技术门户网站 · DeepOil 智慧经营平台
           </h1>
@@ -68,7 +56,7 @@
             </a>
           </div>
         </div>
-      </div> -->
+      </div>
     </section>
 
     <section
@@ -109,7 +97,7 @@ import img3 from "@/assets/images/3.jpg";
 import caiwu from "@/assets/images/caiwu.png";
 import banner1 from "@/assets/images/banner1.jpg";
 import banner2 from "@/assets/images/model.jpeg";
-
+import bgvideo from "@/assets/bg.mp4";
 import bg2 from "@/assets/images/e4.png";
 import g1 from "@/assets/images/g1.png";
 import { useRouter } from "vue-router";