fix: 公告类型字段,另加一个配置功能(类似字典功能),并且也作为消息通知通知到每个人
已修改2个文件
655 ■■■■ 文件已修改
src/api/collaborativeApproval/noticeManagement.js 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/noticeManagement/index.vue 629 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/collaborativeApproval/noticeManagement.js
@@ -50,3 +50,29 @@
        method: 'get',
    })
}
// 查询公告类型列表
export function listNoticeType() {
    return request({
        url: '/noticeType/list',
        method: 'get'
    })
}
// 新增公告类型
export function addNoticeType(data) {
    return request({
        url: '/noticeType/add',
        method: 'post',
        data: data
    })
}
// 删除公告类型
export function delNoticeType(id) {
    return request({
        url: '/noticeType/del',
        method: 'delete',
        data: { id }
    })
}
src/views/collaborativeApproval/noticeManagement/index.vue
@@ -4,136 +4,89 @@
    <div class="search_form">
      <div>
        <el-button type="primary" @click="openForm('add')">新增公告</el-button>
        <el-button type="danger" plain @click="handleDelete" :disabled="!selectedIds.length">删除</el-button>
        <el-button type="info" @click="openNoticeTypeDialog">公告类型配置</el-button>
      </div>
    </div>
    <!-- 通知公告板 -->
    <div class="notice-board">
      <!-- 放假通知区域 -->
      <div class="notice-section" v-if="holidayNoticeCount > 0">
        <div class="section-header">
          <h3>📅 放假通知</h3>
          <span class="section-count">{{ holidayNoticeCount }}条</span>
        </div>
        <div class="notice-cards">
          <div
              v-for="notice in holidayNotices"
              :key="notice.id"
              class="notice-card holiday-card"
              :class="{ 'urgent': notice.priority === '3' }"
          >
            <div class="card-header">
              <div class="card-title">
                <el-icon class="holiday-icon">
                  <Calendar/>
                </el-icon>
                {{ notice.title }}
              </div>
              <div class="card-actions">
                <el-button link type="primary" @click="handleEdit(notice)" :disabled="isNoticeExpired(notice)">编辑</el-button>
                <el-button link type="danger" @click="handleDelete(notice.id)">删除</el-button>
      <el-tabs v-model="activeNoticeTypeTab" @tab-change="handleNoticeTypeTabChange">
        <el-tab-pane
            v-for="noticeType in noticeTypeList"
            :key="noticeType.id"
            :label="noticeType.noticeType"
            :name="String(noticeType.id)"
        >
          <template #label>
            <span>{{ noticeType.noticeType }}
              <span class="tab-count" v-if="getNoticeCountByType(noticeType.id) > 0">
                ({{ getNoticeCountByType(noticeType.id) }})
              </span>
            </span>
          </template>
          <div class="notice-section">
            <div class="notice-cards">
              <div
                  v-for="notice in getNoticesByType(noticeType.id)"
                  :key="notice.id"
                  class="notice-card"
                  :class="{ 'urgent': notice.priority === '3' }"
              >
                <div class="card-header">
                  <div class="card-title">
                    <el-icon class="notice-icon">
                      <Calendar/>
                    </el-icon>
                    {{ notice.title }}
                  </div>
                  <div class="card-actions">
                    <el-button link type="primary" @click="handleEdit(notice)" :disabled="isNoticeExpired(notice)" v-if="notice.status !== 1">编辑</el-button>
                    <el-button link type="success" @click="handlePublish(notice)" v-if="notice.status === 0">发布</el-button>
                    <el-button link type="danger" @click="handleDelete(notice.id)" v-if="notice.status !== 1">删除</el-button>
                  </div>
                </div>
                <div class="card-content">
                  <p>{{ notice.content }}</p>
                </div>
                <div class="card-footer">
                  <div class="card-meta">
                    <span class="priority" :class="'priority-' + notice.priority">
                      {{ getPriorityText(notice.priority) }}
                    </span>
                    <span class="status" :class="'status-' + getNoticeStatus(notice)">
                      {{ getStatusText(getNoticeStatus(notice)) }}
                    </span>
                  </div>
                  <div class="card-info">
                    <span class="creator">{{ notice.createUserName }}</span>
                    <span class="expiration" v-if="notice.expirationDate">截止日期:{{ notice.expirationDate }}</span>
                  </div>
                </div>
                <div class="card-remark" v-if="notice.remark">
                  <el-icon>
                    <InfoFilled/>
                  </el-icon>
                  <span>{{ notice.remark }}</span>
                </div>
              </div>
            </div>
            <div class="card-content">
              <p>{{ notice.content }}</p>
            </div>
            <div class="card-footer">
              <div class="card-meta">
                <span class="priority" :class="'priority-' + notice.priority">
                  {{ getPriorityText(notice.priority) }}
                </span>
                <span class="status" :class="'status-' + getNoticeStatus(notice)">
                  {{ getStatusText(getNoticeStatus(notice)) }}
                </span>
              </div>
              <div class="card-info">
                <span class="creator">{{ notice.createUserName }}</span>
              <span class="expiration" v-if="notice.expirationDate">截止日期:{{ notice.expirationDate }}</span>
              </div>
            </div>
            <div class="card-remark" v-if="notice.remark">
              <el-icon>
                <InfoFilled/>
              </el-icon>
              <span>{{ notice.remark }}</span>
            <pagination
                v-if="getNoticePageByType(noticeType.id).total > 0"
                :total="getNoticePageByType(noticeType.id).total"
                :page="getNoticePageByType(noticeType.id).current"
                :limit="getNoticePageByType(noticeType.id).size"
                @pagination="(val) => handleNoticeCurrentChange(noticeType.id, val)"
            />
            <!-- 空状态 -->
            <div class="empty-state" v-if="getNoticesByType(noticeType.id).length === 0">
              <el-empty description="暂无通知公告"/>
            </div>
          </div>
        </div>
      </div>
      <pagination
          v-if="holidayNoticePage.total > 0"
          :total="holidayNoticePage.total"
          :page="holidayNoticePage.current"
          :limit="holidayNoticePage.size"
          @pagination="handleHolidayNoticeCurrentChange"
      />
      <!-- 设备维修通知区域 -->
      <div class="notice-section" v-if="maintenanceNoticeCount > 0">
        <div class="section-header">
          <h3>🔧 设备维修通知</h3>
          <span class="section-count">{{ maintenanceNoticeCount }}条</span>
        </div>
        <div class="notice-cards">
          <div
              v-for="notice in maintenanceNotices"
              :key="notice.id"
              class="notice-card maintenance-card"
              :class="{ 'urgent': notice.priority === '3' }"
          >
            <div class="card-header">
              <div class="card-title">
                <el-icon class="maintenance-icon">
                  <Tools/>
                </el-icon>
                {{ notice.title }}
              </div>
              <div class="card-actions">
                <el-button link type="primary" @click="handleEdit(notice)" :disabled="isNoticeExpired(notice)">编辑</el-button>
                <el-button link type="danger" @click="handleDelete(notice.id)">删除</el-button>
              </div>
            </div>
            <div class="card-content">
              <p>{{ notice.content }}</p>
            </div>
            <div class="card-footer">
              <div class="card-meta">
                <span class="priority" :class="'priority-' + notice.priority">
                  {{ getPriorityText(notice.priority) }}
                </span>
                <span class="status" :class="'status-' + getNoticeStatus(notice)">
                  {{ getStatusText(getNoticeStatus(notice)) }}
                </span>
              </div>
              <div class="card-info">
                <span class="creator">{{ notice.createUserName }}</span>
              <span class="expiration" v-if="notice.expirationDate">截止日期:{{ notice.expirationDate }}</span>
              </div>
            </div>
            <div class="card-remark" v-if="notice.remark">
              <el-icon>
                <InfoFilled/>
              </el-icon>
              <span>{{ notice.remark }}</span>
            </div>
          </div>
        </div>
      </div>
      <pagination
          v-if="maintenanceNoticePage.total > 0"
          :total="maintenanceNoticePage.total"
          :page="maintenanceNoticePage.current"
          :limit="maintenanceNoticePage.size"
          @pagination="handleMaintenanceNoticeCurrentChange"
      />
      <!-- 空状态 -->
      <div class="empty-state" v-if="holidayNotices.length === 0 && maintenanceNotices.length === 0">
        <el-empty description="暂无通知公告"/>
      </div>
        </el-tab-pane>
      </el-tabs>
    </div>
    <!-- 新增/编辑对话框 -->
@@ -154,8 +107,12 @@
          <el-col :span="12">
            <el-form-item label="公告类型" prop="type">
              <el-select v-model="form.type" placeholder="请选择公告类型" style="width: 100%">
                <el-option label="放假通知" :value="1"/>
                <el-option label="设备维修通知" :value="2"/>
                <el-option
                    v-for="item in noticeTypeList"
                    :key="item.id"
                    :label="item.noticeType"
                    :value="item.id"
                />
              </el-select>
            </el-form-item>
          </el-col>
@@ -223,24 +180,96 @@
        </div>
      </template>
    </el-dialog>
    <!-- 公告类型配置弹框 -->
    <el-dialog
        v-model="noticeTypeDialogVisible"
        title="公告类型配置"
        width="800px"
        @close="handleNoticeTypeDialogClose"
    >
      <div class="notice-type-container">
        <div class="notice-type-header">
          <el-button type="primary" @click="handleAddNoticeType">新增类型</el-button>
        </div>
        <el-table :data="noticeTypeList" border style="width: 100%">
          <el-table-column prop="id" label="ID" width="80" align="center"/>
          <el-table-column prop="noticeType" label="公告类型" align="center">
            <template #default="scope">
              <el-input
                  v-if="scope.row.editing"
                  v-model="scope.row.noticeType"
                  placeholder="请输入公告类型"
              />
              <span v-else>{{ scope.row.noticeType }}</span>
            </template>
          </el-table-column>
          <el-table-column label="操作" width="200" align="center">
            <template #default="scope">
              <el-button
                  v-if="scope.row.editing"
                  link
                  type="primary"
                  size="small"
                  @click="handleSaveNoticeType(scope.row)"
              >
                保存
              </el-button>
              <el-button
                  v-if="scope.row.editing"
                  link
                  type="info"
                  size="small"
                  @click="handleCancelEdit(scope.row)"
              >
                取消
              </el-button>
              <el-button
                  v-if="!scope.row.editing"
                  link
                  type="primary"
                  size="small"
                  @click="handleEditNoticeType(scope.row)"
              >
                编辑
              </el-button>
              <el-button
                  v-if="!scope.row.editing"
                  link
                  type="danger"
                  size="small"
                  @click="handleDeleteNoticeType(scope.row)"
              >
                删除
              </el-button>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </el-dialog>
  </div>
</template>
<script setup>
import {Search, Calendar, Tools, InfoFilled} from "@element-plus/icons-vue";
import {Calendar, InfoFilled} from "@element-plus/icons-vue";
import {onMounted, ref, reactive, toRefs, computed} from "vue";
import {ElMessage, ElMessageBox} from "element-plus";
import {useRoute} from "vue-router";
import useUserStore from "@/store/modules/user";
import {
  addNotice,
  delNotice,
  getCount,
  listNotice,
  updateNotice
  updateNotice,
  listNoticeType,
  addNoticeType,
  delNoticeType
} from "../../../api/collaborativeApproval/noticeManagement.js";
import pagination from "../../../components/PIMTable/Pagination.vue";
const userStore = useUserStore();
const route = useRoute();
// 响应式数据
const data = reactive({
@@ -280,8 +309,16 @@
// 页面状态
const dialogVisible = ref(false);
const dialogTitle = ref("");
const selectedIds = ref([]);
const formRef = ref();
// 公告类型配置相关
const noticeTypeDialogVisible = ref(false);
const noticeTypeList = ref([]);
const activeNoticeTypeTab = ref('');
// 通知数据 - 使用 Map 存储,key 为类型 id
const noticesMap = ref({});
const noticePagesMap = ref({});
// 计算属性
@@ -398,6 +435,28 @@
  });
};
const handlePublish = (notice) => {
  ElMessageBox.confirm(
      "确认发布这条公告吗?",
      "提示",
      {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "info"
      }
  ).then(() => {
    updateNotice({
      ...notice,
      status: 1
    }).then(res => {
      if (res.code === 200) {
        ElMessage.success("发布成功");
        resetTable()
      }
    })
  });
};
const submitForm = () => {
  formRef.value.validate((valid) => {
    if (valid) {
@@ -419,78 +478,257 @@
  });
};
const holidayNoticeCount = ref()
const maintenanceNoticeCount = ref()
const fetchCount = () => {
  getCount().then(res => {
    holidayNoticeCount.value = res.data.filter(item => {
      return item.type === 1
    })[0].count;
    maintenanceNoticeCount.value = res.data.filter(item => {
      return item.type === 2
    })[0].count;
  });
}
// 初始化某个类型的分页数据
const initNoticePage = (typeId) => {
  if (!noticePagesMap.value[typeId]) {
    noticePagesMap.value[typeId] = {
      total: 0,
      current: 1,
      size: 10
    };
  }
  if (!noticesMap.value[typeId]) {
    noticesMap.value[typeId] = [];
  }
};
const holidayNotices = ref([])
const maintenanceNotices = ref([])
const holidayNoticePage = ref({
  total: 0,
  current: 1,
  size: 9
})
// 获取某个类型的通知列表
const getNoticesByType = (typeId) => {
  return noticesMap.value[typeId] || [];
};
const maintenanceNoticePage = ref({
  total: 0,
  current: 1,
  size: 9
})
// 获取某个类型的分页数据
const getNoticePageByType = (typeId) => {
  return noticePagesMap.value[typeId] || { total: 0, current: 1, size: 10 };
};
const fetchHolidayNotices = () => {
  listNotice({...holidayNoticePage.value, type: 1}).then(res => {
    holidayNotices.value = res.data.records
    holidayNoticePage.value.total = res.data.total
// 获取某个类型的数量
const getNoticeCountByType = (typeId) => {
  return getNoticePageByType(typeId).total || 0;
};
// 获取某个类型的通知数据
const fetchNoticesByType = (typeId) => {
  initNoticePage(typeId);
  const pageData = noticePagesMap.value[typeId];
  listNotice({...pageData, type: typeId}).then(res => {
    if (res.code === 200) {
      noticesMap.value[typeId] = res.data.records || [];
      noticePagesMap.value[typeId].total = res.data.total || 0;
    }
  });
};
const fetchMaintenanceNotices = () => {
  listNotice({...holidayNoticePage.value, type: 2}).then(res => {
    maintenanceNotices.value = res.data.records
    maintenanceNoticePage.value.total = res.data.total
  });
// 处理分页变化
const handleNoticeCurrentChange = (typeId, val) => {
  initNoticePage(typeId);
  noticePagesMap.value[typeId].size = val.limit;
  noticePagesMap.value[typeId].current = val.page;
  fetchNoticesByType(typeId);
};
const handleHolidayNoticeCurrentChange = (val) => {
  holidayNoticePage.value.size = val.limit
  holidayNoticePage.value.current = val.page
  fetchHolidayNotices()
};
const handleMaintenanceNoticeCurrentChange = (val) => {
  maintenanceNoticePage.value.size = val.limit
  maintenanceNoticePage.value.current = val.page
  fetchMaintenanceNotices()
// 处理 tab 切换
const handleNoticeTypeTabChange = (tabName) => {
  activeNoticeTypeTab.value = tabName;
  const typeId = Number(tabName);
  fetchNoticesByType(typeId);
};
const resetTable = () => {
  holidayNoticePage.value.current = 1
  holidayNoticePage.value.size = 9
  maintenanceNoticePage.value.current = 1
  maintenanceNoticePage.value.size = 9
  fetchHolidayNotices()
  fetchMaintenanceNotices()
  fetchCount()
  // 重置所有类型的分页并重新获取数据
  noticeTypeList.value.forEach(type => {
    initNoticePage(type.id);
    noticePagesMap.value[type.id].current = 1;
    noticePagesMap.value[type.id].size = 10;
    fetchNoticesByType(type.id);
  });
};
const resetForm = () => {
  formRef.value?.resetFields();
};
// 公告类型配置相关方法
const openNoticeTypeDialog = () => {
  noticeTypeDialogVisible.value = true;
  fetchNoticeTypeList();
};
const fetchNoticeTypeList = () => {
  return listNoticeType().then(res => {
    if (res.code === 200) {
      noticeTypeList.value = res.data.map(item => ({
        ...item,
        editing: false
      }));
      // 检查路由参数中的 type
      const routeType = route.query.type;
      let targetTypeId = null;
      if (routeType) {
        // 如果路由参数中有 type,查找对应的类型
        const typeId = Number(routeType);
        const foundType = noticeTypeList.value.find(item => item.id === typeId);
        if (foundType) {
          targetTypeId = typeId;
        }
      }
      // 如果有类型数据
      if (noticeTypeList.value.length > 0) {
        // 如果路由参数指定了类型且存在,使用路由参数的类型
        // 否则如果没有选中 tab,默认选中第一个
        if (targetTypeId !== null) {
          activeNoticeTypeTab.value = String(targetTypeId);
          fetchNoticesByType(targetTypeId);
        } else if (!activeNoticeTypeTab.value) {
          activeNoticeTypeTab.value = String(noticeTypeList.value[0].id);
          fetchNoticesByType(noticeTypeList.value[0].id);
        }
      }
    }
  });
};
const handleAddNoticeType = () => {
  const newItem = {
    id: undefined,
    noticeType: '',
    editing: true
  };
  noticeTypeList.value.push(newItem);
};
const handleEditNoticeType = (row) => {
  // 保存原始值
  row.originalNoticeType = row.noticeType;
  row.editing = true;
};
const handleSaveNoticeType = (row) => {
  if (!row.noticeType || row.noticeType.trim() === '') {
    ElMessage.warning('公告类型不能为空');
    return;
  }
  const data = {
    noticeType: row.noticeType.trim()
  };
  if (row.id) {
    // 编辑模式 - 先删除再添加(因为只有 add 和 del 接口)
    delNoticeType(row.id).then(res => {
      if (res.code === 200) {
        addNoticeType(data).then(addRes => {
          if (addRes.code === 200) {
            ElMessage.success('编辑成功');
            row.editing = false;
            delete row.originalNoticeType;
            fetchNoticeTypeList().then(() => {
              // 如果当前选中的类型被编辑,需要重新获取数据
              if (activeNoticeTypeTab.value === String(row.id)) {
                fetchNoticesByType(addRes.data?.id || row.id);
              }
            });
          }
        });
      }
    });
  } else {
    // 新增模式
    addNoticeType(data).then(res => {
      if (res.code === 200) {
        ElMessage.success('新增成功');
        row.editing = false;
        fetchNoticeTypeList();
      }
    });
  }
};
const handleDeleteNoticeType = (row) => {
  // 如果没有id,说明是新增但未保存的行,直接从前端删除
  if (!row.id) {
    const index = noticeTypeList.value.indexOf(row);
    if (index > -1) {
      noticeTypeList.value.splice(index, 1);
    }
    return;
  }
  // 如果有id,调用后端接口删除
  ElMessageBox.confirm(
      "确认删除这个公告类型吗?",
      "提示",
      {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
      }
  ).then(() => {
    delNoticeType(row.id).then(res => {
      if (res.code === 200) {
        ElMessage.success("删除成功");
        // 如果删除的是当前选中的类型,切换到第一个类型
        if (activeNoticeTypeTab.value === String(row.id)) {
          fetchNoticeTypeList().then(() => {
            if (noticeTypeList.value.length > 0) {
              activeNoticeTypeTab.value = String(noticeTypeList.value[0].id);
              fetchNoticesByType(noticeTypeList.value[0].id);
            } else {
              activeNoticeTypeTab.value = '';
            }
          });
        } else {
          fetchNoticeTypeList();
        }
      }
    });
  });
};
const handleCancelEdit = (row) => {
  if (!row.id) {
    // 如果是新增但未保存的行,移除它
    const index = noticeTypeList.value.indexOf(row);
    if (index > -1) {
      noticeTypeList.value.splice(index, 1);
    }
  } else {
    // 如果是编辑中的行,取消编辑状态并恢复原值
    row.editing = false;
    if (row.originalNoticeType !== undefined) {
      row.noticeType = row.originalNoticeType;
      delete row.originalNoticeType;
    }
  }
};
const handleNoticeTypeDialogClose = () => {
  // 关闭弹框时,取消所有编辑状态
  noticeTypeList.value.forEach(item => {
    if (item.editing && !item.id) {
      // 如果是新增但未保存的行,移除它
      const index = noticeTypeList.value.indexOf(item);
      if (index > -1) {
        noticeTypeList.value.splice(index, 1);
      }
    } else if (item.editing) {
      // 如果是编辑中的行,取消编辑状态并恢复原值
      item.editing = false;
      if (item.originalNoticeType !== undefined) {
        item.noticeType = item.originalNoticeType;
        delete item.originalNoticeType;
      }
    }
  });
};
// 生命周期
onMounted(() => {
  fetchCount()
  fetchHolidayNotices()
  fetchMaintenanceNotices()
  // 先获取公告类型列表,然后根据类型获取通知数据
  fetchNoticeTypeList();
});
</script>
@@ -569,12 +807,16 @@
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
}
.holiday-card {
  border-left-color: #67c23a;
.notice-icon {
  color: #409eff;
  margin-right: 8px;
  font-size: 18px;
}
.maintenance-card {
  border-left-color: #e6a23c;
.tab-count {
  color: #909399;
  font-size: 12px;
  margin-left: 4px;
}
.urgent {
@@ -598,17 +840,6 @@
  flex: 1;
}
.holiday-icon {
  color: #67c23a;
  margin-right: 8px;
  font-size: 18px;
}
.maintenance-icon {
  color: #e6a23c;
  margin-right: 8px;
  font-size: 18px;
}
.card-actions {
  display: flex;
@@ -713,6 +944,16 @@
  text-align: right;
}
.notice-type-container {
  padding: 10px 0;
}
.notice-type-header {
  margin-bottom: 15px;
  display: flex;
  justify-content: flex-end;
}
/* 响应式设计 */
@media (max-width: 768px) {
  .notice-cards {