zhangjie 1 жил өмнө
parent
commit
aeab1fa67f

+ 155 - 2
src/style/global.less

@@ -163,7 +163,7 @@ body {
   }
 
   &.is-active {
-    color: var(--td-brand-color);
+    color: @brand-color;
     background-color: #e8f3ff;
   }
   &.is-mark {
@@ -240,7 +240,7 @@ body {
 }
 .form-group-title {
   font-weight: bold;
-  // color: var(--td-brand-color);
+  // color: @brand-color;
   color: @dark-text-color;
   border-bottom: 1px dashed #ddd;
   padding-bottom: 5px;
@@ -329,3 +329,156 @@ body {
     }
   }
 }
+// sop-dialog
+.sop-dialog {
+  .t-drawer__header {
+    border: none;
+  }
+  .t-drawer__body {
+    padding-top: 0;
+  }
+  .t-collapse {
+    margin-bottom: 16px;
+    border-radius: 3px;
+  }
+  .t-collapse,
+  .t-collapse-panel__header,
+  .t-collapse-panel__body {
+    border-color: @light-border-color;
+  }
+  .t-collapse-panel__header {
+    padding-top: 8px;
+    padding-bottom: 8px;
+    color: @dark-text-color;
+  }
+  .t-collapse-panel.t-is-disabled {
+    cursor: auto;
+    .t-collapse-panel__header {
+      cursor: auto;
+      color: @dark-text-color;
+    }
+  }
+  .t-collapse-panel__content {
+    background-color: #fff;
+  }
+  .sop-step {
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+  }
+  .sop-step-header {
+    .t-collapse-panel__content {
+      padding: 8px 40px;
+    }
+  }
+  .sop-step-mid {
+    .t-collapse-panel__icon {
+      display: none;
+    }
+    .t-collapse-panel__header {
+      padding-left: 16px;
+      padding-right: 16px;
+    }
+    .t-collapse-panel__content {
+      padding: 8px 16px;
+    }
+    .t-form__label {
+      line-height: 22px;
+    }
+    .t-form__controls,
+    .t-form__controls-content {
+      min-height: 22px;
+    }
+  }
+  .sop-intro {
+    padding: 8px;
+    margin-top: 8px;
+    flex-flow: 0;
+    flex-shrink: 0;
+
+    .t-alert__icon {
+      display: none;
+    }
+    .t-alert__description {
+      color: @error-color;
+    }
+    &-title {
+      float: left;
+      font-size: 14px;
+      .t-icon {
+        margin-top: -3px;
+      }
+    }
+
+    &-content {
+      margin-left: 62px;
+    }
+  }
+  .sop-step-list {
+    display: flex;
+    flex-grow: 2;
+    border-radius: 3px;
+    border: 1px solid @light-border-color;
+    justify-content: space-between;
+    align-items: stretch;
+
+    .t-tabs__bar {
+      display: none;
+    }
+
+    .t-tabs__nav-wrap {
+      padding: 8px;
+      width: 100%;
+    }
+    .t-tabs__nav-item {
+      height: auto;
+      min-height: 36px;
+      border-radius: 3px;
+      &.t-is-active {
+        .t-tabs__nav-item-wrapper {
+          color: @brand-color;
+          background-color: #e8f3ff;
+        }
+      }
+    }
+    .t-tabs__nav-item-wrapper {
+      padding: 7px 16px;
+      line-height: 22px;
+      height: auto;
+      margin: 0;
+      width: 100%;
+      margin-bottom: 5px;
+    }
+
+    .t-tabs__header {
+      flex-grow: 0;
+      flex-shrink: 0;
+      width: 140px;
+      overflow-y: auto;
+      border-right: 1px solid @light-border-color;
+    }
+    .t-tabs__nav-container::after {
+      display: none;
+    }
+
+    .t-tab-panel {
+      height: 100%;
+      display: flex;
+      justify-content: space-between;
+      align-items: stretch;
+      flex-direction: column;
+    }
+    .sop-step-body {
+      flex-grow: 2;
+      overflow: auto;
+      padding: 0 15px 15px;
+    }
+    .sop-step-footer {
+      flex-grow: 0;
+      flex-shrink: 0;
+      padding: 16px;
+      border-top: 1px solid @light-border-color;
+      flex-direction: row-reverse;
+    }
+  }
+}

+ 148 - 83
src/views/sop/sop-manage/office-sop/index.vue

@@ -1,11 +1,34 @@
 <template>
   <div class="office-sop flex flex-col h-full">
     <div class="page-action">
-      <t-space size="small">
-        <t-button theme="danger" :disabled="!selectedRowKeys.length">
-          作废
-        </t-button>
-      </t-space>
+      <t-button
+        theme="primary"
+        :disabled="!selectedRowKeys.length"
+        @click="handleBatchCancel"
+      >
+        <template #icon><svg-icon name="delete" color="#fff" /></template>
+        作废
+      </t-button>
+      <t-button variant="outline" @click="handleAdd">
+        <template #icon><svg-icon name="view" color="#262626" /></template>
+        显示字段</t-button
+      >
+      <t-button variant="outline" @click="handleAdd">
+        <template #icon><svg-icon name="refresh" color="#262626" /></template>
+        刷新</t-button
+      >
+      <t-button variant="outline" @click="handleAdd">
+        <template #icon><svg-icon name="sort" color="#262626" /></template>
+        排序</t-button
+      >
+      <t-button variant="outline" @click="handleAdd">
+        <template #icon><svg-icon name="enlarge" color="#262626" /></template>
+        放大</t-button
+      >
+      <t-button variant="outline" @click="handleAdd">
+        <template #icon><svg-icon name="shrink" color="#262626" /></template>
+        缩小</t-button
+      >
     </div>
     <SearchForm :fields="fields" :params="params">
       <template #service="{ item, params }">
@@ -28,15 +51,62 @@
           showPageSize: false,
           total: pagination.total,
         }"
+        v-loading="tableLoading"
         :selected-row-keys="selectedRowKeys"
         @select-change="selectChange"
       >
+        <template #beginTime="{ col, row }">
+          {{ timestampFilter(row[col.colKey]) }}
+        </template>
+        <template #examStartTime="{ col, row }">
+          {{ timestampFilter(row[col.colKey]) }}
+        </template>
+        <template #examEndTime="{ col, row }">
+          {{ timestampFilter(row[col.colKey]) }}
+        </template>
+        <template #flowCreateTime="{ col, row }">
+          {{ timestampFilter(row[col.colKey]) }}
+        </template>
+        <template #flowUpdateTime="{ col, row }">
+          {{ timestampFilter(row[col.colKey]) }}
+        </template>
+        <template #operate="{ row }">
+          <div class="table-operations">
+            <t-link theme="primary" hover="color" @click="toCurSopFlow(row)">
+              填报
+            </t-link>
+            <t-link theme="primary" hover="color" @click="createSopFlow(row)">
+              新增SOP
+            </t-link>
+            <t-link
+              theme="primary"
+              hover="color"
+              @click="toCreateViolation(row)"
+            >
+              新增违规
+            </t-link>
+            <t-link theme="primary" hover="color" @click="qualityIssue(row)">
+              上报质量问题
+            </t-link>
+            <t-link theme="primary" hover="color" @click="planChange(row)">
+              计划变更报备
+            </t-link>
+          </div>
+        </template>
       </t-table>
     </div>
     <!-- <PlanChangeDialog
       v-model:visible="showPlanChangeDialog"
       :curRow="curRow"
     ></PlanChangeDialog> -->
+
+    <!-- SopStepDialog -->
+    <sop-step-dialog
+      v-model:visible="showSopStepDialog"
+      :sop="curSopData"
+      :type="curSopType"
+      @confirm="fetchData"
+    ></sop-step-dialog>
   </div>
 </template>
 
@@ -44,7 +114,9 @@
 import { ref, reactive, computed } from 'vue';
 import useFetchTable from '@/hooks/useFetchTable';
 import { useRouter } from 'vue-router';
-import { sopListApi } from '@/api/sop';
+import { sopListApi, sopBatchCancelApi } from '@/api/sop';
+import { timestampFilter } from '@/utils/filter';
+import SopStepDialog from '../sop-step/sop-step-dialog.vue';
 // import PlanChangeDialog from './plan-change-dialog.vue';
 // const curRow = ref(null);
 const selectedRowKeys = ref([]);
@@ -60,84 +132,29 @@ const columns = [
     width: 50,
     fixed: 'left',
   },
-  { colKey: 'serviceName', title: '服务单元' },
-  { colKey: 'sopNo', title: 'SOP流水号' },
-  { colKey: 'crmNo', title: '项目单号' },
-  { colKey: 'beginTime', title: '派单时间' },
-  { colKey: 'customManagerName', title: '客户经理' },
-  { colKey: 'customManagerTypeStr', title: '客户类型' },
-  { colKey: 'customName', title: '客户名称' },
-  { colKey: 'crmName', title: '项目名称' },
-  { colKey: 'productName', title: '实施产品' },
-  { colKey: 'examStartTime', title: '考试开始时间' },
-  { colKey: 'examEndTime', title: '考试结束时间' },
+  { colKey: 'serviceName', title: '服务单元', width: 160 },
+  { colKey: 'sopNo', title: 'SOP流水号', width: 200 },
+  { colKey: 'crmNo', title: '项目单号', width: 200 },
+  { colKey: 'beginTime', title: '派单时间', width: 180 },
+  { colKey: 'customManagerName', title: '客户经理', width: 140 },
+  { colKey: 'customManagerTypeStr', title: '客户类型', width: 120 },
+  { colKey: 'customName', title: '客户名称', width: 140 },
+  { colKey: 'crmName', title: '项目名称', minWidth: 160 },
+  { colKey: 'productName', title: '实施产品', width: 120 },
+  { colKey: 'examStartTime', title: '考试开始时间', width: 180 },
+  { colKey: 'examEndTime', title: '考试结束时间', width: 180 },
   //...
-  { colKey: 'flowCreateName', title: '提交人' },
-  { colKey: 'flowCreateTime', title: '提交时间' },
-  { colKey: 'flowUpdateTime', title: '更新时间' },
-  { colKey: 'status', title: '流程状态' },
-  { colKey: 'taskName', title: '流程节点' },
-  { colKey: 'pendApproveName', title: '当前节点负责人' },
+  { colKey: 'flowCreateName', title: '提交人', width: 140 },
+  { colKey: 'flowCreateTime', title: '提交时间', width: 180 },
+  { colKey: 'flowUpdateTime', title: '更新时间', width: 180 },
+  { colKey: 'status', title: '流程状态', width: 120 },
+  { colKey: 'taskName', title: '流程节点', width: 160 },
+  { colKey: 'pendApproveName', title: '当前节点负责人', width: 140 },
   {
     title: '管理',
     colKey: 'operate',
     fixed: 'right',
-    width: 400,
-    cell: (h, { row }) => {
-      return (
-        <div class="table-operations">
-          <t-link
-            theme="primary"
-            hover="color"
-            onClick={(e) => {
-              e.stopPropagation();
-              toCurSopFlow(row);
-            }}
-          >
-            填报
-          </t-link>
-          <t-link
-            theme="primary"
-            hover="color"
-            onClick={(e) => {
-              e.stopPropagation();
-              createSopFlow(row);
-            }}
-          >
-            新增SOP
-          </t-link>
-          <t-link
-            theme="primary"
-            hover="color"
-            onClick={(e) => {
-              e.stopPropagation();
-            }}
-          >
-            新增违规
-          </t-link>
-          <t-link
-            theme="primary"
-            hover="color"
-            onClick={(e) => {
-              e.stopPropagation();
-              qualityIssue();
-            }}
-          >
-            上报质量问题
-          </t-link>
-          <t-link
-            theme="primary"
-            hover="color"
-            onClick={(e) => {
-              e.stopPropagation();
-              planChange(row);
-            }}
-          >
-            计划变更报备
-          </t-link>
-        </div>
-      );
-    },
+    width: 420,
   },
 ];
 const fields = ref([
@@ -149,6 +166,19 @@ const fields = ref([
     colSpan: 6,
     cell: 'service',
   },
+  {
+    type: 'buttons',
+    colSpan: 3,
+    children: [
+      {
+        type: 'button',
+        text: '搜索',
+        onClick: () => {
+          search();
+        },
+      },
+    ],
+  },
 ]);
 const params = reactive({
   serviceId: '',
@@ -160,15 +190,52 @@ const {
   loading: tableLoading,
   pagination,
   tableData,
+  search,
   fetchData,
   onChange,
 } = useFetchTable(sopListApi, { params: transParams });
 
+const handleAdd = () => {};
+
+const handleBatchCancel = () => {
+  if (!selectedRowKeys.value.length) {
+    MessagePlugin.error('请选择要作废的记录');
+    return;
+  }
+  const confirmDia = DialogPlugin({
+    header: '系统通知',
+    body: `是否作废所选sop?`,
+    confirmBtn: '确定',
+    cancelBtn: '取消',
+    theme: 'warning',
+    onConfirm: async () => {
+      confirmDia.hide();
+      const res = await sopBatchCancelApi(selectedRowKeys.value).catch(
+        () => {}
+      );
+      if (!res) return;
+      MessagePlugin.success('操作成功');
+      refresh();
+    },
+  });
+};
+
+const toCreateViolation = (row) => {
+  console.log(row);
+};
+
+const showSopStepDialog = ref(false);
+const curSopData = ref({});
+const curSopType = ref('');
 const createSopFlow = (row) => {
-  router.push({ name: 'SopStep' });
+  curSopType.value = 'add';
+  curSopData.value = row;
+  showSopStepDialog.value = true;
 };
 const toCurSopFlow = (row) => {
-  router.push({ name: 'SopStep', query: { type: 'fill', flowId: row.flowId } });
+  curSopType.value = 'fill';
+  curSopData.value = row;
+  showSopStepDialog.value = true;
 };
 
 const planChange = (row) => {
@@ -180,5 +247,3 @@ const qualityIssue = (row) => {
   router.push({ name: 'QualityIssue' });
 };
 </script>
-
-<style></style>

+ 134 - 63
src/views/sop/sop-manage/sop-step/index.vue

@@ -1,63 +1,144 @@
 <template>
   <div class="sop-step">
-    <div class="page-wrap">
-      <p class="split-line"></p>
-      <div class="p-t-10px p-b-30px" style="background-color: #fff">
-        <t-tabs v-model="curStep" placement="left" class="m-t-10px">
-          <t-tab-panel
-            v-for="item in tabs"
-            :key="item.taskKey"
-            :value="item.value"
-            :label="item.label"
+    <t-collapse class="sop-step-header" defaultExpandAll>
+      <t-collapse-panel header="SOP各协作项目角色说明">
+        <p
+          >1.大区经理:研究生项目前置流程、项目必要信息工作交接、项目内审、SOP产出物检查;</p
+        >
+        <p
+          >2.区域协调人:根据与客户沟通确认的项目关键信息表,组织项目成员共同进行项目风险评估,对SOP产出物进行检查跟进;</p
+        >
+        <p>3.实施工程师:完成现场实施工作,执行SOP及产出物提交;</p>
+        <t-alert class="sop-intro" theme="error">
+          <div slot="title" class="sop-intro-title">
+            <ErrorCircleFilledIcon /> 注意:</div
           >
-            <t-form
-              ref="form"
-              colon
-              :label-width="labelWidth"
-              class="cur-step-view"
-              :rules="rules"
-              :data="formData"
+          <div class="sop-intro-content">
+            <p
+              >1.若存在暂时无法确定的需求为必填项,大区经理可先按照经验填写,客户确定后,可对关键信息进行补充和修改,具体可参考项目计划变更报备流程。</p
             >
-              <t-row :gutter="[0, 20]">
-                <t-col
-                  :span="
-                    fullWidthCodes.includes(config.code)
-                      ? 12
-                      : config.span < 6
-                      ? 6
-                      : config.span || 6
-                  "
-                  v-for="config in curFormConfig"
-                  :key="config.id"
-                >
-                  <MyFormItem
-                    :config="config"
-                    :labelWidth="labelWidth"
-                    @change="itemValueChange"
-                  ></MyFormItem>
-                </t-col>
-              </t-row>
-            </t-form>
-            <s-buttons
-              confirmText="提交"
-              @cancel="router.back()"
-              @confirm="submitHandle"
-              class="m-t-50px"
-            ></s-buttons>
-          </t-tab-panel>
-        </t-tabs>
-      </div>
-    </div>
+            <p>2.区域协调人可能会由大区经理兼任。</p>
+          </div>
+        </t-alert>
+      </t-collapse-panel>
+    </t-collapse>
+    <t-collapse class="sop-step-mid" defaultExpandAll>
+      <t-collapse-panel disabled>
+        <template #expandIcon></template>
+        <template #header>
+          项目派单信息(SOP流水单号:{{ sop.sopNo }})
+        </template>
+        <t-form colon label-width="72px">
+          <t-row :gutter="[0, 4]">
+            <t-col :span="3">
+              <t-form-item label="项目单号">{{ sop.crmNo }}</t-form-item>
+            </t-col>
+            <t-col :span="3">
+              <t-form-item label="项目名称">{{ sop.crmName }}</t-form-item>
+            </t-col>
+            <t-col :span="3">
+              <t-form-item label="派单时间">{{ sop.beginTime }}</t-form-item>
+            </t-col>
+            <t-col :span="3">
+              <t-form-item label="客户经理">{{
+                sop.customManagerName
+              }}</t-form-item>
+            </t-col>
+            <t-col :span="3">
+              <t-form-item label="客户类型">{{
+                sop.customManagerTypeStr
+              }}</t-form-item>
+            </t-col>
+            <t-col :span="3">
+              <t-form-item label="客户名称">{{ sop.customName }}</t-form-item>
+            </t-col>
+            <t-col :span="3">
+              <t-form-item label="考试开始时间" label-width="100px">{{
+                sop.examStartTime
+              }}</t-form-item>
+            </t-col>
+            <t-col :span="3">
+              <t-form-item label="考试结束时间" label-width="100px">{{
+                sop.examEndTime
+              }}</t-form-item>
+            </t-col>
+            <t-col :span="3">
+              <t-form-item label="实施产品">{{ sop.productName }}</t-form-item>
+            </t-col>
+            <t-col :span="3">
+              <t-form-item label="服务单元">{{ sop.serviceName }}</t-form-item>
+            </t-col>
+          </t-row>
+        </t-form>
+      </t-collapse-panel>
+    </t-collapse>
+
+    <t-tabs class="sop-step-list" v-model="curStep" placement="left">
+      <t-tab-panel
+        v-for="item in tabs"
+        :key="item.taskKey"
+        :value="item.value"
+        :label="item.label"
+      >
+        <t-form
+          ref="form"
+          class="sop-step-body"
+          colon
+          :label-width="labelWidth"
+          :rules="rules"
+          :data="formData"
+        >
+          <t-row :gutter="[0, 20]">
+            <t-col
+              :span="
+                fullWidthCodes.includes(config.code)
+                  ? 12
+                  : config.span < 6
+                  ? 6
+                  : config.span || 6
+              "
+              v-for="config in curFormConfig"
+              :key="config.id"
+            >
+              <MyFormItem
+                :config="config"
+                :labelWidth="labelWidth"
+                @change="itemValueChange"
+              ></MyFormItem>
+            </t-col>
+          </t-row>
+        </t-form>
+        <t-space class="sop-step-footer">
+          <t-button theme="primary" @click="submitHandle">提交</t-button>
+          <t-button theme="default" @click="stageHandle">保存草稿</t-button>
+        </t-space>
+      </t-tab-panel>
+    </t-tabs>
   </div>
 </template>
 
 <script setup name="SopStep">
 import { ref, computed, watch } from 'vue';
+import { ErrorCircleFilledIcon } from 'tdesign-icons-vue-next';
+
 import MyFormItem from '../../components/my-form-item.vue';
 import { useRouter, useRoute } from 'vue-router';
 import { sopFlowViewApi } from '@/api/sop';
 import bus from '@/utils/bus';
-// import testData from '../test';
+
+const props = defineProps({
+  type: {
+    type: String,
+    default: 'fill',
+  },
+  sop: {
+    type: Object,
+    default() {
+      return {};
+    },
+  },
+});
+
 const needValueCodes = [
   'NUMBER', //新增
   'TEXT',
@@ -94,7 +175,8 @@ const form = ref(null);
 const allSteps = ref([]);
 const tabs = ref([]);
 const curStep = ref('');
-const flowId = route.query.flowId;
+const flowId = props.sop.flowId;
+
 const initFill = () => {
   loading.value = true;
   sopFlowViewApi({ flowId }).then((res) => {
@@ -119,7 +201,7 @@ const initFill = () => {
   });
 };
 const init = () => {
-  if (route.query.type === 'fill') {
+  if (props.type === 'fill') {
     //type为fill是点击“填报”进来的
     initFill();
   } else {
@@ -157,6 +239,7 @@ watch(curFormConfig, (val) => {
     return obj;
   }, {});
 });
+// 提交
 const submitHandle = () => {
   bus.emit('checkTable');
   let tablePassed = false;
@@ -176,6 +259,8 @@ const submitHandle = () => {
     });
   }, 10);
 };
+// 暂存
+const stageHandle = () => {};
 const itemValueChange = ({ prop, value }) => {
   formData.value[prop] = value;
 };
@@ -183,17 +268,3 @@ const back = () => {
   history.back();
 };
 </script>
-
-<style lang="less" scoped>
-.sop-step {
-  .page-wrap {
-    .split-line {
-      border-bottom: 1px dashed #ddd;
-      margin: 30px 0 10px;
-    }
-    .cur-step-view {
-      // padding: 20px 0;
-    }
-  }
-}
-</style>

+ 49 - 0
src/views/sop/sop-manage/sop-step/sop-step-dialog.vue

@@ -0,0 +1,49 @@
+<template>
+  <my-drawer
+    class="sop-dialog"
+    :visible="visible"
+    :header="title"
+    size="80%"
+    attach="body"
+    :closeOnOverlayClick="false"
+    :close-btn="true"
+    :footer="false"
+    @close="emit('update:visible', false)"
+  >
+    <sop-step
+      v-if="visible"
+      :type="type"
+      :sop="sop"
+      @confirm="stepConfirm"
+    ></sop-step>
+  </my-drawer>
+</template>
+
+<script setup name="SopStepDialog">
+import { computed } from 'vue';
+import SopStep from './index.vue';
+
+const emit = defineEmits(['update:visible', 'confirm']);
+const props = defineProps({
+  visible: Boolean,
+  type: {
+    type: String,
+    default: 'fill',
+  },
+  sop: {
+    type: Object,
+    default() {
+      return {};
+    },
+  },
+});
+
+const title = computed(() => {
+  return props.type === 'fill' ? 'SOP填报' : 'SOP新增';
+});
+
+const stepConfirm = () => {
+  emit('update:visible', false);
+  emit('confirm');
+};
+</script>