Selaa lähdekoodia

Merge branch 'master' into dev_v1.0.0

zhangjie 1 vuosi sitten
vanhempi
commit
da2ac861b5

+ 94 - 0
src/style/global.less

@@ -489,6 +489,7 @@ body {
       flex-grow: 2;
       overflow: auto;
       padding: 15px 15px 30px;
+      position: relative;
     }
     .sop-step-footer {
       flex-grow: 0;
@@ -500,3 +501,96 @@ body {
     }
   }
 }
+// sop-step-history
+.sop-step-history {
+  &-label {
+    position: absolute;
+    z-index: 9;
+    top: 24px;
+    right: 24px;
+    padding: 16px 12px;
+    background: @brand-color;
+    border-radius: 3px;
+    line-height: 22px;
+    color: #ffffff;
+    width: 38px;
+    cursor: pointer;
+    &:hover {
+      background: #124eda;
+    }
+    .t-icon {
+      margin-bottom: 8px;
+      margin-top: -4px;
+      font-size: 16px;
+    }
+  }
+  &-content {
+    position: absolute;
+    z-index: 9;
+    top: 24px;
+    right: 70px;
+    bottom: 89px;
+    width: 366px;
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+
+    background: #ffffff;
+    box-shadow: 0px 2px 12px 0px rgba(0, 0, 0, 0.12);
+    border-radius: 3px;
+    border: 1px solid @light-border-color;
+
+    .content-head {
+      flex-grow: 0;
+      flex-shrink: 0;
+      padding: 16px 24px;
+      border-bottom: 1px solid @light-border-color;
+    }
+    .content-body {
+      flex-grow: 2;
+      overflow-y: auto;
+      padding: 16px;
+
+      .t-collapse,
+      .t-collapse-panel__header,
+      .t-collapse-panel__body {
+        border: none;
+      }
+      .t-collapse-panel {
+        margin-bottom: 8px;
+      }
+      .t-collapse-panel__content {
+        padding: 8px 16px;
+        background: #f2f3f5;
+        border-radius: 3px;
+      }
+      .t-collapse-panel__header {
+        padding: 8px;
+        border-radius: 3px;
+        font-weight: 400;
+        &:hover {
+          background: #e8f3ff;
+        }
+      }
+      .collapse-head-right {
+        color: @dark-text-color-2;
+        font-size: 14px;
+        font-weight: 400;
+      }
+    }
+    .content-detail {
+      &-head {
+        padding-bottom: 8px;
+        line-height: 22px;
+        color: @dark-text-color;
+        border-bottom: 1px solid #d9d9d9;
+      }
+      &-body {
+        color: @dark-text-color-2;
+        font-size: 12px;
+        padding-top: 8px;
+        line-height: 20px;
+      }
+    }
+  }
+}

+ 35 - 0
src/utils/tool.js

@@ -426,3 +426,38 @@ export function objTypeOf(obj) {
   };
   return map[toString.call(obj)];
 }
+
+/**
+ *  获取时间长度文字
+ * @param {Number} timeNumber 时间数值,单位:毫秒
+ */
+export function timeNumberToText(timeNumber) {
+  const DAY_TIME = 24 * 60 * 60 * 1000;
+  const HOUR_TIME = 60 * 60 * 1000;
+  const MINUTE_TIME = 60 * 1000;
+  const SECOND_TIME = 1000;
+  let [day, hour, minute, second] = [0, 0, 0, 0];
+  let residueTime = timeNumber;
+
+  if (residueTime >= DAY_TIME) {
+    day = Math.floor(residueTime / DAY_TIME);
+    residueTime -= day * DAY_TIME;
+    day += '天';
+  }
+  if (residueTime >= HOUR_TIME) {
+    hour = Math.floor(residueTime / HOUR_TIME);
+    residueTime -= hour * HOUR_TIME;
+    hour += '小时';
+  }
+  if (residueTime >= MINUTE_TIME) {
+    minute = Math.floor(residueTime / MINUTE_TIME);
+    residueTime -= minute * MINUTE_TIME;
+    minute += '分钟';
+  }
+  if (residueTime >= SECOND_TIME) {
+    second = Math.round(residueTime / SECOND_TIME);
+    second += '秒';
+  }
+
+  return [day, hour, minute, second].filter((item) => !!item).join('');
+}

+ 20 - 0
src/views/my-workbenches/workbenches/my-waits/waits-list.vue

@@ -4,6 +4,7 @@
       v-for="item in tableData"
       :key="item.id"
       class="message-item cursor-pointer"
+      @click="editSopFlowHandle(item)"
     >
       <div class="m-head">
         <div class="m-title">
@@ -48,15 +49,34 @@
       <img src="../../../../assets/none_message.svg" />
       <p>暂无数据</p>
     </div>
+
+    <!-- SopStepDialog -->
+    <sop-step-dialog
+      v-model:visible="showSopStepDialog"
+      :sop="curSopData"
+      type="fill"
+      @confirm="sopStepConfirm"
+    ></sop-step-dialog>
   </div>
 </template>
 
 <script setup name="MyTaskList">
 import { timestampFilter, customerTypeFilter } from '@/utils/filter';
+import SopStepDialog from '@/views/sop/sop-manange/sop-step/sop-step-dialog.vue';
 
 const { tableData, pagination, onChange } = defineProps([
   'tableData',
   'pagination',
   'onChange',
 ]);
+
+const showSopStepDialog = ref(false);
+const curSopData = ref({});
+const editSopFlowHandle = (row) => {
+  curSopData.value = row;
+  showSopStepDialog.value = true;
+};
+const sopStepConfirm = () => {
+  onChange(pagination);
+};
 </script>

+ 1 - 1
src/views/sop/sop-manage/office-sop/index.vue

@@ -223,7 +223,7 @@ const columns = [
   { colKey: 'flowCreateName', title: '提交人', width: 140 },
   { colKey: 'flowCreateTime', title: '提交时间', width: 180 },
   { colKey: 'flowUpdateTime', title: '更新时间', width: 180 },
-  { colKey: 'status', title: '流程状态', width: 120 },
+  { colKey: 'statusStr', title: '流程状态', width: 120 },
   { colKey: 'taskName', title: '流程节点', width: 160 },
   { colKey: 'pendApproveName', title: '当前节点负责人', width: 140 },
   {

+ 145 - 32
src/views/sop/sop-manage/sop-step/index.vue

@@ -89,8 +89,8 @@
       >
         <t-form
           ref="form"
-          class="sop-step-body"
           colon
+          class="sop-step-body"
           :rules="rules"
           :data="formData"
           labelAlign="top"
@@ -123,7 +123,7 @@
             <t-button
               v-if="!IS_EDIT_MODE"
               theme="primary"
-              @click="submitHandle('START')"
+              @click="submitHandle()"
               >提交</t-button
             >
 
@@ -135,6 +135,60 @@
             >
           </template>
         </t-space>
+        <!-- setup history -->
+        <div v-if="flowApproveHistoryList.length" class="sop-step-history">
+          <div class="sop-step-history-label" @click="toViewHistory">
+            <ChevronRightDoubleIcon v-if="stepHistoryShow" />
+            <ChevronLeftDoubleIcon v-else />
+            流程动态
+          </div>
+          <transition name="fade-slide" mode="out-in" appear>
+            <div v-if="stepHistoryShow" class="sop-step-history-content">
+              <div class="content-head">
+                <h2>
+                  {{ sop.statusStr }}
+                </h2>
+                <p>{{ stepDuration }}</p>
+              </div>
+              <div class="content-body">
+                <t-collapse>
+                  <t-collapse-panel
+                    v-for="(item, findex) in flowApproveHistoryList"
+                    :key="findex"
+                    :header="item.taskName"
+                  >
+                    <template #headerRightContent>
+                      <t-space size="small">
+                        <span class="collapse-head-right">{{
+                          timestampFilter(item.createTime)
+                        }}</span>
+                        <t-tag
+                          :theme="
+                            item.approveOperation === 'REJECT'
+                              ? 'danger'
+                              : 'success'
+                          "
+                          variant="light"
+                        >
+                          {{ item.approveRemark }}
+                        </t-tag>
+                      </t-space>
+                    </template>
+                    <div class="content-detail">
+                      <div class="content-detail-head">
+                        {{ item.approveUserName }}
+                      </div>
+                      <div class="content-detail-body">
+                        <p>开始处理:{{ timestampFilter(item.createTime) }}</p>
+                        <p>处理耗时:{{ item.duration }}</p>
+                      </div>
+                    </div>
+                  </t-collapse-panel>
+                </t-collapse>
+              </div>
+            </div>
+          </transition>
+        </div>
       </t-tab-panel>
     </t-tabs>
   </div>
@@ -142,7 +196,11 @@
 
 <script setup name="SopStep">
 import { ref, computed, watch } from 'vue';
-import { ErrorCircleFilledIcon } from 'tdesign-icons-vue-next';
+import {
+  ErrorCircleFilledIcon,
+  ChevronLeftDoubleIcon,
+  ChevronRightDoubleIcon,
+} from 'tdesign-icons-vue-next';
 import { MessagePlugin } from 'tdesign-vue-next';
 
 import DynamicFormItem from '../../components/dynamic-form-item/index.vue';
@@ -155,7 +213,7 @@ import {
   sopApplyApi,
   flowFormPropertiesApi,
 } from '@/api/sop';
-import { objCopy } from '@/utils/tool';
+import { objCopy, timeNumberToText } from '@/utils/tool';
 import { timestampFilter } from '@/utils/filter';
 
 const props = defineProps({
@@ -217,10 +275,36 @@ const allFormData = ref({});
 const allSteps = ref([]);
 const tabs = ref([]);
 const curStep = ref('');
+const curStepData = ref({});
 const currFlowTaskResultSetup = ref(null);
-const curStepSetup = ref(1);
 const flowId = props.sop.flowId;
 const crmInfo = ref({});
+const approveRejectFormIds = {
+  // [taskkey]:[formId]
+  f_usertask_office_inside_approve_region_3: 'approve_radio_3',
+  f_usertask_office_inside_approve_engineer_4: 'approve_radio_4',
+};
+const stepHistoryShow = ref(false);
+const flowApproveHistoryList = ref([]);
+const stepDuration = ref('');
+
+function getFlowApproveHistoryList(data, allStepData) {
+  if (!data) return [];
+
+  let setupData = {};
+  allStepData.forEach((item) => {
+    setupData[item.setup] = item.taskName;
+  });
+  let lastTime = 0;
+  return data.map((item, index) => {
+    let nitem = { ...item };
+    nitem.duration =
+      index === 0 ? '-' : timeNumberToText(item.createTime - lastTime);
+    lastTime = item.createTime;
+    nitem.taskName = setupData[item.approveSetup];
+    return nitem;
+  });
+}
 
 const initNew = async () => {
   loading.value = true;
@@ -238,7 +322,8 @@ const initNew = async () => {
       label: item.taskName,
     };
   });
-  curStep.value = tabs.value.slice(-1)[0].value;
+  curStep.value = tabs.value[0].value;
+  curStepData.value = allSteps.value[0];
 };
 const initFill = async () => {
   loading.value = true;
@@ -246,31 +331,31 @@ const initFill = async () => {
   crmInfo.value = res.crmInfo;
   curStep.value = res.currFlowTaskResult.taskName;
   currFlowTaskResultSetup.value = res.currFlowTaskResult.setup;
-  curStepSetup.value = res.currFlowTaskResult.setup;
+  curStepData.value = res.currFlowTaskResult;
   res.flowTaskHistoryList = res.flowTaskHistoryList || [];
   res.flowTaskHistoryList.forEach((item) => {
     item.formProperty.forEach((v) => {
       v.writable = false;
     });
   });
-  allSteps.value = [...res.flowTaskHistoryList, res.currFlowTaskResult];
-  tabs.value = [
-    ...res.flowTaskHistoryList.map((item) => {
-      return {
-        value: item.taskName,
-        label: item.taskName,
-      };
-    }),
-    {
-      value: res.currFlowTaskResult.taskName,
-      label: res.currFlowTaskResult.taskName,
-    },
-  ];
+  allSteps.value = [...res.flowTaskHistoryList, res.currFlowTaskResult].sort(
+    (a, b) => a.setup - b.setup
+  );
+  tabs.value = allSteps.value.map((item) => {
+    return {
+      value: item.taskName,
+      label: item.taskName,
+    };
+  });
   allSteps.value.forEach((item) => {
     item.formProperty.forEach((prop) => {
       prop.value = prop.value ? JSON.parse(prop.value).value : null;
     });
   });
+  flowApproveHistoryList.value = getFlowApproveHistoryList(
+    res.flowApproveHistoryList,
+    allSteps.value
+  );
   loading.value = false;
 };
 const initEdit = async () => {
@@ -284,6 +369,10 @@ const initEdit = async () => {
   allSteps.value = Object.values(res.setupMap).sort(
     (a, b) => a.setup - b.setup
   );
+  flowApproveHistoryList.value = getFlowApproveHistoryList(
+    flowRes.flowApproveHistoryList,
+    allSteps.value
+  );
   let allFormMap = {};
   allSteps.value.forEach((item) => {
     item.formProperty.forEach((prop) => {
@@ -301,10 +390,10 @@ const initEdit = async () => {
     };
   });
   curStep.value = tabs.value.slice(-1)[0].value;
-  const curStepData = allSteps.value.find(
+  const stepData = allSteps.value.find(
     (item) => item.taskName === curStep.value
   );
-  curStepSetup.value = curStepData.setup;
+  curStepData.value = stepData;
 };
 const init = () => {
   if (IS_FILL_MODE.value) {
@@ -321,12 +410,15 @@ const init = () => {
 init();
 
 const showAction = computed(() => {
-  if ((IS_EDIT_MODE.value && curStepSetup.value !== 1) || IS_NEW_MODE.value)
+  if (
+    (IS_EDIT_MODE.value && curStepData.value.setup !== 1) ||
+    IS_NEW_MODE.value
+  )
     return true;
 
   if (
     IS_FILL_MODE.value &&
-    curStepSetup.value === currFlowTaskResultSetup.value
+    curStepData.value.setup === currFlowTaskResultSetup.value
   )
     return true;
 
@@ -408,14 +500,19 @@ watch(curFormConfig, (val) => {
   }, {});
 });
 
+const toViewHistory = () => {
+  console.log('11');
+  stepHistoryShow.value = !stepHistoryShow.value;
+};
+
 const stepChange = () => {
   if (IS_EDIT_MODE.value) {
     allFormData.value = { ...allFormData.value, ...formData.value };
   }
-  const curStepData = allSteps.value.find(
+  const stepData = allSteps.value.find(
     (item) => item.taskName === curStep.value
   );
-  curStepSetup.value = curStepData.setup;
+  curStepData.value = stepData;
 };
 
 const itemValueChange = ({ prop, value }) => {
@@ -440,10 +537,10 @@ const getFormData = () => {
     }
   } else {
     // 新增 or 填报
-    const curStepData = allSteps.value.find(
+    const stepData = allSteps.value.find(
       (item) => item.taskName === curStep.value
     );
-    data = { ...curStepData };
+    data = { ...stepData };
     data.formProperty = data.formProperty.map((item) => {
       return {
         ...item,
@@ -456,19 +553,35 @@ const getFormData = () => {
 };
 // 填报-提交
 const submitHandle = async (approve = 'START') => {
-  if (approve === 'START') {
+  if (approve !== 'DRAFT') {
+    // 提交
     const valid = await form.value[0].validate();
     if (valid !== true) return;
 
-    approve = curStepSetup.value === 1 ? 'START' : 'PASS';
+    if (curStepData.value.setup === 1) {
+      approve = 'START';
+    } else {
+      const approveRejectFormId =
+        approveRejectFormIds[curStepData.value.taskKey];
+      if (approveRejectFormId && formData.value[approveRejectFormId] === '0') {
+        approve = 'REJECT';
+      } else {
+        approve = 'PASS';
+      }
+    }
   }
 
   if (IS_FILL_MODE.value) {
-    const res = await sopApproveApi({
+    let data = {
       taskId: props.sop.taskId,
       formProperties: getFormData(),
       approve,
-    }).catch(() => {});
+    };
+    if (approve === 'REJECT') {
+      // 目前只打回给定的允许打回的第一个选项
+      data.setup = curStepData.value.approveRejectList[0].setup;
+    }
+    const res = await sopApproveApi(data).catch(() => {});
     if (!res) return;
   } else if (IS_NEW_MODE.value) {
     const res = await sopApplyApi({

+ 1 - 1
src/views/sop/sop-manage/student-sop/index.vue

@@ -222,7 +222,7 @@ const columns = [
   { colKey: 'flowCreateName', title: '提交人', width: 140 },
   { colKey: 'flowCreateTime', title: '提交时间', width: 180 },
   { colKey: 'flowUpdateTime', title: '更新时间', width: 180 },
-  { colKey: 'status', title: '流程状态', width: 120 },
+  { colKey: 'statusStr', title: '流程状态', width: 120 },
   { colKey: 'taskName', title: '流程节点', width: 160 },
   { colKey: 'pendApproveName', title: '当前节点负责人', width: 140 },
   {

+ 1 - 0
src/views/user/auth-manage/user-manage/index.vue

@@ -96,6 +96,7 @@ const curRow = ref(null);
 const columns = [
   { colKey: 'id', title: '用户ID', width: 200 },
   { colKey: 'realName', title: '姓名', minWidth: 140 },
+  { colKey: 'loginName', title: '登录名', minWidth: 140 },
   { colKey: 'genderStr', title: '性别', width: 80 },
   { colKey: 'mobileNumber', title: '手机', width: 160 },
   { colKey: 'orgName', title: '所属节点', width: 160 },