zhangjie пре 1 година
родитељ
комит
4d9649a868

+ 5 - 0
src/api/project-quality.js

@@ -12,6 +12,11 @@ export const issuesFeedbackDestroyApi = (ids) =>
     params: { ids },
     paramsSerializer,
   });
+export const issuesFeedbackSaveApi = (data) =>
+  request({
+    url: '/api/admin/tb/quality/problem/apply/save',
+    data,
+  });
 
 // issues-query
 export const issuesQueryListApi = (data) =>

+ 30 - 8
src/api/sop.js

@@ -128,10 +128,17 @@ export const approvePlanChange = (params) =>
 
 // 流程相关 ------------------->
 // 获取流程部署信息接口
-export const getFlowDeployment = () =>
+export const flowDeploymentListApi = () =>
   request({
     url: '/api/admin/flow/deployment/data',
   });
+export const flowFormPropertiesApi = (params) =>
+  // flowDeploymentId or taskId or flowId
+  // crmNo
+  request({
+    url: '/api/admin/flow/form_properties/get',
+    params,
+  });
 
 // 流程详细信息接口
 export const getFlowDetail = (data) =>
@@ -146,17 +153,19 @@ export const sopListApi = (data) =>
     url: '/api/admin/sop/list',
     data,
   });
-// sop填报
-export const sopFlowViewApi = (params) =>
-  request({
-    url: '/api/admin/flow/view',
-    params,
-  });
+// sop申请
 export const sopApplyApi = (params) =>
   request({
     url: '/api/admin/sop/apply',
     data: params,
   });
+// sop填报-详情
+export const sopFlowViewApi = (params) =>
+  request({
+    url: '/api/admin/flow/view',
+    params,
+  });
+// sop填报-提交
 export const sopApproveApi = (params) =>
   request({
     url: '/api/admin/sop/approve',
@@ -174,13 +183,26 @@ export const sopBatchCancelApi = (flowIds) =>
     url: '/api/admin/sop/batch/cancel',
     data: { flowIds },
   });
+// sop 编辑-详情
 export const sopEditApi = (sopId) =>
   request({
     url: '/api/admin/sop/edit',
-    params: { sopId },
+    params: { id: sopId },
   });
+// sop 编辑-保存
 export const sopSaveApi = (data) =>
   request({
     url: '/api/admin/sop/save',
     data,
   });
+// 元数据
+export const metadataListApi = (params) =>
+  request({
+    url: '/api/admin/sop/metadata/list',
+    params,
+  });
+export const metadataSaveApi = (formWidgetMetadataList) =>
+  request({
+    url: '/api/admin/sop/metadata/save',
+    data: { formWidgetMetadataList },
+  });

+ 79 - 0
src/components/common/dynamic-table/edit-column-dialog.vue

@@ -0,0 +1,79 @@
+<template>
+  <my-dialog
+    :visible="visible"
+    :header="title"
+    :width="500"
+    attach="body"
+    :closeOnOverlayClick="false"
+    @close="emit('update:visible', false)"
+    @opened="dialogOpened"
+  >
+    <t-form
+      ref="formRef"
+      :data="formData"
+      :rules="rules"
+      :labelWidth="140"
+      colon
+    >
+      <t-form-item
+        v-for="item in columns"
+        :key="item.colKey"
+        :label="item.title"
+        :name="rules[item.colKey] ? item.colKey : undefined"
+      >
+        <SearchFormItem
+          :item="{ ...item.comp, prop: item.colKey, label: item.title }"
+          :params="formData"
+        />
+      </t-form-item>
+    </t-form>
+    <template #foot>
+      <t-button theme="default" @click="emit('update:visible', false)"
+        >取消</t-button
+      >
+      <t-button theme="primary" @click="save">保存</t-button>
+    </template>
+  </my-dialog>
+</template>
+<script setup name="EditColumnDialog">
+import { ref, computed } from 'vue';
+import { MessagePlugin } from 'tdesign-vue-next';
+import SearchFormItem from '../../global/search-form/components/search-form-item.vue';
+
+const emit = defineEmits(['update:visible', 'success']);
+const props = defineProps({
+  visible: Boolean,
+  curRow: Object,
+  columns: Array,
+});
+
+const formRef = ref(null);
+const formData = ref({});
+
+const title = computed(() => {
+  return (props.curRow?.key ? '编辑' : '新增') + '数据';
+});
+
+const rules = computed(() => {
+  let rules = {};
+  props.columns
+    .filter((item) => !!item.rules)
+    .forEach((item) => {
+      rules[item.colKey] = item.rules;
+    });
+  return rules;
+});
+
+const dialogOpened = () => {
+  formData.value = { ...props.curRow };
+};
+
+const save = async () => {
+  const valid = await formRef.value.validate();
+  if (valid !== true) return;
+
+  MessagePlugin.success('保存成功');
+  emit('update:visible', false);
+  emit('success', formData.value);
+};
+</script>

+ 92 - 96
src/components/common/dynamic-table/index.vue

@@ -2,130 +2,126 @@
   <div class="flow-table">
     <t-table
       ref="tableRef"
+      size="small"
       row-key="key"
-      :columns="columns"
+      :columns="tableColumns"
       :data="tableData"
-      :editable-row-keys="editableRowKeys"
       bordered
-      @row-edit="onRowEdit"
-      @row-validate="onRowValidate"
-      @validate="onValidate"
-      size="small"
     >
-      <template #key="{ row }">
-        <div class="flex items-center key-cell">
-          <Icon
-            name="delete"
-            class="delete-icon"
-            @click="deleteRow(row)"
-          ></Icon>
-          <span class="key-index">{{ row.key }}</span>
+      <template v-if="!readonly" #operate="{ row, rowIndex }">
+        <div class="table-operations">
+          <t-link theme="primary" hover="color" @click="handleEdit(row)">
+            修改
+          </t-link>
+          <t-link theme="primary" hover="color" @click="handleDelete(rowIndex)">
+            删除
+          </t-link>
         </div>
       </template>
     </t-table>
     <t-button
+      v-if="!readonly"
       theme="primary"
       class="m-t-15px"
-      @click="createOneRow"
-      v-if="!readonly"
+      @click="handleAdd"
     >
       <template #icon><Icon name="add"></Icon></template>
       添加
     </t-button>
+
+    <!-- EditColumnDialog -->
+    <edit-column-dialog
+      v-model:visible="showEditColumnDialog"
+      :curRow="curRow"
+      :columns="formColumns"
+      @success="columnConfirm"
+    ></edit-column-dialog>
   </div>
 </template>
 
 <script setup name="DynamicTable">
-import { computed, ref } from 'vue';
+import { computed, ref, watch } from 'vue';
 import { Icon } from 'tdesign-icons-vue-next';
-import { omit } from 'lodash';
-const { columns, readonly, data } = defineProps([
-  'columns',
-  'readonly',
-  'data',
-]);
+import { randomCode } from '@/utils/tool';
+import EditColumnDialog from './edit-column-dialog.vue';
+
+const props = defineProps({
+  columns: {
+    type: Array,
+  },
+  readonly: Boolean,
+  modelValue: { type: Array },
+});
+const emit = defineEmits(['update:modelValue', 'change']);
 
 const tableRef = ref();
-const tableData = ref(data || []);
-// const newColumns = computed(() => {
-//   return columns.map((item) => {
-//     if (item.edit) {
-//       item.edit.onEdited = (context) => {
-//         console.log(context);
-//         tableData.value.splice(context.rowIndex, 1, context.newRowData);
-//       };
-//     }
-//   });
-// });
-const resetKeys = () => {
-  tableData.value.forEach((item, index) => {
-    item.key = index + 1 + '';
+const tableData = ref([]);
+const showEditColumnDialog = ref(false);
+const curRow = ref(null);
+
+const tableColumns = computed(() => {
+  let ncolumns = [...props.columns];
+  const operateIndex = props.columns.findIndex(
+    (item) => item.colKey === 'operate'
+  );
+  if (operateIndex !== -1) ncolumns.splice(operateIndex, 1);
+  ncolumns.push({
+    title: '管理',
+    colKey: 'operate',
+    fixed: 'right',
+    width: 120,
   });
-};
+  return ncolumns;
+});
+const formColumns = computed(() => {
+  return props.columns.filter((item) => item.colKey !== 'operate');
+});
 
-const createOneRow = () => {
-  let rowData = columns.reduce((row, item) => {
-    row[item.colKey] = '';
-    return row;
-  }, {});
-  tableData.value.push({ ...rowData });
-  resetKeys();
-};
+watch(
+  () => props.modelValue,
+  (val, oldval) => {
+    if (val === oldval) return;
+    tableData.value = val.map((item) => {
+      return { ...item, key: randomCode() };
+    });
+  },
+  {
+    immediate: true,
+  }
+);
 
-if (!readonly) {
-  createOneRow();
-}
+const rowData = props.columns.reduce((row, item) => {
+  row[item.colKey] = '';
+  return row;
+}, {});
 
-const onRowEdit = (params) => {
-  const { row, editedRow } = params;
-  for (let k in editedRow) {
-    row[k] = editedRow[k];
-  }
+const handleAdd = () => {
+  curRow.value = { ...rowData };
+  showEditColumnDialog.value = true;
 };
-const onRowValidate = () => {};
-const onValidate = () => {};
-
-const editableRowKeys = computed(() => {
-  return tableData.value.map((item) => item.key);
-});
-const deleteRow = (row) => {
-  let index = tableData.value.findIndex((item) => item.key == row.key);
-  tableData.value.splice(index, 1);
-  resetKeys();
+const handleEdit = (row) => {
+  curRow.value = row;
+  showEditColumnDialog.value = true;
 };
-const validate = (cb) => {
-  tableRef.value.validateTableData().then((result) => {
-    if (!Object.keys(result.result).length) {
-      cb();
-    }
-  });
+const handleDelete = (index) => {
+  console.log(index);
+  tableData.value.splice(index, 1);
+  emitChange();
 };
-const exportTableData = () => {
-  return tableData.value.map((item) => ({
-    ...omit(item, 'key'),
-  }));
+
+const columnConfirm = (data) => {
+  if (data.key) {
+    const pos = tableData.value.findIndex((item) => item.key === data.key);
+    tableData.value.splice(pos, 1, data);
+  } else {
+    data.key = randomCode();
+    tableData.value.push(data);
+  }
+  emitChange();
 };
 
-defineExpose({ validate, exportTableData });
+const emitChange = () => {
+  emit('update:modelValue', tableData.value);
+  emit('change', tableData.value);
+};
 </script>
-
-<style lang="less" name="TABLE">
-.flow-table {
-  width: 100%;
-  margin-bottom: 25px;
-  .key-cell {
-    .key-index {
-      font-size: 14px;
-    }
-    .delete-icon {
-      font-size: 18px;
-      color: #777;
-      margin-right: 10px;
-      cursor: pointer;
-      &:hover {
-        color: var(--td-brand-color);
-      }
-    }
-  }
-}
-</style>

+ 114 - 0
src/components/common/select-metadata/index.vue

@@ -0,0 +1,114 @@
+<template>
+  <t-popup placement="bottom-left" trigger="click">
+    <t-button variant="outline">
+      <template #icon><svg-icon name="view" color="#262626" /></template>
+      显示字段
+    </t-button>
+    <template #content>
+      <div class="metadata-options">
+        <t-select
+          v-model="selected"
+          filterable
+          clearable
+          popupVisible
+          :min-collapsed-num="1"
+          :popup-props="{
+            attach: 'scrollParent',
+            overlayInnerStyle: {
+              width: '300px',
+            },
+          }"
+          v-bind="attrs"
+          @change="onChange"
+        >
+          <t-option
+            v-for="item in optionList"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </t-select>
+      </div>
+    </template>
+  </t-popup>
+</template>
+
+<script setup name="SelectMetadata">
+import { onMounted, ref, useAttrs, watch, computed } from 'vue';
+
+import { metadataListApi } from '@/api/sop';
+
+const emit = defineEmits(['update:modelValue', 'change']);
+const props = defineProps({
+  modelValue: { type: [Number, String, Array], default: '' },
+});
+const attrs = useAttrs();
+
+const isMultiple = computed(() => {
+  const multiple = attrs.multiple;
+  return multiple === '' || multiple;
+});
+
+let selected = ref('');
+let optionList = ref([]);
+const search = async () => {
+  optionList.value = [];
+  const res = await metadataListApi({ type: 'OFFICE_SOP_FLOW' }).catch(
+    () => {}
+  );
+  if (!res) return;
+
+  optionList.value = res.map((item) => {
+    return {
+      label: item.fieldTitle,
+      value: item.fieldName,
+    };
+  });
+};
+onMounted(() => {
+  search();
+});
+
+const onChange = () => {
+  const selectedData = isMultiple.value
+    ? optionList.value.filter(
+        (item) => selected.value && selected.value.includes(item.value)
+      )
+    : optionList.value.filter((item) => selected.value === item.value);
+  emit('update:modelValue', selected.value);
+  emit('change', isMultiple.value ? selectedData : selectedData[0]);
+};
+
+watch(
+  () => props.modelValue,
+  (val) => {
+    selected.value = val;
+  }
+);
+</script>
+
+<style lang="less" scoped>
+.metadata-options {
+  width: 300px;
+  padding: 5px 0;
+  border-radius: 3px;
+  background-color: #fff;
+  position: relative;
+
+  :deep(.t-popup__content) {
+    box-shadow: none !important;
+    margin: 0 !important;
+  }
+  :deep(.t-input__wrap + div) {
+    position: static !important;
+    margin-top: 8px;
+  }
+  :deep(.t-popup) {
+    position: static !important;
+    transform: none !important;
+  }
+  :deep(.t-select__list) {
+    padding: 0;
+  }
+}
+</style>

+ 11 - 3
src/layout/index.vue

@@ -20,7 +20,7 @@
       </div>
       <div class="app-menu-footer"></div>
     </t-aside>
-    <t-aside class="app-submenu" width="232px">
+    <t-aside v-if="appStore.showSubmenu" class="app-submenu" width="232px">
       <left-menu class="app-submenu-body"></left-menu>
       <div class="app-submenu-footer">
         <t-dropdown
@@ -70,7 +70,7 @@
 
 <script setup name="Layout" lang="jsx">
 import { ref, onMounted, watch } from 'vue';
-import { useUserStore, useWorkStore } from '@/store';
+import { useUserStore, useWorkStore, useAppStore } from '@/store';
 import { useRouter, useRoute } from 'vue-router';
 import LeftMenu from './left-menu.vue';
 import { moduleMap } from '@/router/asyncRoutes';
@@ -79,6 +79,7 @@ const router = useRouter();
 const route = useRoute();
 const userStore = useUserStore();
 const workStore = useWorkStore();
+const appStore = useAppStore();
 
 const moduleChange = (name) => {
   userStore.setCurPageModule(name);
@@ -115,9 +116,12 @@ watch(
 .app-layout {
   background-color: #f2f3f5;
   height: 100%;
+  flex-direction: row;
+  justify-content: space-between;
+  align-items: stretch;
 
   .layout-content {
-    flex: 1;
+    flex-grow: 2;
     overflow: auto;
     border-left: 1px solid #e5e5e5;
   }
@@ -126,6 +130,8 @@ watch(
     height: 100%;
     overflow-x: hidden;
     overflow-y: auto;
+    flex-grow: 0;
+    flex-shrink: 0;
 
     &-list {
       padding: 16px 8px 40px;
@@ -190,6 +196,8 @@ watch(
     height: 100%;
     overflow-x: hidden;
     overflow-y: auto;
+    flex-grow: 0;
+    flex-shrink: 0;
 
     &-body {
       padding-bottom: 68px;

+ 9 - 2
src/store/modules/app.js

@@ -1,13 +1,16 @@
 import { defineStore } from 'pinia';
-import { getFlowDeployment } from '@/api/sop';
+import { flowDeploymentListApi } from '@/api/sop';
 
 const useAppStore = defineStore('app', {
   state: () => ({
+    // old
     menuCollapse: false,
     menuWidth: 220,
     collapseWidth: 64,
     language: 'zh_CN',
     flowParams: null,
+    // new
+    showSubmenu: true,
   }),
   getters: {
     appCurrentSetting(state) {
@@ -25,7 +28,7 @@ const useAppStore = defineStore('app', {
       this.language = language;
     },
     async getFlowParams() {
-      let res = await getFlowDeployment();
+      let res = await flowDeploymentListApi();
       this.flowParams = res;
     },
     getFlowDetailByType(type) {
@@ -33,6 +36,10 @@ const useAppStore = defineStore('app', {
         ? this.flowParams.find((item) => item.type == type)
         : null;
     },
+    // new
+    toggleSubMenu() {
+      this.showSubmenu = !this.showSubmenu;
+    },
   },
 });
 

+ 14 - 10
src/style/global.less

@@ -239,16 +239,11 @@ body {
   }
 }
 .form-group-title {
+  margin: 20px 0 5px;
+  font-size: 16px;
   font-weight: bold;
-  // color: @brand-color;
   color: @dark-text-color;
-  border-bottom: 1px dashed #ddd;
-  padding-bottom: 5px;
-  margin-bottom: 15px;
-  font-size: 14px;
-  &.next-title {
-    margin-top: 20px;
-  }
+  line-height: 24px;
 }
 .box-justify {
   display: flex;
@@ -260,7 +255,10 @@ body {
   text-indent: 2em;
 }
 .red {
-  color: #e4393d;
+  color: @error-color;
+}
+.gray {
+  color: @light-text-color;
 }
 
 // pages ---------------->
@@ -399,6 +397,9 @@ body {
     .t-alert__icon {
       display: none;
     }
+    .t-alert__content {
+      margin-left: 0;
+    }
     .t-alert__description {
       color: @error-color;
     }
@@ -457,6 +458,9 @@ body {
       overflow-y: auto;
       border-right: 1px solid @light-border-color;
     }
+    .t-tabs__content {
+      flex-grow: 2;
+    }
     .t-tabs__nav-container::after {
       display: none;
     }
@@ -471,7 +475,7 @@ body {
     .sop-step-body {
       flex-grow: 2;
       overflow: auto;
-      padding: 0 15px 15px;
+      padding: 15px;
     }
     .sop-step-footer {
       flex-grow: 0;

+ 10 - 5
src/style/tdesign-reset.less

@@ -65,11 +65,6 @@
   .svg-icon {
     margin-right: 8px;
   }
-  &.t-is-disabled {
-    .svg-icon use {
-      fill: #00000042 !important;
-    }
-  }
 
   &.t-button--theme-default:hover {
     .svg-icon use {
@@ -77,3 +72,13 @@
     }
   }
 }
+.t-button--theme-default.t-is-disabled {
+  .svg-icon use {
+    fill: #00000042 !important;
+  }
+}
+.t-button--theme-primary.t-is-disabled {
+  .svg-icon use {
+    fill: #fff !important;
+  }
+}

+ 4 - 0
src/utils/tool.js

@@ -404,3 +404,7 @@ export function randomCode(len = 16) {
 
   return stepNums.join('');
 }
+
+export function deepCopy(obj) {
+  return JSON.parse(JSON.stringify(obj));
+}

+ 1 - 12
src/views/sop/components/my-form-item.vue

@@ -10,18 +10,8 @@
       :class="{ 'm-t-20px': isBigTitle }"
       v-if="isBigTitle"
     >
-      <!-- <span class="require-icon" :class="{ hide: !config.required }">*</span> -->
       <p>{{ config.title }}</p>
     </div>
-    <!-- <div class="sub-title" v-if="config.subTitle">
-      <p
-        v-for="(item, index) in config.subTitle"
-        :key="index"
-        :style="{ color: item.color }"
-      >
-        {{ item.content }}
-      </p>
-    </div> -->
     <RenderTest></RenderTest>
   </t-form-item>
 </template>
@@ -45,7 +35,6 @@ import NUMBER from './NUMBER.vue';
 const { config, labelWidth } = defineProps(['config', 'labelWidth']);
 const emit = defineEmits(['change']);
 const onChange = (obj) => {
-  console.log('obj', obj);
   emit('change', obj);
 };
 const bigTitles = ref(['FORM_GROUP_TITLE', 'ONLY_TITLE']);
@@ -97,7 +86,7 @@ const RenderTest = defineComponent({
         return <NUMBER config={config} onChange={onChange}></NUMBER>;
       case 'SIGN':
         return <SIGN></SIGN>;
-      case 'ONLE_TITLE':
+      case 'ONLY_TITLE':
         return <div></div>;
       case '':
         return <div></div>;

+ 85 - 30
src/views/sop/sop-manage/office-sop/index.vue

@@ -9,10 +9,14 @@
         <template #icon><svg-icon name="delete" color="#fff" /></template>
         作废
       </t-button>
-      <t-button variant="outline" @click="handleAdd">
+      <!-- <t-button variant="outline" @click="handleAdd">
         <template #icon><svg-icon name="view" color="#262626" /></template>
         显示字段</t-button
-      >
+      > -->
+      <select-metadata
+        v-model="params.formWidgetMetadataList"
+        multiple
+      ></select-metadata>
       <t-button variant="outline" @click="handleAdd">
         <template #icon><svg-icon name="refresh" color="#262626" /></template>
         刷新</t-button
@@ -21,11 +25,15 @@
         <template #icon><svg-icon name="sort" color="#262626" /></template>
         排序</t-button
       >
-      <t-button variant="outline" @click="handleAdd">
+      <t-button
+        v-if="appStore.showSubmenu"
+        variant="outline"
+        @click="toggleSubMenuHandle"
+      >
         <template #icon><svg-icon name="enlarge" color="#262626" /></template>
         放大</t-button
       >
-      <t-button variant="outline" @click="handleAdd">
+      <t-button v-else variant="outline" @click="toggleSubMenuHandle">
         <template #icon><svg-icon name="shrink" color="#262626" /></template>
         缩小</t-button
       >
@@ -72,33 +80,52 @@
         </template>
         <template #operate="{ row }">
           <div class="table-operations">
-            <t-link theme="primary" hover="color" @click="toCurSopFlow(row)">
+            <t-link
+              theme="primary"
+              hover="color"
+              @click="editSopFlowHandle(row, 'fill')"
+            >
               填报
             </t-link>
-            <t-link theme="primary" hover="color" @click="createSopFlow(row)">
+            <t-link
+              theme="primary"
+              hover="color"
+              @click="editSopFlowHandle(row, 'add')"
+            >
               新增SOP
             </t-link>
             <t-link
               theme="primary"
               hover="color"
-              @click="toCreateViolation(row)"
+              @click="createViolationHandle(row)"
             >
               新增违规
             </t-link>
-            <t-link theme="primary" hover="color" @click="qualityIssue(row)">
+            <t-link
+              theme="primary"
+              hover="color"
+              @click="createQualityIssueHandle(row)"
+            >
               上报质量问题
             </t-link>
-            <t-link theme="primary" hover="color" @click="planChange(row)">
+            <t-link
+              theme="primary"
+              hover="color"
+              @click="planChangeHandle(row)"
+            >
               计划变更报备
             </t-link>
+            <t-link
+              theme="primary"
+              hover="color"
+              @click="editSopFlowHandle(row, 'edit')"
+            >
+              编辑
+            </t-link>
           </div>
         </template>
       </t-table>
     </div>
-    <!-- <PlanChangeDialog
-      v-model:visible="showPlanChangeDialog"
-      :curRow="curRow"
-    ></PlanChangeDialog> -->
 
     <!-- SopStepDialog -->
     <sop-step-dialog
@@ -107,6 +134,20 @@
       :type="curSopType"
       @confirm="fetchData"
     ></sop-step-dialog>
+    <!-- QualityIssueDialog -->
+    <quality-issue-dialog
+      v-model:visible="showQualityIssueDialog"
+      :sop="curSopData"
+      type="new"
+      @confirm="fetchData"
+    ></quality-issue-dialog>
+    <!-- PlanChangeDialog -->
+    <PlanChangeDialog
+      v-model:visible="showPlanChangeDialog"
+      :sop="curSopData"
+      type="new"
+      @confirm="fetchData"
+    ></PlanChangeDialog>
   </div>
 </template>
 
@@ -117,8 +158,12 @@ import { useRouter } from 'vue-router';
 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);
+import QualityIssueDialog from '../quality-issue/quality-issue-dialog.vue';
+import PlanChangeDialog from '../plan-change/plan-change-dialog.vue';
+import { useAppStore } from '@/store';
+
+const appStore = useAppStore();
+
 const selectedRowKeys = ref([]);
 const selectChange = (value, { selectedRowData }) => {
   selectedRowKeys.value = value;
@@ -154,7 +199,7 @@ const columns = [
     title: '管理',
     colKey: 'operate',
     fixed: 'right',
-    width: 420,
+    width: 450,
   },
 ];
 const fields = ref([
@@ -182,6 +227,7 @@ const fields = ref([
 ]);
 const params = reactive({
   serviceId: '',
+  formWidgetMetadataList: [],
 });
 const transParams = computed(() => {
   return { ...params, type: 'OFFICE_SOP_FLOW' };
@@ -196,6 +242,9 @@ const {
 } = useFetchTable(sopListApi, { params: transParams });
 
 const handleAdd = () => {};
+const toggleSubMenuHandle = () => {
+  appStore.toggleSubMenu();
+};
 
 const handleBatchCancel = () => {
   if (!selectedRowKeys.value.length) {
@@ -220,30 +269,36 @@ const handleBatchCancel = () => {
   });
 };
 
-const toCreateViolation = (row) => {
+const createViolationHandle = (row) => {
   console.log(row);
 };
 
 const showSopStepDialog = ref(false);
 const curSopData = ref({});
 const curSopType = ref('');
-const createSopFlow = (row) => {
-  curSopType.value = 'add';
-  curSopData.value = row;
-  showSopStepDialog.value = true;
-};
-const toCurSopFlow = (row) => {
-  curSopType.value = 'fill';
+const editSopFlowHandle = (row, type = 'fill') => {
+  curSopType.value = type;
   curSopData.value = row;
   showSopStepDialog.value = true;
 };
 
-const planChange = (row) => {
-  // curRow.value = row;
-  // showPlanChangeDialog.value = true;
-  router.push({ name: 'PlanChange', query: { new: true } });
+const showPlanChangeDialog = ref(false);
+const planChangeHandle = (row) => {
+  curSopData.value = {
+    ...row,
+    flowDeploymentId: appStore.getFlowDetailByType('PROJECT_EXCHANGE_FLOW')
+      ?.flowDeploymentId,
+  };
+  showPlanChangeDialog.value = true;
 };
-const qualityIssue = (row) => {
-  router.push({ name: 'QualityIssue' });
+
+const showQualityIssueDialog = ref(false);
+const createQualityIssueHandle = (row) => {
+  curSopData.value = {
+    ...row,
+    flowDeploymentId: appStore.getFlowDetailByType('QUALITY_PROBLEM_FLOW')
+      ?.flowDeploymentId,
+  };
+  showQualityIssueDialog.value = true;
 };
 </script>

+ 225 - 168
src/views/sop/sop-manage/plan-change/index.vue

@@ -1,130 +1,152 @@
 <template>
-  <div class="plan-change">
-    <t-form ref="formRef" :data="formData" :labelWidth="180" :rules="rules">
-      <t-row :gutter="[0, 20]">
-        <t-col :span="12">
-          <div class="form-group-title" style="margin-bottom: 0">
-            项目派单信息(SOP流水单号:20230601001)
-          </div>
-        </t-col>
-        <t-col :span="6">
-          <t-form-item label="项目单号">1111 </t-form-item>
-        </t-col>
-        <t-col :span="6">
-          <t-form-item label="项目名称"> 1111 </t-form-item>
-        </t-col>
-        <t-col :span="6">
-          <t-form-item label="派单时间"> 1111 </t-form-item>
-        </t-col>
-        <t-col :span="6">
-          <t-form-item label="客户经理"> 1111 </t-form-item>
-        </t-col>
-        <t-col :span="6">
-          <t-form-item label="客户类型"> 1111 </t-form-item>
-        </t-col>
-        <t-col :span="6">
-          <t-form-item label="客户名称"> 1111 </t-form-item>
-        </t-col>
-        <t-col :span="6">
-          <t-form-item label="考试开始时间"> 1111 </t-form-item>
-        </t-col>
-        <t-col :span="6">
-          <t-form-item label="考试结束时间"> 1111 </t-form-item>
-        </t-col>
-        <t-col :span="6">
-          <t-form-item label="实施产品"> 1111 </t-form-item>
-        </t-col>
-        <t-col :span="6">
-          <t-form-item label="服务单元"> 1111 </t-form-item>
-        </t-col>
-        <t-col :span="12">
-          <div class="form-group-title"> SOP项目计划变更说明 </div>
-          <div class="sub-title">
-            <p :style="{ color: 'gray' }">
-              SOP项目计划需要变更时,可手动发起一个或多个申请,质控专员审核后,在SOP流程中进行计划变更调整,完成变更后,申请流程结束并通知到发起申请人
-            </p>
-          </div>
-        </t-col>
-        <t-col :span="6">
-          <t-form-item label="报备申请人"> 1111 </t-form-item>
-        </t-col>
-        <t-col :span="6">
-          <t-form-item label="报备申请时间"> 1111 </t-form-item>
-        </t-col>
+  <div class="plan-change sop-step">
+    <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-collapse class="sop-step-mid" defaultExpandAll>
+      <t-collapse-panel disabled>
+        <template #expandIcon></template>
+        <template #header> SOP项目计划变更说明 </template>
+        <p class="red">
+          SOP项目计划需要变更时,可手动发起一个或多个申请,质控专员审核后,在SOP流程中进行计划变更调整,完成变更后,申请流程结束并通知到发起申请人
+        </p>
+      </t-collapse-panel>
+    </t-collapse>
 
-        <t-col :span="12">
-          <t-form-item label="变更类型">
-            <div>
-              <div class="sub-title">
-                <p :style="{ color: 'gray' }">
-                  1.关键信息及计划变更:项目关键信息里填写的项目关键信息或时间计划内容调整;
-                </p>
-                <p :style="{ color: 'gray' }">
-                  2.项目取消:若项目取消,则只需填写变更原因后提交。
-                </p>
-              </div>
-              <t-radio-group v-model="formData.type">
-                <t-radio
-                  :value="item.value"
-                  :key="item.value"
-                  v-for="item in dictToOptionList(PLAN_CHANGE_TYPE)"
-                  >{{ item.label }}</t-radio
-                >
-              </t-radio-group>
-            </div>
-          </t-form-item>
-        </t-col>
-        <t-col :span="12">
-          <t-form-item label="变更原因" name="reason">
-            <t-input
-              :disabled="readonly"
-              v-model="formData.reason"
-              placeholder="50字以内"
-              :maxlength="50"
-            ></t-input>
-          </t-form-item>
-        </t-col>
-        <t-col :span="12">
-          <t-form-item label="项目信息及计划变更明细">
-            <dynamic-table
-              :columns="columns"
-              ref="dTable"
-              :readonly="readonly"
-            ></dynamic-table>
-          </t-form-item>
-        </t-col>
-        <template v-if="!route.new">
+    <div class="sop-step-list" style="flex-direction: column">
+      <t-form
+        class="sop-step-body"
+        ref="formRef"
+        :data="formData"
+        :labelWidth="180"
+        :rules="rules"
+        colon
+      >
+        <t-row :gutter="[0, 20]">
+          <template v-if="!IS_NEW_MODE">
+            <t-col :span="4">
+              <t-form-item label="报备申请人"> 1111 </t-form-item>
+            </t-col>
+            <t-col :span="4">
+              <t-form-item label="报备申请时间"> 1111 </t-form-item>
+            </t-col>
+          </template>
           <t-col :span="12">
-            <div class="form-group-title next"> 变更处理结果 </div>
+            <t-form-item label="变更类型" requiredMark>
+              <div style="padding-top: 3px">
+                <t-radio-group v-model="formData.type">
+                  <t-radio
+                    :value="item.value"
+                    :key="item.value"
+                    v-for="item in dictToOptionList(PLAN_CHANGE_TYPE)"
+                    >{{ item.label }}</t-radio
+                  >
+                </t-radio-group>
+                <div class="sub-title gray">
+                  <p>
+                    1.关键信息及计划变更:项目关键信息里填写的项目关键信息或时间计划内容调整;
+                  </p>
+                  <p> 2.项目取消:若项目取消,则只需填写变更原因后提交。 </p>
+                </div>
+              </div>
+            </t-form-item>
           </t-col>
           <t-col :span="12">
-            <t-form-item label="处理结果">
-              <t-radio-group v-model="formData.projectExchangeApprove">
-                <t-radio value="FINISH">已完成</t-radio>
-                <t-radio value="NOT_UPDATE"
-                  >经沟通,取消变更(原因请填写变更备注)</t-radio
-                >
-                <t-radio value="PARTIAL_UPDATE"
-                  >经沟通,部分变更(详情请填写变更备注)</t-radio
-                >
-              </t-radio-group>
+            <t-form-item label="变更原因" name="reason">
+              <t-input
+                :disabled="readonly"
+                v-model="formData.reason"
+                placeholder="50字以内"
+                :maxlength="50"
+              ></t-input>
             </t-form-item>
           </t-col>
-
           <t-col :span="12">
-            <t-form-item label="变更备注" name="remark">
-              <t-textarea v-model="formData.remark"></t-textarea>
+            <t-form-item label="项目信息及计划变更明细">
+              <dynamic-table
+                :columns="columns"
+                ref="dTable"
+                :readonly="readonly"
+              ></dynamic-table>
             </t-form-item>
           </t-col>
-        </template>
-      </t-row>
-      <s-button
-        class="m-t-30px"
-        @cancel="router.back()"
-        confirm-text="保存"
-        @confirm="save"
-      ></s-button>
-    </t-form>
+          <template v-if="!IS_NEW_MODE">
+            <t-col :span="12">
+              <div class="form-group-title next"> 变更处理结果 </div>
+            </t-col>
+            <t-col :span="12">
+              <t-form-item label="处理结果">
+                <t-radio-group v-model="formData.projectExchangeApprove">
+                  <t-radio value="FINISH">已完成</t-radio>
+                  <t-radio value="NOT_UPDATE"
+                    >经沟通,取消变更(原因请填写变更备注)</t-radio
+                  >
+                  <t-radio value="PARTIAL_UPDATE"
+                    >经沟通,部分变更(详情请填写变更备注)</t-radio
+                  >
+                </t-radio-group>
+              </t-form-item>
+            </t-col>
+            <t-col :span="12">
+              <t-form-item label="变更备注" name="remark">
+                <t-textarea v-model="formData.remark"></t-textarea>
+              </t-form-item>
+            </t-col>
+          </template>
+        </t-row>
+      </t-form>
+      <t-space class="sop-step-footer">
+        <t-button theme="primary" @click="submitHandle">提交</t-button>
+        <t-button theme="default" @click="cancelHandle">取消</t-button>
+      </t-space>
+    </div>
   </div>
 </template>
 <script setup name="PlanChange">
@@ -137,46 +159,71 @@ import { PLAN_CHANGE_TYPE } from '@/config/constants';
 import { createPlanChange, approvePlanChange } from '@/api/sop';
 import { useAppStore } from '@/store';
 import { omit } from 'lodash';
-const appStore = useAppStore();
+
 const dTable = ref();
-const router = useRouter();
 const route = useRoute();
+
+const props = defineProps({
+  sop: {
+    type: Object,
+    default() {
+      return {};
+    },
+  },
+  type: {
+    type: String,
+    default: 'new',
+  },
+});
+const emit = defineEmits(['confirm', 'cancel']);
+
+const IS_NEW_MODE = computed(() => {
+  return props.type === 'new';
+});
 const readonly = computed(() => {
-  return !route.query.new;
+  return !IS_NEW_MODE.value;
 });
+
 const columns = computed(() => [
   {
     colKey: 'before',
     title: '变更字段(全称)',
-    edit: {
-      component: Input,
-      props: {
+    comp: {
+      type: 'text',
+      attrs: {
         clearable: true,
         disabled: readonly.value,
       },
-      validateTrigger: 'change',
-      rules: [{ required: true, message: '不能为空' }],
     },
+    rules: [
+      { required: true, message: '不能为空', type: 'error', trigger: 'change' },
+    ],
   },
   {
     colKey: 'after',
     title: '变更后内容',
-    edit: {
-      component: Input,
-      props: {
+    comp: {
+      type: 'text',
+      attrs: {
         clearable: true,
         disabled: readonly.value,
       },
-      validateTrigger: 'change',
-      rules: [{ required: true, message: '不能为空' }],
     },
+    rules: [
+      {
+        required: true,
+        message: '不能为空',
+        type: 'error',
+        trigger: 'change',
+      },
+    ],
   },
   {
     colKey: 'remark',
     title: '备注',
-    edit: {
-      component: Input,
-      props: {
+    comp: {
+      type: 'text',
+      attrs: {
         clearable: true,
         disabled: readonly.value,
       },
@@ -189,10 +236,23 @@ const formData = reactive({
   sopNo: '',
   crmNo: '',
   type: 'PLAN',
+  flowDeploymentId: '',
   reason: '',
+  contentJson: '',
   projectExchangeApprove: 'FINISH',
   remark: '',
 });
+
+const initData = () => {
+  if (IS_NEW_MODE) {
+    formData.serviceId = props.sop.serviceId;
+    formData.sopNo = props.sop.sopNo;
+    formData.crmNo = props.sop.crmNo;
+    formData.flowDeploymentId = props.sop.flowDeploymentId;
+  }
+};
+initData();
+
 const rules = computed(() => {
   return {
     reason: [
@@ -221,40 +281,37 @@ const rules = computed(() => {
     ],
   };
 });
-const save = () => {
-  formRef.value.validate().then(async (result) => {
-    if (result === true) {
-      if (!readonly) {
-        dTable.value?.validate(() => {
-          let tableData = dTable.value.exportTableData();
-          createPlanChange({
-            ...omit(formData, ['projectExchangeApprove', 'remark']),
-            contentJson: JSON.stringify(tableData),
-            flowApprove: 'START',
-            flowDeploymentId: appStore.getFlowDetailByType(
-              'PROJECT_EXCHANGE_FLOW'
-            ).flowDeploymentId,
-          });
-        });
-      } else {
-        //流程审批
-        approvePlanChange({
-          flowApprove: 'PASS',
-          projectExchangeApprove: formData.projectExchangeApprove,
-          remark: formData.remark,
-          taskId: route.query.taskId,
-        }).then(() => {
-          MessagePlugin.success('操作成功');
-          router.back();
-        });
-      }
-    }
-  });
+
+const submitHandle = async () => {
+  const valid = await formRef.value.validate();
+  if (valid !== true) return;
+
+  if (IS_NEW_MODE.value) {
+    dTable.value?.validate(() => {});
+
+    let tableData = dTable.value.exportTableData();
+    const res = await createPlanChange({
+      ...omit(formData, ['projectExchangeApprove', 'remark']),
+      contentJson: JSON.stringify(tableData),
+      flowApprove: 'START',
+    }).catch(() => {});
+    if (!res) return;
+  } else {
+    // 流程审批
+    const res = await approvePlanChange({
+      flowApprove: 'PASS',
+      projectExchangeApprove: formData.projectExchangeApprove,
+      remark: formData.remark,
+      taskId: route.query.taskId,
+    }).catch(() => {});
+    if (!res) return;
+  }
+
+  MessagePlugin.success('保存成功');
+  emit('confirm');
+};
+
+const cancelHandle = () => {
+  emit('cancel');
 };
 </script>
-<style lang="less" scoped>
-.plan-change {
-  background-color: #fff;
-  padding: 16px;
-}
-</style>

+ 45 - 0
src/views/sop/sop-manage/plan-change/plan-change-dialog.vue

@@ -0,0 +1,45 @@
+<template>
+  <my-drawer
+    class="sop-dialog"
+    :visible="visible"
+    header="计划变更报备"
+    size="80%"
+    attach="body"
+    :closeOnOverlayClick="false"
+    :close-btn="true"
+    :footer="false"
+    @close="emit('update:visible', false)"
+  >
+    <plan-change
+      v-if="visible"
+      :sop="sop"
+      :type="type"
+      @confirm="stepConfirm"
+      @cancel="emit('update:visible', false)"
+    ></plan-change>
+  </my-drawer>
+</template>
+
+<script setup name="PlanChangeDialog">
+import PlanChange from './index.vue';
+
+const emit = defineEmits(['update:visible', 'confirm', 'concel']);
+const props = defineProps({
+  visible: Boolean,
+  sop: {
+    type: Object,
+    default() {
+      return {};
+    },
+  },
+  type: {
+    type: String,
+    default: 'new',
+  },
+});
+
+const stepConfirm = () => {
+  emit('update:visible', false);
+  emit('confirm');
+};
+</script>

+ 265 - 58
src/views/sop/sop-manage/quality-issue/index.vue

@@ -1,66 +1,273 @@
 <template>
-  <div class="quality-issue">
-    <div class="page-wrap">
-      <t-form ref="formRef" :labelWidth="100">
-        <t-row :gutter="[0, 10]">
-          <t-col :span="12">
-            <div class="form-group-title" style="margin-bottom: 0">
-              SOP信息
-            </div>
-          </t-col>
-          <t-col :span="3">
-            <t-form-item label="服务单元"></t-form-item>
-          </t-col>
-          <t-col :span="3">
-            <t-form-item label="SOP流水号"></t-form-item>
-          </t-col>
-          <t-col :span="3">
-            <t-form-item label="客户类型"></t-form-item>
-          </t-col>
-          <t-col :span="3">
-            <t-form-item label="客户名称"></t-form-item>
-          </t-col>
-          <t-col :span="3">
-            <t-form-item label="项目单号"></t-form-item>
-          </t-col>
-          <t-col :span="3">
-            <t-form-item label="实施产品"></t-form-item>
-          </t-col>
-          <t-col :span="3">
-            <t-form-item label="项目名称"></t-form-item>
-          </t-col>
-        </t-row>
-      </t-form>
-      <t-tabs v-model="curStep" placement="left" class="m-t-20px">
-        <t-tab-panel
-          v-for="item in tabs"
-          :key="item.value"
-          :value="item.value"
-          :label="item.label"
+  <div class="quality-issue sop-step">
+    <t-collapse class="sop-step-mid" defaultExpandAll>
+      <t-collapse-panel disabled>
+        <template #expandIcon></template>
+        <template #header> SOP信息 </template>
+        <t-form colon label-width="72px">
+          <t-row :gutter="[0, 4]">
+            <t-col :span="3">
+              <t-form-item label="服务单元">{{ sop.serviceName }}</t-form-item>
+            </t-col>
+            <t-col :span="3">
+              <t-form-item label="SOP流水号">{{ sop.sopNo }}</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="项目单号">{{ sop.crmNo }}</t-form-item>
+            </t-col>
+            <t-col :span="3">
+              <t-form-item label="实施产品">{{ sop.productName }}</t-form-item>
+            </t-col>
+            <t-col :span="6">
+              <t-form-item label="项目名称">{{ sop.crmName }}</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"
+      @change="stepChange"
+    >
+      <t-tab-panel
+        v-for="item in tabs"
+        :key="item.taskKey"
+        :value="item.value"
+        :label="item.label"
+        :disabled="item.disabled"
+      >
+        <t-form
+          ref="form"
+          class="sop-step-body"
+          colon
+          :label-width="labelWidth"
+          :rules="rules"
+          :data="formData"
         >
-          <Step1 v-if="curStep === 'step1'"></Step1>
-          <Step2 v-else-if="curStep === 'step2'"></Step2>
-          <Step3 v-else-if="curStep === 'step3'"></Step3>
-          <Step4 v-else-if="curStep === 'step4'"></Step4>
-        </t-tab-panel>
-      </t-tabs>
-    </div>
+          <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
+                v-if="config.visable"
+                :config="config"
+                :labelWidth="labelWidth"
+                @change="itemValueChange"
+              ></MyFormItem>
+            </t-col>
+          </t-row>
+        </t-form>
+        <t-space class="sop-step-footer">
+          <t-button
+            v-if="IS_NEW_MODE"
+            theme="primary"
+            @click="submitHandle('START')"
+            >提交</t-button
+          >
+
+          <t-button
+            v-if="IS_NEW_MODE"
+            theme="default"
+            @click="submitHandle('DRAFT')"
+            >保存草稿</t-button
+          >
+        </t-space>
+      </t-tab-panel>
+    </t-tabs>
   </div>
 </template>
 
 <script setup name="QualityIssue">
-import { ref } from 'vue';
-import Step1 from './step1.vue';
-import Step2 from './step2.vue';
-import Step3 from './step3.vue';
-import Step4 from './step4.vue';
-const tabs = ref([
-  { value: 'step1', label: '质量问题上报' },
-  { value: 'step2', label: '质量问题初审' },
-  { value: 'step3', label: '甲方复核' },
-  { value: 'step4', label: '乙方复核' },
+import { ref, computed, watch } from 'vue';
+import { issuesFeedbackSaveApi } from '@/api/project-quality';
+import { flowFormPropertiesApi } from '@/api/sop';
+import { MessagePlugin } from 'tdesign-vue-next';
+
+import MyFormItem from '../../components/my-form-item.vue';
+
+const props = defineProps({
+  sop: {
+    type: Object,
+    default() {
+      return {};
+    },
+  },
+  type: {
+    type: String,
+    default: 'new',
+  },
+});
+const emit = defineEmits(['confirm']);
+
+const IS_NEW_MODE = computed(() => {
+  return props.type === 'new';
+});
+
+const needValueCodes = [
+  'NUMBER', //新增
+  'TEXT',
+  'DATE',
+  'SELECT',
+  'CHECKBOX',
+  'TEXTAREA',
+  'TABLE',
+  'RADIO',
+  'RADIO_WITH_INPUT',
+  'SIGN',
+  'DEVICE_OUT_TABLE',
+  'DEVICE_IN_TABLE',
+  'FILE',
+];
+const rules = ref({});
+const formData = ref({});
+
+const loading = ref(false);
+const labelWidth = ref(120);
+const fullWidthCodes = ref([
+  'TABLE',
+  'FORM_GROUP_TITLE',
+  'TEXTAREA',
+  'ONLY_TITLE',
+  'DEVICE_OUT_TABLE',
+  'DEVICE_IN_TABLE',
 ]);
-const curStep = ref('step3');
-</script>
+const form = ref(null);
+
+const allSteps = ref([]);
+const tabs = ref([]);
+const curStep = ref('');
+
+const initNew = async () => {
+  loading.value = true;
+  const res = await flowFormPropertiesApi({
+    flowDeploymentId: props.sop.flowDeploymentId,
+  });
+  loading.value = false;
 
-<style></style>
+  allSteps.value = [res.formProperties];
+  tabs.value = allSteps.value.map((item) => {
+    return {
+      value: item.taskName,
+      label: item.taskName,
+      disabled: false,
+    };
+  });
+  curStep.value = tabs.value.slice(-1)[0].value;
+};
+const init = () => {
+  if (IS_NEW_MODE.value) {
+    // 发起流程
+    initNew();
+  } else {
+    // initNew();
+  }
+};
+init();
+
+const curFormConfig = computed(() => {
+  const formProperty =
+    allSteps.value.find((item) => item.taskName === curStep.value)
+      ?.formProperty || [];
+  formProperty.forEach((item) => {
+    if (IS_NEW_MODE.value) {
+      // item.value = item.value ? JSON.parse(item.value).value : null;
+      item.value = null;
+    }
+  });
+  return formProperty;
+});
+watch(curFormConfig, (val) => {
+  formData.value = val.reduce((obj, item) => {
+    if (needValueCodes.includes(item.code)) {
+      obj[item.formName] = '';
+    }
+    return obj;
+  }, {});
+  rules.value = val.reduce((obj, item) => {
+    let ruleItem =
+      item.required && needValueCodes.includes(item.code)
+        ? {
+            required: true,
+            message: `${item.title}不能为空`,
+            type: 'error',
+            trigger: 'change',
+          }
+        : null;
+    if (ruleItem) {
+      obj[item.formName] = [ruleItem];
+    }
+    return obj;
+  }, {});
+});
+
+const stepChange = () => {
+  // if (IS_EDIT_MODE.value) {
+  //   allFormData.value = { ...allFormData.value, ...formData.value };
+  // }
+};
+
+const itemValueChange = ({ prop, value }) => {
+  console.log(prop, ':', value);
+  formData.value[prop] = value;
+};
+
+const getFormData = () => {
+  let data = {};
+  if (IS_NEW_MODE.value) {
+    const curStepData = allSteps.value.find(
+      (item) => item.taskName === curStep.value
+    );
+    data = { ...curStepData };
+    data.formProperty = data.formProperty.map((item) => {
+      return {
+        ...item,
+        value: JSON.stringify({ value: formData.value[item.formName] || null }),
+      };
+    });
+  }
+
+  return JSON.stringify(data);
+};
+
+// 填报-提交
+const submitHandle = async (flowApprove = 'START') => {
+  if (flowApprove === 'START') {
+    const valid = await form.value[0].validate();
+    if (valid !== true) return;
+  }
+
+  if (IS_NEW_MODE.value) {
+    const res = await issuesFeedbackSaveApi({
+      serviceId: props.sop.serviceId,
+      crmNo: props.sop.crmNo,
+      flowDeploymentId: props.sop.flowDeploymentId,
+      sopNo: props.sop.sopNo,
+      formProperties: getFormData(),
+      flowApprove,
+    }).catch(() => {});
+    if (!res) return;
+  }
+
+  MessagePlugin.success('保存成功');
+  emit('confirm');
+};
+</script>

+ 44 - 0
src/views/sop/sop-manage/quality-issue/quality-issue-dialog.vue

@@ -0,0 +1,44 @@
+<template>
+  <my-drawer
+    class="sop-dialog"
+    :visible="visible"
+    header="上报质量问题"
+    size="80%"
+    attach="body"
+    :closeOnOverlayClick="false"
+    :close-btn="true"
+    :footer="false"
+    @close="emit('update:visible', false)"
+  >
+    <quality-issue
+      v-if="visible"
+      :sop="sop"
+      :type="type"
+      @confirm="stepConfirm"
+    ></quality-issue>
+  </my-drawer>
+</template>
+
+<script setup name="QualityIssueDialog">
+import QualityIssue from './index.vue';
+
+const emit = defineEmits(['update:visible', 'confirm']);
+const props = defineProps({
+  visible: Boolean,
+  sop: {
+    type: Object,
+    default() {
+      return {};
+    },
+  },
+  type: {
+    type: String,
+    default: 'new',
+  },
+});
+
+const stepConfirm = () => {
+  emit('update:visible', false);
+  emit('confirm');
+};
+</script>

+ 41 - 51
src/views/sop/sop-manage/quality-issue/step1.vue

@@ -1,56 +1,46 @@
 <template>
-  <div class="step1 p-20px">
-    <t-form ref="formRef" :data="formData" :labelWidth="110">
-      <t-row :gutter="[0, 20]">
-        <t-col :span="12">
-          <div class="form-group-title" style="margin-bottom: 0">
-            质量问题上报(质量问题编号:1234567)
+  <t-form ref="formRef" :data="formData" :labelWidth="110" colon>
+    <t-row :gutter="[0, 20]">
+      <t-col :span="12">
+        <div class="form-group-title" style="margin-bottom: 0">
+          质量问题上报(质量问题编号:1234567)
+        </div>
+      </t-col>
+      <t-col :span="12">
+        <t-form-item label="填写说明">
+          <div class="p-t-5px">
+            <p class="red"
+              >1.质量问题定义:所指客户在项目中的事故或是不满意体验的反馈,并希望通过向我们反馈得以解决的问题。不包含的范围:</p
+            >
+            <p class="red text-indent">1)客户反馈但并不希望我们协助解决的;</p>
+            <p class="red text-indent">2)不属于公司服务范围或能力内的。</p>
+            <p class="red">2.多个质量问题,请逐条提交。</p>
           </div>
-        </t-col>
-        <t-col :span="12">
-          <t-form-item label="填写说明">
-            <div class="p-t-5px">
-              <p class="red"
-                >1.质量问题定义:所指客户在项目中的事故或是不满意体验的反馈,并希望通过向我们反馈得以解决的问题。不包含的范围:</p
-              >
-              <p class="red text-indent"
-                >1)客户反馈但并不希望我们协助解决的;</p
-              >
-              <p class="red text-indent">2)不属于公司服务范围或能力内的。</p>
-              <p class="red">2.多个质量问题,请逐条提交。</p>
-            </div>
-          </t-form-item>
-        </t-col>
-        <t-col :span="6">
-          <t-form-item label="提交人"> 张三 </t-form-item>
-        </t-col>
-        <t-col :span="6">
-          <t-form-item label="提交时间"> 2023-06-23 15:23 </t-form-item>
-        </t-col>
-        <t-col :span="12">
-          <t-form-item label="问题简要">
-            <t-input v-model="formData.a" placeholder="30字以内"></t-input>
-          </t-form-item>
-        </t-col>
-        <t-col :span="12">
-          <t-form-item label="问题情况说明">
-            <t-textarea
-              v-model="formData.b"
-              placeholder="300字以内"
-            ></t-textarea>
-          </t-form-item>
-        </t-col>
-        <t-col :span="12">
-          <t-form-item label="附件说明">
-            <UploadImage
-              :config="{ limit: 3 }"
-              @Change="imgChange"
-            ></UploadImage>
-          </t-form-item>
-        </t-col>
-      </t-row>
-    </t-form>
-  </div>
+        </t-form-item>
+      </t-col>
+      <t-col :span="6">
+        <t-form-item label="提交人"> 张三 </t-form-item>
+      </t-col>
+      <t-col :span="6">
+        <t-form-item label="提交时间"> 2023-06-23 15:23 </t-form-item>
+      </t-col>
+      <t-col :span="12">
+        <t-form-item label="问题简要">
+          <t-input v-model="formData.a" placeholder="30字以内"></t-input>
+        </t-form-item>
+      </t-col>
+      <t-col :span="12">
+        <t-form-item label="问题情况说明">
+          <t-textarea v-model="formData.b" placeholder="300字以内"></t-textarea>
+        </t-form-item>
+      </t-col>
+      <t-col :span="12">
+        <t-form-item label="附件说明">
+          <UploadImage :config="{ limit: 3 }" @Change="imgChange"></UploadImage>
+        </t-form-item>
+      </t-col>
+    </t-row>
+  </t-form>
 </template>
 
 <script setup name="QualityIssueStep1">

+ 94 - 103
src/views/sop/sop-manage/quality-issue/step2.vue

@@ -1,113 +1,104 @@
 <template>
-  <div class="step2 p-20px">
-    <t-form ref="formRef" :data="formData" :labelWidth="200">
-      <t-row :gutter="[0, 20]">
-        <t-col :span="12">
-          <div class="form-group-title" style="margin-bottom: 0">
-            初审意见
+  <t-form ref="formRef" :data="formData" :labelWidth="200">
+    <t-row :gutter="[0, 20]">
+      <t-col :span="12">
+        <div class="form-group-title" style="margin-bottom: 0"> 初审意见 </div>
+      </t-col>
+      <t-col :span="12">
+        <t-form-item label="核实情况简要">
+          <t-input v-model="formData.a" placeholder="30字以内"></t-input>
+        </t-form-item>
+      </t-col>
+      <t-col :span="12">
+        <t-form-item label="核实情况备注">
+          <t-textarea v-model="formData.b" placeholder="300字以内"></t-textarea>
+        </t-form-item>
+      </t-col>
+      <t-col :span="12">
+        <t-form-item label="质量问题类型">
+          <div class="p-t-5px">
+            <p class="red"
+              >1、修正类:未执行SOP或未按要求正确操作,需要修正的问题;</p
+            >
+            <p class="red"
+              >2、优化类:SOP未定义或无明确要求和流程的,需要进行流程优化的问题;</p
+            >
+            <p class="red">3、不是问题:不是质量问题。</p>
+            <t-radio-group
+              v-model="formData.c"
+              allow-uncheck
+              :options="[
+                { value: '1', label: '修正类' },
+                { value: '2', label: '优化类' },
+                { value: '3', label: '不是问题' },
+              ]"
+            ></t-radio-group>
           </div>
-        </t-col>
-        <t-col :span="12">
-          <t-form-item label="核实情况简要">
-            <t-input v-model="formData.a" placeholder="30字以内"></t-input>
-          </t-form-item>
-        </t-col>
-        <t-col :span="12">
-          <t-form-item label="核实情况备注">
-            <t-textarea
-              v-model="formData.b"
-              placeholder="300字以内"
-            ></t-textarea>
-          </t-form-item>
-        </t-col>
-        <t-col :span="12">
-          <t-form-item label="质量问题类型">
-            <div class="p-t-5px">
-              <p class="red"
-                >1、修正类:未执行SOP或未按要求正确操作,需要修正的问题;</p
-              >
-              <p class="red"
-                >2、优化类:SOP未定义或无明确要求和流程的,需要进行流程优化的问题;</p
-              >
-              <p class="red">3、不是问题:不是质量问题。</p>
-              <t-radio-group
-                v-model="formData.c"
-                allow-uncheck
-                :options="[
-                  { value: '1', label: '修正类' },
-                  { value: '2', label: '优化类' },
-                  { value: '3', label: '不是问题' },
-                ]"
-              ></t-radio-group>
-            </div>
-          </t-form-item>
-        </t-col>
-        <t-col :span="12">
-          <t-form-item label="问题归因">
-            <div class="p-t-5px">
-              <p class="red"
-                >1.执行类:在项目关键信息明确的前提下,人员疏忽、失误、处理不当、项目风险预估不足、预估到风险却未及时采取合理措施,或未按流程严格执行到位等;</p
-              >
-              <p class="red"
-                >2.管理协调类:项目管理未处理好内外项目关键信息沟通协调、计划延期完成等。</p
-              >
-              <p class="red"
-                >3.产品缺陷类:产品设计缺陷、BUG,新应用场景需求等;</p
-              >
-              <p class="red"
-                >4.产品运维类:例如服务器高并发崩溃、网络阻塞等;</p
-              >
-              <p class="red"
-                >5.流程制度类:流程制度本身缺失、设计不合理导致的问题。</p
-              >
-              <t-radio-group
-                v-model="formData.d"
-                allow-uncheck
-                :options="[
-                  { value: '1', label: '修正类' },
-                  { value: '2', label: '优化类' },
-                  { value: '3', label: '不是问题' },
-                  { value: '4', label: '不是问题' },
-                  { value: '5', label: '不是问题' },
-                  { value: '6', label: '其他' },
-                ]"
-              ></t-radio-group>
-            </div>
-          </t-form-item>
-        </t-col>
-        <t-col :span="12">
-          <t-form-item label="影响度">
+        </t-form-item>
+      </t-col>
+      <t-col :span="12">
+        <t-form-item label="问题归因">
+          <div class="p-t-5px">
+            <p class="red"
+              >1.执行类:在项目关键信息明确的前提下,人员疏忽、失误、处理不当、项目风险预估不足、预估到风险却未及时采取合理措施,或未按流程严格执行到位等;</p
+            >
+            <p class="red"
+              >2.管理协调类:项目管理未处理好内外项目关键信息沟通协调、计划延期完成等。</p
+            >
+            <p class="red"
+              >3.产品缺陷类:产品设计缺陷、BUG,新应用场景需求等;</p
+            >
+            <p class="red">4.产品运维类:例如服务器高并发崩溃、网络阻塞等;</p>
+            <p class="red"
+              >5.流程制度类:流程制度本身缺失、设计不合理导致的问题。</p
+            >
             <t-radio-group
-              v-model="formData.e"
+              v-model="formData.d"
               allow-uncheck
               :options="[
-                { value: 'A', label: 'A级' },
-                { value: 'B', label: 'B级' },
-                { value: 'C', label: 'C级' },
-                { value: 'D', label: 'D级' },
+                { value: '1', label: '修正类' },
+                { value: '2', label: '优化类' },
+                { value: '3', label: '不是问题' },
+                { value: '4', label: '不是问题' },
+                { value: '5', label: '不是问题' },
+                { value: '6', label: '其他' },
               ]"
             ></t-radio-group>
-          </t-form-item>
-        </t-col>
-        <t-col :span="6">
-          <t-form-item label="责任人(可多选)">
-            <t-select v-model="formData.f" :options="[]" multiple />
-          </t-form-item>
-        </t-col>
-        <t-col :span="6"> </t-col>
-        <t-col :span="6">
-          <t-form-item label="甲方复审人员(可多选)">
-            <t-select v-model="formData.g" :options="[]" multiple />
-          </t-form-item>
-        </t-col>
-        <t-col :span="6">
-          <t-form-item label="乙方复审人员(可多选)">
-            <t-select v-model="formData.h" :options="[]" multiple />
-          </t-form-item>
-        </t-col>
-      </t-row>
-    </t-form>
-  </div>
+          </div>
+        </t-form-item>
+      </t-col>
+      <t-col :span="12">
+        <t-form-item label="影响度">
+          <t-radio-group
+            v-model="formData.e"
+            allow-uncheck
+            :options="[
+              { value: 'A', label: 'A级' },
+              { value: 'B', label: 'B级' },
+              { value: 'C', label: 'C级' },
+              { value: 'D', label: 'D级' },
+            ]"
+          ></t-radio-group>
+        </t-form-item>
+      </t-col>
+      <t-col :span="6">
+        <t-form-item label="责任人(可多选)">
+          <t-select v-model="formData.f" :options="[]" multiple />
+        </t-form-item>
+      </t-col>
+      <t-col :span="6"> </t-col>
+      <t-col :span="6">
+        <t-form-item label="甲方复审人员(可多选)">
+          <t-select v-model="formData.g" :options="[]" multiple />
+        </t-form-item>
+      </t-col>
+      <t-col :span="6">
+        <t-form-item label="乙方复审人员(可多选)">
+          <t-select v-model="formData.h" :options="[]" multiple />
+        </t-form-item>
+      </t-col>
+    </t-row>
+  </t-form>
 </template>
 
 <script setup name="QualityIssueStep2">

+ 24 - 28
src/views/sop/sop-manage/quality-issue/step3.vue

@@ -1,32 +1,28 @@
 <template>
-  <div class="step3 p-20px">
-    <t-form ref="formRef" :data="formData" :labelWidth="90">
-      <t-row :gutter="[0, 20]">
-        <t-col :span="12">
-          <div class="form-group-title" style="margin-bottom: 0">
-            甲方复核
-          </div>
-        </t-col>
-        <t-col :span="12">
-          <t-form-item label="审核意见">
-            <t-radio-group
-              v-model="formData.a"
-              allow-uncheck
-              :options="[
-                { value: '1', label: '同意' },
-                { value: '2', label: '不同意' },
-              ]"
-            ></t-radio-group>
-          </t-form-item>
-        </t-col>
-        <t-col :span="12">
-          <t-form-item label="备注">
-            <t-textarea v-model="formData.b"></t-textarea>
-          </t-form-item>
-        </t-col>
-      </t-row>
-    </t-form>
-  </div>
+  <t-form ref="formRef" :data="formData" :labelWidth="90">
+    <t-row :gutter="[0, 20]">
+      <t-col :span="12">
+        <div class="form-group-title" style="margin-bottom: 0"> 甲方复核 </div>
+      </t-col>
+      <t-col :span="12">
+        <t-form-item label="审核意见">
+          <t-radio-group
+            v-model="formData.a"
+            allow-uncheck
+            :options="[
+              { value: '1', label: '同意' },
+              { value: '2', label: '不同意' },
+            ]"
+          ></t-radio-group>
+        </t-form-item>
+      </t-col>
+      <t-col :span="12">
+        <t-form-item label="备注">
+          <t-textarea v-model="formData.b"></t-textarea>
+        </t-form-item>
+      </t-col>
+    </t-row>
+  </t-form>
 </template>
 
 <script setup name="QualityIssueStep3">

+ 24 - 28
src/views/sop/sop-manage/quality-issue/step4.vue

@@ -1,32 +1,28 @@
 <template>
-  <div class="step4 p-20px">
-    <t-form ref="formRef" :data="formData" :labelWidth="90">
-      <t-row :gutter="[0, 20]">
-        <t-col :span="12">
-          <div class="form-group-title" style="margin-bottom: 0">
-            甲方复核
-          </div>
-        </t-col>
-        <t-col :span="12">
-          <t-form-item label="审核意见">
-            <t-radio-group
-              v-model="formData.a"
-              allow-uncheck
-              :options="[
-                { value: '1', label: '同意' },
-                { value: '2', label: '不同意' },
-              ]"
-            ></t-radio-group>
-          </t-form-item>
-        </t-col>
-        <t-col :span="12">
-          <t-form-item label="备注">
-            <t-textarea v-model="formData.b"></t-textarea>
-          </t-form-item>
-        </t-col>
-      </t-row>
-    </t-form>
-  </div>
+  <t-form ref="formRef" :data="formData" :labelWidth="90">
+    <t-row :gutter="[0, 20]">
+      <t-col :span="12">
+        <div class="form-group-title" style="margin-bottom: 0"> 甲方复核 </div>
+      </t-col>
+      <t-col :span="12">
+        <t-form-item label="审核意见">
+          <t-radio-group
+            v-model="formData.a"
+            allow-uncheck
+            :options="[
+              { value: '1', label: '同意' },
+              { value: '2', label: '不同意' },
+            ]"
+          ></t-radio-group>
+        </t-form-item>
+      </t-col>
+      <t-col :span="12">
+        <t-form-item label="备注">
+          <t-textarea v-model="formData.b"></t-textarea>
+        </t-form-item>
+      </t-col>
+    </t-row>
+  </t-form>
 </template>
 
 <script setup name="QualityIssueStep4">

+ 195 - 56
src/views/sop/sop-manage/sop-step/index.vue

@@ -73,12 +73,18 @@
       </t-collapse-panel>
     </t-collapse>
 
-    <t-tabs class="sop-step-list" v-model="curStep" placement="left">
+    <t-tabs
+      class="sop-step-list"
+      v-model="curStep"
+      placement="left"
+      @change="stepChange"
+    >
       <t-tab-panel
         v-for="item in tabs"
         :key="item.taskKey"
         :value="item.value"
         :label="item.label"
+        :disabled="item.disabled"
       >
         <t-form
           ref="form"
@@ -101,6 +107,7 @@
               :key="config.id"
             >
               <MyFormItem
+                v-if="config.visable"
                 :config="config"
                 :labelWidth="labelWidth"
                 @change="itemValueChange"
@@ -109,8 +116,22 @@
           </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-button v-if="IS_EDIT_MODE" theme="primary" @click="saveHandle"
+            >提交</t-button
+          >
+          <t-button
+            v-if="IS_FILL_MODE"
+            theme="primary"
+            @click="submitHandle('START')"
+            >提交</t-button
+          >
+
+          <t-button
+            v-if="IS_FILL_MODE"
+            theme="default"
+            @click="submitHandle('DRAFT')"
+            >保存草稿</t-button
+          >
         </t-space>
       </t-tab-panel>
     </t-tabs>
@@ -120,11 +141,18 @@
 <script setup name="SopStep">
 import { ref, computed, watch } from 'vue';
 import { ErrorCircleFilledIcon } from 'tdesign-icons-vue-next';
+import { MessagePlugin } from 'tdesign-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 { useRouter, useRoute } from 'vue-router';
+import {
+  sopFlowViewApi,
+  sopEditApi,
+  sopSaveApi,
+  sopApproveApi,
+} from '@/api/sop';
+import { deepCopy } from '@/utils/tool';
+// import bus from '@/utils/bus';
 
 const props = defineProps({
   type: {
@@ -138,6 +166,14 @@ const props = defineProps({
     },
   },
 });
+const emit = defineEmits(['confirm']);
+
+const IS_FILL_MODE = computed(() => {
+  return props.type === 'fill';
+});
+const IS_EDIT_MODE = computed(() => {
+  return props.type === 'edit';
+});
 
 const needValueCodes = [
   'NUMBER', //新增
@@ -158,10 +194,10 @@ const rules = ref({
   // scan_net_radio_2: [{ required: true, type: 'error', message: '大大是的' }],
 });
 const formData = ref({});
-const formProperty = ref([]);
+// const formProperty = ref([]);
 const loading = ref(false);
-const route = useRoute();
-const router = useRouter();
+// const route = useRoute();
+// const router = useRouter();
 const labelWidth = ref(230);
 const fullWidthCodes = ref([
   'TABLE',
@@ -172,38 +208,75 @@ const fullWidthCodes = ref([
   'DEVICE_IN_TABLE',
 ]);
 const form = ref(null);
+const sopEditDetail = ref({});
+const allFormData = ref({});
 const allSteps = ref([]);
 const tabs = ref([]);
 const curStep = ref('');
 const flowId = props.sop.flowId;
 
-const initFill = () => {
+const initFill = async () => {
   loading.value = true;
-  sopFlowViewApi({ flowId }).then((res) => {
-    loading.value = false;
-    curStep.value = res.currFlowTaskResult.taskName;
-    res.flowTaskHistoryList = res.flowTaskHistoryList || [];
-    res.flowTaskHistoryList.forEach((item) => {
-      item.formProperty.forEach((v) => {
-        v.writable = false;
-      });
+  const res = await sopFlowViewApi({ flowId });
+  loading.value = false;
+  curStep.value = res.currFlowTaskResult.taskName;
+  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];
+  tabs.value = [
+    ...res.flowTaskHistoryList.map((item) => {
+      return {
+        value: item.taskName,
+        label: item.taskName,
+        disabled: true,
+      };
+    }),
+    {
+      value: res.currFlowTaskResult.taskName,
+      label: res.currFlowTaskResult.taskName,
+      disabled: false,
+    },
+  ];
+};
+const initEdit = async () => {
+  loading.value = true;
+  const res = await sopEditApi(props.sop.id);
+  loading.value = false;
+
+  sopEditDetail.value = res;
+  allSteps.value = Object.values(res.setupMap).sort(
+    (a, b) => a.setup - b.setup
+  );
+  // let allFormMap = {};
+  // allSteps.value.forEach((item) => {
+  //   item.formProperty.forEach((prop) => {
+  //     allFormMap[prop.formName] = prop.value
+  //       ? JSON.parse(prop.value).value
+  //       : null;
+  //   });
+  // });
+  // allFormData.value = allFormMap;
+
+  tabs.value = allSteps.value.map((item) => {
+    return {
+      value: item.taskName,
+      label: item.taskName,
+      disabled: false,
+    };
+  });
+  curStep.value = tabs.value.slice(-1)[0].value;
 };
 const init = () => {
-  if (props.type === 'fill') {
-    //type为fill是点击“填报”进来的
+  if (IS_FILL_MODE.value) {
+    // 填报
     initFill();
+  } else if (IS_EDIT_MODE.value) {
+    // 编辑
+    initEdit();
   } else {
     //还有可能是点击“新增SOP”进来的
   }
@@ -211,10 +284,19 @@ const init = () => {
 init();
 
 const curFormConfig = computed(() => {
-  return (
+  const formProperty =
     allSteps.value.find((item) => item.taskName === curStep.value)
-      ?.formProperty || []
-  );
+      ?.formProperty || [];
+  formProperty.forEach((item) => {
+    if (IS_EDIT_MODE.value) {
+      item.writable = true;
+      item.value = allFormData.value[item.formName];
+    } else if (IS_FILL_MODE.value) {
+      // item.value = item.value ? JSON.parse(item.value).value : null;
+      item.value = null;
+    }
+  });
+  return formProperty;
 });
 watch(curFormConfig, (val) => {
   formData.value = val.reduce((obj, item) => {
@@ -239,32 +321,89 @@ watch(curFormConfig, (val) => {
     return obj;
   }, {});
 });
-// 提交
-const submitHandle = () => {
-  bus.emit('checkTable');
-  let tablePassed = false;
-  bus.on('tablePassed', () => {
-    tablePassed = true;
-    console.log('table通过了');
-  });
-  bus.on('tableFailed', () => {
-    tablePassed = false;
-    console.log('table不通过');
-  });
-  setTimeout(() => {
-    form.value[0].validate().then(async (result) => {
-      if (result === true && tablePassed) {
-        //提交表单接口执行
-      }
-    });
-  }, 10);
+
+const stepChange = () => {
+  if (IS_EDIT_MODE.value) {
+    allFormData.value = { ...allFormData.value, ...formData.value };
+  }
 };
-// 暂存
-const stageHandle = () => {};
+
 const itemValueChange = ({ prop, value }) => {
+  console.log(prop, ':', value);
   formData.value[prop] = value;
 };
-const back = () => {
-  history.back();
+
+const getFormData = () => {
+  let data = {};
+  if (IS_FILL_MODE.value) {
+    const curStepData = allSteps.value.find(
+      (item) => item.taskName === curStep.value
+    );
+    data = { ...curStepData };
+    data.formProperty = data.formProperty.map((item) => {
+      return {
+        ...item,
+        value: JSON.stringify({ value: formData.value[item.formName] || null }),
+      };
+    });
+  }
+
+  if (IS_EDIT_MODE.value) {
+    data = deepCopy(sopEditDetail.value);
+    for (const k in data.setupMap) {
+      if (Object.hasOwnProperty.call(data.setupMap, k)) {
+        const element = data.setupMap[k];
+        element.formProperty.forEach((item) => {
+          item.value = JSON.stringify({
+            value: allFormData.value[item.formName] || null,
+          });
+        });
+      }
+    }
+  }
+
+  return JSON.stringify(data);
+};
+// 填报-提交
+const submitHandle = async (approve = 'START') => {
+  if (approve === 'START') {
+    // bus.emit('checkTable');
+    // let tablePassed = false;
+    // bus.on('tablePassed', () => {
+    //   tablePassed = true;
+    //   console.log('table通过了');
+    // });
+    // bus.on('tableFailed', () => {
+    //   tablePassed = false;
+    //   console.log('table不通过');
+    // });
+    const valid = await form.value[0].validate();
+    if (valid !== true) return;
+  }
+
+  const res = await sopApproveApi({
+    taskId: props.sop.taskId,
+    formProperties: getFormData(),
+    approve,
+  }).catch(() => {});
+  if (!res) return;
+
+  MessagePlugin.success('保存成功');
+  emit('confirm');
+};
+// 编辑-保存
+const saveHandle = async () => {
+  stepChange();
+  // const valid = await form.value[0].validate();
+  // if (valid !== true) return;
+
+  const res = await sopSaveApi({
+    id: props.sop.id,
+    formProperties: getFormData(),
+  }).catch(() => {});
+  if (!res) return;
+
+  MessagePlugin.success('保存成功');
+  emit('confirm');
 };
 </script>

+ 1 - 1
src/views/system/config-manage/checkin-manage/edit-checkin-dialog.vue

@@ -88,7 +88,7 @@
 import { ref, computed } from 'vue';
 import { MessagePlugin } from 'tdesign-vue-next';
 import useClearDialog from '@/hooks/useClearDialog';
-import { CUSTOMER_TYPE } from '@/config/constants';
+// import { CUSTOMER_TYPE } from '@/config/constants';
 import { checkinEditApi } from '@/api/system';
 import { omit } from 'lodash';
 const emit = defineEmits(['update:visible', 'success']);