瀏覽代碼

延期预警

刘洋 1 年之前
父節點
當前提交
efc5890e2d

+ 14 - 0
src/api/common.js

@@ -0,0 +1,14 @@
+import { request } from '@/utils/request.js';
+import { getFileMD5 } from '@/utils/crypto.js';
+//上传附件接口
+export const uploadFiles = (formData, md5) => {
+  return request({
+    url: '/api/admin/common/file/upload',
+    method: 'post',
+    data: formData,
+    headers: {
+      md5,
+      'Content-Type': 'multipart/form-data',
+    },
+  });
+};

+ 39 - 0
src/api/sop.js

@@ -0,0 +1,39 @@
+import { request } from '@/utils/request.js';
+
+//延期预警列表
+export const getDelayWarnList = (data) =>
+  request({
+    url: '/api/admin/tb/delay/warn/query',
+    method: 'post',
+    params: data,
+  });
+
+//关闭单个延期预警
+export const closeDelayWarn = (id) =>
+  request({
+    url: '/api/admin/tb/delay/warn/close?id=' + id,
+    method: 'post',
+    loading: true,
+  });
+//重启延期预警
+export const restartDelayWarn = (id) =>
+  request({
+    url: '/api/admin/tb/delay/warn/restart?id=' + id,
+    method: 'post',
+    loading: true,
+  });
+//延期预警明细
+export const delayWarnDetail = (id) =>
+  request({
+    url: '/api/admin/tb/delay/warn/detail/list?id=' + id,
+    method: 'post',
+  });
+
+//延期预警跟进
+export const flowDelayWarn = (data) =>
+  request({
+    url: '/api/admin/tb/delay/warn/detail/save',
+    method: 'post',
+    data,
+    loading: true,
+  });

+ 99 - 0
src/components/global/my-upload/index.vue

@@ -0,0 +1,99 @@
+<template>
+  <t-upload
+    ref="uploadRef3"
+    v-model="files"
+    :theme="theme"
+    :tips="`最多只能上传 ${imgLength} 张图片`"
+    accept="image/*"
+    :abridge-name="[6, 6]"
+    :disabled="disabled"
+    :auto-upload="true"
+    :upload-all-files-in-one-request="false"
+    multiple
+    :max="imgLength"
+    :before-upload="handleBeforeUpload"
+    :request-method="upload"
+    @fail="handleFail"
+    @change="change"
+  >
+  </t-upload>
+</template>
+<script setup name="MyUpload">
+import { ref } from 'vue';
+import { MessagePlugin } from 'tdesign-vue-next';
+import { uploadFiles } from '@/api/common';
+import { getFileMD5 } from '@/utils/crypto';
+const emit = defineEmits(['change']);
+const props = defineProps({
+  theme: {
+    type: String,
+    default: 'image',
+  },
+  accept: {
+    type: String,
+    default: '.jpg,.jpeg,.png',
+  },
+  imgLength: {
+    type: Number,
+    default: 3,
+  },
+  maxSize: {
+    // 单位kb
+    type: Number,
+    default: 20 * 1024,
+  },
+  disabled: {
+    type: Boolean,
+    default: false,
+  },
+});
+const files = ref([]);
+const checkFileFormat = (fileType) => {
+  const _file_format = '.' + fileType.split('.').pop().toLocaleLowerCase();
+  return props.accept.split(',').includes(_file_format.toLocaleLowerCase());
+};
+const handleBeforeUpload = (file) => {
+  if (file.size > props.maxSize * 1024) {
+    const size =
+      props.maxSize < 1024
+        ? `${props.maxSize}kb`
+        : `${Math.floor(props.maxSize / 1024)}M`;
+    const content = '文件大小不能超过' + size;
+    MessagePlugin.error(content);
+    return false;
+  }
+
+  if (!checkFileFormat(file.name)) {
+    const content = '只支持文件格式为' + props.accept;
+    MessagePlugin.error(content);
+    return false;
+  }
+
+  return true;
+};
+const handleFail = ({ file }) => {
+  MessagePlugin.error(`文件 ${file.name} 上传失败`);
+};
+
+const upload = async (files) => {
+  console.log('file', files);
+  let formData = new FormData();
+
+  const file = files[0].raw;
+  formData.append('file', file);
+  formData.append('type', 'FILE');
+  const md5 = await getFileMD5(file);
+  console.log(md5);
+
+  const res = await uploadFiles(formData, md5).catch(() => {});
+  console.log('res', res);
+  if (res) {
+    return { status: 'success', response: res };
+  } else {
+    return { status: 'fail', error: '上传失败' };
+  }
+};
+const change = (a, b, c) => {
+  console.log(a, b, c);
+};
+</script>

+ 19 - 4
src/config/constants.js

@@ -91,13 +91,28 @@ export const FLOW_STATUS = {
 };
 
 export const MESSAGE_TYPE = {
-  BEFORE: '提前提醒',
-  AFTER: '延期提醒',
-  OFFICE_SOP: '教务处SOP',
-  CLOUD_MARK_SOP: '云阅卷SOP',
+  // BEFORE: '提前提醒',
+  // AFTER: '延期提醒',
+  // OFFICE_SOP: '教务处SOP',
+  // CLOUD_MARK_SOP: '云阅卷SOP',
   QUALITY: '质量问题提醒',
   EXCEPTION_APPROVE: '异常审核提醒',
   VIOLATION: '违规提醒',
   SYSTEM_PLAN_CHANGE: '系统计划变更提醒',
   SYSTEM: '系统公告',
 };
+
+// SOP管理-延期预警-预警类型
+export const WARN_TYPE = {
+  PLAN: '关键信息及计划',
+  TIME: '处理时限',
+  // CANCEL: '取消',
+};
+
+//SOP管理-延期预警-跟进状态
+export const WARN_FLOW_STATUS = {
+  NOT_START: '未跟进',
+  FOLLOW: '跟进',
+  CLOSE: '关闭',
+  RESTART: '重启',
+};

+ 1 - 1
src/router/modules/sop.js

@@ -47,7 +47,7 @@ export default {
           component: () => import('@/views/sop/sop-manage/sop-step/index.vue'),
           meta: {
             title: '当前SOP流程',
-            alias: 'office',
+            bind: 'office',
             // bind: 'OfficeSop',
           },
         },

+ 9 - 0
src/style/global.less

@@ -94,3 +94,12 @@ body {
     }
   }
 }
+.form-group-title {
+  font-weight: bold;
+  color: var(--td-brand-color);
+  margin-bottom: 15px;
+  font-size: 14px;
+  &.next-title {
+    margin-top: 20px;
+  }
+}

+ 2 - 2
src/views/my-workbenches/workbenches/notice/index.vue

@@ -45,7 +45,7 @@ import NoticeList from './notice-list.vue';
 import { omit } from 'lodash';
 const params = reactive({
   types: ['SYSTEM'],
-  query: '',
+  title: '',
   time: [],
   status: 'undefined',
 });
@@ -85,7 +85,7 @@ const fields = ref([
   },
 
   {
-    prop: 'query',
+    prop: 'title',
     label: '标题关键字',
     labelWidth: 100,
     colSpan: 5,

+ 1 - 2
src/views/my-workbenches/workbenches/notice/notice-list.vue

@@ -20,7 +20,7 @@
 <script setup name="MessageList">
 const columns = [
   {
-    colKey: 'content',
+    colKey: 'title',
     title: '通知标题',
   },
   {
@@ -34,7 +34,6 @@ const { tableData, loading, pagination, onChange } = defineProps([
   'pagination',
   'onChange',
 ]);
-console.log(tableData, loading, pagination);
 </script>
 
 <style lang="less" scoped></style>

+ 1 - 1
src/views/resource-guard/person-guard/person-files/add-person-file-dialog.vue

@@ -124,7 +124,7 @@
 import { ref, computed } from 'vue';
 import { MessagePlugin } from 'tdesign-vue-next';
 import useClearDialog from '@/hooks/useClearDialog';
-import { personfilesEditApi } from '@/api/resource-guard';
+import { personFilesEditApi } from '@/api/resource-guard';
 
 const emit = defineEmits(['update:visible']);
 const formRef = ref(null);

+ 120 - 0
src/views/sop/sop-monitor/delay-warning/flow-dialog.vue

@@ -0,0 +1,120 @@
+<template>
+  <my-dialog
+    :visible="visible"
+    @close="emit('update:visible', false)"
+    :header="`跟进`"
+    :width="1000"
+    :closeOnOverlayClick="false"
+  >
+    <t-form ref="formRef" :data="formData" labelWidth="85px" :rules="rules">
+      <div class="form-group-title">预警信息</div>
+      <t-row :gutter="[0, 10]">
+        <t-col :span="3">
+          <t-form-item label="服务单元:"> {{ curRow.service }}</t-form-item>
+        </t-col>
+        <t-col :span="3">
+          <t-form-item label="SOP流水号:">
+            {{ curRow.sopNo }}
+          </t-form-item>
+        </t-col>
+        <t-col :span="3">
+          <t-form-item label="客户类型:">
+            {{ curRow.customType }}
+          </t-form-item>
+        </t-col>
+        <t-col :span="3">
+          <t-form-item label="客户名称:">
+            {{ curRow.custom }}
+          </t-form-item>
+        </t-col>
+        <t-col :span="3">
+          <t-form-item label="预警时间:">
+            {{ dateFormat(curRow.warnTime, 'yyyy-MM-dd hh:mm') }}
+          </t-form-item>
+        </t-col>
+        <t-col :span="3">
+          <t-form-item label="节点负责人:">
+            {{ curRow.userName }}
+          </t-form-item>
+        </t-col>
+        <t-col :span="3">
+          <t-form-item label="预警类型:">
+            {{ WARN_TYPE[curRow.type] || curRow.type }}
+          </t-form-item>
+        </t-col>
+        <t-col :span="3">
+          <t-form-item label="预警字段:">
+            {{ curRow.fieldObj }}
+          </t-form-item>
+        </t-col>
+      </t-row>
+      <div class="form-group-title next-title">新增跟进</div>
+      <t-row :gutter="[0, 10]">
+        <t-col :span="12">
+          <t-form-item label="跟进人:">
+            {{ userStore.user.realName }}</t-form-item
+          >
+        </t-col>
+        <t-col :span="12">
+          <t-form-item label="附件说明:">
+            <my-upload></my-upload>
+          </t-form-item>
+        </t-col>
+      </t-row>
+    </t-form>
+    <template #foot></template>
+  </my-dialog>
+</template>
+<script setup name="FlowDialog">
+import useClearDialog from '@/hooks/useClearDialog';
+import { ref, computed } from 'vue';
+import { WARN_TYPE, WARN_FLOW_STATUS } from '@/config/constants';
+import { dateFormat } from '@/utils/tool';
+import { useUserStore } from '@/store';
+const userStore = useUserStore();
+const props = defineProps({
+  visible: Boolean,
+  curRow: Object,
+});
+const { curRow } = props;
+const emit = defineEmits(['update:visible', 'success']);
+const formRef = ref(null);
+const getDetail = async () => {
+  //编辑状态下获取回显数据的接口请求业务,如果curRow里的字段够用,就直接把curRow里的字段赋值给formData
+  for (let key in formData) {
+    // formData[key] = props.curRow[key];
+  }
+};
+const { formData, isEdit } = useClearDialog(
+  {
+    attachmentIds: '',
+    remark: '',
+  },
+  props,
+  formRef,
+  getDetail
+);
+
+const rules = {
+  remark: [
+    {
+      required: true,
+      message: '请填写跟进说明',
+      type: 'error',
+      trigger: 'blur',
+    },
+  ],
+};
+const flowHandler = () => {
+  //   addUser({ ...formData, id: props.curRow?.id || undefined }).then(() => {
+  //     emit('success');
+  //   });
+};
+const save = () => {
+  formRef.value.validate().then(async (result) => {
+    if (result === true) {
+      flowHandler();
+    }
+  });
+};
+</script>

+ 154 - 67
src/views/sop/sop-monitor/delay-warning/index.vue

@@ -1,6 +1,13 @@
 <template>
   <div class="delay-warning flex flex-col h-full">
-    <SearchForm :fields="fields" :params="params"></SearchForm>
+    <SearchForm :fields="fields" :params="params">
+      <template #service="{ item, params }">
+        <select-service-unit
+          v-model="params[item.prop]"
+          clearable
+        ></select-service-unit>
+      </template>
+    </SearchForm>
     <div class="flex-1 page-wrap">
       <t-table
         size="small"
@@ -17,28 +24,89 @@
       >
       </t-table>
     </div>
+    <FlowDialog
+      v-model:visible="showFlowDialog"
+      :curRow="curRow"
+      @success="editSuccess"
+    ></FlowDialog>
   </div>
 </template>
 
 <script setup lang="jsx" name="DelayWarning">
-import { reactive, ref } from 'vue';
-import { getTableData } from '@/api/test';
+import { reactive, ref, computed } from 'vue';
+import { getDelayWarnList, closeDelayWarn, restartDelayWarn } from '@/api/sop';
 import useFetchTable from '@/hooks/useFetchTable';
-
-const rowData = ref({});
-
+import { dictToOptionList } from '@/utils/tool';
+import { WARN_TYPE, WARN_FLOW_STATUS } from '@/config/constants';
+import { omit } from 'lodash';
+import { DialogPlugin, MessagePlugin } from 'tdesign-vue-next';
+import FlowDialog from './flow-dialog';
+const showFlowDialog = ref(true);
+const curRow = ref({
+  service: 'aaa',
+  sopNo: 'bbb',
+  customType: 'ccc',
+  custom: 'ddd',
+  warnTime: 1692599171692,
+  userName: '张三',
+  type: 'PLAN',
+  fieldObj: '',
+});
+const editSuccess = () => {};
+const restartHandler = (row) => {
+  const confirmDia = DialogPlugin({
+    header: '操作提示',
+    body: `确定要重启吗`,
+    confirmBtn: '确定',
+    cancelBtn: '取消',
+    onConfirm: () => {
+      restartDelayWarn(row.id).then(() => {
+        confirmDia.hide();
+        MessagePlugin.success('操作成功');
+        fetchData();
+      });
+    },
+  });
+};
+const closeHandler = (row) => {
+  const confirmDia = DialogPlugin({
+    header: '操作提示',
+    body: `确定要关闭吗`,
+    confirmBtn: '确定',
+    cancelBtn: '取消',
+    onConfirm: () => {
+      closeDelayWarn(row.id).then(() => {
+        confirmDia.hide();
+        MessagePlugin.success('操作成功');
+        fetchData();
+      });
+    },
+  });
+};
 const columns = [
-  { colKey: 'a', title: '服务单元' },
-  { colKey: 'b', title: '预警流水号' },
-  { colKey: 'c', title: '预警时间' },
-  { colKey: 'd', title: 'SOP流水号' },
-  { colKey: 'e', title: '节点负责人' },
-  { colKey: 'f', title: '客户名称' },
-  { colKey: 'g', title: '项目单号' },
-  { colKey: 'h', title: '项目名称' },
-  { colKey: 'i', title: '预警类型' },
-  { colKey: 'j', title: '预警字段' },
-  { colKey: 'k', title: '跟进状态' },
+  { colKey: 'service', title: '服务单元' },
+  // { colKey: 'b', title: '预警流水号' },
+  { colKey: 'warnTime', title: '预警时间' },
+  { colKey: 'sopNo', title: 'SOP流水号' },
+  { colKey: 'userName', title: '节点负责人' },
+  { colKey: 'custom', title: '客户名称' },
+  { colKey: 'crmNo', title: '项目单号' },
+  { colKey: 'crmName', title: '项目名称' },
+  {
+    colKey: 'type',
+    title: '预警类型',
+    cell: (h, { row }) => {
+      return <span>{WARN_TYPE[row.type] || row.type}</span>;
+    },
+  },
+  { colKey: 'fieldObj', title: '预警字段' },
+  {
+    colKey: 'status',
+    title: '跟进状态',
+    cell: (h, { row }) => {
+      return <span>{WARN_FLOW_STATUS[row.status] || row.status}</span>;
+    },
+  },
   {
     title: '操作',
     colKey: 'operate',
@@ -47,33 +115,40 @@ const columns = [
     cell: (h, { row }) => {
       return (
         <div class="table-operations">
-          <t-link
-            theme="primary"
-            hover="color"
-            onClick={(e) => {
-              e.stopPropagation();
-            }}
-          >
-            跟进
-          </t-link>
-          <t-link
-            theme="primary"
-            hover="color"
-            onClick={(e) => {
-              e.stopPropagation();
-            }}
-          >
-            重启
-          </t-link>
-          <t-link
-            theme="primary"
-            hover="color"
-            onClick={(e) => {
-              e.stopPropagation();
-            }}
-          >
-            关闭
-          </t-link>
+          {row.status === 'NOT_START' ? (
+            <t-link
+              theme="primary"
+              hover="color"
+              onClick={(e) => {
+                e.stopPropagation();
+              }}
+            >
+              跟进
+            </t-link>
+          ) : null}
+          {row.status === 'CLOSE' ? (
+            <t-link
+              theme="primary"
+              hover="color"
+              onClick={(e) => {
+                e.stopPropagation();
+                restartHandler(row);
+              }}
+            >
+              重启
+            </t-link>
+          ) : (
+            <t-link
+              theme="primary"
+              hover="color"
+              onClick={(e) => {
+                e.stopPropagation();
+                closeHandler(row);
+              }}
+            >
+              关闭
+            </t-link>
+          )}
         </div>
       );
     },
@@ -82,30 +157,38 @@ const columns = [
 
 const fields = ref([
   {
-    prop: 'a',
+    prop: 'serviceId',
     label: '服务单元',
     type: 'select',
-    labelWidth: 80,
+    labelWidth: 100,
     colSpan: 5,
+    cell: 'service',
   },
   {
-    prop: 'b',
+    prop: 'type',
     label: '预警类型',
     type: 'select',
     labelWidth: 80,
+    options: dictToOptionList(WARN_TYPE),
     colSpan: 5,
+    attrs: {
+      clearable: true,
+    },
   },
   {
-    prop: 'c',
+    prop: 'status',
     label: '跟进状态',
     type: 'select',
     labelWidth: 80,
+    options: dictToOptionList(WARN_FLOW_STATUS),
     colSpan: 5,
+    attrs: {
+      clearable: true,
+    },
   },
   {
-    prop: 'd',
+    prop: 'custom',
     label: '客户名称',
-    type: 'select',
     labelWidth: 80,
     colSpan: 5,
   },
@@ -120,42 +203,46 @@ const fields = ref([
     ],
   },
   {
-    prop: 'e',
+    prop: 'sopNo',
     label: 'SOP流水号',
     labelWidth: 80,
     colSpan: 5,
   },
   {
-    prop: 'f',
+    prop: 'time',
     label: '预警时间',
     type: 'daterange',
     labelWidth: 80,
     colSpan: 10,
   },
   {
-    prop: 'g',
+    prop: 'fieldObj',
     label: '预警字段',
     labelWidth: 80,
     colSpan: 5,
   },
 ]);
 const params = reactive({
-  a: '',
-  b: '',
-  c: '',
-  d: '',
-  e: '',
-  f: [],
-  g: '',
+  serviceId: '',
+  type: '',
+  status: '',
+  custom: '',
+  sopNo: '',
+  time: [],
+  fieldObj: '',
+});
+const transParams = computed(() => {
+  return {
+    ...omit(params, 'time'),
+    startTime: params.time[0],
+    endTime: params.time[1],
+  };
 });
 
-const {
-  loading: tableLoading,
-  pagination,
-  tableData,
-  fetchData,
-  onChange,
-} = useFetchTable(getTableData);
+const { loading, pagination, tableData, onChange, fetchData, search } =
+  useFetchTable(getDelayWarnList, {
+    params: transParams,
+  });
 
 const refresh = async () => {};
 </script>