Browse Source

crm待办移动适配

yanghao 2 days ago
parent
commit
e85e57ce1c
2 changed files with 275 additions and 399 deletions
  1. 1 1
      src/router/index.ts
  2. 274 398
      src/views/flow/crmTodoListMobile.vue

+ 1 - 1
src/router/index.ts

@@ -80,7 +80,7 @@ const routes: RouteRecordRaw[] = [
   },
 
   {
-    path: "/moblie-crm-todo-list",
+    path: "/mobile-crm-todo-list",
     name: "MobileCRMTodoList",
     component: () => import("@/views/flow/crmTodoListMobile.vue"),
     meta: {

+ 274 - 398
src/views/flow/crmTodoListMobile.vue

@@ -26,256 +26,93 @@
           />返回</el-button
         >
       </div>
-      <div v-loading="loading" class="table-wrapper">
-        <el-table
-          v-loading="loading"
-          :data="oaTasks"
-          style="width: 100%"
-          height="70vh"
-          stripe
-          element-loading-text="加载中..."
-          :empty-text="loading ? '' : '暂无数据'"
-          :header-cell-style="{
-            backgroundColor: 'var(--bg-table-head) !important',
-            color: 'var(--text-primary)',
-            fontWeight: '400',
-          }"
-        >
-          <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>
+      <div v-loading="loading && oaTasks.length === 0" class="table-wrapper">
+        <div class="table-summary mb-4 text-var(--text-secondary)!">
+          共 {{ pagination.total }} 条待办
+        </div>
 
-          <el-table-column
-            prop="createAt"
-            label="任务创建时间"
-            width="180"
-            align="center"
+        <div class="task-scroll-list">
+          <div
+            v-for="(item, index) in oaTasks"
+            :key="item.id || item.instId || `${item.instTitle}-${index}`"
+            class="task-card"
           >
-            <template #default="scope">
-              {{ timestampToDateTime(scope.row.createdAt) }}
-            </template>
-          </el-table-column>
+            <div class="task-card__header">
+              <span class="task-card__index">{{ index + 1 }}</span>
+              <span class="task-card__status">
+                {{ getStatusText(item.status) }}
+              </span>
+            </div>
+
+            <div class="task-card__title">
+              {{ item.instTitle || "--" }}
+            </div>
+
+            <div class="task-card__grid">
+              <div class="task-card__field">
+                <span class="task-card__label">业务类型</span>
+                <span class="task-card__value">{{ item.entityTypeId || "--" }}</span>
+              </div>
+              <div class="task-card__field">
+                <span class="task-card__label">优先级别</span>
+                <span class="task-card__value">{{
+                  getPriorityText(item.priority)
+                }}</span>
+              </div>
+              <div class="task-card__field">
+                <span class="task-card__label">任务提交人</span>
+                <span class="task-card__value">{{ item.userName || "--" }}</span>
+              </div>
+              <div class="task-card__field">
+                <span class="task-card__label">任务状态</span>
+                <span class="task-card__value">{{
+                  getStatusText(item.status)
+                }}</span>
+              </div>
+              <div class="task-card__field task-card__field--full">
+                <span class="task-card__label">任务提交时间</span>
+                <span class="task-card__value">{{
+                  timestampToDateTime(item.submitAt)
+                }}</span>
+              </div>
+              <div class="task-card__field task-card__field--full">
+                <span class="task-card__label">任务创建时间</span>
+                <span class="task-card__value">{{
+                  timestampToDateTime(item.createdAt)
+                }}</span>
+              </div>
+              <div class="task-card__field task-card__field--full">
+                <span class="task-card__label">任务完成时间</span>
+                <span class="task-card__value">{{
+                  timestampToDateTime(item.endAt)
+                }}</span>
+              </div>
+            </div>
+
+            <div class="task-card__footer">
+              <span class="task-card__action" @click="goBackPage(item)"
+                >处理</span
+              >
+            </div>
+          </div>
 
-          <el-table-column
-            prop="endAt"
-            label="任务完成时间"
-            width="180"
-            align="center"
+          <InfiniteLoading
+            :identifier="infiniteId"
+            spinner="spiral"
+            @infinite="loadMore"
           >
-            <template #default="scope">
-              {{ timestampToDateTime(scope.row.endAt) }}
+            <template #no-more>
+              <div class="infinite-status">没有更多数据了</div>
             </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 #no-results>
+              <div class="infinite-status">暂无数据</div>
             </template>
-          </el-table-column>
-
-          <el-table-column
-            label="操作"
-            width="120"
-            fixed="right"
-            align="center"
-          >
-            <template #default="scope">
-              <span
-                class="text-[#1e90ff] cursor-pointer"
-                @click="goBackPage(scope.row)"
-                >处理</span
-              >
+            <template #error>
+              <div class="infinite-status">加载失败,请稍后重试</div>
             </template>
-          </el-table-column>
-        </el-table>
-      </div>
-
-      <div class="pagination-container">
-        <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"
-        />
+          </InfiniteLoading>
+        </div>
       </div>
     </div>
 
@@ -285,17 +122,20 @@
 
 <script setup>
 import Header from "@components/home/header.vue";
-import { ref, onMounted } from "vue";
-import { getCRMTasks, ssoLogin } from "@/api/user";
+import { onMounted, ref } from "vue";
+import { getCRMTasks } from "@/api/user";
 import { useUserStore } from "@/stores/useUserStore";
 import { getAccessToken } from "@/utils/auth";
 import * as dd from "dingtalk-jsapi";
 import { Icon } from "@iconify/vue";
 import router from "@/router";
+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,
@@ -303,36 +143,83 @@ const pagination = ref({
   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 getPriorityText = (priority) => {
+  if (priority === 90) return "紧急";
+  if (priority === 50) return "一般";
+  return "--";
+};
+
+const getStatusText = (status) => {
+  const statusMap = {
+    1: "待提交",
+    2: "待重新提交",
+    3: "待提交到退回节点",
+    4: "已提交",
+    5: "待办理",
+    6: "已同意",
+    7: "已拒绝",
+    8: "已转办",
+    9: "已抄送",
+    10: "加签挂起",
+    11: "任务挂起",
+    12: "已收回",
+    13: "已移交",
+    14: "已委托",
+    99: "无需办理",
+  };
+
+  return statusMap[status] || "--";
 };
 
-const handleSizeChange = async (size) => {
-  pagination.value.pageSize = size;
+const loadTaskPage = async () => {
+  const res = await getCRMTasks({
+    id: userStore.getUser.username,
+    type: "pending",
+    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;
-  loading.value = true;
+  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 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;
+    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;
   }
@@ -340,32 +227,30 @@ const handleSizeChange = async (size) => {
 
 const goBackPage = async (row) => {
   if (userStore.getUser.username && getAccessToken()) {
-    const ua = window.navigator.userAgent; // const ua = navigator.userAgent;
+    const ua = window.navigator.userAgent;
     const isDesktop =
       ua.includes("DingTalk") &&
       (ua.includes("Windows") || ua.includes("Macintosh"));
 
-    let ua2 = navigator.userAgent.toLowerCase();
-    var isMobile = ua2.indexOf("dingtalk") > -1;
+    const ua2 = navigator.userAgent.toLowerCase();
+    const isMobile = ua2.indexOf("dingtalk") > -1;
+
     if (ua2.includes("dingtalk") || ua2.includes("dingtalkwork")) {
       if (isMobile && !isDesktop) {
         const originPath =
           "/bff/spa/crmh5/index.html#/home?platform=H5&deviceType=0";
-        // 连续三次encodeURIComponent
-        let e1 = encodeURIComponent(originPath);
-        let e2 = encodeURIComponent(e1);
-        let e3 = encodeURIComponent(e2);
+        const e1 = encodeURIComponent(originPath);
+        const e2 = encodeURIComponent(e1);
+        const e3 = encodeURIComponent(e2);
         dd.biz.util.openLink({
           url: `https://crm-tencent.xiaoshouyi.com/global/sso/callback/00APEB9EEEA9B2E338B686B7ECFA8585808C.action?token=${getAccessToken()}&returnUri=${e3}`,
-
           onSuccess: () => {},
         });
       } else if (isDesktop) {
         const originPath = "/bff/neoweb#/approval_workbench";
-        // 连续三次encodeURIComponent
-        let e1 = encodeURIComponent(originPath);
-        let e2 = encodeURIComponent(e1);
-        let e3 = encodeURIComponent(e2);
+        const e1 = encodeURIComponent(originPath);
+        const e2 = encodeURIComponent(e1);
+        const e3 = encodeURIComponent(e2);
         dd.biz.util.openLink({
           url: `https://crm-tencent.xiaoshouyi.com/global/sso/callback/00APEB9EEEA9B2E338B686B7ECFA8585808C.action?token=${getAccessToken()}&returnUri=${e3}`,
           onSuccess: () => {},
@@ -374,10 +259,9 @@ const goBackPage = async (row) => {
     } else {
       const newTab = window.open("", "_blank");
       const originPath = "/bff/neoweb#/approval_workbench";
-      // 连续三次encodeURIComponent
-      let e1 = encodeURIComponent(originPath);
-      let e2 = encodeURIComponent(e1);
-      let e3 = encodeURIComponent(e2);
+      const e1 = encodeURIComponent(originPath);
+      const e2 = encodeURIComponent(e1);
+      const e3 = encodeURIComponent(e2);
       newTab.location.href = `https://crm-tencent.xiaoshouyi.com/global/sso/callback/00APEB9EEEA9B2E338B686B7ECFA8585808C.action?token=${getAccessToken()}&returnUri=${e3}`;
     }
   } else {
@@ -386,40 +270,26 @@ const goBackPage = async (row) => {
 };
 
 function timestampToDateTime(timestamp) {
-  // 兼容 10位(秒) / 13位(毫秒)
+  if (!timestamp) return "--";
+
   const len = String(timestamp).length;
   const date = new Date(Number(timestamp) * (len === 10 ? 1000 : 1));
 
-  // 年
+  if (Number.isNaN(date.getTime())) return "--";
+
   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 () => {
+
+onMounted(() => {
   if (userStore.getUser.username) {
-    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;
-    }
+    resetList();
   }
 });
 </script>
@@ -513,6 +383,7 @@ onMounted(async () => {
 .nav {
   background-color: var(--bg-table);
 }
+
 .todo-list {
   min-height: 100vh;
   display: flex;
@@ -540,7 +411,6 @@ onMounted(async () => {
 .content-wrapper {
   flex: 1;
   max-width: 1200px;
-  /* margin: 0 auto; */
   padding: 20px;
   width: 100%;
   box-sizing: border-box;
@@ -554,7 +424,7 @@ onMounted(async () => {
 }
 
 .table-wrapper {
-  min-height: 400px;
+  min-height: 300px;
   background: #fff;
   padding: 20px;
   border-radius: 4px;
@@ -566,135 +436,141 @@ onMounted(async () => {
 }
 
 .table-summary {
-  color: #606266;
   font-size: 13px;
 }
 
-.pagination-container {
-  display: flex;
-  justify-content: center;
-  margin-top: 0px;
-  padding: 20px 0;
-}
-
-:deep(.el-table) {
-  background-color: var(--bg-table) !important;
-  color: var(--text-primary) !important;
+.task-scroll-list {
+  padding-right: 4px;
+  background-color: transparent;
 }
 
-:deep(.table-wrapper) {
-  background: var(--bg-table) !important;
+.task-card {
+  background: transparent !important;
+  border: 1px solid var(--portal-line);
+  border-radius: 18px;
+  padding: 16px;
+  margin-bottom: 14px;
+  box-shadow: var(--portal-shadow);
 }
 
-:deep(.el-table__header-wrapper thead) {
-  background: var(--bg-table) !important;
-  color: var(--text-primary) !important;
+.task-card__header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 12px;
+  gap: 12px;
 }
 
-:deep(.el-table__header-wrapper .el-table__header) {
-  border-bottom: none;
-}
-:deep(.el-table__inner-wrapper) {
-  background: var(--bg-table) !important;
-  color: var(--text-primary) !important;
-}
-:deep(.el-table__body-wrapper .el-table__row) {
-  background: var(--bg-table);
-  color: var(--text-primary) !important;
+.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;
 }
 
-:deep(.el-table__body-wrapper .el-table__row .hover-row) {
-  background: var(--bg-table) !important;
-  color: var(--text-primary) !important;
+.task-card__status {
+  color: var(--portal-accent);
+  font-size: 12px;
+  background: var(--portal-accent-soft);
+  padding: 4px 10px;
+  border-radius: 999px;
 }
 
-:deep(.el-table__body-wrapper .el-table__row .el-table__cell) {
-  background: var(--bg-table) !important;
-  color: var(--text-primary) !important;
-  border-bottom: 1px solid var(--border-color) !important;
+.task-card__title {
+  font-size: 16px;
+  font-weight: 600;
+  line-height: 1.5;
+  color: var(--portal-title);
+  margin-bottom: 14px;
+  word-break: break-word;
 }
 
-:deep(.el-loading-mask) {
-  background-color: var(--bg-table) !important; /* 半透明深色遮罩 */
+.task-card__grid {
+  display: grid;
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+  gap: 12px;
 }
 
-:deep(.el-table__body-wrapper .el-table__row .el-table__cell) {
-  border-bottom: 1px solid #313849;
+.task-card__field {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+  min-width: 0;
 }
 
-:deep(.el-pagination is-background) {
-  background: var(--bg-table) !important;
+.task-card__field--full {
+  grid-column: 1 / -1;
 }
 
-:deep(.el-pagination .el-pager li) {
-  background: var(--bg-table) !important;
-  color: var(--text-tertiary) !important;
+.task-card__label {
+  font-size: 12px;
+  color: var(--portal-text-soft);
 }
 
-:deep(.el-pagination .el-pager li.is-active) {
-  /* background: #2d8cf0 !important; */
-  color: var(--portal-accent) !important;
+.task-card__value {
+  font-size: 13px;
+  line-height: 1.5;
+  color: var(--portal-text);
+  word-break: break-all;
 }
 
-:deep(.el-select .el-select__wrapper) {
-  background: var(--bg-table) !important;
-  border: 1px solid #313849 !important;
-  outline: none !important;
+.task-card__footer {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 16px;
+  padding-top: 12px;
+  border-top: 1px solid var(--portal-line);
 }
 
-:deep(.el-input) {
-  border: 1px solid #313849 !important;
-  outline: none !important;
+.task-card__action {
+  color: #1e90ff;
+  cursor: pointer;
+  font-size: 14px;
+  font-weight: 500;
 }
 
-:deep(.btn-next) {
-  background: var(--bg-table) !important;
-  color: var(--text-tertiary) !important;
+.infinite-status {
+  color: var(--portal-text-soft);
+  text-align: center;
+  padding: 12px 0;
+  font-size: 13px;
 }
 
-:deep(.btn-prev) {
+:deep(.table-wrapper) {
   background: var(--bg-table) !important;
-  color: var(--text-tertiary) !important;
 }
 
-/* 下拉菜单弹出层的背景 */
-:deep(.el-select-dropdown) {
+:deep(.el-loading-mask) {
   background-color: var(--bg-table) !important;
-  border: 1px solid #313849 !important;
-}
-
-/* 下拉菜单项 */
-:deep(.el-select-dropdown__item) {
-  color: var(--bg-table) !important;
 }
 
-:deep(.el-select-dropdown__item.hover),
-:deep(.el-select-dropdown__item:hover) {
-  background-color: var(--bg-table) !important; /* 悬停深色背景 */
-}
+@media (max-width: 640px) {
+  .content-wrapper {
+    padding: 16px;
+  }
 
-/* 关键:修复下拉选择框 (PageSize) 的白色边框/背景 */
-:deep(.el-pagination .el-select .el-select__wrapper) {
-  background-color: var(--bg-table) !important;
-  box-shadow: none !important; /* 去掉可能的阴影 */
-  border: 1px solid #313849 !important; /* 统一边框颜色 */
-}
+  .table-wrapper {
+    padding: 16px;
+  }
 
-/* 去掉聚焦时的白色/蓝色轮廓 */
-:deep(.el-pagination .el-select .el-select__wrapper.is-focused),
-:deep(.el-pagination .el-select .el-select__wrapper:hover) {
-  box-shadow: none !important;
-  border-color: #409eff !important; /* 聚焦时变为主题蓝,或者保持 #313849 */
-}
+  .task-card {
+    padding: 14px;
+    border-radius: 16px;
+  }
 
-/* 通用输入框边框修复 (如果其他地方也有) */
-:deep(.el-input__wrapper) {
-  background-color: var(--bg-table) !important;
-  box-shadow: none !important; /* 去掉默认的白色/灰色阴影边框 */
-  border: 1px solid #313849 !important;
-}
+  .task-card__grid {
+    grid-template-columns: 1fr;
+  }
 
-:deep(.el-input__inner) {
-  color: var(--bg-primary) !important;
+  .task-card__field--full {
+    grid-column: auto;
+  }
 }
 </style>