zhangwencui
2 天以前 ac135a715d43b83f685365dbb68a8205ca019338
运行管理模块
已添加2个文件
已修改3个文件
1187 ■■■■ 文件已修改
src/api/equipmentManagement/runManagement.js 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages.json 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/noticeManagement/index.vue 306 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/runManagement/index.vue 793 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/index.vue 65 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/equipmentManagement/runManagement.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
import request from "@/utils/request";
export const getLedgerPage = (params) => {
  return request({
    url: "/device/ledger/page",
    method: "get",
    params,
  });
};
export const editLedger = (data) => {
  return request({
    url: "/device/ledger",
    method: "put",
    data,
  });
};
src/pages.json
@@ -492,6 +492,13 @@
      }
    },
    {
      "path": "pages/equipmentManagement/runManagement/index",
      "style": {
        "navigationBarTitleText": "运行管理",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/equipmentManagement/repair/index",
      "style": {
        "navigationBarTitleText": "设备报修",
src/pages/cooperativeOffice/noticeManagement/index.vue
@@ -1,6 +1,7 @@
<template>
  <view class="notice-page">
    <PageHeader title="通知公告" @back="goBack" />
    <PageHeader title="通知公告"
                @back="goBack" />
    <!-- æœç´¢è¡¨å• -->
    <!-- <view class="search_form">
      <up-button type="primary" size="small" @click="openForm('add')">新增公告</up-button>
@@ -8,27 +9,27 @@
          åˆ é™¤
        </up-button>
    </view> -->
    <!-- é€šçŸ¥å…¬å‘Šæ¿ -->
    <view class="notice-board">
      <!-- ç»Ÿä¸€é€šçŸ¥åŒºåŸŸ -->
      <view class="notice-section" v-if="totalNoticeCount > 0">
      <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"
          <view v-for="notice in holidayNotices"
              :key="'holiday-' + notice.id"
              class="notice-card holiday-card"
              :class="{ 'urgent': notice.priority === '3' }"
          >
                :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" />
                  <up-icon name="calendar"
                           size="18"
                           color="#67c23a" />
                </view>
                <text>{{ notice.title }}</text>
              </view>
@@ -57,57 +58,59 @@
            </view>
            <view class="card-footer">
              <view class="card-meta">
                <text class="type" :class="'type-' + notice.type">
                <text class="type"
                      :class="'type-' + notice.type">
                  {{ notice.type }}
                </text>
                <text class="priority" :class="'priority-' + notice.priority">
                <text class="priority"
                      :class="'priority-' + notice.priority">
                  {{ getPriorityText(notice.priority) }}
                </text>
                <text class="status" :class="'status-' + getNoticeStatus(notice)">
                <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>
                <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" />
            <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"
          <view v-for="notice in maintenanceNotices"
              :key="'maintenance-' + notice.id"
              class="notice-card maintenance-card"
              :class="{ 'urgent': notice.priority === '3' }"
          >
                :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" />
                  <up-icon name="wrench"
                           size="18"
                           color="#e6a23c" />
                </view>
                <text>{{ notice.title }}</text>
              </view>
              <view class="card-actions">
                <up-button
                  text
                <up-button text
                  type="primary"
                  size="mini"
                  @click="handleEdit(notice)"
                  :disabled="isNoticeExpired(notice)"
                >
                           :disabled="isNoticeExpired(notice)">
                  ç¼–辑
                </up-button>
                <up-button
                  text
                <up-button text
                  type="error"
                  size="mini"
                  @click="handleDelete(notice.id)"
                >
                           @click="handleDelete(notice.id)">
                  åˆ é™¤
                </up-button>
              </view>
@@ -117,56 +120,61 @@
            </view>
            <view class="card-footer">
              <view class="card-meta">
                <text class="priority" :class="'priority-' + notice.priority">
                <text class="priority"
                      :class="'priority-' + notice.priority">
                  {{ getPriorityText(notice.priority) }}
                </text>
                <text class="status" :class="'status-' + getNoticeStatus(notice)">
                <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>
                <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" />
            <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">
      <view class="empty-state"
            v-if="holidayNotices.length === 0 && maintenanceNotices.length === 0">
        <text>暂无通知公告</text>
      </view>
    </view>
    <!-- æ–°å¢ž/编辑弹窗 -->
    <up-popup
      v-model:show="dialogVisible"
    <up-popup v-model:show="dialogVisible"
      mode="bottom"
      :round="18"
      :safeAreaInsetBottom="true"
      @close="resetForm"
    >
              @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"
          <up-form ref="formRef"
            :model="form"
            :rules="rules"
            labelWidth="80"
          >
            <up-form-item label="公告标题" prop="title">
              <up-input v-model="form.title" placeholder="请输入公告标题" />
                   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 label="公告类型"
                          prop="type">
              <up-input v-model="form.type"
                        placeholder="请输入公告类型" />
            </up-form-item>
            <up-form-item label="状态">
              <up-radio-group v-model="form.status">
@@ -174,58 +182,47 @@
                <up-radio :name="1">正式发布</up-radio>
              </up-radio-group>
            </up-form-item>
            <up-form-item label="优先级" prop="priority">
              <up-select
                v-model="form.priority"
            <up-form-item label="优先级"
                          prop="priority">
              <up-select v-model="form.priority"
                :options="priorityOptions"
                placeholder="请选择优先级"
              />
                         placeholder="请选择优先级" />
            </up-form-item>
            <up-form-item label="过期时间" prop="expirationDate">
              <up-datetime-picker
                v-model="form.expirationDate"
            <up-form-item label="过期时间"
                          prop="expirationDate">
              <up-datetime-picker v-model="form.expirationDate"
                mode="date"
                @confirm="onExpireConfirm"
              >
                <up-input
                  :value="form.expirationDate"
                                  @confirm="onExpireConfirm">
                <up-input :value="form.expirationDate"
                  placeholder="请选择日期"
                  readonly
                />
                          readonly />
              </up-datetime-picker>
            </up-form-item>
            <up-form-item label="公告内容" prop="content">
              <up-textarea
                v-model="form.content"
            <up-form-item label="公告内容"
                          prop="content">
              <up-textarea v-model="form.content"
                placeholder="请输入公告内容"
                :maxlength="500"
                count
              />
                           count />
            </up-form-item>
            <up-form-item label="备注">
              <up-textarea
                v-model="form.remark"
              <up-textarea v-model="form.remark"
                placeholder="请输入备注信息"
                :maxlength="200"
                count
              />
                           count />
            </up-form-item>
          </up-form>
        </view>
        <view class="dialog-footer">
          <up-button
            text="取消"
          <up-button text="取消"
            type="info"
            plain
            @click="dialogVisible = false"
            :customStyle="{ marginRight: '10px', flex: 1 }"
          />
          <up-button
            text="确定"
                     :customStyle="{ marginRight: '10px', flex: 1 }" />
          <up-button text="确定"
            type="primary"
            @click="submitForm"
            :customStyle="{ flex: 1 }"
          />
                     :customStyle="{ flex: 1 }" />
        </view>
      </view>
    </up-popup>
@@ -242,7 +239,7 @@
  delNotice,
  getCount,
  listNotice,
  updateNotice
    updateNotice,
} from "@/api/collaborativeApproval/noticeManagement.js";
const userStore = useUserStore();
@@ -265,19 +262,13 @@
        expirationDate: "",
  },
  rules: {
    title: [
      {required: true, message: "公告标题不能为空", trigger: "blur"}
    ],
    type: [
      {required: true, message: "请选择公告类型", trigger: "change"}
    ],
    content: [
      {required: true, message: "公告内容不能为空", trigger: "blur"}
    ],
      title: [{ required: true, message: "公告标题不能为空", trigger: "blur" }],
      type: [{ required: true, message: "请选择公告类型", trigger: "change" }],
      content: [{ required: true, message: "公告内容不能为空", trigger: "blur" }],
        expirationDate: [
      {required: true, message: "请选择日期", trigger: "change"}
    ]
  }
        { required: true, message: "请选择日期", trigger: "change" },
      ],
    },
});
const {searchForm, form, rules} = toRefs(data);
@@ -298,25 +289,24 @@
  uni.navigateBack();
};
const onExpireConfirm = (e) => {
  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": "紧急"};
  const getPriorityText = priority => {
    const priorityMap = { 1: "普通", 2: "重要", 3: "紧急" };
  return priorityMap[priority] || "普通";
};
const getStatusText = (status) => {
  const statusMap = {"0": "草稿", "1": "已发布", "2": "已过期"};
  const getStatusText = status => {
    const statusMap = { 0: "草稿", 1: "已发布", 2: "已过期" };
  return statusMap[status] || "未知";
};
const isNoticeExpired = (notice) => {
  const isNoticeExpired = notice => {
  if (!notice || !notice.expirationDate) {
    return false;
  }
@@ -332,16 +322,17 @@
  return new Date() > expiration;
};
const getNoticeStatus = (notice) => {
  const normalizedStatus = notice && notice.status !== undefined && notice.status !== null
  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') {
  const openForm = type => {
    if (type === "add") {
    dialogTitle.value = "新增公告";
    form.value = {
      id: undefined,
@@ -357,11 +348,11 @@
  dialogVisible.value = true;
};
const handleEdit = (row) => {
  const handleEdit = row => {
  if (isNoticeExpired(row)) {
    uni.showToast({
      title: "已过期的公告不可编辑",
      icon: "none"
        icon: "none",
    });
    return;
  }
@@ -370,22 +361,22 @@
  dialogVisible.value = true;
};
const handleDelete = (id) => {
  const handleDelete = id => {
  if (!id) return;
  uni.showModal({
    title: "提示",
    content: "确认删除这条公告吗?",
    success: (res) => {
      success: res => {
      if (res.confirm) {
        delNotice(id).then(() => {
          uni.showToast({
            title: "删除成功",
            icon: "success"
              icon: "success",
          });
          resetTable();
        });
      }
    }
      },
  });
};
@@ -395,105 +386,108 @@
  uni.showModal({
    title: "提示",
    content: "确认删除选中的公告吗?",
    success: (res) => {
      success: res => {
      if (res.confirm) {
        // æ ¹æ®selectedIds执行批量删除逻辑(可按需扩展)
      }
    }
      },
  });
};
const submitForm = () => {
  formRef.value.validate((valid) => {
    formRef.value.validate(valid => {
    if (valid) {
      if (form.value.id) {
        // ç¼–辑模式
        updateNotice(form.value).then(res => {
          uni.showToast({
            title: "修改成功",
            icon: "success"
              icon: "success",
          });
          resetTable();
        })
          });
      } else {
        // æ–°å¢žæ¨¡å¼
        addNotice(form.value).then(res => {
          uni.showToast({
            title: "新增成功",
            icon: "success"
              icon: "success",
          });
          resetTable();
        })
          });
      }
      dialogVisible.value = false;
    }
  });
};
const totalNoticeCount = ref(0)
  const totalNoticeCount = ref(0);
const fetchCount = () => {
  getCount().then(res => {
    totalNoticeCount.value = res.data.reduce((total, item) => total + item.count, 0);
      totalNoticeCount.value = res.data.reduce(
        (total, item) => total + item.count,
        0
      );
  });
}
  };
const holidayNotices = ref([])
const maintenanceNotices = ref([])
  const holidayNotices = ref([]);
  const maintenanceNotices = ref([]);
const holidayNoticePage = ref({
  total: 0,
  current: 1,
  size: 9
})
    size: 9,
  });
const maintenanceNoticePage = ref({
  total: 0,
  current: 1,
  size: 9
})
    size: 9,
  });
const isLoadingMore = ref(false)
  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
      const records = res?.data?.records || [];
      holidayNoticePage.value.total = res?.data?.total || 0;
    if (append && holidayNotices.value.length) {
      holidayNotices.value = [...holidayNotices.value, ...records]
        holidayNotices.value = [...holidayNotices.value, ...records];
    } else {
      holidayNotices.value = records
        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
      const records = res?.data?.records || [];
      maintenanceNoticePage.value.total = res?.data?.total || 0;
    if (append && maintenanceNotices.value.length) {
      maintenanceNotices.value = [...maintenanceNotices.value, ...records]
        maintenanceNotices.value = [...maintenanceNotices.value, ...records];
    } else {
      maintenanceNotices.value = records
        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 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()
    holidayNoticePage.value.current = 1;
    holidayNoticePage.value.size = 9;
    maintenanceNoticePage.value.current = 1;
    maintenanceNoticePage.value.size = 9;
    fetchHolidayNotices();
    fetchMaintenanceNotices();
    fetchCount();
};
const resetForm = () => {
@@ -502,9 +496,9 @@
// ç”Ÿå‘½å‘¨æœŸ
onMounted(() => {
  fetchCount()
  fetchHolidayNotices()
  fetchMaintenanceNotices()
    fetchCount();
    fetchHolidayNotices();
    fetchMaintenanceNotices();
});
// ä¸Šåˆ’加载更多
@@ -516,14 +510,14 @@
  maintenanceNoticePage.value.current += 1;
  Promise.all([
    new Promise((resolve) => {
      new Promise(resolve => {
      fetchHolidayNotices(true);
      resolve();
    }),
    new Promise((resolve) => {
      new Promise(resolve => {
      fetchMaintenanceNotices(true);
      resolve();
    })
      }),
  ]).finally(() => {
    isLoadingMore.value = false;
  });
@@ -678,7 +672,9 @@
  gap: 8px;
}
.type, .priority, .status {
  .type,
  .priority,
  .status {
  padding: 2px 8px;
  border-radius: 12px;
  font-size: 12px;
src/pages/equipmentManagement/runManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,793 @@
<template>
  <view class="notice-page">
    <PageHeader title="运行管理"
                @back="goBack" />
    <!-- é€šçŸ¥å…¬å‘Šæ¿ -->
    <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': isOverdue(notice) }">
            <view class="card-header">
              <view class="card-title">
                <view class="holiday-icon">
                  <up-icon name="calendar"
                           size="18"
                           color="#67c23a" />
                </view>
                <text>设备名称:{{ notice.deviceName }}</text>
              </view>
              <view class="card-actions warning"
                    v-if="isOverdue(notice)">
                <up-icon name="info-circle"
                         size="16"
                         color="#fff" />
                <text>{{ '超时未启动' }}</text>
              </view>
              <view v-else
                    class="card-actions"
                    :class="getTagType(notice.status)">
                <up-icon :name="getIconName(notice.status)"
                         size="16"
                         color="#fff" />
                <text>{{ notice.status || '未知' }}</text>
              </view>
            </view>
            <view class="card-content">
              <text>规格型号:{{ notice.deviceModel || "-" }}</text>
            </view>
            <view class="card-content">
              <text>计划运行时间:{{ notice.planRuntimeTime || "-" }}</text>
            </view>
            <view class="card-content">
              <text>开始运行时间:{{ notice.startRuntimeTime || "-" }}</text>
            </view>
            <view class="card-content">
              <text>结束运行时间:{{ notice.endRuntimeTime || "-" }}</text>
            </view>
            <view class="card-content">
              <text>运行时长:{{ notice.runtimeDuration || "-" }}</text>
            </view>
            <up-button text
                       v-if="isOverdue(notice)"
                       type="warning"
                       size="small"
                       @click="handleEdit(notice, '启动运行')"
                       :disabled="isNoticeExpired(notice)">
              <up-icon name="play-circle"
                       size="16"
                       style="margin-right: 10rpx;"
                       color="#fff" />
              ç«‹å³å¯åЍ
            </up-button>
            <up-button text
                       v-else-if="notice.status"
                       :type="getTagType2(notice.status)"
                       size="small"
                       @click="handleEdit(notice, notice.status === '运行中' ? '停止运行' : '启动运行')"
                       :disabled="isNoticeExpired(notice)">
              <up-icon :name="getIconName2(notice.status)"
                       size="16"
                       color="#fff" />
              {{ notice.status === '运行中' ? '停止运行' : '立即启动' }}
            </up-button>
          </view>
        </view>
      </view>
      <!-- ç©ºçŠ¶æ€ -->
      <view class="empty-state"
            v-if="holidayNotices.length === 0 && maintenanceNotices.length === 0">
        <text>暂无通知公告</text>
      </view>
    </view>
  </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";
  import {
    getLedgerPage,
    editLedger,
  } from "@/api/equipmentManagement/runManagement.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 getTagType = status => {
    switch (status) {
      case "运行中":
        return "success";
      case "停止运行":
        return "error";
      default:
        return "info";
    }
  };
  const getTagType2 = status => {
    switch (status) {
      case "停止运行":
        return "success";
      case "运行中":
        return "error";
      default:
        return "info";
    }
  };
  // èŽ·å–å›¾æ ‡åç§°
  const getIconName = status => {
    switch (status) {
      case "运行中":
        return "play-circle";
      case "停止运行":
        return "pause-circle";
      default:
        return "question-circle";
    }
  };
  // èŽ·å–å›¾æ ‡åç§°2
  const getIconName2 = status => {
    switch (status) {
      case "停止运行":
        return "play-circle";
      case "运行中":
        return "pause-circle";
      default:
        return "question-circle";
    }
  };
  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 isOverdue = notice => {
    if (
      notice.status == "运行中" ||
      !notice.planRuntimeTime ||
      notice.startRuntimeTime
    ) {
      return false;
    }
    const planTime = new Date(notice.planRuntimeTime).getTime();
    const currentTime = new Date().getTime();
    return currentTime > planTime;
  };
  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 = async (device, status) => {
    try {
      const currentTime = new Date()
        .toLocaleString("zh-CN", {
          year: "numeric",
          month: "2-digit",
          day: "2-digit",
          hour: "2-digit",
          minute: "2-digit",
          second: "2-digit",
          hour12: false,
        })
        .replace(/\//g, "-");
      // æ›´æ–°è®¾å¤‡çŠ¶æ€å’Œç›¸å…³æ—¶é—´å­—æ®µ
      if (status === "启动运行") {
        device.status = "运行中";
        device.startRuntimeTime = currentTime;
        device.endRuntimeTime = null; // æ¸…空结束时间
        device.runtimeDuration = null; // æ¸…空运行时长
      } else {
        device.status = "停止运行";
        device.endRuntimeTime = currentTime;
        // è®¡ç®—运行时长
        if (device.startRuntimeTime) {
          const startTime = new Date(device.startRuntimeTime);
          const endTime = new Date(currentTime);
          const duration = endTime - startTime;
          const hours = Math.floor(duration / (1000 * 60 * 60));
          const minutes = Math.floor((duration % (1000 * 60 * 60)) / (1000 * 60));
          device.runtimeDuration = `${hours}小时${minutes}分钟`;
        }
      }
      const params = {
        id: device.id,
        status: device.status,
        planRuntimeTime: device.planRuntimeTime,
        startRuntimeTime: device.startRuntimeTime,
        endRuntimeTime: device.endRuntimeTime,
        runtimeDuration: device.runtimeDuration,
      };
      // è°ƒç”¨API更新设备状态
      const response = await editLedger(params);
      if (response.code === 200) {
        showToast(`${device.deviceName} ${status}成功`);
        // åˆ·æ–°åˆ—表
        await fetchHolidayNotices();
      } else {
        showToast(response.msg || "操作失败");
      }
    } catch (error) {
      console.error("更新设备状态失败:", error);
      showToast("操作失败");
    }
  };
  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) => {
    getLedgerPage({}).then(res => {
      holidayNotices.value = res?.data?.records || [];
      // 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: #bec4c3;
  }
  .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;
    padding: 4rpx 8rpx;
    border-radius: 10rpx;
  }
  .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;
  }
  .success {
    background: #67c23a;
    color: #fff;
  }
  .error {
    background: #f56c6c;
    color: #fff;
  }
  .info {
    background: #a8a9aa;
    color: #fff;
  }
  .warning {
    background: #e6a23c;
    color: #fff;
  }
  .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
@@ -278,18 +278,6 @@
  // ååŒåŠžå…¬åŠŸèƒ½æ•°æ®
  const collaborationItems = reactive([
    // {
    //   icon: "/static/images/icon/gongchuguanli@2x.png",
    //   label: "公出管理",
    // },
    // {
    //   icon: "/static/images/icon/qingjiaguanli@2x.png",
    //   label: "请假管理",
    // },
    // {
    //   icon: "/static/images/icon/chuchaiguanli@2x.png",
    //   label: "出差管理",
    // },
    {
      icon: "/static/images/icon/gongchuguanli@2x.png",
      label: "考勤管理",
@@ -298,54 +286,10 @@
      icon: "/static/images/icon/baoxiaoguanli.png",
      label: "财务管理",
    },
    // {
    //   icon: "/static/images/icon/chuchaiguanli@2x.png",
    //   label: "报销管理",
    // },
    // {
    //   icon: "/static/images/icon/chuchaiguanli@2x.png",
    //   label: "采购管理",
    // },
    // {
    //   icon: "/static/images/icon/chuchaiguanli@2x.png",
    //   label: "报价管理",
    // },
    // {
    //   icon: "/static/images/icon/chuchaiguanli@2x.png",
    //   label: "出库管理",
    // },
    {
      icon: "/static/images/icon/huiyiliebiao@2x.png",
      label: "会议管理",
    },
    // {
    //   icon: "/static/images/icon/qingjiaguanli@2x.png",
    //   label: "会议设置",
    // },
    // {
    //   icon: "/static/images/icon/qingjiaguanli@2x.png",
    //   label: "会议列表",
    // },
    // {
    //   icon: "/static/images/icon/qingjiaguanli@2x.png",
    //   label: "会议申请",
    // },
    // {
    //   icon: "/static/images/icon/qingjiaguanli@2x.png",
    //   label: "会议审批",
    // },
    // {
    //   icon: "/static/images/icon/qingjiaguanli@2x.png",
    //   label: "会议发布",
    // },
    // {
    //   icon: "/static/images/icon/qingjiaguanli@2x.png",
    //   label: "会议总结",
    // },
    // {
    //   icon: "/static/images/icon/qingjiaguanli@2x.png",
    //   label: "会议看板",
    // },
    {
      icon: "/static/images/icon/tongzhigonggao@2x.png",
      label: "通知公告",
@@ -407,6 +351,10 @@
    //     icon: '/static/images/icon/shebeitaizhang@2x.png',
    //     label: '设备台账',
    // },
    {
      icon: "/static/images/icon/shbeibaoxiu@2x.png",
      label: "运行管理",
    },
    {
      icon: "/static/images/icon/shbeibaoxiu@2x.png",
      label: "设备报修",
@@ -649,6 +597,11 @@
          url: "/pages/equipmentManagement/ledger/index",
        });
        break;
      case "运行管理":
        uni.navigateTo({
          url: "/pages/equipmentManagement/runManagement/index",
        });
        break;
      case "设备报修":
        uni.navigateTo({
          url: "/pages/equipmentManagement/repair/index",