ソースを参照

待办移动端适配

yanghao 2 日 前
コミット
2c57bd988b
6 ファイル変更603 行追加3 行削除
  1. 1 0
      package.json
  2. 18 0
      pnpm-lock.yaml
  3. 9 0
      src/router/index.ts
  4. 552 0
      src/views/flow/todoListMobile.vue
  5. 21 1
      src/views/index.vue
  6. 2 2
      src/views/login.vue

+ 1 - 0
package.json

@@ -32,6 +32,7 @@
     "pinia-plugin-persistedstate": "^4.7.1",
     "qs": "^6.14.0",
     "vue": "^3.5.22",
+    "vue-infinite-loading": "3.0.0-alpha.0-0",
     "vue-router": "^4.6.3",
     "vue-types": "^6.0.0",
     "vue3-count-to": "^1.1.2",

+ 18 - 0
pnpm-lock.yaml

@@ -50,6 +50,9 @@ importers:
       vue:
         specifier: ^3.5.22
         version: 3.5.22(typescript@5.9.3)
+      vue-infinite-loading:
+        specifier: 3.0.0-alpha.0-0
+        version: 3.0.0-alpha.0-0(vue@3.5.22(typescript@5.9.3))
       vue-router:
         specifier: ^4.6.3
         version: 4.6.3(vue@3.5.22(typescript@5.9.3))
@@ -1485,6 +1488,9 @@ packages:
     resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
     engines: {node: '>=6'}
 
+  tiny-emitter@2.1.0:
+    resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==}
+
   tinyglobby@0.2.15:
     resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
     engines: {node: '>=12.0.0'}
@@ -1641,6 +1647,11 @@ packages:
       '@vue/composition-api':
         optional: true
 
+  vue-infinite-loading@3.0.0-alpha.0-0:
+    resolution: {integrity: sha512-k51cQtes3hyF3D28Hi1hk1zDmgiJ8EakNnccb9xqHc4MGDKeHizzdrFV+eP70qYFJjRqo2LJ3Ovs+X8/kGNsbA==}
+    peerDependencies:
+      vue: ^3.0.5
+
   vue-router@4.6.3:
     resolution: {integrity: sha512-ARBedLm9YlbvQomnmq91Os7ck6efydTSpRP3nuOKCvgJOHNrhRoJDSKtee8kcL1Vf7nz6U+PMBL+hTvR3bTVQg==}
     peerDependencies:
@@ -3002,6 +3013,8 @@ snapshots:
 
   tapable@2.3.0: {}
 
+  tiny-emitter@2.1.0: {}
+
   tinyglobby@0.2.15:
     dependencies:
       fdir: 6.5.0(picomatch@4.0.3)
@@ -3157,6 +3170,11 @@ snapshots:
     dependencies:
       vue: 3.5.22(typescript@5.9.3)
 
+  vue-infinite-loading@3.0.0-alpha.0-0(vue@3.5.22(typescript@5.9.3)):
+    dependencies:
+      tiny-emitter: 2.1.0
+      vue: 3.5.22(typescript@5.9.3)
+
   vue-router@4.6.3(vue@3.5.22(typescript@5.9.3)):
     dependencies:
       '@vue/devtools-api': 6.6.4

+ 9 - 0
src/router/index.ts

@@ -52,6 +52,15 @@ const routes: RouteRecordRaw[] = [
     },
   },
 
+  {
+    path: "/mobile-todo-list",
+    name: "TodoListMobile",
+    component: () => import("@/views/flow/todoListMobile.vue"),
+    meta: {
+      title: "DeepOil 智慧经营平台 | 待办列表",
+    },
+  },
+
   {
     path: "/oa-done-list",
     name: "OADoneList",

+ 552 - 0
src/views/flow/todoListMobile.vue

@@ -0,0 +1,552 @@
+<template>
+  <div class="todo-list">
+    <Header />
+
+    <div class="content-wrapper mt-15 max-w-[1200px] mx-auto">
+      <div class="nav flex gap-5 items-center mb-4 py-2 pl-4 rounded-sm">
+        <p class="flex items-center text-var(--text-primary)">
+          <Icon
+            icon="mynaui:arrow-up-down"
+            class="icon pr-1 h-8 w-8"
+            color="#014099"
+          />OA待办任务列表
+        </p>
+
+        <el-button
+          type="primary"
+          round
+          size="small"
+          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 v-loading="loading && oaTasks.length === 0" class="table-wrapper">
+        <div class="table-summary mb-4 text-var(--text-secondary)!">
+          共 {{ pagination.total }} 条待办
+        </div>
+
+        <div class="task-scroll-list" infinite-wrapper>
+          <div
+            v-for="(item, index) in oaTasks"
+            :key="item.requestId || `${item.requestName}-${index}`"
+            class="task-card"
+          >
+            <div class="task-card__header">
+              <span class="task-card__index">{{ index + 1 }}</span>
+              <span class="task-card__status">{{
+                item.status || "待处理"
+              }}</span>
+            </div>
+
+            <div class="task-card__title">
+              {{ item.requestName || "--" }}
+            </div>
+
+            <div class="task-card__grid">
+              <div class="task-card__field">
+                <span class="task-card__label">流程类型</span>
+                <span class="task-card__value">{{
+                  item.workflowBaseInfo?.workflowTypeName || "--"
+                }}</span>
+              </div>
+              <div class="task-card__field">
+                <span class="task-card__label">紧急程度</span>
+                <span class="task-card__value">{{
+                  item.requestLevel || "--"
+                }}</span>
+              </div>
+              <div class="task-card__field">
+                <span class="task-card__label">系统名称</span>
+                <span class="task-card__value">{{ item.sysName || "--" }}</span>
+              </div>
+              <div class="task-card__field">
+                <span class="task-card__label">当前节点</span>
+                <span class="task-card__value">{{
+                  item.currentNodeName || "--"
+                }}</span>
+              </div>
+              <div class="task-card__field">
+                <span class="task-card__label">创建人</span>
+                <span class="task-card__value">{{
+                  item.creatorName || "--"
+                }}</span>
+              </div>
+              <div class="task-card__field">
+                <span class="task-card__label">最后处理人</span>
+                <span class="task-card__value">{{
+                  item.lastOperatorName || "--"
+                }}</span>
+              </div>
+              <div class="task-card__field task-card__field--full">
+                <span class="task-card__label">流程名称</span>
+                <span class="task-card__value">{{
+                  item.workflowBaseInfo?.workflowName || "--"
+                }}</span>
+              </div>
+              <div class="task-card__field task-card__field--full">
+                <span class="task-card__label">创建时间</span>
+                <span class="task-card__value">{{
+                  item.createTime || "--"
+                }}</span>
+              </div>
+              <div class="task-card__field task-card__field--full">
+                <span class="task-card__label">最后操作时间</span>
+                <span class="task-card__value">{{
+                  item.lastOperateTime || "--"
+                }}</span>
+              </div>
+              <div class="task-card__field task-card__field--full">
+                <span class="task-card__label">接收时间</span>
+                <span class="task-card__value">{{
+                  item.receiveTime || "--"
+                }}</span>
+              </div>
+            </div>
+
+            <div class="task-card__footer">
+              <span class="task-card__action" @click="goBackPage(item)"
+                >处理</span
+              >
+            </div>
+          </div>
+
+          <InfiniteLoading
+            :identifier="infiniteId"
+            spinner="spiral"
+            @infinite="loadMore"
+          >
+            <template #no-more>
+              <div class="infinite-status">没有更多数据了</div>
+            </template>
+            <template #no-results>
+              <div class="infinite-status">没有更多数据了</div>
+            </template>
+            <template #error>
+              <div class="infinite-status">加载失败,请稍后重试</div>
+            </template>
+          </InfiniteLoading>
+        </div>
+      </div>
+    </div>
+
+    <Footer />
+  </div>
+</template>
+
+<script setup>
+import Header from "@components/home/header.vue";
+import * as dd from "dingtalk-jsapi";
+import { onMounted, ref } from "vue";
+import { getOATasks, ssoLogin } from "@/api/user";
+import { useUserStore } from "@/stores/useUserStore";
+import { Icon } from "@iconify/vue";
+import router from "@/router";
+import { ElLoading } from "element-plus";
+import InfiniteLoading from "vue-infinite-loading";
+
+const userStore = useUserStore();
+
+const oaTasks = ref([]);
+const loading = ref(false);
+const infiniteId = ref(Date.now());
+
+const pagination = ref({
+  pageNum: 1,
+  pageSize: 10,
+  total: 0,
+});
+
+const goBackPage = 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",
+        onSuccess: () => {
+          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 loadingInstance = ElLoading.service({
+        lock: true,
+        text: "姝e湪璺宠浆锛岃绋嶅€?..",
+        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(() => {
+        newTab.location.href = `https://yfoa.keruioil.com/spa/workflow/static4form/index.html?_rdm=1776063595284#/main/workflow/req?requestid=${row.requestId}`;
+        setTimeout(() => {
+          loadingInstance.close();
+        }, 500);
+      }, 100);
+    }
+  }
+};
+
+const loadTaskPage = async () => {
+  const res = await getOATasks({
+    id: userStore.getUser.username,
+    pageNum: pagination.value.pageNum,
+    pageSize: pagination.value.pageSize,
+  });
+
+  pagination.value.total = Number(res?.todoCount || 0);
+  return Array.isArray(res?.todoList) ? res.todoList : [];
+};
+
+const resetList = () => {
+  oaTasks.value = [];
+  pagination.value.pageNum = 1;
+  pagination.value.total = 0;
+  infiniteId.value = Date.now();
+};
+
+const loadMore = async ($state) => {
+  if (!userStore.getUser.username) {
+    $state.complete();
+    return;
+  }
+
+  loading.value = pagination.value.pageNum === 1;
+
+  try {
+    const list = await loadTaskPage();
+
+    if (pagination.value.pageNum === 1) {
+      oaTasks.value = list;
+    } else {
+      oaTasks.value = [...oaTasks.value, ...list];
+    }
+
+    const loadedCount = oaTasks.value.length;
+    const hasMore =
+      list.length === pagination.value.pageSize &&
+      loadedCount < pagination.value.total;
+
+    if (hasMore) {
+      pagination.value.pageNum += 1;
+      $state.loaded();
+    } else {
+      $state.complete();
+    }
+  } catch (error) {
+    $state.error();
+  } finally {
+    loading.value = false;
+  }
+};
+
+onMounted(() => {
+  if (userStore.getUser.username) {
+    resetList();
+  }
+});
+</script>
+
+<style scoped>
+.todo-list {
+  --portal-text: #17345f;
+  --portal-text-muted: #5f6f83;
+  --portal-text-soft: #7f8fa6;
+  --portal-title: #163867;
+  --portal-subtitle: rgba(61, 92, 135, 0.86);
+  --portal-line: rgba(126, 156, 201, 0.24);
+  --portal-card: rgba(255, 255, 255, 0.82);
+  --portal-card-2: rgba(248, 251, 255, 0.94);
+  --portal-card-3: rgba(240, 246, 255, 0.88);
+  --portal-card-4: rgba(231, 239, 251, 0.92);
+  --portal-nav-bg: rgba(255, 255, 255, 0.72);
+  --portal-nav-hover: rgba(219, 232, 252, 0.8);
+  --portal-input-bg: rgba(255, 255, 255, 0.7);
+  --portal-input-hover: rgba(255, 255, 255, 0.92);
+  --portal-shadow: 0 18px 40px rgba(23, 52, 95, 0.12);
+  --portal-shadow-strong: 0 24px 60px rgba(23, 52, 95, 0.16);
+  --portal-accent: #245edb;
+  --portal-accent-2: #4e8cff;
+  --portal-accent-soft: rgba(36, 94, 219, 0.14);
+  --portal-todo-bg: rgba(242, 247, 255, 0.94);
+  --portal-todo-hover: rgba(228, 238, 252, 0.95);
+  --portal-number-todo: #e15a5a;
+  --portal-number-done: #2da04d;
+  color: var(--portal-text);
+  background:
+    radial-gradient(
+      circle at 18% 12%,
+      rgba(83, 126, 255, 0.14),
+      transparent 22%
+    ),
+    radial-gradient(
+      circle at 82% 20%,
+      rgba(71, 148, 255, 0.14),
+      transparent 20%
+    ),
+    radial-gradient(
+      circle at 50% 100%,
+      rgba(97, 142, 247, 0.12),
+      transparent 28%
+    ),
+    linear-gradient(180deg, #eef3f9 0%, #f7faff 46%, #eef3f9 100%);
+}
+
+:global([data-theme="dark"] .todo-list) {
+  --portal-text: #eaf1ff;
+  --portal-text-muted: rgba(234, 241, 255, 0.95);
+  --portal-text-soft: #8a9ab0;
+  --portal-title: #f4f7ff;
+  --portal-subtitle: rgba(188, 205, 255, 0.82);
+  --portal-line: rgba(97, 129, 206, 0.28);
+  --portal-card: rgba(10, 19, 43, 0.8);
+  --portal-card-2: rgba(12, 24, 52, 0.92);
+  --portal-card-3: rgba(17, 25, 48, 0.8);
+  --portal-card-4: rgba(15, 24, 45, 0.82);
+  --portal-nav-bg: rgba(10, 19, 43, 0.8);
+  --portal-nav-hover: rgba(28, 40, 72, 0.8);
+  --portal-input-bg: rgba(255, 255, 255, 0.08);
+  --portal-input-hover: rgba(255, 255, 255, 0.12);
+  --portal-shadow: 0 16px 34px rgba(0, 0, 0, 0.22);
+  --portal-shadow-strong: 0 24px 60px rgba(0, 0, 0, 0.38);
+  --portal-accent: #6e7dff;
+  --portal-accent-2: #8d4dff;
+  --portal-accent-soft: rgba(110, 125, 255, 0.16);
+  --portal-todo-bg: #070e20;
+  --portal-todo-hover: rgba(28, 40, 72, 0.8);
+  --portal-number-todo: #f56c6c;
+  --portal-number-done: #ffffff;
+  --bg-table: #091126;
+  color: var(--portal-text);
+  background:
+    radial-gradient(
+      circle at 18% 12%,
+      rgba(79, 82, 221, 0.34),
+      transparent 22%
+    ),
+    radial-gradient(circle at 82% 20%, rgba(28, 95, 255, 0.2), transparent 20%),
+    radial-gradient(
+      circle at 50% 100%,
+      rgba(103, 46, 255, 0.16),
+      transparent 28%
+    ),
+    linear-gradient(180deg, #040814 0%, #060d1d 46%, #040814 100%);
+}
+
+.nav {
+  background-color: var(--bg-table);
+}
+
+.todo-list {
+  min-height: 100vh;
+  display: flex;
+  flex-direction: column;
+}
+
+.content {
+  padding: 16px 20px;
+  margin-top: 100px;
+}
+
+.pagination-wrap {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 16px;
+}
+
+.news-container {
+  min-height: 100vh;
+  display: flex;
+  flex-direction: column;
+  background-color: #f5f7fa;
+}
+
+.content-wrapper {
+  flex: 1;
+  max-width: 1200px;
+  padding: 20px;
+  width: 100%;
+  box-sizing: border-box;
+}
+
+.page-title {
+  margin-bottom: 20px;
+  color: #303133;
+  border-left: 5px solid #409eff;
+  padding-left: 10px;
+}
+
+.table-wrapper {
+  min-height: 300px;
+  background: #fff;
+  padding: 20px;
+  border-radius: 4px;
+}
+
+.table-title {
+  font-weight: 500;
+  color: #303133;
+}
+
+.table-summary {
+  font-size: 13px;
+}
+
+.task-scroll-list {
+  max-height: 70vh;
+  overflow-y: auto;
+  padding-right: 4px;
+  background-color: transparent;
+}
+
+.task-card {
+  background: transparent !important;
+  border: 1px solid var(--portal-line);
+  border-radius: 18px;
+  padding: 16px;
+  margin-bottom: 14px;
+  box-shadow: var(--portal-shadow);
+}
+
+.task-card__header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 12px;
+  gap: 12px;
+}
+
+.task-card__index {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  width: 28px;
+  height: 28px;
+  border-radius: 999px;
+  background: var(--portal-accent-soft);
+  color: var(--portal-accent);
+  font-size: 13px;
+  font-weight: 600;
+  flex-shrink: 0;
+}
+
+.task-card__status {
+  color: var(--portal-accent);
+  font-size: 12px;
+  background: var(--portal-accent-soft);
+  padding: 4px 10px;
+  border-radius: 999px;
+}
+
+.task-card__title {
+  font-size: 16px;
+  font-weight: 600;
+  line-height: 1.5;
+  color: var(--portal-title);
+  margin-bottom: 14px;
+  word-break: break-word;
+}
+
+.task-card__grid {
+  display: grid;
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+  gap: 12px;
+}
+
+.task-card__field {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+  min-width: 0;
+}
+
+.task-card__field--full {
+  grid-column: 1 / -1;
+}
+
+.task-card__label {
+  font-size: 12px;
+  color: var(--portal-text-soft);
+}
+
+.task-card__value {
+  font-size: 13px;
+  line-height: 1.5;
+  color: var(--portal-text);
+  word-break: break-all;
+}
+
+.task-card__footer {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 16px;
+  padding-top: 12px;
+  border-top: 1px solid var(--portal-line);
+}
+
+.task-card__action {
+  color: #1e90ff;
+  cursor: pointer;
+  font-size: 14px;
+  font-weight: 500;
+}
+
+.infinite-status {
+  color: var(--portal-text-soft);
+  text-align: center;
+  padding: 12px 0;
+  font-size: 13px;
+}
+
+:deep(.table-wrapper) {
+  background: var(--bg-table) !important;
+}
+
+:deep(.el-loading-mask) {
+  background-color: var(--bg-table) !important;
+}
+
+@media (max-width: 640px) {
+  .content-wrapper {
+    padding: 16px;
+  }
+
+  .table-wrapper {
+    padding: 16px;
+  }
+
+  .task-card {
+    padding: 14px;
+    border-radius: 16px;
+  }
+
+  .task-card__grid {
+    grid-template-columns: 1fr;
+  }
+
+  .task-card__field--full {
+    grid-column: auto;
+  }
+}
+</style>

+ 21 - 1
src/views/index.vue

@@ -742,6 +742,8 @@ const handleSearch = () => {
 };
 
 const handleQuickAccessCommand = (command: string) => {
+  let ua2 = navigator.userAgent.toLowerCase();
+  let isMobile = ua2.indexOf("dingtalk") > -1;
   const routeMap: Record<string, string> = {
     "todo-oa": "/todo-list?type=oa",
     "todo-crm": "/crm-todo-list?type=crm",
@@ -751,7 +753,25 @@ const handleQuickAccessCommand = (command: string) => {
     "done-srm": "/srm-done-list?type=srm",
   };
 
-  const target = routeMap[command];
+  const routeMap2: Record<string, string> = {
+    "todo-oa": "/mobile-todo-list?type=oa",
+    "done-oa": "/oa-done-list?type=oa",
+    "todo-crm": "/crm-todo-list?type=crm",
+    "done-crm": "/crm-done-list?type=crm",
+    "todo-srm": "/srm-todo-list?type=srm",
+    "done-srm": "/srm-done-list?type=srm",
+  };
+
+  // const target = routeMap[command];
+  let target = null;
+
+  console.log("isMobile:", isMobile);
+
+  if (isMobile) {
+    target = routeMap2[command];
+  } else {
+    target = routeMap[command];
+  }
   if (target) {
     router.push(target);
   }

+ 2 - 2
src/views/login.vue

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