Просмотр исходного кода

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

yanghao 1 день назад
Родитель
Сommit
a7466d6e88

+ 3 - 0
components.d.ts

@@ -24,7 +24,10 @@ declare module 'vue' {
     ElFormItem: typeof import('element-plus/es')['ElFormItem']
     ElIcon: typeof import('element-plus/es')['ElIcon']
     ElInput: typeof import('element-plus/es')['ElInput']
+    ElPagination: typeof import('element-plus/es')['ElPagination']
     ElPopover: typeof import('element-plus/es')['ElPopover']
+    ElSkeleton: typeof import('element-plus/es')['ElSkeleton']
+    ElSkeletonItem: typeof import('element-plus/es')['ElSkeletonItem']
     ElTable: typeof import('element-plus/es')['ElTable']
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
     ElTabPane: typeof import('element-plus/es')['ElTabPane']

+ 18 - 4
src/api/user.ts

@@ -93,16 +93,30 @@ export const getFlows = async (params) => {
 };
 
 // oa待办任务
-export const getOATasks = async (id) => {
+export const getOATasks = async (params) => {
   return await request.get({
-    url: "/admin-api/portal/todo/oa?workcode=" + id,
+    url:
+      "/admin-api/portal/todo/oa?workcode=" +
+      params.id +
+      "&pageNo=" +
+      params.pageNum +
+      "&pageSize=" +
+      params.pageSize,
   });
 };
 
 // crm待办任务
-export const getCRMTasks = async (id) => {
+export const getCRMTasks = async (params) => {
   return await request.get({
-    url: "/admin-api/portal/todo/crm?workcode=" + id,
+    url:
+      "/admin-api/portal/todo/crm?workcode=" +
+      params.id +
+      "&type=" +
+      params.type +
+      "&pageNo=" +
+      params.pageNum +
+      "&pageSize=" +
+      params.pageSize,
   });
 };
 

+ 13 - 0
src/components/home/header.vue

@@ -423,6 +423,19 @@ onMounted(async () => {
     messages.value = messageList;
   }
 
+  setInterval(
+    async () => {
+      if (isLoggedIn.value) {
+        await getNotifyMessages(userStore.getUser.username);
+        const messageList = await getNotifyMessageList(
+          userStore.getUser.username,
+        );
+        messages.value = messageList;
+      }
+    },
+    1000 * 60 * 1,
+  );
+
   setInterval(
     () => {
       if (userStore.getIsSetUser) {

+ 7 - 0
src/main.ts

@@ -6,6 +6,9 @@ import { setupStore } from "@/stores";
 import "./assets/style/main.css";
 import "element-plus/dist/index.css";
 
+import ElementPlus from "element-plus";
+import zhCn from "element-plus/es/locale/lang/zh-cn";
+
 import App from "./App.vue";
 import router from "./router";
 
@@ -13,6 +16,10 @@ const app = createApp(App);
 
 setupStore(app);
 
+app.use(ElementPlus, {
+  locale: zhCn,
+});
+
 app.use(router);
 
 app.mount("#app");

+ 18 - 0
src/router/index.ts

@@ -48,6 +48,15 @@ const routes: RouteRecordRaw[] = [
     },
   },
 
+  {
+    path: "/oa-done-list",
+    name: "OADoneList",
+    component: () => import("@/views/flow/oaDoneList.vue"),
+    meta: {
+      title: "DeepOil 智慧经营平台 | 已办列表",
+    },
+  },
+
   {
     path: "/crm-todo-list",
     name: "CRMTodoList",
@@ -56,6 +65,15 @@ const routes: RouteRecordRaw[] = [
       title: "DeepOil 智慧经营平台 | CRM待办列表",
     },
   },
+
+  {
+    path: "/crm-done-list",
+    name: "CRMDoneList",
+    component: () => import("@/views/flow/crmDoneList.vue"),
+    meta: {
+      title: "DeepOil 智慧经营平台 | CRM已办列表",
+    },
+  },
 ];
 
 const router = createRouter({

+ 412 - 0
src/views/flow/crmDoneList.vue

@@ -0,0 +1,412 @@
+<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>
+      <div class="h-[80vh]">
+        <el-table
+          :data="oaTasks"
+          style="width: 100%"
+          height="70vh"
+          :empty-text="loading ? '加载中...' : '暂无数据'"
+          :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"
+          >
+            <template #default="scope">
+              <el-tag v-if="scope.row.priority == 50" type="warning" size="mini"
+                >一般</el-tag
+              >
+              <el-tag
+                v-else-if="scope.row.priority === 90"
+                type="danger"
+                size="mini"
+              >
+                紧急
+              </el-tag>
+
+              <span v-else></span>
+            </template>
+          </el-table-column>
+
+          <el-table-column
+            prop="createAt"
+            label="任务提交时间"
+            width="180"
+            align="center"
+          >
+            <template #default="scope">
+              {{ timestampToDateTime(scope.row.submitAt) }}
+            </template>
+          </el-table-column>
+
+          <el-table-column
+            prop="createAt"
+            label="任务创建时间"
+            width="180"
+            align="center"
+          >
+            <template #default="scope">
+              {{ timestampToDateTime(scope.row.createdAt) }}
+            </template>
+          </el-table-column>
+
+          <el-table-column
+            prop="endAt"
+            label="任务完成时间"
+            width="180"
+            align="center"
+          >
+            <template #default="scope">
+              {{ timestampToDateTime(scope.row.endAt) }}
+            </template>
+          </el-table-column>
+
+          <el-table-column
+            prop="userName"
+            label="任务提交人"
+            width="120"
+            align="center"
+          />
+
+          <el-table-column
+            prop="status"
+            label="任务状态"
+            width="100"
+            align="center"
+          >
+            <template #default="scope">
+              <el-tag v-if="scope.row.status === 1" type="info" size="mini"
+                >待提交</el-tag
+              >
+              <el-tag
+                v-else-if="scope.row.status === 2"
+                type="info"
+                size="mini"
+              >
+                待重新提交
+              </el-tag>
+
+              <el-tag
+                v-else-if="scope.row.status === 3"
+                type="info"
+                size="mini"
+              >
+                待提交到退回节点
+              </el-tag>
+
+              <el-tag
+                v-else-if="scope.row.status === 4"
+                type="primary"
+                size="mini"
+              >
+                已提交
+              </el-tag>
+
+              <el-tag
+                v-else-if="scope.row.status === 5"
+                type="info"
+                size="mini"
+              >
+                待办理
+              </el-tag>
+
+              <el-tag
+                v-else-if="scope.row.status === 6"
+                type="success"
+                size="mini"
+              >
+                已同意
+              </el-tag>
+
+              <el-tag
+                v-else-if="scope.row.status === 7"
+                type="danger"
+                size="mini"
+              >
+                已拒绝
+              </el-tag>
+
+              <el-tag
+                v-else-if="scope.row.status === 8"
+                type="danger"
+                size="mini"
+              >
+                已转办
+              </el-tag>
+
+              <el-tag
+                v-else-if="scope.row.status === 9"
+                type="primary"
+                size="mini"
+              >
+                已抄送
+              </el-tag>
+
+              <el-tag
+                v-else-if="scope.row.status === 10"
+                type="warning"
+                size="mini"
+              >
+                加签挂起
+              </el-tag>
+
+              <el-tag
+                v-else-if="scope.row.status === 11"
+                type="warning"
+                size="mini"
+              >
+                任务挂起
+              </el-tag>
+
+              <el-tag
+                v-else-if="scope.row.status === 12"
+                type="warning"
+                size="mini"
+              >
+                已收回
+              </el-tag>
+
+              <el-tag
+                v-else-if="scope.row.status === 13"
+                type="warning"
+                size="mini"
+              >
+                已移交
+              </el-tag>
+
+              <el-tag
+                v-else-if="scope.row.status === 14"
+                type="warning"
+                size="mini"
+              >
+                已委托
+              </el-tag>
+
+              <el-tag
+                v-else-if="scope.row.status === 99"
+                type="success"
+                size="mini"
+              >
+                无需办理
+              </el-tag>
+              <span v-else></span>
+            </template>
+          </el-table-column>
+
+          <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 class="pagination-wrap">
+          <el-pagination
+            v-model:current-page="pagination.pageNum"
+            v-model:page-size="pagination.pageSize"
+            :total="pagination.total"
+            :page-sizes="[10, 20, 50, 100]"
+            layout="total, sizes, prev, pager, next"
+            background
+            @current-change="handleCurrentChange"
+            @size-change="handleSizeChange"
+          />
+        </div>
+      </div>
+    </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 pagination = ref({
+  pageNum: 1,
+  pageSize: 10,
+  total: 0,
+});
+
+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}`;
+  //   }, 50);
+  // }
+};
+
+const handleCurrentChange = async (page) => {
+  pagination.value.pageNum = page;
+  loading.value = true;
+  try {
+    const res = await getCRMTasks({
+      id: userStore.getUser.username,
+      type: "approved",
+      pageNum: pagination.value.pageNum,
+      pageSize: pagination.value.pageSize,
+    });
+    oaTasks.value = res.todoList;
+    pagination.value.total = res.todoCount;
+  } finally {
+    loading.value = false;
+  }
+};
+
+const handleSizeChange = async (size) => {
+  pagination.value.pageSize = size;
+  pagination.value.pageNum = 1;
+  loading.value = true;
+  try {
+    const res = await getCRMTasks({
+      id: userStore.getUser.username,
+      type: "approved",
+      pageNum: pagination.value.pageNum,
+      pageSize: pagination.value.pageSize,
+    });
+    oaTasks.value = res.todoList;
+    pagination.value.total = res.todoCount;
+  } finally {
+    loading.value = false;
+  }
+};
+
+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}`;
+}
+onMounted(async () => {
+  if (userStore.getUser.username) {
+    loading.value = true;
+    try {
+      const res = await getCRMTasks({
+        id: userStore.getUser.username,
+        type: "approved",
+        pageNum: pagination.value.pageNum,
+        pageSize: pagination.value.pageSize,
+      });
+      oaTasks.value = res.todoList;
+      pagination.value.total = res.todoCount;
+    } finally {
+      loading.value = false;
+    }
+  }
+});
+</script>
+
+<style scoped>
+.todo-list {
+  display: flex;
+  flex-direction: column;
+  min-height: 100%;
+}
+
+.content {
+  padding: 16px 20px;
+  margin-top: 100px;
+}
+
+.pagination-wrap {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 16px;
+}
+</style>

+ 326 - 80
src/views/flow/crmTodoList.vue

@@ -26,85 +26,257 @@
           />返回</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 class="h-[80vh]">
+        <el-table
+          :data="oaTasks"
+          style="width: 100%"
+          height="70vh"
+          :empty-text="loading ? '加载中...' : '暂无数据'"
+          :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"
+          >
+            <template #default="scope">
+              <el-tag v-if="scope.row.priority == 50" type="warning" size="mini"
+                >一般</el-tag
+              >
+              <el-tag
+                v-else-if="scope.row.priority === 90"
+                type="danger"
+                size="mini"
+              >
+                紧急
+              </el-tag>
+
+              <span v-else></span>
+            </template>
+          </el-table-column>
+
+          <el-table-column
+            prop="createAt"
+            label="任务提交时间"
+            width="180"
+            align="center"
+          >
+            <template #default="scope">
+              {{ timestampToDateTime(scope.row.submitAt) }}
+            </template>
+          </el-table-column>
+
+          <el-table-column
+            prop="createAt"
+            label="任务创建时间"
+            width="180"
+            align="center"
+          >
+            <template #default="scope">
+              {{ timestampToDateTime(scope.row.createdAt) }}
+            </template>
+          </el-table-column>
+
+          <el-table-column
+            prop="endAt"
+            label="任务完成时间"
+            width="180"
+            align="center"
+          >
+            <template #default="scope">
+              {{ timestampToDateTime(scope.row.endAt) }}
+            </template>
+          </el-table-column>
+
+          <el-table-column
+            prop="userName"
+            label="任务提交人"
+            width="120"
+            align="center"
+          />
+
+          <el-table-column
+            prop="status"
+            label="任务状态"
+            width="100"
+            align="center"
+          >
+            <template #default="scope">
+              <el-tag v-if="scope.row.status === 1" type="info" size="mini"
+                >待提交</el-tag
+              >
+              <el-tag
+                v-else-if="scope.row.status === 2"
+                type="info"
+                size="mini"
+              >
+                待重新提交
+              </el-tag>
+
+              <el-tag
+                v-else-if="scope.row.status === 3"
+                type="info"
+                size="mini"
+              >
+                待提交到退回节点
+              </el-tag>
+
+              <el-tag
+                v-else-if="scope.row.status === 4"
+                type="primary"
+                size="mini"
+              >
+                已提交
+              </el-tag>
+
+              <el-tag
+                v-else-if="scope.row.status === 5"
+                type="info"
+                size="mini"
+              >
+                待办理
+              </el-tag>
+
+              <el-tag
+                v-else-if="scope.row.status === 6"
+                type="success"
+                size="mini"
+              >
+                已同意
+              </el-tag>
+
+              <el-tag
+                v-else-if="scope.row.status === 7"
+                type="danger"
+                size="mini"
+              >
+                已拒绝
+              </el-tag>
+
+              <el-tag
+                v-else-if="scope.row.status === 8"
+                type="danger"
+                size="mini"
+              >
+                已转办
+              </el-tag>
+
+              <el-tag
+                v-else-if="scope.row.status === 9"
+                type="primary"
+                size="mini"
+              >
+                已抄送
+              </el-tag>
+
+              <el-tag
+                v-else-if="scope.row.status === 10"
+                type="warning"
+                size="mini"
+              >
+                加签挂起
+              </el-tag>
+
+              <el-tag
+                v-else-if="scope.row.status === 11"
+                type="warning"
+                size="mini"
+              >
+                任务挂起
+              </el-tag>
+
+              <el-tag
+                v-else-if="scope.row.status === 12"
+                type="warning"
+                size="mini"
+              >
+                已收回
+              </el-tag>
+
+              <el-tag
+                v-else-if="scope.row.status === 13"
+                type="warning"
+                size="mini"
+              >
+                已移交
+              </el-tag>
+
+              <el-tag
+                v-else-if="scope.row.status === 14"
+                type="warning"
+                size="mini"
+              >
+                已委托
+              </el-tag>
+
+              <el-tag
+                v-else-if="scope.row.status === 99"
+                type="success"
+                size="mini"
+              >
+                无需办理
+              </el-tag>
+              <span v-else></span>
+            </template>
+          </el-table-column>
+
+          <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 class="pagination-wrap">
+          <el-pagination
+            v-model:current-page="pagination.pageNum"
+            v-model:page-size="pagination.pageSize"
+            :total="pagination.total"
+            :page-sizes="[10, 20, 50, 100]"
+            layout="total, sizes, prev, pager, next"
+            background
+            @current-change="handleCurrentChange"
+            @size-change="handleSizeChange"
+          />
+        </div>
+      </div>
     </div>
 
     <Footer />
@@ -123,6 +295,47 @@ const userStore = useUserStore();
 const oaTasks = ref([]);
 const loading = ref(false);
 
+const pagination = ref({
+  pageNum: 1,
+  pageSize: 10,
+  total: 0,
+});
+
+const handleCurrentChange = async (page) => {
+  pagination.value.pageNum = page;
+  loading.value = true;
+  try {
+    const res = await getCRMTasks({
+      id: userStore.getUser.username,
+      type: "pending",
+      pageNum: pagination.value.pageNum,
+      pageSize: pagination.value.pageSize,
+    });
+    oaTasks.value = res.todoList;
+    pagination.value.total = res.todoCount;
+  } finally {
+    loading.value = false;
+  }
+};
+
+const handleSizeChange = async (size) => {
+  pagination.value.pageSize = size;
+  pagination.value.pageNum = 1;
+  loading.value = true;
+  try {
+    const res = await getCRMTasks({
+      id: userStore.getUser.username,
+      type: "pending",
+      pageNum: pagination.value.pageNum,
+      pageSize: pagination.value.pageSize,
+    });
+    oaTasks.value = res.todoList;
+    pagination.value.total = res.todoCount;
+  } finally {
+    loading.value = false;
+  }
+};
+
 const goBackPage = async (row) => {
   // const res = await ssoLogin({
   //   username: userStore.getUser.username,
@@ -139,12 +352,39 @@ const goBackPage = async (row) => {
   //   }, 50);
   // }
 };
+
+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}`;
+}
 onMounted(async () => {
   if (userStore.getUser.username) {
     loading.value = true;
     try {
-      const res = await getCRMTasks(userStore.getUser.username);
+      const res = await getCRMTasks({
+        id: userStore.getUser.username,
+        type: "pending",
+        pageNum: pagination.value.pageNum,
+        pageSize: pagination.value.pageSize,
+      });
       oaTasks.value = res.todoList;
+      pagination.value.total = res.todoCount;
     } finally {
       loading.value = false;
     }
@@ -163,4 +403,10 @@ onMounted(async () => {
   padding: 16px 20px;
   margin-top: 100px;
 }
+
+.pagination-wrap {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 16px;
+}
 </style>

+ 117 - 46
src/views/flow/index.vue

@@ -30,12 +30,11 @@
       <div class="total-card" v-for="(item, index) in stats" :key="index">
         <el-popover
           placement="top"
-          :width="200"
+          :width="100"
           trigger="hover"
           popper-class="glass-popover"
           :disabled="getDetailList(index).length === 0"
-          raw-content
-          transition="el-zoom-in-top"
+          transition="el-zoom-in-left"
         >
           <template #reference>
             <div class="card-wrapper">
@@ -45,7 +44,15 @@
               </div>
               <div class="card-content">
                 <p class="card-title">{{ item.title }}</p>
-                <p class="card-number">{{ item.number }}</p>
+                <el-skeleton :rows="1" :animated="true" :loading="statsLoading">
+                  <template #template>
+                    <el-skeleton-item
+                      variant="text"
+                      style="width: 60%; height: 32px; border-radius: 50px"
+                    />
+                  </template>
+                  <p class="card-number">{{ item.number }}</p>
+                </el-skeleton>
               </div>
             </div>
           </template>
@@ -174,6 +181,7 @@ import { useUserStore } from "@/stores/useUserStore";
 import { getAccessToken } from "@/utils/auth";
 import * as echarts from "echarts";
 import { useRouter } from "vue-router";
+import { pa } from "element-plus/es/locales.mjs";
 const router = useRouter();
 
 const userStore = useUserStore();
@@ -411,7 +419,7 @@ const go = async (item) => {
 
         setTimeout(function () {
           newTab.location.href = item.flowUrl;
-        }, 50);
+        }, 100);
       }
     }
 
@@ -425,7 +433,7 @@ const go = async (item) => {
 
       setTimeout(function () {
         newTab.location.href = item.flowUrl;
-      }, 50);
+      }, 100);
     }
   } else {
     router.push({ path: "/login" });
@@ -442,16 +450,31 @@ const handleDetailClick = (task, categoryTitle) => {
     });
   }
 
+  if (task.name === "OA" && categoryTitle === "已办事项") {
+    router.push({
+      path: "/oa-done-list",
+      query: { type: task.name.toLowerCase() },
+    });
+  }
+
   if (task.name === "CRM" && categoryTitle === "我的待办") {
     router.push({
       path: "/crm-todo-list",
       query: { type: task.name.toLowerCase() },
     });
   }
+
+  if (task.name === "CRM" && categoryTitle === "已办事项") {
+    router.push({
+      path: "/crm-done-list",
+      query: { type: task.name.toLowerCase() },
+    });
+  }
 };
 
 let oaTasks = ref([]);
 let crmTasks = ref([]);
+const statsLoading = ref(true);
 
 const stats = ref([
   {
@@ -469,21 +492,6 @@ const stats = ref([
     bgcolor: "#eff6ff",
     color: "#2563eb",
   },
-  {
-    icon: "mdi:arrow-right-bold-box-outline",
-    title: "发起流程",
-    number: 0,
-    extra: "85% 准时",
-    bgcolor: "#eff6ff",
-    color: "#2563eb",
-  },
-  {
-    icon: "mdi:file-document-outline",
-    title: "草稿箱",
-    number: 0,
-    bgcolor: "#f8fafc",
-    color: "#475569",
-  },
 ]);
 
 const todoCount = ref([
@@ -497,18 +505,6 @@ const doneCount = ref([
   { name: "CRM", value: 0 },
 ]);
 
-// 发起流程
-const startCount = ref([
-  { name: "OA", value: 0 },
-  { name: "CRM", value: 0 },
-]);
-
-// 草稿箱
-const drafts = ref([
-  { name: "OA", value: 0 },
-  { name: "CRM", value: 0 },
-]);
-
 onMounted(async () => {
   getAll();
   // 等待 DOM 与样式生效,避免移动端首屏尺寸为 0
@@ -528,20 +524,95 @@ onMounted(async () => {
   });
 
   if (userStore.getUser.username) {
-    const res = await getOATasks(userStore.getUser.username);
-    oaTasks.value = res;
+    try {
+      const res = await getOATasks({
+        id: userStore.getUser.username,
+        pageNum: 1,
+        pageSize: 99,
+      });
+      oaTasks.value = res;
 
-    const crmRes = await getCRMTasks(userStore.getUser.username);
-    crmTasks.value = crmRes;
-  }
+      const crmRes = await getCRMTasks({
+        id: userStore.getUser.username,
+        type: "pending",
+        pageNum: 1,
+        pageSize: 10,
+      });
+      crmTasks.value = crmRes;
+
+      stats.value[0].number =
+        Number(oaTasks.value.todoCount) + Number(crmTasks.value.todoCount);
 
-  stats.value[0].number =
-    Number(oaTasks.value.todoCount) + Number(crmTasks.value.todoCount);
+      todoCount.value = [
+        { name: "OA", value: oaTasks.value.todoCount ?? 0 },
+        { name: "CRM", value: crmTasks.value.todoCount ?? 0 },
+      ];
 
-  todoCount.value = [
-    { name: "OA", value: oaTasks.value.todoCount },
-    { name: "CRM", value: crmTasks.value.todoCount },
-  ];
+      // *****************已办事项统计*************************
+
+      const crmDoneRes = await getCRMTasks({
+        id: userStore.getUser.username,
+        type: "approved",
+        pageNum: 1,
+        pageSize: 10,
+      });
+
+      stats.value[1].number =
+        Number(oaTasks.value.doneCount) + Number(crmDoneRes.todoCount);
+
+      doneCount.value = [
+        { name: "OA", value: oaTasks.value.doneCount ?? 0 },
+        { name: "CRM", value: crmDoneRes.todoCount ?? 0 },
+      ];
+    } finally {
+      statsLoading.value = false;
+    }
+
+    setInterval(
+      async () => {
+        const res = await getOATasks({
+          id: userStore.getUser.username,
+          pageNum: 1,
+          pageSize: 10,
+        });
+        oaTasks.value = res;
+
+        const crmRes = await getCRMTasks({
+          id: userStore.getUser.username,
+          type: "pending",
+          pageNum: 1,
+          pageSize: 10,
+        });
+        crmTasks.value = crmRes;
+
+        stats.value[0].number =
+          Number(oaTasks.value.todoCount) + Number(crmTasks.value.todoCount);
+
+        todoCount.value = [
+          { name: "OA", value: oaTasks.value.todoCount ?? 0 },
+          { name: "CRM", value: crmTasks.value.todoCount ?? 0 },
+        ];
+
+        const crmDoneRes = await getCRMTasks({
+          id: userStore.getUser.username,
+          type: "approved",
+          pageNum: 1,
+          pageSize: 10,
+        });
+
+        stats.value[1].number =
+          Number(oaTasks.value.doneCount) + Number(crmDoneRes.todoCount);
+
+        doneCount.value = [
+          { name: "OA", value: oaTasks.value.doneCount ?? 0 },
+          { name: "CRM", value: crmDoneRes.todoCount ?? 0 },
+        ];
+      },
+      5 * 60 * 1000,
+    ); // 每5分钟刷新一次
+  } else {
+    statsLoading.value = false;
+  }
 });
 
 // 组件卸载时移除监听,防止内存泄漏
@@ -843,8 +914,8 @@ onBeforeUnmount(() => {
 }
 
 .total-card {
-  flex: 1;
-  min-width: 0;
+  /* flex: 1; */
+  width: 300px;
   background: #ffffff;
   border-radius: 16px;
   padding: 20px;

+ 264 - 0
src/views/flow/oaDoneList.vue

@@ -0,0 +1,264 @@
+<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"
+          />OA已办任务列表
+        </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"
+          />&#36820;&#22238;</el-button
+        >
+      </div>
+      <div class="h-[90vh]">
+        <el-table
+          :data="oaTasks"
+          style="width: 100%"
+          height="80vh"
+          :empty-text="
+            loading
+              ? '&#21152;&#36733;&#20013;...'
+              : '&#26242;&#26080;&#25968;&#25454;'
+          "
+          :header-cell-style="{
+            backgroundColor: '#e9f7ff',
+            color: 'black',
+            fontWeight: '400',
+          }"
+          :cell-style="{
+            color: 'black',
+          }"
+        >
+          <el-table-column
+            type="index"
+            label="&#24207;&#21495;"
+            width="80"
+            fixed="left"
+            align="center"
+            :index="getIndex"
+          />
+          <el-table-column
+            prop="requestName"
+            label="&#35831;&#27714;&#26631;&#39064;"
+            min-width="220"
+            fixed="left"
+            align="center"
+          />
+          <el-table-column
+            prop="workflowBaseInfo.workflowTypeName"
+            label="&#27969;&#31243;&#31867;&#22411;"
+            width="140"
+            align="center"
+          />
+          <el-table-column
+            prop="requestLevel"
+            label="&#32039;&#24613;&#31243;&#24230;"
+            width="100"
+            align="center"
+          />
+          <el-table-column
+            prop="status"
+            label="&#36335;&#24452;&#29366;&#24577;"
+            width="100"
+            align="center"
+          />
+          <el-table-column
+            prop="sysName"
+            label="&#31995;&#32479;&#21517;&#31216;"
+            width="140"
+            align="center"
+          />
+          <el-table-column
+            prop="createTime"
+            label="&#21019;&#24314;&#26102;&#38388;"
+            width="180"
+            align="center"
+          />
+
+          <el-table-column
+            prop="creatorName"
+            label="&#21019;&#24314;&#20154;&#21517;&#31216;"
+            width="120"
+            align="center"
+          />
+
+          <el-table-column
+            prop="currentNodeName"
+            label="&#24403;&#21069;&#33410;&#28857;&#21517;&#31216;"
+            width="140"
+            align="center"
+          />
+          <el-table-column
+            prop="lastOperateTime"
+            label="&#26368;&#21518;&#25805;&#20316;&#26102;&#38388;"
+            width="180"
+            align="center"
+          />
+
+          <el-table-column
+            prop="lastOperatorName"
+            label="&#26368;&#21518;&#25805;&#20316;&#20154;&#21517;&#31216;"
+            width="140"
+            align="center"
+          />
+          <el-table-column
+            prop="receiveTime"
+            label="&#25509;&#25910;&#26102;&#38388;"
+            width="180"
+            align="center"
+          />
+
+          <el-table-column
+            prop="workflowBaseInfo.workflowName"
+            label="&#27969;&#31243;&#21517;&#31216;"
+            min-width="200"
+            align="center"
+          />
+
+          <el-table-column
+            label="&#25805;&#20316;"
+            width="120"
+            fixed="right"
+            align="center"
+          >
+            <template #default="scope">
+              <span
+                class="text-[#02409b] cursor-pointer"
+                @click="goBackPage(scope.row)"
+                >&#22788;&#29702;</span
+              >
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <div class="pagination-wrap">
+          <el-pagination
+            v-model:current-page="pagination.pageNum"
+            v-model:page-size="pagination.pageSize"
+            :total="pagination.total"
+            :page-sizes="[10, 20, 50, 100]"
+            layout="total, sizes, prev, pager, next"
+            background
+            @current-change="handleCurrentChange"
+            @size-change="handleSizeChange"
+          />
+        </div>
+      </div>
+    </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 pagination = ref({
+  pageNum: 1,
+  pageSize: 10,
+  total: 0,
+});
+
+const getIndex = (index) => {
+  return (pagination.value.pageNum - 1) * pagination.value.pageSize + index + 1;
+};
+
+const getDoneList = async () => {
+  if (!userStore.getUser.username) return;
+
+  loading.value = true;
+  try {
+    const res = await getOATasks({
+      id: userStore.getUser.username,
+      pageNum: pagination.value.pageNum,
+      pageSize: pagination.value.pageSize,
+    });
+
+    oaTasks.value = res.doneList || [];
+    pagination.value.total = Number(res.doneCount ?? oaTasks.value.length ?? 0);
+  } finally {
+    loading.value = false;
+  }
+};
+
+const handleCurrentChange = (page) => {
+  pagination.value.pageNum = page;
+  getDoneList();
+};
+
+const handleSizeChange = (size) => {
+  pagination.value.pageSize = size;
+  pagination.value.pageNum = 1;
+  getDoneList();
+};
+
+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}`;
+    }, 100);
+  }
+};
+
+onMounted(async () => {
+  if (userStore.getUser.username) {
+    getDoneList();
+  }
+});
+</script>
+
+<style scoped>
+.todo-list {
+  display: flex;
+  flex-direction: column;
+  min-height: 100%;
+}
+
+.content {
+  padding: 16px 20px;
+  margin-top: 100px;
+}
+
+.pagination-wrap {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 16px;
+}
+</style>

+ 182 - 112
src/views/flow/todoList.vue

@@ -9,7 +9,7 @@
             icon="mynaui:arrow-up-down"
             class="icon pr-1 h-6 w-6"
             color="#014099"
-          />待办任务列表
+          />OA待办任务列表
         </p>
 
         <el-button
@@ -26,115 +26,135 @@
           />返回</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 class="h-[80vh]">
+        <el-table
+          :data="oaTasks"
+          style="width: 100%"
+          :empty-text="loading ? '加载中...' : '暂无数据'"
+          height="70vh"
+          :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 class="pagination-wrap">
+          <el-pagination
+            v-model:current-page="pagination.pageNum"
+            v-model:page-size="pagination.pageSize"
+            :total="pagination.total"
+            :page-sizes="[10, 20, 50, 100]"
+            layout="total, sizes, prev, pager, next"
+            background
+            @current-change="handleCurrentChange"
+            @size-change="handleSizeChange"
+          />
+        </div>
+      </div>
     </div>
 
     <Footer />
@@ -153,6 +173,12 @@ const userStore = useUserStore();
 const oaTasks = ref([]);
 const loading = ref(false);
 
+const pagination = ref({
+  pageNum: 1,
+  pageSize: 10,
+  total: 0,
+});
+
 const goBackPage = async (row) => {
   const res = await ssoLogin({
     username: userStore.getUser.username,
@@ -169,15 +195,53 @@ const goBackPage = async (row) => {
 
     setTimeout(function () {
       newTab.location.href = `https://yfoa.keruioil.com/spa/workflow/static4form/index.html?_rdm=1776063595284#/main/workflow/req?requestid=${row.requestId}`;
-    }, 50);
+    }, 100);
   }
 };
+
+const handleCurrentChange = async (page) => {
+  pagination.value.pageNum = page;
+  try {
+    const res = await getOATasks({
+      id: userStore.getUser.username,
+      pageNum: pagination.value.pageNum,
+      pageSize: pagination.value.pageSize,
+    });
+    oaTasks.value = res.todoList;
+    pagination.value.total = Number(res.todoCount);
+  } finally {
+    loading.value = false;
+  }
+};
+
+const handleSizeChange = async (size) => {
+  pagination.value.pageSize = size;
+  pagination.value.pageNum = 1;
+
+  try {
+    const res = await getOATasks({
+      id: userStore.getUser.username,
+      pageNum: pagination.value.pageNum,
+      pageSize: pagination.value.pageSize,
+    });
+    oaTasks.value = res.todoList;
+    pagination.value.total = Number(res.todoCount);
+  } finally {
+    loading.value = false;
+  }
+};
+
 onMounted(async () => {
   if (userStore.getUser.username) {
     loading.value = true;
     try {
-      const res = await getOATasks(userStore.getUser.username);
+      const res = await getOATasks({
+        id: userStore.getUser.username,
+        pageNum: pagination.value.pageNum,
+        pageSize: pagination.value.pageSize,
+      });
       oaTasks.value = res.todoList;
+      pagination.value.total = Number(res.todoCount);
     } finally {
       loading.value = false;
     }
@@ -196,4 +260,10 @@ onMounted(async () => {
   padding: 16px 20px;
   margin-top: 100px;
 }
+
+.pagination-wrap {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 16px;
+}
 </style>

+ 2 - 2
src/views/login.vue

@@ -21,7 +21,7 @@
         <h1 class="text-2xl font-bold text-center">登录</h1>
 
         <!-- 用户名密码登陆 -->
-        <!-- <div>
+        <div>
           <el-form
             :model="form"
             :rules="rules"
@@ -62,7 +62,7 @@
               >
             </div>
           </div>
-        </div> -->
+        </div>
 
         <!-- 钉钉登陆 -->
         <div class="text-center">