Ver Fonte

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

yanghao há 5 dias atrás
pai
commit
3c02b45b03

+ 4 - 1
src/App.vue

@@ -42,7 +42,10 @@ const logoutByIdle = () => {
   sessionStorage.setItem(manualLogoutKey, "true");
   sessionStorage.removeItem(reloginCancelKey);
   removeToken();
-  window.location.href = "/login";
+  const ua = window.navigator.userAgent.toLowerCase();
+  const logoutRedirect =
+    ua.includes("dingtalk") || ua.includes("dingtalkwork") ? "/" : "/login";
+  window.location.href = logoutRedirect;
 };
 
 const resetIdleTimer = () => {

+ 4 - 1
src/components/home/header.vue

@@ -766,7 +766,10 @@ const onUserCommand = async (command: string) => {
     sessionStorage.setItem(manualLogoutKey, "true");
     sessionStorage.removeItem(reloginCancelKey);
     removeToken();
-    window.location.href = "/login";
+    const ua = window.navigator.userAgent.toLowerCase();
+    const logoutRedirect =
+      ua.includes("dingtalk") || ua.includes("dingtalkwork") ? "/" : "/login";
+    window.location.href = logoutRedirect;
   }
 };
 </script>

+ 31 - 25
src/config/axios/service.ts

@@ -21,39 +21,36 @@ import errorCode from "./errorCode";
 import { resetRouter } from "@/router";
 import { deleteUserCache } from "@hooks/useCache";
 const { result_code, base_url, request_timeout } = config;
-// 需要忽略的提示。忽略后,自动 Promise.reject('error')
-const ignoreMsgs = [
-  "无效的刷新令牌", // 刷新令牌被删除时,不用提示
-  "刷新令牌已过期", // 使用刷新令牌,刷新获取新的访问令牌时,结果因为过期失败,此时需要忽略。否则,会导致继续 401,无法跳转到登出界面
-];
+// 需要忽略的提示。忽略后,自�?Promise.reject('error')
+const ignoreMsgs = ["无效的刷新令牌", "刷新令牌被删除", "刷新令牌已过期"];
 // 是否显示重新登录
 export const isRelogin = { show: false };
 export const reloginCancelKey = "reloginCancel";
 export const manualLogoutKey = "manualLogout";
-// Axios 无感知刷新令牌,参考 https://www.dashingdog.cn/article/11 与 https://segmentfault.com/a/1190000020210980 实现
+// Axios 无感知刷新令牌,参考https://www.dashingdog.cn/article/11?https://segmentfault.com/a/1190000020210980 实现
 // 请求队列
 let requestList: any[] = [];
-// 是否正在刷新
+// 是否正在刷新?
 let isRefreshToken = false;
 // 请求白名单,无须token的接口
 const whiteList: string[] = ["/login", "/refresh-token"];
 
 // 创建axios实例
 const service: AxiosInstance = axios.create({
-  baseURL: base_url, // api base_url
+  baseURL: base_url, // api �?base_url
   timeout: request_timeout, // 请求超时时间
-  withCredentials: false, // 禁用 Cookie 等信
+  withCredentials: false, // 禁用 Cookie 等信�?
   // 自定义参数序列化函数
   paramsSerializer: (params) => {
     return qs.stringify(params, { allowDots: true });
   },
 });
 
-// request拦截器
+// request拦截器?
 service.interceptors.request.use(
   (config: InternalAxiosRequestConfig) => {
     config.headers["tenant-id"] = 1;
-    // 是否需要设置 token
+    // 是否需要设置token?
     let isToken = (config!.headers || {}).isToken === false;
     whiteList.some((v) => {
       if (config.url && config.url.indexOf(v) > -1) {
@@ -89,23 +86,23 @@ service.interceptors.request.use(
   },
 );
 
-// response 拦截器
+// response 拦截器?
 service.interceptors.response.use(
   async (response: AxiosResponse<any>) => {
     let { data } = response;
     const config = response.config;
     if (!data) {
-      // 返回“[HTTP]请求没有返回值”;
+      // 返回“[HTTP]请求没有返回值?
       throw new Error();
     }
 
-    // 未设置状态码则默认成功状态
+    // 未设置状态码则默认成功状态?
     // 二进制数据则直接返回,例如说 Excel 导出
     if (
       response.request.responseType === "blob" ||
       response.request.responseType === "arraybuffer"
     ) {
-      // 注意:如果导出的响应json,说明可能失败了,不直接返回进行下载
+      // 注意:如果导出的响应�?json,说明可能失败了,不直接返回进行下载
       if (response.data.type !== "application/json") {
         return response.data;
       }
@@ -128,7 +125,7 @@ service.interceptors.response.use(
         // 2. 进行刷新访问令牌
         try {
           const refreshTokenRes = await refreshToken();
-          // 2.1 刷新成功,则回放队列的请+ 当前请求
+          // 2.1 刷新成功,则回放队列的请�?+ 当前请求
           setToken((await refreshTokenRes).data.data);
           config.headers!.Authorization = "Bearer " + getAccessToken();
           requestList.forEach((cb: any) => {
@@ -137,8 +134,8 @@ service.interceptors.response.use(
           requestList = [];
           return service(config);
         } catch (e) {
-          // 为什么需要 catch 异常呢?刷新失败时,请求因为 Promise.reject 触发异常。
-          // 2.2 刷新失败,只回放队列的请求
+          // 为什么需要?catch 异常呢?刷新失败时,请求因为 Promise.reject 触发异常�?
+          // 2.2 刷新失败,只回放队列的请求?
           requestList.forEach((cb: any) => {
             cb();
           });
@@ -149,10 +146,10 @@ service.interceptors.response.use(
           isRefreshToken = false;
         }
       } else {
-        // 添加到队列,等待刷新获取到新的令牌
+        // 添加到队列,等待刷新获取到新的令牌?
         return new Promise((resolve) => {
           requestList.push(() => {
-            config.headers!.Authorization = "Bearer " + getAccessToken(); // 让每个请求携带自定义token 请根据实际情况自行修
+            config.headers!.Authorization = "Bearer " + getAccessToken(); // 让每个请求携带自定义token 请根据实际情况自行修�?
             resolve(service(config));
           });
         });
@@ -187,7 +184,7 @@ service.interceptors.response.use(
     if (message === "Network Error") {
       message = "操作失败,系统异常!";
     } else if (message.includes("timeout")) {
-      message = "接口请求超时,请刷新页面重试!";
+      message = "接口请求超时,请刷新页面重试";
     } else if (message.includes("Request failed with status code")) {
       message = "请求出错,请稍候重试" + message.substr(message.length - 3);
     }
@@ -209,12 +206,13 @@ const handleAuthorized = () => {
   const isManualLogout = sessionStorage.getItem(manualLogoutKey) === "true";
   const isReloginCanceled = sessionStorage.getItem(reloginCancelKey) === "true";
   const ua = window.navigator.userAgent.toLowerCase();
+  const isDingTalkEnv = ua.includes("dingtalk") || ua.includes("dingtalkwork");
   if (isManualLogout || isReloginCanceled) {
     deleteUserCache();
     removeToken();
 
     if (!window.location.href.includes("login")) {
-      if (ua.includes("dingtalk") || ua.includes("dingtalkwork")) {
+      if (isDingTalkEnv) {
         window.location.href = "/";
       } else {
         window.location.href = "/login";
@@ -223,6 +221,14 @@ const handleAuthorized = () => {
     return Promise.reject("登录超时,请重新登录");
   }
 
+  if (isDingTalkEnv) {
+    deleteUserCache();
+    removeToken();
+    isRelogin.show = false;
+    window.location.href = "/";
+    return Promise.reject("登录超时,请重新登录");
+  }
+
   if (!isRelogin.show) {
     if (window.location.href.includes("login")) {
       return Promise.reject("登录超时,请重新登录");
@@ -243,7 +249,7 @@ const handleAuthorized = () => {
         removeToken();
         isRelogin.show = false;
 
-        if (ua.includes("dingtalk") || ua.includes("dingtalkwork")) {
+        if (isDingTalkEnv) {
           window.location.href = "/";
         } else {
           window.location.href = "/login";
@@ -253,8 +259,8 @@ const handleAuthorized = () => {
         sessionStorage.setItem(reloginCancelKey, "true");
         deleteUserCache(); // 删除用户缓存
         removeToken();
-        isRelogin.show = false; // 重置显示状
-        if (ua.includes("dingtalk") || ua.includes("dingtalkwork")) {
+        isRelogin.show = false; // 重置显示状?
+        if (isDingTalkEnv) {
           window.location.href = "/";
         } else {
           window.location.href = "/login";

+ 65 - 17
src/views/flow/crmDoneList.vue

@@ -2,8 +2,8 @@
   <div class="todo-list">
     <Header />
 
-    <div class="content mt-15 max-w-[1200px] mx-auto">
-      <div class="flex gap-5 items-center mb-4 bg-[#f9f9f9] py-2">
+    <div class="content-wrapper mt-15 max-w-[1200px] mx-auto">
+      <div class="flex gap-5 items-center mb-4 bg-[#fff] py-2 pl-4 rounded-sm">
         <p class="flex items-center">
           <Icon
             icon="mynaui:arrow-up-down"
@@ -15,7 +15,7 @@
         <el-button
           type="primary"
           round
-          size="default"
+          size="small"
           color="#02409b"
           @click="router.back()"
           ><Icon
@@ -26,7 +26,7 @@
           />返回</el-button
         >
       </div>
-      <div class="h-[80vh]">
+      <div v-loading="loading" class="table-wrapper">
         <el-table
           v-loading="loading"
           :data="oaTasks"
@@ -265,19 +265,19 @@
             </template>
           </el-table-column>
         </el-table>
+      </div>
 
-        <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 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"
+        />
       </div>
     </div>
 
@@ -396,9 +396,10 @@ onMounted(async () => {
 
 <style scoped>
 .todo-list {
+  min-height: 100vh;
   display: flex;
   flex-direction: column;
-  min-height: 100%;
+  background-color: #f5f7fa;
 }
 
 .content {
@@ -411,4 +412,51 @@ onMounted(async () => {
   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;
+  /* margin: 0 auto; */
+  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: 400px;
+  background: #fff;
+  padding: 20px;
+  border-radius: 4px;
+}
+
+.table-title {
+  font-weight: 500;
+  color: #303133;
+}
+
+.table-summary {
+  color: #606266;
+  font-size: 13px;
+}
+
+.pagination-container {
+  display: flex;
+  justify-content: center;
+  margin-top: 0px;
+  padding: 20px 0;
+}
 </style>

+ 66 - 17
src/views/flow/crmTodoList.vue

@@ -2,8 +2,8 @@
   <div class="todo-list">
     <Header />
 
-    <div class="content mt-15 max-w-[1200px] mx-auto">
-      <div class="flex gap-5 items-center mb-4 bg-[#f9f9f9] py-2">
+    <div class="content-wrapper mt-15 max-w-[1200px] mx-auto">
+      <div class="flex gap-5 items-center mb-4 bg-[#fff] py-2 pl-4 rounded-sm">
         <p class="flex items-center">
           <Icon
             icon="mynaui:arrow-up-down"
@@ -15,7 +15,7 @@
         <el-button
           type="primary"
           round
-          size="default"
+          size="small"
           color="#02409b"
           @click="router.back()"
           ><Icon
@@ -26,12 +26,13 @@
           />返回</el-button
         >
       </div>
-      <div class="h-[80vh]">
+      <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="{
@@ -265,19 +266,19 @@
             </template>
           </el-table-column>
         </el-table>
+      </div>
 
-        <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 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"
+        />
       </div>
     </div>
 
@@ -396,9 +397,10 @@ onMounted(async () => {
 
 <style scoped>
 .todo-list {
+  min-height: 100vh;
   display: flex;
   flex-direction: column;
-  min-height: 100%;
+  background-color: #f5f7fa;
 }
 
 .content {
@@ -411,4 +413,51 @@ onMounted(async () => {
   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;
+  /* margin: 0 auto; */
+  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: 400px;
+  background: #fff;
+  padding: 20px;
+  border-radius: 4px;
+}
+
+.table-title {
+  font-weight: 500;
+  color: #303133;
+}
+
+.table-summary {
+  color: #606266;
+  font-size: 13px;
+}
+
+.pagination-container {
+  display: flex;
+  justify-content: center;
+  margin-top: 0px;
+  padding: 20px 0;
+}
 </style>

+ 68 - 20
src/views/flow/oaDoneList.vue

@@ -2,8 +2,8 @@
   <div class="todo-list">
     <Header />
 
-    <div class="content mt-15 max-w-[1200px] mx-auto">
-      <div class="flex gap-5 items-center mb-4 bg-[#f9f9f9] py-2">
+    <div class="content-wrapper mt-15 max-w-[1200px] mx-auto">
+      <div class="flex gap-5 items-center mb-4 bg-[#fff] py-2 pl-4 rounded-sm">
         <p class="flex items-center">
           <Icon
             icon="mynaui:arrow-up-down"
@@ -15,7 +15,7 @@
         <el-button
           type="primary"
           round
-          size="default"
+          size="small"
           color="#02409b"
           @click="router.back()"
           ><Icon
@@ -26,11 +26,12 @@
           />&#36820;&#22238;</el-button
         >
       </div>
-      <div class="h-[80vh]">
+      <div v-loading="loading" class="table-wrapper">
         <el-table
           v-loading="loading"
           :data="oaTasks"
           style="width: 100%"
+          stripe
           height="70vh"
           element-loading-text="加载中..."
           :empty-text="loading ? '' : '暂无数据'"
@@ -54,7 +55,7 @@
           <el-table-column
             prop="requestName"
             label="&#35831;&#27714;&#26631;&#39064;"
-            min-width="220"
+            min-width="250"
             fixed="left"
             align="center"
           />
@@ -137,26 +138,25 @@
           >
             <template #default="scope">
               <span
-                class="text-[#02409b] cursor-pointer"
+                class="text-[#1e90ff] cursor-pointer text-[13px]"
                 @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 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"
+        />
       </div>
     </div>
 
@@ -270,9 +270,10 @@ onMounted(async () => {
 
 <style scoped>
 .todo-list {
+  min-height: 100vh;
   display: flex;
   flex-direction: column;
-  min-height: 100%;
+  background-color: #f5f7fa;
 }
 
 .content {
@@ -285,4 +286,51 @@ onMounted(async () => {
   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;
+  /* margin: 0 auto; */
+  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: 400px;
+  background: #fff;
+  padding: 20px;
+  border-radius: 4px;
+}
+
+.table-title {
+  font-weight: 500;
+  color: #303133;
+}
+
+.table-summary {
+  color: #606266;
+  font-size: 13px;
+}
+
+.pagination-container {
+  display: flex;
+  justify-content: center;
+  margin-top: 0px;
+  padding: 20px 0;
+}
 </style>

+ 86 - 37
src/views/flow/todoList.vue

@@ -2,8 +2,8 @@
   <div class="todo-list">
     <Header />
 
-    <div class="content mt-15 max-w-[1200px] mx-auto">
-      <div class="flex gap-5 items-center mb-4 bg-[#f9f9f9] py-2">
+    <div class="content-wrapper mt-15 max-w-[1200px] mx-auto">
+      <div class="flex gap-5 items-center mb-4 bg-[#fff] py-2 pl-4 rounded-sm">
         <p class="flex items-center">
           <Icon
             icon="mynaui:arrow-up-down"
@@ -15,7 +15,7 @@
         <el-button
           type="primary"
           round
-          size="default"
+          size="small"
           color="#02409b"
           @click="router.back()"
           ><Icon
@@ -23,17 +23,18 @@
             class="icon pr-1"
             width="20"
             height="20"
-          />返回</el-button
+          />&#36820;&#22238;</el-button
         >
       </div>
-      <div class="h-[80vh]">
+      <div v-loading="loading" class="table-wrapper">
         <el-table
           v-loading="loading"
           :data="oaTasks"
           style="width: 100%"
+          stripe
+          height="70vh"
           element-loading-text="加载中..."
           :empty-text="loading ? '' : '暂无数据'"
-          height="70vh"
           :header-cell-style="{
             backgroundColor: '#e9f7ff',
             color: 'black',
@@ -45,117 +46,117 @@
         >
           <el-table-column
             type="index"
-            label="序号"
+            label="&#24207;&#21495;"
             width="80"
             fixed="left"
             align="center"
+            :index="getIndex"
           />
           <el-table-column
             prop="requestName"
-            label="请求标题"
-            min-width="220"
+            label="&#35831;&#27714;&#26631;&#39064;"
+            min-width="250"
             fixed="left"
             align="center"
           />
           <el-table-column
             prop="workflowBaseInfo.workflowTypeName"
-            label="流程类型"
+            label="&#27969;&#31243;&#31867;&#22411;"
             width="140"
             align="center"
           />
           <el-table-column
             prop="requestLevel"
-            label="紧急程度"
+            label="&#32039;&#24613;&#31243;&#24230;"
             width="100"
             align="center"
           />
           <el-table-column
             prop="status"
-            label="路径状态"
+            label="&#36335;&#24452;&#29366;&#24577;"
             width="100"
             align="center"
           />
           <el-table-column
             prop="sysName"
-            label="系统名称"
+            label="&#31995;&#32479;&#21517;&#31216;"
             width="140"
             align="center"
           />
           <el-table-column
             prop="createTime"
-            label="创建时间"
+            label="&#21019;&#24314;&#26102;&#38388;"
             width="180"
             align="center"
           />
 
           <el-table-column
             prop="creatorName"
-            label="创建人名称"
+            label="&#21019;&#24314;&#20154;&#21517;&#31216;"
             width="120"
             align="center"
           />
 
           <el-table-column
             prop="currentNodeName"
-            label="当前节点名称"
+            label="&#24403;&#21069;&#33410;&#28857;&#21517;&#31216;"
             width="140"
             align="center"
           />
           <el-table-column
             prop="lastOperateTime"
-            label="最后操作时间"
+            label="&#26368;&#21518;&#25805;&#20316;&#26102;&#38388;"
             width="180"
             align="center"
           />
 
           <el-table-column
             prop="lastOperatorName"
-            label="最后操作人名称"
+            label="&#26368;&#21518;&#25805;&#20316;&#20154;&#21517;&#31216;"
             width="140"
             align="center"
           />
           <el-table-column
             prop="receiveTime"
-            label="接收时间"
+            label="&#25509;&#25910;&#26102;&#38388;"
             width="180"
             align="center"
           />
 
           <el-table-column
             prop="workflowBaseInfo.workflowName"
-            label="流程名称"
+            label="&#27969;&#31243;&#21517;&#31216;"
             min-width="200"
             align="center"
           />
 
           <el-table-column
-            label="操作"
+            label="&#25805;&#20316;"
             width="120"
             fixed="right"
             align="center"
           >
             <template #default="scope">
               <span
-                class="text-[#02409b] cursor-pointer"
+                class="text-[#1e90ff] cursor-pointer text-[13px]"
                 @click="goBackPage(scope.row)"
-                >处理</span
+                >&#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 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"
+        />
       </div>
     </div>
 
@@ -280,9 +281,10 @@ onMounted(async () => {
 
 <style scoped>
 .todo-list {
+  min-height: 100vh;
   display: flex;
   flex-direction: column;
-  min-height: 100%;
+  background-color: #f5f7fa;
 }
 
 .content {
@@ -295,4 +297,51 @@ onMounted(async () => {
   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;
+  /* margin: 0 auto; */
+  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: 400px;
+  background: #fff;
+  padding: 20px;
+  border-radius: 4px;
+}
+
+.table-title {
+  font-weight: 500;
+  color: #303133;
+}
+
+.table-summary {
+  color: #606266;
+  font-size: 13px;
+}
+
+.pagination-container {
+  display: flex;
+  justify-content: center;
+  margin-top: 0px;
+  padding: 20px 0;
+}
 </style>