chauncey 1 год назад
Родитель
Сommit
08150a47d9
28 измененных файлов с 1679 добавлено и 0 удалено
  1. BIN
      src/assets/images/reportmessage/delete.png
  2. BIN
      src/assets/images/reportmessage/edit.png
  3. BIN
      src/assets/images/reportmessage/log.png
  4. BIN
      src/assets/images/reportmessage/view.png
  5. 0 0
      src/main.css
  6. 1 0
      src/main.ts
  7. 60 0
      src/views/message/SelectTree.vue
  8. 12 0
      src/views/message/constant.ts
  9. 265 0
      src/views/message/persongroup/UserGroup.vue
  10. 63 0
      src/views/message/persongroup/api/index.ts
  11. 316 0
      src/views/message/persongroup/components/GroupBoard.vue
  12. 36 0
      src/views/message/persongroup/components/Search.vue
  13. 219 0
      src/views/message/persongroup/components/SelectTree.vue
  14. 60 0
      src/views/message/persongroup/hook/index.ts
  15. BIN
      src/views/message/persongroup/img/create.png
  16. BIN
      src/views/message/persongroup/img/refresh.png
  17. BIN
      src/views/message/persongroup/img/search.png
  18. 53 0
      src/views/message/persongroup/overviewColumns.ts
  19. 34 0
      src/views/message/persongroup/store/index.ts
  20. 58 0
      src/views/message/persongroup/type.ts
  21. 65 0
      src/views/message/reportmessage/ReportMessage.vue
  22. 40 0
      src/views/message/reportmessage/api/index.ts
  23. 146 0
      src/views/message/reportmessage/components/Form.vue
  24. 78 0
      src/views/message/reportmessage/components/LogForm.vue
  25. 13 0
      src/views/message/reportmessage/constant.ts
  26. 104 0
      src/views/message/reportmessage/overviewColumns.ts
  27. 16 0
      src/views/message/reportmessage/store/useFormList.ts
  28. 40 0
      src/views/message/reportmessage/type.ts

BIN
src/assets/images/reportmessage/delete.png


BIN
src/assets/images/reportmessage/edit.png


BIN
src/assets/images/reportmessage/log.png


BIN
src/assets/images/reportmessage/view.png


+ 0 - 0
src/main.css


+ 1 - 0
src/main.ts

@@ -4,6 +4,7 @@ import './styles/index.scss';
 import 'element-plus/theme-chalk/display.css';
 import 'element-plus/theme-chalk/dark/css-vars.css';
 import 'nprogress/nprogress.css';
+import './main.css'
 import VueKonva from 'vue-konva';
 
 import { createApp } from 'vue';

+ 60 - 0
src/views/message/SelectTree.vue

@@ -0,0 +1,60 @@
+<template>
+  <div>
+    <el-select
+      v-model="userList"
+      value-key="id"
+      multiple
+      placeholder="请选择人员"
+      @click="dialogVisible = true"
+    >
+      <el-option v-for="user in selectedUser" :key="user.id" :label="user.name" :value="user">
+      </el-option>
+    </el-select>
+    <el-dialog
+      v-model="dialogVisible"
+      title="添加组内成员"
+      align-center
+      :close-on-click-modal="false"
+      style="height: 583px"
+      :width="731"
+      :destroy-on-close="true"
+    >
+      <SelectTree @cancel="handleCancle" @submit="handleSubmit" :selectedUser="selectedUser" />
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, watch } from 'vue';
+import SelectTree from './persongroup/components/SelectTree.vue';
+interface UserList {
+  id: string;
+  name: string;
+  userId: number;
+}
+const dialogVisible = ref<boolean>(false);
+const userList = ref<UserList[]>([]);
+const selectedUser = ref<UserList[]>([]);
+const handleCancle = () => {
+  dialogVisible.value = false;
+};
+const handleSubmit = (selectedData: UserList[]) => {
+  selectedUser.value = selectedData;
+  userList.value = selectedData;
+  dialogVisible.value = false;
+};
+watch(
+  () => userList.value,
+  (newUserList) => {
+    const ids = newUserList.map((user) => user.id);
+    selectedUser.value = selectedUser.value.filter((user) => ids.includes(user.id));
+  },
+  { immediate: true },
+);
+</script>
+
+<style lang="scss" scoped>
+::v-deep .el-dialog__body {
+  height: 527px;
+}
+</style>

+ 12 - 0
src/views/message/constant.ts

@@ -0,0 +1,12 @@
+export const typeName = [
+    { value: 1, label: "平台违规报警" },
+    { value: 2, label: "平台访问统计" },
+    { value: 3, label: "人员访问数据" },
+];
+export const statisticTypeName = [
+    { value: 1, label: "周报" },
+    { value: 2, label: "月报" },
+    { value: 3, label: "季报" },
+    { value: 4, label: "年报" },
+    { value: 5, label: "自定义" },
+];

+ 265 - 0
src/views/message/persongroup/UserGroup.vue

@@ -0,0 +1,265 @@
+<template>
+  <div class="user-group">
+    <Search />
+    <div class="user-list">
+      <BasicTable
+        :columns="userGroupCol"
+        :data-source="userListData"
+        :row-key="(row) => row.id"
+        :action-column="actionColumn"
+        :pagination="{
+          total: total,
+          pageSize: pagesize,
+          currentPage: page,
+          hideOnSinglePage: !userListData,
+        }"
+        :tableSetting="{
+          size: false,
+          redo: false,
+          fullscreen: false,
+          striped: false,
+          setting: false,
+        }"
+        :striped="true"
+        ref="tableRef"
+        @page-num-change="handlePageNumChange"
+        @page-size-change="handlePageSizeChange"
+      >
+        <template #tableTitle>
+          <el-button type="primary" @click="handleCreateGroup">
+            <img src="./img/create.png" style="margin-right: 8px" />新建人员分组
+          </el-button>
+        </template>
+        <template #empty>
+          <div class="empty-content flex flex-col items-center">
+            <span class="empty-text">暂无数据</span>
+          </div>
+        </template>
+      </BasicTable>
+      <el-dialog
+        v-model="errorVisible"
+        title="无法删除"
+        width="420"
+        class="errorDialog"
+        align-center
+        :show-close="false"
+        :close-on-click-modal="false"
+      >
+        <div class="title">该分组在“推送对象”中已被引用,如需删除,请先解除引用关系。</div>
+        <div class="list">
+          <div
+            v-for="(item, index) in visibleRefGroup"
+            :key="index"
+            style="display: flex; align-items: center"
+          >
+            <span class="dot"></span>{{ getLabel(item.type, item.statisticType) }}
+          </div>
+        </div>
+        <div v-if="refGroup && refGroup.length > 3" @click="showAll = !showAll" class="more-button">
+          {{ showAll ? '收起' : '更多' }}
+        </div>
+        <el-button type="primary" @click="errorVisible = false"> 已知晓 </el-button>
+      </el-dialog>
+      <el-drawer
+        v-model="drawer"
+        :title="drawerTitle"
+        size="450"
+        :close-on-click-modal="false"
+        :destroy-on-close="true"
+      >
+        <GroupBoard @close="drawer = false" :drawer-title="drawerTitle" :form-data="formData" />
+      </el-drawer>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import Search from './components/Search.vue';
+import GroupBoard from './components/GroupBoard.vue';
+import { h, ref, reactive, onMounted, computed } from 'vue';
+import { BasicTable, TableActionIcons, BasicColumn } from '@/components/Table';
+import { userGroupCol } from './overviewColumns';
+import userGroupList from './store/index';
+import { storeToRefs } from 'pinia';
+const userGroup = userGroupList();
+const { userListData, total, page, pagesize } = storeToRefs(userGroup);
+const { getUserGroup } = userGroup;
+import { verifyUserGroup, deleteUserGroup, queryUserGroupDetail } from './api/index';
+import { FormData } from './type';
+import viewIcon from '@/assets/images/reportmessage/view.png';
+import editIcon from '@/assets/images/reportmessage/edit.png';
+import deleteIcon from '@/assets/images/reportmessage/delete.png';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { typeName, statisticTypeName } from '@/views/message/constant';
+const drawer = ref(false);
+const drawerTitle = ref<string>('新建人员分组');
+const handleCreateGroup = () => {
+  drawer.value = true;
+  drawerTitle.value = '新建人员分组';
+};
+const getLabel = (type, statisticType) => {
+  const typeLabel = typeName.find((item) => item.value === type)?.label;
+  const statisticTypeLabel = statisticTypeName.find((item) => item.value === statisticType)?.label;
+  return `${typeLabel} - ${statisticTypeLabel}`;
+};
+const errorVisible = ref<boolean>(false);
+const showAll = ref<boolean>(false);
+const refGroup = ref<Array<{ type: number; statisticType: number }>>();
+const actionColumn: BasicColumn = reactive({
+  width: 224,
+  title: '操作',
+  prop: 'action',
+  key: 'action',
+  fixed: 'right',
+  render(record) {
+    return h(TableActionIcons as any, {
+      space: 20,
+      color: '#629bf9',
+      style: 'img',
+      size: 16,
+      actionIcons: [
+        {
+          label: '查看',
+          icon: viewIcon,
+          onClick: handleView.bind(null, record.row),
+        },
+        {
+          label: '编辑',
+          icon: editIcon,
+          onClick: handleEdit.bind(null, record.row),
+        },
+        {
+          label: '删除',
+          icon: deleteIcon,
+          onClick: handleDelete.bind(null, record.row),
+        },
+      ],
+    });
+  },
+});
+const handlePageNumChange = (pageNum) => {
+  page.value = pageNum;
+  getUserGroup();
+};
+const handlePageSizeChange = (size) => {
+  pagesize.value = size;
+  page.value = 1;
+  getUserGroup();
+};
+const formData = ref<FormData>();
+const handleView = (record: Recordable) => {
+  drawer.value = true;
+  drawerTitle.value = '查看人员分组';
+  queryUserGroupDetail(record.userGroupId).then((res) => {
+    formData.value = res;
+  });
+};
+const handleEdit = (record: Recordable) => {
+  drawer.value = true;
+  drawerTitle.value = '编辑人员分组';
+  queryUserGroupDetail(record.userGroupId).then((res) => {
+    formData.value = res;
+  });
+};
+const handleDelete = (record: Recordable) => {
+  ElMessageBox.confirm('删除之后,该分组将无法恢复', '请确认是否删除', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+  })
+    .then(() => {
+      verifyUserGroup(record.userGroupId).then((res) => {
+        refGroup.value = res;
+        if (refGroup.value?.length! > 0) {
+          errorVisible.value = true;
+        } else {
+          deleteUserGroup(record.userGroupId).then(() => {
+            ElMessage.success('删除成功');
+            getUserGroup();
+          });
+        }
+      });
+    })
+    .catch(() => {});
+};
+const visibleRefGroup = computed(() => {
+  return showAll.value ? refGroup.value : refGroup.value!.slice(0, 3);
+});
+onMounted(() => {
+  getUserGroup();
+});
+</script>
+
+<style lang="scss" scoped>
+.user-group {
+  position: relative;
+  height: calc(100vh - 64px - 18px);
+  background-color: rgba(255, 255, 255, 1);
+  padding: 21px;
+  .user-list {
+    margin-top: 24px;
+  }
+  ::v-deep .el-pagination {
+    position: absolute;
+    bottom: 35px;
+    right: 67px;
+  }
+  .errorDialog {
+    .el-button {
+      position: absolute;
+      bottom: 5px;
+      right: 24px;
+    }
+    .title {
+      font-weight: 400;
+      font-size: 14px;
+      color: rgba(0, 0, 0, 0.88);
+      line-height: 22px;
+      margin-bottom: 8px;
+    }
+    .list {
+      display: flex;
+      flex-direction: column;
+      gap: 4px;
+      font-weight: 400;
+      font-size: 14px;
+      color: rgba(0, 0, 0, 0.88);
+      line-height: 22px;
+      max-height: 300px;
+      overflow-y: auto;
+      margin-bottom: 15px;
+      .dot {
+        display: inline-block;
+        width: 8px;
+        height: 8px;
+        background-color: #ff4d4f;
+        border-radius: 50%;
+        margin-right: 8px;
+      }
+    }
+    .more-button {
+      font-weight: 400;
+      font-size: 14px;
+      color: #1890ff;
+      line-height: 20px;
+      text-decoration: underline;
+      cursor: pointer;
+    }
+  }
+  ::v-deep .el-drawer__header {
+    margin: 0;
+    border-bottom: 1px solid rgba(0, 0, 0, 0.06);
+    span {
+      margin-bottom: 20px;
+      font-weight: 500;
+      font-size: 16px;
+      color: rgba(0, 0, 0, 0.88);
+      line-height: 24px;
+    }
+    button {
+      margin-bottom: 20px;
+      color: #000000;
+    }
+  }
+}
+</style>

+ 63 - 0
src/views/message/persongroup/api/index.ts

@@ -0,0 +1,63 @@
+import { http } from '@/utils/http/axios';
+export function queryUserTree() {
+    return http.request({
+        url: '/dept/queryUserTree',
+        method: 'get',
+    });
+}
+export interface addUserGroupParams {
+    description?: string;
+    name: string;
+    total: number;
+    userIdList: number[]
+}
+export function addUserGroup(params: addUserGroupParams) {
+    return http.request({
+        url: '/userGroup/addUserGroup',
+        method: 'post',
+        params,
+    });
+}
+export interface queryUserGroupListParams {
+    pageNumber: number;
+    pageSize: number;
+    queryStr?: string;
+}
+export function queryUserGroupList(params: queryUserGroupListParams) {
+    return http.request({
+        url: '/userGroup/queryUserGroupList',
+        method: 'post',
+        params,
+    });
+}
+export function verifyUserGroup(userGroupId: number) {
+    return http.request({
+        url: '/userGroup/verifyUserGroup',
+        method: 'get',
+        params: { userGroupId },
+    });
+}
+export function deleteUserGroup(userGroupId: number) {
+    return http.request({
+        url: `/userGroup/deleteUserGroup?userGroupId=${userGroupId}`,
+        method: 'delete',
+        params: { userGroupId },
+    });
+}
+export function queryUserGroupDetail(userGroupId: number) {
+    return http.request({
+        url: '/userGroup/queryUserGroupDetail',
+        method: 'get',
+        params: { userGroupId },
+    });
+}
+export interface modifyUserGroupParams extends addUserGroupParams {
+    userGroupId: number;
+}
+export function modifyUserGroup(params: modifyUserGroupParams) {
+    return http.request({
+        url: '/userGroup/modifyUserGroup',
+        method: 'post',
+        params,
+    });
+}

+ 316 - 0
src/views/message/persongroup/components/GroupBoard.vue

@@ -0,0 +1,316 @@
+<template>
+  <div class="group-board">
+    <el-form
+      ref="ruleFormRef"
+      style="max-width: 600px"
+      :model="ruleForm"
+      label-width="auto"
+      class="demo-ruleForm"
+    >
+      <el-form-item label="分组名称:" prop="name" v-if="props.drawerTitle === '查看人员分组'">
+        <el-input
+          v-model="ruleForm.name"
+          type="textarea"
+          :autosize="{ minRows: 1, maxRows: 2 }"
+          autocomplete="off"
+          disabled="true"
+        />
+      </el-form-item>
+      <el-form-item
+        label="分组名称:"
+        prop="name"
+        :rules="[{ required: true, message: '分组名称不能为空' }]"
+        v-else
+      >
+        <el-input
+          v-model="ruleForm.name"
+          type="textarea"
+          :autosize="{ minRows: 1, maxRows: 2 }"
+          autocomplete="off"
+          placeholder="请输入15字以内分组名称"
+          maxlength="15"
+          show-word-limit
+        />
+      </el-form-item>
+      <el-form-item
+        label="分组描述:"
+        prop="description"
+        v-if="props.drawerTitle === '查看人员分组'"
+      >
+        <el-input
+          v-model="ruleForm.description"
+          type="textarea"
+          :rows="4"
+          autocomplete="off"
+          disabled="true"
+        />
+      </el-form-item>
+      <el-form-item label="分组描述:" prop="description" v-else>
+        <el-input
+          v-model="ruleForm.description"
+          type="textarea"
+          :rows="4"
+          maxlength="50"
+          show-word-limit
+          autocomplete="off"
+          placeholder="请输入50字以内分组描述"
+        />
+      </el-form-item>
+      <el-form-item label="组内成员:" prop="userList" v-if="props.drawerTitle === '查看人员分组'">
+        <el-select v-model="ruleForm.userList" value-key="id" multiple disabled="true">
+          <el-option v-for="user in selectedUser" :key="user.id" :label="user.name" :value="user">
+          </el-option>
+        </el-select>
+        <p
+          >共<span>&nbsp;{{ total }}&nbsp;</span>人</p
+        >
+      </el-form-item>
+      <el-form-item
+        label="组内成员:"
+        prop="userList"
+        :rules="[{ required: true, message: '组内成员不能为空' }]"
+        v-else
+      >
+        <el-select
+          v-model="ruleForm.userList"
+          value-key="id"
+          multiple
+          placeholder="请选择人员"
+          @click="dialogVisible = true"
+        >
+          <el-option v-for="user in selectedUser" :key="user.id" :label="user.name" :value="user">
+          </el-option>
+        </el-select>
+        <p
+          >共<span>&nbsp;{{ total }}&nbsp;</span>人</p
+        >
+      </el-form-item>
+      <el-form-item
+        label="操作人:"
+        prop="operator"
+        v-if="props.drawerTitle === '查看人员分组' || props.drawerTitle === '编辑人员分组'"
+      >
+        <el-input v-model="ruleForm.operator" disabled="true" />
+      </el-form-item>
+      <el-form-item label="创建人:" prop="operator" v-else>
+        <el-input v-model="ruleForm.operator" disabled="true" />
+      </el-form-item>
+      <el-form-item class="buttons" v-if="props.drawerTitle === '编辑人员分组'">
+        <el-button @click="recoverForm(ruleFormRef)"> 重置 </el-button>
+        <el-button type="primary" @click="changeForm(ruleFormRef)">提交</el-button>
+      </el-form-item>
+      <el-form-item class="buttons" v-if="props.drawerTitle === '新建人员分组'">
+        <el-button @click="resetForm(ruleFormRef)"> 重置 </el-button>
+        <el-button type="primary" @click="submitForm(ruleFormRef)">提交</el-button>
+      </el-form-item>
+    </el-form>
+    <el-dialog
+      v-model="dialogVisible"
+      title="添加组内成员"
+      align-center
+      :close-on-click-modal="false"
+      style="height: 583px"
+      :width="731"
+      :destroy-on-close="true"
+    >
+      <SelectTree @cancel="handleCancle" @submit="handleSubmit" :selectedUser="selectedUser" />
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive, watch } from 'vue';
+import SelectTree from './SelectTree.vue';
+import { storeToRefs } from 'pinia';
+import { useUserStore } from '@/store/modules/user';
+import type { FormInstance } from 'element-plus';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { debounce } from 'lodash-es';
+import {
+  addUserGroup,
+  addUserGroupParams,
+  modifyUserGroup,
+  modifyUserGroupParams,
+} from '../api/index';
+import { FormData } from '../type';
+const useUser = useUserStore();
+const { info } = storeToRefs(useUser);
+import userGroupList from '../store/index';
+const userGroup = userGroupList();
+const { getUserGroup } = userGroup;
+interface UserList {
+  id: string;
+  name: string;
+  userId: number;
+}
+const selectedUser = ref<UserList[]>([]);
+const dialogVisible = ref<boolean>(false);
+const ruleFormRef = ref<FormInstance>();
+const ruleForm = reactive({
+  name: '',
+  description: '',
+  userList: [] as UserList[],
+  operator: info.value.nickname,
+});
+const handleCancle = () => {
+  dialogVisible.value = false;
+};
+const total = ref<number>(0);
+const handleSubmit = (selectedData: UserList[]) => {
+  selectedUser.value = selectedData;
+  ruleForm.userList = selectedData;
+  total.value = ruleForm.userList.length;
+  dialogVisible.value = false;
+};
+const emit = defineEmits(['close']);
+const debounceEmit = debounce((addUserGroupParams) => {
+  addUserGroup(addUserGroupParams)
+    .then(() => {
+      ElMessage({
+        message: '创建分组成功!',
+        type: 'success',
+      });
+      emit('close');
+      getUserGroup();
+    })
+    .catch((e) => console.error(e));
+}, 500);
+const debounceChange = debounce((modifyUserGroupParams) => {
+  modifyUserGroup(modifyUserGroupParams)
+    .then(() => {
+      ElMessage({
+        message: '更新分组成功!',
+        type: 'success',
+      });
+      emit('close');
+      getUserGroup();
+    })
+    .catch((e) => console.error(e));
+}, 500);
+const submitForm = (formEl: FormInstance | undefined) => {
+  if (!formEl) return;
+  formEl.validate((valid) => {
+    if (!valid) return;
+    const params = ref<addUserGroupParams>();
+    params.value = {
+      name: ruleForm.name,
+      description: ruleForm.description,
+      userIdList: ruleForm.userList.map((user) => user.userId),
+      total: total.value,
+    };
+    debounceEmit(params.value);
+  });
+};
+const changeForm = (formEl: FormInstance | undefined) => {
+  ElMessageBox.confirm('更新之后,引用该分组的“推送对象”将同步更新', '请确认是否更新', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+  }).then(() => {
+    if (!formEl) return;
+    formEl.validate((valid) => {
+      if (!valid) return;
+      const params = ref<modifyUserGroupParams>();
+      params.value = {
+        userGroupId: props.formData.userGroupId,
+        name: ruleForm.name,
+        description: ruleForm.description,
+        userIdList: ruleForm.userList.map((user) => user.userId),
+        total: total.value,
+      };
+      debounceChange(params.value);
+    });
+  });
+};
+const resetForm = (formEl: FormInstance | undefined) => {
+  if (!formEl) return;
+  formEl.resetFields();
+};
+const userList = ref<UserList[]>();
+const recoverForm = (formEl: FormInstance | undefined) => {
+  if (!formEl) return;
+  ruleForm.name = props.formData.name;
+  ruleForm.description = props.formData.description ? props.formData.description : '';
+  userList.value = props.formData.userList.map((user) => ({
+    id: `u${user.userId}`,
+    userId: user.userId,
+    name: `${user.loginName}-${user.nickname}`,
+  }));
+  selectedUser.value = userList.value;
+  ruleForm.userList = userList.value;
+  total.value = props.formData.userList.length;
+};
+const props = defineProps<{
+  drawerTitle: string;
+  formData: FormData;
+}>();
+watch(
+  () => ruleForm.userList,
+  (newUserList) => {
+    total.value = newUserList.length;
+    const ids = newUserList.map((user) => user.id);
+    selectedUser.value = selectedUser.value.filter((user) => ids.includes(user.id));
+  },
+  { immediate: true },
+);
+watch(
+  () => props.formData,
+  (newForm) => {
+    ruleForm.name = newForm.name;
+    ruleForm.description = newForm.description ? newForm.description : '';
+    if (props.drawerTitle === '查看人员分组') {
+      ruleForm.operator = newForm.operator;
+    }
+    userList.value = newForm.userList.map((user) => ({
+      id: `u${user.userId}`,
+      userId: user.userId,
+      name: `${user.loginName}-${user.nickname}`,
+    }));
+    selectedUser.value = userList.value;
+    ruleForm.userList = userList.value;
+    total.value = newForm.userList.length;
+  },
+);
+</script>
+
+<style lang="scss" scoped>
+.el-form {
+  display: flex;
+  flex-direction: column;
+  margin-top: 24px;
+  gap: 20px;
+  ::v-deep .el-select__selection {
+    max-height: 60px;
+    overflow-y: auto;
+  }
+  p {
+    margin-top: 8px;
+    font-weight: 400;
+    font-size: 14px;
+    color: rgba(0, 0, 0, 0.85);
+    line-height: 22px;
+  }
+  span {
+    font-weight: 400;
+    font-size: 14px;
+    color: #1890ff;
+    line-height: 22px;
+  }
+  .buttons {
+    display: flex;
+    gap: 8px;
+    position: absolute;
+    bottom: 35px;
+    right: 79px;
+    .el-button {
+      width: 65px;
+      height: 32px;
+      padding: 5px 16px 5px 16px;
+      border-radius: 2px;
+    }
+  }
+}
+::v-deep .el-dialog__body {
+  height: 527px;
+}
+</style>

+ 36 - 0
src/views/message/persongroup/components/Search.vue

@@ -0,0 +1,36 @@
+<template>
+  <div class="flex items-center query-head">
+    <el-space alignment="center" :size="30">
+      <el-input
+        v-model="queryStr"
+        :style="{ width: '300px' }"
+        placeholder="请输入搜索内容"
+        :prefix-icon="Search"
+      />
+    </el-space>
+    <div class="flex-1 flex">
+      <el-button type="primary" style="margin-left: 15px" @click="getUserGroup">
+        <img src="../img/search.png" style="margin-right: 8px;" />搜索
+      </el-button>
+      <el-button style="margin-left: 15px" @click="resetSearch"
+        ><img src="../img/refresh.png" style="margin-right: 8px;" />重置
+      </el-button>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { Search } from '@element-plus/icons-vue';
+import userGroupList from '../store/index';
+import { storeToRefs } from 'pinia';
+const userGroup = userGroupList();
+const { queryStr } = storeToRefs(userGroup);
+const { getUserGroup } = userGroup;
+const resetSearch = () => {
+  queryStr.value = '';
+  getUserGroup();
+};
+</script>
+
+<style lang="scss" scoped>
+</style>

+ 219 - 0
src/views/message/persongroup/components/SelectTree.vue

@@ -0,0 +1,219 @@
+<template>
+  <div class="select-tree">
+    <div class="left">
+      <el-input
+        v-model="queryStr"
+        :style="{ width: '300px', height: '30px' }"
+        placeholder="请输入搜索内容"
+        :prefix-icon="Search"
+        @keyup.enter="onSearch"
+      />
+      <el-tree
+        ref="treeRef"
+        :data="filterData"
+        show-checkbox
+        node-key="id"
+        :props="defaultProps"
+        :default-expand-all="true"
+        @check-change="handleCheckChange"
+      />
+    </div>
+    <el-divider direction="vertical" style="height: auto" />
+    <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.id)"
+        >
+          {{ 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 } from 'vue';
+import { Search } from '@element-plus/icons-vue';
+import type { ElTree } from 'element-plus';
+import { treeSelected, TreeNode, FormattedNode } from '../type';
+import { countLeafNodes, formatTree } from '../hook/index';
+import { queryUserTree } from '../api/index';
+import { cloneDeep } from 'lodash-es';
+const queryStr = ref<string>('');
+const defaultProps = {
+  children: 'children',
+  label: 'name',
+};
+const treeData = ref<TreeNode[]>();
+const nodeData = ref<FormattedNode[]>();
+const filterData = ref<FormattedNode[]>();
+
+const filterTree = (nodes: FormattedNode[], keyword: string): FormattedNode[] => {
+  const filteredNodes: FormattedNode[] = [];
+  const traverse = (node) => {
+    let includeNode = false;
+    if (node.name.includes(keyword)) {
+      includeNode = true;
+    }
+    if (node.children) {
+      const filteredChildren = node.children
+        .map((child) => traverse(child))
+        .filter((child) => child !== null);
+      if (filteredChildren.length > 0) {
+        includeNode = true;
+        node = { ...node, children: filteredChildren };
+      }
+    }
+    return includeNode ? node : null;
+  };
+  nodes.forEach((node) => {
+    const result = traverse(node);
+    if (result !== null) {
+      filteredNodes.push(result);
+    }
+  });
+  return filteredNodes;
+};
+
+const onSearch = () => {
+  if (queryStr.value) {
+    filterData.value = filterTree(nodeData.value!, queryStr.value);
+  } else {
+    filterData.value = nodeData.value;
+  }
+  queryStr.value = '';
+};
+const total = ref<number>(0);
+const selected = ref<number>(0);
+const selectedPeople = ref<treeSelected[]>([]);
+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,
+      });
+    } else {
+      const index = selectedPeople.value.findIndex((item) => item.id === node.id);
+      if (index !== -1) {
+        selectedPeople.value.splice(index, 1);
+      }
+    }
+    selected.value = selectedPeople.value.length;
+  }
+};
+const treeRef = ref<InstanceType<typeof ElTree>>();
+const handleTagClose = (id) => {
+  const index = selectedPeople.value.findIndex((item) => item.id === id);
+  if (index !== -1) {
+    selectedPeople.value.splice(index, 1);
+    selected.value = selectedPeople.value.length;
+    treeRef.value!.setChecked(id, false, true);
+  }
+};
+const emit = defineEmits(['cancel', 'submit']);
+const handleCancle = () => {
+  emit('cancel');
+};
+const handleSubmit = () => {
+  emit('submit', selectedPeople.value);
+};
+const props = defineProps<{
+  selectedUser: treeSelected[];
+}>();
+onMounted(() => {
+  queryUserTree().then((res) => {
+    treeData.value = res;
+    nodeData.value = formatTree(treeData.value!);
+    filterData.value = nodeData.value;
+    total.value = countLeafNodes(nodeData.value);
+    const selectedIds: string[] = selectedPeople.value.map((item) => item.id as string);
+    treeRef.value!.setCheckedKeys(selectedIds);
+  });
+});
+watch(
+  () => props.selectedUser,
+  (newSelected) => {
+    selectedPeople.value = cloneDeep(newSelected);
+    selected.value = selectedPeople.value.length;
+  },
+  { immediate: true },
+);
+</script>
+
+<style lang="scss" scoped>
+.select-tree {
+  display: flex;
+  width: 100%;
+  height: 100%;
+  .left {
+    display: flex;
+    flex-direction: column;
+    width: 50%;
+    height: 100%;
+    .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;
+    width: 50%;
+    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>

+ 60 - 0
src/views/message/persongroup/hook/index.ts

@@ -0,0 +1,60 @@
+import { TreeNode, FormattedNode } from '../type'
+//格式化组织树
+export const formatTree = (treeData: TreeNode[]): FormattedNode[] => {
+    const countUsers = (node: TreeNode): number => {
+        let count = node.userVOList ? node.userVOList.length : 0;
+        if (node.children) {
+            node.children.forEach(child => {
+                count += countUsers(child);
+            });
+        }
+        return count;
+    };
+    const formatNode = (node: TreeNode): FormattedNode => {
+        const userCount = countUsers(node);
+        const formattedNode: FormattedNode = {
+            id: `d${node.deptId}`,
+            name: node.deptName + `(${userCount}人)`,
+            userId: null,
+            children: [],
+        };
+        if (node.children) {
+            node.children.forEach((childNode) => {
+                if (childNode) {
+                    formattedNode.children.push(formatNode(childNode))
+                }
+            })
+        }
+        if (node.userVOList) {
+            node.userVOList.forEach((user) => {
+                if (user) {
+                    formattedNode.children.push({
+                        id: `u${user.userId}`,
+                        name: `${user.userLoginName}-${user.userNickname}`,
+                        userId: user.userId,
+                        children: [],
+                    });
+                }
+            })
+        }
+        return formattedNode;
+    }
+    const formattedTree: FormattedNode[] = [];
+    treeData.forEach((node) => {
+        formattedTree.push(formatNode(node))
+    })
+    return formattedTree
+}
+//计算组织树数量
+export const countLeafNodes = (nodes) => {
+    let count = 0;
+    const traverse = (node) => {
+        if (!node.children || node.children.length === 0 && node.userId) {
+            count++;
+        } else {
+            node.children.forEach(child => traverse(child));
+        }
+    }
+    nodes.forEach(node => traverse(node))
+    return count;
+}

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


BIN
src/views/message/persongroup/img/refresh.png


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


+ 53 - 0
src/views/message/persongroup/overviewColumns.ts

@@ -0,0 +1,53 @@
+import { h } from 'vue';
+import { ElTooltip } from 'element-plus';
+import type { BasicColumn } from '@/components/Table';
+export const userGroupCol: BasicColumn[] = [
+  {
+    label: '分组名称',
+    prop: 'name',
+  },
+  {
+    label: '分组描述',
+    prop: 'description',
+    minWidth: 100,
+    render(record) {
+      const truncatedDescription = record.row.description.length > 15 ? record.row.description.substring(0, 15) + '...' : record.row.description;
+      return h(
+        ElTooltip,
+        {
+          content: record.row.description,
+          disabled:record.row.description.length<=15,
+          effect: 'light',
+        },
+        {
+          default: () =>
+            h(
+              'span',
+              {
+                style: {
+                  whiteSpace: 'nowrap',
+                  overflow: 'hidden',
+                  textOverflow: 'ellipsis',
+                  display: 'inline-block',
+                  maxWidth:'80%',
+                },
+              },
+              truncatedDescription
+            ),
+        }
+      );
+    }
+  },
+  {
+    label: '组内人员',
+    prop: 'total',
+  },
+  {
+    label: '操作人',
+    prop: 'operatorName',
+  },
+  {
+    label: '操作时间',
+    prop: 'operationTime',
+  },
+];

+ 34 - 0
src/views/message/persongroup/store/index.ts

@@ -0,0 +1,34 @@
+import { ref } from 'vue'
+import { defineStore } from "pinia";
+import { userData } from '../type';
+import { queryUserGroupListParams, queryUserGroupList } from '../api/index'
+import { useRequest } from 'vue-hooks-plus';
+export const userGroupList = defineStore('user-group', () => {
+    const total = ref<number>(0)
+    const page = ref<number>(1);
+    const pagesize = ref<number>(10);
+    const queryStr = ref<string>('')
+    const conditionSearch = () => {
+        const params: queryUserGroupListParams = {
+            pageNumber: page.value,
+            pageSize: pagesize.value,
+        };
+        if (queryStr.value) {
+            params.queryStr = queryStr.value
+        }
+        return queryUserGroupList(params).then((res) => {
+            return res;
+        });
+    }
+    const userListData = ref<userData[]>([])
+    const { run: getUserGroup } = useRequest(conditionSearch, {
+        manual: true,
+        onSuccess: (res) => {
+            userListData.value = res.groupVOList;
+            total.value = res.total;
+        },
+    });
+    return { total, page, pagesize, queryStr, userListData, getUserGroup }
+});
+
+export default userGroupList;

+ 58 - 0
src/views/message/persongroup/type.ts

@@ -0,0 +1,58 @@
+export interface userData {
+    //分组描述
+    description?: string;
+    //ID
+    id?: number;
+    //用户组名称
+    name?: string;
+    //操作时间
+    operationTime?: string;
+    //操作人姓名
+    operatorName?: string;
+    //人数
+    total?: number;
+}
+export interface treeSelected {
+    //ID
+    id?: string;
+    //名称
+    name?: string;
+    //userID
+    userId?: number;
+}
+interface UserVO {
+    userId: number;
+    userNickname: string | null;
+    userNumber: string | null;
+    userLoginName: string | null;
+}
+export interface TreeNode {
+    deptId: number;
+    deptName: string;
+    treePath: string | null;
+    grade: string | null;
+    parent: number;
+    parentName: string | null;
+    orderNum: number | null;
+    children: TreeNode[] | null;
+    userVOList: UserVO[] | null;
+}
+export interface FormattedNode {
+    id: string;
+    name: string | null;
+    userId: number | null;
+    children: FormattedNode[];
+}
+export interface FromUserList {
+    userId: number;
+    loginName: string;
+    nickname: string;
+}
+export interface FormData {
+    description?: string;
+    name: string;
+    operator: string;
+    total: number;
+    userGroupId: number;
+    userList: FromUserList[];
+}

+ 65 - 0
src/views/message/reportmessage/ReportMessage.vue

@@ -0,0 +1,65 @@
+<template>
+  <div class="report-message">
+    <div class="flex reportmessage-head-tabs">
+      <div
+        class="flex justify-center items-center tab-item"
+        :class="{ 'tab-item-active': type === item.value }"
+        @click="switchTable(item.value)"
+        v-for="item in typeName"
+        :key="item.value"
+      >
+        {{ item.label }}
+      </div>
+    </div>
+    <Form />
+  </div>
+</template>
+  
+  <script setup lang="ts">
+import Form from './components/Form.vue';
+import { typeName } from '@/views/message/constant'
+import { storeToRefs } from 'pinia';
+import useFormList from './store/useFormList';
+const formStore = useFormList();
+const { type } = storeToRefs(formStore);
+const switchTable = (value) => {
+  type.value = value;
+};
+</script>
+  
+  <style scoped lang="scss">
+.report-message {
+  height: calc(100vh - 64px - 18px);
+  background-color: rgba(255, 255, 255, 1);
+  padding: 21px;
+}
+.reportmessage-head {
+  height: 56px;
+
+  &-tabs {
+    margin: 18px 0;
+
+    .tab-item {
+      width: 188px;
+      height: 38px;
+      background: #fafafa;
+      border: 1px solid #d9d9d9;
+      cursor: pointer;
+
+      &-active {
+        color: rgba(22, 119, 255, 1);
+        background: #e2eefe;
+        border: 1px solid #1890ff;
+      }
+    }
+
+    :first-child {
+      border-radius: 8px 0px 0px 8px;
+    }
+
+    :last-child {
+      border-radius: 0px 8px 8px 0px;
+    }
+  }
+}
+</style>

+ 40 - 0
src/views/message/reportmessage/api/index.ts

@@ -0,0 +1,40 @@
+import { http } from '@/utils/http/axios';
+import qs from 'qs'
+export function queryReportConfigList(type: number) {
+    return http.request({
+        url: '/reportMessage/queryReportConfigList',
+        method: 'get',
+        params: { type },
+    });
+}
+export function deleteReportConfig(type: number, statisticType: number) {
+    return http.request({
+        url: `/reportMessage/deleteReportConfig?type=${type}&statisticType=${statisticType}`,
+        method: 'post',
+    });
+}
+export interface queryPushRecordsParams {
+    pageNum: number,
+    pageSize: number,
+    statisticType?: number,
+    type?: number,
+}
+export function queryPushRecords(params: queryPushRecordsParams) {
+    return http.request({
+        url: '/reportMessage/queryPushRecords',
+        method: 'post',
+        params,
+    });
+}
+export interface updateStatusParams {
+    type: number,
+    statisticType: number,
+    status: number
+}
+export function updateStatus(params: updateStatusParams) {
+    const str = qs.stringify(params);
+    return http.request({
+        url: `/reportMessage/updateStatus?${str}`,
+        method: 'post',
+    });
+}

+ 146 - 0
src/views/message/reportmessage/components/Form.vue

@@ -0,0 +1,146 @@
+<template>
+  <div class="report-list">
+    <BasicTable
+      :columns="reportDataCol"
+      :data-source="formList"
+      :row-key="(row) => row.code"
+      :action-column="actionColumn"
+      :tableSetting="{
+        size: false,
+        redo: false,
+        fullscreen: false,
+        striped: false,
+        setting: false,
+      }"
+      :striped="true"
+      ref="tableRef"
+    >
+      <template #tableTitle>
+        <el-button type="primary" :icon="Plus" @click="CreateReport(type)">新建报表</el-button>
+      </template>
+      <template #empty>
+        <div class="empty-content flex flex-col items-center">
+          <span class="empty-text">暂无数据</span>
+        </div>
+      </template>
+    </BasicTable>
+    <el-dialog
+      v-model="logFormVisible"
+      title="推送记录"
+      width="800"
+      align-center
+      :close-on-click-modal="false"
+      :destroy-on-close="true"
+    >
+      <LogForm :statisticType="statisticType" />
+    </el-dialog>
+  </div>
+</template>
+  
+  <script lang="ts" setup>
+import { h, ref, reactive, watch, onUnmounted } from 'vue';
+import { BasicTable, TableActionIcons, BasicColumn } from '@/components/Table';
+import { reportDataCol } from '../overviewColumns';
+import { Plus } from '@element-plus/icons-vue';
+import logIcon from '@/assets/images/reportmessage/log.png';
+import viewIcon from '@/assets/images/reportmessage/view.png';
+import editIcon from '@/assets/images/reportmessage/edit.png';
+import deleteIcon from '@/assets/images/reportmessage/delete.png';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import LogForm from './LogForm.vue';
+import { storeToRefs } from 'pinia';
+import useFormList from '../store/useFormList';
+const formStore = useFormList();
+const { getForm } = formStore;
+import { deleteReportConfig } from '../api/index';
+const { type, formList } = storeToRefs(formStore);
+const logFormVisible = ref(false);
+const actionColumn: BasicColumn = reactive({
+  width: 224,
+  title: '操作',
+  prop: 'action',
+  key: 'action',
+  fixed: 'right',
+  render(record) {
+    return h(TableActionIcons as any, {
+      space: 20,
+      color: '#629bf9',
+      style: 'img',
+      size: 16,
+      actionIcons: [
+        {
+          label: '推送记录',
+          icon: logIcon,
+          onClick: handleLog.bind(null, record.row),
+        },
+        {
+          label: '查看',
+          icon: viewIcon,
+          onClick: handleView.bind(null, record.row),
+        },
+        {
+          label: '编辑',
+          icon: editIcon,
+          onClick: handleEdit.bind(null, record.row),
+        },
+        {
+          label: '删除',
+          icon: deleteIcon,
+          onClick: handleDelete.bind(null, record.row),
+        },
+      ],
+    });
+  },
+});
+import { useRouter } from 'vue-router';
+const router = useRouter();
+const CreateReport = (type) => {
+  router.push(`/message/reportoperation?type=${type}&operationType=1`);
+};
+const statisticType = ref<number>(0);
+const handleLog = (record: Recordable) => {
+  logFormVisible.value = true;
+  statisticType.value = record.statisticType;
+};
+const handleView = (record: Recordable) => {
+  router.push(
+    `/message/reportoperation?type=${type.value}&statisticType=${record.statisticType}&operationType=2`,
+  );
+};
+const handleEdit = (record: Recordable) => {
+  router.push(
+    `/message/reportoperation?type=${type.value}&statisticType=${record.statisticType}&operationType=3`,
+  );
+};
+const handleDelete = (record: Recordable) => {
+  ElMessageBox.confirm('删除之后,这条数据无法恢复', '请确认是否删除', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+  })
+    .then(() => {
+      deleteReportConfig(type.value, record.statisticType).then(() => {
+        ElMessage.success('删除成功');
+        getForm(type.value);
+      });
+    })
+    .catch(() => {});
+};
+onUnmounted(() => {
+  type.value = 1;
+  formList.value = [];
+});
+watch(
+  type,
+  (newType) => {
+    getForm(newType);
+  },
+  { immediate: true },
+);
+</script>
+  
+  <style lang="scss" scoped>
+.report-list {
+  position: relative;
+  width: 100%;
+}
+</style>

+ 78 - 0
src/views/message/reportmessage/components/LogForm.vue

@@ -0,0 +1,78 @@
+<template>
+  <div class="log-form">
+    <BasicTable
+      :columns="logColumns"
+      :data-source="logList"
+      :row-key="(row) => row.code"
+      :pagination="{
+        total: total,
+        pageSize: pageSize,
+        currentPage: pageNum,
+        hideOnSinglePage: !logList,
+      }"
+      :tableSetting="{
+        size: false,
+        redo: false,
+        fullscreen: false,
+        striped: false,
+        setting: false,
+      }"
+      :striped="true"
+      ref="tableRef"
+      @page-num-change="handlePageNumChange"
+      @page-size-change="handlePageSizeChange"
+    >
+      <template #empty>
+        <div class="empty-content flex flex-col items-center">
+          <span class="empty-text">暂无推送记录</span>
+        </div>
+      </template>
+    </BasicTable>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, onMounted } from 'vue';
+import { BasicTable } from '@/components/Table';
+import { logColumns } from '../overviewColumns';
+import { logData } from '../type';
+import { queryPushRecords, queryPushRecordsParams } from '../api/index';
+import { storeToRefs } from 'pinia';
+import useFormList from '../store/useFormList';
+const formStore = useFormList();
+const { type } = storeToRefs(formStore);
+const props = defineProps<{
+  statisticType: number;
+}>();
+const logList = ref<logData[]>();
+const total = ref<number>(0);
+const pageSize = ref<number>(10);
+const pageNum = ref<number>(1);
+const getLoglist = (pageNum, pageSize) => {
+  const params: queryPushRecordsParams = {
+    pageNum: pageNum,
+    pageSize: pageSize,
+    statisticType: props.statisticType,
+    type: type.value,
+  };
+  queryPushRecords(params).then((res) => {
+    logList.value = res.reportPushRecords;
+    total.value = res.total;
+  });
+};
+const handlePageNumChange = (num) => {
+  pageNum.value = num;
+  getLoglist(pageNum.value, pageSize.value);
+};
+const handlePageSizeChange = (size) => {
+  pageNum.value = 1;
+  pageSize.value = size;
+  getLoglist(pageNum.value, pageSize.value);
+};
+onMounted(() => {
+  getLoglist(pageNum.value, pageSize.value);
+});
+</script>
+
+<style lang="scss" scoped>
+</style>

+ 13 - 0
src/views/message/reportmessage/constant.ts

@@ -0,0 +1,13 @@
+export const recipientTypeName = [
+    { value: 1, label: "全员" },
+    { value: 2, label: "分组" },
+    { value: 3, label: "自定义" }
+]
+export const pushChannelName = [
+    { value: 1, label: "蓝信" },
+    { value: 2, label: "平台" },
+]
+export const statusName = [
+    { value: 0, label: "已推送" },
+    { value: 1, label: "未推送" },
+]

+ 104 - 0
src/views/message/reportmessage/overviewColumns.ts

@@ -0,0 +1,104 @@
+import { h } from 'vue';
+import type { BasicColumn } from '@/components/Table';
+import { ElSwitch } from 'element-plus';
+import { pushChannelName, recipientTypeName, statusName } from './constant'
+import { statisticTypeName } from '@/views/message/constant'
+import { storeToRefs } from 'pinia';
+import useFormList from './store/useFormList';
+const formStore = useFormList();
+const { type } = storeToRefs(formStore);
+const { getForm } = formStore;
+import { updateStatus, updateStatusParams } from './api/index'
+export const reportDataCol: BasicColumn[] = [
+  {
+    label: '报表类型',
+    prop: 'statisticType',
+    render(record) {
+      const typeName = statisticTypeName.find(item => item.value === record.row.statisticType);
+      return h(
+        'span',
+        {},
+        typeName ? typeName.label : ''
+      )
+    }
+  },
+  {
+    label: '推送渠道',
+    prop: 'pushChannel',
+    render(record) {
+      const labels = record.row.pushChannel
+        .map(channel => {
+          const typeName = pushChannelName.find(item => item.value === channel);
+          return typeName ? typeName.label : '';
+        })
+        .filter(label => label !== '')
+        .join(', ');
+      return h('span', {}, labels);
+    }
+  },
+  {
+    label: '推送对象',
+    prop: 'recipientType',
+    render(record) {
+      const length = record.row.recipientType.length;
+      if (length > 1) {
+        return h('span', {}, '多类对象');
+      }
+      const label = recipientTypeName.find(item => item.value === record.row.recipientType[0])
+      return h('span', {}, label ? label.label : '');
+    }
+  },
+  {
+    label: '是否启用',
+    prop: 'status',
+    render(record) {
+      return h(
+        ElSwitch,
+        {
+          modelValue: record.row.status,
+          onChange: () => {
+            const params: updateStatusParams = {
+              type: type.value,
+              statisticType: record.row.statisticType,
+              status: record.row.status === 1 ? 0 : 1
+            }
+            updateStatus(params).then(() => {
+              getForm(type.value);
+            });
+          },
+          activeValue: 0,
+          inactiveValue: 1,
+        },
+        {},
+      );
+    },
+  },
+  {
+    label: '操作时间',
+    prop: 'updatedAt',
+  }
+];
+export const logColumns: BasicColumn[] = [
+  {
+    label: '序号',
+    type: 'index',
+    minWidth: 56,
+  },
+  {
+    label: '推送时间',
+    prop: 'createdAt',
+  },
+  {
+    label: '推送状态',
+    prop: 'status',
+    render(record) {
+      const name = statusName.find(item => item.value === record.row.status);
+      const style = record.row.status === 1 ? { color: '#FAAD14' } : {};
+      return h(
+        'span',
+        { style },
+        name ? name.label : ''
+      )
+    }
+  },
+];

+ 16 - 0
src/views/message/reportmessage/store/useFormList.ts

@@ -0,0 +1,16 @@
+import { ref } from 'vue'
+import { defineStore } from "pinia";
+import { reportData } from '../type';
+import { queryReportConfigList } from '../api/index'
+export const useFormList = defineStore('form-list', () => {
+    const type = ref<number>(1)
+    const formList = ref<reportData[]>([])
+    const getForm = (type: number) => {
+        queryReportConfigList(type).then((res) => {
+            formList.value = res
+        })
+    }
+    return { type, formList, getForm }
+});
+
+export default useFormList;

+ 40 - 0
src/views/message/reportmessage/type.ts

@@ -0,0 +1,40 @@
+export interface reportData {
+    //ID
+    id: number;
+    //推送消息类型
+    type: number;
+    //报表类型
+    statisticType: number;
+    //推送渠道
+    pushChannel: number[];
+    //推送对象
+    recipientType: number[];
+    //是否启用
+    status: number;
+    //操作时间
+    updatedAt: string;
+}
+export interface logData {
+    //ID
+    id: number;
+    //报表消息配置id
+    reportConfigId: number;
+    //推送消息类型
+    type: number;
+    //报表类型
+    statisticType: number;
+    //推送标题
+    title: string;
+    //推送内容
+    content: string;
+    //推送渠道
+    pushChannel: number;
+    //推送状态
+    status: number;
+    //创建时间
+    createdAt: string;
+    //是否删除
+    isDeleted: number;
+    //更新时间
+    updatedAt: string
+}