浏览代码

办件中心和通知公告模块开发

songxy 1 年之前
父节点
当前提交
9156f47c4e

+ 0 - 1
client/src/api/system/user/index.ts

@@ -94,7 +94,6 @@ export const getSimpleUserMap = (): Promise<any> => {
     userLists.forEach((item) => {
       userMap[item.id] = item.nickname
     })
-    console.log(userMap)
     resolve(userMap)
   })
 }

+ 6 - 2
client_h5/.env.dev

@@ -1,3 +1,7 @@
-VITE_BASE_URL='http://10.10.10.7:48080/'
+# VITE_BASE_URL='http://10.10.10.7:18080/'
+VITE_BASE_URL='http://localhost:6090/'
 
-VITE_AUTHORIZATION='Bearer test83e06d0d-af60-4419-9437-c9a68bf1b669'
+# File上传路径
+VITE_FILE_BASE_URI='/admin-api/infra/file'
+
+VITE_AUTHORIZATION='test83e06d0d-af60-4419-9437-c9a68bf1b669'

+ 1 - 1
client_h5/index.html

@@ -6,7 +6,7 @@
     <meta name="viewport" content="width=device-width,initial-scale=1.0, maximum-scale=1">
     <script src='https://cdn.bootcss.com/vConsole/3.3.2/vconsole.min.js'></script>
     <script type="text/javascript">
-      window.vConsole = new window.VConsole()
+      // window.vConsole = new window.VConsole()
     </script>
     <title></title>
   </head>

+ 1 - 0
client_h5/package.json

@@ -19,6 +19,7 @@
     "dingtalk-jsapi": "^3.0.33",
     "moment": "^2.30.1",
     "pinia": "^2.1.7",
+    "qs": "^6.12.1",
     "vant": "^4.8.11",
     "vue": "^3.4.19",
     "vue-router": "^4.2.5"

+ 91 - 0
client_h5/src/pages/handleCenter/index.scss

@@ -0,0 +1,91 @@
+.handlecenter_box {
+  background-color: #eff1f5;
+  min-height: 100%;
+  .search_box {
+    display: flex;
+    align-items: center;
+    >.search_input {
+      flex: 1;  
+      :deep(.van-search__field){
+        background-color: #fff;
+      }
+    }
+    >.search_icon {
+      padding-right: 6px;
+      display: flex;
+      align-items: center;
+    }
+  }
+  .handlecenter_card_box {
+    .handlecenter_card {
+      background-color: #fff;
+      margin-bottom: 15px;
+      padding: 10px 15px;
+      >div {
+        &.header {
+          display: flex;
+          justify-content: space-between;
+          >p {
+            font-size: 14px;
+            margin-bottom: 20px;
+            &.title{
+              color: #6B7177;
+            }
+            &.status {
+              color: #00B758;
+              >.icon {
+                width: 10px;
+                height: 10px;
+                border-radius: 50%;
+                background-color: #00B758;
+                display: inline-block;
+                margin-right: 5px;
+              }
+            }
+          }
+        }
+        &.content {
+          >div {
+            &.title {
+              display: flex;
+              color: #171B1F;
+              justify-content: space-between;
+              >p {
+                font-size: 17px;
+                &:first-child {
+                  font-weight: bold;
+                }
+                &:last-child {
+                  font-size: 14px;
+                  >span {
+                    color: #1989FA;
+                  }
+                }
+              }
+            }
+            &:not(.title) {
+              color: #6B7177;
+            }
+            &.desc {
+              font-size: 15px;
+              margin: 6px 0px;
+              white-space: nowrap;
+              text-overflow: ellipsis;
+              overflow: hidden;
+            }
+            &.tip {
+              font-size: 13px;
+              >span {
+                display: inline-block;
+                margin-left: 3px;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+  p {
+    margin: 0px;
+  }
+}

+ 256 - 0
client_h5/src/pages/handleCenter/index.vue

@@ -0,0 +1,256 @@
+
+<template>
+  <div class="handlecenter_box">
+    <van-tabs v-model:active="searchData['status']" :sticky="true" @click-tab="tabClickHandle">
+        <div class="search_box">
+          <div class="search_input">
+            <van-search
+              v-model="searchData['searchVal']"
+              background="transparent"
+              placeholder="搜索标题、申请编号、申请内容"
+              @search="searchHandler"
+            />
+          </div>
+          <div class="search_icon">
+            <van-button type="primary" icon="replay" size="small" @click="resetHandler"></van-button>
+            <van-icon name="filter-o" size="24" color="#51555A" @click="modelShow = true" style="margin-left: 6px;"/>
+          </div>
+        </div>
+        <van-tab :title="'待办('+handlerCaseStatis['normal']+')'" name="1">
+          <div class="handlecenter_card_box">
+            <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
+              <van-list
+                v-model:loading="loading"
+                :finished="finished"
+                finished-text="没有更多了"
+                @load="onLoad"
+              >
+                <div class="handlecenter_card" v-for="(item,index) in handleLists" :key="index" @click="clickHandler(item, false)">
+                  <div class="header">
+                    <p class="title">
+                      <span>业务编号:</span>
+                      <span>{{item['CODE']}}</span>
+                    </p>
+                    <p class="status">
+                      <span class="icon"></span>
+                      <span>正常</span>
+                    </p>
+                  </div>
+                  <div class="content">
+                    <div class="title">
+                      <p>{{item['NAME']}}</p>
+                      <p>办理环节:<span>{{item['ACTIVITYNAME']}}</span></p>
+                    </div>
+                    <div class="desc">{{item['DESCRIBTION']??'暂无流程描述'}}</div>
+                    <div class="tip">
+                      <van-icon name="clock-o" />
+                      <span>{{formatDateTime(new Date(item['STARTTIME']))}}</span>
+                    </div>
+                  </div>
+                </div>
+              </van-list>
+            </van-pull-refresh>
+          </div>
+        </van-tab>
+        <van-tab :title="'已完成('+handlerCaseStatis['finish']+')'" name="90">
+          <div class="handlecenter_card_box">
+            <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
+              <van-list
+                v-model:loading="loading"
+                :finished="finished"
+                finished-text="没有更多了"
+                @load="onLoad"
+              >
+                <div class="handlecenter_card" v-for="(item,index) in handleLists" :key="index" @click="clickHandler(item, true)">
+                  <div class="header">
+                    <p class="title">
+                      <span>业务编号:</span>
+                      <span>{{item['CODE']}}</span>
+                    </p>
+                    <p class="status">
+                      <span class="icon"></span>
+                      <span>{{ item['IS_END'] === 1 ? '归档' : '完成' }}</span>
+                    </p>
+                  </div>
+                  <div class="content">
+                    <div class="title">
+                      <p>{{item['NAME']}}</p>
+                      <p>办理环节:<span>{{item['ACTIVITYNAME']}}</span></p>
+                    </div>
+                    <div class="desc">{{item['DESCRIBTION']??'暂无流程描述'}}</div>
+                    <div class="tip">
+                      <van-icon name="clock-o" />
+                      <span>{{formatDateTime(new Date(item['FINISH_TIME']))}}</span>
+                    </div>
+                  </div>
+                </div>
+              </van-list>
+            </van-pull-refresh>
+          </div>
+        </van-tab>
+    </van-tabs>
+    <van-dialog :show-cancel-button="true" v-model:show="modelShow" title="流程目录" @confirm="confirmHandle">
+      <van-tree-select
+          v-model:active-id="activeIds"
+          v-model:main-active-index="activeIndex"
+          :items="items"
+        />
+    </van-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { getHandlerCaseCenterList, getHandlerCaseCenterCount, getFlowTemplateTreeDataByUser, saveAndGetMobileUrl } from '@/service/flow';
+import { listToTree } from '@/utils/tree';
+import { formatDateTime } from '@/utils/common';
+
+const items = ref([]);
+const loading = ref(false);
+const finished = ref(false);
+const modelShow = ref(false);
+const refreshing = ref(false);
+const activeIds = ref<string[]>([]);
+const activeIndex = ref<number>(0);
+const onRefresh = () => {
+  queryHandlerCaseCenterCount();
+  queryHandlerCaseCenterList(true);
+};
+const onLoad = () => {
+  loading.value = true;
+  pageData.value.page = pageData.value.page + 1
+  queryHandlerCaseCenterList();
+};
+const searchHandler = () => {
+  queryHandlerCaseCenterCount();
+  queryHandlerCaseCenterList(true);
+}
+const pageData = ref<{
+  page: number
+  rows: number
+  records?: number
+}>({
+  page: 0,
+  rows: 10,
+  records: 0
+})
+const searchData = ref<{
+  status: string
+  searchVal: string
+}>({
+  status: '1',
+  searchVal: ''
+})
+const handlerCaseStatis = ref<{
+  normal: number
+  finish: number
+}>({
+  normal: 0,
+  finish: 0
+})
+const queryHandlerCaseCenterCount = () => {
+  const sendData = {
+    isMobile: true,
+    toSystemId: '',
+    flowTemIds: JSON.stringify(activeIds.value),
+    ...searchData.value
+  }
+  getHandlerCaseCenterCount(sendData).then((result:any) => {
+    if (result) {
+      handlerCaseStatis.value.normal = result.NORMAL
+      handlerCaseStatis.value.finish = result.FINISH
+    }
+  })
+}
+queryHandlerCaseCenterCount()
+const handleLists = ref<any[]>([]);
+const queryHandlerCaseCenterList = (isPull = false) => {
+  if (isPull) {
+    pageData.value.page = 1
+    refreshing.value = true;
+    finished.value = false;
+  }
+  const sendData = {
+    IEnd: 0,
+    isMobile: true,
+    toSystemId: '',
+    flowTemIds: JSON.stringify(activeIds.value),
+    ...pageData.value,
+    ...searchData.value
+  }
+  getHandlerCaseCenterList(sendData).then((result: any) => { 
+    console.log(result)
+    if (result.error_code) {
+      finished.value = true;
+      return;
+    }
+    if (result && result.rows) {
+      isPull ? handleLists.value = result.rows : handleLists.value?.push(...result.rows) 
+    }
+    if (isPull) {
+      refreshing.value = false
+    }
+    /** 用来解决tab切换时,容器高度发生变化自动触发loading事件 */
+    const time = setTimeout(() => {
+      loading.value = false;
+      clearTimeout(time);
+    }, 800);
+    if (result.records === handleLists.value.length) { 
+      finished.value = true;
+    }
+  });
+}
+const tabClickHandle = () => {
+  loading.value = true;
+  queryHandlerCaseCenterList(true);
+}
+const sendData = {
+  isRight: 0,
+  officeStatus: 1,
+  excludedSystemId: '',
+  toSystemId: '',
+};
+getFlowTemplateTreeDataByUser(sendData).then((result: any) => { 
+  result.forEach((item: any) => {
+    item['text'] = item['name']
+  })
+  if (result && result.length > 0) { 
+    items.value = listToTree(result)[0]['children']; 
+  }
+});
+const resetHandler = () => {
+  loading.value = true;
+  searchData.value.searchVal = ''
+  activeIds.value = []
+  queryHandlerCaseCenterCount();
+  queryHandlerCaseCenterList(true);
+}
+const confirmHandle = () => {
+  loading.value = true;
+  queryHandlerCaseCenterCount();
+  queryHandlerCaseCenterList(true);
+}
+/**
+ * 流程办理或查看
+ */
+const toPageHandle = (path: string) => {
+  window.location.href = path;
+};
+const clickHandler = (item: any, isView: boolean) => {
+  const sendData = {
+    "activityInsId": item['ACTIVITYINSID'],
+    "flowInsId": item['FLOWINSID'],
+    "participantId": item['PARTICIPANTID'],
+    "isView": isView,
+    "status": ''
+  }
+  saveAndGetMobileUrl(sendData).then((result) => { 
+    if (typeof(result) === 'string') {
+      toPageHandle(result)
+    }
+  });
+}
+</script>
+
+<style lang="scss" scoped>
+@import "./index.scss";
+</style>

+ 1 - 1
client_h5/src/pages/home/index.vue

@@ -91,7 +91,7 @@ const toProcessHandle = (item: ProcessMenuItem): void => {
     templateId:item.instanceId
   }).then((result: any) => { 
     if (typeof(result) === 'string') {
-      location.href = result
+      toPageHandle(result)
     }
   })
 }

+ 74 - 0
client_h5/src/pages/notice/details/index.vue

@@ -0,0 +1,74 @@
+<template>
+  <div class="notice_detail_box">
+    <h2 class="title">{{ detail['title'] }}</h2>
+    <div class="info">
+      <div>
+        <span>发布时间:{{ detail['createTime'] }}</span>
+      </div>
+      <div>
+        <span>阅读次数:{{ detail['readNum'] }}</span>
+      </div>
+    </div>
+    <div class="file_box">
+      <span>附件:</span>
+      <ul>
+        <li v-for="(file, index) in fileUrls" @click="downloadFile(file['url'])"  :key="index">{{ file['name'] }}</li>
+      </ul>
+    </div>
+    <div class="content" v-html="detail['content']"></div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import request from "@/utils/request";
+import { useFiles } from './mixins.ts'
+
+const { fileUrls, queryFiles, downloadFile } = useFiles(request)
+
+const detail = ref({});
+/**
+ * 获取详情
+ * **/
+ async function queryDetailById(idStr: any): Promise<void> {
+  const urlApi = `/admin-api/adm/noticeAndLearn/query/detailById`
+  const params = {
+    id: idStr
+  }
+   const result = await request.get(urlApi, { params: params })
+   console.log(result)
+   const resultData = result.data;
+  if (resultData) {
+    detail.value = resultData
+    queryFiles(resultData.files)
+  }
+}
+const route = useRoute()
+const query = route.query
+if (query.id) {
+  queryDetailById(query.id)
+}
+</script>
+
+<style lang="scss" scoped>
+.notice_detail_box {
+  padding: 20px;
+  >.info {
+    display: flex;
+    justify-content: space-between;
+  }
+  >.file_box {
+    display: flex;
+    margin-top: 15px;
+    >span {
+      color: #aaa;
+      width: 80px;
+    }
+    >ul {
+      >li {
+        margin-bottom: 10px;
+        color: #2e77e6;
+      }
+    }
+  }
+}
+</style>

+ 26 - 0
client_h5/src/pages/notice/details/mixins.ts

@@ -0,0 +1,26 @@
+interface FileInterface {
+  id: number
+  name: string
+  url: string
+}
+export const useFiles = (request: any) => {
+  const fileUrls = ref<FileInterface[]>([])
+
+  async function queryFiles(ids: string): Promise<void> {
+    if (!ids) return
+    const url = `${import.meta.env.VITE_FILE_BASE_URI}/get-list?ids=${ids}`
+    const result = await request.get(url)
+    const resultData = result.data;
+    if (resultData) {
+      fileUrls.value = resultData
+    }
+  }
+  async function downloadFile(fileUrl: string): Promise<void> {
+    window.open(fileUrl)  //待定
+  }
+  return {
+    fileUrls,
+    queryFiles,
+    downloadFile,
+  }
+}

+ 33 - 0
client_h5/src/pages/notice/index.scss

@@ -0,0 +1,33 @@
+.handlecenter_box {
+  background-color: #eff1f5;
+  min-height: 100%;
+  .handlecenter_card_box {
+    .handlecenter_card {
+      background-color: #fff;
+      padding: 15px;
+      margin-bottom: 10px;
+      >.desc {
+        font-size: 16px;
+        margin: 6px 0px;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+        overflow: hidden;
+        margin-bottom: 15px;
+      }
+      >.tip {
+        font-size: 13px;
+        display: flex;
+        justify-content: space-between;
+        >div {
+          >span {
+            display: inline-block;
+            margin-left: 3px;
+          }
+        }
+      }
+    }
+  }
+  p {
+    margin: 0px;
+  }
+}

+ 126 - 0
client_h5/src/pages/notice/index.vue

@@ -0,0 +1,126 @@
+
+<template>
+  <div class="handlecenter_box">
+        <div class="search_box">
+          <div class="search_input">
+            <van-search
+              v-model="searchData['title']"
+              background="transparent"
+              placeholder="请输入搜索标题"
+              @search="queryNoticeAndLearnPage(true)"
+            />
+          </div>
+        </div>
+        <div class="handlecenter_card_box">
+          <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
+            <van-list
+              v-model:loading="loading"
+              :finished="finished"
+              finished-text="没有更多了"
+              @load="onLoad"
+            >
+              <div class="handlecenter_card" v-for="(item,index) in noticeLists" :key="index" @click="clickHandler(item, false)">
+                <div class="desc">{{item['title']??'暂无标题'}}</div>
+                <div class="tip">
+                  <div>
+                    <van-icon name="user" />
+                    <span>{{userMap[item['creator']] ?? item['nickname']}}</span>
+                  </div>
+                  <div>
+                    <van-icon name="clock-o" />
+                    <span>{{item['createTime']}}</span>
+                  </div>
+                </div>
+              </div>
+            </van-list>
+          </van-pull-refresh>
+        </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { getSimpleUserMap } from '@/service/user'
+import reqest from "@/utils/request";
+
+const loading = ref(false);
+const finished = ref(false);
+const refreshing = ref(false);
+const onRefresh = () => {
+  queryNoticeAndLearnPage(true);
+};
+const onLoad = () => {
+  loading.value = true;
+  pageData.value.pageNo = pageData.value.pageNo + 1
+  queryNoticeAndLearnPage();
+};
+const pageData = ref<{
+  pageNo: number
+  pageSize: number
+  total?: number
+}>({
+  pageNo: 0,
+  pageSize: 10,
+  total: 0
+})
+const searchData = ref<{
+  readType: number
+  title: string
+}>({
+  readType: 2,
+  title: ''
+})
+
+const userMap = ref<any>({})
+async function initSimpleUserMap() {
+  userMap.value = await getSimpleUserMap()
+}
+initSimpleUserMap()
+const noticeLists = ref<any[]>([]);
+const queryNoticeAndLearnPage = (isPull = false) => {
+  if (isPull) {
+    pageData.value.pageNo = 1
+    refreshing.value = true;
+    finished.value = false;
+  }
+  const sendData = {
+    type: 1,
+    ...pageData.value,
+    ...searchData.value
+  }
+  reqest.post('/admin-api/adm/noticeAndLearn/query/page', sendData).then((result: any) => { 
+    const resultData = result.data;
+    if (resultData) {
+      if (resultData && resultData.list) {
+        isPull ? noticeLists.value = resultData.list : noticeLists.value?.push(...resultData.list) 
+      }
+      if (isPull) {
+        refreshing.value = false
+      }
+      /** 用来解决tab切换时,容器高度发生变化自动触发loading事件 */
+      const time = setTimeout(() => {
+        loading.value = false;
+        clearTimeout(time);
+      }, 800);
+      if (resultData.total === noticeLists.value.length) { 
+        finished.value = true;
+      }
+    } else {
+      loading.value = false;
+      finished.value = true;
+    }
+  });
+}
+const router = useRouter()
+const clickHandler = (item) => {
+  router.push({
+    path: '/notice/detail',
+    query: {
+      id: item['id']
+    }
+  });
+}
+</script>
+
+<style lang="scss" scoped>
+@import "./index.scss";
+</style>

+ 24 - 0
client_h5/src/router/routes.ts

@@ -13,6 +13,30 @@ const routes: RouteRecordRaw[] = [
         },
         component: () => import("@/pages/home/index.vue"),
       },
+      {
+        path: "handleCenter",
+        name: "HandleCenter",
+        meta: {
+          title: "办件中心",
+        },
+        component: () => import("@/pages/handleCenter/index.vue"),
+      },
+      {
+        path: "notice",
+        name: "Notice",
+        meta: {
+          title: "通知公告",
+        },
+        component: () => import("@/pages/notice/index.vue"),
+      },
+      {
+        path: "notice/detail",
+        name: "NoticeDetail",
+        meta: {
+          title: "通知公告详情",
+        },
+        component: () => import("@/pages/notice/details/index.vue"),
+      },
       {
         path: "leave",
         name: "Leave",

+ 37 - 5
client_h5/src/service/flow.ts

@@ -1,4 +1,4 @@
-import reqest from "@/utils/request";
+import request from "@/utils/request";
 
 export interface FlowDTO {
     activityInstanceId: string
@@ -8,25 +8,57 @@ export interface FlowDTO {
 export interface FlowCreateDTO {
   templateId: string
 }
+/** 获取办件中心列表 */
+ export const getHandlerCaseCenterList = async (data: any) => {
+  return await request.post(`/HandlerCaseCenter/list`, data, {
+    headers: {
+      'Content-Type': 'application/x-www-form-urlencoded'
+    }
+  });
+}
+/** 获取办件中心汇总 */
+ export const getHandlerCaseCenterCount = async (data: any) => {
+  return await request.post(`/HandlerCaseCenter/count`, data, {
+    headers: {
+      'Content-Type': 'application/x-www-form-urlencoded'
+    }
+  });
+}
+/** 办件中心流程跳转或办理 */
+export const saveAndGetMobileUrl = async (data: any) => {
+  return await request.post(`/HandlerCaseCenter/saveAndGetMobileUrl`, data, {
+    headers: {
+      'Content-Type': 'application/x-www-form-urlencoded'
+    }
+  });
+}
+/** 获取流程目录 */
+export const getFlowTemplateTreeDataByUser = async (data: any) => {
+  return await request.post(`/TFlowTemplate/GetFlowTemplateTreeDataByUser`, data, {
+    headers: {
+      'Content-Type': 'application/x-www-form-urlencoded'
+    }
+  });
+}
 
 /** 获取下一步流程(转件) */
 export const getNextActivity = async (data: FlowDTO) => {
-  return await reqest.post(`/workflow/Transfer/getNextActivity`, data);
+  return await request.post(`/workflow/Transfer/getNextActivity`, data);
 }
 
 /** 发起流程 */
 export const createProcessByModalId = async (createData: FlowCreateDTO) => {
-  return await reqest.get(`/workflow/TProcessEngine/addMobile`, {
+  return await request.get(`/workflow/TProcessEngine/addMobile`, {
     params: createData,
   });
 }
 
 /** 获取流程意见 */
 export const getOpinionListByFlowInstanceId = async (flowInstanceId: String) => {
-  return await reqest.get(`/workflow/IFlowOpinion/getOpinionListByFlowInstanceId?flowInstanceId=${flowInstanceId}`);
+  return await request.get(`/workflow/IFlowOpinion/getOpinionListByFlowInstanceId?flowInstanceId=${flowInstanceId}`);
 }
 
 /** 获取流程模板意见 */
 export const getTemplateOpinionListByFlowInstanceId = async (flowInstanceId: String) => {
-  return await reqest.get(`/workflow/TFlowOpinion/getOpinionListByFlowInstanceId?flowInstanceId=${flowInstanceId}`);
+  return await request.get(`/workflow/TFlowOpinion/getOpinionListByFlowInstanceId?flowInstanceId=${flowInstanceId}`);
 }

+ 14 - 0
client_h5/src/service/user.ts

@@ -0,0 +1,14 @@
+import request from "@/utils/request";
+
+// 获取用户精简信息Map
+export const getSimpleUserMap = (): Promise<any> => {
+  const userMap = {}
+  return new Promise<any>(async (resolve, reject) => {
+    const result = await request.get({ url: '/system/user/list-all-simple' })
+    const userLists = result.data;
+    userLists.forEach((item) => {
+      userMap[item.id] = item.nickname
+    })
+    resolve(userMap)
+  })
+}

+ 37 - 0
client_h5/src/utils/common.ts

@@ -65,4 +65,41 @@ export const formatDateTime = (date: Date) => {
   const minute = date.getMinutes()
   const second = date.getSeconds()
   return `${year}-${zeroFillToString(month)}-${zeroFillToString(day)} ${zeroFillToString(hours)}:${zeroFillToString(minute)}:${zeroFillToString(second)}`;
+}
+/**
+ * 格式化时间
+ */
+export const formatDateTimeByTime = (time: number) => {
+  console.log('1233')
+  const date = new Date();
+  date.setTime(time);
+  console.log(formatDateTime(date))
+  return formatDateTime(date);
+}
+
+/** 
+ * 格式化获取localStore object类型数据
+ * @param lKey:string localStore key 必填
+ * @param key?:string obj key 可选
+ *  */
+export const getStoreObject = (lKey: string, key?: string) => { 
+  if (!lKey) throw new Error('key不能为空!');
+  const result: string | null = localStorage.getItem(lKey);
+  if (!result) return null;
+  const obj = JSON.parse(result);
+  if (!key) return obj;
+  return obj[key];
+}
+
+export const setStoreObject = (lKey: string, data: any) => {
+  if (!lKey) throw new Error('key不能为空!');
+  if (!data) return;
+  if (typeof (data) === 'object') { 
+    localStorage.setItem(lKey, JSON.stringify(data));
+    return;
+  }
+  if (typeof (data) === 'string') { 
+    localStorage.setItem(lKey, data);
+    return;
+  }
 }

+ 166 - 0
client_h5/src/utils/dict.ts

@@ -0,0 +1,166 @@
+/**
+ * 数据字典工具类
+ */
+import request from "@/utils/request";
+import { getStoreObject, setStoreObject } from './common'
+
+const DICT_NAME = '_dict';
+export const useDictStoreWithOut = () => {
+  const listSimpleDictData = async () => {
+    const result = await request.get('/system/dict-data/list-all-simple')
+    return result.data;
+  }
+  const dictObj = getStoreObject(DICT_NAME);
+  if (!dictObj) { 
+    const resultData = listSimpleDictData()
+    if (resultData && resultData?.length > 0) {
+      setStoreObject(DICT_NAME, resultData);
+    }
+  }
+}
+const dictStore = useDictStoreWithOut()
+
+/**
+ * 获取 dictType 对应的数据字典数组
+ *
+ * @param dictType 数据类型
+ * @returns {*|Array} 数据字典数组
+ */
+export interface DictDataType {
+  dictType: string
+  label: string
+  value: string | number | boolean
+  cssClass: string
+}
+
+export const getDictOptions = (dictType: string) => {
+  return dictStore.getDictByType(dictType) || []
+}
+
+export const getIntDictOptions = (dictType: string) => {
+  const dictOption: DictDataType[] = []
+  const dictOptions: DictDataType[] = getDictOptions(dictType)
+  dictOptions.forEach((dict: DictDataType) => {
+    dictOption.push({
+      ...dict,
+      value: parseInt(dict.value + '')
+    })
+  })
+  return dictOption
+}
+
+export const getStrDictOptions = (dictType: string) => {
+  const dictOption: DictDataType[] = []
+  const dictOptions: DictDataType[] = getDictOptions(dictType)
+  dictOptions.forEach((dict: DictDataType) => {
+    dictOption.push({
+      ...dict,
+      value: dict.value + ''
+    })
+  })
+  return dictOption
+}
+
+export const getBoolDictOptions = (dictType: string) => {
+  const dictOption: DictDataType[] = []
+  const dictOptions: DictDataType[] = getDictOptions(dictType)
+  dictOptions.forEach((dict: DictDataType) => {
+    dictOption.push({
+      ...dict,
+      value: dict.value + '' === 'true'
+    })
+  })
+  return dictOption
+}
+
+/**
+ * 获取指定字典类型的指定值对应的字典对象
+ * @param dictType 字典类型
+ * @param value 字典值
+ * @return DictDataType 字典对象
+ */
+export const getDictObj = (dictType: string, value: any): DictDataType | undefined => {
+  const dictOptions: DictDataType[] = getDictOptions(dictType)
+  for (const dict of dictOptions) {
+    if (dict.value === value + '') {
+      return dict
+    }
+  }
+}
+
+/**
+ * 获得字典数据的文本展示
+ *
+ * @param dictType 字典类型
+ * @param value 字典数据的值
+ * @return 字典名称
+ */
+export const getDictLabel = (dictType: string, value: any): string => {
+  const dictOptions: DictDataType[] = getDictOptions(dictType)
+  const dictLabel = ref('')
+  dictOptions.forEach((dict: DictDataType) => {
+    if (dict.value === value + '') {
+      dictLabel.value = dict.label
+    }
+  })
+  return dictLabel.value
+}
+
+export enum DICT_TYPE {
+  USER_TYPE = 'user_type',
+  COMMON_STATUS = 'common_status',
+  COMMON_STATE = 'common_state',
+  SYSTEM_TENANT_PACKAGE_ID = 'system_tenant_package_id',
+  TERMINAL = 'terminal', // 终端
+
+  // ========== SYSTEM 模块 ==========
+  SYSTEM_USER_SEX = 'system_user_sex',
+  SYSTEM_MENU_TYPE = 'system_menu_type',
+  SYSTEM_ROLE_TYPE = 'system_role_type',
+  SYSTEM_DATA_SCOPE = 'system_data_scope',
+  SYSTEM_NOTICE_TYPE = 'system_notice_type',
+  SYSTEM_OPERATE_TYPE = 'system_operate_type',
+  SYSTEM_LOGIN_TYPE = 'system_login_type',
+  SYSTEM_LOGIN_RESULT = 'system_login_result',
+  SYSTEM_SMS_CHANNEL_CODE = 'system_sms_channel_code',
+  SYSTEM_SMS_TEMPLATE_TYPE = 'system_sms_template_type',
+  SYSTEM_SMS_SEND_STATUS = 'system_sms_send_status',
+  SYSTEM_SMS_RECEIVE_STATUS = 'system_sms_receive_status',
+  SYSTEM_ERROR_CODE_TYPE = 'system_error_code_type',
+  SYSTEM_OAUTH2_GRANT_TYPE = 'system_oauth2_grant_type',
+  SYSTEM_MAIL_SEND_STATUS = 'system_mail_send_status',
+  SYSTEM_NOTIFY_TEMPLATE_TYPE = 'system_notify_template_type',
+
+  // ========== INFRA 模块 ==========
+  INFRA_BOOLEAN_STRING = 'infra_boolean_string',
+  INFRA_JOB_STATUS = 'infra_job_status',
+  INFRA_JOB_LOG_STATUS = 'infra_job_log_status',
+  INFRA_API_ERROR_LOG_PROCESS_STATUS = 'infra_api_error_log_process_status',
+  INFRA_CONFIG_TYPE = 'infra_config_type',
+  INFRA_CODEGEN_TEMPLATE_TYPE = 'infra_codegen_template_type',
+  INFRA_CODEGEN_FRONT_TYPE = 'infra_codegen_front_type',
+  INFRA_CODEGEN_SCENE = 'infra_codegen_scene',
+  INFRA_FILE_STORAGE = 'infra_file_storage',
+
+  // ========== ADM 模块 ==========
+
+  ADM_ATTENDANCE_STATUS = 'adm_attendance_status',
+
+  // ========== BUSINESS 模块 ==========
+
+  WF_COMMON_COST_TYPE = 'WF_COMMON_COST_TYPE',
+  WF_SEAL_TYPE = 'WF_SEAL_TYPE',
+  WF_SEAL_NAME = 'WF_SEAL_NAME',
+  FINANCE_STATUS = 'finance_status',
+  PROJECT_STATUS = 'project_status',
+  CONTRACT_COST_STATUS = 'CONTRACT_COST_STATUS',
+  CONTRACT_SIGN_STATUS = 'contract_is_sign',
+  CONTRACT_SIGN_WAY = 'contract_sign_way',
+  CONTRACT_MAIN_TYPE = 'contract_main_type',
+  CONTRACT_SECOND_TYPE = 'contract_second_type',
+  PROJECT_HY = 'project_hy',
+  POST_TYPE = 'post_type',
+  ABILITY_LEVEL = 'ABILITY_LEVEL',
+  INVOICE_OUT_STATUS = 'INVOICE_OUT_STATUS',
+  INVOICE_TYPE = 'invoice_type'
+}

+ 12 - 4
client_h5/src/utils/request.ts

@@ -1,5 +1,5 @@
-import axios, { AxiosBasicCredentials } from 'axios';
- 
+import axios, { AxiosRequestHeaders } from 'axios';
+import qs from 'qs';
  
 const defaultConfig = {
   baseURL: import.meta.env.VITE_BASE_URL
@@ -10,8 +10,16 @@ const instance = axios.create(Object.assign({}, defaultConfig));
 instance.interceptors.request.use(
   (config) => {
       ('isAuth' in config.headers) ? null : config.headers.isAuth = true
-      if (config.headers.isAuth) {
-          config.headers.Authorization = `Bearer ${import.meta.env.VITE_AUTHORIZATION}`;
+        if (config.headers.isAuth) {
+            config.headers.Authorization = `Bearer ${import.meta.env.VITE_AUTHORIZATION}`;
+        }
+        const data = config.data || false
+        if (
+          config.method?.toUpperCase() === 'POST' &&
+          (config.headers as AxiosRequestHeaders)['Content-Type'] ===
+            'application/x-www-form-urlencoded'
+        ) {
+          config.data = qs.stringify(data)
         }
         return config;
     },

+ 404 - 0
client_h5/src/utils/tree.ts

@@ -0,0 +1,404 @@
+interface TreeHelperConfig {
+  id: string
+  children: string
+  pid: string
+}
+
+const DEFAULT_CONFIG: TreeHelperConfig = {
+  id: 'id',
+  children: 'children',
+  pid: 'pid'
+}
+export const defaultProps = {
+  children: 'children',
+  label: 'name',
+  value: 'id',
+  isLeaf: 'leaf'
+}
+
+const getConfig = (config: Partial<TreeHelperConfig>) => Object.assign({}, DEFAULT_CONFIG, config)
+
+// tree from list
+export const listToTree = <T = any>(list: any[], config: Partial<TreeHelperConfig> = {}): T[] => {
+  const conf = getConfig(config) as TreeHelperConfig
+  const nodeMap = new Map()
+  const result: T[] = []
+  const { id, children, pid } = conf
+
+  for (const node of list) {
+    node[children] = node[children] || []
+    nodeMap.set(node[id], node)
+  }
+  for (const node of list) {
+    const parent = nodeMap.get(node[pid])
+    ;(parent ? parent.children : result).push(node)
+  }
+  return result
+}
+
+export const treeToList = <T = any>(tree: any, config: Partial<TreeHelperConfig> = {}): T => {
+  config = getConfig(config)
+  const { children } = config
+  const result: any = [...tree]
+  for (let i = 0; i < result.length; i++) {
+    if (!result[i][children!]) continue
+    result.splice(i + 1, 0, ...result[i][children!])
+  }
+  return result
+}
+
+export const findNode = <T = any>(
+  tree: any,
+  func: Fn,
+  config: Partial<TreeHelperConfig> = {}
+): T | null => {
+  config = getConfig(config)
+  const { children } = config
+  const list = [...tree]
+  for (const node of list) {
+    if (func(node)) return node
+    node[children!] && list.push(...node[children!])
+  }
+  return null
+}
+
+export const findNodeAll = <T = any>(
+  tree: any,
+  func: Fn,
+  config: Partial<TreeHelperConfig> = {}
+): T[] => {
+  config = getConfig(config)
+  const { children } = config
+  const list = [...tree]
+  const result: T[] = []
+  for (const node of list) {
+    func(node) && result.push(node)
+    node[children!] && list.push(...node[children!])
+  }
+  return result
+}
+
+export const findPath = <T = any>(
+  tree: any,
+  func: Fn,
+  config: Partial<TreeHelperConfig> = {}
+): T | T[] | null => {
+  config = getConfig(config)
+  const path: T[] = []
+  const list = [...tree]
+  const visitedSet = new Set()
+  const { children } = config
+  while (list.length) {
+    const node = list[0]
+    if (visitedSet.has(node)) {
+      path.pop()
+      list.shift()
+    } else {
+      visitedSet.add(node)
+      node[children!] && list.unshift(...node[children!])
+      path.push(node)
+      if (func(node)) {
+        return path
+      }
+    }
+  }
+  return null
+}
+
+export const findPathAll = (tree: any, func: Fn, config: Partial<TreeHelperConfig> = {}) => {
+  config = getConfig(config)
+  const path: any[] = []
+  const list = [...tree]
+  const result: any[] = []
+  const visitedSet = new Set(),
+    { children } = config
+  while (list.length) {
+    const node = list[0]
+    if (visitedSet.has(node)) {
+      path.pop()
+      list.shift()
+    } else {
+      visitedSet.add(node)
+      node[children!] && list.unshift(...node[children!])
+      path.push(node)
+      func(node) && result.push([...path])
+    }
+  }
+  return result
+}
+
+export const filter = <T = any>(
+  tree: T[],
+  func: (n: T) => boolean,
+  config: Partial<TreeHelperConfig> = {}
+): T[] => {
+  config = getConfig(config)
+  const children = config.children as string
+
+  function listFilter(list: T[]) {
+    return list
+      .map((node: any) => ({ ...node }))
+      .filter((node) => {
+        node[children] = node[children] && listFilter(node[children])
+        return func(node) || (node[children] && node[children].length)
+      })
+  }
+
+  return listFilter(tree)
+}
+
+export const forEach = <T = any>(
+  tree: T[],
+  func: (n: T) => any,
+  config: Partial<TreeHelperConfig> = {}
+): void => {
+  config = getConfig(config)
+  const list: any[] = [...tree]
+  const { children } = config
+  for (let i = 0; i < list.length; i++) {
+    // func 返回true就终止遍历,避免大量节点场景下无意义循环,引起浏览器卡顿
+    if (func(list[i])) {
+      return
+    }
+    children && list[i][children] && list.splice(i + 1, 0, ...list[i][children])
+  }
+}
+
+/**
+ * @description: Extract tree specified structure
+ */
+export const treeMap = <T = any>(
+  treeData: T[],
+  opt: { children?: string; conversion: Fn }
+): T[] => {
+  return treeData.map((item) => treeMapEach(item, opt))
+}
+
+/**
+ * @description: Extract tree specified structure
+ */
+export const treeMapEach = (
+  data: any,
+  { children = 'children', conversion }: { children?: string; conversion: Fn }
+) => {
+  const haveChildren = Array.isArray(data[children]) && data[children].length > 0
+  const conversionData = conversion(data) || {}
+  if (haveChildren) {
+    return {
+      ...conversionData,
+      [children]: data[children].map((i: number) =>
+        treeMapEach(i, {
+          children,
+          conversion
+        })
+      )
+    }
+  } else {
+    return {
+      ...conversionData
+    }
+  }
+}
+
+/**
+ * 递归遍历树结构
+ * @param treeDatas 树
+ * @param callBack 回调
+ * @param parentNode 父节点
+ */
+export const eachTree = (treeDatas: any[], callBack: Fn, parentNode = {}) => {
+  treeDatas.forEach((element) => {
+    const newNode = callBack(element, parentNode) || element
+    if (element.children) {
+      eachTree(element.children, callBack, newNode)
+    }
+  })
+}
+
+/**
+ * 构造树型结构数据
+ * @param {*} data 数据源
+ * @param {*} id id字段 默认 'id'
+ * @param {*} parentId 父节点字段 默认 'parentId'
+ * @param {*} children 孩子节点字段 默认 'children'
+ */
+export const handleTree = (data: any[], id?: string, parentId?: string, children?: string) => {
+  if (!Array.isArray(data)) {
+    console.warn('data must be an array')
+    return []
+  }
+  const config = {
+    id: id || 'id',
+    parentId: parentId || 'parentId',
+    childrenList: children || 'children'
+  }
+
+  const childrenListMap = {}
+  const nodeIds = {}
+  const tree: any[] = []
+
+  for (const d of data) {
+    const parentId = d[config.parentId]
+    if (childrenListMap[parentId] == null) {
+      childrenListMap[parentId] = []
+    }
+    nodeIds[d[config.id]] = d
+    childrenListMap[parentId].push(d)
+  }
+
+  for (const d of data) {
+    const parentId = d[config.parentId]
+    if (nodeIds[parentId] == null) {
+      tree.push(d)
+    }
+  }
+
+  for (const t of tree) {
+    adaptToChildrenList(t)
+  }
+
+  function adaptToChildrenList(o) {
+    if (childrenListMap[o[config.id]] !== null) {
+      o[config.childrenList] = childrenListMap[o[config.id]]
+    }
+    if (o[config.childrenList]) {
+      for (const c of o[config.childrenList]) {
+        adaptToChildrenList(c)
+      }
+    }
+  }
+
+  return tree
+}
+
+/**
+ * 构造树型结构数据
+ * @param {*} data 数据源
+ * @param {*} id id字段 默认 'id'
+ * @param {*} parentId 父节点字段 默认 'parentId'
+ * @param {*} children 孩子节点字段 默认 'children'
+ * @param {*} rootId 根Id 默认 0
+ */
+// @ts-ignore
+export const handleTree2 = (data, id, parentId, children, rootId) => {
+  id = id || 'id'
+  parentId = parentId || 'parentId'
+  // children = children || 'children'
+  rootId =
+    rootId ||
+    Math.min(
+      ...data.map((item) => {
+        return item[parentId]
+      })
+    ) ||
+    0
+  // 对源数据深度克隆
+  const cloneData = JSON.parse(JSON.stringify(data))
+  // 循环所有项
+  const treeData = cloneData.filter((father) => {
+    const branchArr = cloneData.filter((child) => {
+      // 返回每一项的子级数组
+      return father[id] === child[parentId]
+    })
+    branchArr.length > 0 ? (father.children = branchArr) : ''
+    // 返回第一层
+    return father[parentId] === rootId
+  })
+  return treeData !== '' ? treeData : data
+}
+
+/**
+ * 校验选中的节点,是否为指定 level
+ *
+ * @param tree 要操作的树结构数据
+ * @param nodeId 需要判断在什么层级的数据
+ * @param level 检查的级别, 默认检查到二级
+ * @return true 是;false 否
+ */
+export const checkSelectedNode = (tree: any[], nodeId: any, level = 2): boolean => {
+  if (typeof tree === 'undefined' || !Array.isArray(tree) || tree.length === 0) {
+    console.warn('tree must be an array')
+    return false
+  }
+
+  // 校验是否是一级节点
+  if (tree.some((item) => item.id === nodeId)) {
+    return false
+  }
+
+  // 递归计数
+  let count = 1
+
+  // 深层次校验
+  function performAThoroughValidation(arr: any[]): boolean {
+    count += 1
+    for (const item of arr) {
+      if (item.id === nodeId) {
+        return true
+      } else if (typeof item.children !== 'undefined' && item.children.length !== 0) {
+        if (performAThoroughValidation(item.children)) {
+          return true
+        }
+      }
+    }
+    return false
+  }
+
+  for (const item of tree) {
+    count = 1
+    if (performAThoroughValidation(item.children)) {
+      // 找到后对比是否是期望的层级
+      if (count >= level) {
+        return true
+      }
+    }
+  }
+
+  return false
+}
+
+/**
+ * 获取节点的完整结构
+ * @param tree 树数据
+ * @param nodeId 节点 id
+ */
+export const treeToString = (tree: any[], nodeId) => {
+  if (typeof tree === 'undefined' || !Array.isArray(tree) || tree.length === 0) {
+    console.warn('tree must be an array')
+    return ''
+  }
+  // 校验是否是一级节点
+  const node = tree.find((item) => item.id === nodeId)
+  if (typeof node !== 'undefined') {
+    return node.name
+  }
+  let str = ''
+
+  function performAThoroughValidation(arr) {
+    for (const item of arr) {
+      if (item.id === nodeId) {
+        str += `/${item.name}`
+        return true
+      } else if (typeof item.children !== 'undefined' && item.children.length !== 0) {
+        str += `/${item.name}`
+        if (performAThoroughValidation(item.children)) {
+          return true
+        }
+      }
+    }
+    return false
+  }
+
+  for (const item of tree) {
+    str = `${item.name}`
+    if (performAThoroughValidation(item.children)) {
+      break
+    }
+  }
+  return str
+}
+
+export const filterNodeMethod = (value, data) => {
+  if (!data) return
+  return data.name.includes(value)
+}

+ 3 - 2
zjugis-framework/zjugis-spring-boot-starter-workflow/src/main/java/com/zjugis/framework/workflow/utils/Constant.java

@@ -48,7 +48,8 @@ public class Constant {
 	}
 
 	public static String getUserId() {
-//	    return "dc794fe0-66fe-4b1d-9273-f747950b27c3";
-        return SecurityFrameworkUtils.getLoginUserId();
+	    return "fc01f89c-9ce0-4f89-ad60-1a3ca9cdce8e";
+//        return "91507848-736f-4327-887e-22aec122d5c7";
+//        return SecurityFrameworkUtils.getLoginUserId();
 	}
 }