gaoluyang
6 小时以前 1e6748fbf03f44ff06967bc887076dea1f1c6e33
海川开心添加通知公告模块
已添加1个文件
已修改3个文件
870 ■■■■■ 文件已修改
src/api/collaborativeApproval/noticeManagement.js 35 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages.json 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/noticeManagement/index.vue 813 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/index.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/collaborativeApproval/noticeManagement.js
@@ -3,7 +3,7 @@
// æŸ¥è¯¢å…¬å‘Šåˆ—表
export function listNotice(query) {
  return request({
    url: '/collaborativeApproval/notice/list',
    url: '/collaborativeApproval/notice/page',
    method: 'get',
    params: query
  })
@@ -20,7 +20,7 @@
// æ–°å¢žå…¬å‘Š
export function addNotice(data) {
  return request({
    url: '/collaborativeApproval/notice',
    url: '/collaborativeApproval/notice/add',
    method: 'post',
    data: data
  })
@@ -29,41 +29,24 @@
// ä¿®æ”¹å…¬å‘Š
export function updateNotice(data) {
  return request({
    url: '/collaborativeApproval/notice',
    url: '/collaborativeApproval/notice/update',
    method: 'put',
    data: data
  })
}
// åˆ é™¤å…¬å‘Š
export function delNotice(noticeId) {
export function delNotice(ids) {
  return request({
    url: '/collaborativeApproval/notice/' + noticeId,
    method: 'delete'
  })
}
// æ‰¹é‡åˆ é™¤å…¬å‘Š
export function delNoticeBatch(noticeIds) {
  return request({
    url: '/collaborativeApproval/notice/batch',
    url: '/collaborativeApproval/notice/' + ids,
    method: 'delete',
    data: noticeIds
  })
}
// å‘布公告
export function publishNotice(noticeId) {
// èŽ·å–å…¬å‘Šæ•°é‡
export function getCount() {
  return request({
    url: '/collaborativeApproval/notice/publish/' + noticeId,
    method: 'put'
  })
}
// ä¸‹çº¿å…¬å‘Š
export function offlineNotice(noticeId) {
  return request({
    url: '/collaborativeApproval/notice/offline/' + noticeId,
    method: 'put'
    url: '/collaborativeApproval/notice/count',
    method: 'get',
  })
}
src/pages.json
@@ -317,6 +317,13 @@
      }
    },
    {
      "path": "pages/cooperativeOffice/noticeManagement/index",
      "style": {
        "navigationBarTitleText": "通知公告",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/equipmentManagement/ledger/index",
      "style": {
        "navigationBarTitleText": "设备台账",
@@ -412,8 +419,8 @@
      "style": {
        "navigationBarTitleText": "巡检上传",
        "navigationStyle": "custom",
                "enablePullDownRefresh": true, // å¼€å¯ä¸‹æ‹‰åˆ·æ–°
                "backgroundColor": "#f8f8f8" // ä¸‹æ‹‰åˆ·æ–°æ—¶çš„背景色
                "enablePullDownRefresh": true,
                "backgroundColor": "#f8f8f8"
      }
    },
    {
src/pages/cooperativeOffice/noticeManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,813 @@
<template>
  <view class="notice-page">
    <PageHeader title="通知公告" @back="goBack" />
    <!-- æœç´¢è¡¨å• -->
    <!-- <view class="search_form">
      <up-button type="primary" size="small" @click="openForm('add')">新增公告</up-button>
        <up-button type="error" size="small" plain @click="handleDeleteBatch" :disabled="!selectedIds.length">
          åˆ é™¤
        </up-button>
    </view> -->
    <!-- é€šçŸ¥å…¬å‘Šæ¿ -->
    <view class="notice-board">
      <!-- ç»Ÿä¸€é€šçŸ¥åŒºåŸŸ -->
      <view class="notice-section" v-if="totalNoticeCount > 0">
        <view class="section-header">
          <h3>� é€šçŸ¥å…¬å‘Š</h3>
          <text class="section-count">{{ totalNoticeCount }}条</text>
        </view>
        <view class="notice-cards">
          <!-- æ”¾å‡é€šçŸ¥ -->
          <view
              v-for="notice in holidayNotices"
              :key="'holiday-' + notice.id"
              class="notice-card holiday-card"
              :class="{ 'urgent': notice.priority === '3' }"
          >
            <view class="card-header">
              <view class="card-title">
                <view class="holiday-icon">
                  <up-icon name="calendar" size="18" color="#67c23a" />
                </view>
                <text>{{ notice.title }}</text>
              </view>
<!--              <view class="card-actions">-->
<!--                <up-button-->
<!--                  text-->
<!--                  type="primary"-->
<!--                  size="mini"-->
<!--                  @click="handleEdit(notice)"-->
<!--                  :disabled="isNoticeExpired(notice)"-->
<!--                >-->
<!--                  ç¼–辑-->
<!--                </up-button>-->
<!--                <up-button-->
<!--                  text-->
<!--                  type="error"-->
<!--                  size="mini"-->
<!--                  @click="handleDelete(notice.id)"-->
<!--                >-->
<!--                  åˆ é™¤-->
<!--                </up-button>-->
<!--              </view>-->
            </view>
            <view class="card-content">
              <text>{{ notice.content }}</text>
            </view>
            <view class="card-footer">
              <view class="card-meta">
                <text class="type" :class="'type-' + notice.type">
                  {{ notice.type }}
                </text>
                <text class="priority" :class="'priority-' + notice.priority">
                  {{ getPriorityText(notice.priority) }}
                </text>
                <text class="status" :class="'status-' + getNoticeStatus(notice)">
                  {{ getStatusText(getNoticeStatus(notice)) }}
                </text>
              </view>
              <view class="card-info">
                <text class="creator">{{ notice.createUserName }}</text>
                <text class="expiration" v-if="notice.expirationDate">截止日期:{{ notice.expirationDate }}</text>
              </view>
            </view>
            <view class="card-remark" v-if="notice.remark">
              <up-icon name="info-circle" size="16" color="#409eff" />
              <text>{{ notice.remark }}</text>
            </view>
          </view>
          <!-- è®¾å¤‡ç»´ä¿®é€šçŸ¥ -->
          <view
              v-for="notice in maintenanceNotices"
              :key="'maintenance-' + notice.id"
              class="notice-card maintenance-card"
              :class="{ 'urgent': notice.priority === '3' }"
          >
            <view class="card-header">
              <view class="card-title">
                <view class="maintenance-icon">
                  <up-icon name="wrench" size="18" color="#e6a23c" />
                </view>
                <text>{{ notice.title }}</text>
              </view>
              <view class="card-actions">
                <up-button
                  text
                  type="primary"
                  size="mini"
                  @click="handleEdit(notice)"
                  :disabled="isNoticeExpired(notice)"
                >
                  ç¼–辑
                </up-button>
                <up-button
                  text
                  type="error"
                  size="mini"
                  @click="handleDelete(notice.id)"
                >
                  åˆ é™¤
                </up-button>
              </view>
            </view>
            <view class="card-content">
              <text>{{ notice.content }}</text>
            </view>
            <view class="card-footer">
              <view class="card-meta">
                <text class="priority" :class="'priority-' + notice.priority">
                  {{ getPriorityText(notice.priority) }}
                </text>
                <text class="status" :class="'status-' + getNoticeStatus(notice)">
                  {{ getStatusText(getNoticeStatus(notice)) }}
                </text>
              </view>
              <view class="card-info">
                <text class="creator">{{ notice.createUserName }}</text>
                <text class="expiration" v-if="notice.expirationDate">截止日期:{{ notice.expirationDate }}</text>
              </view>
            </view>
            <view class="card-remark" v-if="notice.remark">
              <up-icon name="info-circle" size="16" color="#409eff" />
              <text>{{ notice.remark }}</text>
            </view>
          </view>
        </view>
      </view>
      <!-- ç©ºçŠ¶æ€ -->
      <view class="empty-state" v-if="holidayNotices.length === 0 && maintenanceNotices.length === 0">
        <text>暂无通知公告</text>
      </view>
    </view>
    <!-- æ–°å¢ž/编辑弹窗 -->
    <up-popup
      v-model:show="dialogVisible"
      mode="bottom"
      :round="18"
      :safeAreaInsetBottom="true"
      @close="resetForm"
    >
      <view class="dialog-container">
        <view class="dialog-header">
          <text class="dialog-title">{{ dialogTitle }}</text>
        </view>
        <view class="dialog-body">
          <up-form
            ref="formRef"
            :model="form"
            :rules="rules"
            labelWidth="80"
          >
            <up-form-item label="公告标题" prop="title">
              <up-input v-model="form.title" placeholder="请输入公告标题" />
            </up-form-item>
            <up-form-item label="公告类型" prop="type">
              <up-input v-model="form.type" placeholder="请输入公告类型" />
            </up-form-item>
            <up-form-item label="状态">
              <up-radio-group v-model="form.status">
                <up-radio :name="0">草稿</up-radio>
                <up-radio :name="1">正式发布</up-radio>
              </up-radio-group>
            </up-form-item>
            <up-form-item label="优先级" prop="priority">
              <up-select
                v-model="form.priority"
                :options="priorityOptions"
                placeholder="请选择优先级"
              />
            </up-form-item>
            <up-form-item label="过期时间" prop="expirationDate">
              <up-datetime-picker
                v-model="form.expirationDate"
                mode="date"
                @confirm="onExpireConfirm"
              >
                <up-input
                  :value="form.expirationDate"
                  placeholder="请选择日期"
                  readonly
                />
              </up-datetime-picker>
            </up-form-item>
            <up-form-item label="公告内容" prop="content">
              <up-textarea
                v-model="form.content"
                placeholder="请输入公告内容"
                :maxlength="500"
                count
              />
            </up-form-item>
            <up-form-item label="备注">
              <up-textarea
                v-model="form.remark"
                placeholder="请输入备注信息"
                :maxlength="200"
                count
              />
            </up-form-item>
          </up-form>
        </view>
        <view class="dialog-footer">
          <up-button
            text="取消"
            type="info"
            plain
            @click="dialogVisible = false"
            :customStyle="{ marginRight: '10px', flex: 1 }"
          />
          <up-button
            text="确定"
            type="primary"
            @click="submitForm"
            :customStyle="{ flex: 1 }"
          />
        </view>
      </view>
    </up-popup>
  </view>
</template>
<script setup>
import { onMounted, ref, reactive, toRefs } from "vue";
import { onReachBottom } from "@dcloudio/uni-app";
import PageHeader from "@/components/PageHeader.vue";
import useUserStore from "@/store/modules/user";
import {
  addNotice,
  delNotice,
  getCount,
  listNotice,
  updateNotice
} from "@/api/collaborativeApproval/noticeManagement.js";
const userStore = useUserStore();
// å“åº”式数据
const data = reactive({
  searchForm: {
    title: "",
    type: undefined,
    status: undefined,
  },
  form: {
    id: undefined,
    title: "",
    type: null,
    content: "",
    status: 0,
    priority: 1,
    remark: "",
        expirationDate: "",
  },
  rules: {
    title: [
      {required: true, message: "公告标题不能为空", trigger: "blur"}
    ],
    type: [
      {required: true, message: "请选择公告类型", trigger: "change"}
    ],
    content: [
      {required: true, message: "公告内容不能为空", trigger: "blur"}
    ],
        expirationDate: [
      {required: true, message: "请选择日期", trigger: "change"}
    ]
  }
});
const {searchForm, form, rules} = toRefs(data);
// é¡µé¢çŠ¶æ€
const dialogVisible = ref(false);
const dialogTitle = ref("");
const selectedIds = ref([]);
const formRef = ref();
const priorityOptions = [
  { label: "普通", value: 1 },
  { label: "重要", value: 2 },
  { label: "紧急", value: 3 },
];
const goBack = () => {
  uni.navigateBack();
};
const onExpireConfirm = (e) => {
  if (!e) return;
  // uview-plus datetime-picker confirm äº‹ä»¶è¿”回的 value
  const value = e.value || e;
  form.value.expirationDate = value;
};
const getPriorityText = (priority) => {
  const priorityMap = {"1": "普通", "2": "重要", "3": "紧急"};
  return priorityMap[priority] || "普通";
};
const getStatusText = (status) => {
  const statusMap = {"0": "草稿", "1": "已发布", "2": "已过期"};
  return statusMap[status] || "未知";
};
const isNoticeExpired = (notice) => {
  if (!notice || !notice.expirationDate) {
    return false;
  }
  const expiration = new Date(notice.expirationDate);
  if (Number.isNaN(expiration.getTime())) {
    return false;
  }
  expiration.setHours(23, 59, 59, 999);
  return new Date() > expiration;
};
const getNoticeStatus = (notice) => {
  const normalizedStatus = notice && notice.status !== undefined && notice.status !== null
      ? String(notice.status)
      : "0";
  return isNoticeExpired(notice) ? "2" : normalizedStatus;
};
const openForm = (type) => {
  if (type === 'add') {
    dialogTitle.value = "新增公告";
    form.value = {
      id: undefined,
      title: "",
      type: undefined,
      content: "",
      status: 0,
      priority: 1,
      remark: "",
      expirationDate: "",
    };
  }
  dialogVisible.value = true;
};
const handleEdit = (row) => {
  if (isNoticeExpired(row)) {
    uni.showToast({
      title: "已过期的公告不可编辑",
      icon: "none"
    });
    return;
  }
  dialogTitle.value = "编辑公告";
  form.value = {...row};
  dialogVisible.value = true;
};
const handleDelete = (id) => {
  if (!id) return;
  uni.showModal({
    title: "提示",
    content: "确认删除这条公告吗?",
    success: (res) => {
      if (res.confirm) {
        delNotice(id).then(() => {
          uni.showToast({
            title: "删除成功",
            icon: "success"
          });
          resetTable();
        });
      }
    }
  });
};
// é¢„留批量删除(目前未实现选中逻辑,仅占位)
const handleDeleteBatch = () => {
  if (!selectedIds.value.length) return;
  uni.showModal({
    title: "提示",
    content: "确认删除选中的公告吗?",
    success: (res) => {
      if (res.confirm) {
        // æ ¹æ®selectedIds执行批量删除逻辑(可按需扩展)
      }
    }
  });
};
const submitForm = () => {
  formRef.value.validate((valid) => {
    if (valid) {
      if (form.value.id) {
        // ç¼–辑模式
        updateNotice(form.value).then(res => {
          uni.showToast({
            title: "修改成功",
            icon: "success"
          });
          resetTable();
        })
      } else {
        // æ–°å¢žæ¨¡å¼
        addNotice(form.value).then(res => {
          uni.showToast({
            title: "新增成功",
            icon: "success"
          });
          resetTable();
        })
      }
      dialogVisible.value = false;
    }
  });
};
const totalNoticeCount = ref(0)
const fetchCount = () => {
  getCount().then(res => {
    totalNoticeCount.value = res.data.reduce((total, item) => total + item.count, 0);
  });
}
const holidayNotices = ref([])
const maintenanceNotices = ref([])
const holidayNoticePage = ref({
  total: 0,
  current: 1,
  size: 9
})
const maintenanceNoticePage = ref({
  total: 0,
  current: 1,
  size: 9
})
const isLoadingMore = ref(false)
const fetchHolidayNotices = (append = false) => {
  listNotice({...holidayNoticePage.value}).then(res => {
    const records = res?.data?.records || []
    holidayNoticePage.value.total = res?.data?.total || 0
    if (append && holidayNotices.value.length) {
      holidayNotices.value = [...holidayNotices.value, ...records]
    } else {
      holidayNotices.value = records
    }
  });
};
const fetchMaintenanceNotices = (append = false) => {
  listNotice({...holidayNoticePage.value, type: 2}).then(res => {
    const records = res?.data?.records || []
    maintenanceNoticePage.value.total = res?.data?.total || 0
    if (append && maintenanceNotices.value.length) {
      maintenanceNotices.value = [...maintenanceNotices.value, ...records]
    } else {
      maintenanceNotices.value = records
    }
  });
};
const handleCurrentChange = (val) => {
  holidayNoticePage.value.size = val.limit
  holidayNoticePage.value.current = val.page
  maintenanceNoticePage.value.size = val.limit
  maintenanceNoticePage.value.current = val.page
  fetchHolidayNotices()
  fetchMaintenanceNotices()
};
const resetTable = () => {
  holidayNoticePage.value.current = 1
  holidayNoticePage.value.size = 9
  maintenanceNoticePage.value.current = 1
  maintenanceNoticePage.value.size = 9
  fetchHolidayNotices()
  fetchMaintenanceNotices()
  fetchCount()
};
const resetForm = () => {
  formRef.value?.resetFields();
};
// ç”Ÿå‘½å‘¨æœŸ
onMounted(() => {
  fetchCount()
  fetchHolidayNotices()
  fetchMaintenanceNotices()
});
// ä¸Šåˆ’加载更多
onReachBottom(() => {
  if (isLoadingMore.value) return;
  isLoadingMore.value = true;
  holidayNoticePage.value.current += 1;
  maintenanceNoticePage.value.current += 1;
  Promise.all([
    new Promise((resolve) => {
      fetchHolidayNotices(true);
      resolve();
    }),
    new Promise((resolve) => {
      fetchMaintenanceNotices(true);
      resolve();
    })
  ]).finally(() => {
    isLoadingMore.value = false;
  });
});
</script>
<style scoped>
.notice-page {
  min-height: 100vh;
  background: #f5f7fa;
  padding-bottom: 16px;
  display: flex;
  flex-direction: column;
}
.search_form {
  background: #ffffff;
  padding: 12px 16px;
  margin: 8px 12px 12px;
  border-radius: 10px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.04);
  display: flex;
  justify-content: flex-start;
  align-items: center;
}
.search_title {
  font-weight: 500;
  color: #333;
  margin-right: 8px;
}
.ml10 {
  margin-left: 10px;
}
.notice-board {
  padding: 0 12px 16px;
}
.notice-section {
  margin-bottom: 16px;
}
.section-header {
  display: flex;
  align-items: center;
  margin: 4px 4px 12px;
}
.section-header h3 {
  margin: 0;
  color: #303133;
  font-size: 16px;
  font-weight: 600;
}
.section-count {
  margin-left: 10px;
  background: #409eff;
  color: white;
  padding: 2px 8px;
  border-radius: 12px;
  font-size: 12px;
}
.notice-cards {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.notice-card {
  background: white;
  border-radius: 12px;
  padding: 14px 14px 10px;
  box-shadow: 0 4px 10px rgba(15, 23, 42, 0.06);
  transition: all 0.3s ease;
  border-left: 4px solid transparent;
}
.notice-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
}
.holiday-card {
  border-left-color: #67c23a;
}
.maintenance-card {
  border-left-color: #e6a23c;
}
.urgent {
  border-left-color: #f56c6c;
  background: linear-gradient(135deg, #fff5f5 0%, #ffffff 100%);
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 10px;
}
.card-title {
  display: flex;
  align-items: center;
  font-size: 15px;
  font-weight: 600;
  color: #303133;
  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;
  gap: 8px;
}
.card-content {
  margin-bottom: 10px;
}
.card-content text {
  color: #606266;
  line-height: 1.6;
  font-size: 13px;
}
.card-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 10px;
}
.card-meta {
  display: flex;
  gap: 8px;
}
.type, .priority, .status {
  padding: 2px 8px;
  border-radius: 12px;
  font-size: 12px;
  font-weight: 500;
}
.type-1 {
  background: #f0f9ff;
  color: #0369a1;
}
.type-2 {
  background: #fef3c7;
  color: #d97706;
}
.priority-1 {
  background: #f0f9ff;
  color: #0369a1;
}
.priority-2 {
  background: #fef3c7;
  color: #d97706;
}
.priority-3 {
  background: #fef2f2;
  color: #dc2626;
}
.status-0 {
  background: #f3f4f6;
  color: #6b7280;
}
.status-1 {
  background: #d1fae5;
  color: #059669;
}
.status-2 {
  background: #fef3c7;
  color: #d97706;
}
.card-info {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  font-size: 12px;
  color: #909399;
}
.creator {
  font-weight: 500;
  margin-bottom: 2px;
}
.expiration {
  margin-top: 2px;
}
.card-remark {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 12px;
  background: #f8f9fa;
  border-radius: 6px;
  font-size: 12px;
  color: #606266;
  border-left: 3px solid #409eff;
}
.empty-state {
  text-align: center;
  padding: 48px 16px;
  color: #999;
  font-size: 13px;
}
.dialog-footer {
  text-align: right;
}
/* ç§»åŠ¨ç«¯å¼¹çª—æ ·å¼ */
.dialog-container {
  background: #ffffff;
  border-radius: 18px 18px 0 0;
  max-height: 80vh;
  display: flex;
  flex-direction: column;
}
.dialog-header {
  padding: 16px 20px 8px 20px;
}
.dialog-title {
  font-size: 16px;
  font-weight: 600;
  color: #303133;
}
.dialog-body {
  flex: 1;
  padding: 0 16px 12px 16px;
  overflow-y: auto;
}
.dialog-footer {
  display: flex;
  padding: 12px 16px 16px 16px;
  border-top: 1px solid #f0f0f0;
}
/* å“åº”式设计 */
@media (max-width: 768px) {
  .search_form {
    flex-direction: column;
    gap: 15px;
    align-items: flex-start;
  }
  .search_form > div {
    width: 100%;
    display: flex;
    gap: 10px;
  }
}
</style>
src/pages/index.vue
@@ -261,7 +261,11 @@
        {
            icon: '/static/images/icon/kehubaifang@2x.png',
            label: '客户拜访',
        }
        },
        {
            icon: '/static/images/icon/qingjiaguanli@2x.png',
            label: '通知公告',
        },
    ]);
    // ååŒåŠžå…¬åŠŸèƒ½æ•°æ®inventoryManagement/receiptManagement
    const inventoryManagement = reactive([{
@@ -430,6 +434,11 @@
                    url: '/pages/cooperativeOffice/clientVisit/index'
                });
                break;
            case '通知公告':
                uni.navigateTo({
                    url: '/pages/cooperativeOffice/noticeManagement/index'
                });
                break;
            case '自定义入库':
                uni.navigateTo({
                    url: '/pages/inventoryManagement/receiptManagement/index'