Sfoglia il codice sorgente

Merge branch 'CC-system' into 'dev'

完成消息系统通知

See merge request skyeye/skyeye_frontend/skyeye-admin!114
楼航飞 1 anno fa
parent
commit
0ea262810d
30 ha cambiato i file con 1525 aggiunte e 257 eliminazioni
  1. BIN
      src/assets/images/deleteTip.png
  2. 47 0
      src/main.css
  3. 1 0
      src/views/message/CustomSelectTree.vue
  4. 14 13
      src/views/message/SelectTree.vue
  5. 1 1
      src/views/message/alarmMessages/alarmMessages.vue
  6. 188 183
      src/views/message/components/PushObject.vue
  7. 1 0
      src/views/message/designatedUserSelectTree.vue
  8. 1 3
      src/views/message/persongroup/components/GroupBoard.vue
  9. 32 25
      src/views/message/persongroup/components/SelectTree.vue
  10. 1 2
      src/views/message/reportmessage/api/index.ts
  11. 68 30
      src/views/message/sysnotion-config/SysnotionConfig.vue
  12. 22 0
      src/views/message/sysnotion-config/api/index.ts
  13. 21 0
      src/views/message/sysnotion-config/type.ts
  14. 59 0
      src/views/message/systemNotifications/api/index.ts
  15. 42 0
      src/views/message/systemNotifications/components/SearchBar.vue
  16. 242 0
      src/views/message/systemNotifications/components/WorkShopTree.vue
  17. 332 0
      src/views/message/systemNotifications/components/problemHandleTable.vue
  18. 216 0
      src/views/message/systemNotifications/components/systemNotificationTable.vue
  19. BIN
      src/views/message/systemNotifications/img/completed.png
  20. BIN
      src/views/message/systemNotifications/img/create.png
  21. BIN
      src/views/message/systemNotifications/img/delete.png
  22. BIN
      src/views/message/systemNotifications/img/deleteTip.png
  23. BIN
      src/views/message/systemNotifications/img/edit.png
  24. BIN
      src/views/message/systemNotifications/img/empty.png
  25. BIN
      src/views/message/systemNotifications/img/prepareToPush.png
  26. BIN
      src/views/message/systemNotifications/img/search.png
  27. BIN
      src/views/message/systemNotifications/img/view.png
  28. 33 0
      src/views/message/systemNotifications/store/index.ts
  29. 76 0
      src/views/message/systemNotifications/systemNotifications.vue
  30. 128 0
      src/views/message/systemNotifications/type.ts

BIN
src/assets/images/deleteTip.png


+ 47 - 0
src/main.css

@@ -63,4 +63,51 @@
         height: calc(100% - 63px);
         overflow-y: auto;
     }
+}
+
+.workShopDialog {
+    box-shadow: 0px 9px 28px 8px rgba(0, 0, 0, 0.05), 0px 6px 16px 0px rgba(0, 0, 0, 0.08), 0px 3px 6px -4px rgba(0, 0, 0, 0.12);
+    border-radius: 8px;
+
+    .el-dialog__header {
+        span {
+            font-weight: 500;
+            font-size: 16px;
+            color: rgba(0, 0, 0, 0.88);
+            line-height: 24px;
+        }
+    }
+
+    .el-dialog__body {
+        height: 527px;
+    }
+}
+
+.contentDialog {
+    border-radius: 8px;
+
+    .el-dialog__header {
+        padding: 0 0 16px 0;
+
+        span {
+            display: flex;
+            align-items: center;
+            font-weight: 500;
+            font-size: 16px;
+            color: rgba(0, 0, 0, 0.88);
+            line-height: 24px;
+            position: relative;
+
+            &::before {
+                content: '';
+                display: inline-block;
+                width: 16px;
+                height: 16px;
+                background-image: url('@/assets/images/deleteTip.png');
+                background-size: contain;
+                background-repeat: no-repeat;
+                margin-right: 8px;
+            }
+        }
+    }
 }

+ 1 - 0
src/views/message/CustomSelectTree.vue

@@ -20,6 +20,7 @@
       style="height: 583px"
       :width="731"
       :destroy-on-close="true"
+      class="workShopDialog"
     >
       <SelectTree @cancel="handleCancle" @submit="handleSubmit" :selectedUser="selectedUser" />
     </el-dialog>

+ 14 - 13
src/views/message/SelectTree.vue

@@ -20,6 +20,7 @@
       style="height: 583px"
       :width="731"
       :destroy-on-close="true"
+      class="workShopDialog"
     >
       <SelectTree @cancel="handleCancle" @submit="handleSubmit" :selectedUser="selectedUser" />
     </el-dialog>
@@ -37,7 +38,7 @@ interface UserList {
 const dialogVisible = ref<boolean>(false);
 const selectedUser = ref<UserList[]>([]);
 
-const prop = defineProps(['form', 'disableType'])
+const prop = defineProps(['form', 'disableType']);
 
 const handleCancle = () => {
   dialogVisible.value = false;
@@ -50,16 +51,16 @@ const handleSubmit = (selectedData: UserList[]) => {
 watch(
   () => prop.form.customUserList.value,
   (newSelected) => {
-    selectedUser.value = newSelected
+    selectedUser.value = newSelected;
   },
-  { deep: true,immediate: true },
+  { deep: true, immediate: true },
 );
-  onMounted(()=>{
-    if(prop.form.customUserList.value.length > 0){
-      console.log(prop.form.customUserList.value)
-      selectedUser.value = prop.form.customUserList.value
-    }
-  })
+onMounted(() => {
+  if (prop.form.customUserList.value.length > 0) {
+    console.log(prop.form.customUserList.value);
+    selectedUser.value = prop.form.customUserList.value;
+  }
+});
 </script>
 
 <style lang="scss" scoped>
@@ -67,8 +68,8 @@ watch(
   height: 527px;
 }
 ::v-deep .el-select__selection {
-    min-height: 25px;
-    max-height: 60px;
-    overflow-y: auto;
-  }
+  min-height: 25px;
+  max-height: 60px;
+  overflow-y: auto;
+}
 </style>

+ 1 - 1
src/views/message/alarmMessages/alarmMessages.vue

@@ -15,7 +15,7 @@
     <el-table
       :data="alarmDataList"
       stripe
-      height="500px"
+      height="calc(100vh - 300px)"
       :cell-style="{ textAlign: 'center' }"
       :header-cell-style="{ 'text-align': 'center' }"
       style="--el-table-border-color: none"

+ 188 - 183
src/views/message/components/PushObject.vue

@@ -90,196 +90,201 @@
 </template>
 
 <script setup lang="ts">
-  import { onMounted, ref, reactive, watch, watchEffect } from 'vue';
-  import SelectTree from '../persongroup/components/SelectTree.vue';
-  import Group from './Group.vue';
-  import { recipientTypeName } from '../constant';
-  import { ToPushObjectqueryUserGroupList, queryUserGroupDetail } from '../api/index';
-  import type { FormInstance } from 'element-plus';
-  import { GroupData } from '../persongroup/type';
-  const ruleFormRef = ref<FormInstance>();
-  const groupInfo = ref<boolean>(false);
-  const userInfo = ref<boolean>(false);
-  const disabled = ref<boolean>(false);
-  export interface customUserList {
-    userId: number;
-    userLoginName: string;
-    userNickname: string;
-    userNumber: string;
-  }
-  export interface userGroupVOList {
-    userGroupId: number;
-    total: number;
-    operatorName: string;
-    operationTime: string;
-    name: string;
-    description: string;
-  }
-  interface UserList {
-    id: string;
-    name: string;
-    userId: number;
-  }
-  const selectedUser = ref<UserList[]>([]);
-  interface RuleForm {
-    recipientType?: number;
-    userGroupList: number[];
-    customUserList: UserList[];
-  }
-  const ruleForm = reactive<RuleForm>({
-    userGroupList: [],
-    customUserList: [],
-  });
-  const props = defineProps<{
-    recipientType?: number;
-    userGroupList?: userGroupVOList[];
-    customUserList?: customUserList[];
-    disabled?: boolean;
-  }>();
-  const emit = defineEmits(['submitWithForm']);
-  interface Options {
-    userGroupId?: number;
-    name?: string;
-    description: string;
-    total: number;
-    operatorName: string;
-    operationTime: string;
-  }
-  const options = ref<Options[]>([]);
-  const userGroupInfo = ref<GroupData[]>();
-  const queryGroupInfo = (groupList) => {
-    groupInfo.value = true;
-    queryUserGroupDetail(groupList).then((res) => {
-      userGroupInfo.value = res.map((group) => ({
-        ...group,
-        isExpand: false,
-        isHidden: false,
-      }));
-    });
-  };
-  const submitForm = () => {
-    return ruleFormRef.value!.validate(() => {});
-  };
-  const validateForm = () => {
-    return new Promise((resolve, reject) => {
-      ruleFormRef
-        .value!.validate(() => {})
-        .then((valid) => {
-          if (valid) {
-            const res = getChildValue();
-            emit('submitWithForm', res);
-            resolve(() => {});
-          } else {
-            reject(new Error('error submit!'));
-          }
-        });
-    });
-  };
-  const getChildValue = () => {
-    return {
-      recipientType: ruleForm.recipientType,
-      userGroupList:
-        ruleForm.recipientType === 3
-          ? []
-          : ruleForm.recipientType === 1
-          ? []
-          : ruleForm.userGroupList.map((item) => item),
-      customUserList:
-        ruleForm.recipientType === 2
-          ? []
-          : ruleForm.recipientType === 1
-          ? []
-          : ruleForm.customUserList.map((item) => item.userId),
-    };
-  };
-  const handleCancle = () => {
-    userInfo.value = false;
-  };
-  const handleSubmit = (selectedData: UserList[]) => {
-    selectedUser.value = selectedData;
-    ruleForm.customUserList = selectedUser.value;
-    userInfo.value = false;
-  };
-  const formatCustomUserList = (customList: customUserList[]): UserList[] => {
-    return customList.map((item) => ({
-      id: `u${item.userId}`,
-      userId: item.userId,
-      name: `${item.userLoginName}-${item.userNickname}`,
+import { onMounted, ref, reactive, watch, watchEffect } from 'vue';
+import SelectTree from '../persongroup/components/SelectTree.vue';
+import Group from './Group.vue';
+import { recipientTypeName } from '../constant';
+import { ToPushObjectqueryUserGroupList, queryUserGroupDetail } from '../api/index';
+import type { FormInstance } from 'element-plus';
+import { GroupData } from '../persongroup/type';
+const ruleFormRef = ref<FormInstance>();
+const groupInfo = ref<boolean>(false);
+const userInfo = ref<boolean>(false);
+const disabled = ref<boolean>(false);
+export interface customUserList {
+  userId: number;
+  userLoginName: string;
+  userNickname: string;
+  userNumber: string;
+}
+export interface userGroupVOList {
+  userGroupId: number;
+  total: number;
+  operatorName: string;
+  operationTime: string;
+  name: string;
+  description: string;
+}
+interface UserList {
+  id: string;
+  name: string;
+  userId: number;
+}
+const selectedUser = ref<UserList[]>([]);
+interface RuleForm {
+  recipientType?: number;
+  userGroupList: number[];
+  customUserList: UserList[];
+}
+const ruleForm = reactive<RuleForm>({
+  userGroupList: [],
+  customUserList: [],
+});
+const props = defineProps<{
+  recipientType?: number;
+  userGroupList?: userGroupVOList[];
+  customUserList?: customUserList[];
+  disabled?: boolean;
+}>();
+const emit = defineEmits(['submitWithForm']);
+interface Options {
+  userGroupId?: number;
+  name?: string;
+  description: string;
+  total: number;
+  operatorName: string;
+  operationTime: string;
+}
+const options = ref<Options[]>([]);
+const userGroupInfo = ref<GroupData[]>();
+const queryGroupInfo = (groupList) => {
+  groupInfo.value = true;
+  queryUserGroupDetail(groupList).then((res) => {
+    userGroupInfo.value = res.map((group) => ({
+      ...group,
+      isExpand: false,
+      isHidden: false,
     }));
-  };
-  defineExpose({
-    submitForm,
-    validateForm,
-    getChildValue,
   });
-  onMounted(() => {
-    ToPushObjectqueryUserGroupList().then((res) => {
-      options.value = res.groupVOList;
-    });
+};
+const submitForm = () => {
+  return ruleFormRef.value!.validate(() => {});
+};
+const refreshForm = () => {
+  if (!ruleFormRef.value) return;
+  ruleFormRef.value!.resetFields();
+};
+const validateForm = () => {
+  return new Promise((resolve, reject) => {
+    ruleFormRef
+      .value!.validate(() => {})
+      .then((valid) => {
+        if (valid) {
+          const res = getChildValue();
+          emit('submitWithForm', res);
+          resolve(() => {});
+        } else {
+          reject(new Error('error submit!'));
+        }
+      });
   });
-  watchEffect(() => {
-    if (props.recipientType) {
-      ruleForm.recipientType = props.recipientType;
-    }
-    if (props.userGroupList) {
-      ruleForm.userGroupList = props.userGroupList.map((item) => item.userGroupId);
-    }
-    if (props.customUserList) {
-      ruleForm.customUserList = formatCustomUserList(props.customUserList);
-      selectedUser.value = formatCustomUserList(props.customUserList);
-    }
-    if (props.disabled) {
-      disabled.value = props.disabled;
-    }
+};
+const getChildValue = () => {
+  return {
+    recipientType: ruleForm.recipientType,
+    userGroupList:
+      ruleForm.recipientType === 3
+        ? []
+        : ruleForm.recipientType === 1
+        ? []
+        : ruleForm.userGroupList.map((item) => item),
+    customUserList:
+      ruleForm.recipientType === 2
+        ? []
+        : ruleForm.recipientType === 1
+        ? []
+        : ruleForm.customUserList.map((item) => item.userId),
+  };
+};
+const handleCancle = () => {
+  userInfo.value = false;
+};
+const handleSubmit = (selectedData: UserList[]) => {
+  selectedUser.value = selectedData;
+  ruleForm.customUserList = selectedUser.value;
+  userInfo.value = false;
+};
+const formatCustomUserList = (customList: customUserList[]): UserList[] => {
+  return customList.map((item) => ({
+    id: `u${item.userId}`,
+    userId: item.userId,
+    name: `${item.userLoginName}-${item.userNickname}`,
+  }));
+};
+defineExpose({
+  submitForm,
+  refreshForm,
+  validateForm,
+  getChildValue,
+});
+onMounted(() => {
+  ToPushObjectqueryUserGroupList().then((res) => {
+    options.value = res.groupVOList;
   });
-  watch(
-    () => ruleForm.customUserList,
-    (newSelected) => {
-      selectedUser.value = newSelected;
-    },
-    { immediate: true },
-  );
+});
+watchEffect(() => {
+  if (props.recipientType) {
+    ruleForm.recipientType = props.recipientType;
+  }
+  if (props.userGroupList) {
+    ruleForm.userGroupList = props.userGroupList.map((item) => item.userGroupId);
+  }
+  if (props.customUserList) {
+    ruleForm.customUserList = formatCustomUserList(props.customUserList);
+    selectedUser.value = formatCustomUserList(props.customUserList);
+  }
+  if (props.disabled) {
+    disabled.value = props.disabled;
+  }
+});
+watch(
+  () => ruleForm.customUserList,
+  (newSelected) => {
+    selectedUser.value = newSelected;
+  },
+  { immediate: true },
+);
 </script>
 
 <style lang="scss" scoped>
-  .userGroupList {
-    margin-left: 12%;
-    width: 88%;
-    max-height: 120px;
-    padding: 12px 17px 12px 12px;
-    background: #fafafa;
-    border-radius: 4px;
-    :deep(.el-form-item) {
-      margin-bottom: 12px !important;
-    }
-    ::v-deep .el-select__selection {
-      min-height: 25px;
-      max-height: 60px;
-      overflow-y: auto;
-    }
-    span {
-      cursor: pointer;
-      margin-left: 80px;
-      font-weight: 400;
-      font-size: 10px;
-      color: #1777ff;
-      line-height: 14px;
-    }
+.userGroupList {
+  margin-left: 12%;
+  width: 88%;
+  max-height: 120px;
+  padding: 12px 17px 12px 12px;
+  background: #fafafa;
+  border-radius: 4px;
+  :deep(.el-form-item) {
+    margin-bottom: 12px !important;
+  }
+  ::v-deep .el-select__selection {
+    min-height: 25px;
+    max-height: 60px;
+    overflow-y: auto;
+  }
+  span {
+    cursor: pointer;
+    margin-left: 80px;
+    font-weight: 400;
+    font-size: 10px;
+    color: #1777ff;
+    line-height: 14px;
+  }
+}
+.customUserList {
+  margin-left: 12%;
+  width: 88%;
+  max-height: 120px;
+  padding: 12px 17px 12px 12px;
+  background: #fafafa;
+  border-radius: 4px;
+  :deep(.el-form-item) {
+    margin-bottom: 12px !important;
   }
-  .customUserList {
-    margin-left: 12%;
-    width: 88%;
-    max-height: 120px;
-    padding: 12px 17px 12px 12px;
-    background: #fafafa;
-    border-radius: 4px;
-    :deep(.el-form-item) {
-      margin-bottom: 12px !important;
-    }
-    ::v-deep .el-select__selection {
-      min-height: 25px;
-      max-height: 60px;
-      overflow-y: auto;
-    }
+  ::v-deep .el-select__selection {
+    min-height: 25px;
+    max-height: 60px;
+    overflow-y: auto;
   }
+}
 </style>

+ 1 - 0
src/views/message/designatedUserSelectTree.vue

@@ -20,6 +20,7 @@
       style="height: 583px"
       :width="731"
       :destroy-on-close="true"
+      class="workShopDialog"
     >
       <SelectTree @cancel="handleCancle" @submit="handleSubmit" :selectedUser="selectedUser" />
     </el-dialog>

+ 1 - 3
src/views/message/persongroup/components/GroupBoard.vue

@@ -117,6 +117,7 @@
       style="height: 583px"
       :width="731"
       :destroy-on-close="true"
+      class="workShopDialog"
     >
       <SelectTree @cancel="handleCancle" @submit="handleSubmit" :selectedUser="selectedUser" />
     </el-dialog>
@@ -316,7 +317,4 @@ watch(
     }
   }
 }
-::v-deep .el-dialog__body {
-  height: 527px;
-}
 </style>

+ 32 - 25
src/views/message/persongroup/components/SelectTree.vue

@@ -1,13 +1,13 @@
 <template>
   <div class="select-tree">
     <div class="left">
-    <el-form-item>
-      <el-input
-        v-model="queryStr"
-        :style="{ width: '300px', height: '30px' }"
-        placeholder="请输入搜索内容"
-        :prefix-icon="Search"
-        clearable
+      <el-form-item>
+        <el-input
+          v-model="queryStr"
+          :style="{ width: '300px', height: '30px' }"
+          placeholder="请输入搜索内容"
+          :prefix-icon="Search"
+          clearable
       /></el-form-item>
       <el-tree
         v-loading="loading"
@@ -23,7 +23,6 @@
         @check-change="handleCheckChange"
       />
     </div>
-    <!-- <el-divider direction="vertical" style="height: 100%;flex:1" /> -->
     <div class="right" style="margin-left: 16px">
       <div class="head" style="margin-bottom: 22px">
         <span
@@ -107,11 +106,14 @@ const handleSubmit = () => {
 const handleCheckChange = (node, checked) => {
   if (!node.children || (node.children.length === 0 && node.userId)) {
     if (checked) {
-      selectedPeople.value.push({
-        id: node.id,
-        name: node.name,
-        userId: node.userId,
-      });
+      const exists = selectedPeople.value.some((item) => item.id === node.id);
+      if (!exists) {
+        selectedPeople.value.push({
+          id: node.id,
+          name: node.name,
+          userId: node.userId,
+        });
+      }
     } else {
       const index = selectedPeople.value.findIndex((item) => item.id === node.id);
       if (index !== -1) {
@@ -122,13 +124,18 @@ const handleCheckChange = (node, checked) => {
   }
 };
 const handleNodeClick = (node, checked) => {
-  handleCheckChange(node, checked.checked);
-  treeRef.value!.setChecked(node.id, !checked.checked, true);
+  if (node.children.length === 0 && node.userId) {
+    treeRef.value!.setChecked(node.id, !checked.checked, true);
+  }
+  // handleCheckChange(node, checked.checked);
+  // treeRef.value!.setChecked(node.id, !checked.checked, true);
 };
 const props = defineProps<{
   selectedUser: treeSelected[];
 }>();
 onMounted(() => {
+  selectedPeople.value = cloneDeep(props.selectedUser);
+  selected.value = selectedPeople.value.length;
   queryUserTree().then((res) => {
     treeData.value = res;
     nodeData.value = formatTree(treeData.value!);
@@ -138,14 +145,14 @@ onMounted(() => {
     loading.value = false;
   });
 });
-watch(
-  () => props.selectedUser,
-  (newSelected) => {
-    selectedPeople.value = cloneDeep(newSelected);
-    selected.value = selectedPeople.value.length;
-  },
-  { immediate: true },
-);
+// watch(
+//   () => props.selectedUser,
+//   (newSelected) => {
+//     selectedPeople.value = cloneDeep(newSelected);
+//     selected.value = selectedPeople.value.length;
+//   },
+//   { immediate: true },
+// );
 watch(queryStr, (query) => {
   treeRef.value!.filter(query);
 });
@@ -155,13 +162,13 @@ watch(queryStr, (query) => {
 .select-tree {
   display: flex;
   width: 100%;
-  height: 100%;
+  height: 98%;
   .left {
     display: flex;
     flex-direction: column;
     width: 50%;
     height: 100%;
-    border-right: 1px solid rgba(0,0,0,0.06);
+    border-right: 1px solid rgba(0, 0, 0, 0.06);
     .el-tree {
       width: 100%;
       margin-top: 20px;

+ 1 - 2
src/views/message/reportmessage/api/index.ts

@@ -31,9 +31,8 @@ export interface updateStatusParams {
     status: number
 }
 export function updateStatus(params: updateStatusParams) {
-    const str = qs.stringify(params);
     return http.request({
-        url: `/reportMessage/updateStatus?${str}`,
+        url: `/reportMessage/updateStatus?statisticType=${params.statisticType}&status=${params.status}&type=${params.type}`,
         method: 'post',
     });
 }

+ 68 - 30
src/views/message/sysnotion-config/SysnotionConfig.vue

@@ -1,8 +1,11 @@
 <template>
   <div class="sysnotion-config">
     <div class="tophead">
-      <div><img src="@/views/message/reportmessage/img/rollback.png" />返回</div>
-      <span>新建系统通知</span>
+      <div @click="rollback()"
+        ><img src="@/views/message/reportmessage/img/rollback.png" />返回</div
+      >
+      <span v-if="!isDisabled">新建系统通知</span>
+      <span v-else>查看系统通知</span>
     </div>
     <div class="content">
       <div class="left">
@@ -24,6 +27,7 @@
               placeholder="请输入20字以内的消息标题"
               maxlength="20"
               show-word-limit
+              :disabled="isDisabled"
             />
           </el-form-item>
           <el-form-item label="消息内容" prop="content" class="transprant">
@@ -34,6 +38,7 @@
               :rows="5"
               maxlength="500"
               show-word-limit
+              :disabled="isDisabled"
             />
           </el-form-item>
           <el-form-item
@@ -47,6 +52,7 @@
               :key="item.value"
               :value="item.value"
               :label="item.label"
+              :disabled="isDisabled"
             />
           </el-form-item>
           <PushObject
@@ -54,14 +60,14 @@
             :recipientType="ruleForm.object.recipientType"
             :userGroupList="ruleForm.object.userGroupList"
             :customUserList="ruleForm.object.customUserList"
+            :disabled="isDisabled"
           />
           <el-form-item label="操作人" prop="operator" class="transprant">
             <el-input v-model="ruleForm.operator" :disabled="true" />
           </el-form-item>
         </el-form>
-        <div class="btns">
-          <el-button>重置</el-button>
-          <el-button>暂存</el-button>
+        <div class="btns" v-if="!isDisabled">
+          <el-button @click="refresh()">重置</el-button>
           <el-button type="primary" @click="submitForm()">确定</el-button>
         </div>
       </div>
@@ -146,42 +152,29 @@
 </template>
 
 <script lang="ts" setup>
-import { ref, reactive } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import { ref, reactive, onMounted } from 'vue';
 import { storeToRefs } from 'pinia';
+import { debounce } from 'lodash-es';
+import { ElMessage } from 'element-plus';
 import { useUserStore } from '@/store/modules/user';
 import { pushChannelName } from '../constant';
 import type { FormProps } from 'element-plus';
 import PushObject from '../components/PushObject.vue';
 import type { FormInstance } from 'element-plus';
+import { queryReportConfigListParams, queryReportConfigList, viewSystemMessage } from './api/index';
+import { ObjectFrom } from './type';
 const title = ref<string>('本系统进行了重大升级,请查看详细内容');
 const content = ref<string>(
   '尊敬的用户:\n    我们计划于2024年9月5日进行平台系统升级,以提升服务性能和用户体验,升级期间,平台将暂时不可用,预计停机时间为4小时,从上午2:00至6:00。请您提前做好相关安排,以避免不便,感谢您的理解与支持。如有疑问,请联系客服支持团队。\n敬请留意。\n天眼团队',
 );
+const isDisabled = ref<boolean>(false);
 const ruleFormRef = ref<FormInstance>();
 const childFromRef = ref();
 const validate = ref<boolean>();
 const useUser = useUserStore();
 const { info } = storeToRefs(useUser);
 const labelPosition = ref<FormProps['labelPosition']>('left');
-interface UserList {
-  userId: number;
-  userLoginName:string;
-  userNickname:string;
-  userNumber:string;
-}
-interface GroupList {
-  userGroupId:number;
-  total:number;
-  operatorName:string;
-  operationTime:string;
-  name:string;
-  description:string;
-}
-interface ObjectFrom {
-  recipientType?: number;
-  userGroupList?: GroupList[];
-  customUserList?: UserList[];
-}
 interface RuleForm {
   title: string;
   content: string;
@@ -193,25 +186,70 @@ const ruleForm = reactive<RuleForm>({
   title: '',
   content: '',
   channel: [],
-  object: {
-    userGroupList:[]
-  },
+  object: {},
   operator: info.value.nickname,
 });
 const activeName = ref('platform');
+const debounceEmit = debounce((params) => {
+  queryReportConfigList(params)
+    .then(() => {
+      ElMessage({
+        message: '下发成功!',
+        type: 'success',
+      });
+      router.back();
+    })
+    .catch((e) => console.error(e));
+}, 500);
 const submitForm = () => {
-  console.log(childFromRef.value!.getChildValue())
+  const childValue = childFromRef.value!.getChildValue();
   childFromRef.value!.submitForm().then((res) => {
     validate.value = res;
   });
   ruleFormRef.value!.validate((valid) => {
     if (validate.value && valid) {
-      console.log(ruleForm);
+      const params: queryReportConfigListParams = {
+        content: ruleForm.content,
+        title: ruleForm.title,
+        pushChannel: ruleForm.channel.map((item) => item),
+        recipientType: childValue.recipientType,
+        userGroupList: childValue.userGroupList,
+        customUserList: childValue.customUserList,
+      };
+      debounceEmit(params);
     } else {
       console.log('下发失败');
     }
   });
 };
+const refresh = () => {
+  if (!ruleFormRef.value) return;
+  ruleFormRef.value.resetFields();
+  childFromRef.value.refreshForm();
+};
+const router = useRouter();
+const rollback = () => {
+  router.back();
+};
+const route = useRoute();
+const sysId = route.query.id;
+onMounted(() => {
+  if (sysId) {
+    isDisabled.value = true;
+    viewSystemMessage(Number(sysId)).then((res) => {
+      ruleForm.title = res.title;
+      ruleForm.content = res.content ? res.content : ' ';
+      ruleForm.channel = res.pushChannel;
+      ruleForm.object.recipientType = res.recipientType;
+      if (res.recipientType === 2) {
+        ruleForm.object.userGroupList = res.userGroupList;
+      }
+      if (res.recipientType === 3) {
+        ruleForm.object.customUserList = res.customUserList;
+      }
+    });
+  }
+});
 </script>
 
 <style lang="scss" scoped>

+ 22 - 0
src/views/message/sysnotion-config/api/index.ts

@@ -0,0 +1,22 @@
+import { http } from '@/utils/http/axios';
+export interface queryReportConfigListParams {
+    content?: string;
+    title?: string;
+    pushChannel?: number[];
+    recipientType?: number;
+    userGroupList?: number[];
+    customUserList?: number[];
+}
+export function queryReportConfigList(params: queryReportConfigListParams) {
+    return http.request({
+        url: '/systemMessage/addSystemMessage',
+        method: 'post',
+        params,
+    });
+}
+export function viewSystemMessage(id: number) {
+    return http.request({
+        url: `/systemMessage/viewSystemMessage?id=${id}`,
+        method: 'get',
+    });
+}

+ 21 - 0
src/views/message/sysnotion-config/type.ts

@@ -0,0 +1,21 @@
+export interface UserList {
+    userId: number;
+    userLoginName: string;
+    userNickname: string;
+    userNumber: string;
+}
+
+export interface GroupList {
+    userGroupId: number;
+    total: number;
+    operatorName: string;
+    operationTime: string;
+    name: string;
+    description: string;
+}
+
+export interface ObjectFrom {
+    recipientType?: number;
+    userGroupList?: GroupList[];
+    customUserList?: UserList[];
+}

+ 59 - 0
src/views/message/systemNotifications/api/index.ts

@@ -0,0 +1,59 @@
+import { http } from '@/utils/http/axios';
+import { queryParams } from '../type'
+// 获取系统通知数据列表
+export function getSystemMessageList(params: queryParams) {
+    return http.request({
+        url: '/systemMessage/getSystemMessageList',
+        method: 'get',
+        params
+    });
+}
+// 删除系统消息通知
+export function deleteSystemMessage(id: number) {
+    return http.request({
+        url: `/systemMessage/deleteSystemMessage?id=${id}`,
+        method: 'post',
+    });
+}
+
+// 获取问题处理通知
+export function queryIssueProcessMessage() {
+    return http.request({
+        url: '/issueProcessMessage/queryIssueProcessMessage',
+        method: 'post',
+    });
+}
+
+// 修改推送文案
+export function modifyContent(content: string, id: number) {
+    return http.request({
+        url: `/issueProcessMessage/modifyContent?content=${content}&id=${id}`,
+        method: 'post',
+    });
+}
+
+// 获取车间列表
+export function getList() {
+    return http.request({
+        url: '/scene/getList',
+        method: 'get',
+    });
+}
+
+// 下发车间
+export function modifyWorkshopList(workshopList: number[]) {
+    return http.request({
+        url: '/issueProcessMessage/modifyWorkshopList',
+        method: 'post',
+        params: { workshopList }
+    });
+}
+
+// 查询车间姓名
+export function queryWorkshopNamebyIds(workshopList: number[]) {
+    return http.request({
+        url: '/issueProcessMessage/queryWorkshopNamebyIds',
+        method: 'post',
+        params: { workshopList }
+    });
+}

+ 42 - 0
src/views/message/systemNotifications/components/SearchBar.vue

@@ -0,0 +1,42 @@
+<template>
+  <div class="searchBar">
+    <el-space alignment="center" :size="30">
+      <el-input
+        v-model="content"
+        style="width: 294px; height: 32px"
+        placeholder="请输入搜索内容"
+        :prefix-icon="Search"
+      />
+    </el-space>
+    <div>
+      <el-button type="primary" class="allBtn" @click="getSysNotion()"> 搜索 </el-button>
+      <el-button class="allBtn" @click="resetSearch"> 重置 </el-button>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { Search } from '@element-plus/icons-vue';
+import { storeToRefs } from 'pinia';
+import useSysNotion from '../store/index';
+const sysNotion = useSysNotion();
+const { content } = storeToRefs(sysNotion);
+const { getSysNotion } = sysNotion;
+
+const resetSearch = () => {
+  content.value = '';
+  getSysNotion();
+};
+</script>
+
+<style scoped>
+.searchBar {
+  display: flex;
+}
+
+.allBtn {
+  margin-left: 15px;
+  width: 65px;
+  height: 32px;
+}
+</style>

+ 242 - 0
src/views/message/systemNotifications/components/WorkShopTree.vue

@@ -0,0 +1,242 @@
+<template>
+  <div class="select-tree">
+    <div class="left">
+      <el-form-item>
+        <el-input
+          v-model="queryStr"
+          :style="{ width: '300px', height: '30px' }"
+          placeholder="请输入搜索内容"
+          :prefix-icon="Search"
+          clearable
+      /></el-form-item>
+      <el-tree
+        ref="treeRef"
+        v-loading="loading"
+        :data="nodeData"
+        show-checkbox
+        node-key="code"
+        :props="defaultProps"
+        :render-after-expand="false"
+        :filter-node-method="filterNode"
+        accordion
+        @node-click="handleNodeClick"
+        @check-change="handleCheckChange"
+      />
+    </div>
+    <div class="right" style="margin-left: 16px">
+      <div class="head" style="margin-bottom: 22px">
+        <span
+          style="font-weight: 400; font-size: 16px; color: rgba(0, 0, 0, 0.88); line-height: 22px"
+          >已选择({{ selected }}</span
+        >
+        <span
+          style="
+            margin-left: 4px;
+            font-size: 10px;
+            font-weight: 400;
+            color: rgba(0, 0, 0, 0.45);
+            line-height: 22px;
+          "
+          >/&nbsp;{{ total }})</span
+        >
+      </div>
+      <div class="selected">
+        <el-tag
+          v-for="(person, index) in selectedPeople"
+          :key="index"
+          closable
+          @close="handleTagClose(person.code)"
+        >
+          {{ person.name }}
+        </el-tag>
+      </div>
+      <div class="footer">
+        <el-button @click="handleCancle"> 取消 </el-button>
+        <el-button type="primary" @click="handleSubmit">提交</el-button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { onMounted, ref, watch, computed } from 'vue';
+import { Search } from '@element-plus/icons-vue';
+import { ElTree } from 'element-plus';
+import { countLeafNodes } from '@/views/message/persongroup/hook/index';
+import { getList } from '../api/index';
+import useScene from '@/views/system-config/scene-manage/use-scene';
+import { storeToRefs } from 'pinia';
+import { cloneDeep } from 'lodash-es';
+import { TreeKey } from 'element-plus/es/components/tree/src/tree.type';
+const sceneInfos = useScene();
+const { tableData } = storeToRefs(sceneInfos);
+const loading = ref(true);
+const queryStr = ref<string>('');
+const defaultProps = { label: 'name' };
+const nodeData = computed(() => {
+  const newList: any[] = [];
+  if (tableData.value && tableData.value.length) {
+    for (let i = 0; i < tableData.value.length; i++) {
+      const data = tableData.value[i];
+      if (data.children && data.children.length) {
+        const treeItem = {
+          id: data.id,
+          code: data.id,
+          name: data.name,
+          children: data
+            .labelList!.map((item) => {
+              return {
+                id: item.id,
+                code: item.code,
+                name: item.name,
+                children: data
+                  .children!.filter((children) => children.sceneLabelId === item.id)
+                  .map((children) => {
+                    return {
+                      id: children.id,
+                      code: children.code,
+                      name: children.name,
+                      isShop: true,
+                    };
+                  }),
+              };
+            })
+            .filter((label) => label.children.length),
+        };
+        newList.push(treeItem);
+      }
+    }
+  }
+  return newList;
+});
+const filterNode = (query, nodeData, node) => {
+  if (!query) return true;
+  nodeData.filter = nodeData.name!.includes(query);
+  if (!nodeData.filter && node.level > 1) {
+    nodeData.filter = node.parent.data.filter;
+  }
+  return nodeData.filter!;
+};
+const treeRef = ref<InstanceType<typeof ElTree>>();
+const total = ref<number>(0);
+const selected = ref<number>(0);
+interface treeSelected {
+  code: number | string;
+  name: string;
+  id: number;
+}
+const selectedPeople = ref<treeSelected[]>([]);
+const handleTagClose = (code) => {
+  const index = selectedPeople.value.findIndex((item) => item.code === code);
+  if (index !== -1) {
+    selectedPeople.value.splice(index, 1);
+    selected.value = selectedPeople.value.length;
+    treeRef.value!.setChecked(code, false, true);
+  }
+};
+const emit = defineEmits(['cancel', 'submit']);
+const handleCancle = () => {
+  emit('cancel');
+};
+const handleSubmit = () => {
+  emit('submit', selectedPeople.value);
+};
+const handleCheckChange = (node, checked) => {
+  if (!node.children) {
+    if (checked) {
+      const exists = selectedPeople.value.some((item) => item.code === node.code);
+      if (!exists) {
+        selectedPeople.value.push({
+          id: node.id,
+          code: node.code,
+          name: node.name,
+        });
+      }
+    } else {
+      const index = selectedPeople.value.findIndex((item) => item.code === node.code);
+      if (index !== -1) {
+        selectedPeople.value.splice(index, 1);
+      }
+    }
+    selected.value = selectedPeople.value.length;
+  }
+};
+const handleNodeClick = (node, checked) => {
+  if (!node.children) {
+    treeRef.value!.setChecked(node.code, !checked.checked, true);
+  }
+};
+const props = defineProps<{
+  selectedUser: treeSelected[];
+}>();
+onMounted(() => {
+  if (props.selectedUser) {
+    selectedPeople.value = cloneDeep(props.selectedUser);
+  }
+  selected.value = selectedPeople.value.length;
+  getList().then((res) => {
+    tableData.value = res;
+    total.value = countLeafNodes(nodeData.value);
+    const selectedIds: TreeKey[] = selectedPeople.value.map((item) => item.code);
+    treeRef.value!.setCheckedKeys(selectedIds);
+    loading.value = false;
+  });
+});
+watch(queryStr, (query) => {
+  treeRef.value!.filter(query);
+});
+</script>
+
+<style lang="scss" scoped>
+.select-tree {
+  display: flex;
+  width: 100%;
+  height: 100%;
+  .left {
+    display: flex;
+    flex-direction: column;
+    width: 50%;
+    height: 100%;
+    border-right: 1px solid rgba(0, 0, 0, 0.06);
+    .el-tree {
+      width: 100%;
+      margin-top: 20px;
+      font-weight: 500;
+      font-size: 14px;
+      color: #303133;
+      line-height: 22px;
+      overflow-y: auto;
+      max-height: calc(100% - 80px);
+    }
+  }
+  .right {
+    display: flex;
+    flex-direction: column;
+    flex: 1;
+    height: 100%;
+    position: relative;
+    .head {
+      display: flex;
+      align-items: center;
+      font-weight: 400;
+      font-size: 16px;
+      color: rgba(0, 0, 0, 0.88);
+      line-height: 22px;
+    }
+    .selected {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 8px;
+      overflow-y: auto;
+      max-height: calc(100% - 120px);
+    }
+    .footer {
+      display: flex;
+      gap: 6px;
+      position: absolute;
+      right: 24px;
+      bottom: 20px;
+    }
+  }
+}
+</style>

+ 332 - 0
src/views/message/systemNotifications/components/problemHandleTable.vue

@@ -0,0 +1,332 @@
+<template>
+  <div>
+    <div style="padding-bottom: 20px; border-bottom: 1px solid rgba(0, 0, 0, 0.06)">
+      <div class="pushWorkShopBar">
+        <span class="pushiWorkShopSpan">推送车间</span>
+        <img src="../img/edit.png" @click="handleWorkShopEdit()" />
+        <span class="descriptionSpan">请点击编辑按钮,选择需要发送问题处理通知</span>
+      </div>
+      <div class="workshopList">
+        <div class="left" ref="listLeft">
+          <el-tag type="primary" v-for="item in selectedUser" :key="item.code">
+            {{ item.name }}
+          </el-tag>
+        </div>
+        <div v-if="!isExpandShow" class="right" @click="toExpand()">
+          <div
+            style="display: flex; height: 24px; justify-content: center; align-items: center"
+            v-if="isExpand"
+          >
+            <span>展开</span><img src="@/assets/icons/arrow_bottom.png" />
+          </div>
+          <div
+            style="display: flex; height: 24px; justify-content: center; align-items: center"
+            v-else
+          >
+            <span>收起</span><img src="@/assets/icons/arrow_top.png" />
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <el-table
+      :data="problemTableData"
+      stripe
+      height="calc(100vh - 400px)"
+      :cell-style="{ textAlign: 'center' }"
+      :header-cell-style="{ 'text-align': 'center' }"
+      style="width: 100%; margin-top: 16px; --el-table-border-color: none"
+    >
+      <el-table-column width="156" prop="issuePhase" label="问题阶段" />
+
+      <el-table-column width="176" prop="issueState" label="审核状态">
+        <template #default="scope">
+          <el-tag type="warning" v-if="scope.row.issueState === 1">
+            {{ issueStateMapping[scope.row.issueState] }}
+          </el-tag>
+          <el-tag type="danger" v-else-if="scope.row.issueState === 6">
+            {{ issueStateMapping[scope.row.issueState] }}
+          </el-tag>
+          <el-tag type="success" v-else-if="scope.row.issueState === 7">
+            {{ issueStateMapping[scope.row.issueState] }}
+          </el-tag>
+          <el-tag type="primary" v-else>{{ issueStateMapping[scope.row.issueState] }}</el-tag>
+        </template>
+      </el-table-column>
+
+      <el-table-column width="156" prop="recipient" label="推送对象" />
+
+      <el-table-column width="520" prop="content" label="推送文案" />
+
+      <el-table-column label="操作" fixed="right">
+        <template #default="scope">
+          <div class="operation">
+            <el-tooltip class="box-item" effect="light" content="编辑" placement="bottom">
+              <img
+                src="../img/edit.png"
+                @click="handleProbleEdit(scope.row.id, scope.row.content)"
+              />
+            </el-tooltip>
+          </div>
+        </template>
+      </el-table-column>
+
+      <template #empty>
+        <div class="emptyDiv">
+          <img src="../img/empty.png" class="emptyImg" />
+          <span class="emptySpan">暂无数据</span>
+        </div>
+      </template>
+    </el-table>
+    <el-dialog
+      v-model="workDialog"
+      title="添加推送车间"
+      align-center
+      :close-on-click-modal="false"
+      style="height: 583px"
+      :width="731"
+      :destroy-on-close="true"
+      class="workShopDialog"
+    >
+      <WorkShopTree @cancel="handleCancle" @submit="handleSubmit" :selectedUser="selectedUser" />
+    </el-dialog>
+    <el-dialog
+      v-model="showDialog"
+      title="请输入推送文案"
+      align-center="true"
+      width="400"
+      @close="closeDialog()"
+      :close-on-click-modal="false"
+      class="contentDialog"
+    >
+      <el-input
+        v-model="content"
+        style="width: 380px; padding: 0 65px 0 0"
+        :autosize="{ minRows: 1, maxRows: 4 }"
+        maxlength="100"
+        show-word-limit
+        type="textarea"
+      />
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="closeDialog()">取消</el-button>
+          <el-button type="primary" @click="submitDialog()">确认</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+<script lang="ts" setup>
+import { ref, onMounted, nextTick } from 'vue';
+import { problemPhase, issueStateMapping } from '../type';
+import {
+  queryIssueProcessMessage,
+  modifyContent,
+  modifyWorkshopList,
+  queryWorkshopNamebyIds,
+} from '../api/index';
+import WorkShopTree from './WorkShopTree.vue';
+import { ElMessage } from 'element-plus';
+const problemTableData = ref<problemPhase[]>([]);
+const workDialog = ref<boolean>(false);
+const showDialog = ref<boolean>(false);
+const content = ref<string>('');
+const listLeft = ref();
+const isExpandShow = ref<boolean>(false);
+const isExpand = ref<boolean>(true);
+const toExpand = () => {
+  isExpand.value = !isExpand.value;
+  if (isExpand.value) {
+    listLeft.value.style.maxHeight = '24px';
+  } else {
+    listLeft.value.style.maxHeight = 'none';
+  }
+};
+const editId = ref<number>(0);
+const handleWorkShopEdit = () => {
+  workDialog.value = true;
+};
+const handleProbleEdit = (id: number, _content: string) => {
+  showDialog.value = true;
+  editId.value = id;
+  content.value = _content;
+};
+
+const submitDialog = () => {
+  modifyContent(content.value, editId.value)
+    .then(() => {
+      ElMessage({
+        message: '修改成功',
+        type: 'success',
+        plain: true,
+      });
+      closeDialog();
+      queryIssueData();
+    })
+    .catch((error) => {
+      console.error(error);
+    });
+};
+
+const closeDialog = () => {
+  showDialog.value = false;
+};
+interface UserList {
+  code: number | string;
+  name: string;
+  id: number;
+}
+const selectedUser = ref<UserList[]>([]);
+const queryIssueData = () => {
+  queryIssueProcessMessage().then((res) => {
+    let params;
+    if (res[0].workshopList) {
+      params = JSON.parse(res[0].workshopList);
+    } else {
+      params = [];
+    }
+    queryWorkshopNamebyIds(params).then((res) => {
+      selectedUser.value = res;
+      nextTick(() => {
+        const height = listLeft.value.scrollHeight;
+        isExpandShow.value = height > 24 ? false : true;
+      });
+    });
+    problemTableData.value = res;
+  });
+};
+const handleCancle = () => {
+  workDialog.value = false;
+};
+const handleSubmit = (selectedData: UserList[]) => {
+  const params = selectedData.map((item) => item.id);
+  modifyWorkshopList(params).then(() => {
+    ElMessage({
+      message: '添加成功',
+      type: 'success',
+      plain: true,
+    });
+    workDialog.value = false;
+    queryIssueData();
+  });
+};
+onMounted(() => {
+  queryIssueData();
+});
+</script>
+<style lang="scss" scoped>
+.pushWorkShopBar {
+  margin: 24px 0 0 0;
+  display: flex;
+  img {
+    cursor: pointer;
+  }
+  :nth-of-type(1) {
+    margin-right: 12px;
+    margin-bottom: 16px;
+  }
+  .pushiWorkShopSpan {
+    width: 56px;
+    height: 20px;
+    font-weight: 600;
+    font-size: 14px;
+    color: #303133;
+    line-height: 20px;
+  }
+  .descriptionSpan {
+    width: 250px;
+    height: 14px;
+    font-weight: 400;
+    font-size: 12px;
+    color: #999999;
+    line-height: 20px;
+  }
+}
+.workshopList {
+  display: flex;
+  .left {
+    display: flex;
+    flex-wrap: wrap;
+    width: 95%;
+    max-height: 24px;
+    overflow-y: hidden;
+    gap: 20px;
+  }
+  .right {
+    flex: 1;
+    cursor: pointer;
+    span {
+      font-weight: 400;
+      font-size: 12px;
+      color: #303133;
+      line-height: 17px;
+    }
+    img {
+      margin-left: 4px;
+    }
+  }
+}
+.emptyDiv {
+  margin-top: 78px;
+  margin: auto;
+  width: 396px;
+  .emptyImg {
+    height: 257px;
+  }
+  .emptySpan {
+    font-family: PingFangSC, PingFang SC;
+    font-weight: 400;
+    font-size: 18px;
+    color: rgba(0, 0, 0, 0.45);
+    text-align: left;
+    font-style: normal;
+  }
+}
+.operation {
+  display: flex;
+  justify-content: center;
+  width: 100%;
+}
+
+// .editWorkShopDialog {
+//   .editWorkShopHeader {
+//     width: 96px;
+//     height: 24px;
+//     .titleSpan {
+//       font-weight: 600;
+//       font-size: 16px;
+//       color: rgba(0, 0, 0, 0.88);
+//       line-height: 24px;
+//     }
+//   }
+
+//   .editWorkShopDialogLeft {
+//     height: 523px;
+//     width: 50%;
+//     border-right: 1px solid rgba(0, 0, 0, 0.06);
+//   }
+
+//   .editWorkShopDialogRight {
+//     height: 523px;
+//     width: 50%;
+//     position: relative;
+//     .dialogBottom {
+//       position: absolute;
+//       right: 0;
+//       bottom: 0;
+//       display: flex;
+//       justify-content: flex-end;
+//       margin-top: 12px;
+//     }
+//   }
+// }
+
+// :deep(.el-dialog) {
+//   border-radius: 8px;
+// }
+// :deep(.el-dialog__body) {
+//   height: 523px;
+//   border-radius: 8px;
+//   display: flex;
+// }
+</style>

+ 216 - 0
src/views/message/systemNotifications/components/systemNotificationTable.vue

@@ -0,0 +1,216 @@
+<template>
+  <SearchBar />
+
+  <el-button
+    type="primary"
+    @click="createNotification"
+    style="margin-top: 24px; margin-bottom: 16px; width: 138px"
+  >
+    <img src="../img/create.png" style="margin-top: -1px; margin-right: 5px" />新建系统通知
+  </el-button>
+
+  <el-table
+    :data="sysNotionData"
+    stripe
+    height="calc(100vh - 400px)"
+    :cell-style="{ textAlign: 'center' }"
+    :header-cell-style="{ 'text-align': 'center' }"
+    style="width: 100%; margin-top: 16px; --el-table-border-color: none"
+  >
+    <el-table-column width="380" prop="title" label="消息标题" />
+
+    <el-table-column width="200" prop="pushChannel" label="推送渠道">
+      <template #default="scope">
+        <div>
+          {{ scope.row.pushChannel.map((channel) => pushChannelMapping[channel]).join(',') }}
+        </div>
+      </template>
+    </el-table-column>
+
+    <el-table-column width="200" prop="recipientType" label="推送对象">
+      <template #default="scope">
+        <div>
+          {{ recipientTypeMapping[scope.row.recipientType] }}
+        </div>
+      </template>
+    </el-table-column>
+
+    <el-table-column width="135" prop="status" label="推送状态">
+      <template #default="scope">
+        <div class="pushstatus" v-if="scope.row.status === 0"
+          ><img src="../img//prepareToPush.png" alt=""
+        /></div>
+        <div class="pushstatus" v-else><img src="../img/completed.png" alt="" /></div>
+      </template>
+    </el-table-column>
+
+    <el-table-column prop="pushAt" label="推送时间" />
+
+    <el-table-column label="操作" fixed="right">
+      <template #default="scope">
+        <div class="operation">
+          <el-tooltip class="box-item" effect="light" content="查看" placement="bottom">
+            <img src="../img/view.png" @click="handleView(scope.row.id)" />
+          </el-tooltip>
+          <el-tooltip class="box-item" effect="light" content="删除" placement="bottom">
+            <img src="../img/delete.png" @click="handleDelete(scope.row.id)" />
+          </el-tooltip>
+        </div>
+      </template>
+    </el-table-column>
+
+    <template #empty>
+      <div class="emptyDiv">
+        <img src="../img/empty.png" class="emptyImg" />
+        <span class="emptySpan">暂无数据</span>
+      </div>
+    </template>
+  </el-table>
+
+  <div class="pagination" v-if="total != 0">
+    <el-pagination
+      :page-sizes="[10, 20, 30, 40, 50, 80]"
+      :small="false"
+      :background="true"
+      layout="total, sizes, prev, pager, next, jumper"
+      :total="total"
+      @size-change="handleSizeChange"
+      @current-change="handleCurrentChange"
+    />
+  </div>
+
+  <el-dialog v-model="deleteDialog" width="424px" top="20%" class="deleteDialog">
+    <template #header="">
+      <div class="deleteDialogHeader">
+        <img src="../img/deleteTip.png" class="deleteTip" />
+        <span class="titleSpan">请确认是否删除</span>
+      </div>
+    </template>
+    <span style="margin-left: 37px">删除之后,该条数据将无法恢复</span>
+    <div class="dialogBottom">
+      <el-button class="dialogBtn" @click="deleteDialog = false">取消</el-button>
+      <el-button class="dialogBtn" type="primary" @click="confirmDelete">确定</el-button>
+    </div>
+  </el-dialog>
+</template>
+<script lang="ts" setup>
+import { ref, onMounted } from 'vue';
+import { useRouter } from 'vue-router';
+import { pushChannelMapping, recipientTypeMapping, pushStatusMapping } from '../type';
+import SearchBar from './SearchBar.vue';
+import { storeToRefs } from 'pinia';
+import useSysNotion from '../store/index';
+import { deleteSystemMessage } from '../api/index';
+import { ElMessage } from 'element-plus';
+const sysNotion = useSysNotion();
+const { total, page, pagesize, sysNotionData } = storeToRefs(sysNotion);
+const { getSysNotion } = sysNotion;
+const deleteDialog = ref(false);
+const router = useRouter();
+
+const createNotification = () => {
+  router.push('/message/sysnotion-config');
+};
+
+const handleView = (id: number) => {
+  router.push(`/message/sysnotion-config?id=${id}`);
+};
+const deleteId = ref<number>(0);
+const handleDelete = (id: number) => {
+  deleteDialog.value = true;
+  deleteId.value = id;
+};
+
+const confirmDelete = () => {
+  deleteSystemMessage(deleteId.value).then(() => {
+    ElMessage({
+      message: '删除成功',
+      type: 'success',
+      plain: true,
+    });
+    deleteDialog.value = false;
+    getSysNotion();
+  });
+};
+
+const handleSizeChange = (newPageSize: number) => {
+  pagesize.value = newPageSize;
+  getSysNotion();
+};
+
+const handleCurrentChange = (newCurrentPage: number) => {
+  page.value = newCurrentPage;
+  getSysNotion();
+};
+
+onMounted(() => {
+  getSysNotion();
+});
+</script>
+<style lang="scss" scoped>
+.pagination {
+  position: absolute;
+  bottom: 35px;
+  right: 67px;
+}
+
+.deleteDialog {
+  .deleteDialogHeader {
+    display: flex;
+    .deleteTip {
+      height: 24px;
+      width: 24px;
+    }
+    .titleSpan {
+      height: 24px;
+      font-size: 16px;
+      color: rgba(0, 0, 0, 0.88);
+      line-height: 24px;
+      text-align: center;
+      margin-left: 12px;
+    }
+  }
+  .dialogBottom {
+    display: flex;
+    justify-content: flex-end;
+    margin-top: 12px;
+  }
+}
+
+.operation {
+  display: flex;
+  justify-content: center;
+  /* :first-child {
+      margin-right: 20px;
+    } */
+  :nth-child(2) {
+    margin-left: 20px;
+  }
+}
+
+.emptyDiv {
+  margin-top: 78px;
+  margin: auto;
+  width: 396px;
+  .emptyImg {
+    height: 257px;
+  }
+  .emptySpan {
+    font-family: PingFangSC, PingFang SC;
+    font-weight: 400;
+    font-size: 18px;
+    color: rgba(0, 0, 0, 0.45);
+    text-align: left;
+    font-style: normal;
+  }
+}
+
+:deep(.el-dialog) {
+  border-radius: 8px;
+}
+.pushstatus {
+  display: flex;
+  justify-content: center;
+  width: 100%;
+}
+</style>

BIN
src/views/message/systemNotifications/img/completed.png


BIN
src/views/message/systemNotifications/img/create.png


BIN
src/views/message/systemNotifications/img/delete.png


BIN
src/views/message/systemNotifications/img/deleteTip.png


BIN
src/views/message/systemNotifications/img/edit.png


BIN
src/views/message/systemNotifications/img/empty.png


BIN
src/views/message/systemNotifications/img/prepareToPush.png


BIN
src/views/message/systemNotifications/img/search.png


BIN
src/views/message/systemNotifications/img/view.png


+ 33 - 0
src/views/message/systemNotifications/store/index.ts

@@ -0,0 +1,33 @@
+import { ref } from 'vue'
+import { defineStore } from "pinia";
+import { systemTableData, queryParams } from '../type'
+import { getSystemMessageList } from '../api/index'
+import { useRequest } from 'vue-hooks-plus';
+export const useSysNotion = defineStore('sys-notion', () => {
+    const total = ref<number>(0)
+    const page = ref<number>(1);
+    const pagesize = ref<number>(10);
+    const content = ref<string>('');
+    const sysNotionData = ref<systemTableData[]>([]);
+    const conditionSearch = () => {
+        const params: queryParams = {
+            pageNumber: page.value,
+            pageSize: pagesize.value,
+        };
+        if (content.value) {
+            params.content = content.value
+        }
+        return getSystemMessageList(params).then((res) => {
+            return res;
+        });
+    }
+    const { run: getSysNotion } = useRequest(conditionSearch, {
+        manual: true,
+        onSuccess: (res) => {
+            sysNotionData.value = res.records;
+            total.value = res.totalRow;
+        },
+    });
+    return { total, page, pagesize, content, sysNotionData, getSysNotion }
+})
+export default useSysNotion;

+ 76 - 0
src/views/message/systemNotifications/systemNotifications.vue

@@ -0,0 +1,76 @@
+<template>
+  <div class="notificationPage">
+    <div class="notificationSelectionBar">
+      <div
+        :class="
+          currentNotification === notificationType.system ? 'notificationSelected' : 'notification'
+        "
+        @click="changeNotification(notificationType.system)"
+        >系统消息通知</div
+      >
+      <div
+        :class="
+          currentNotification === notificationType.problemHandle
+            ? 'notificationSelected'
+            : 'notification'
+        "
+        @click="changeNotification(notificationType.problemHandle)"
+        >问题处理通知</div
+      >
+    </div>
+
+    <systemNotificationTable v-if="currentNotification === notificationType.system" />
+    <problemHandleTable v-if="currentNotification === notificationType.problemHandle" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { ref } from 'vue';
+  import systemNotificationTable from './components/systemNotificationTable.vue';
+  import problemHandleTable from './components/problemHandleTable.vue';
+
+  enum notificationType {
+    system,
+    problemHandle,
+  }
+  const currentNotification = ref(notificationType.system);
+
+  const changeNotification = (notificationType: notificationType) => {
+    currentNotification.value = notificationType;
+  };
+</script>
+
+<style lang="scss" scoped>
+  .notificationPage {
+    height: calc(100vh - 64px - 18px);
+    background-color: rgba(255, 255, 255, 1);
+    padding: 24px 44px 35px 21px;
+    position: relative;
+  }
+
+  .notificationSelectionBar {
+    display: flex;
+    width: 376px;
+    height: 38px;
+    border: 1px solid #d9d9d9;
+    border-radius: 4px;
+    background: rgba(0, 0, 0, 0.02);
+    margin-bottom: 24px;
+    cursor: pointer;
+    .notification {
+      width: 50%;
+      border-radius: 4px;
+      background: rgba(0, 0, 0, 0.02);
+      line-height: 36px;
+      text-align: center;
+    }
+    .notificationSelected {
+      width: 50%;
+      outline: 1px solid #1890ff;
+      border-radius: 4px;
+      background: rgba(24, 144, 255, 0.15);
+      line-height: 36px;
+      text-align: center;
+    }
+  }
+</style>

+ 128 - 0
src/views/message/systemNotifications/type.ts

@@ -0,0 +1,128 @@
+export interface systemTableData {
+    id: number,
+    // 消息标题
+    title: string,
+    // 推送渠道
+    pushChannel: number[],
+    // 推送对象
+    recipientType: number,
+    // 推送状态
+    status: number,
+    // 推送时间
+    pushAt: string
+}
+export interface queryParams {
+    content?: string;
+    pageNumber: number;
+    pageSize: number;
+}
+
+export enum pushChannelEnum {
+    platform = 1,
+    lanxin = 2,
+}
+
+export const pushChannelMapping = {
+    [pushChannelEnum.platform]: '平台',
+    [pushChannelEnum.lanxin]: '蓝信',
+};
+
+
+export enum recipientTypeEnum {
+    all = 1,
+    byGroup = 2,
+    custom = 3
+}
+
+export const recipientTypeMapping = {
+    [recipientTypeEnum.all]: '全员',
+    [recipientTypeEnum.byGroup]: '分组',
+    [recipientTypeEnum.custom]: '自定义',
+};
+
+export enum pushStatus {
+    completed = 1,
+    prepareToPush = 0
+}
+
+export const pushStatusMapping = {
+    [pushStatus.completed]: '推送完成',
+    [pushStatus.prepareToPush]: '等待推送',
+};
+
+
+export interface systemInfoRes {
+    records: systemMessage[],
+    pageNumber: number,
+    pageSize: number,
+    totalPage: number,
+    totalRow: number
+}
+
+
+export interface systemMessage {
+    id: number,
+    // 消息标题
+    messagType: string,
+    // 推送渠道
+    pushChannel: string,
+    // 推送对象
+    recipientType: recipientTypeEnum,
+    // 推送状态
+    pushStatus: pushStatus,
+    // 推送时间
+    pushTime: string
+}
+
+
+export interface problemInfoRes {
+    records: problemPhase[],
+    pageNumber: number,
+    pageSize: number,
+    totalPage: number,
+    totalRow: number
+}
+
+export interface problemPhase {
+    id: number,
+    //推送内容
+    content: string;
+    //车间id
+    workshopList: number[];
+    //问题状态
+    issuePhase: string;
+    //问题状态
+    issueState: number;
+    //接收对象
+    recipient: string;
+    //创建时间
+    createdAt: string;
+    //更新时间
+    updatedAt: string;
+    //是否删除
+    isDeleted: number;
+}
+
+enum issueStateEnum {
+    tobeSubmitted = 0,
+    tobeReviewed = 1,
+    tobeIssued = 2,
+    revoked = 3,
+    pending = 4,
+    review = 5,
+    process = 6,
+    reviewSuccess = 7,
+    closed = 8
+}
+
+export const issueStateMapping = {
+    [issueStateEnum.tobeSubmitted]: '待提交',
+    [issueStateEnum.tobeReviewed]: '待审核',
+    [issueStateEnum.tobeIssued]: '待下发',
+    [issueStateEnum.revoked]: '已撤消',
+    [issueStateEnum.pending]: '待处理',
+    [issueStateEnum.review]: '待复核',
+    [issueStateEnum.process]: '复核未通过',
+    [issueStateEnum.reviewSuccess]: '复核通过',
+    [issueStateEnum.closed]: '已关闭'
+};