gaoluyang
6 小时以前 614b5e303ea058589c39a03e163aa0221b319dde
军泰伟业app
1.添加营销管理模块和采购管理模块并联调
已添加76个文件
已修改45个文件
已删除4个文件
34524 ■■■■ 文件已修改
src/api/collaborativeApproval/approvalProcess.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/collaborativeApproval/noticeManagement.js 35 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/cooperativeOffice/clientVisit.js 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/managementMeetings/knowledgeBase.js 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/managementMeetings/meetExamine.js 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/managementMeetings/meeting.js 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/managementMeetings/meetingSettings.js 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/managementMeetings/rulesRegulationsManagement.js 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/managementMeetings/sealManagement.js 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/attendance.js 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/monthlyStatistics.js 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/onboarding.js 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/salesManagement/deliveryLedger.js 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages.json 227 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/clientVisit/detail.vue 595 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/clientVisit/index.vue 310 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/clientVisit/view.vue 203 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/collaborativeApproval/approve.vue 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/collaborativeApproval/contactSelect.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/collaborativeApproval/detail.vue 1493 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/collaborativeApproval/index.vue 641 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/collaborativeApproval/index1.vue 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/collaborativeApproval/index2.vue 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/collaborativeApproval/index3.vue 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/collaborativeApproval/index4.vue 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/collaborativeApproval/index5.vue 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/collaborativeApproval/index6.vue 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/collaborativeApproval/index7.vue 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/collaborativeApproval/index8.vue 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/customerManage/detail.vue 493 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/noticeManagement/index.vue 809 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/inspection/detail.vue 1364 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/inspection/index.vue 881 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/repair/add.vue 840 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/repair/index.vue 347 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/repair/maintain.vue 495 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/runManagement/index.vue 548 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/upkeep/fileList.vue 562 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/upkeep/index.vue 491 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/index.vue 141 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/indexItem.vue 1124 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/managementMeetings/knowledgeBase/detail.vue 500 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/managementMeetings/knowledgeBase/index.vue 310 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/managementMeetings/meetApplication/index.vue 499 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/managementMeetings/meetExamine/approve.vue 470 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/managementMeetings/meetExamine/index.vue 352 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/managementMeetings/meetPublish/approve.vue 497 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/managementMeetings/meetPublish/index.vue 348 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/managementMeetings/meetSummary/approve.vue 560 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/managementMeetings/meetSummary/index.vue 353 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/managementMeetings/meetingBoard/index.vue 393 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/managementMeetings/meetingList/index.vue 537 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/managementMeetings/meetingSettings/detail.vue 344 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/managementMeetings/meetingSettings/index.vue 244 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/managementMeetings/meetingSettings/view.vue 178 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/managementMeetings/rulesRegulationsManagement/detail.vue 455 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/managementMeetings/rulesRegulationsManagement/fileList.vue 559 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/managementMeetings/rulesRegulationsManagement/index.vue 451 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/managementMeetings/sealManagement/detail.vue 330 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/managementMeetings/sealManagement/index.vue 348 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/procurementManagement/invoiceEntry/add.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/procurementManagement/invoiceEntry/index.vue 439 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/procurementManagement/paymentEntry/add.vue 455 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/procurementManagement/paymentEntry/index.vue 369 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/procurementManagement/paymentLedger/detail.vue 521 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/procurementManagement/paymentLedger/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/procurementManagement/procurementInvoiceLedger/detail.vue 525 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/procurementManagement/procurementInvoiceLedger/index.vue 769 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/procurementManagement/procurementLedger/detail.vue 2348 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/procurementManagement/procurementLedger/index.vue 435 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/procurementManagement/receiptPaymentHistory/index.vue 395 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/deliveryLedger/index.vue 785 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/invoiceLedger/detail.vue 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/invoiceLedger/index.vue 1155 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/invoicingRegistration/add.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/invoicingRegistration/index.vue 454 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/invoicingRegistration/view.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/receiptPayment/add.vue 477 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/receiptPayment/index.vue 370 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/receiptPaymentHistory/index.vue 449 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/receiptPaymentLedger/detail.vue 542 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/receiptPaymentLedger/index.vue 285 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/salesAccount/detail.vue 1863 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/salesAccount/goOut.vue 657 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/salesAccount/index.vue 476 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/salesAccount/out.vue 407 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/salesAccount/view.vue 340 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/icon/baojiaguanli.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/icon/baoxiaoguanli.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/icon/caigouguanli.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/icon/chuchaiguanli@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/icon/chukuguanli@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/icon/gongchuguanli@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/icon/guizhangzhidu@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/icon/huiyifabu@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/icon/huiyikanban@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/icon/huiyiliebiao@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/icon/huiyishenpi@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/icon/huiyishenqing@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/icon/huiyishezhi@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/icon/huiyizongjie@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/icon/qingjiaguanli@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/icon/rukuguanli@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/icon/tongzhigonggao@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/icon/yongyinguanli@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/icon/zhishiku@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/icon/zidingyichuku.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/index/caigoushuju.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/index/kucunshuju.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/index/num1.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/index/num2.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/index/num3.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/index/xiaoshoushuju.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/tabbar/home.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/tabbar/homeBlue.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/tabbar/home_.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/tabbar/mine.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/tabbar/mine_.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/tabbar/my.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/tabbar/myBlue.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/tabbar/new.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/tabbar/newBlue.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/tabbar/work.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/tabbar/workBlue.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/tabbar/work_.png 补丁 | 查看 | 原始文档 | blame | 历史
src/api/collaborativeApproval/approvalProcess.js
@@ -30,6 +30,7 @@
        data: query,
    })
}
// ä¿®æ”¹å®¡æ‰¹æµç¨‹
export function approveProcessUpdate(query) {
    return request({
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/api/cooperativeOffice/clientVisit.js
@@ -9,6 +9,14 @@
        data: data
    })
}
// å®¢æˆ·æ‹œè®¿ç­¾åˆ°
export function clientVisitUpdate(data) {
    return request({
        url: '/customerVisits/update',
        method: 'post',
        data: data
    })
}
// èŽ·å–æ‹œè®¿è®°å½•åˆ—è¡¨
export function getVisitRecords(query) {
@@ -17,4 +25,20 @@
        method: 'get',
        params: query
    })
}
// åˆ é™¤å®¢æˆ·æ¡£æ¡ˆ
export function delCustomer(ids) {
    return request({
        url: '/customerVisits/'+ids,
        method: 'delete',
    })
}
// æŸ¥è¯¢å®¢æˆ·æ¡£æ¡ˆè¯¦ç»†
export function getCustomer(id) {
    return request({
        url: '/basic/customer/' + id,
        method: 'get'
    })
}
src/api/managementMeetings/knowledgeBase.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
// çŸ¥è¯†åº“相关 API
import request from '@/utils/request';
export function listKnowledgeBase(params) {
  return request({
    url: '/knowledgeBase/getList',
    method: 'get',
    params
  });
}
// æ–°å¢žçŸ¥è¯†åº“
export function addKnowledgeBase(data) {
  return request({
    url: "/knowledgeBase/add",
    method: "post",
    data: data,
  });
}
// ä¿®æ”¹çŸ¥è¯†åº“
export function updateKnowledgeBase(data) {
  return request({
    url: "/knowledgeBase/update",
    method: "post",
    data: data,
  });
}
// åˆ é™¤çŸ¥è¯†åº“
export function delKnowledgeBase(query) {
  return request({
    url: "/knowledgeBase/delete",
    method: "delete",
    data: query,
  });
}
src/api/managementMeetings/meetExamine.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,60 @@
import request from '@/utils/request';
export function getExamineList(data) {
    return request({
        url: "/meeting/applicationList",
        method: "post",
        data: data,
    });
}
export function getRoomEnum() {
    return request({
        url: "/meeting/roomEnum",
        method: "get",
    });
}
export function saveMeetingApplication(data){
    return request({
        url: "/meeting/saveMeetingApplication",
        method: "post",
        data: data,
    });
}
export function getMeetingPublish(data){
    return request({
        url: "/meeting/meetingPublishList",
        method: "post",
        data: data
    });
}
export function getMeetingMinutesByMeetingId(id){
    return request({
        url: "/meeting/getMeetingMinutesByMeetingId/"+id,
        method: "get",
    });
}
export function saveMeetingMinutes(data){
    return request({
        url: "/meeting/saveMeetingMinutes",
        method: "post",
        data: data,
    });
}
export function getMeetSummary(){
    return request({
        url: "/meeting/getMeetSummary",
        method: "get",
    });
}
export function getMeetSummaryItems(){
    return request({
        url: "/meeting/getMeetSummaryItems",
        method: "get",
    });
}
src/api/managementMeetings/meeting.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
import request from '@/utils/request';
// èŽ·å–ä¼šè®®ä½¿ç”¨åˆ—è¡¨
export function getMeetingUseList(data){
    return request({
        url: "/meeting/meetingUseList",
        method: "post",
        data: data,
    });
}
// ä¿å­˜ä¼šè®®ç”³è¯·
export function saveMeetingApplication(data){
    return request({
        url: "/meeting/saveMeetingApplication",
        method: "post",
        data: data,
    });
}
export function getRoomEnum() {
    return request({
        url: "/meeting/roomEnum",
        method: "get",
    });
}
src/api/managementMeetings/meetingSettings.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
// ä¼šè®®ç®¡ç†
import request from "@/utils/request";
export function getMeetingRoomList(data) {
    return request({
        url: "/meeting/roomList",
        method: "post",
        data: data,
    });
}
export function saveRoom(data) {
    return request({
        url: "/meeting/saveRoom",
        method: "post",
        data: data,
    });
}
export function delRoom(id) {
    return request({
        url: "/meeting/delRoom/"+id,
        method: "delete",
    });
}
src/api/managementMeetings/rulesRegulationsManagement.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,82 @@
import request from '@/utils/request';
// æŸ¥è¯¢è§„章制度列表
export function listRuleManagement(page,query) {
  return request({
    url: "/rulesRegulationsManagement/getList",
    method: "get",
    params: {
      ...page,
      ...query},
  });
}
// æŸ¥è¯¢é˜…读状态列表
export function getReadingStatusList(page,query) {
  return request({
    url: "/rulesRegulationsManagement/getReadingStatusList",
    method: "get",
    params: {
      ...page,
      ...query},
  });
}
// æ ¹æ®è§„则id查询阅读状态列表
export function getReadingStatusByRuleId(id) {
  return request({
    url: "/rulesRegulationsManagement/getReadingStatusByRuleId/"+id,
    method: "get"
  });
}
// ä¿®æ”¹è§„章制度
export function updateRuleManagement(data) {
  return request({
    url: "/rulesRegulationsManagement/update",
    method: "post",
    data: data,
  });
}
// æ–°å¢žè§„章制度
export function addRuleManagement(data) {
  return request({
    url: "/rulesRegulationsManagement/add",
    method: "post",
    data: data,
  });
}
// é™„件列表
export function listRuleFiles(query) {
  return request({
    url: "/rulesRegulationsManagementFile/listPage",
    method: "get",
    params: query,
  });
}
// æ–°å¢žé™„ä»¶
export function addRuleFile(data) {
  return request({
    url: "/rulesRegulationsManagementFile/add",
    method: "post",
    data,
  });
}
// åˆ é™¤é™„件(支持传递 id æ•°ç»„)
export function delRuleFile(ids) {
  return request({
    url: "/rulesRegulationsManagementFile/del",
    method: "delete",
    data: ids,
  });
}
// ä¸Šä¼ é™„ä»¶
export function upload(query) {
  return request({
    url: "/file/upload",
    method: "post",
    data: query,
    responseType: "blob",
  });
}
src/api/managementMeetings/sealManagement.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
import request from "@/utils/request";
// æŸ¥è¯¢å°ç« ç”³è¯·åˆ—表
export function listSealApplication(page,query) {
  return request({
    url: "/sealApplicationManagement/getList",
    method: "get",
    params: {
      ...page,
      ...query},
  });
}
// ä¿®æ”¹å°ç« ç”³è¯·
export function updateSealApplication(data) {
  return request({
    url: "/sealApplicationManagement/update",
    method: "post",
    data: data,
  });
}
export function addSealApplication(data) {
  return request({
    url: "/sealApplicationManagement/add",
    method: "post",
    data: data,
  });
}
src/api/personnelManagement/attendance.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
import request from '@/utils/request'
export function createPersonalAttendanceRecord(params) {
    return request({
        url: "/personalAttendanceRecords",
        method: "post",
        data: params,
    });
}
export function findPersonalAttendanceRecords(query) {
    return request({
        url: "/personalAttendanceRecords/listPage",
        method: "get",
        params: query,
    });
}
export function findTodayPersonalAttendanceRecord(query) {
    return request({
        url: "/personalAttendanceRecords/today",
        method: "get",
        params: query,
    });
}
src/api/personnelManagement/monthlyStatistics.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,65 @@
import request from "@/utils/request";
// äººå‘˜è–ªèµ„台账列表
export function monthlyStatisticsListPage(query) {
  return request({
    url: "/compensationPerformance/listPage",
    method: "get",
    params: query,
  });
}
// äººå‘˜è–ªèµ„台账详情
export function monthlyStatisticsGet(id) {
  return request({
    url: "/monthlyStatistics/get",
    method: "get",
    params: { id },
  });
}
// æ–°å¢žäººå‘˜è–ªèµ„台账
export function monthlyStatisticsAdd(data) {
  return request({
    url: "/compensationPerformance/add",
    method: "post",
    data,
  });
}
// ç¼–辑人员薪资台账
export function monthlyStatisticsUpdate(data) {
  return request({
    url: "/compensationPerformance/update",
    method: "post",
    data,
  });
}
// åˆ é™¤äººå‘˜è–ªèµ„台账
export function monthlyStatisticsDelete(ids) {
  return request({
    url: "/compensationPerformance/delete",
    method: "delete",
    data: ids,
  });
}
// å¯¼å‡ºäººå‘˜è–ªèµ„台账
export function monthlyStatisticsExport(query) {
  return request({
    url: "/compensationPerformance/export",
    method: "get",
    params: query,
    responseType: "blob",
  });
}
// äººå‘˜åˆ—表
export function staffOnJobList(query) {
  return request({
    url: "/staff/staffOnJob/list",
    method: "get",
    params: query,
  });
}
src/api/personnelManagement/onboarding.js
@@ -47,3 +47,11 @@
    method: "get",
  });
}
// æŸ¥è¯¢åœ¨èŒå‘˜å·¥å°è´¦
export function staffOnJobListPage(query) {
    return request({
        url: '/staff/staffOnJob/listPage',
        method: 'get',
        params: query,
    })
}
src/api/salesManagement/deliveryLedger.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,86 @@
// å‘货台账页面接口
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function deliveryLedgerListPage(query) {
  return request({
    url: "/shippingInfo/listPage",
    method: "get",
    params: query,
  });
}
// ä¿®æ”¹å‘货台账
export function addOrUpdateDeliveryLedger(query) {
  return request({
    url: "/shippingInfo/update",
    method: "post",
    data: query,
  });
}
// ä¿®æ”¹å‘货台账
export function deductStock(query) {
  return request({
    url: "/shippingInfo/deductStock",
    method: "post",
    data: query,
  });
}
// åˆ é™¤å‘货台账
export function delDeliveryLedger(query) {
  return request({
    url: "/shippingInfo/delete",
    method: "delete",
    data: query,
  });
}
// æ–°å¢žå‘货信息
export function addShippingInfo(data) {
  return request({
    url: "/shippingInfo/add",
    method: "post",
    data,
  });
}
// å‘货明细接口
// åˆ†é¡µæŸ¥è¯¢å‘货明细
export function shippingInfoDetailListPage(query) {
  return request({
    url: "/shippingInfoDetail/listPage",
    method: "get",
    params: query,
  });
}
// æ–°å¢žå‘货明细
export function addShippingInfoDetail(data) {
  return request({
    url: "/shippingInfoDetail/add",
    method: "post",
    data,
  });
}
// ä¿®æ”¹å‘货明细
export function updateShippingInfoDetail(data) {
  return request({
    url: "/shippingInfoDetail/update",
    method: "post",
    data,
  });
}
// åˆ é™¤å‘货明细
export function delShippingInfoDetail(ids) {
  return request({
    url: "/shippingInfoDetail/delete",
    method: "delete",
    data: ids,
  });
}
src/pages.json
@@ -58,6 +58,27 @@
      }
    },
    {
      "path": "pages/sales/deliveryLedger/index",
      "style": {
        "navigationBarTitleText": "发货台账",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/sales/salesAccount/out",
      "style": {
        "navigationBarTitleText": "发货状态",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/sales/salesAccount/goOut",
      "style": {
        "navigationBarTitleText": "发货",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/sales/salesAccount/detail",
      "style": {
        "navigationBarTitleText": "修改台账",
@@ -247,9 +268,184 @@
      }
    },
    {
      "path": "pages/cooperativeOffice/collaborativeApproval/index",
      "path": "pages/cooperativeOffice/collaborativeApproval/index1",
      "style": {
        "navigationBarTitleText": "审批管理",
        "navigationBarTitleText": "公出管理",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/cooperativeOffice/collaborativeApproval/index2",
      "style": {
        "navigationBarTitleText": "请假管理",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/cooperativeOffice/collaborativeApproval/index3",
      "style": {
        "navigationBarTitleText": "出差管理",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/cooperativeOffice/collaborativeApproval/index4",
      "style": {
        "navigationBarTitleText": "报销管理",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/indexItem",
      "style": {
        "navigationBarTitleText": "考勤管理",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/cooperativeOffice/collaborativeApproval/index5",
      "style": {
        "navigationBarTitleText": "采购管理",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/cooperativeOffice/collaborativeApproval/index6",
      "style": {
        "navigationBarTitleText": "报价管理",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/cooperativeOffice/collaborativeApproval/index7",
      "style": {
        "navigationBarTitleText": "发货审批",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/managementMeetings/meetingSettings/index",
      "style": {
        "navigationBarTitleText": "会议设置",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/managementMeetings/meetingSettings/detail",
      "style": {
        "navigationBarTitleText": "会议室详情",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/managementMeetings/meetingList/index",
      "style": {
        "navigationBarTitleText": "会议列表",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/managementMeetings/meetApplication/index",
      "style": {
        "navigationBarTitleText": "会议申请",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/managementMeetings/meetExamine/index",
      "style": {
        "navigationBarTitleText": "会议审批",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/managementMeetings/meetExamine/approve",
      "style": {
        "navigationBarTitleText": "审批",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/managementMeetings/meetPublish/index",
      "style": {
        "navigationBarTitleText": "会议发布",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/managementMeetings/meetPublish/approve",
      "style": {
        "navigationBarTitleText": "发布",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/managementMeetings/meetSummary/index",
      "style": {
        "navigationBarTitleText": "会议总结",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/managementMeetings/meetingBoard/index",
      "style": {
        "navigationBarTitleText": "会议看板",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/managementMeetings/meetSummary/approve",
      "style": {
        "navigationBarTitleText": "总结",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/managementMeetings/knowledgeBase/index",
      "style": {
        "navigationBarTitleText": "知识库",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/managementMeetings/sealManagement/index",
      "style": {
        "navigationBarTitleText": "用印管理",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/managementMeetings/sealManagement/detail",
      "style": {
        "navigationBarTitleText": "用印详情",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/managementMeetings/rulesRegulationsManagement/index",
      "style": {
        "navigationBarTitleText": "规章制度管理",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/managementMeetings/rulesRegulationsManagement/detail",
      "style": {
        "navigationBarTitleText": "规章制度详情",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/managementMeetings/rulesRegulationsManagement/fileList",
      "style": {
        "navigationBarTitleText": "规章制度文件管理",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/managementMeetings/knowledgeBase/detail",
      "style": {
        "navigationBarTitleText": "知识库详情",
        "navigationStyle": "custom"
      }
    },
@@ -296,6 +492,13 @@
      }
    },
    {
      "path": "pages/cooperativeOffice/noticeManagement/index",
      "style": {
        "navigationBarTitleText": "通知公告",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/equipmentManagement/ledger/index",
      "style": {
        "navigationBarTitleText": "设备台账",
@@ -306,6 +509,13 @@
      "path": "pages/equipmentManagement/ledger/detail",
      "style": {
        "navigationBarTitleText": "设备台账详情",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/equipmentManagement/runManagement/index",
      "style": {
        "navigationBarTitleText": "运行管理",
        "navigationStyle": "custom"
      }
    },
@@ -348,6 +558,13 @@
      "path": "pages/equipmentManagement/upkeep/maintain",
      "style": {
        "navigationBarTitleText": "维修保养",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/equipmentManagement/upkeep/fileList",
      "style": {
        "navigationBarTitleText": "保养文件管理",
        "navigationStyle": "custom"
      }
    },
@@ -628,13 +845,13 @@
      {
        "pagePath": "pages/index",
        "iconPath": "static/images/tabbar/home.png",
        "selectedIconPath": "static/images/tabbar/home_.png",
        "selectedIconPath": "static/images/tabbar/homeBlue.png",
        "text": "首页"
      },
      {
        "pagePath": "pages/mine",
        "iconPath": "static/images/tabbar/mine.png",
        "selectedIconPath": "static/images/tabbar/mine_.png",
        "iconPath": "static/images/tabbar/my.png",
        "selectedIconPath": "static/images/tabbar/myBlue.png",
        "text": "我的"
      }
    ]
src/pages/cooperativeOffice/clientVisit/detail.vue
@@ -1,324 +1,383 @@
<template>
  <view class="client-visit-detail">
    <PageHeader title="客户拜访详情" @back="goBack" />
    <u-form @submit="handleSignIn" ref="formRef" label-width="90">
    <PageHeader title="客户拜访详情"
                @back="goBack" />
    <u-form @submit="handleSignIn"
            ref="formRef"
            label-width="90">
      <!-- å®¢æˆ·ä¿¡æ¯ -->
      <u-cell-group title="客户信息">
        <u-form-item label="客户名称" prop="customerName" required border-bottom>
          <u-input
            v-model="form.customerName"
            placeholder="请输入客户名称"
          />
        <u-form-item label="客户名称"
                     prop="customerName"
                     required
                     border-bottom>
          <u-input v-model="form.customerName"
                   placeholder="请输入客户名称" />
        </u-form-item>
        <u-form-item label="联系人" prop="contact" border-bottom>
          <u-input
            v-model="form.contact"
            placeholder="请输入联系人"
          />
        <u-form-item label="联系人"
                     prop="contact"
                     border-bottom>
          <u-input v-model="form.contact"
                   placeholder="请输入联系人" />
        </u-form-item>
        <u-form-item label="联系电话" prop="contactPhone" border-bottom>
          <u-input
            v-model="form.contactPhone"
            placeholder="请输入联系电话"
          />
        <u-form-item label="联系电话"
                     prop="contactPhone"
                     border-bottom>
          <u-input v-model="form.contactPhone"
                   placeholder="请输入联系电话" />
        </u-form-item>
      </u-cell-group>
      <!-- æ‹œè®¿ä¿¡æ¯ -->
      <u-cell-group title="拜访信息">
        <u-form-item label="拜访目的" prop="purposeVisit" required border-bottom>
          <u-input
            v-model="form.purposeVisit"
            placeholder="请输入拜访目的"
          />
        <u-form-item label="拜访目的"
                     prop="purposeVisit"
                     required
                     border-bottom>
          <u-input v-model="form.purposeVisit"
                   placeholder="请输入拜访目的" />
        </u-form-item>
        <u-form-item label="拜访时间" prop="purposeDate" required border-bottom>
          <u-input
            v-model="form.purposeDate"
            placeholder="请选择拜访时间"
            @click="showTimePicker"
          />
        <u-form-item label="拜访时间"
                     prop="purposeDate"
                     required
                     border-bottom>
          <u-input v-model="form.purposeDate"
                   placeholder="请选择拜访时间"
                   @click="showTimePicker" />
          <template #right>
                    <up-icon
                        name="arrow-right"
                        @click="showTimePicker"
                    ></up-icon>
                </template>
            <up-icon name="arrow-right"
                     @click="showTimePicker"></up-icon>
          </template>
        </u-form-item>
        <u-form-item label="拜访地点" prop="visitAddress" required border-bottom>
          <u-input
            v-model="form.visitAddress"
            placeholder="请输入拜访地点"
          >
        <u-form-item label="拜访地点"
                     prop="visitAddress"
                     required
                     border-bottom>
          <u-input v-model="form.visitAddress"
                   placeholder="请输入拜访地点">
            <template #suffix>
              <u-icon name="map" @click="getCurrentLocation" class="location-icon" />
              <u-icon name="map"
                      @click="getCurrentLocation"
                      class="location-icon" />
            </template>
          </u-input>
        </u-form-item>
      </u-cell-group>
      <!-- å¤‡æ³¨ä¿¡æ¯ -->
      <u-cell-group title="备注信息">
        <u-form-item label="备注" prop="remark" border-bottom>
          <u-textarea
            v-model="form.remark"
            placeholder="请输入备注信息"
            :maxlength="200"
            count
            :autoHeight="true"
          />
        <u-form-item label="备注"
                     prop="remark"
                     border-bottom>
          <u-textarea v-model="form.remark"
                      placeholder="请输入备注信息"
                      :maxlength="200"
                      count
                      :autoHeight="true" />
        </u-form-item>
      </u-cell-group>
      <!-- æäº¤æŒ‰é’® -->
      <view class="footer-btns">
        <u-button class="cancel-btn" @click="goBack">取消</u-button>
        <u-button class="sign-btn" type="primary" @click="handleSignIn" :loading="loading">签到</u-button>
        <u-button class="cancel-btn"
                  @click="goBack">取消</u-button>
        <u-button class="sign-btn"
                  type="primary"
                  @click="handleSignIn"
                  :loading="loading">签到</u-button>
      </view>
    </u-form>
    <!-- æ—¶é—´é€‰æ‹©å™¨ -->
    <up-datetime-picker
                    :show="showTime"
                    v-model="currentTime"
                    @confirm="onTimeConfirm"
                    @cancel="showTime = false"
                    mode="datetime"
                />
    <up-datetime-picker :show="showTime"
                        v-model="currentTime"
                        @confirm="onTimeConfirm"
                        @cancel="showTime = false"
                        mode="datetime" />
  </view>
</template>
<script setup>
// æ›¿æ¢ toast æ–¹æ³•
defineOptions({name: 'client-visit-detail'})
const showToast = (message) => {
  uni.showToast({
    title: message,
    icon: 'none'
  })
}
  // æ›¿æ¢ toast æ–¹æ³•
  defineOptions({ name: "client-visit-detail" });
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
import { ref, onMounted } from 'vue'
import PageHeader from '@/components/PageHeader.vue'
import { clientVisitSignIn } from '@/api/cooperativeOffice/clientVisit'
import useUserStore from "@/store/modules/user"
import dayjs from "dayjs"
  import { ref, onMounted } from "vue";
  import PageHeader from "@/components/PageHeader.vue";
  import {
    clientVisitSignIn,
    clientVisitUpdate,
  } from "@/api/cooperativeOffice/clientVisit";
  import useUserStore from "@/store/modules/user";
  import dayjs from "dayjs";
  import { onLoad } from "@dcloudio/uni-app";
const userStore = useUserStore()
  const userStore = useUserStore();
// è¡¨å•数据
const form = ref({
  customerName: '',
  contact: '',
  contactPhone: '',
  visitingPeople: '',
  purposeVisit: '',
  purposeDate: '',
  visitAddress: '',
  latitude: '',
  longitude: '',
  locationAddress: '',
  remark: ''
})
  // è¡¨å•数据
  const form = ref({
    customerName: "",
    contact: "",
    contactPhone: "",
    visitingPeople: "",
    purposeVisit: "",
    purposeDate: "",
    visitAddress: "",
    latitude: "",
    longitude: "",
    locationAddress: "",
    remark: "",
  });
// é¡µé¢çŠ¶æ€
const loading = ref(false)
const formRef = ref(null)
  // é¡µé¢çŠ¶æ€
  const loading = ref(false);
  const formRef = ref(null);
// æ—¶é—´ç›¸å…³
const currentTime = ref(Date.now())
const showTime = ref(false)
  // æ—¶é—´ç›¸å…³
  const currentTime = ref(Date.now());
  const showTime = ref(false);
// è¿”回上一页
const goBack = () => {
  // è¿”回时清除本地存储的ID
  uni.removeStorageSync('clientVisit')
  uni.navigateBack()
}
  // è¿”回上一页
  const goBack = () => {
    // è¿”回时清除本地存储的ID
    uni.removeStorageSync("clientVisit");
    uni.navigateBack();
  };
// æ˜¾ç¤ºæ—¶é—´é€‰æ‹©å™¨
const showTimePicker = () => {
  showTime.value = true
}
  // æ˜¾ç¤ºæ—¶é—´é€‰æ‹©å™¨
  const showTimePicker = () => {
    showTime.value = true;
  };
// ç¡®è®¤æ—¶é—´é€‰æ‹©
const onTimeConfirm = (e) => {
  console.log(e)
  form.value.purposeDate = dayjs(e.value).format('YYYY-MM-DD HH:mm:ss')
    currentTime.value = e.value
    showTime.value = false;
}
  // ç¡®è®¤æ—¶é—´é€‰æ‹©
  const onTimeConfirm = e => {
    console.log(e);
    form.value.purposeDate = dayjs(e.value).format("YYYY-MM-DD HH:mm:ss");
    currentTime.value = e.value;
    showTime.value = false;
  };
// èŽ·å–å½“å‰ä½ç½®
const getCurrentLocation = () => {
  uni.showLoading({ title: '获取位置中...' })
  uni.getLocation({
    type: 'gcj02',
    success: (res) => {
      form.value.latitude = res.latitude
      form.value.longitude = res.longitude
      // ä½¿ç”¨é€†åœ°ç†ç¼–码获取地址信息
      uni.request({
        url: `https://restapi.amap.com/v3/geocode/regeo?key=c120a5dc69a9f61839f7763e6057005f&location=${res.longitude},${res.latitude}&radius=1000&extensions=all`,
        success: (geoRes) => {
          uni.hideLoading()
          if (geoRes.data.status === '1' && geoRes.data.regeocode) {
            const regeocode = geoRes.data.regeocode
            const address = regeocode.formatted_address
            // ä¼˜å…ˆæ˜¾ç¤ºè¯¦ç»†åœ°å€
            if (address) {
              form.value.visitAddress = address
              showToast('位置获取成功')
  // èŽ·å–å½“å‰ä½ç½®
  const getCurrentLocation = () => {
    uni.showLoading({ title: "获取位置中..." });
    uni.getLocation({
      type: "gcj02",
      success: res => {
        form.value.latitude = res.latitude;
        form.value.longitude = res.longitude;
        // ä½¿ç”¨é€†åœ°ç†ç¼–码获取地址信息
        uni.request({
          url: `https://restapi.amap.com/v3/geocode/regeo?key=c120a5dc69a9f61839f7763e6057005f&location=${res.longitude},${res.latitude}&radius=1000&extensions=all`,
          success: geoRes => {
            uni.hideLoading();
            if (geoRes.data.status === "1" && geoRes.data.regeocode) {
              const regeocode = geoRes.data.regeocode;
              const address = regeocode.formatted_address;
              // ä¼˜å…ˆæ˜¾ç¤ºè¯¦ç»†åœ°å€
              if (address) {
                form.value.visitAddress = address;
                showToast("位置获取成功");
              } else {
                // å¦‚果没有详细地址,尝试组合地址信息
                const addressComponent = regeocode.addressComponent;
                const combinedAddress = `${addressComponent.province}${addressComponent.city}${addressComponent.district}${addressComponent.township}`;
                form.value.visitAddress = combinedAddress;
                showToast("位置获取成功");
              }
            } else {
              // å¦‚果没有详细地址,尝试组合地址信息
              const addressComponent = regeocode.addressComponent
              const combinedAddress = `${addressComponent.province}${addressComponent.city}${addressComponent.district}${addressComponent.township}`
              form.value.visitAddress = combinedAddress
              showToast('位置获取成功')
              // API调用成功但没有返回地址信息
              const fallbackAddress = `位置: ${res.latitude.toFixed(
                4
              )}, ${res.longitude.toFixed(4)}`;
              form.value.visitAddress = fallbackAddress;
              showToast("获取到位置,但地址解析失败");
            }
          } else {
            // API调用成功但没有返回地址信息
            const fallbackAddress = `位置: ${res.latitude.toFixed(4)}, ${res.longitude.toFixed(4)}`
            form.value.visitAddress = fallbackAddress
            showToast('获取到位置,但地址解析失败')
          }
        },
        fail: (err) => {
          uni.hideLoading()
          console.error('逆地理编码失败:', err)
          // é€†åœ°ç†ç¼–码失败时,显示简化的位置信息
          const fallbackAddress = `位置: ${res.latitude.toFixed(4)}, ${res.longitude.toFixed(4)}`
          form.value.visitAddress = fallbackAddress
          showToast('位置获取成功,但地址解析失败')
          },
          fail: err => {
            uni.hideLoading();
            console.error("逆地理编码失败:", err);
            // é€†åœ°ç†ç¼–码失败时,显示简化的位置信息
            const fallbackAddress = `位置: ${res.latitude.toFixed(
              4
            )}, ${res.longitude.toFixed(4)}`;
            form.value.visitAddress = fallbackAddress;
            showToast("位置获取成功,但地址解析失败");
          },
        });
      },
      fail: err => {
        uni.hideLoading();
        console.error("获取位置失败:", err);
        // æ˜¾ç¤ºé”™è¯¯æç¤ºå¹¶å¼•导用户检查权限
        showToast("获取位置失败,请检查定位权限");
        // å¼•导用户检查权限设置
        uni.showModal({
          title: "位置权限提示",
          content:
            "获取位置失败,可能是因为位置权限未开启,请在设备设置中检查并开启位置权限。",
          confirmText: "知道了",
          cancelText: "取消",
          success: res => {
            if (res.confirm) {
              // å¯ä»¥å°è¯•打开设置页面(如果支持)
              if (uni.openSetting) {
                uni.openSetting({
                  success: settingRes => {
                    console.log("设置结果:", settingRes);
                  },
                });
              }
            }
          },
        });
        // å¤±è´¥æ—¶æ˜¾ç¤ºé”™è¯¯ä¿¡æ¯
        form.value.visitAddress = "位置获取失败";
      },
    });
  };
  // æäº¤ç­¾åˆ°
  const handleSignIn = async () => {
    if (!form.value.customerName) {
      showToast("请输入客户名称");
      return;
    }
    if (!form.value.purposeVisit) {
      showToast("请输入拜访目的");
      return;
    }
    if (!form.value.purposeDate) {
      showToast("请选择拜访时间");
      return;
    }
    if (!form.value.visitAddress) {
      showToast("请获取当前位置");
      return;
    }
    try {
      loading.value = true;
      // ä½¿ç”¨å®‰å…¨æµ…拷贝,避免对象展开在某些运行时抛错
      const source =
        form.value && typeof form.value === "object" ? form.value : {};
      const submitData = {};
      Object.keys(source).forEach(k => {
        submitData[k] = source[k];
      });
      console.log("submitData", submitData);
      if (isEdit.value) {
        const { code } = await clientVisitUpdate(submitData);
        if (code === 200) {
          showToast("修改成功");
          setTimeout(() => {
            goBack();
          }, 500);
        } else {
          loading.value = false;
          showToast("签到失败,请重试");
        }
      })
    },
    fail: (err) => {
      uni.hideLoading()
      showToast('获取位置失败,请检查定位权限')
      console.error('获取位置失败:', err)
      // å¤±è´¥æ—¶æ˜¾ç¤ºé”™è¯¯ä¿¡æ¯
      form.value.visitAddress = '位置获取失败'
      } else {
        const { code } = await clientVisitSignIn(submitData);
        if (code === 200) {
          showToast("签到成功");
          setTimeout(() => {
            goBack();
          }, 500);
        } else {
          loading.value = false;
          showToast("签到失败,请重试");
        }
      }
    } catch (e) {
      loading.value = false;
      console.error("签到失败:", e);
    }
  })
}
// æäº¤ç­¾åˆ°
const handleSignIn = async () => {
  if (!form.value.customerName) {
    showToast('请输入客户名称')
    return
  }
  if (!form.value.purposeVisit) {
    showToast('请输入拜访目的')
    return
  }
  if (!form.value.purposeDate) {
    showToast('请选择拜访时间')
    return
  }
  if (!form.value.visitAddress) {
    showToast('请获取当前位置')
    return
  }
  try {
    loading.value = true
    // ä½¿ç”¨å®‰å…¨æµ…拷贝,避免对象展开在某些运行时抛错
    const source = (form.value && typeof form.value === 'object') ? form.value : {}
    const submitData = {}
    Object.keys(source).forEach((k) => {
      submitData[k] = source[k]
    })
        console.log('submitData', submitData)
    const { code } = await clientVisitSignIn(submitData)
    if (code === 200) {
      showToast('签到成功')
      setTimeout(() => {
                goBack()
      }, 500)
  };
  const isEdit = ref(false);
  onLoad(() => {
    // ç¼–辑拜访时,从本地存储获取拜访记录
    const visit = uni.getStorageSync("clientVisit");
    if (visit) {
      form.value = visit;
      isEdit.value = true;
      console.log("form.value", form.value);
    } else {
      loading.value = false
      showToast('签到失败,请重试')
      isEdit.value = false;
    }
  } catch (e) {
    loading.value = false
    console.error('签到失败:', e)
  }
}
  });
// åˆå§‹åŒ–页面数据
const initPageData = () => {
  // è®¾ç½®é»˜è®¤æ‹œè®¿æ—¶é—´ä¸ºå½“前时间
  form.value.purposeDate = dayjs().format('YYYY-MM-DD HH:mm:ss')
  currentTime.value = Date.now()
  // è®¾ç½®æ‹œè®¿äººä¸ºå½“前登录用户的昵称
  form.value.visitingPeople = userStore.nickName || ''
}
  // åˆå§‹åŒ–页面数据
  const initPageData = () => {
    // è®¾ç½®é»˜è®¤æ‹œè®¿æ—¶é—´ä¸ºå½“前时间
    form.value.purposeDate = dayjs().format("YYYY-MM-DD HH:mm:ss");
    currentTime.value = Date.now();
onMounted(() => {
  initPageData()
})
    // è®¾ç½®æ‹œè®¿äººä¸ºå½“前登录用户的昵称
    form.value.visitingPeople = userStore.nickName || "";
  };
  onMounted(() => {
    initPageData();
  });
</script>
<style scoped lang="scss">
@import '@/static/scss/form-common.scss';
.client-visit {
  min-height: 100vh;
  background: #f8f9fa;
  padding-bottom: 5rem;
}
  @import "@/static/scss/form-common.scss";
  .client-visit {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 5rem;
  }
.footer-btns {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  background: #fff;
  display: flex;
  justify-content: space-around;
  align-items: center;
  padding: 0.75rem 0;
  box-shadow: 0 -0.125rem 0.5rem rgba(0,0,0,0.05);
  z-index: 1000;
}
  .footer-btns {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    background: #fff;
    display: flex;
    justify-content: space-around;
    align-items: center;
    padding: 0.75rem 0;
    box-shadow: 0 -0.125rem 0.5rem rgba(0, 0, 0, 0.05);
    z-index: 1000;
  }
.cancel-btn {
  font-weight: 400;
  font-size: 1rem;
  color: #666;
  background: #f5f5f5;
  border: 1px solid #ddd;
  width: 45%;
  height: 2.5rem;
  border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
}
  .cancel-btn {
    font-weight: 400;
    font-size: 1rem;
    color: #666;
    background: #f5f5f5;
    border: 1px solid #ddd;
    width: 45%;
    height: 2.5rem;
    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
  }
.sign-btn {
  font-weight: 500;
  font-size: 1rem;
  color: #fff;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  border: none;
  width: 45%;
  height: 2.5rem;
  border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
}
  .sign-btn {
    font-weight: 500;
    font-size: 1rem;
    color: #fff;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    border: none;
    width: 45%;
    height: 2.5rem;
    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
  }
.location-icon {
  color: #1989fa;
  font-size: 1.2rem;
}
  .location-icon {
    color: #1989fa;
    font-size: 1.2rem;
  }
</style>
src/pages/cooperativeOffice/clientVisit/index.vue
@@ -1,40 +1,43 @@
<template>
  <view class="sales-accoun">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="客户拜访" @back="goBack" />
    <PageHeader title="客户拜访"
                @back="goBack" />
    <!-- æœç´¢å’Œç­›é€‰åŒºåŸŸ -->
    <view class="search-section">
      <view class="search-bar">
        <view class="search-input">
          <up-input
            class="search-text"
            placeholder="请输入客户名称"
            v-model="customerName"
            @blur="getList"
            clearable
          />
          <up-input class="search-text"
                    placeholder="请输入客户名称"
                    v-model="customerName"
                    @blur="getList"
                    clearable />
        </view>
        <view class="filter-button" @click="getList">
          <u-icon name="search" size="24" color="#999"></u-icon>
        <view class="filter-button"
              @click="getList">
          <u-icon name="search"
                  size="24"
                  color="#999"></u-icon>
        </view>
      </view>
    </view>
    <!-- æ‹œè®¿è®°å½•列表 -->
    <view class="ledger-list" v-if="visitList.length > 0">
      <view v-for="(item, index) in visitList" :key="index">
    <view class="ledger-list"
          v-if="visitList.length > 0">
      <view v-for="(item, index) in visitList"
            :key="index">
        <view class="ledger-item">
          <view class="item-header">
            <view class="item-left">
              <view class="document-icon">
                <up-icon name="file-text" size="16" color="#ffffff"></up-icon>
                <up-icon name="file-text"
                         size="16"
                         color="#ffffff"></up-icon>
              </view>
              <text class="item-id">客户:{{ item.customerName }}</text>
            </view>
          </view>
          <up-divider></up-divider>
          <view class="item-details">
            <view class="detail-row">
              <text class="detail-label">联系人</text>
@@ -60,155 +63,204 @@
              <text class="detail-label">拜访人</text>
              <text class="detail-value">{{ item.visitingPeople || '-' }}</text>
            </view>
            <view class="detail-row" v-if="item.remark">
            <view class="detail-row"
                  v-if="item.remark">
              <text class="detail-label">备注</text>
              <text class="detail-value">{{ item.remark }}</text>
            </view>
          </view>
          <!-- æŒ‰é’®åŒºåŸŸ -->
          <view class="action-buttons">
            <u-button
              type="primary"
              size="small"
              class="action-btn"
              @click="viewDetail(item)"
            >
            <u-button type="info"
                      size="small"
                      class="action-btn"
                      @click="viewDetail(item)">
              æŸ¥çœ‹è¯¦æƒ…
            </u-button>
            <u-button type="primary"
                      size="small"
                      class="action-btn"
                      @click="editVisit(item)">
              ç¼–辑
            </u-button>
            <!-- <u-button type="error"
                      size="small"
                      class="action-btn"
                      @click="deleteVisit(item)">
              åˆ é™¤
            </u-button> -->
          </view>
        </view>
      </view>
    </view>
    <view v-else class="no-data">
    <view v-else
          class="no-data">
      <text>暂无拜访记录</text>
    </view>
    <!-- æµ®åŠ¨æ–°å¢žæŒ‰é’® -->
    <view class="fab-button" @click="addVisit">
      <up-icon name="plus" size="24" color="#ffffff"></up-icon>
    <view class="fab-button"
          @click="addVisit">
      <up-icon name="plus"
               size="24"
               color="#ffffff"></up-icon>
    </view>
  </view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import PageHeader from '@/components/PageHeader.vue'
import { getVisitRecords } from '@/api/cooperativeOffice/clientVisit'
import useUserStore from "@/store/modules/user"
// æ›¿æ¢ toast æ–¹æ³•
defineOptions({name: 'client-visit-index'})
const showToast = (message) => {
  uni.showToast({
    title: message,
    icon: 'none'
  })
}
  import { ref, onMounted } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import {
    getVisitRecords,
    delCustomer,
  } from "@/api/cooperativeOffice/clientVisit";
  import useUserStore from "@/store/modules/user";
  // æ›¿æ¢ toast æ–¹æ³•
  defineOptions({ name: "client-visit-index" });
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
import dayjs from "dayjs"
  import dayjs from "dayjs";
const userStore = useUserStore()
  const userStore = useUserStore();
// æœç´¢å…³é”®è¯
const customerName = ref('')
  // æœç´¢å…³é”®è¯
  const customerName = ref("");
// æ‹œè®¿è®°å½•数据
const visitList = ref([])
  // æ‹œè®¿è®°å½•数据
  const visitList = ref([]);
// è¿”回上一页
const goBack = () => {
  uni.navigateBack()
}
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
// æŸ¥è¯¢åˆ—表
const getList = () => {
  showLoadingToast('加载中...')
  const params = {
    current: -1,
    size: -1,
    customerName: customerName.value,
  }
  getVisitRecords(params)
    .then((res) => {
      visitList.value = res.records || res.data?.records || []
      closeToast()
    })
    .catch(() => {
      closeToast()
      showToast('获取数据失败')
    })
}
  // æŸ¥è¯¢åˆ—表
  const getList = () => {
    showLoadingToast("加载中...");
    const params = {
      current: -1,
      size: -1,
      customerName: customerName.value,
    };
    getVisitRecords(params)
      .then(res => {
        visitList.value = res.records || res.data?.records || [];
        closeToast();
      })
      .catch(() => {
        closeToast();
        showToast("获取数据失败");
      });
  };
// æ˜¾ç¤ºåŠ è½½æç¤º
const showLoadingToast = (message) => {
  uni.showLoading({
    title: message,
    mask: true
  // æ˜¾ç¤ºåŠ è½½æç¤º
  const showLoadingToast = message => {
    uni.showLoading({
      title: message,
      mask: true,
    });
  };
  // å…³é—­æç¤º
  const closeToast = () => {
    uni.hideLoading();
  };
  // æ–°å¢žæ‹œè®¿ - è·³è½¬åˆ°ç™»è®°é¡µé¢
  const addVisit = () => {
    uni.removeStorageSync("clientVisit");
    uni.navigateTo({
      url: "/pages/cooperativeOffice/clientVisit/detail",
    });
  };
  // ç¼–辑拜访 - è·³è½¬åˆ°ç™»è®°é¡µé¢
  const editVisit = item => {
    uni.setStorageSync("clientVisit", item);
    // ç¼–辑拜访跳转到登记页面
    uni.navigateTo({
      url: "/pages/cooperativeOffice/clientVisit/detail",
    });
  };
  // åˆ é™¤æ‹œè®¿
  const deleteVisit = item => {
    uni.showModal({
      title: "删除确认",
      content: `确定要删除该拜访记录吗?`,
      success: res => {
        if (res.confirm) {
          deleteClientVisit(item.id);
        }
      },
    });
  };
  // åˆ é™¤æ‹œè®¿è®°å½•
  const deleteClientVisit = id => {
    showLoadingToast("删除中...");
    delCustomer(id)
      .then(() => {
        closeToast();
        showToast("删除成功");
        getList();
      })
      .catch(() => {
        closeToast();
        showToast("删除失败");
      });
  };
  // æŸ¥çœ‹è¯¦æƒ…
  const viewDetail = item => {
    uni.setStorageSync("clientVisit", item);
    // æŸ¥çœ‹è¯¦æƒ…跳转到只读展示页面
    uni.navigateTo({
      url: "/pages/cooperativeOffice/clientVisit/view",
    });
  };
  onMounted(() => {
    getList();
  });
};
// å…³é—­æç¤º
const closeToast = () => {
  uni.hideLoading();
};
// æ–°å¢žæ‹œè®¿ - è·³è½¬åˆ°ç™»è®°é¡µé¢
const addVisit = () => {
  uni.navigateTo({
    url: '/pages/cooperativeOffice/clientVisit/detail'
  })
}
// æŸ¥çœ‹è¯¦æƒ…
const viewDetail = (item) => {
  uni.setStorageSync('clientVisit', item)
  // æŸ¥çœ‹è¯¦æƒ…跳转到只读展示页面
  uni.navigateTo({
    url: '/pages/cooperativeOffice/clientVisit/view'
  })
}
onMounted(() => {
  getList()
})
onShow(() => {
  getList()
})
  onShow(() => {
    getList();
  });
</script>
<style scoped lang="scss">
@import "../../../styles/sales-common.scss";
  @import "../../../styles/sales-common.scss";
// é¡µé¢ç‰¹å®šçš„æ ·å¼è¦†ç›–
.sales-accoun {
  min-height: 100vh;
  background: #f8f9fa;
  position: relative;
  padding-bottom: 80px;
}
  // é¡µé¢ç‰¹å®šçš„æ ·å¼è¦†ç›–
  .sales-accoun {
    min-height: 100vh;
    background: #f8f9fa;
    position: relative;
    padding-bottom: 80px;
  }
// ç‰¹å®šçš„图标样式
.document-icon {
  background: #667eea; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„背景色
}
  // ç‰¹å®šçš„图标样式
  .document-icon {
    background: #667eea; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„背景色
  }
// ç‰¹æœ‰æ ·å¼
.visit-status {
  display: flex;
  align-items: center;
}
  // ç‰¹æœ‰æ ·å¼
  .visit-status {
    display: flex;
    align-items: center;
  }
.detail-value {
  word-break: break-all; // ä¿ç•™é¡µé¢ç‰¹æœ‰çš„æ–‡æœ¬æ¢è¡Œæ ·å¼
}
  .detail-value {
    word-break: break-all; // ä¿ç•™é¡µé¢ç‰¹æœ‰çš„æ–‡æœ¬æ¢è¡Œæ ·å¼
  }
// ç‰¹å®šçš„æµ®åŠ¨æŒ‰é’®æ ·å¼
.fab-button {
  background: #667eea; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„背景色
  box-shadow: 0 4px 16px rgba(102, 126, 234, 0.3); // ä¿æŒé¡µé¢ç‰¹æœ‰çš„阴影效果
}
  // ç‰¹å®šçš„æµ®åŠ¨æŒ‰é’®æ ·å¼
  .fab-button {
    background: #667eea; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„背景色
    box-shadow: 0 4px 16px rgba(102, 126, 234, 0.3); // ä¿æŒé¡µé¢ç‰¹æœ‰çš„阴影效果
  }
</style>
src/pages/cooperativeOffice/clientVisit/view.vue
@@ -1,7 +1,7 @@
<template>
  <view class="client-visit-detail">
    <PageHeader title="客户拜访详情" @back="goBack" />
    <PageHeader title="客户拜访详情"
                @back="goBack" />
    <!-- å†…容容器 -->
    <view class="content-container">
      <!-- å®¢æˆ·ä¿¡æ¯ -->
@@ -20,7 +20,6 @@
          <text class="info-value">{{ form.contactPhone || '-' }}</text>
        </view>
      </view>
      <!-- æ‹œè®¿ä¿¡æ¯ -->
      <view class="section">
        <view class="section-title">拜访信息</view>
@@ -40,12 +39,12 @@
          <text class="info-label">拜访人</text>
          <text class="info-value">{{ form.visitingPeople || '-' }}</text>
        </view>
        <view class="info-item" v-if="form.latitude && form.longitude">
        <view class="info-item"
              v-if="form.latitude && form.longitude">
          <text class="info-label">经纬度</text>
          <text class="info-value">{{ form.latitude }}, {{ form.longitude }}</text>
        </view>
      </view>
      <!-- å¤‡æ³¨ä¿¡æ¯ -->
      <view class="section">
        <view class="section-title">备注信息</view>
@@ -59,120 +58,120 @@
</template>
<script setup>
// æ›¿æ¢ toast æ–¹æ³•
const showToast = (message) => {
  uni.showToast({
    title: message,
    icon: 'none'
  })
}
  // æ›¿æ¢ toast æ–¹æ³•
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
import { ref, onMounted } from 'vue'
import PageHeader from '@/components/PageHeader.vue'
import useUserStore from "@/store/modules/user"
  import { ref, onMounted } from "vue";
  import PageHeader from "@/components/PageHeader.vue";
  import useUserStore from "@/store/modules/user";
const userStore = useUserStore()
  const userStore = useUserStore();
// è¡¨å•数据
const form = ref({
  customerName: '',
  contact: '',
  contactPhone: '',
  visitingPeople: '',
  purposeVisit: '',
  purposeDate: '',
  visitAddress: '',
  latitude: '',
  longitude: '',
  locationAddress: '',
  remark: ''
})
  // è¡¨å•数据
  const form = ref({
    customerName: "",
    contact: "",
    contactPhone: "",
    visitingPeople: "",
    purposeVisit: "",
    purposeDate: "",
    visitAddress: "",
    latitude: "",
    longitude: "",
    locationAddress: "",
    remark: "",
  });
// è¿”回上一页
const goBack = () => {
  // è¿”回时清除本地存储的ID
  uni.removeStorageSync('clientVisit')
  uni.navigateBack()
}
  // è¿”回上一页
  const goBack = () => {
    // è¿”回时清除本地存储的ID
    uni.removeStorageSync("clientVisit");
    uni.navigateBack();
  };
// åˆå§‹åŒ–页面数据
const initPageData = () => {
  // ä»Žæœ¬åœ°å­˜å‚¨èŽ·å–æ‹œè®¿è®°å½•è¯¦æƒ…
  const row = uni.getStorageSync('clientVisit')
  if (row) {
    form.value = { ...row }
  } else {
    showToast('暂无拜访记录数据')
  }
}
  // åˆå§‹åŒ–页面数据
  const initPageData = () => {
    // ä»Žæœ¬åœ°å­˜å‚¨èŽ·å–æ‹œè®¿è®°å½•è¯¦æƒ…
    const row = uni.getStorageSync("clientVisit");
    if (row) {
      form.value = { ...row };
    } else {
      showToast("暂无拜访记录数据");
    }
  };
onMounted(() => {
  initPageData()
})
  onMounted(() => {
    initPageData();
  });
</script>
<style scoped lang="scss">
@import '@/static/scss/form-common.scss';
  @import "@/static/scss/form-common.scss";
.client-visit-detail {
  min-height: 100vh;
  background-color: #f8f9fa;
}
  .client-visit-detail {
    min-height: 100vh;
    background-color: #f8f9fa;
  }
.content-container {
  padding: 16px;
}
  .content-container {
    padding: 16px;
  }
.section {
  background-color: #ffffff;
  border-radius: 12px;
  margin-bottom: 16px;
  overflow: hidden;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
  .section {
    background-color: #ffffff;
    border-radius: 12px;
    margin-bottom: 16px;
    overflow: hidden;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
  }
.section-title {
  font-size: 16px;
  font-weight: 600;
  color: #333333;
  padding: 16px 16px 12px;
  border-bottom: 1px solid #f0f0f0;
}
  .section-title {
    font-size: 16px;
    font-weight: 600;
    color: #333333;
    padding: 16px 16px 12px;
    border-bottom: 1px solid #f0f0f0;
  }
.info-item {
  display: flex;
  padding: 14px 16px;
  border-bottom: 1px solid #f8f8f8;
  align-items: flex-start;
}
  .info-item {
    display: flex;
    padding: 14px 16px;
    border-bottom: 1px solid #f8f8f8;
    align-items: flex-start;
  }
.info-item:last-child {
  border-bottom: none;
}
  .info-item:last-child {
    border-bottom: none;
  }
.info-label {
  font-size: 14px;
  color: #666666;
  min-width: 80px;
  flex-shrink: 0;
  line-height: 22px;
}
  .info-label {
    font-size: 14px;
    color: #666666;
    min-width: 80px;
    flex-shrink: 0;
    line-height: 22px;
  }
.info-value {
  font-size: 14px;
  color: #333333;
  flex: 1;
  line-height: 22px;
  text-align: right;
}
  .info-value {
    font-size: 14px;
    color: #333333;
    flex: 1;
    line-height: 22px;
    text-align: right;
  }
.multi-line {
  text-align: left;
  word-break: break-all;
  line-height: 1.6;
}
  .multi-line {
    text-align: left;
    word-break: break-all;
    line-height: 1.6;
  }
.remark-item {
  padding-bottom: 16px;
}
  .remark-item {
    padding-bottom: 16px;
  }
</style>
src/pages/cooperativeOffice/collaborativeApproval/approve.vue
@@ -25,6 +25,30 @@
          <text class="info-label">申请日期</text>
          <text class="info-value">{{ approvalData.approveTime }}</text>
        </view>
        <!-- approveType=2 è¯·å‡ç›¸å…³å­—段 -->
        <template v-if="approvalData.approveType === 2">
          <view class="info-row">
            <text class="info-label">请假开始时间</text>
            <text class="info-value">{{ approvalData.startDate || '-' }}</text>
          </view>
          <view class="info-row">
            <text class="info-label">请假结束时间</text>
            <text class="info-value">{{ approvalData.endDate || '-' }}</text>
          </view>
        </template>
        <!-- approveType=3 å‡ºå·®ç›¸å…³å­—段 -->
        <view v-if="approvalData.approveType === 3" class="info-row">
          <text class="info-label">出差地点</text>
          <text class="info-value">{{ approvalData.location || '-' }}</text>
        </view>
        <!-- approveType=4 æŠ¥é”€ç›¸å…³å­—段 -->
        <view v-if="approvalData.approveType === 4" class="info-row">
          <text class="info-label">报销金额</text>
          <text class="info-value">{{ approvalData.price ? `Â¥${approvalData.price}` : '-' }}</text>
        </view>
      </view>
    </view>
src/pages/cooperativeOffice/collaborativeApproval/contactSelect.vue
@@ -1,11 +1,11 @@
<template>
  <view class="contact-select">
    <!-- é¡¶éƒ¨æ ‡é¢˜æ  -->
    <view class="header">
      <up-icon name="arrow-left" size="20" color="#333" @click="goBack" />
      <text class="title">选择联系人</text>
      <text class="confirm-btn" @click="confirmSelect">确定</text>
    </view>
    <PageHeader title="选择联系人" @back="goBack">
      <template #right>
        <text class="confirm-btn" @click="confirmSelect">确定</text>
      </template>
    </PageHeader>
    <!-- æœç´¢æ¡† -->
<!--    <view class="search-section">-->
src/pages/cooperativeOffice/collaborativeApproval/detail.vue
@@ -1,85 +1,163 @@
<template>
  <view class="account-detail">
    <PageHeader title="审批流程" @back="goBack" />
    <PageHeader title="审批流程"
                @back="goBack" />
    <!-- è¡¨å•区域 -->
    <u-form ref="formRef" @submit="submitForm" :rules="rules" :model="form" label-width="140rpx">
      <u-form-item prop="approveReason" label="申请事由" required>
        <u-input
          v-model="form.approveReason"
          type="textarea"
          rows="2"
          auto-height
          maxlength="200"
          placeholder="请输入申请事由"
          show-word-limit
        />
    <u-form ref="formRef"
            @submit="submitForm"
            :rules="rules"
            :model="form"
            label-width="140rpx">
      <u-form-item prop="approveReason"
                   label="流程编号">
        <u-input v-model="form.approveId"
                 disabled
                 placeholder="自动编号" />
      </u-form-item>
      <u-form-item prop="approveDeptName" label="申请部门" required>
        <u-input
          v-model="form.approveDeptName"
          readonly
          placeholder="请选择申请部门"
          @click="showPicker = true"
        />
      <u-form-item prop="approveReason"
                   :label="approveType === 5 ? '采购事由' : '申请事由'"
                   required>
        <u-input v-model="form.approveReason"
                 type="textarea"
                 rows="2"
                 auto-height
                 maxlength="200"
                 :placeholder="approveType === 5 ? '请输入采购事由' : '请输入申请事由'"
                 show-word-limit />
      </u-form-item>
      <u-form-item prop="approveDeptName"
                   label="申请部门"
                   required>
        <!-- <u-input v-model="form.approveDeptName"
                 placeholder="请选择申请部门" /> -->
        <u-input v-model="form.approveDeptName"
                 readonly
                 placeholder="请选择申请部门"
                 @click="showPicker = true" />
        <template #right>
                    <up-icon
                        name="arrow-right"
                        @click="showPicker = true"
                    ></up-icon>
                </template>
          <up-icon name="arrow-right"
                   @click="showPicker = true"></up-icon>
        </template>
      </u-form-item>
      <u-form-item prop="approveUser" label="申请人" required>
        <u-input
          v-model="form.approveUserName"
          placeholder="请输入申请人"
          readonly
        />
      <u-form-item prop="approveUser"
                   label="申请人"
                   required>
        <u-input v-model="form.approveUserName"
                 placeholder="请输入申请人"
                 readonly />
      </u-form-item>
      <u-form-item prop="approveTime" label="申请日期" required>
        <u-input
          v-model="form.approveTime"
          placeholder="请选择"
          readonly
          @click="showDatePicker"
        />
      <u-form-item prop="approveTime"
                   label="申请日期"
                   required>
        <u-input v-model="form.approveTime"
                 readonly
                 placeholder="请选择"
                 @click="showDatePicker" />
        <template #right>
          <up-icon name="arrow-right"
                   @click="showDatePicker"></up-icon>
        </template>
      </u-form-item>
      <!-- approveType=2 è¯·å‡ç›¸å…³å­—段 -->
      <template v-if="approveType === 2">
        <u-form-item prop="startDate"
                     label="开始时间"
                     required>
          <u-input v-model="form.startDate"
                   readonly
                   placeholder="请假开始时间"
                   @click="showStartDatePicker" />
          <template #right>
            <up-icon name="arrow-right"
                     @click="showStartDatePicker"></up-icon>
          </template>
        </u-form-item>
        <u-form-item prop="endDate"
                     label="结束时间"
                     required>
          <u-input v-model="form.endDate"
                   readonly
                   placeholder="请假结束时间"
                   @click="showEndDatePicker" />
          <template #right>
            <up-icon name="arrow-right"
                     @click="showEndDatePicker"></up-icon>
          </template>
        </u-form-item>
      </template>
      <!-- approveType=3 å‡ºå·®ç›¸å…³å­—段 -->
      <u-form-item v-if="approveType === 3"
                   prop="location"
                   label="出差地点"
                   required>
        <u-input v-model="form.location"
                 placeholder="请输入出差地点"
                 clearable />
      </u-form-item>
      <!-- approveType=4 æŠ¥é”€ç›¸å…³å­—段 -->
      <u-form-item v-if="approveType === 4"
                   prop="price"
                   label="报销金额"
                   required>
        <u-input v-model="form.price"
                 type="number"
                 placeholder="请输入报销金额"
                 clearable />
      </u-form-item>
    </u-form>
    <!-- é€‰æ‹©å™¨å¼¹çª— -->
    <up-action-sheet
      :show="showPicker"
      :actions="productOptions"
      title="选择部门"
      @select="onConfirm"
      @close="showPicker = false"
    />
    <up-action-sheet :show="showPicker"
                     :actions="productOptions"
                     title="选择部门"
                     @select="onConfirm"
                     @close="showPicker = false" />
    <!-- æ—¥æœŸé€‰æ‹©å™¨ -->
    <u-popup v-model="showDate" mode="bottom">
      <u-datetime-picker
        v-model="currentDate"
        title="选择日期"
        mode="date"
        @confirm="onDateConfirm"
        @cancel="showDate = false"
      />
    </u-popup>
    <up-popup :show="showDate"
              mode="bottom"
              @close="showDate = false">
      <up-datetime-picker :show="true"
                          v-model="currentDate"
                          @confirm="onDateConfirm"
                          @cancel="showDate = false"
                          mode="date" />
    </up-popup>
    <!-- è¯·å‡å¼€å§‹æ—¶é—´é€‰æ‹©å™¨ -->
    <up-popup :show="showStartDate"
              mode="bottom"
              @close="showStartDate = false">
      <up-datetime-picker :show="true"
                          v-model="startDateValue"
                          @confirm="onStartDateConfirm"
                          @cancel="showStartDate = false"
                          mode="date" />
    </up-popup>
    <!-- è¯·å‡ç»“束时间选择器 -->
    <up-popup :show="showEndDate"
              mode="bottom"
              @close="showEndDate = false">
      <up-datetime-picker :show="true"
                          v-model="endDateValue"
                          @confirm="onEndDateConfirm"
                          @cancel="showEndDate = false"
                          mode="date" />
    </up-popup>
    <!-- å®¡æ ¸æµç¨‹åŒºåŸŸ -->
    <view class="approval-process">
      <view class="approval-header">
        <text class="approval-title">审核流程</text>
        <text class="approval-desc">每个步骤只能选择一个审批人</text>
      </view>
      <view class="approval-steps">
        <view v-for="(step, stepIndex) in approverNodes" :key="stepIndex" class="approval-step">
        <view v-for="(step, stepIndex) in approverNodes"
              :key="stepIndex"
              class="approval-step">
          <view class="step-dot"></view>
          <view class="step-title">
            <text>审批人</text>
          </view>
          <view class="approver-container">
            <view v-if="step.nickName" class="approver-item">
            <view v-if="step.nickName"
                  class="approver-item">
              <view class="approver-avatar">
                <text class="avatar-text">{{ step.nickName.charAt(0) }}</text>
                <view class="status-dot"></view>
@@ -87,664 +165,765 @@
              <view class="approver-info">
                <text class="approver-name">{{ step.nickName }}</text>
              </view>
              <view class="delete-approver-btn" @click="removeApprover(stepIndex)">×</view>
              <view class="delete-approver-btn"
                    @click="removeApprover(stepIndex)">×</view>
            </view>
            <view v-else class="add-approver-btn" @click="addApprover(stepIndex)">
            <view v-else
                  class="add-approver-btn"
                  @click="addApprover(stepIndex)">
              <view class="add-circle">+</view>
              <text class="add-label">选择审批人</text>
            </view>
          </view>
          <view class="step-line" v-if="stepIndex < approverNodes.length - 1"></view>
          <view class="delete-step-btn" v-if="approverNodes.length > 1" @click="removeApprovalStep(stepIndex)">删除节点</view>
          <view class="step-line"
                v-if="stepIndex < approverNodes.length - 1"></view>
          <view class="delete-step-btn"
                v-if="approverNodes.length > 1"
                @click="removeApprovalStep(stepIndex)">删除节点</view>
        </view>
      </view>
      <view class="add-step-btn">
            <u-button icon="plus" plain type="primary" style="width: 100%" @click="addApprovalStep">新增节点</u-button>
        <u-button icon="plus"
                  plain
                  type="primary"
                  style="width: 100%"
                  @click="addApprovalStep">新增节点</u-button>
      </view>
    </view>
    <!-- åº•部按钮 -->
    <view class="footer-btns">
      <u-button class="cancel-btn" @click="goBack">取消</u-button>
      <u-button class="save-btn" @click="submitForm">保存</u-button>
      <u-button class="cancel-btn"
                @click="goBack">取消</u-button>
      <u-button class="save-btn"
                @click="submitForm">保存</u-button>
    </view>
  </view>
</template>
<script setup>
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
import PageHeader from "@/components/PageHeader.vue";
import useUserStore from "@/store/modules/user";
import { formatDateToYMD } from '@/utils/ruoyi'
import {getDept, approveProcessGetInfo, approveProcessAdd, approveProcessUpdate} from "@/api/collaborativeApproval/approvalProcess";
const showToast = (message) => {
    uni.showToast({
        title: message,
        icon: 'none'
    })
}
import {userListNoPageByTenantId} from "@/api/system/user";
  import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
  import PageHeader from "@/components/PageHeader.vue";
  import useUserStore from "@/store/modules/user";
  import { formatDateToYMD } from "@/utils/ruoyi";
  import {
    getDept,
    approveProcessGetInfo,
    approveProcessAdd,
    approveProcessUpdate,
  } from "@/api/collaborativeApproval/approvalProcess";
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
  import { userListNoPageByTenantId } from "@/api/system/user";
const data = reactive({
    form: {
        approveTime: "",
        approveId: "",
        approveUser: "",
        approveUserName: "",
        approveDeptName: "",
        approveDeptId: "",
        approveReason: "",
        checkResult: "",
        tempFileIds: [],
        approverList: [] // æ–°å¢žå­—段,存储所有节点的审批人id
    },
    rules: {
        approveTime: [{ required: false, message: "请输入", trigger: "change" },],
        approveId: [{ required: false, message: "请输入", trigger: "blur" }],
        approveDeptId: [{ required: true, message: "请输入", trigger: "blur" }],
        approveReason: [{ required: true, message: "请输入", trigger: "blur" }],
        checkResult: [{ required: false, message: "请输入", trigger: "blur" }],
    },
});
const { form, rules } = toRefs(data);
const result = ref("");
const showPicker = ref(false);
const productOptions = ref([]);
const operationType = ref("");
const currentApproveStatus = ref("");
const approverNodes = ref([]);
const userList = ref([]);
const formRef = ref(null);
const message = ref("");
const showDate = ref(false)
const currentDate = ref([new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()])
const userStore = useUserStore()
const getProductOptions = () => {
    getDept().then((res) => {
        productOptions.value = res.data.map(item => ({
            value: item.deptId,
            name: item.deptName
        }))
    });
};
const fileList = ref([]);
let nextApproverId = 2;
onMounted(async () => {
  try {
        getProductOptions()
        userListNoPageByTenantId().then((res) => {
            userList.value = res.data
        })
        form.value.approveUser = userStore.id
        form.value.approveUserName = userStore.nickName
        form.value.approveTime = getCurrentDate();
        // ä»Žæœ¬åœ°å­˜å‚¨èŽ·å–å‚æ•°
        operationType.value = uni.getStorageSync('operationType') || 'add';
        // å¦‚果是编辑模式,从本地存储获取数据
        if (operationType.value === 'edit') {
            const storedData = uni.getStorageSync('invoiceLedgerEditRow');
            if (storedData) {
                const row = JSON.parse(storedData);
                fileList.value = row.commonFileList || [];
                form.value.tempFileIds = fileList.value.map(file => file.id);
                currentApproveStatus.value = row.approveStatus;
                approveProcessGetInfo({id: row.approveId, approveReason: '1'}).then(res => {
                    form.value = {...res.data};
                    // åæ˜¾å®¡æ‰¹äºº
                    if (res.data && res.data.approveUserIds) {
                        const userIds = res.data.approveUserIds.split(',');
                        approverNodes.value = userIds.map((userId, idx) => {
                            const userIdNum = parseInt(userId.trim());
                            // ä»ŽuserList中找到对应的用户信息
                            const userInfo = userList.value.find(user => user.userId === userIdNum);
                            return {
                                id: idx + 1,
                                userId: userIdNum,
                                nickName: userInfo ? userInfo.nickName : null
                            };
                        });
                        nextApproverId = userIds.length + 1;
                    } else {
                        // æ–°å¢žæ¨¡å¼ï¼Œåˆå§‹åŒ–一个空的审批节点
                        approverNodes.value = [{ id: 1, userId: null, nickName: null }];
                        nextApproverId = 2;
                    }
                });
            }
        } else {
            // æ–°å¢žæ¨¡å¼ï¼Œåˆå§‹åŒ–一个空的审批节点
            approverNodes.value = [{ id: 1, userId: null }];
        }
    // ç›‘听联系人选择事件
    uni.$on('selectContact', handleSelectContact);
  } catch (error) {
    console.error("获取部门数据失败:", error);
  }
});
onUnmounted(() => {
  // ç§»é™¤äº‹ä»¶ç›‘听
  uni.$off('selectContact', handleSelectContact);
});
const onConfirm = (item) => {
  // è®¾ç½®é€‰ä¸­çš„部门
  form.value.approveDeptName = item.name;
  // ç¡®ä¿è®¾ç½®çš„æ˜¯å­—符串类型的部门ID
  form.value.approveDeptId = String(item.value || '');
  console.log('部门选择后的值:', {
    approveDeptId: form.value.approveDeptId,
    approveDeptName: form.value.approveDeptName
  const data = reactive({
    form: {
      approveTime: "",
      approveId: "",
      approveUser: "",
      approveUserName: "",
      approveDeptName: "",
      approveDeptId: "",
      approveReason: "",
      checkResult: "",
      tempFileIds: [],
      approverList: [], // æ–°å¢žå­—段,存储所有节点的审批人id
      startDate: "",
      endDate: "",
      location: "",
      price: "",
    },
    rules: {
      approveTime: [{ required: false, message: "请输入", trigger: "change" }],
      approveId: [{ required: false, message: "请输入", trigger: "blur" }],
      approveDeptId: [{ required: true, message: "请输入", trigger: "blur" }],
      approveReason: [{ required: true, message: "请输入", trigger: "blur" }],
      checkResult: [{ required: false, message: "请输入", trigger: "blur" }],
      startDate: [
        { required: false, message: "请选择开始时间", trigger: "change" },
      ],
      endDate: [
        { required: false, message: "请选择结束时间", trigger: "change" },
      ],
      location: [{ required: false, message: "请输入出差地点", trigger: "blur" }],
      price: [{ required: false, message: "请输入报销金额", trigger: "blur" }],
    },
  });
  showPicker.value = false;
};
  const { form, rules } = toRefs(data);
  const result = ref("");
  const showPicker = ref(false);
  const productOptions = ref([]);
  const operationType = ref("");
  const currentApproveStatus = ref("");
  const approverNodes = ref([]);
  const userList = ref([]);
  const formRef = ref(null);
  const message = ref("");
  const showDate = ref(false);
  const currentDate = ref(Date.now());
  const showStartDate = ref(false);
  const startDateValue = ref(Date.now());
  const showEndDate = ref(false);
  const endDateValue = ref(Date.now());
  const userStore = useUserStore();
  const approveType = ref(0);
const goBack = () => {
    // æ¸…除本地存储的数据
  uni.removeStorageSync('operationType');
    uni.removeStorageSync('invoiceLedgerEditRow');
  uni.navigateBack();
};
  const getProductOptions = () => {
    getDept().then(res => {
      productOptions.value = res.data.map(item => ({
        value: item.deptId,
        name: item.deptName,
      }));
    });
  };
  const fileList = ref([]);
  let nextApproverId = 2;
  const getCurrentinfo = () => {
    userStore.getInfo().then(res => {
      form.value.approveDeptId = res.user.tenantId;
      console.log(res.user.tenantId, "res.user.tenantId");
    });
  };
  onMounted(async () => {
    try {
      getProductOptions();
      userListNoPageByTenantId().then(res => {
        userList.value = res.data;
      });
      form.value.approveUser = userStore.id;
      form.value.approveUserName = userStore.nickName;
      form.value.approveTime = getCurrentDate();
      getCurrentinfo();
      // ä»Žæœ¬åœ°å­˜å‚¨èŽ·å–å‚æ•°
      operationType.value = uni.getStorageSync("operationType") || "add";
      approveType.value = uni.getStorageSync("approveType") || 0;
const submitForm = () => {
  // æ£€æŸ¥æ¯ä¸ªå®¡æ‰¹æ­¥éª¤æ˜¯å¦éƒ½æœ‰å®¡æ‰¹äºº
  const hasEmptyStep = approverNodes.value.some(step => !step.nickName);
  if (hasEmptyStep) {
        showToast('请为每个审批步骤选择审批人');
    return;
  }
  // æ‰‹åŠ¨æ£€æŸ¥å¿…å¡«å­—æ®µï¼Œé˜²æ­¢å› æ•°æ®ç±»åž‹é—®é¢˜å¯¼è‡´çš„æ ¡éªŒå¤±è´¥
  if (!form.value.approveReason || !form.value.approveReason.trim()) {
    showToast('请输入申请事由');
    return;
  }
  if (!form.value.approveDeptId || String(form.value.approveDeptId).trim() === '') {
    showToast('请选择申请部门');
    return;
  }
  if (!form.value.approveTime) {
    showToast('请选择申请日期');
    return;
  }
  formRef.value.validate().then((valid) => {
    if (valid) {
      // è¡¨å•校验通过,可以提交数据
      // æ”¶é›†æ‰€æœ‰èŠ‚ç‚¹çš„å®¡æ‰¹äººid
      console.log('approverNodes---', approverNodes.value)
      form.value.approveUserIds = approverNodes.value.map(node => node.userId).join(',')
      form.value.approveType = 0
      if (operationType.value === "add" || currentApproveStatus.value == 3) {
        approveProcessAdd(form.value).then(res => {
          showToast("提交成功");
          goBack()
        })
      } else {
        approveProcessUpdate(form.value).then(res => {
          showToast("提交成功");
          goBack()
        })
      }
    }
  }).catch((error) => {
    console.error("表单校验失败:", error);
    // å°è¯•获取具体的错误字段
    if (error && error.errors) {
      const firstError = error.errors[0];
      if (firstError) {
        uni.showToast({
          title: firstError.message || '表单校验失败,请检查必填项',
          icon: 'none'
        });
        return;
      // å¦‚果是编辑模式,从本地存储获取数据
      if (operationType.value === "edit") {
        const storedData = uni.getStorageSync("invoiceLedgerEditRow");
        if (storedData) {
          const row = JSON.parse(storedData);
          fileList.value = row.commonFileList || [];
          form.value.tempFileIds = fileList.value.map(file => file.id);
          currentApproveStatus.value = row.approveStatus;
          approveProcessGetInfo({ id: row.approveId, approveReason: "1" }).then(
            res => {
              form.value = { ...res.data };
              // åæ˜¾å®¡æ‰¹äºº
              if (res.data && res.data.approveUserIds) {
                const userIds = res.data.approveUserIds.split(",");
                approverNodes.value = userIds.map((userId, idx) => {
                  const userIdNum = parseInt(userId.trim());
                  // ä»ŽuserList中找到对应的用户信息
                  const userInfo = userList.value.find(
                    user => user.userId === userIdNum
                  );
                  return {
                    id: idx + 1,
                    userId: userIdNum,
                    nickName: userInfo ? userInfo.nickName : null,
                  };
                });
                nextApproverId = userIds.length + 1;
              } else {
                // æ–°å¢žæ¨¡å¼ï¼Œåˆå§‹åŒ–一个空的审批节点
                approverNodes.value = [{ id: 1, userId: null, nickName: null }];
                nextApproverId = 2;
              }
            }
          );
        }
      } else {
        // æ–°å¢žæ¨¡å¼ï¼Œåˆå§‹åŒ–一个空的审批节点
        approverNodes.value = [{ id: 1, userId: null }];
      }
      // ç›‘听联系人选择事件
      uni.$on("selectContact", handleSelectContact);
    } catch (error) {
      console.error("获取部门数据失败:", error);
    }
    // æ˜¾ç¤ºé€šç”¨é”™è¯¯ä¿¡æ¯
    uni.showToast({
      title: '表单校验失败,请检查必填项',
      icon: 'none'
    });
  });
};
// å¤„理联系人选择结果
const handleSelectContact = (data) => {
  const { stepIndex, contact } = data;
  // å°†é€‰ä¸­çš„联系人设置为对应审批步骤的审批人
  approverNodes.value[stepIndex].userId = contact.userId;
  approverNodes.value[stepIndex].nickName = contact.nickName;
};
const addApprover = (stepIndex) => {
  // è·³è½¬åˆ°è”系人选择页面
  uni.setStorageSync('stepIndex', stepIndex);
  uni.navigateTo({
    url: "/pages/cooperativeOffice/collaborativeApproval/contactSelect"
  onUnmounted(() => {
    // ç§»é™¤äº‹ä»¶ç›‘听
    uni.$off("selectContact", handleSelectContact);
  });
};
const addApprovalStep = () => {
  // æ·»åŠ æ–°çš„å®¡æ‰¹æ­¥éª¤
  approverNodes.value.push({ userId: null, nickName: null });
};
const removeApprover = (stepIndex) => {
  // ç§»é™¤å®¡æ‰¹äºº
  approverNodes.value[stepIndex].userId = null;
  approverNodes.value[stepIndex].nickName = null;
};
const removeApprovalStep = (stepIndex) => {
  // ç¡®ä¿è‡³å°‘保留一个审批步骤
  if (approverNodes.value.length > 1) {
    approverNodes.value.splice(stepIndex, 1);
  } else {
    uni.showToast({
      title: '至少需要一个审批步骤',
      icon: 'none'
  const onConfirm = item => {
    // è®¾ç½®é€‰ä¸­çš„部门
    form.value.approveDeptName = item.name;
    // ç¡®ä¿è®¾ç½®çš„æ˜¯å­—符串类型的部门ID
    form.value.approveDeptId = String(item.value || "");
    console.log("部门选择后的值:", {
      approveDeptId: form.value.approveDeptId,
      approveDeptName: form.value.approveDeptName,
    });
    showPicker.value = false;
  };
  const goBack = () => {
    // æ¸…除本地存储的数据
    uni.removeStorageSync("operationType");
    uni.removeStorageSync("invoiceLedgerEditRow");
    uni.removeStorageSync("approveType");
    uni.navigateBack();
  };
  const submitForm = () => {
    // æ£€æŸ¥æ¯ä¸ªå®¡æ‰¹æ­¥éª¤æ˜¯å¦éƒ½æœ‰å®¡æ‰¹äºº
    const hasEmptyStep = approverNodes.value.some(step => !step.nickName);
    if (hasEmptyStep) {
      showToast("请为每个审批步骤选择审批人");
      return;
    }
    // æ‰‹åŠ¨æ£€æŸ¥å¿…å¡«å­—æ®µï¼Œé˜²æ­¢å› æ•°æ®ç±»åž‹é—®é¢˜å¯¼è‡´çš„æ ¡éªŒå¤±è´¥
    if (!form.value.approveReason || !form.value.approveReason.trim()) {
      showToast("请输入申请事由");
      return;
    }
    if (
      !form.value.approveDeptId ||
      String(form.value.approveDeptId).trim() === ""
    ) {
      showToast("请选择申请部门");
      return;
    }
    if (!form.value.approveTime) {
      showToast("请选择申请日期");
      return;
    }
    formRef.value
      .validate()
      .then(valid => {
        if (valid) {
          // è¡¨å•校验通过,可以提交数据
          // æ”¶é›†æ‰€æœ‰èŠ‚ç‚¹çš„å®¡æ‰¹äººid
          console.log("approverNodes---", approverNodes.value);
          form.value.approveUserIds = approverNodes.value
            .map(node => node.userId)
            .join(",");
          form.value.approveType = approveType.value;
          form.value.approveDeptId = Number(form.value.approveDeptId);
          // const submitForm = {
          //   approveDeptId: form.value.approveDeptId,
          //   approveDeptName: form.value.approveDeptName,
          //   approveReason: form.value.approveReason,
          //   approveTime: form.value.approveTime,
          //   approveType: form.value.approveType,
          //   approveUser: form.value.approveUser,
          //   approveUserIds: form.value.approveUserIds,
          //   endDate: form.value.endDate,
          //   startDate: form.value.startDate,
          // };
          // console.log("form.value---", form.value);
          // console.log("submitForm", submitForm);
          if (operationType.value === "add" || currentApproveStatus.value == 3) {
            approveProcessAdd(form.value).then(res => {
              showToast("提交成功");
              goBack();
            });
          } else {
            approveProcessUpdate(form.value).then(res => {
              showToast("提交成功");
              goBack();
            });
          }
        }
      })
      .catch(error => {
        console.error("表单校验失败:", error);
        // å°è¯•获取具体的错误字段
        if (error && error.errors) {
          const firstError = error.errors[0];
          if (firstError) {
            uni.showToast({
              title: firstError.message || "表单校验失败,请检查必填项",
              icon: "none",
            });
            return;
          }
        }
        // æ˜¾ç¤ºé€šç”¨é”™è¯¯ä¿¡æ¯
        uni.showToast({
          title: "表单校验失败,请检查必填项",
          icon: "none",
        });
      });
  };
  // å¤„理联系人选择结果
  const handleSelectContact = data => {
    const { stepIndex, contact } = data;
    // å°†é€‰ä¸­çš„联系人设置为对应审批步骤的审批人
    approverNodes.value[stepIndex].userId = contact.userId;
    approverNodes.value[stepIndex].nickName = contact.nickName;
  };
  const addApprover = stepIndex => {
    // è·³è½¬åˆ°è”系人选择页面
    uni.setStorageSync("stepIndex", stepIndex);
    uni.navigateTo({
      url: "/pages/cooperativeOffice/collaborativeApproval/contactSelect",
    });
  };
  const addApprovalStep = () => {
    // æ·»åŠ æ–°çš„å®¡æ‰¹æ­¥éª¤
    approverNodes.value.push({ userId: null, nickName: null });
  };
  const removeApprover = stepIndex => {
    // ç§»é™¤å®¡æ‰¹äºº
    approverNodes.value[stepIndex].userId = null;
    approverNodes.value[stepIndex].nickName = null;
  };
  const removeApprovalStep = stepIndex => {
    // ç¡®ä¿è‡³å°‘保留一个审批步骤
    if (approverNodes.value.length > 1) {
      approverNodes.value.splice(stepIndex, 1);
    } else {
      uni.showToast({
        title: "至少需要一个审批步骤",
        icon: "none",
      });
    }
  };
  // æ˜¾ç¤ºæ—¥æœŸé€‰æ‹©å™¨
  const showDatePicker = () => {
    showDate.value = true;
  };
  // ç¡®è®¤æ—¥æœŸé€‰æ‹©
  const onDateConfirm = e => {
    form.value.approveTime = formatDateToYMD(e.value);
    currentDate.value = formatDateToYMD(e.value);
    showDate.value = false;
  };
  // æ˜¾ç¤ºè¯·å‡å¼€å§‹æ—¶é—´é€‰æ‹©å™¨
  const showStartDatePicker = () => {
    showStartDate.value = true;
  };
  // ç¡®è®¤è¯·å‡å¼€å§‹æ—¶é—´é€‰æ‹©
  const onStartDateConfirm = e => {
    form.value.startDate = formatDateToYMD(e.value);
    showStartDate.value = false;
  };
  const showEndDatePicker = () => {
    showEndDate.value = true;
  };
  // ç¡®è®¤è¯·å‡ç»“束时间选择
  const onEndDateConfirm = e => {
    form.value.endDate = formatDateToYMD(e.value);
    showEndDate.value = false;
  };
  // èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
  function getCurrentDate() {
    const today = new Date();
    const year = today.getFullYear();
    const month = String(today.getMonth() + 1).padStart(2, "0"); // æœˆä»½ä»Ž0开始
    const day = String(today.getDate()).padStart(2, "0");
    return `${year}-${month}-${day}`;
  }
};
// æ˜¾ç¤ºæ—¥æœŸé€‰æ‹©å™¨
const showDatePicker = () => {
    showDate.value = true
}
// ç¡®è®¤æ—¥æœŸé€‰æ‹©
const onDateConfirm = (e) => {
  form.value.approveTime = formatDateToYMD(e.value)
    currentDate.value = formatDateToYMD(e.value)
    showDate.value = false;
}
// èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
function getCurrentDate() {
    const today = new Date();
    const year = today.getFullYear();
    const month = String(today.getMonth() + 1).padStart(2, "0"); // æœˆä»½ä»Ž0开始
    const day = String(today.getDate()).padStart(2, "0");
    return `${year}-${month}-${day}`;
}
</script>
<style scoped lang="scss">
@import '@/static/scss/form-common.scss';
  @import "@/static/scss/form-common.scss";
.approval-process {
  background: #fff;
  margin: 16px;
  border-radius: 16px;
  padding: 16px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
}
.approval-header {
  margin-bottom: 16px;
}
.approval-title {
  font-size: 16px;
  font-weight: 600;
  color: #333;
  display: block;
  margin-bottom: 4px;
}
.approval-desc {
  font-size: 12px;
  color: #999;
}
/* æ ·å¼å¢žå¼ºä¸ºâ€œç®€æ´å°åœ†åœˆé£Žæ ¼â€ */
.approval-steps {
  padding-left: 22px;
  position: relative;
  &::before {
    content: '';
    position: absolute;
    left: 11px;
    top: 40px;
    bottom: 40px;
    width: 2px;
    background: linear-gradient(to bottom, #e6f7ff 0%, #bae7ff 50%, #91d5ff 100%);
    border-radius: 1px;
  }
}
.approval-step {
  position: relative;
  margin-bottom: 24px;
  &::before {
    content: '';
    position: absolute;
    left: -18px;
    top: 14px; // ä»Ž 8px è°ƒæ•´ä¸º 14px,与文字中心对齐
    width: 12px;
    height: 12px;
  .approval-process {
    background: #fff;
    border: 3px solid #006cfb;
    border-radius: 50%;
    z-index: 2;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    margin: 16px;
    border-radius: 16px;
    padding: 16px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
  }
}
.step-title {
    top: 12px;
  margin-bottom: 12px;
  position: relative;
    margin-left: 6px;
}
.step-title text {
  font-size: 14px;
  color: #666;
  background: #f0f0f0;
  padding: 4px 12px;
  border-radius: 12px;
  position: relative;
  line-height: 1.4; // ç¡®ä¿æ–‡å­—行高一致
}
.approver-item {
  display: flex;
  align-items: center;
  background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
  border-radius: 16px;
  padding: 16px;
  gap: 12px;
  position: relative;
  border: 1px solid #e6f7ff;
  box-shadow: 0 4px 12px rgba(0, 108, 251, 0.08);
  transition: all 0.3s ease;
}
.approver-avatar {
  width: 48px;
  height: 48px;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.avatar-text {
  color: #fff;
  font-size: 18px;
  font-weight: 600;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.approver-info {
  flex: 1;
  position: relative;
}
.approver-name {
  display: block;
  font-size: 16px;
  color: #333;
  font-weight: 500;
  position: relative;
}
.approver-dept {
  font-size: 12px;
  color: #999;
  background: rgba(0, 108, 251, 0.05);
  padding: 2px 8px;
  border-radius: 8px;
  display: inline-block;
  position: relative;
  &::before {
    content: '';
    position: absolute;
    left: 4px;
    top: 50%;
    transform: translateY(-50%);
    width: 2px;
    height: 2px;
    background: #006cfb;
    border-radius: 50%;
  .approval-header {
    margin-bottom: 16px;
  }
}
.delete-approver-btn {
  font-size: 16px;
  color: #ff4d4f;
  background: linear-gradient(135deg, rgba(255, 77, 79, 0.1) 0%, rgba(255, 77, 79, 0.05) 100%);
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.3s ease;
  position: relative;
}
  .approval-title {
    font-size: 16px;
    font-weight: 600;
    color: #333;
    display: block;
    margin-bottom: 4px;
  }
.add-approver-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, #f0f8ff 0%, #e6f7ff 100%);
  border: 2px dashed #006cfb;
  border-radius: 16px;
  padding: 20px;
  color: #006cfb;
  font-size: 14px;
  position: relative;
  transition: all 0.3s ease;
  &::before {
    content: '';
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    width: 32px;
    height: 32px;
    border: 2px solid #006cfb;
    border-radius: 50%;
    opacity: 0;
  .approval-desc {
    font-size: 12px;
    color: #999;
  }
  /* æ ·å¼å¢žå¼ºä¸ºâ€œç®€æ´å°åœ†åœˆé£Žæ ¼â€ */
  .approval-steps {
    padding-left: 22px;
    position: relative;
    &::before {
      content: "";
      position: absolute;
      left: 11px;
      top: 40px;
      bottom: 40px;
      width: 2px;
      background: linear-gradient(
        to bottom,
        #e6f7ff 0%,
        #bae7ff 50%,
        #91d5ff 100%
      );
      border-radius: 1px;
    }
  }
  .approval-step {
    position: relative;
    margin-bottom: 24px;
    &::before {
      content: "";
      position: absolute;
      left: -18px;
      top: 14px; // ä»Ž 8px è°ƒæ•´ä¸º 14px,与文字中心对齐
      width: 12px;
      height: 12px;
      background: #fff;
      border: 3px solid #006cfb;
      border-radius: 50%;
      z-index: 2;
      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    }
  }
  .step-title {
    top: 12px;
    margin-bottom: 12px;
    position: relative;
    margin-left: 6px;
  }
  .step-title text {
    font-size: 14px;
    color: #666;
    background: #f0f0f0;
    padding: 4px 12px;
    border-radius: 12px;
    position: relative;
    line-height: 1.4; // ç¡®ä¿æ–‡å­—行高一致
  }
  .approver-item {
    display: flex;
    align-items: center;
    background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
    border-radius: 16px;
    padding: 16px;
    gap: 12px;
    position: relative;
    border: 1px solid #e6f7ff;
    box-shadow: 0 4px 12px rgba(0, 108, 251, 0.08);
    transition: all 0.3s ease;
  }
}
.delete-step-btn {
  color: #ff4d4f;
  font-size: 12px;
  background: linear-gradient(135deg, rgba(255, 77, 79, 0.1) 0%, rgba(255, 77, 79, 0.05) 100%);
  padding: 6px 12px;
  border-radius: 12px;
  display: inline-block;
  position: relative;
  transition: all 0.3s ease;
  &::before {
    content: '';
    position: absolute;
    left: 6px;
    top: 50%;
    transform: translateY(-50%);
    width: 4px;
    height: 4px;
    background: #ff4d4f;
  .approver-avatar {
    width: 48px;
    height: 48px;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
    box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
  }
}
.step-line {
  display: none; // éšè—åŽŸæ¥çš„çº¿æ¡ï¼Œä½¿ç”¨ä¼ªå…ƒç´ ä»£æ›¿
}
.add-step-btn {
  display: flex;
  align-items: center;
  justify-content: center;
}
.footer-btns {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    background: #fff;
    display: flex;
    justify-content: space-around;
    align-items: center;
    padding: 0.75rem 0;
    box-shadow: 0 -0.125rem 0.5rem rgba(0,0,0,0.05);
    z-index: 1000;
}
.cancel-btn {
    font-weight: 400;
    font-size: 1rem;
    color: #FFFFFF;
    width: 6.375rem;
    background: #C7C9CC;
    box-shadow: 0 0.25rem 0.625rem 0 rgba(3,88,185,0.2);
    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
}
.save-btn {
    font-weight: 400;
    font-size: 1rem;
    color: #FFFFFF;
    width: 14rem;
    background: linear-gradient( 140deg, #00BAFF 0%, #006CFB 100%);
    box-shadow: 0 0.25rem 0.625rem 0 rgba(3,88,185,0.2);
    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
}
// åŠ¨ç”»å®šä¹‰
@keyframes pulse {
  0% {
    transform: scale(1);
    opacity: 1;
  .avatar-text {
    color: #fff;
    font-size: 18px;
    font-weight: 600;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
  }
  50% {
    transform: scale(1.2);
    opacity: 0.7;
  .approver-info {
    flex: 1;
    position: relative;
  }
  100% {
    transform: scale(1);
    opacity: 1;
  .approver-name {
    display: block;
    font-size: 16px;
    color: #333;
    font-weight: 500;
    position: relative;
  }
}
@keyframes rotate {
  0% {
    transform: rotate(0deg);
  .approver-dept {
    font-size: 12px;
    color: #999;
    background: rgba(0, 108, 251, 0.05);
    padding: 2px 8px;
    border-radius: 8px;
    display: inline-block;
    position: relative;
    &::before {
      content: "";
      position: absolute;
      left: 4px;
      top: 50%;
      transform: translateY(-50%);
      width: 2px;
      height: 2px;
      background: #006cfb;
      border-radius: 50%;
    }
  }
  100% {
    transform: rotate(360deg);
  .delete-approver-btn {
    font-size: 16px;
    color: #ff4d4f;
    background: linear-gradient(
      135deg,
      rgba(255, 77, 79, 0.1) 0%,
      rgba(255, 77, 79, 0.05) 100%
    );
    width: 28px;
    height: 28px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.3s ease;
    position: relative;
  }
}
@keyframes ripple {
  0% {
    transform: translate(-50%, -50%) scale(0.8);
    opacity: 1;
  .add-approver-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(135deg, #f0f8ff 0%, #e6f7ff 100%);
    border: 2px dashed #006cfb;
    border-radius: 16px;
    padding: 20px;
    color: #006cfb;
    font-size: 14px;
    position: relative;
    transition: all 0.3s ease;
    &::before {
      content: "";
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
      width: 32px;
      height: 32px;
      border: 2px solid #006cfb;
      border-radius: 50%;
      opacity: 0;
      transition: all 0.3s ease;
    }
  }
  100% {
    transform: translate(-50%, -50%) scale(1.6);
    opacity: 0;
  .delete-step-btn {
    color: #ff4d4f;
    font-size: 12px;
    background: linear-gradient(
      135deg,
      rgba(255, 77, 79, 0.1) 0%,
      rgba(255, 77, 79, 0.05) 100%
    );
    padding: 6px 12px;
    border-radius: 12px;
    display: inline-block;
    position: relative;
    transition: all 0.3s ease;
    &::before {
      content: "";
      position: absolute;
      left: 6px;
      top: 50%;
      transform: translateY(-50%);
      width: 4px;
      height: 4px;
      background: #ff4d4f;
      border-radius: 50%;
    }
  }
}
/* å¦‚果已有 .step-line,这里更精准定位到左侧与小圆点对齐 */
.step-line {
  position: absolute;
  left: 4px;
  top: 48px;
  width: 2px;
  height: calc(100% - 48px);
  background: #E5E7EB;
}
  .step-line {
    display: none; // éšè—åŽŸæ¥çš„çº¿æ¡ï¼Œä½¿ç”¨ä¼ªå…ƒç´ ä»£æ›¿
  }
.approver-container {
  display: flex;
  align-items: center;
  background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
  border-radius: 16px;
  gap: 12px;
  padding: 10px 0;
  background: transparent;
  border: none;
  box-shadow: none;
}
  .add-step-btn {
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .footer-btns {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    background: #fff;
    display: flex;
    justify-content: space-around;
    align-items: center;
    padding: 0.75rem 0;
    box-shadow: 0 -0.125rem 0.5rem rgba(0, 0, 0, 0.05);
    z-index: 1000;
  }
.approver-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 10px;
  background: transparent;
  border: none;
  box-shadow: none;
  border-radius: 0;
}
  .cancel-btn {
    font-weight: 400;
    font-size: 1rem;
    color: #ffffff;
    width: 6.375rem;
    background: #c7c9cc;
    box-shadow: 0 0.25rem 0.625rem 0 rgba(3, 88, 185, 0.2);
    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
  }
.approver-avatar {
  position: relative;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: #F3F4F6;
  border: 2px solid #E5E7EB;
  display: flex;
  align-items: center;
  justify-content: center;
  animation: none; /* ç¦ç”¨æ—‹è½¬ç­‰åŠ¨ç”»ï¼Œå›žå½’ç®€æ´ */
}
  .save-btn {
    font-weight: 400;
    font-size: 1rem;
    color: #ffffff;
    width: 14rem;
    background: linear-gradient(140deg, #00baff 0%, #006cfb 100%);
    box-shadow: 0 0.25rem 0.625rem 0 rgba(3, 88, 185, 0.2);
    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
  }
.avatar-text {
  font-size: 14px;
  color: #374151;
  font-weight: 600;
}
  // åŠ¨ç”»å®šä¹‰
  @keyframes pulse {
    0% {
      transform: scale(1);
      opacity: 1;
    }
    50% {
      transform: scale(1.2);
      opacity: 0.7;
    }
    100% {
      transform: scale(1);
      opacity: 1;
    }
  }
.add-approver-btn {
  display: flex;
  align-items: center;
  gap: 8px;
  background: transparent;
  border: none;
  box-shadow: none;
  padding: 0;
}
  @keyframes rotate {
    0% {
      transform: rotate(0deg);
    }
    100% {
      transform: rotate(360deg);
    }
  }
.add-approver-btn .add-circle {
  width: 40px;
  height: 40px;
  border: 2px dashed #A0AEC0;
  border-radius: 50%;
  color: #6B7280;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 22px;
  line-height: 1;
}
  @keyframes ripple {
    0% {
      transform: translate(-50%, -50%) scale(0.8);
      opacity: 1;
    }
    100% {
      transform: translate(-50%, -50%) scale(1.6);
      opacity: 0;
    }
  }
.add-approver-btn .add-label {
  color: #3B82F6;
  font-size: 14px;
}
  /* å¦‚果已有 .step-line,这里更精准定位到左侧与小圆点对齐 */
  .step-line {
    position: absolute;
    left: 4px;
    top: 48px;
    width: 2px;
    height: calc(100% - 48px);
    background: #e5e7eb;
  }
  .approver-container {
    display: flex;
    align-items: center;
    background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
    border-radius: 16px;
    gap: 12px;
    padding: 10px 0;
    background: transparent;
    border: none;
    box-shadow: none;
  }
  .approver-item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 8px 10px;
    background: transparent;
    border: none;
    box-shadow: none;
    border-radius: 0;
  }
  .approver-avatar {
    position: relative;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: #f3f4f6;
    border: 2px solid #e5e7eb;
    display: flex;
    align-items: center;
    justify-content: center;
    animation: none; /* ç¦ç”¨æ—‹è½¬ç­‰åŠ¨ç”»ï¼Œå›žå½’ç®€æ´ */
  }
  .avatar-text {
    font-size: 14px;
    color: #374151;
    font-weight: 600;
  }
  .add-approver-btn {
    display: flex;
    align-items: center;
    gap: 8px;
    background: transparent;
    border: none;
    box-shadow: none;
    padding: 0;
  }
  .add-approver-btn .add-circle {
    width: 40px;
    height: 40px;
    border: 2px dashed #a0aec0;
    border-radius: 50%;
    color: #6b7280;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 22px;
    line-height: 1;
  }
  .add-approver-btn .add-label {
    color: #3b82f6;
    font-size: 14px;
  }
</style>
src/pages/cooperativeOffice/collaborativeApproval/index.vue
@@ -1,315 +1,376 @@
// å®¡æ‰¹ç®¡ç†ä¸»é¡µé¢
<template>
    <view class="sales-account">
        <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
        <PageHeader title="审批管理" @back="goBack" />
        <!-- æœç´¢å’Œç­›é€‰åŒºåŸŸ -->
        <view class="search-section">
            <view class="search-bar">
                <view class="search-input">
                    <up-input
                        class="search-text"
                        placeholder="请输入流程编号"
                        v-model="searchForm.approveId"
                        clearable
                    />
                </view>
                <view class="search-button" @click="getList">
                    <up-icon name="search" size="24" color="#999"></up-icon>
                </view>
            </view>
        </view>
        <!-- å®¡æ‰¹åˆ—表 -->
        <view class="ledger-list" v-if="ledgerList.length > 0">
            <view v-for="(item, index) in ledgerList" :key="index">
                <view class="ledger-item">
                    <view class="item-header">
                        <view class="item-left">
                            <view class="document-icon">
                                <up-icon name="file-text" size="16" color="#ffffff"></up-icon>
                            </view>
                            <text class="item-id">{{ item.approveId }}</text>
                        </view>
                        <view class="item-tag">
                            <u-tag :type="getTagClass(item.approveStatus)">{{ formatReceiptType(item.approveStatus) }}</u-tag>
                        </view>
                    </view>
                    <up-divider></up-divider>
                    <view class="item-details">
                        <view class="detail-row">
                            <text class="detail-label">申请人</text>
                            <text class="detail-value">{{ item.approveUserName }}</text>
                        </view>
                        <view class="detail-row">
                            <text class="detail-label">申请部门</text>
                            <text class="detail-value">{{ item.approveDeptName }}</text>
                        </view>
                        <view class="detail-row-approveReason">
                            <text class="detail-label">审批事由</text>
                            <text class="detail-value highlightBlue">{{ item.approveReason }}</text>
                        </view>
                        <view class="detail-row">
                            <text class="detail-label">申请日期</text>
                            <text class="detail-value">{{ item.approveTime }}</text>
                        </view>
                        <view class="detail-row">
                            <text class="detail-label">结束日期</text>
                            <text class="detail-value">{{ item.approveOverTime }}</text>
                        </view>
                        <up-divider></up-divider>
                        <view class="detail-info">
                            <view class="detail-row-user">
                                <text class="detail-label">当前审批人</text>
                                <view class="detail-value approver-value">
                                    <view class="approver-chip">
                                        <text class="approver-name">{{ item.approveUserCurrentName || '未分配' }}</text>
                                    </view>
                                </view>
                            </view>
                            <view class="detail-row">
                                <view class="actions">
                                    <u-button
                                        type="primary"
                                        size="small"
                                        class="action-btn edit"
                                        :disabled="item.approveStatus == 2 || item.approveStatus == 1 || item.approveStatus == 4"
                                        @click="handleItemClick(item)"
                                    >
                                            ç¼–辑
                                    </u-button>
                                    <u-button
                                        type="success"
                                        size="small"
                                        class="action-btn approve"
                                        :disabled="item.approveUserCurrentId == null || item.approveStatus == 2 || item.approveStatus == 3 || item.approveStatus == 4 || item.approveUserCurrentId !== userStore.id"
                                        @click="approve(item)"
                                    >
                                            å®¡æ ¸
                                    </u-button>
                                </view>
                            </view>
                        </view>
                    </view>
                </view>
            </view>
        </view>
        <view v-else class="no-data">
            <text>暂无审批数据</text>
        </view>
        <!-- æµ®åŠ¨æ“ä½œæŒ‰é’® -->
        <view class="fab-button" @click="handleAdd">
            <up-icon name="plus" size="24" color="#ffffff"></up-icon>
        </view>
    </view>
  <view class="sales-account">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader :title="pageTitle"
                @back="goBack" />
    <!-- æœç´¢å’Œç­›é€‰åŒºåŸŸ -->
    <view class="search-section">
      <view class="search-bar">
        <view class="search-input">
          <up-input class="search-text"
                    placeholder="请输入流程编号"
                    v-model="searchForm.approveId"
                    clearable />
        </view>
        <view class="search-button"
              @click="getList">
          <up-icon name="search"
                   size="24"
                   color="#999"></up-icon>
        </view>
      </view>
    </view>
    <!-- å®¡æ‰¹åˆ—表 -->
    <view class="ledger-list"
          v-if="ledgerList.length > 0">
      <view v-for="(item, index) in ledgerList"
            :key="index">
        <view class="ledger-item">
          <view class="item-header">
            <view class="item-left">
              <view class="document-icon">
                <up-icon name="file-text"
                         size="16"
                         color="#ffffff"></up-icon>
              </view>
              <text class="item-id">{{ item.approveId }}</text>
            </view>
            <view class="item-tag">
              <u-tag :type="getTagClass(item.approveStatus)">{{ formatReceiptType(item.approveStatus) }}</u-tag>
            </view>
          </view>
          <up-divider></up-divider>
          <view class="item-details">
            <view class="detail-row">
              <text class="detail-label">申请人</text>
              <text class="detail-value">{{ item.approveUserName }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">申请部门</text>
              <text class="detail-value">{{ item.approveDeptName }}</text>
            </view>
            <view class="detail-row-approveReason">
              <text class="detail-label">审批事由</text>
              <text class="detail-value highlightBlue">{{ item.approveReason }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">申请日期</text>
              <text class="detail-value">{{ item.approveTime }}</text>
            </view>
            <!-- approveType=2 è¯·å‡ç›¸å…³å­—段 -->
            <template v-if="item.approveType === 2">
              <view class="detail-row">
                <text class="detail-label">请假开始时间</text>
                <text class="detail-value">{{ item.startDate || '-' }}</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">请假结束时间</text>
                <text class="detail-value">{{ item.endDate || '-' }}</text>
              </view>
            </template>
            <!-- approveType=3 å‡ºå·®ç›¸å…³å­—段 -->
            <view v-if="item.approveType === 3"
                  class="detail-row">
              <text class="detail-label">出差地点</text>
              <text class="detail-value">{{ item.location || '-' }}</text>
            </view>
            <!-- approveType=4 æŠ¥é”€ç›¸å…³å­—段 -->
            <view v-if="item.approveType === 4"
                  class="detail-row">
              <text class="detail-label">报销金额</text>
              <text class="detail-value highlightYellow">{{ item.price ? `Â¥${item.price}` : '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">结束日期</text>
              <text class="detail-value">{{ item.approveOverTime }}</text>
            </view>
            <up-divider></up-divider>
            <view class="detail-info">
              <view class="detail-row-user">
                <text class="detail-label">当前审批人</text>
                <view class="detail-value approver-value">
                  <view class="approver-chip">
                    <text class="approver-name">{{ item.approveUserCurrentName || '未分配' }}</text>
                  </view>
                </view>
              </view>
              <view class="detail-row">
                <view class="actions">
                  <u-button type="primary"
                            size="small"
                            class="action-btn edit"
                            :disabled="item.approveStatus == 2 || item.approveStatus == 1 || item.approveStatus == 4 || item.approveStatus == 8"
                            @click="handleItemClick(item)">
                    ç¼–辑
                  </u-button>
                  <u-button type="success"
                            size="small"
                            class="action-btn approve"
                            :disabled="item.approveUserCurrentId == null || item.approveStatus == 2 || item.approveStatus == 3 || item.approveStatus == 4 || item.approveStatus == 8 || item.approveUserCurrentId !== userStore.id"
                            @click="approve(item)">
                    å®¡æ ¸
                  </u-button>
                </view>
              </view>
            </view>
          </view>
        </view>
      </view>
    </view>
    <view v-else
          class="no-data">
      <text>暂无审批数据</text>
    </view>
    <!-- æµ®åŠ¨æ“ä½œæŒ‰é’® -->
    <view class="fab-button"
          v-if="props.approveType != 5 && props.approveType != 6 && props.approveType != 7"
          @click="handleAdd">
      <up-icon name="plus"
               size="24"
               color="#ffffff"></up-icon>
    </view>
  </view>
</template>
<script setup>
    import {
        ref,
        toRefs,
        reactive
    } from "vue";
    import PageHeader from "@/components/PageHeader.vue";
    import {approveProcessListPage} from "@/api/collaborativeApproval/approvalProcess";
    import {onShow} from "@dcloudio/uni-app";
    import useUserStore from "@/store/modules/user";
    const userStore = useUserStore()
    // æ•°æ®
    const ledgerList = ref([]);
    const data = reactive({
        searchForm: {
            approveId: "",
        },
    });
    const { searchForm } = toRefs(data);
  import { ref, toRefs, reactive } from "vue";
  import PageHeader from "@/components/PageHeader.vue";
  import { approveProcessListPage } from "@/api/collaborativeApproval/approvalProcess";
  import { onLoad, onShow } from "@dcloudio/uni-app";
  import useUserStore from "@/store/modules/user";
    // è¿”回上一页
    const goBack = () => {
        uni.navigateBack();
    };
    // æŸ¥è¯¢åˆ—表
    const getList = () => {
        showLoadingToast('加载中...')
        const page = {
            current: -1,
            size: -1,
        };
        approveProcessListPage({
                ...page,approveType: 0,...searchForm.value
            })
            .then((res) => {
                ledgerList.value = res.data.records;
                closeToast()
            })
            .catch(() => {
                closeToast()
            });
    };
    // æ˜¾ç¤ºåŠ è½½æç¤º
    const showLoadingToast = (message) => {
        uni.showLoading({
            title: message,
            mask: true
        });
    };
  // æŽ¥æ”¶çˆ¶ç»„件传递的 approveType å‚æ•°
  const props = defineProps({
    approveType: {
      type: Number,
      default: 0,
    },
  });
    // å…³é—­æç¤º
    const closeToast = () => {
        uni.hideLoading();
    };
  // æ˜ å°„ approveType åˆ°å¯¹åº”的页面标题
  const getPageTitle = type => {
    const titleMap = {
      1: "公出管理",
      2: "请假管理",
      3: "出差管理",
      4: "报销管理",
      5: "采购管理",
      6: "报价管理",
      7: "发货审批",
      8: "危险作业审批",
    };
    return titleMap[type] || "审批管理";
  };
    // æ˜¾ç¤ºç­›é€‰é€‰é¡¹
    const showFilterOptions = () => {
        uni.showActionSheet({
            itemList: ["按日期筛选", "按状态筛选", "按金额筛选"],
            success: (res) => {
                console.log("选择了筛选选项:", res.tapIndex);
            },
        });
    };
    // æ ¼å¼åŒ–回款方式
    const formatReceiptType = (params) => {
        if (params == 0) {
            return "待审核";
        } else if (params == 1) {
            return "审核中";
        } else if (params == 2) {
            return "审核完成";
        } else if (params == 4) {
            return "已重新提交";
        } else {
            return '不通过';
        }
    };
    // èŽ·å–æ ‡ç­¾æ ·å¼ç±»
    const getTagClass = (type) => {
        if (type == 0) {
            return "warning";
        } else if (type == 1) {
            return "primary";
        } else if (type == 2) {
            return "success";
        } else if (type == 4) {
            return "primary";
        } else {
            return "error";
        }
    };
  const pageTitle = getPageTitle(props.approveType);
    // ç‚¹å‡»åˆ—表项
    const handleItemClick = (item) => {
        // ä½¿ç”¨æœ¬åœ°å­˜å‚¨ä¼ é€’数据
        uni.setStorageSync('invoiceLedgerEditRow', JSON.stringify(item));
        uni.setStorageSync('operationType', 'edit');
        uni.setStorageSync('approveId', item.approveId);
        uni.navigateTo({
            url: "/pages/cooperativeOffice/collaborativeApproval/detail",
        });
    };
  const userStore = useUserStore();
  // æ•°æ®
  const ledgerList = ref([]);
  const data = reactive({
    searchForm: {
      approveId: "",
    },
  });
  const { searchForm } = toRefs(data);
    // æ·»åŠ æ–°è®°å½•
    const handleAdd = () => {
        uni.setStorageSync('operationType', 'add');
        uni.navigateTo({
            url: "/pages/cooperativeOffice/collaborativeApproval/detail",
        });
    };
    // ç‚¹å‡»å®¡æ ¸
    const approve = (item) => {
        uni.setStorageSync('approveId', item.approveId);
        uni.navigateTo({
            url: "/pages/cooperativeOffice/collaborativeApproval/approve"
        })
    }
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  // æŸ¥è¯¢åˆ—表
  const getList = () => {
    showLoadingToast("加载中...");
    const page = {
      current: -1,
      size: -1,
    };
    approveProcessListPage({
      ...page,
      approveType: props.approveType,
      ...searchForm.value,
    })
      .then(res => {
        ledgerList.value = res.data.records;
        closeToast();
      })
      .catch(() => {
        closeToast();
      });
  };
  // æ˜¾ç¤ºåŠ è½½æç¤º
  const showLoadingToast = message => {
    uni.showLoading({
      title: message,
      mask: true,
    });
  };
    onShow(() => {
        // é¡µé¢åŠ è½½å®ŒæˆåŽçš„åˆå§‹åŒ–é€»è¾‘
        getList();
    });
  // å…³é—­æç¤º
  const closeToast = () => {
    uni.hideLoading();
  };
  // æ˜¾ç¤ºç­›é€‰é€‰é¡¹
  const showFilterOptions = () => {
    uni.showActionSheet({
      itemList: ["按日期筛选", "按状态筛选", "按金额筛选"],
      success: res => {
        console.log("选择了筛选选项:", res.tapIndex);
      },
    });
  };
  // æ ¼å¼åŒ–回款方式
  const formatReceiptType = params => {
    if (params == 0) {
      return "待审核";
    } else if (params == 1) {
      return "审核中";
    } else if (params == 2) {
      return "审核完成";
    } else if (params == 4) {
      return "已重新提交";
    } else {
      return "不通过";
    }
  };
  // èŽ·å–æ ‡ç­¾æ ·å¼ç±»
  const getTagClass = type => {
    if (type == 0) {
      return "warning";
    } else if (type == 1) {
      return "primary";
    } else if (type == 2) {
      return "success";
    } else if (type == 4) {
      return "primary";
    } else {
      return "error";
    }
  };
  // ç‚¹å‡»åˆ—表项
  const handleItemClick = item => {
    // ä½¿ç”¨æœ¬åœ°å­˜å‚¨ä¼ é€’数据
    uni.setStorageSync("invoiceLedgerEditRow", JSON.stringify(item));
    uni.setStorageSync("operationType", "edit");
    uni.setStorageSync("approveId", item.approveId);
    uni.setStorageSync("approveType", props.approveType);
    uni.navigateTo({
      url: "/pages/cooperativeOffice/collaborativeApproval/detail",
    });
  };
  // æ·»åŠ æ–°è®°å½•
  const handleAdd = () => {
    uni.setStorageSync("operationType", "add");
    uni.setStorageSync("approveType", props.approveType);
    uni.navigateTo({
      url: `/pages/cooperativeOffice/collaborativeApproval/detail?approveType=${props.approveType}`,
    });
  };
  // ç‚¹å‡»å®¡æ ¸
  const approve = item => {
    uni.setStorageSync("approveId", item.approveId);
    uni.setStorageSync("approveType", props.approveType);
    uni.navigateTo({
      url:
        "/pages/cooperativeOffice/collaborativeApproval/approve?approveType=" +
        props.approveType,
    });
  };
  onLoad(options => {
    // è§£æžapproveId
    if (options.approveId) {
      searchForm.value.approveId = options.approveId;
    }
  });
  onShow(() => {
    // é¡µé¢åŠ è½½å®ŒæˆåŽçš„åˆå§‹åŒ–é€»è¾‘
    getList();
  });
</script>
<style scoped lang="scss">
    @import "../../../styles/sales-common.scss";
  @import "../../../styles/sales-common.scss";
    .u-divider {
        margin: 0 !important;
    }
  .u-divider {
    margin: 0 !important;
  }
    // æ–‡æ¡£å›¾æ ‡æ ·å¼ - è¦†ç›–公共样式中的背景色
    .document-icon {
        background: #ed8d05;
    }
  // æ–‡æ¡£å›¾æ ‡æ ·å¼ - è¦†ç›–公共样式中的背景色
  .document-icon {
    background: #ed8d05;
  }
    // æµ®åŠ¨æŒ‰é’®æ ·å¼ - è¦†ç›–公共样式中的背景色
    .fab-button {
        background: #ed8d05;
    }
  // æµ®åŠ¨æŒ‰é’®æ ·å¼ - è¦†ç›–公共样式中的背景色
  .fab-button {
    background: #ed8d05;
  }
    // ç‰¹æœ‰æ ·å¼
    .detail-row-user {
        display: flex;
        align-items: center;
        justify-content: space-between;
    }
    .detail-row-approveReason {
        display: flex;
        align-items: center;
        justify-content: space-between;
        margin-bottom: 8px;
    }
  // ç‰¹æœ‰æ ·å¼
  .detail-row-user {
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
    .detail-value.highlightBlue {
        color: #2979ff;
        font-weight: 500;
    }
  .detail-row-approveReason {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 8px;
  }
    .detail-value.highlightYellow {
        color: #ed8d05;
        font-weight: 500;
    }
  .detail-value.highlightBlue {
    color: #2979ff;
    font-weight: 500;
  }
    .approver-value {
        display: flex;
        justify-content: flex-end;
    }
    .approver-chip {
        display: inline-flex;
        align-items: center;
        gap: 6px;
        background: #f0f6ff;
        color: #2b7cff;
        border: 1px solid #e0efff;
        border-radius: 999px;
        padding: 4px 10px;
        max-width: 100%;
    }
    .approver-name {
        font-size: 12px;
        color: #2b7cff;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
  .detail-value.highlightYellow {
    color: #ed8d05;
    font-weight: 500;
  }
    .actions {
        display: flex;
        gap: 10px;
        align-items: center;
        justify-content: flex-end;
    }
  .approver-value {
    display: flex;
    justify-content: flex-end;
  }
    .action-btn {
        border-radius: 16px;
        height: 28px;
        line-height: 28px;
        padding: 0 12px;
    }
  .approver-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: #f0f6ff;
    color: #2b7cff;
    border: 1px solid #e0efff;
    border-radius: 999px;
    padding: 4px 10px;
    max-width: 100%;
  }
  .approver-name {
    font-size: 12px;
    color: #2b7cff;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .actions {
    display: flex;
    gap: 10px;
    align-items: center;
    justify-content: flex-end;
  }
  .action-btn {
    border-radius: 16px;
    height: 28px;
    line-height: 28px;
    padding: 0 12px;
  }
</style>
src/pages/cooperativeOffice/collaborativeApproval/index1.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
<template>
  <view class="container">
    <!-- å¼•å…¥index.vue组件并传递参数 -->
    <ApprovalProcessIndex :approveType="1" />
  </view>
</template>
<script setup>
import ApprovalProcessIndex from './index.vue'
</script>
<style scoped>
.container {
  width: 100%;
  height: 100%;
}
</style>
src/pages/cooperativeOffice/collaborativeApproval/index2.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
<template>
  <view class="container">
    <!-- å¼•å…¥index.vue组件并传递参数 -->
    <ApprovalProcessIndex :approveType="2" />
  </view>
</template>
<script setup>
import ApprovalProcessIndex from './index.vue'
</script>
<style scoped>
.container {
  width: 100%;
  height: 100%;
}
</style>
src/pages/cooperativeOffice/collaborativeApproval/index3.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
<template>
  <view class="container">
    <!-- å¼•å…¥index.vue组件并传递参数 -->
    <ApprovalProcessIndex :approveType="3" />
  </view>
</template>
<script setup>
import ApprovalProcessIndex from './index.vue'
</script>
<style scoped>
.container {
  width: 100%;
  height: 100%;
}
</style>
src/pages/cooperativeOffice/collaborativeApproval/index4.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
<template>
  <view class="container">
    <!-- å¼•å…¥index.vue组件并传递参数 -->
    <ApprovalProcessIndex :approveType="4" />
  </view>
</template>
<script setup>
import ApprovalProcessIndex from './index.vue'
</script>
<style scoped>
.container {
  width: 100%;
  height: 100%;
}
</style>
src/pages/cooperativeOffice/collaborativeApproval/index5.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
<template>
  <view class="container">
    <!-- å¼•å…¥index.vue组件并传递参数 -->
    <ApprovalProcessIndex :approveType="5" />
  </view>
</template>
<script setup>
  import ApprovalProcessIndex from "./index.vue";
</script>
<style scoped>
  .container {
    width: 100%;
    height: 100%;
  }
</style>
src/pages/cooperativeOffice/collaborativeApproval/index6.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
<template>
  <view class="container">
    <!-- å¼•å…¥index.vue组件并传递参数 -->
    <ApprovalProcessIndex :approveType="6" />
  </view>
</template>
<script setup>
  import ApprovalProcessIndex from "./index.vue";
</script>
<style scoped>
  .container {
    width: 100%;
    height: 100%;
  }
</style>
src/pages/cooperativeOffice/collaborativeApproval/index7.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
<template>
  <view class="container">
    <!-- å¼•å…¥index.vue组件并传递参数 -->
    <ApprovalProcessIndex :approveType="7" />
  </view>
</template>
<script setup>
  import ApprovalProcessIndex from "./index.vue";
</script>
<style scoped>
  .container {
    width: 100%;
    height: 100%;
  }
</style>
src/pages/cooperativeOffice/collaborativeApproval/index8.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
<template>
  <view class="container">
    <!-- å¼•å…¥index.vue组件并传递参数 -->
    <ApprovalProcessIndex :approveType="8" />
  </view>
</template>
<script setup>
  import ApprovalProcessIndex from "./index.vue";
</script>
<style scoped>
  .container {
    width: 100%;
    height: 100%;
  }
</style>
src/pages/cooperativeOffice/customerManage/detail.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,493 @@
<template>
  <view class="customer-detail-page">
    <!-- é¡µé¢å¤´éƒ¨ -->
    <PageHeader title="客户详情"
                @back="goBack" />
    <!-- è¯¦æƒ…内容 -->
    <view class="detail-content">
      <!-- å®¢æˆ·åŸºæœ¬ä¿¡æ¯ -->
      <view class="section">
        <view class="section-title">客户基本信息</view>
        <view class="info-list">
          <view class="info-item">
            <text class="info-label">客户名称</text>
            <text class="info-value">{{ detailData.customerName || '-' }}</text>
          </view>
          <view class="info-item">
            <text class="info-label">客户类型</text>
            <text class="info-value">{{ detailData.customerType || '-' }}</text>
          </view>
          <view class="info-item">
            <text class="info-label">纳税人识别号</text>
            <text class="info-value">{{ detailData.taxpayerIdentificationNumber || '-' }}</text>
          </view>
          <view class="info-item">
            <text class="info-label">公司地址</text>
            <text class="info-value">{{ detailData.companyAddress || '-' }}</text>
          </view>
          <view class="info-item">
            <text class="info-label">公司电话</text>
            <text class="info-value">{{ detailData.companyPhone || '-' }}</text>
          </view>
          <view class="info-item">
            <text class="info-label">联系人</text>
            <text class="info-value">{{ detailData.contactPerson || '-' }}</text>
          </view>
          <view class="info-item">
            <text class="info-label">联系电话</text>
            <text class="info-value">{{ detailData.contactPhone || '-' }}</text>
          </view>
        </view>
      </view>
      <!-- è·Ÿè¿›è®°å½• -->
      <view class="section">
        <view class="section-title">
          <view class="title-left">
            <up-icon name="clock-fill"
                     size="16"
                     color="#409eff" />
            <text>跟进记录</text>
          </view>
          <text class="record-count"
                v-if="detailData.followUpList && detailData.followUpList.length > 0">
            å…±{{ detailData.followUpList.length }}条
          </text>
        </view>
        <view v-if="detailData.followUpList && detailData.followUpList.length > 0"
              class="follow-up-list">
          <view v-for="(item, index) in detailData.followUpList"
                :key="index"
                class="follow-up-item">
            <!-- æ—¶é—´è½´å·¦ä¾§çº¿æ¡ -->
            <view class="timeline-left">
              <view class="timeline-dot"></view>
              <view class="timeline-line"
                    v-if="index !== detailData.followUpList.length - 1"></view>
            </view>
            <!-- å†…容区域 -->
            <view class="follow-up-content-wrapper">
              <view class="follow-up-header">
                <view class="follow-up-info">
                  <view class="user-info">
                    <up-icon name="account-fill"
                             size="14"
                             color="#909399" />
                    <text class="follow-up-name">{{ item.followerUserName || '-' }}</text>
                  </view>
                  <view class="time-info">
                    <up-icon name="clock"
                             size="12"
                             color="#c0c4cc" />
                    <text class="follow-up-time">{{ formatDateTime(item.followUpTime) }}</text>
                  </view>
                </view>
                <view class="follow-up-tags">
                  <up-tag type="primary"
                          size="mini">{{ item.followUpMethod || '-' }}</up-tag>
                  <up-tag :type="getFollowUpLevelType(item.followUpLevel)"
                          size="mini">{{ item.followUpLevel || '-' }}</up-tag>
                </view>
              </view>
              <view class="follow-up-content">
                <view class="content-label">
                  <up-icon name="edit-pen-fill"
                           size="12"
                           color="#c0c4cc" />
                  <text>跟进内容</text>
                </view>
                <view class="content-box">
                  <text class="content-text">{{ item.content || '-' }}</text>
                </view>
              </view>
              <!-- <view v-if="item.fileList && item.fileList.length > 0"
                    class="follow-up-files">
                <view class="files-header">
                  <up-icon name="folder-open"
                           size="12"
                           color="#c0c4cc" />
                  <text class="files-label">附件 ({{ item.fileList.length }})</text>
                </view>
                <view class="files-list">
                  <view v-for="(file, fileIndex) in item.fileList"
                        :key="fileIndex"
                        class="file-item">
                    <up-icon name="attach"
                             size="14"
                             color="#409eff" />
                    <text class="file-name">{{ file.name || '附件' + (fileIndex + 1) }}</text>
                  </view>
                </view>
              </view> -->
            </view>
          </view>
        </view>
        <view v-else
              class="no-data">
          <up-empty mode="data"
                    text="暂无跟进记录" />
        </view>
      </view>
      <!-- é“¶è¡Œä¿¡æ¯ -->
      <view class="section">
        <view class="section-title">银行信息</view>
        <view class="info-list">
          <view class="info-item">
            <text class="info-label">基本银行账户</text>
            <text class="info-value">{{ detailData.basicBankAccount || '-' }}</text>
          </view>
          <view class="info-item">
            <text class="info-label">开户行</text>
            <text class="info-value">{{ detailData.bankAccount || '-' }}</text>
          </view>
          <view class="info-item">
            <text class="info-label">银行账号</text>
            <text class="info-value">{{ detailData.bankCode || '-' }}</text>
          </view>
        </view>
      </view>
      <!-- ç»´æŠ¤ä¿¡æ¯ -->
      <view class="section">
        <view class="section-title">维护信息</view>
        <view class="info-list">
          <view class="info-item">
            <text class="info-label">维护人</text>
            <text class="info-value">{{ detailData.maintainer || '-' }}</text>
          </view>
          <view class="info-item">
            <text class="info-label">维护时间</text>
            <text class="info-value">{{ formatDateTime(detailData.maintenanceTime) }}</text>
          </view>
        </view>
      </view>
    </view>
  </view>
</template>
<script setup>
  import { ref, onMounted } from "vue";
  import PageHeader from "@/components/PageHeader.vue";
  import { getCustomer } from "@/api/cooperativeOffice/clientVisit";
  import { onLoad, onShow } from "@dcloudio/uni-app";
  import dayjs from "dayjs";
  // èŽ·å–é¡µé¢å‚æ•°
  const getPageId = () => {
    const pages = getCurrentPages();
    const currentPage = pages[pages.length - 1];
    const options = currentPage.options || {};
    return options.id || "";
  };
  // è¯¦æƒ…数据
  const detailData = ref({});
  // æ ¼å¼åŒ–日期时间
  const formatDateTime = dateStr => {
    if (!dateStr) return "-";
    return dayjs(dateStr).format("YYYY-MM-DD HH:mm:ss");
  };
  // èŽ·å–è·Ÿè¿›çº§åˆ«æ ‡ç­¾ç±»åž‹
  const getFollowUpLevelType = level => {
    switch (level) {
      case "非常熟悉":
        return "success";
      case "熟悉":
        return "primary";
      case "一般":
        return "warning";
      case "不熟悉":
        return "error";
      default:
        return "info";
    }
  };
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  const customerId = ref("");
  // customerId
  onLoad(options => {
    // è§£æžcustomerId
    if (options.customerId) {
      customerId.value = options.customerId;
      getDetail();
    }
  });
  // èŽ·å–è¯¦æƒ…æ•°æ®
  const getDetail = async () => {
    if (!customerId.value) {
      uni.showToast({
        title: "参数错误",
        icon: "none",
      });
      return;
    }
    try {
      uni.showLoading({
        title: "加载中...",
      });
      const res = await getCustomer(customerId.value);
      if (res.code === 200) {
        detailData.value = res.data;
      } else {
        uni.showToast({
          title: res.msg || "获取详情失败",
          icon: "none",
        });
      }
    } catch (error) {
      console.error("加载详情数据失败:", error);
      uni.showToast({
        title: "网络错误",
        icon: "none",
      });
    } finally {
      uni.hideLoading();
    }
  };
  onMounted(() => {});
</script>
<style scoped lang="scss">
  .customer-detail-page {
    min-height: 100vh;
    background-color: #f5f5f5;
  }
  .detail-content {
    padding: 16px;
  }
  .section {
    background: #ffffff;
    border-radius: 12px;
    margin-bottom: 16px;
    overflow: hidden;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
  }
  .section-title {
    padding: 16px;
    font-size: 16px;
    font-weight: 600;
    color: #303133;
    border-bottom: 1px solid #f0f0f0;
  }
  .info-list {
    padding: 8px 0;
  }
  .info-item {
    display: flex;
    padding: 12px 16px;
    border-bottom: 1px solid #f8f8f8;
  }
  .info-item:last-child {
    border-bottom: none;
  }
  .info-label {
    width: 120px;
    font-size: 14px;
    color: #606266;
  }
  .info-value {
    flex: 1;
    font-size: 14px;
    color: #303133;
    text-align: right;
  }
  .section-title {
    padding: 16px;
    font-size: 16px;
    font-weight: 600;
    color: #303133;
    border-bottom: 1px solid #f0f0f0;
    display: flex;
    justify-content: space-between;
    align-items: center;
  }
  .title-left {
    display: flex;
    align-items: center;
    gap: 8px;
  }
  .record-count {
    font-size: 12px;
    color: #909399;
    font-weight: normal;
    background: #f5f7fa;
    padding: 2px 8px;
    border-radius: 10px;
  }
  .follow-up-list {
    padding: 0;
  }
  .follow-up-item {
    display: flex;
    padding: 16px;
    position: relative;
  }
  .timeline-left {
    display: flex;
    flex-direction: column;
    align-items: center;
    margin-right: 12px;
    padding-top: 4px;
  }
  .timeline-dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: #409eff;
    border: 2px solid #e6f2ff;
    flex-shrink: 0;
  }
  .timeline-line {
    width: 2px;
    flex: 1;
    background: #e4e7ed;
    margin-top: 4px;
    min-height: 40px;
  }
  .follow-up-content-wrapper {
    flex: 1;
    background: #f8f9fa;
    border-radius: 8px;
    padding: 12px;
  }
  .follow-up-header {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    margin-bottom: 12px;
  }
  .follow-up-info {
    display: flex;
    flex-direction: column;
    gap: 6px;
  }
  .user-info {
    display: flex;
    align-items: center;
    gap: 6px;
  }
  .time-info {
    display: flex;
    align-items: center;
    gap: 4px;
  }
  .follow-up-name {
    font-size: 14px;
    font-weight: 600;
    color: #303133;
  }
  .follow-up-time {
    font-size: 12px;
    color: #909399;
  }
  .follow-up-tags {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
  }
  .follow-up-content {
    margin-bottom: 12px;
  }
  .content-label {
    display: flex;
    align-items: center;
    gap: 4px;
    font-size: 12px;
    color: #909399;
    margin-bottom: 8px;
  }
  .content-box {
    background: #ffffff;
    border-radius: 6px;
    padding: 10px 12px;
    border-left: 3px solid #409eff;
  }
  .content-text {
    font-size: 14px;
    color: #303133;
    line-height: 1.6;
    word-break: break-all;
    word-wrap: break-word;
  }
  .follow-up-files {
    margin-top: 12px;
    padding-top: 12px;
    border-top: 1px dashed #e4e7ed;
  }
  .files-header {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-bottom: 8px;
  }
  .files-label {
    font-size: 12px;
    color: #909399;
  }
  .files-list {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
  }
  .file-item {
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 6px 10px;
    background: #ffffff;
    border: 1px solid #e4e7ed;
    border-radius: 4px;
  }
  .file-name {
    font-size: 12px;
    color: #606266;
  }
  .no-data {
    padding: 40px 20px;
    text-align: center;
  }
</style>
src/pages/cooperativeOffice/noticeManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,809 @@
<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/equipmentManagement/inspection/detail.vue
@@ -1,20 +1,25 @@
<template>
  <view class="inspection-detail">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="设备巡检详情" @back="goBack" />
    <PageHeader title="设备巡检详情"
                @back="goBack" />
    <!-- è®¾å¤‡ä¿¡æ¯å¡ç‰‡ -->
    <view class="device-card">
      <view class="device-header">
        <view class="device-icon">
          <up-icon name="settings" size="24" color="#1890ff"></up-icon>
          <up-icon name="settings"
                   size="24"
                   color="#1890ff"></up-icon>
        </view>
        <view class="device-info">
          <text class="device-name">{{ deviceInfo.deviceName }}</text>
          <text class="device-code">{{ deviceInfo.deviceCode }}</text>
        </view>
        <view class="qr-scan" @click="scanDeviceQR">
          <up-icon name="scan" size="20" color="#1890ff"></up-icon>
        <view class="qr-scan"
              @click="scanDeviceQR">
          <up-icon name="scan"
                   size="20"
                   color="#1890ff"></up-icon>
          <text class="scan-text">扫码</text>
        </view>
      </view>
@@ -33,134 +38,131 @@
        </view>
      </view>
    </view>
    <!-- å·¡æ£€é¡¹ç›®æ¸…单 -->
    <view class="inspection-items">
      <view class="section-title">
        <up-icon name="list" size="18" color="#333"></up-icon>
        <up-icon name="list"
                 size="18"
                 color="#333"></up-icon>
        <text class="title-text">巡检项目清单</text>
        <text class="progress-text">({{ completedItems }}/{{ totalItems }})</text>
      </view>
      <view class="items-list">
        <view
          v-for="(item, index) in inspectionItems"
          :key="index"
          class="inspection-item"
          :class="{ 'completed': item.completed, 'abnormal': item.isAbnormal }"
        >
          <view class="item-header" @click="toggleItem(index)">
        <view v-for="(item, index) in inspectionItems"
              :key="index"
              class="inspection-item"
              :class="{ 'completed': item.completed, 'abnormal': item.isAbnormal }">
          <view class="item-header"
                @click="toggleItem(index)">
            <view class="item-left">
              <view class="checkbox" :class="{ 'checked': item.completed }">
                <up-icon v-if="item.completed" name="checkmark" size="14" color="#ffffff"></up-icon>
              <view class="checkbox"
                    :class="{ 'checked': item.completed }">
                <up-icon v-if="item.completed"
                         name="checkmark"
                         size="14"
                         color="#ffffff"></up-icon>
              </view>
              <text class="item-name">{{ item.name }}</text>
            </view>
            <view class="item-status">
              <u-tag v-if="item.isAbnormal" type="error" size="mini">异常</u-tag>
              <u-tag v-else-if="item.completed" type="success" size="mini">正常</u-tag>
              <u-tag v-else type="info" size="mini">待检</u-tag>
              <u-tag v-if="item.isAbnormal"
                     type="error"
                     size="mini">异常</u-tag>
              <u-tag v-else-if="item.completed"
                     type="success"
                     size="mini">正常</u-tag>
              <u-tag v-else
                     type="info"
                     size="mini">待检</u-tag>
            </view>
          </view>
          <!-- å±•开的详情内容 -->
          <view v-if="item.expanded" class="item-content">
          <view v-if="item.expanded"
                class="item-content">
            <view class="item-description">
              <text class="desc-text">{{ item.description }}</text>
            </view>
            <!-- å·¡æ£€ç»“果选择 -->
            <view class="result-section">
              <text class="section-label">巡检结果:</text>
              <view class="result-options">
                <u-radio-group v-model="item.result" @change="onResultChange(index, $event)">
                  <u-radio
                    v-for="option in resultOptions"
                    :key="option.value"
                    :label="option.value"
                    :name="option.label"
                    size="small"
                  >
                <u-radio-group v-model="item.result"
                               @change="onResultChange(index, $event)">
                  <u-radio v-for="option in resultOptions"
                           :key="option.value"
                           :label="option.value"
                           :name="option.label"
                           size="small">
                    {{ option.label }}
                  </u-radio>
                </u-radio-group>
              </view>
            </view>
            <!-- å¼‚常情况描述 -->
            <view v-if="item.result === 'abnormal'" class="abnormal-section">
            <view v-if="item.result === 'abnormal'"
                  class="abnormal-section">
              <text class="section-label">异常描述:</text>
              <up-textarea
                v-model="item.abnormalDesc"
                placeholder="请详细描述异常情况"
                :maxlength="200"
                count
                height="80"
              ></up-textarea>
              <up-textarea v-model="item.abnormalDesc"
                           placeholder="请详细描述异常情况"
                           :maxlength="200"
                           count
                           height="80"></up-textarea>
            </view>
            <!-- å›¾ç‰‡ä¸Šä¼  -->
            <view class="upload-section">
              <text class="section-label">现场照片:</text>
              <up-upload
                :fileList="item.images"
                @afterRead="(event) => afterRead(event, index, 'images')"
                @delete="(event) => deleteFile(event, index, 'images')"
                name="images"
                multiple
                :maxCount="5"
                :previewImage="true"
              >
              <up-upload :fileList="item.images"
                         @afterRead="(event) => afterRead(event, index, 'images')"
                         @delete="(event) => deleteFile(event, index, 'images')"
                         name="images"
                         multiple
                         :maxCount="5"
                         :previewImage="true">
                <view class="upload-btn">
                  <up-icon name="camera" size="20" color="#999"></up-icon>
                  <up-icon name="camera"
                           size="20"
                           color="#999"></up-icon>
                  <text class="upload-text">添加照片</text>
                </view>
              </up-upload>
            </view>
            <!-- è§†é¢‘上传 -->
            <view class="upload-section">
              <text class="section-label">现场视频:</text>
              <up-upload
                :fileList="item.videos"
                @afterRead="(event) => afterRead(event, index, 'videos')"
                @delete="(event) => deleteFile(event, index, 'videos')"
                name="videos"
                :maxCount="2"
                accept="video"
              >
              <up-upload :fileList="item.videos"
                         @afterRead="(event) => afterRead(event, index, 'videos')"
                         @delete="(event) => deleteFile(event, index, 'videos')"
                         name="videos"
                         :maxCount="2"
                         accept="video">
                <view class="upload-btn">
                  <up-icon name="play-circle" size="20" color="#999"></up-icon>
                  <up-icon name="play-circle"
                           size="20"
                           color="#999"></up-icon>
                  <text class="upload-text">添加视频</text>
                </view>
              </up-upload>
            </view>
            <!-- å¤‡æ³¨ -->
            <view class="remark-section">
              <text class="section-label">备注:</text>
              <up-textarea
                v-model="item.remark"
                placeholder="请输入备注信息(可选)"
                :maxlength="100"
                count
                height="60"
              ></up-textarea>
              <up-textarea v-model="item.remark"
                           placeholder="请输入备注信息(可选)"
                           :maxlength="100"
                           count
                           height="60"></up-textarea>
            </view>
          </view>
        </view>
      </view>
    </view>
    <!-- åº•部操作按钮 -->
    <view class="bottom-actions">
      <u-button
        type="primary"
        size="large"
        :disabled="!canSubmit"
        @click="submitInspection"
        :loading="submitting"
      >
      <u-button type="primary"
                size="large"
                :disabled="!canSubmit"
                @click="submitInspection"
                :loading="submitting">
        {{ allCompleted ? '提交巡检记录' : `继续巡检 (${completedItems}/${totalItems})` }}
      </u-button>
    </view>
@@ -168,638 +170,646 @@
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import PageHeader from '@/components/PageHeader.vue'
import { submitInspectionRecord } from '@/api/equipmentManagement/inspection'
import dayjs from 'dayjs'
  import { ref, computed, onMounted } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import { submitInspectionRecord } from "@/api/equipmentManagement/inspection";
  import dayjs from "dayjs";
// è®¾å¤‡ä¿¡æ¯
const deviceInfo = ref({})
  // è®¾å¤‡ä¿¡æ¯
  const deviceInfo = ref({});
// å·¡æ£€é¡¹ç›®åˆ—表
const inspectionItems = ref([])
  // å·¡æ£€é¡¹ç›®åˆ—表
  const inspectionItems = ref([]);
// æäº¤çŠ¶æ€
const submitting = ref(false)
  // æäº¤çŠ¶æ€
  const submitting = ref(false);
// å·¡æ£€ç»“果选项
const resultOptions = [
  { label: '正常', value: 'normal' },
  { label: '异常', value: 'abnormal' }
]
  // å·¡æ£€ç»“果选项
  const resultOptions = [
    { label: "正常", value: "normal" },
    { label: "异常", value: "abnormal" },
  ];
// æ˜¾ç¤ºæç¤ºä¿¡æ¯
const showToast = (message) => {
  uni.showToast({
    title: message,
    icon: 'none'
  })
}
  // æ˜¾ç¤ºæç¤ºä¿¡æ¯
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
// è®¡ç®—属性
const totalItems = computed(() => inspectionItems.value.length)
const completedItems = computed(() => inspectionItems.value.filter(item => item.completed).length)
const allCompleted = computed(() => completedItems.value === totalItems.value && totalItems.value > 0)
const canSubmit = computed(() => completedItems.value > 0)
  // è®¡ç®—属性
  const totalItems = computed(() => inspectionItems.value.length);
  const completedItems = computed(
    () => inspectionItems.value.filter(item => item.completed).length
  );
  const allCompleted = computed(
    () => completedItems.value === totalItems.value && totalItems.value > 0
  );
  const canSubmit = computed(() => completedItems.value > 0);
// è¿”回上一页
const goBack = () => {
  if (completedItems.value > 0) {
    uni.showModal({
      title: '提示',
      content: '当前有未保存的巡检记录,确定要离开吗?',
      success: (res) => {
        if (res.confirm) {
          uni.navigateBack()
  // è¿”回上一页
  const goBack = () => {
    if (completedItems.value > 0) {
      uni.showModal({
        title: "提示",
        content: "当前有未保存的巡检记录,确定要离开吗?",
        success: res => {
          if (res.confirm) {
            uni.navigateBack();
          }
        },
      });
    } else {
      uni.navigateBack();
    }
  };
  // æ‰«æè®¾å¤‡äºŒç»´ç 
  const scanDeviceQR = () => {
    uni.scanCode({
      success: res => {
        console.log("扫码结果:", res);
        if (res.result.includes(deviceInfo.value.deviceCode)) {
          showToast("设备确认成功");
          // è®°å½•扫码时间
          deviceInfo.value.scanTime = new Date().toISOString();
        } else {
          showToast("设备二维码不匹配");
        }
      },
      fail: err => {
        console.log("扫码失败:", err);
        showToast("扫码失败");
      },
    });
  };
  // åˆ‡æ¢å·¡æ£€é¡¹ç›®
  const toggleItem = index => {
    inspectionItems.value[index].expanded =
      !inspectionItems.value[index].expanded;
  };
  // å·¡æ£€ç»“果改变
  const onResultChange = (index, value) => {
    const item = inspectionItems.value[index];
    item.result = value;
    item.completed = true;
    item.isAbnormal = value === "abnormal";
    // å¦‚果选择正常,清空异常描述
    if (value === "normal") {
      item.abnormalDesc = "";
    }
  };
  // æ–‡ä»¶ä¸Šä¼ åŽå¤„理
  const afterRead = async (event, index, type) => {
    const { file } = event;
    const item = inspectionItems.value[index];
    // æ¨¡æ‹Ÿä¸Šä¼ è¿‡ç¨‹
    uni.showLoading({ title: "上传中..." });
    try {
      // è¿™é‡Œåº”该调用实际的上传API
      await new Promise(resolve => setTimeout(resolve, 1000));
      // æ·»åŠ åˆ°å¯¹åº”çš„æ–‡ä»¶åˆ—è¡¨
      if (type === "images") {
        item.images = item.images || [];
        item.images.push({
          url: file.url,
          name: file.name,
          size: file.size,
        });
      } else if (type === "videos") {
        item.videos = item.videos || [];
        item.videos.push({
          url: file.url,
          name: file.name,
          size: file.size,
        });
      }
    })
  } else {
    uni.navigateBack()
  }
}
// æ‰«æè®¾å¤‡äºŒç»´ç 
const scanDeviceQR = () => {
  uni.scanCode({
    success: (res) => {
      console.log('扫码结果:', res)
      if (res.result.includes(deviceInfo.value.deviceCode)) {
        showToast('设备确认成功')
        // è®°å½•扫码时间
        deviceInfo.value.scanTime = new Date().toISOString()
      } else {
        showToast('设备二维码不匹配')
      uni.hideLoading();
      showToast("上传成功");
    } catch (error) {
      uni.hideLoading();
      showToast("上传失败");
    }
  };
  // åˆ é™¤æ–‡ä»¶
  const deleteFile = (event, index, type) => {
    const item = inspectionItems.value[index];
    if (type === "images") {
      item.images.splice(event.index, 1);
    } else if (type === "videos") {
      item.videos.splice(event.index, 1);
    }
  };
  // æäº¤å·¡æ£€è®°å½•
  const submitInspection = async () => {
    if (!canSubmit.value) {
      showToast("请至少完成一项巡检");
      return;
    }
    // æ£€æŸ¥å¼‚常项目是否填写了描述
    const abnormalItems = inspectionItems.value.filter(item => item.isAbnormal);
    for (const item of abnormalItems) {
      if (!item.abnormalDesc || item.abnormalDesc.trim() === "") {
        showToast(`请填写"${item.name}"的异常描述`);
        return;
      }
    },
    fail: (err) => {
      console.log('扫码失败:', err)
      showToast('扫码失败')
    }
  })
}
// åˆ‡æ¢å·¡æ£€é¡¹ç›®
const toggleItem = (index) => {
  inspectionItems.value[index].expanded = !inspectionItems.value[index].expanded
}
    submitting.value = true;
// å·¡æ£€ç»“果改变
const onResultChange = (index, value) => {
  const item = inspectionItems.value[index]
  item.result = value
  item.completed = true
  item.isAbnormal = value === 'abnormal'
  // å¦‚果选择正常,清空异常描述
  if (value === 'normal') {
    item.abnormalDesc = ''
  }
}
    try {
      const recordData = {
        deviceId: deviceInfo.value.id,
        deviceCode: deviceInfo.value.deviceCode,
        inspectionDate: dayjs().format("YYYY-MM-DD"),
        inspector: deviceInfo.value.inspector,
        scanTime: deviceInfo.value.scanTime,
        items: inspectionItems.value.map(item => ({
          name: item.name,
          result: item.result,
          completed: item.completed,
          isAbnormal: item.isAbnormal,
          abnormalDesc: item.abnormalDesc,
          images: item.images || [],
          videos: item.videos || [],
          remark: item.remark,
        })),
        completedAt: new Date().toISOString(),
      };
// æ–‡ä»¶ä¸Šä¼ åŽå¤„理
const afterRead = async (event, index, type) => {
  const { file } = event
  const item = inspectionItems.value[index]
  // æ¨¡æ‹Ÿä¸Šä¼ è¿‡ç¨‹
  uni.showLoading({ title: '上传中...' })
  try {
    // è¿™é‡Œåº”该调用实际的上传API
    await new Promise(resolve => setTimeout(resolve, 1000))
    // æ·»åŠ åˆ°å¯¹åº”çš„æ–‡ä»¶åˆ—è¡¨
    if (type === 'images') {
      item.images = item.images || []
      item.images.push({
        url: file.url,
        name: file.name,
        size: file.size
      })
    } else if (type === 'videos') {
      item.videos = item.videos || []
      item.videos.push({
        url: file.url,
        name: file.name,
        size: file.size
      })
      // æ¨¡æ‹ŸAPI调用
      await new Promise(resolve => setTimeout(resolve, 2000));
      // å®žé™…API调用
      // await submitInspectionRecord(recordData)
      showToast("巡检记录提交成功");
      // è¿”回列表页面
      setTimeout(() => {
        uni.navigateBack();
      }, 1500);
    } catch (error) {
      showToast("提交失败,请重试");
    } finally {
      submitting.value = false;
    }
    uni.hideLoading()
    showToast('上传成功')
  } catch (error) {
    uni.hideLoading()
    showToast('上传失败')
  }
}
  };
// åˆ é™¤æ–‡ä»¶
const deleteFile = (event, index, type) => {
  const item = inspectionItems.value[index]
  if (type === 'images') {
    item.images.splice(event.index, 1)
  } else if (type === 'videos') {
    item.videos.splice(event.index, 1)
  }
}
// æäº¤å·¡æ£€è®°å½•
const submitInspection = async () => {
  if (!canSubmit.value) {
    showToast('请至少完成一项巡检')
    return
  }
  // æ£€æŸ¥å¼‚常项目是否填写了描述
  const abnormalItems = inspectionItems.value.filter(item => item.isAbnormal)
  for (const item of abnormalItems) {
    if (!item.abnormalDesc || item.abnormalDesc.trim() === '') {
      showToast(`请填写"${item.name}"的异常描述`)
      return
  // åˆå§‹åŒ–数据
  const initData = () => {
    // ä»Žå­˜å‚¨ä¸­èŽ·å–å½“å‰å·¡æ£€ä¿¡æ¯
    const currentInspection = uni.getStorageSync("currentInspection");
    if (currentInspection) {
      deviceInfo.value = currentInspection;
    }
  }
  submitting.value = true
  try {
    const recordData = {
      deviceId: deviceInfo.value.id,
      deviceCode: deviceInfo.value.deviceCode,
      inspectionDate: dayjs().format('YYYY-MM-DD'),
      inspector: deviceInfo.value.inspector,
      scanTime: deviceInfo.value.scanTime,
      items: inspectionItems.value.map(item => ({
        name: item.name,
        result: item.result,
        completed: item.completed,
        isAbnormal: item.isAbnormal,
        abnormalDesc: item.abnormalDesc,
        images: item.images || [],
        videos: item.videos || [],
        remark: item.remark
      })),
      completedAt: new Date().toISOString()
    }
    // æ¨¡æ‹ŸAPI调用
    await new Promise(resolve => setTimeout(resolve, 2000))
    // å®žé™…API调用
    // await submitInspectionRecord(recordData)
    showToast('巡检记录提交成功')
    // è¿”回列表页面
    setTimeout(() => {
      uni.navigateBack()
    }, 1500)
  } catch (error) {
    showToast('提交失败,请重试')
  } finally {
    submitting.value = false
  }
}
// åˆå§‹åŒ–数据
const initData = () => {
  // ä»Žå­˜å‚¨ä¸­èŽ·å–å½“å‰å·¡æ£€ä¿¡æ¯
  const currentInspection = uni.getStorageSync('currentInspection')
  if (currentInspection) {
    deviceInfo.value = currentInspection
  }
  // æ¨¡æ‹Ÿå·¡æ£€é¡¹ç›®æ•°æ®
  inspectionItems.value = [
    {
      name: '设备外观检查',
      description: '检查设备外观是否有损坏、锈蚀、变形等异常情况',
      completed: false,
      expanded: false,
      result: '',
      isAbnormal: false,
      abnormalDesc: '',
      images: [],
      videos: [],
      remark: ''
    },
    {
      name: '运行状态检查',
      description: '检查设备运行是否正常,有无异常声音、振动等',
      completed: false,
      expanded: false,
      result: '',
      isAbnormal: false,
      abnormalDesc: '',
      images: [],
      videos: [],
      remark: ''
    },
    {
      name: '安全装置检查',
      description: '检查各类安全装置是否完好,安全标识是否清晰',
      completed: false,
      expanded: false,
      result: '',
      isAbnormal: false,
      abnormalDesc: '',
      images: [],
      videos: [],
      remark: ''
    },
    {
      name: '环境条件检查',
      description: '检查设备周围环境是否符合要求,通风、照明等是否正常',
      completed: false,
      expanded: false,
      result: '',
      isAbnormal: false,
      abnormalDesc: '',
      images: [],
      videos: [],
      remark: ''
    },
    {
      name: '仪表读数记录',
      description: '记录相关仪表的读数,检查是否在正常范围内',
      completed: false,
      expanded: false,
      result: '',
      isAbnormal: false,
      abnormalDesc: '',
      images: [],
      videos: [],
      remark: ''
    }
  ]
}
    // æ¨¡æ‹Ÿå·¡æ£€é¡¹ç›®æ•°æ®
    inspectionItems.value = [
      {
        name: "设备外观检查",
        description: "检查设备外观是否有损坏、锈蚀、变形等异常情况",
        completed: false,
        expanded: false,
        result: "",
        isAbnormal: false,
        abnormalDesc: "",
        images: [],
        videos: [],
        remark: "",
      },
      {
        name: "运行状态检查",
        description: "检查设备运行是否正常,有无异常声音、振动等",
        completed: false,
        expanded: false,
        result: "",
        isAbnormal: false,
        abnormalDesc: "",
        images: [],
        videos: [],
        remark: "",
      },
      {
        name: "安全装置检查",
        description: "检查各类安全装置是否完好,安全标识是否清晰",
        completed: false,
        expanded: false,
        result: "",
        isAbnormal: false,
        abnormalDesc: "",
        images: [],
        videos: [],
        remark: "",
      },
      {
        name: "环境条件检查",
        description: "检查设备周围环境是否符合要求,通风、照明等是否正常",
        completed: false,
        expanded: false,
        result: "",
        isAbnormal: false,
        abnormalDesc: "",
        images: [],
        videos: [],
        remark: "",
      },
      {
        name: "仪表读数记录",
        description: "记录相关仪表的读数,检查是否在正常范围内",
        completed: false,
        expanded: false,
        result: "",
        isAbnormal: false,
        abnormalDesc: "",
        images: [],
        videos: [],
        remark: "",
      },
    ];
  };
onMounted(() => {
  initData()
})
  onMounted(() => {
    initData();
  });
onShow(() => {
  // é¡µé¢æ˜¾ç¤ºæ—¶åˆ·æ–°æ•°æ®
})
  onShow(() => {
    // é¡µé¢æ˜¾ç¤ºæ—¶åˆ·æ–°æ•°æ®
  });
</script>
<style scoped lang="scss">
.inspection-detail {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  min-height: 100vh;
  padding-bottom: 80px;
  position: relative;
  &::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 200px;
    background: linear-gradient(135deg, rgba(102, 126, 234, 0.8) 0%, rgba(118, 75, 162, 0.8) 100%);
    z-index: 0;
  .inspection-detail {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    min-height: 100vh;
    padding-bottom: 80px;
    position: relative;
    &::before {
      content: "";
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      height: 200px;
      background: linear-gradient(
        135deg,
        rgba(102, 126, 234, 0.8) 0%,
        rgba(118, 75, 162, 0.8) 100%
      );
      z-index: 0;
    }
  }
}
.device-card {
  background: rgba(255, 255, 255, 0.95);
  backdrop-filter: blur(15px);
  margin: 10px 20px;
  border-radius: 20px;
  padding: 24px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.2);
  position: relative;
  z-index: 1;
  transition: all 0.3s ease;
  &:hover {
    transform: translateY(-2px);
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
  }
}
  .device-card {
    background: rgba(255, 255, 255, 0.95);
    backdrop-filter: blur(15px);
    margin: 10px 20px;
    border-radius: 20px;
    padding: 24px;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
    border: 1px solid rgba(255, 255, 255, 0.2);
    position: relative;
    z-index: 1;
    transition: all 0.3s ease;
.device-header {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-bottom: 20px;
}
.device-icon {
  width: 56px;
  height: 56px;
  background: linear-gradient(135deg, #667eea, #764ba2);
  border-radius: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 4px 16px rgba(102, 126, 234, 0.3);
  transition: all 0.3s ease;
  &:hover {
    transform: scale(1.05);
  }
}
.device-info {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.device-name {
  font-size: 20px;
  font-weight: 600;
  color: #1a1a1a;
  line-height: 1.3;
}
.device-code {
  font-size: 13px;
  color: #8c8c8c;
  font-weight: 500;
  padding: 4px 12px;
  background: rgba(140, 140, 140, 0.1);
  border-radius: 12px;
  display: inline-block;
  width: fit-content;
}
.qr-scan {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 12px 16px;
  background: linear-gradient(135deg, #52c41a, #389e0d);
  border-radius: 12px;
  box-shadow: 0 4px 15px rgba(82, 196, 26, 0.3);
  transition: all 0.3s ease;
  &:hover {
    transform: scale(1.05);
    box-shadow: 0 6px 20px rgba(82, 196, 26, 0.4);
  }
  &:active {
    transform: scale(0.98);
  }
}
.scan-text {
  font-size: 13px;
  color: #ffffff;
  font-weight: 600;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.device-details {
  display: flex;
  flex-direction: column;
  gap: 12px;
  background: rgba(248, 250, 252, 0.8);
  border-radius: 16px;
  padding: 16px;
  backdrop-filter: blur(10px);
}
.detail-item {
  display: flex;
  align-items: center;
  font-size: 14px;
  padding: 8px 0;
  transition: all 0.2s ease;
  &:hover {
    background: rgba(255, 255, 255, 0.5);
    margin: 0 -8px;
    padding-left: 8px;
    padding-right: 8px;
    border-radius: 8px;
  }
}
.label {
  color: #595959;
  min-width: 80px;
  font-weight: 500;
}
.value {
  color: #262626;
  flex: 1;
  font-weight: 500;
}
.inspection-items {
  margin: 10px 20px;
  position: relative;
  z-index: 1;
}
.section-title {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 20px 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.2);
  background: rgba(255, 255, 255, 0.95);
  backdrop-filter: blur(15px);
  border-radius: 16px;
  padding: 20px;
  margin-bottom: 16px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
}
.title-text {
  font-size: 18px;
  font-weight: 600;
  color: #1a1a1a;
  flex: 1;
}
.progress-text {
  font-size: 15px;
  font-weight: 600;
  background: linear-gradient(135deg, #667eea, #764ba2);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}
.items-list {
  background: rgba(255, 255, 255, 0.95);
  backdrop-filter: blur(15px);
  border-radius: 20px;
  overflow: hidden;
  margin-top: 0;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.2);
}
.inspection-item {
  border-bottom: 1px solid rgba(0, 0, 0, 0.06);
  transition: all 0.3s ease;
  &:last-child {
    border-bottom: none;
  }
  &.completed {
    background: rgba(82, 196, 26, 0.05);
    border-left: 4px solid #52c41a;
  }
  &.abnormal {
    background: rgba(255, 77, 79, 0.05);
    border-left: 4px solid #ff4d4f;
  }
  &:hover {
    background: rgba(102, 126, 234, 0.05);
  }
}
.item-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 15px;
  cursor: pointer;
}
.item-left {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: 1;
}
.checkbox {
  width: 20px;
  height: 20px;
  border: 2px solid #d9d9d9;
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.3s;
  &.checked {
    background: #52c41a;
    border-color: #52c41a;
  }
}
.item-name {
  font-size: 15px;
  color: #333;
  font-weight: 500;
}
.item-status {
  flex-shrink: 0;
}
.item-content {
  padding: 0 15px 20px;
  border-top: 1px solid #f5f5f5;
}
.item-description {
  padding: 15px 0;
}
.desc-text {
  font-size: 14px;
  color: #666;
  line-height: 1.5;
}
.result-section,
.abnormal-section,
.upload-section,
.remark-section {
  margin-top: 15px;
}
.section-label {
  display: block;
  font-size: 14px;
  color: #333;
  margin-bottom: 8px;
  font-weight: 500;
}
.result-options {
  display: flex;
  gap: 20px;
}
.upload-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 88px;
  height: 88px;
  border: 2px dashed rgba(102, 126, 234, 0.3);
  border-radius: 16px;
  background: rgba(102, 126, 234, 0.05);
  gap: 8px;
  transition: all 0.3s ease;
  &:hover {
    border-color: rgba(102, 126, 234, 0.5);
    background: rgba(102, 126, 234, 0.1);
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2);
  }
  &:active {
    transform: translateY(0);
  }
}
.upload-text {
  font-size: 13px;
  color: #667eea;
  font-weight: 500;
}
.bottom-actions {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  background: rgba(255, 255, 255, 0.95);
  backdrop-filter: blur(20px);
  padding: 20px;
  border-top: 1px solid rgba(255, 255, 255, 0.2);
  box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.1);
  z-index: 10;
  button {
    height: 48px;
    border-radius: 16px;
    font-weight: 600;
    font-size: 16px;
    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
    &:hover {
      transform: translateY(-2px);
      box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
      box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
    }
  }
  .device-header {
    display: flex;
    align-items: center;
    gap: 16px;
    margin-bottom: 20px;
  }
  .device-icon {
    width: 56px;
    height: 56px;
    background: linear-gradient(135deg, #667eea, #764ba2);
    border-radius: 16px;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 4px 16px rgba(102, 126, 234, 0.3);
    transition: all 0.3s ease;
    &:hover {
      transform: scale(1.05);
    }
  }
  .device-info {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 6px;
  }
  .device-name {
    font-size: 20px;
    font-weight: 600;
    color: #1a1a1a;
    line-height: 1.3;
  }
  .device-code {
    font-size: 13px;
    color: #8c8c8c;
    font-weight: 500;
    padding: 4px 12px;
    background: rgba(140, 140, 140, 0.1);
    border-radius: 12px;
    display: inline-block;
    width: fit-content;
  }
  .qr-scan {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 12px 16px;
    background: linear-gradient(135deg, #52c41a, #389e0d);
    border-radius: 12px;
    box-shadow: 0 4px 15px rgba(82, 196, 26, 0.3);
    transition: all 0.3s ease;
    &:hover {
      transform: scale(1.05);
      box-shadow: 0 6px 20px rgba(82, 196, 26, 0.4);
    }
    &:active {
      transform: scale(0.98);
    }
  }
  .scan-text {
    font-size: 13px;
    color: #ffffff;
    font-weight: 600;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
  }
  .device-details {
    display: flex;
    flex-direction: column;
    gap: 12px;
    background: rgba(248, 250, 252, 0.8);
    border-radius: 16px;
    padding: 16px;
    backdrop-filter: blur(10px);
  }
  .detail-item {
    display: flex;
    align-items: center;
    font-size: 14px;
    padding: 8px 0;
    transition: all 0.2s ease;
    &:hover {
      background: rgba(255, 255, 255, 0.5);
      margin: 0 -8px;
      padding-left: 8px;
      padding-right: 8px;
      border-radius: 8px;
    }
  }
  .label {
    color: #595959;
    min-width: 80px;
    font-weight: 500;
  }
  .value {
    color: #262626;
    flex: 1;
    font-weight: 500;
  }
  .inspection-items {
    margin: 10px 20px;
    position: relative;
    z-index: 1;
  }
  .section-title {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 20px 0;
    border-bottom: 1px solid rgba(255, 255, 255, 0.2);
    background: rgba(255, 255, 255, 0.95);
    backdrop-filter: blur(15px);
    border-radius: 16px;
    padding: 20px;
    margin-bottom: 16px;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
  }
  .title-text {
    font-size: 18px;
    font-weight: 600;
    color: #1a1a1a;
    flex: 1;
  }
  .progress-text {
    font-size: 15px;
    font-weight: 600;
    background: linear-gradient(135deg, #667eea, #764ba2);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
  }
  .items-list {
    background: rgba(255, 255, 255, 0.95);
    backdrop-filter: blur(15px);
    border-radius: 20px;
    overflow: hidden;
    margin-top: 0;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
    border: 1px solid rgba(255, 255, 255, 0.2);
  }
  .inspection-item {
    border-bottom: 1px solid rgba(0, 0, 0, 0.06);
    transition: all 0.3s ease;
    &:last-child {
      border-bottom: none;
    }
    &.completed {
      background: rgba(82, 196, 26, 0.05);
      border-left: 4px solid #52c41a;
    }
    &.abnormal {
      background: rgba(255, 77, 79, 0.05);
      border-left: 4px solid #ff4d4f;
    }
    &:hover {
      background: rgba(102, 126, 234, 0.05);
    }
  }
  .item-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 15px;
    cursor: pointer;
  }
  .item-left {
    display: flex;
    align-items: center;
    gap: 12px;
    flex: 1;
  }
  .checkbox {
    width: 20px;
    height: 20px;
    border: 2px solid #d9d9d9;
    border-radius: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.3s;
    &.checked {
      background: #52c41a;
      border-color: #52c41a;
    }
  }
  .item-name {
    font-size: 15px;
    color: #333;
    font-weight: 500;
  }
  .item-status {
    flex-shrink: 0;
  }
  .item-content {
    padding: 0 15px 20px;
    border-top: 1px solid #f5f5f5;
  }
  .item-description {
    padding: 15px 0;
  }
  .desc-text {
    font-size: 14px;
    color: #666;
    line-height: 1.5;
  }
  .result-section,
  .abnormal-section,
  .upload-section,
  .remark-section {
    margin-top: 15px;
  }
  .section-label {
    display: block;
    font-size: 14px;
    color: #333;
    margin-bottom: 8px;
    font-weight: 500;
  }
  .result-options {
    display: flex;
    gap: 20px;
  }
  .upload-btn {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    width: 88px;
    height: 88px;
    border: 2px dashed rgba(102, 126, 234, 0.3);
    border-radius: 16px;
    background: rgba(102, 126, 234, 0.05);
    gap: 8px;
    transition: all 0.3s ease;
    &:hover {
      border-color: rgba(102, 126, 234, 0.5);
      background: rgba(102, 126, 234, 0.1);
      transform: translateY(-2px);
      box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2);
    }
    &:active {
      transform: translateY(0);
    }
  }
}
  .upload-text {
    font-size: 13px;
    color: #667eea;
    font-weight: 500;
  }
  .bottom-actions {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    background: rgba(255, 255, 255, 0.95);
    backdrop-filter: blur(20px);
    padding: 20px;
    border-top: 1px solid rgba(255, 255, 255, 0.2);
    box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.1);
    z-index: 10;
    button {
      height: 48px;
      border-radius: 16px;
      font-weight: 600;
      font-size: 16px;
      transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
      box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
      &:hover {
        transform: translateY(-2px);
        box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
      }
      &:active {
        transform: translateY(0);
      }
    }
  }
</style>
src/pages/equipmentManagement/inspection/index.vue
@@ -1,8 +1,8 @@
<template>
  <view class="inspection-page">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="设备巡检" @back="goBack" />
    <PageHeader title="设备巡检"
                @back="goBack" />
    <!-- ç»Ÿè®¡ä¿¡æ¯å¡ç‰‡ -->
    <view class="stats-cards">
      <view class="stat-card">
@@ -18,15 +18,20 @@
        <text class="stat-label">待巡检</text>
      </view>
    </view>
    <!-- å·¡æ£€æ¸…单 -->
    <view class="inspection-list" v-if="inspectionList.length > 0">
      <view v-for="(item, index) in inspectionList" :key="index">
        <view class="inspection-item" @click="startInspection(item)">
    <view class="inspection-list"
          v-if="inspectionList.length > 0">
      <view v-for="(item, index) in inspectionList"
            :key="index">
        <view class="inspection-item"
              @click="startInspection(item)">
          <view class="item-header">
            <view class="item-left">
              <view class="device-icon" :class="getStatusClass(item.status)">
                <up-icon :name="getStatusIcon(item.status)" size="16" color="#ffffff"></up-icon>
              <view class="device-icon"
                    :class="getStatusClass(item.status)">
                <up-icon :name="getStatusIcon(item.status)"
                         size="16"
                         color="#ffffff"></up-icon>
              </view>
              <view class="device-info">
                <text class="device-name">{{ item.deviceName }}</text>
@@ -34,14 +39,13 @@
              </view>
            </view>
            <view class="status-tag">
              <u-tag :type="getTagType(item.status)" size="mini">
              <u-tag :type="getTagType(item.status)"
                     size="mini">
                {{ getStatusText(item.status) }}
              </u-tag>
            </view>
          </view>
          <up-divider></up-divider>
          <view class="item-details">
            <view class="detail-row">
              <text class="detail-label">设备编号</text>
@@ -55,472 +59,485 @@
              <text class="detail-label">负责人</text>
              <text class="detail-value">{{ item.inspector || '-' }}</text>
            </view>
            <view class="detail-row" v-if="item.status === 2">
            <view class="detail-row"
                  v-if="item.status === 2">
              <text class="detail-label">完成时间</text>
              <text class="detail-value">{{ formatDateTime(item.completedTime) || '-' }}</text>
            </view>
          </view>
          <!-- æ“ä½œæŒ‰é’® -->
          <view class="action-buttons">
            <u-button
              type="primary"
              size="small"
              class="action-btn"
              :disabled="item.status === 2"
              @click.stop="startInspection(item)"
            >
            <u-button type="primary"
                      size="small"
                      class="action-btn"
                      :disabled="item.status === 2"
                      @click.stop="startInspection(item)">
              {{ item.status === 0 ? '开始巡检' : item.status === 1 ? '继续巡检' : '查看详情' }}
            </u-button>
            <u-button
              type="success"
              size="small"
              class="action-btn"
              :disabled="item.status !== 1"
              @click.stop="scanQRCode(item)"
            >
            <u-button type="success"
                      size="small"
                      class="action-btn"
                      :disabled="item.status !== 1"
                      @click.stop="scanQRCode(item)">
              æ‰«ç æ‰“卡
            </u-button>
          </view>
        </view>
      </view>
    </view>
    <view v-else class="no-data">
      <up-empty mode="data" text="暂无巡检任务"></up-empty>
    <view v-else
          class="no-data">
      <up-empty mode="data"
                text="暂无巡检任务"></up-empty>
    </view>
  </view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import PageHeader from '@/components/PageHeader.vue'
// import { getInspectionList } from '@/api/inspectionUpload/index'
import dayjs from 'dayjs'
  import { ref, computed, onMounted } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  // import { getInspectionList } from '@/api/inspectionUpload/index'
  import dayjs from "dayjs";
// é€‰ä¸­çš„æ—¥æœŸ
const selectedDate = ref(Date.now())
  // é€‰ä¸­çš„æ—¥æœŸ
  const selectedDate = ref(Date.now());
// å·¡æ£€æ¸…单数据
const inspectionList = ref([])
  // å·¡æ£€æ¸…单数据
  const inspectionList = ref([]);
// æ˜¾ç¤ºæç¤ºä¿¡æ¯
const showToast = (message) => {
  uni.showToast({
    title: message,
    icon: 'none'
  })
}
  // æ˜¾ç¤ºæç¤ºä¿¡æ¯
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
// è®¡ç®—统计数据
const totalCount = computed(() => inspectionList.value.length)
const completedCount = computed(() => inspectionList.value.filter(item => item.status === 2).length)
const pendingCount = computed(() => inspectionList.value.filter(item => item.status === 0).length)
  // è®¡ç®—统计数据
  const totalCount = computed(() => inspectionList.value.length);
  const completedCount = computed(
    () => inspectionList.value.filter(item => item.status === 2).length
  );
  const pendingCount = computed(
    () => inspectionList.value.filter(item => item.status === 0).length
  );
// è¿”回上一页
const goBack = () => {
  uni.navigateBack()
}
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
// æ—¥æœŸæ ¼å¼åŒ–器
const dateFormatter = (type, value) => {
  if (type === 'year') {
    return `${value}å¹´`
  }
  if (type === 'month') {
    return `${value}月`
  }
  if (type === 'day') {
    return `${value}日`
  }
  return value
}
// æ ¼å¼åŒ–日期
const formatDate = (timestamp) => {
  return dayjs(timestamp).format('YYYYå¹´MM月DD日')
}
// æ ¼å¼åŒ–日期时间
const formatDateTime = (dateStr) => {
  if (!dateStr) return ''
  return dayjs(dateStr).format('MM-DD HH:mm')
}
// æ—¥æœŸæ”¹å˜äº‹ä»¶
const onDateChange = (value) => {
  selectedDate.value = value.value
  getList()
}
// èŽ·å–çŠ¶æ€æ ·å¼ç±»
const getStatusClass = (status) => {
  switch (status) {
    case 0: return 'status-pending'
    case 1: return 'status-progress'
    case 2: return 'status-completed'
    default: return 'status-pending'
  }
}
// èŽ·å–çŠ¶æ€å›¾æ ‡
const getStatusIcon = (status) => {
  switch (status) {
    case 0: return 'clock'
    case 1: return 'play-circle'
    case 2: return 'checkmark-circle'
    default: return 'clock'
  }
}
// èŽ·å–æ ‡ç­¾ç±»åž‹
const getTagType = (status) => {
  switch (status) {
    case 0: return 'warning'
    case 1: return 'primary'
    case 2: return 'success'
    default: return 'info'
  }
}
// èŽ·å–çŠ¶æ€æ–‡æœ¬
const getStatusText = (status) => {
  switch (status) {
    case 0: return '待巡检'
    case 1: return '巡检中'
    case 2: return '已完成'
    default: return '未知'
  }
}
// å¼€å§‹å·¡æ£€
const startInspection = (item) => {
  // å­˜å‚¨å½“前巡检项目信息
  uni.setStorageSync('currentInspection', item)
  uni.navigateTo({
    url: '/pages/equipmentManagement/inspection/detail'
  })
}
// æ‰«ç æ‰“卡
const scanQRCode = (item) => {
  uni.scanCode({
    success: (res) => {
      console.log('扫码结果:', res)
      // éªŒè¯äºŒç»´ç å†…容
      if (res.result.includes(item.deviceCode)) {
        showToast('打卡成功')
        // æ›´æ–°æ‰“卡状态
        updateCheckInStatus(item.id)
      } else {
        showToast('二维码不匹配,请扫描正确的设备二维码')
      }
    },
    fail: (err) => {
      console.log('扫码失败:', err)
      showToast('扫码失败')
  // æ—¥æœŸæ ¼å¼åŒ–器
  const dateFormatter = (type, value) => {
    if (type === "year") {
      return `${value}å¹´`;
    }
  })
}
    if (type === "month") {
      return `${value}月`;
    }
    if (type === "day") {
      return `${value}日`;
    }
    return value;
  };
// æ›´æ–°æ‰“卡状态
const updateCheckInStatus = (id) => {
  // è¿™é‡Œåº”该调用API更新打卡状态
  // æš‚时模拟更新本地数据
  const item = inspectionList.value.find(item => item.id === id)
  if (item) {
    item.checkInTime = new Date().toISOString()
  }
}
  // æ ¼å¼åŒ–日期
  const formatDate = timestamp => {
    return dayjs(timestamp).format("YYYYå¹´MM月DD日");
  };
// æŸ¥è¯¢å·¡æ£€æ¸…单
const getList = () => {
  uni.showLoading({
    title: '加载中...',
    mask: true
  })
  const params = {
    date: dayjs(selectedDate.value).format('YYYY-MM-DD')
  }
  // æ¨¡æ‹Ÿæ•°æ®ï¼Œå®žé™…应该调用API
  setTimeout(() => {
    inspectionList.value = [
      {
        id: 1,
        deviceName: '空压机A01',
        deviceCode: 'KYJ-A01',
        location: '生产车间A区',
        inspectionTime: '08:00-09:00',
        inspector: '张三',
        status: 0, // 0:待巡检 1:巡检中 2:已完成
        completedTime: null
  // æ ¼å¼åŒ–日期时间
  const formatDateTime = dateStr => {
    if (!dateStr) return "";
    return dayjs(dateStr).format("MM-DD HH:mm");
  };
  // æ—¥æœŸæ”¹å˜äº‹ä»¶
  const onDateChange = value => {
    selectedDate.value = value.value;
    getList();
  };
  // èŽ·å–çŠ¶æ€æ ·å¼ç±»
  const getStatusClass = status => {
    switch (status) {
      case 0:
        return "status-pending";
      case 1:
        return "status-progress";
      case 2:
        return "status-completed";
      default:
        return "status-pending";
    }
  };
  // èŽ·å–çŠ¶æ€å›¾æ ‡
  const getStatusIcon = status => {
    switch (status) {
      case 0:
        return "clock";
      case 1:
        return "play-circle";
      case 2:
        return "checkmark-circle";
      default:
        return "clock";
    }
  };
  // èŽ·å–æ ‡ç­¾ç±»åž‹
  const getTagType = status => {
    switch (status) {
      case 0:
        return "warning";
      case 1:
        return "primary";
      case 2:
        return "success";
      default:
        return "info";
    }
  };
  // èŽ·å–çŠ¶æ€æ–‡æœ¬
  const getStatusText = status => {
    switch (status) {
      case 0:
        return "待巡检";
      case 1:
        return "巡检中";
      case 2:
        return "已完成";
      default:
        return "未知";
    }
  };
  // å¼€å§‹å·¡æ£€
  const startInspection = item => {
    // å­˜å‚¨å½“前巡检项目信息
    uni.setStorageSync("currentInspection", item);
    uni.navigateTo({
      url: "/pages/equipmentManagement/inspection/detail",
    });
  };
  // æ‰«ç æ‰“卡
  const scanQRCode = item => {
    uni.scanCode({
      success: res => {
        console.log("扫码结果:", res);
        // éªŒè¯äºŒç»´ç å†…容
        if (res.result.includes(item.deviceCode)) {
          showToast("打卡成功");
          // æ›´æ–°æ‰“卡状态
          updateCheckInStatus(item.id);
        } else {
          showToast("二维码不匹配,请扫描正确的设备二维码");
        }
      },
      {
        id: 2,
        deviceName: '冷却塔B02',
        deviceCode: 'LQT-B02',
        location: '生产车间B区',
        inspectionTime: '09:00-10:00',
        inspector: '李四',
        status: 1,
        completedTime: null
      fail: err => {
        console.log("扫码失败:", err);
        showToast("扫码失败");
      },
      {
        id: 3,
        deviceName: '变压器C03',
        deviceCode: 'BYQ-C03',
        location: '配电房',
        inspectionTime: '10:00-11:00',
        inspector: '王五',
        status: 2,
        completedTime: '2024-01-15T10:30:00'
      }
    ]
    uni.hideLoading()
  }, 1000)
  // å®žé™…API调用
  // getInspectionList(params)
  //   .then((res) => {
  //     inspectionList.value = res.records || res.data?.records || []
  //     uni.hideLoading()
  //   })
  //   .catch(() => {
  //     uni.hideLoading()
  //     showToast('获取数据失败')
  //   })
}
    });
  };
onMounted(() => {
  getList()
})
  // æ›´æ–°æ‰“卡状态
  const updateCheckInStatus = id => {
    // è¿™é‡Œåº”该调用API更新打卡状态
    // æš‚时模拟更新本地数据
    const item = inspectionList.value.find(item => item.id === id);
    if (item) {
      item.checkInTime = new Date().toISOString();
    }
  };
onShow(() => {
  getList()
})
  // æŸ¥è¯¢å·¡æ£€æ¸…单
  const getList = () => {
    uni.showLoading({
      title: "加载中...",
      mask: true,
    });
    const params = {
      date: dayjs(selectedDate.value).format("YYYY-MM-DD"),
    };
    // æ¨¡æ‹Ÿæ•°æ®ï¼Œå®žé™…应该调用API
    setTimeout(() => {
      inspectionList.value = [
        {
          id: 1,
          deviceName: "空压机A01",
          deviceCode: "KYJ-A01",
          location: "生产车间A区",
          inspectionTime: "08:00-09:00",
          inspector: "张三",
          status: 0, // 0:待巡检 1:巡检中 2:已完成
          completedTime: null,
        },
        {
          id: 2,
          deviceName: "冷却塔B02",
          deviceCode: "LQT-B02",
          location: "生产车间B区",
          inspectionTime: "09:00-10:00",
          inspector: "李四",
          status: 1,
          completedTime: null,
        },
        {
          id: 3,
          deviceName: "变压器C03",
          deviceCode: "BYQ-C03",
          location: "配电房",
          inspectionTime: "10:00-11:00",
          inspector: "王五",
          status: 2,
          completedTime: "2024-01-15T10:30:00",
        },
      ];
      uni.hideLoading();
    }, 1000);
    // å®žé™…API调用
    // getInspectionList(params)
    //   .then((res) => {
    //     inspectionList.value = res.records || res.data?.records || []
    //     uni.hideLoading()
    //   })
    //   .catch(() => {
    //     uni.hideLoading()
    //     showToast('获取数据失败')
    //   })
  };
  onMounted(() => {
    getList();
  });
  onShow(() => {
    getList();
  });
</script>
<style scoped lang="scss">
@import '@/styles/sales-common.scss';
  @import "@/styles/sales-common.scss";
.inspection-page {
  background: #ffffff;
  min-height: 100vh;
  padding-bottom: 20px;
}
.stats-cards {
  display: flex;
  gap: 10px;
  padding: 0 15px;
  margin: 15px 0;
}
.stat-card {
  flex: 1;
  background: #ffffff;
  border-radius: 12px;
  padding: 15px 12px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  border: 1px solid #f0f0f0;
  transition: all 0.3s ease;
  &:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  .inspection-page {
    background: #ffffff;
    min-height: 100vh;
    padding-bottom: 20px;
  }
  .stat-number {
    font-size: 24px;
    font-weight: 700;
    color: #1890ff;
  .stats-cards {
    display: flex;
    gap: 10px;
    padding: 0 15px;
    margin: 15px 0;
  }
  .stat-label {
  .stat-card {
    flex: 1;
    background: #ffffff;
    border-radius: 12px;
    padding: 15px 12px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    border: 1px solid #f0f0f0;
    transition: all 0.3s ease;
    &:hover {
      transform: translateY(-2px);
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    }
    .stat-number {
      font-size: 24px;
      font-weight: 700;
      color: #1890ff;
    }
    .stat-label {
      font-size: 13px;
      color: #666;
      font-weight: 500;
    }
  }
  .inspection-list {
    padding: 0 15px;
  }
  .inspection-item {
    background: #ffffff;
    border-radius: 12px;
    padding: 15px;
    margin-bottom: 12px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    border: 1px solid #f0f0f0;
    transition: all 0.3s ease;
    &:hover {
      transform: translateY(-2px);
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    }
  }
  .item-header {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    margin-bottom: 12px;
  }
  .item-left {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    flex: 1;
  }
  .device-icon {
    width: 48px;
    height: 48px;
    border-radius: 16px;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
    transition: all 0.3s ease;
    &.status-pending {
      background: linear-gradient(135deg, #faad14, #fa8c16);
      box-shadow: 0 4px 16px rgba(250, 173, 20, 0.3);
    }
    &.status-progress {
      background: linear-gradient(135deg, #1890ff, #096dd9);
      box-shadow: 0 4px 16px rgba(24, 144, 255, 0.3);
    }
    &.status-completed {
      background: linear-gradient(135deg, #52c41a, #389e0d);
      box-shadow: 0 4px 16px rgba(82, 196, 26, 0.3);
    }
    &:hover {
      transform: scale(1.05);
    }
  }
  .device-info {
    display: flex;
    flex-direction: column;
    gap: 6px;
  }
  .device-name {
    font-size: 18px;
    font-weight: 600;
    color: #1a1a1a;
    line-height: 1.3;
  }
  .device-location {
    font-size: 13px;
    color: #666;
    color: #8c8c8c;
    font-weight: 500;
    padding: 2px 8px;
    background: rgba(140, 140, 140, 0.1);
    border-radius: 12px;
    display: inline-block;
    width: fit-content;
  }
  .status-tag {
    flex-shrink: 0;
    transform: scale(1.1);
  }
  .item-details {
    margin: 15px 0;
    background: rgba(248, 250, 252, 0.8);
    border-radius: 12px;
    padding: 12px;
    backdrop-filter: blur(10px);
  }
  .detail-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 10px 0;
    border-bottom: 1px solid rgba(0, 0, 0, 0.06);
    transition: all 0.2s ease;
    &:last-child {
      border-bottom: none;
      padding-bottom: 0;
    }
    &:hover {
      background: rgba(255, 255, 255, 0.5);
      margin: 0 -8px;
      padding-left: 8px;
      padding-right: 8px;
      border-radius: 8px;
    }
  }
  .detail-label {
    font-size: 14px;
    color: #595959;
    min-width: 80px;
    font-weight: 500;
  }
}
.inspection-list {
  padding: 0 15px;
}
.inspection-item {
  background: #ffffff;
  border-radius: 12px;
  padding: 15px;
  margin-bottom: 12px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  border: 1px solid #f0f0f0;
  transition: all 0.3s ease;
  &:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  .detail-value {
    font-size: 14px;
    color: #262626;
    text-align: right;
    flex: 1;
    font-weight: 500;
  }
}
.item-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 12px;
}
.item-left {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  flex: 1;
}
.device-icon {
  width: 48px;
  height: 48px;
  border-radius: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
  transition: all 0.3s ease;
  &.status-pending {
    background: linear-gradient(135deg, #faad14, #fa8c16);
    box-shadow: 0 4px 16px rgba(250, 173, 20, 0.3);
  .action-buttons {
    display: flex;
    gap: 10px;
    margin-top: 15px;
    padding-top: 15px;
    border-top: 1px solid rgba(0, 0, 0, 0.06);
  }
  &.status-progress {
    background: linear-gradient(135deg, #1890ff, #096dd9);
    box-shadow: 0 4px 16px rgba(24, 144, 255, 0.3);
  .action-btn {
    flex: 1;
    height: 44px;
    border-radius: 12px;
    font-weight: 600;
    font-size: 14px;
    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    &:hover {
      transform: translateY(-2px);
      box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
    }
    &:active {
      transform: translateY(0);
    }
  }
  &.status-completed {
    background: linear-gradient(135deg, #52c41a, #389e0d);
    box-shadow: 0 4px 16px rgba(82, 196, 26, 0.3);
  .no-data {
    padding: 80px 20px;
    text-align: center;
  }
  &:hover {
    transform: scale(1.05);
  }
}
.device-info {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.device-name {
  font-size: 18px;
  font-weight: 600;
  color: #1a1a1a;
  line-height: 1.3;
}
.device-location {
  font-size: 13px;
  color: #8c8c8c;
  font-weight: 500;
  padding: 2px 8px;
  background: rgba(140, 140, 140, 0.1);
  border-radius: 12px;
  display: inline-block;
  width: fit-content;
}
.status-tag {
  flex-shrink: 0;
  transform: scale(1.1);
}
.item-details {
  margin: 15px 0;
  background: rgba(248, 250, 252, 0.8);
  border-radius: 12px;
  padding: 12px;
  backdrop-filter: blur(10px);
}
.detail-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 0;
  border-bottom: 1px solid rgba(0, 0, 0, 0.06);
  transition: all 0.2s ease;
  &:last-child {
    border-bottom: none;
    padding-bottom: 0;
  }
  &:hover {
    background: rgba(255, 255, 255, 0.5);
    margin: 0 -8px;
    padding-left: 8px;
    padding-right: 8px;
    border-radius: 8px;
  }
}
.detail-label {
  font-size: 14px;
  color: #595959;
  min-width: 80px;
  font-weight: 500;
}
.detail-value {
  font-size: 14px;
  color: #262626;
  text-align: right;
  flex: 1;
  font-weight: 500;
}
.action-buttons {
  display: flex;
  gap: 10px;
  margin-top: 15px;
  padding-top: 15px;
  border-top: 1px solid rgba(0, 0, 0, 0.06);
}
.action-btn {
  flex: 1;
  height: 44px;
  border-radius: 12px;
  font-weight: 600;
  font-size: 14px;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  &:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
  }
  &:active {
    transform: translateY(0);
  }
}
.no-data {
  padding: 80px 20px;
  text-align: center;
}
</style>
src/pages/equipmentManagement/repair/add.vue
@@ -1,435 +1,489 @@
<template>
    <view class="repair-add">
        <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
        <PageHeader :title="operationType === 'edit' ? '编辑报修' : '新增报修'" @back="goBack" />
        <!-- è¡¨å•内容 -->
        <u-form @submit="sendForm" ref="formRef" :rules="formRules" :model="form" label-width="110">
            <!-- åŸºæœ¬ä¿¡æ¯ -->
            <u-cell-group title="基本信息">
                <u-form-item label="设备名称" prop="deviceLedgerId" required border-bottom>
                    <u-input
                        v-model="deviceNameText"
                        placeholder="请选择设备名称"
                        @click="showDevicePicker"
                        clearable
                        readonly=""
                    />
                    <template #right>
                        <u-icon name="scan" @click="startScan" class="scan-icon" />
                    </template>
                </u-form-item>
                <u-form-item label="规格型号" prop="deviceModel" border-bottom>
                    <u-input
                        v-model="form.deviceModel"
                        placeholder="请输入规格型号"
                        clearable
                    />
                </u-form-item>
                <u-form-item label="报修日期" prop="repairTime" required border-bottom>
                    <u-input
                        v-model="form.repairTime"
                        placeholder="请选择报修日期"
                        readonly
                        @click="showDatePicker"
                        clearable
                    />
                    <template #right>
                        <u-icon name="arrow-right" @click="showDatePicker"></u-icon>
                    </template>
                </u-form-item>
                <u-form-item label="报修人" prop="repairName" required border-bottom>
                    <u-input
                        v-model="form.repairName"
                        placeholder="请输入报修人"
                        clearable
                    />
                </u-form-item>
                <u-form-item label="故障现象" prop="remark" required border-bottom>
                    <u-textarea
                        v-model="form.remark"
                        rows="3"
                        placeholder="请输入故障现象"
                        clearable
                        count
                        maxlength="200"
                    />
                </u-form-item>
            </u-cell-group>
            <!-- æäº¤æŒ‰é’® -->
            <view class="footer-btns">
                <u-button class="cancel-btn" @click="goBack">取消</u-button>
                <u-button class="save-btn" type="primary" @click="sendForm" :loading="loading">保存</u-button>
            </view>
        </u-form>
        <!-- è®¾å¤‡é€‰æ‹©å™¨ -->
        <up-action-sheet
            :show="showDevice"
            :actions="deviceActionList"
            title="选择设备名称"
            @select="onDeviceSelect"
            @close="showDevice = false"
        />
        <!-- æ—¥æœŸé€‰æ‹©å™¨ -->
        <up-datetime-picker
            :show="showDate"
            v-model="pickerDateValue"
            @confirm="onDateConfirm"
            @cancel="showDate = false"
            mode="date"
        />
    </view>
  <view class="repair-add">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader :title="operationType === 'edit' ? '编辑报修' : '新增报修'"
                @back="goBack" />
    <!-- è¡¨å•内容 -->
    <u-form @submit="sendForm"
            ref="formRef"
            :rules="formRules"
            :model="form"
            label-width="110">
      <!-- åŸºæœ¬ä¿¡æ¯ -->
      <u-cell-group title="基本信息">
        <u-form-item label="设备名称"
                     prop="deviceLedgerId"
                     required
                     border-bottom>
          <u-input v-model="deviceNameText"
                   placeholder="请选择设备名称"
                   @click="showDevicePicker"
                   clearable
                   readonly="" />
          <template #right>
            <u-icon name="scan"
                    @click="startScan"
                    class="scan-icon" />
          </template>
        </u-form-item>
        <u-form-item label="规格型号"
                     prop="deviceModel"
                     border-bottom>
          <u-input v-model="form.deviceModel"
                   placeholder="请输入规格型号"
                   clearable />
        </u-form-item>
        <u-form-item label="报修日期"
                     prop="repairTime"
                     required
                     border-bottom>
          <u-input v-model="form.repairTime"
                   placeholder="请选择报修日期"
                   readonly
                   @click="showDatePicker"
                   clearable />
          <template #right>
            <u-icon name="arrow-right"
                    @click="showDatePicker"></u-icon>
          </template>
        </u-form-item>
        <u-form-item label="报修状态"
                     prop="repairTime"
                     required
                     border-bottom>
          <u-input v-model="repairStatusText"
                   placeholder="请选择报修状态"
                   readonly
                   @click="openRepairStatusPicker"
                   clearable />
          <template #right>
            <u-icon name="arrow-right"
                    @click="openRepairStatusPicker"></u-icon>
          </template>
        </u-form-item>
        <u-form-item label="报修人"
                     prop="repairName"
                     required
                     border-bottom>
          <u-input v-model="form.repairName"
                   placeholder="请输入报修人"
                   clearable />
        </u-form-item>
        <u-form-item label="故障现象"
                     prop="remark"
                     required
                     border-bottom>
          <u-textarea v-model="form.remark"
                      rows="3"
                      placeholder="请输入故障现象"
                      clearable
                      count
                      maxlength="200" />
        </u-form-item>
      </u-cell-group>
      <!-- æäº¤æŒ‰é’® -->
      <view class="footer-btns">
        <u-button class="cancel-btn"
                  @click="goBack">取消</u-button>
        <u-button class="save-btn"
                  type="primary"
                  @click="sendForm"
                  :loading="loading">保存</u-button>
      </view>
    </u-form>
    <!-- è®¾å¤‡é€‰æ‹©å™¨ -->
    <up-action-sheet :show="showDevice"
                     :actions="deviceActionList"
                     title="选择设备名称"
                     @select="onDeviceSelect"
                     @close="showDevice = false" />
    <!-- æ—¥æœŸé€‰æ‹©å™¨ -->
    <up-datetime-picker :show="showDate"
                        v-model="pickerDateValue"
                        @confirm="onDateConfirm"
                        @cancel="showDate = false"
                        mode="date" />
  </view>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue';
import { onShow } from '@dcloudio/uni-app';
import PageHeader from '@/components/PageHeader.vue';
import { getDeviceLedger } from '@/api/equipmentManagement/ledger';
import { addRepair, editRepair, getRepairById } from '@/api/equipmentManagement/repair';
import dayjs from "dayjs";
import { formatDateToYMD } from '@/utils/ruoyi';
const showToast = (message) => {
    uni.showToast({
        title: message,
        icon: 'none'
    })
}
  import { ref, computed, onMounted, onUnmounted } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
  import {
    addRepair,
    editRepair,
    getRepairById,
  } from "@/api/equipmentManagement/repair";
  import dayjs from "dayjs";
  import { formatDateToYMD } from "@/utils/ruoyi";
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
defineOptions({
    name: "设备报修表单",
});
  defineOptions({
    name: "设备报修表单",
  });
// è¡¨å•引用
const formRef = ref(null);
const operationType = ref('add');
const loading = ref(false);
const showDevice = ref(false);
const showDate = ref(false);
const pickerDateValue = ref(Date.now());
  // è¡¨å•引用
  const formRef = ref(null);
  const operationType = ref("add");
  const loading = ref(false);
  const showDevice = ref(false);
  const showDate = ref(false);
  const pickerDateValue = ref(Date.now());
// è®¾å¤‡é€‰é¡¹
const deviceOptions = ref([]);
const deviceNameText = ref('');
const deviceActionList = computed(() => {
    return deviceOptions.value.map(item => ({
        name: item.deviceName,
        value: item.id
    }));
});
  // è®¾å¤‡é€‰é¡¹
  const deviceOptions = ref([]);
  const deviceNameText = ref("");
  const deviceActionList = computed(() => {
    return deviceOptions.value.map(item => ({
      name: item.deviceName,
      value: item.id,
    }));
  });
// æ‰«ç ç›¸å…³çŠ¶æ€
const isScanning = ref(false);
const scanTimer = ref(null);
  // æ‰«ç ç›¸å…³çŠ¶æ€
  const isScanning = ref(false);
  const scanTimer = ref(null);
// è¡¨å•验证规则
const formRules = {
    deviceLedgerId: [{ required: true, trigger: "change", message: "请选择设备名称" }],
    repairTime: [{ required: true, trigger: "change", message: "请选择报修日期" }],
    repairName: [{ required: true, trigger: "blur", message: "请输入报修人" }],
    remark: [{ required: true, trigger: "blur", message: "请输入故障现象" }],
};
  // è¡¨å•验证规则
  const formRules = {
    deviceLedgerId: [
      { required: true, trigger: "change", message: "请选择设备名称" },
    ],
    repairTime: [
      { required: true, trigger: "change", message: "请选择报修日期" },
    ],
    repairName: [{ required: true, trigger: "blur", message: "请输入报修人" }],
    remark: [{ required: true, trigger: "blur", message: "请输入故障现象" }],
  };
// ä½¿ç”¨ ref å£°æ˜Žè¡¨å•数据
const form = ref({
    deviceLedgerId: undefined, // è®¾å¤‡ID
    deviceModel: undefined, // è§„格型号
    repairTime: dayjs().format("YYYY-MM-DD"), // æŠ¥ä¿®æ—¥æœŸ
    repairName: undefined, // æŠ¥ä¿®äºº
    remark: undefined, // æ•…障现象
});
  // ä½¿ç”¨ ref å£°æ˜Žè¡¨å•数据
  const form = ref({
    deviceLedgerId: undefined, // è®¾å¤‡ID
    deviceModel: undefined, // è§„格型号
    repairTime: dayjs().format("YYYY-MM-DD"), // æŠ¥ä¿®æ—¥æœŸ
    repairName: undefined, // æŠ¥ä¿®äºº
    remark: undefined, // æ•…障现象
  });
// åŠ è½½è®¾å¤‡åˆ—è¡¨
const loadDeviceName = async () => {
    try {
        const { data } = await getDeviceLedger();
        deviceOptions.value = data || [];
    } catch (e) {
        showToast('获取设备列表失败');
    }
};
  // æŠ¥ä¿®çŠ¶æ€é€‰é¡¹
  const repairStatusOptions = ref([
    { name: "待维修", value: "0" },
    { name: "完结", value: "1" },
    { name: "失败", value: "2" },
  ]);
  const repairStatusText = ref("");
// è®¾ç½®è®¾å¤‡è§„格型号
const setDeviceModel = (id) => {
    const option = deviceOptions.value.find((item) => item.id === id);
    if (option) {
        form.value.deviceModel = option.deviceModel;
        deviceNameText.value = option.deviceName;
    }
};
  // æ‰“开报修状态选择器
  const openRepairStatusPicker = () => {
    uni.showActionSheet({
      itemList: repairStatusOptions.value.map(item => item.name),
      success: res => {
        form.value.status = repairStatusOptions.value[res.tapIndex].value;
        repairStatusText.value = repairStatusOptions.value[res.tapIndex].name;
      },
    });
  };
// åŠ è½½è¡¨å•æ•°æ®ï¼ˆç¼–è¾‘æ¨¡å¼ï¼‰
const loadForm = async (id) => {
    if (id) {
        operationType.value = 'edit';
        try {
            const { code, data } = await getRepairById(id);
            if (code == 200) {
                form.value.deviceLedgerId = data.deviceLedgerId;
                form.value.deviceModel = data.deviceModel;
                form.value.repairTime = dayjs(data.repairTime).format("YYYY-MM-DD");
                form.value.repairName = data.repairName;
                form.value.remark = data.remark;
                // è®¾ç½®è®¾å¤‡åç§°æ˜¾ç¤º
                const device = deviceOptions.value.find(item => item.id === data.deviceLedgerId);
                if (device) {
                    deviceNameText.value = device.deviceName;
                }
            }
        } catch (e) {
            showToast('获取详情失败');
        }
    } else {
        // æ–°å¢žæ¨¡å¼
        operationType.value = 'add';
    }
};
  // åŠ è½½è®¾å¤‡åˆ—è¡¨
  const loadDeviceName = async () => {
    try {
      const { data } = await getDeviceLedger();
      deviceOptions.value = data || [];
    } catch (e) {
      showToast("获取设备列表失败");
    }
  };
// æ‰«æäºŒç»´ç åŠŸèƒ½
const startScan = () => {
    if (isScanning.value) {
        showToast('正在扫描中,请稍候...');
        return;
    }
    // è°ƒç”¨uni-app的扫码API
    uni.scanCode({
        scanType: ['qrCode', 'barCode'],
        success: (res) => {
            handleScanResult(res.result);
        },
        fail: (err) => {
            console.error('扫码失败:', err);
            showToast('扫码失败,请重试');
        }
    });
};
  // è®¾ç½®è®¾å¤‡è§„格型号
  const setDeviceModel = id => {
    const option = deviceOptions.value.find(item => item.id === id);
    if (option) {
      form.value.deviceModel = option.deviceModel;
      deviceNameText.value = option.deviceName;
    }
  };
// å¤„理扫码结果
const handleScanResult = (scanResult) => {
    if (!scanResult) {
        showToast('扫码结果为空');
        return;
    }
    isScanning.value = true;
    showToast('扫码成功');
    // 3秒后处理扫码结果
    scanTimer.value = setTimeout(() => {
        processScanResult(scanResult);
        isScanning.value = false;
    }, 100);
};
function getDeviceIdByRegExp(url) {
    // åŒ¹é…deviceId=后面的数字
    const reg = /deviceId=(\d+)/;
    const match = url.match(reg);
    // å¦‚果匹配到结果,返回数字类型,否则返回null
    return match ? Number(match[1]) : null;
}
  // åŠ è½½è¡¨å•æ•°æ®ï¼ˆç¼–è¾‘æ¨¡å¼ï¼‰
  const loadForm = async id => {
    if (id) {
      operationType.value = "edit";
      try {
        const { code, data } = await getRepairById(id);
        if (code == 200) {
          form.value.deviceLedgerId = data.deviceLedgerId;
          form.value.deviceModel = data.deviceModel;
          form.value.repairTime = dayjs(data.repairTime).format("YYYY-MM-DD");
          form.value.repairName = data.repairName;
          form.value.remark = data.remark;
          repairStatusText.value =
            repairStatusOptions.value.find(item => item.value == data.status)
              ?.name || "";
          // è®¾ç½®è®¾å¤‡åç§°æ˜¾ç¤º
          const device = deviceOptions.value.find(
            item => item.id === data.deviceLedgerId
          );
          if (device) {
            deviceNameText.value = device.deviceName;
          }
        }
      } catch (e) {
        showToast("获取详情失败");
      }
    } else {
      // æ–°å¢žæ¨¡å¼
      operationType.value = "add";
    }
  };
// å¤„理扫码结果并匹配设备
const processScanResult = (scanResult) => {
    const deviceId = getDeviceIdByRegExp(scanResult);
    const matchedDevice = deviceOptions.value.find(item => item.id == deviceId);
    if (matchedDevice) {
        // æ‰¾åˆ°åŒ¹é…çš„设备,自动填充
        form.value.deviceLedgerId = matchedDevice.id;
        deviceNameText.value = matchedDevice.deviceName;
        form.value.deviceModel = matchedDevice.deviceModel;
        showToast('设备信息已自动填充');
    } else {
        // æœªæ‰¾åˆ°åŒ¹é…çš„设备
        showToast('未找到匹配的设备,请手动选择');
    }
};
  // æ‰«æäºŒç»´ç åŠŸèƒ½
  const startScan = () => {
    if (isScanning.value) {
      showToast("正在扫描中,请稍候...");
      return;
    }
// æ˜¾ç¤ºè®¾å¤‡é€‰æ‹©å™¨
const showDevicePicker = () => {
    showDevice.value = true;
};
    // è°ƒç”¨uni-app的扫码API
    uni.scanCode({
      scanType: ["qrCode", "barCode"],
      success: res => {
        handleScanResult(res.result);
      },
      fail: err => {
        console.error("扫码失败:", err);
        showToast("扫码失败,请重试");
      },
    });
  };
// ç¡®è®¤è®¾å¤‡é€‰æ‹©
const onDeviceSelect = (e) => {
    form.value.deviceLedgerId = e.value;
    setDeviceModel(e.value);
    showDevice.value = false;
};
  // å¤„理扫码结果
  const handleScanResult = scanResult => {
    if (!scanResult) {
      showToast("扫码结果为空");
      return;
    }
// æ˜¾ç¤ºæ—¥æœŸé€‰æ‹©å™¨
const showDatePicker = () => {
    showDate.value = true;
};
    isScanning.value = true;
    showToast("扫码成功");
// ç¡®è®¤æ—¥æœŸé€‰æ‹©
const onDateConfirm = (e) => {
    form.value.repairTime = formatDateToYMD(e.value);
    pickerDateValue.value = dayjs(e.value).format("YYYY-MM-DD");
    showDate.value = false;
};
    // 3秒后处理扫码结果
    scanTimer.value = setTimeout(() => {
      processScanResult(scanResult);
      isScanning.value = false;
    }, 100);
  };
  function getDeviceIdByRegExp(url) {
    // åŒ¹é…deviceId=后面的数字
    const reg = /deviceId=(\d+)/;
    const match = url.match(reg);
    // å¦‚果匹配到结果,返回数字类型,否则返回null
    return match ? Number(match[1]) : null;
  }
onShow(() => {
    // é¡µé¢æ˜¾ç¤ºæ—¶èŽ·å–å‚æ•°
    getPageParams();
});
  // å¤„理扫码结果并匹配设备
  const processScanResult = scanResult => {
    const deviceId = getDeviceIdByRegExp(scanResult);
    const matchedDevice = deviceOptions.value.find(item => item.id == deviceId);
onMounted(() => {
    // é¡µé¢åŠ è½½æ—¶èŽ·å–è®¾å¤‡åˆ—è¡¨å’Œå‚æ•°
    loadDeviceName();
    getPageParams();
});
    if (matchedDevice) {
      // æ‰¾åˆ°åŒ¹é…çš„设备,自动填充
      form.value.deviceLedgerId = matchedDevice.id;
      deviceNameText.value = matchedDevice.deviceName;
      form.value.deviceModel = matchedDevice.deviceModel;
      showToast("设备信息已自动填充");
    } else {
      // æœªæ‰¾åˆ°åŒ¹é…çš„设备
      showToast("未找到匹配的设备,请手动选择");
    }
  };
// ç»„件卸载时清理定时器
onUnmounted(() => {
    if (scanTimer.value) {
        clearTimeout(scanTimer.value);
    }
});
  // æ˜¾ç¤ºè®¾å¤‡é€‰æ‹©å™¨
  const showDevicePicker = () => {
    showDevice.value = true;
  };
// æäº¤è¡¨å•
const sendForm = async () => {
    try {
        // æ‰‹åŠ¨éªŒè¯è¡¨å•
        let isValid = true;
        let errorMessage = '';
        if (!form.value.deviceLedgerId) {
            isValid = false;
            errorMessage = '请选择设备名称';
        } else if (!form.value.repairTime || form.value.repairTime.trim() === '') {
            isValid = false;
            errorMessage = '请选择报修日期';
        } else if (!form.value.repairName || form.value.repairName.trim() === '') {
            isValid = false;
            errorMessage = '请输入报修人';
        } else if (!form.value.remark || form.value.remark.trim() === '') {
            isValid = false;
            errorMessage = '请输入故障现象';
        }
  // ç¡®è®¤è®¾å¤‡é€‰æ‹©
  const onDeviceSelect = e => {
    form.value.deviceLedgerId = e.value;
    setDeviceModel(e.value);
    showDevice.value = false;
  };
        if (!isValid) {
            showToast(errorMessage);
            return;
        }
  // æ˜¾ç¤ºæ—¥æœŸé€‰æ‹©å™¨
  const showDatePicker = () => {
    showDate.value = true;
  };
        loading.value = true;
        const id = getPageId();
  // ç¡®è®¤æ—¥æœŸé€‰æ‹©
  const onDateConfirm = e => {
    form.value.repairTime = formatDateToYMD(e.value);
    pickerDateValue.value = dayjs(e.value).format("YYYY-MM-DD");
    showDate.value = false;
  };
        // å‡†å¤‡æäº¤æ•°æ®
        const submitData = { ...form.value };
  onShow(() => {
    // é¡µé¢æ˜¾ç¤ºæ—¶èŽ·å–å‚æ•°
    getPageParams();
  });
        const { code } = id
            ? await editRepair({ id: id, ...submitData })
            : await addRepair(submitData);
  onMounted(() => {
    // é¡µé¢åŠ è½½æ—¶èŽ·å–è®¾å¤‡åˆ—è¡¨å’Œå‚æ•°
    loadDeviceName();
    getPageParams();
  });
        if (code == 200) {
            showToast(`${id ? "编辑" : "新增"}报修成功`);
            setTimeout(() => {
                uni.navigateBack();
            }, 1500);
        } else {
            loading.value = false;
        }
    } catch (e) {
        loading.value = false;
        showToast('表单验证失败');
    }
};
  // ç»„件卸载时清理定时器
  onUnmounted(() => {
    if (scanTimer.value) {
      clearTimeout(scanTimer.value);
    }
  });
// è¿”回上一页
const goBack = () => {
    uni.removeStorageSync('repairId');
    uni.navigateBack();
};
  // æäº¤è¡¨å•
  const sendForm = async () => {
    try {
      // æ‰‹åŠ¨éªŒè¯è¡¨å•
      let isValid = true;
      let errorMessage = "";
      if (!form.value.deviceLedgerId) {
        isValid = false;
        errorMessage = "请选择设备名称";
      } else if (!form.value.repairTime || form.value.repairTime.trim() === "") {
        isValid = false;
        errorMessage = "请选择报修日期";
      } else if (!form.value.repairName || form.value.repairName.trim() === "") {
        isValid = false;
        errorMessage = "请输入报修人";
      } else if (!form.value.remark || form.value.remark.trim() === "") {
        isValid = false;
        errorMessage = "请输入故障现象";
      }
// èŽ·å–é¡µé¢å‚æ•°
const getPageParams = () => {
    // ä½¿ç”¨uni.getStorageSync获取id
    const id = uni.getStorageSync('repairId');
    // æ ¹æ®æ˜¯å¦æœ‰id参数来判断是新增还是编辑
    if (id) {
        // ç¼–辑模式,获取详情
        loadForm(id);
        // å¯é€‰ï¼šèŽ·å–åŽæ¸…é™¤å­˜å‚¨çš„id,避免影响后续操作
        uni.removeStorageSync('repairId');
    } else {
        // æ–°å¢žæ¨¡å¼
        loadForm();
    }
};
      if (!isValid) {
        showToast(errorMessage);
        return;
      }
// èŽ·å–é¡µé¢ID
const getPageId = () => {
    // ä½¿ç”¨uni.getStorageSync获取id
    const id = uni.getStorageSync('repairId');
    return id;
};
      loading.value = true;
      const id = getPageId();
      // å‡†å¤‡æäº¤æ•°æ®
      const submitData = { ...form.value };
      const { code } = id
        ? await editRepair({ id: id, ...submitData })
        : await addRepair(submitData);
      if (code == 200) {
        showToast(`${id ? "编辑" : "新增"}报修成功`);
        setTimeout(() => {
          uni.navigateBack();
        }, 1500);
      } else {
        loading.value = false;
      }
    } catch (e) {
      loading.value = false;
      showToast("表单验证失败");
    }
  };
  // è¿”回上一页
  const goBack = () => {
    uni.removeStorageSync("repairId");
    uni.navigateBack();
  };
  // èŽ·å–é¡µé¢å‚æ•°
  const getPageParams = () => {
    // ä½¿ç”¨uni.getStorageSync获取id
    const id = uni.getStorageSync("repairId");
    // æ ¹æ®æ˜¯å¦æœ‰id参数来判断是新增还是编辑
    if (id) {
      // ç¼–辑模式,获取详情
      loadForm(id);
      // å¯é€‰ï¼šèŽ·å–åŽæ¸…é™¤å­˜å‚¨çš„id,避免影响后续操作
      uni.removeStorageSync("repairId");
    } else {
      // æ–°å¢žæ¨¡å¼
      loadForm();
    }
  };
  // èŽ·å–é¡µé¢ID
  const getPageId = () => {
    // ä½¿ç”¨uni.getStorageSync获取id
    const id = uni.getStorageSync("repairId");
    return id;
  };
</script>
<style scoped lang="scss">
@import '@/static/scss/form-common.scss';
.repair-add {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 5rem;
}
  @import "@/static/scss/form-common.scss";
  .repair-add {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 5rem;
  }
.footer-btns {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    background: #fff;
    display: flex;
    justify-content: space-around;
    align-items: center;
    padding: 0.75rem 0;
    box-shadow: 0 -0.125rem 0.5rem rgba(0,0,0,0.05);
    z-index: 1000;
}
  .footer-btns {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    background: #fff;
    display: flex;
    justify-content: space-around;
    align-items: center;
    padding: 0.75rem 0;
    box-shadow: 0 -0.125rem 0.5rem rgba(0, 0, 0, 0.05);
    z-index: 1000;
  }
.cancel-btn {
    font-weight: 400;
    font-size: 1rem;
    color: #FFFFFF;
    width: 6.375rem;
    background: #C7C9CC;
    box-shadow: 0 0.25rem 0.625rem 0 rgba(3,88,185,0.2);
    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
}
  .cancel-btn {
    font-weight: 400;
    font-size: 1rem;
    color: #ffffff;
    width: 6.375rem;
    background: #c7c9cc;
    box-shadow: 0 0.25rem 0.625rem 0 rgba(3, 88, 185, 0.2);
    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
  }
.save-btn {
    font-weight: 400;
    font-size: 1rem;
    color: #FFFFFF;
    width: 14rem;
    background: linear-gradient( 140deg, #00BAFF 0%, #006CFB 100%);
    box-shadow: 0 0.25rem 0.625rem 0 rgba(3,88,185,0.2);
    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
}
  .save-btn {
    font-weight: 400;
    font-size: 1rem;
    color: #ffffff;
    width: 14rem;
    background: linear-gradient(140deg, #00baff 0%, #006cfb 100%);
    box-shadow: 0 0.25rem 0.625rem 0 rgba(3, 88, 185, 0.2);
    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
  }
// å“åº”式调整
@media (max-width: 768px) {
    .submit-section {
        padding: 12px;
    }
}
  // å“åº”式调整
  @media (max-width: 768px) {
    .submit-section {
      padding: 12px;
    }
  }
.tip-text {
    padding: 4px 16px 0 16px;
    font-size: 12px;
    color: #888;
}
  .tip-text {
    padding: 4px 16px 0 16px;
    font-size: 12px;
    color: #888;
  }
.scan-icon {
    color: #1989fa;
    font-size: 18px;
    margin-left: 8px;
    cursor: pointer;
}
  .scan-icon {
    color: #1989fa;
    font-size: 18px;
    margin-left: 8px;
    cursor: pointer;
  }
</style>
src/pages/equipmentManagement/repair/index.vue
@@ -1,44 +1,49 @@
<template>
  <view class="sales-account">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="设备报修" @back="goBack" />
    <PageHeader title="设备报修"
                @back="goBack" />
    <!-- æœç´¢åŒºåŸŸ -->
    <view class="search-section">
      <view class="search-bar">
        <view class="search-input">
          <up-input
            class="search-text"
            placeholder="请输入设备名称搜索"
            v-model="searchKeyword"
            @change="getList"
            clearable
          />
          <up-input class="search-text"
                    placeholder="请输入设备名称搜索"
                    v-model="searchKeyword"
                    @change="getList"
                    clearable />
        </view>
        <view class="filter-button" @click="getList">
          <up-icon name="search" size="24" color="#999"></up-icon>
        <view class="filter-button"
              @click="getList">
          <up-icon name="search"
                   size="24"
                   color="#999"></up-icon>
        </view>
      </view>
    </view>
    <!-- è®¾å¤‡æŠ¥ä¿®åˆ—表 -->
    <view class="ledger-list" v-if="repairList.length > 0">
      <view v-for="(item, index) in repairList" :key="index">
    <view class="ledger-list"
          v-if="repairList.length > 0">
      <view v-for="(item, index) in repairList"
            :key="index">
        <view class="ledger-item">
          <view class="item-header">
            <view class="item-left">
              <view class="document-icon">
                <up-icon name="file-text" size="16" color="#ffffff"></up-icon>
                <up-icon name="file-text"
                         size="16"
                         color="#ffffff"></up-icon>
              </view>
              <text class="item-id">设备名称:{{ item.deviceName }}</text>
            </view>
            <view class="status-tag">
              <u-tag v-if="item.status === 1" type="success">完结</u-tag>
              <u-tag v-if="item.status === 0" type="error">待维修</u-tag>
              <u-tag v-if="item.status === 1"
                     type="success">完结</u-tag>
              <u-tag v-if="item.status === 0"
                     type="error">待维修</u-tag>
            </view>
          </view>
          <up-divider></up-divider>
          <view class="item-details">
            <view class="detail-row">
              <text class="detail-label">规格型号</text>
@@ -69,197 +74,193 @@
              <text class="detail-value">{{ formatDate(item.maintenanceTime) || '-' }}</text>
            </view>
          </view>
          <!-- æŒ‰é’®åŒºåŸŸ -->
          <view class="action-buttons">
            <u-button
              type="primary"
              size="small"
              class="action-btn"
              :disabled="item.status === 1"
              @click="edit(item.id)"
            >
            <u-button type="primary"
                      size="small"
                      class="action-btn"
                      :disabled="item.status === 1"
                      @click="edit(item.id)">
              ç¼–辑
            </u-button>
            <u-button
              type="warning"
              size="small"
              class="action-btn"
              :disabled="item.status === 1"
              @click="addMaintain(item.id)"
            >
            <u-button type="warning"
                      size="small"
                      class="action-btn"
                      :disabled="item.status === 1"
                      @click="addMaintain(item.id)">
              æ–°å¢žç»´ä¿®
            </u-button>
            <u-button
              type="error"
              size="small"
              plain
              class="action-btn"
              @click="delRepairByIds(item.id)"
            >
            <u-button type="error"
                      size="small"
                      plain
                      class="action-btn"
                      @click="delRepairByIds(item.id)">
              åˆ é™¤
            </u-button>
          </view>
        </view>
      </view>
    </view>
    <view v-else class="no-data">
    <view v-else
          class="no-data">
      <text>暂无设备报修数据</text>
    </view>
    <!-- æµ®åŠ¨æ“ä½œæŒ‰é’® -->
        <view class="fab-button" @click="addRepair">
            <up-icon name="plus" size="24" color="#ffffff"></up-icon>
        </view>
    <view class="fab-button"
          @click="addRepair">
      <up-icon name="plus"
               size="24"
               color="#ffffff"></up-icon>
    </view>
  </view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import PageHeader from '@/components/PageHeader.vue'
import { getRepairPage, delRepair } from '@/api/equipmentManagement/repair'
import useUserStore from "@/store/modules/user"
  import { ref, onMounted } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import { getRepairPage, delRepair } from "@/api/equipmentManagement/repair";
  import useUserStore from "@/store/modules/user";
const showToast = (message) => {
  uni.showToast({
    title: message,
    icon: 'none'
  })
}
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
const userStore = useUserStore()
  const userStore = useUserStore();
// æœç´¢å…³é”®è¯
const searchKeyword = ref('')
  // æœç´¢å…³é”®è¯
  const searchKeyword = ref("");
// è®¾å¤‡æŠ¥ä¿®æ•°æ®
const repairList = ref([])
  // è®¾å¤‡æŠ¥ä¿®æ•°æ®
  const repairList = ref([]);
// è¿”回上一页
const goBack = () => {
  uni.navigateBack()
}
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
// æ ¼å¼åŒ–日期
const formatDate = (dateStr) => {
  if (!dateStr) return ''
  const date = new Date(dateStr)
  const year = date.getFullYear()
  const month = String(date.getMonth() + 1).padStart(2, '0')
  const day = String(date.getDate()).padStart(2, '0')
  return `${year}-${month}-${day}`
}
  // æ ¼å¼åŒ–日期
  const formatDate = dateStr => {
    if (!dateStr) return "";
    const date = new Date(dateStr);
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, "0");
    const day = String(date.getDate()).padStart(2, "0");
    return `${year}-${month}-${day}`;
  };
// æŸ¥è¯¢åˆ—表
const getList = () => {
  showLoadingToast('加载中...')
  const params = {
    current: -1,
    size: -1,
    deviceName: searchKeyword.value || undefined
  }
  getRepairPage(params)
    .then((res) => {
      repairList.value = res.records || res.data?.records || []
      closeToast()
    })
    .catch(() => {
      closeToast()
      showToast('获取数据失败')
    })
}
  // æŸ¥è¯¢åˆ—表
  const getList = () => {
    showLoadingToast("加载中...");
    const params = {
      current: -1,
      size: -1,
      deviceName: searchKeyword.value || undefined,
    };
    getRepairPage(params)
      .then(res => {
        repairList.value = res.records || res.data?.records || [];
        closeToast();
      })
      .catch(() => {
        closeToast();
        showToast("获取数据失败");
      });
  };
// æ˜¾ç¤ºåŠ è½½æç¤º
const showLoadingToast = (message) => {
  uni.showLoading({
    title: message,
    mask: true
  });
};
  // æ˜¾ç¤ºåŠ è½½æç¤º
  const showLoadingToast = message => {
    uni.showLoading({
      title: message,
      mask: true,
    });
  };
// å…³é—­æç¤º
const closeToast = () => {
  uni.hideLoading();
};
  // å…³é—­æç¤º
  const closeToast = () => {
    uni.hideLoading();
  };
// æ–°å¢žç»´ä¿® - è·³è½¬åˆ°ç»´ä¿®é¡µé¢
const addMaintain = (id) => {
  if (!id) {
    showToast('参数错误')
    return
  }
  // ä½¿ç”¨uni.setStorageSync存储id
  uni.setStorageSync('repairId', id)
  uni.navigateTo({
    url: '/pages/equipmentManagement/repair/maintain'
  })
}
// æ–°å¢žæŠ¥ä¿® - è·³è½¬åˆ°æŠ¥ä¿®é¡µé¢
const addRepair = () => {
  uni.navigateTo({
    url: '/pages/equipmentManagement/repair/add'
  })
}
// ç¼–辑 - è·³è½¬åˆ°add页面,通过id区分新增还是编辑
const edit = (id) => {
  if (!id) return
  // ä½¿ç”¨uni.setStorageSync存储id
  uni.setStorageSync('repairId', id)
  uni.navigateTo({
    url: '/pages/equipmentManagement/repair/add'
  })
}
// åˆ é™¤æŠ¥ä¿®æ•°æ®
const delRepairByIds = async (ids) => {
  uni.showModal({
    title: '警告',
    content: '确认删除报修数据, æ­¤æ“ä½œä¸å¯é€†?',
    confirmText: '确定',
    cancelText: '取消',
    success: async (res) => {
      if (!res.confirm) return
      try {
        const response = await delRepair(ids)
        if (response.code === 200) {
          showToast('删除成功')
          getList()
        } else {
          showToast('删除失败')
        }
      } catch (e) {
        showToast('删除失败')
      }
  // æ–°å¢žç»´ä¿® - è·³è½¬åˆ°ç»´ä¿®é¡µé¢
  const addMaintain = id => {
    if (!id) {
      showToast("参数错误");
      return;
    }
  })
}
    // ä½¿ç”¨uni.setStorageSync存储id
    uni.setStorageSync("repairId", id);
    uni.navigateTo({
      url: "/pages/equipmentManagement/repair/maintain",
    });
  };
onMounted(() => {
  getList()
})
  // æ–°å¢žæŠ¥ä¿® - è·³è½¬åˆ°æŠ¥ä¿®é¡µé¢
  const addRepair = () => {
    uni.navigateTo({
      url: "/pages/equipmentManagement/repair/add",
    });
  };
onShow(() => {
  getList()
})
  // ç¼–辑 - è·³è½¬åˆ°add页面,通过id区分新增还是编辑
  const edit = id => {
    if (!id) return;
    // ä½¿ç”¨uni.setStorageSync存储id
    uni.setStorageSync("repairId", id);
    uni.navigateTo({
      url: "/pages/equipmentManagement/repair/add",
    });
  };
  // åˆ é™¤æŠ¥ä¿®æ•°æ®
  const delRepairByIds = async ids => {
    uni.showModal({
      title: "警告",
      content: "确认删除报修数据, æ­¤æ“ä½œä¸å¯é€†?",
      confirmText: "确定",
      cancelText: "取消",
      success: async res => {
        if (!res.confirm) return;
        try {
          const response = await delRepair(ids);
          if (response.code === 200) {
            showToast("删除成功");
            getList();
          } else {
            showToast("删除失败");
          }
        } catch (e) {
          showToast("删除失败");
        }
      },
    });
  };
  onMounted(() => {
    getList();
  });
  onShow(() => {
    getList();
  });
</script>
<style scoped lang="scss">
@import '@/styles/sales-common.scss';
  @import "@/styles/sales-common.scss";
// è®¾å¤‡ç»´ä¿®ç‰¹æœ‰æ ·å¼
.sales-account {
  padding-bottom: 80px; // ä¸ºæµ®åŠ¨æŒ‰é’®ç•™å‡ºç©ºé—´
}
  // è®¾å¤‡ç»´ä¿®ç‰¹æœ‰æ ·å¼
  .sales-account {
    padding-bottom: 80px; // ä¸ºæµ®åŠ¨æŒ‰é’®ç•™å‡ºç©ºé—´
  }
.status-tag {
  display: flex;
  align-items: center;
}
  .status-tag {
    display: flex;
    align-items: center;
  }
.action-buttons {
  gap: 8px; // ä¸Žå…¬å…±æ ·å¼ä¸­çš„ 12px ä¸åŒ
}
  .action-buttons {
    gap: 8px; // ä¸Žå…¬å…±æ ·å¼ä¸­çš„ 12px ä¸åŒ
  }
</style>
src/pages/equipmentManagement/repair/maintain.vue
@@ -1,256 +1,297 @@
<template>
    <view class="repair-maintain">
        <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
        <PageHeader title="新增维修" @back="goBack" />
        <!-- è¡¨å•内容 -->
        <u-form ref="formRef" :model="form" :rules="formRules" label-width="140rpx">
            <!-- åŸºæœ¬ä¿¡æ¯ -->
            <u-cell-group title="维修信息" inset>
                <u-form-item prop="maintenanceName" label="维修人" required>
                    <u-input
                        v-model="form.maintenanceName"
                        placeholder="请输入维修人"
                        clearable
                    />
                </u-form-item>
                <u-form-item prop="maintenanceResult" label="维修结果" required>
                    <u-input
                        v-model="form.maintenanceResult"
                        type="textarea"
                        rows="3"
                        placeholder="请输入维修结果"
                        clearable
                        maxlength="200"
                        show-word-limit
                    />
                </u-form-item>
                <u-form-item label="维修日期" prop="maintenanceTime" required border-bottom>
                    <u-input
                        v-model="form.maintenanceTime"
                        placeholder="请选择维修日期"
                        readonly
                        @click="showDatePicker = true"
                        clearable
                    />
                    <template #right>
                        <u-icon name="arrow-right" @click="showDatePicker = true"></u-icon>
                    </template>
                </u-form-item>
            </u-cell-group>
            <!-- æäº¤æŒ‰é’® -->
            <view class="footer-btns">
                <u-button class="cancel-btn" @click="goBack">取消</u-button>
                <u-button class="save-btn" @click="submitForm" :loading="loading">保存</u-button>
            </view>
        </u-form>
        <!-- æ—¥æœŸé€‰æ‹©å™¨ -->
        <up-datetime-picker
            :show="showDatePicker"
            v-model="pickerDateValue"
            mode="datetime"
            title="选择日期"
            format="YYYY-MM-DD HH:mm:ss"
            @confirm="onDateConfirm"
            @cancel="showDatePicker = false"
        />
    </view>
  <view class="repair-maintain">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="新增维修"
                @back="goBack" />
    <!-- è¡¨å•内容 -->
    <u-form ref="formRef"
            :model="form"
            :rules="formRules"
            label-width="140rpx">
      <!-- åŸºæœ¬ä¿¡æ¯ -->
      <u-cell-group title="维修信息"
                    inset>
        <u-form-item prop="maintenanceName"
                     label="维修人"
                     required>
          <u-input v-model="form.maintenanceName"
                   placeholder="请输入维修人"
                   clearable />
        </u-form-item>
        <u-form-item prop="maintenanceResult"
                     label="维修结果"
                     required>
          <u-input v-model="form.maintenanceResult"
                   type="textarea"
                   rows="3"
                   placeholder="请输入维修结果"
                   clearable
                   maxlength="200"
                   show-word-limit />
        </u-form-item>
        <u-form-item label="维修状态"
                     prop="repairTime"
                     required
                     border-bottom>
          <u-input v-model="repairStatusText"
                   placeholder="请选择维修状态"
                   readonly
                   @click="openRepairStatusPicker"
                   clearable />
          <template #right>
            <u-icon name="arrow-right"
                    @click="openRepairStatusPicker"></u-icon>
          </template>
        </u-form-item>
        <u-form-item label="维修日期"
                     prop="maintenanceTime"
                     required
                     border-bottom>
          <u-input v-model="form.maintenanceTime"
                   placeholder="请选择维修日期"
                   readonly
                   @click="showDatePicker = true"
                   clearable />
          <template #right>
            <u-icon name="arrow-right"
                    @click="showDatePicker = true"></u-icon>
          </template>
        </u-form-item>
      </u-cell-group>
      <!-- æäº¤æŒ‰é’® -->
      <view class="footer-btns">
        <u-button class="cancel-btn"
                  @click="goBack">取消</u-button>
        <u-button class="save-btn"
                  @click="submitForm"
                  :loading="loading">保存</u-button>
      </view>
    </u-form>
    <!-- æ—¥æœŸé€‰æ‹©å™¨ -->
    <up-datetime-picker :show="showDatePicker"
                        v-model="pickerDateValue"
                        mode="datetime"
                        title="选择日期"
                        format="YYYY-MM-DD HH:mm:ss"
                        @confirm="onDateConfirm"
                        @cancel="showDatePicker = false" />
  </view>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { onShow } from '@dcloudio/uni-app';
import PageHeader from '@/components/PageHeader.vue';
import { addMaintain } from '@/api/equipmentManagement/repair';
import useUserStore from "@/store/modules/user";
import dayjs from "dayjs";
  import { ref, onMounted } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import { addMaintain } from "@/api/equipmentManagement/repair";
  import useUserStore from "@/store/modules/user";
  import dayjs from "dayjs";
defineOptions({
    name: "设备维修表单",
});
  defineOptions({
    name: "设备维修表单",
  });
const userStore = useUserStore();
  const userStore = useUserStore();
// è¡¨å•引用
const formRef = ref(null);
const loading = ref(false);
const showDatePicker = ref(false);
const pickerDateValue = ref(Date.now());; // ä½¿ç”¨æ—¶é—´æˆ³
  // è¡¨å•引用
  const formRef = ref(null);
  const loading = ref(false);
  const showDatePicker = ref(false);
  const pickerDateValue = ref(Date.now()); // ä½¿ç”¨æ—¶é—´æˆ³
// è¡¨å•验证规则
const formRules = {
    maintenanceName: [{ required: true, trigger: "blur", message: "请输入维修人" }],
    maintenanceResult: [{ required: true, trigger: "blur", message: "请输入维修结果" }],
    maintenanceTime: [{ required: true, trigger: "change", message: "请选择维修日期" }],
};
  // è¡¨å•验证规则
  const formRules = {
    maintenanceName: [
      { required: true, trigger: "blur", message: "请输入维修人" },
    ],
    maintenanceResult: [
      { required: true, trigger: "blur", message: "请输入维修结果" },
    ],
    maintenanceTime: [
      { required: true, trigger: "change", message: "请选择维修日期" },
    ],
  };
  const repairStatusOptions = ref([
    { name: "待维修", value: "0" },
    { name: "完结", value: "1" },
    { name: "失败", value: "2" },
  ]);
  const repairStatusText = ref("完结");
  // æ‰“开报修状态选择器
  const openRepairStatusPicker = () => {
    uni.showActionSheet({
      itemList: repairStatusOptions.value.map(item => item.name),
      success: res => {
        form.value.status = repairStatusOptions.value[res.tapIndex].value;
        repairStatusText.value = repairStatusOptions.value[res.tapIndex].name;
      },
    });
  };
  // ä½¿ç”¨ ref å£°æ˜Žè¡¨å•数据
  const form = ref({
    maintenanceName: userStore.nickName || "", // é»˜è®¤ä½¿ç”¨å½“前用户昵称
    maintenanceResult: undefined, // ç»´ä¿®ç»“æžœ
    maintenanceTime: dayjs().format("YYYY-MM-DD HH:mm:ss"), // ç»´ä¿®æ—¥æœŸï¼ˆåªæ˜¾ç¤ºæ—¥æœŸï¼‰
  });
// ä½¿ç”¨ ref å£°æ˜Žè¡¨å•数据
const form = ref({
    maintenanceName: userStore.nickName || '', // é»˜è®¤ä½¿ç”¨å½“前用户昵称
    maintenanceResult: undefined, // ç»´ä¿®ç»“æžœ
    maintenanceTime: dayjs().format("YYYY-MM-DD HH:mm:ss"), // ç»´ä¿®æ—¥æœŸï¼ˆåªæ˜¾ç¤ºæ—¥æœŸï¼‰
});
  // è‡ªå®šä¹‰showToast函数
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
// è‡ªå®šä¹‰showToast函数
const showToast = (message) => {
  uni.showToast({
    title: message,
    icon: 'none'
  })
};
  // é‡ç½®è¡¨å•数据和校验状态
  const resetForm = () => {
    form.value = {
      maintenanceName: userStore.nickName || "",
      maintenanceResult: undefined,
      maintenanceTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
    };
  };
// é‡ç½®è¡¨å•数据和校验状态
const resetForm = () => {
    form.value = {
        maintenanceName: userStore.nickName || '',
        maintenanceResult: undefined,
        maintenanceTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
    };
};
  const resetFormAndValidate = () => {
    resetForm();
  };
const resetFormAndValidate = () => {
    resetForm();
};
  // æäº¤è¡¨å•
  const submitForm = async () => {
    try {
      // ä½¿ç”¨uview-plus的表单验证方式
      const valid = await formRef.value.validate();
      if (valid) {
        submitFormData();
      }
    } catch (e) {
      showToast("表单验证失败");
    }
  };
// æäº¤è¡¨å•
const submitForm = async () => {
    try {
        // ä½¿ç”¨uview-plus的表单验证方式
        const valid = await formRef.value.validate();
        if (valid) {
            submitFormData();
        }
    } catch (e) {
        showToast('表单验证失败');
    }
};
  // æäº¤è¡¨å•数据
  const submitFormData = async () => {
    try {
      loading.value = true;
      const id = getPageId();
// æäº¤è¡¨å•数据
const submitFormData = async () => {
    try {
        loading.value = true;
        const id = getPageId();
        if (!id) {
            showToast('参数错误');
            loading.value = false;
            return;
        }
        // å‡†å¤‡æäº¤æ•°æ®ï¼ŒmaintenanceTime åŠ ä¸Šå½“å‰æ—¶åˆ†ç§’
        const submitData = { ...form.value };
        const { code } = await addMaintain({ id: id, ...submitData });
        if (code == 200) {
            showToast('新增维修成功');
            resetFormAndValidate();
            setTimeout(() => {
                goBack();
            }, 500);
        } else {
            loading.value = false;
        }
    } catch (e) {
        console.log(e);
        loading.value = false;
        showToast('操作失败');
    }
};
      if (!id) {
        showToast("参数错误");
        loading.value = false;
        return;
      }
      form.value.status = Number(form.value.status);
      // å‡†å¤‡æäº¤æ•°æ®ï¼ŒmaintenanceTime åŠ ä¸Šå½“å‰æ—¶åˆ†ç§’
      const submitData = { ...form.value };
// è¿”回上一页
const goBack = () => {
    uni.removeStorageSync('repairId');
    uni.navigateBack();
};
      const { code } = await addMaintain({ id: id, ...submitData });
// èŽ·å–é¡µé¢ID
const getPageId = () => {
    const id = uni.getStorageSync('repairId');
    return id;
};
      if (code == 200) {
        showToast("新增维修成功");
        resetFormAndValidate();
        setTimeout(() => {
          goBack();
        }, 500);
      } else {
        loading.value = false;
      }
    } catch (e) {
      console.log(e);
// ç¡®è®¤æ—¥æœŸé€‰æ‹©
const onDateConfirm = (e) => {
    form.value.maintenanceTime = dayjs(e.value).format('YYYY-MM-DD HH:mm:ss')
    pickerDateValue.value = e.value
    showDatePicker.value = false;
};
      loading.value = false;
      showToast("操作失败");
    }
  };
// åˆå§‹åŒ–表单数据
const initForm = () => {
    // è®¾ç½®ç»´ä¿®äººä¸ºå½“前用户昵称
    form.value.maintenanceName = userStore.nickName || '';
    // è®¾ç½®å½“前日期(只包含年月日)
    form.value.maintenanceTime = dayjs().format('YYYY-MM-DD HH:mm:ss');
};
  // è¿”回上一页
  const goBack = () => {
    uni.removeStorageSync("repairId");
    uni.navigateBack();
  };
onShow(() => {
    // é¡µé¢æ˜¾ç¤ºæ—¶åˆå§‹åŒ–表单
    initForm();
});
  // èŽ·å–é¡µé¢ID
  const getPageId = () => {
    const id = uni.getStorageSync("repairId");
    return id;
  };
onMounted(() => {
    // é¡µé¢åŠ è½½æ—¶åˆå§‹åŒ–è¡¨å•
    initForm();
});
  // ç¡®è®¤æ—¥æœŸé€‰æ‹©
  const onDateConfirm = e => {
    form.value.maintenanceTime = dayjs(e.value).format("YYYY-MM-DD HH:mm:ss");
    pickerDateValue.value = e.value;
    showDatePicker.value = false;
  };
  // åˆå§‹åŒ–表单数据
  const initForm = () => {
    form.value.status = "1";
    // è®¾ç½®ç»´ä¿®äººä¸ºå½“前用户昵称
    form.value.maintenanceName = userStore.nickName || "";
    // è®¾ç½®å½“前日期(只包含年月日)
    form.value.maintenanceTime = dayjs().format("YYYY-MM-DD HH:mm:ss");
  };
  onShow(() => {
    // é¡µé¢æ˜¾ç¤ºæ—¶åˆå§‹åŒ–表单
    initForm();
  });
  onMounted(() => {
    // é¡µé¢åŠ è½½æ—¶åˆå§‹åŒ–è¡¨å•
    initForm();
  });
</script>
<style scoped lang="scss">
@import '@/static/scss/form-common.scss';
.repair-maintain {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 5rem;
}
  @import "@/static/scss/form-common.scss";
  .repair-maintain {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 5rem;
  }
.footer-btns {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    background: #fff;
    display: flex;
    justify-content: space-around;
    align-items: center;
    padding: 0.75rem 0;
    box-shadow: 0 -0.125rem 0.5rem rgba(0,0,0,0.05);
    z-index: 1000;
}
  .footer-btns {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    background: #fff;
    display: flex;
    justify-content: space-around;
    align-items: center;
    padding: 0.75rem 0;
    box-shadow: 0 -0.125rem 0.5rem rgba(0, 0, 0, 0.05);
    z-index: 1000;
  }
.cancel-btn {
    font-weight: 400;
    font-size: 1rem;
    color: #FFFFFF;
    width: 6.375rem;
    background: #C7C9CC;
    box-shadow: 0 0.25rem 0.625rem 0 rgba(3,88,185,0.2);
    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
}
  .cancel-btn {
    font-weight: 400;
    font-size: 1rem;
    color: #ffffff;
    width: 6.375rem;
    background: #c7c9cc;
    box-shadow: 0 0.25rem 0.625rem 0 rgba(3, 88, 185, 0.2);
    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
  }
.save-btn {
    font-weight: 400;
    font-size: 1rem;
    color: #FFFFFF;
    width: 14rem;
    background: linear-gradient( 140deg, #00BAFF 0%, #006CFB 100%);
    box-shadow: 0 0.25rem 0.625rem 0 rgba(3,88,185,0.2);
    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
}
  .save-btn {
    font-weight: 400;
    font-size: 1rem;
    color: #ffffff;
    width: 14rem;
    background: linear-gradient(140deg, #00baff 0%, #006cfb 100%);
    box-shadow: 0 0.25rem 0.625rem 0 rgba(3, 88, 185, 0.2);
    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
  }
// å“åº”式调整
@media (max-width: 768px) {
    .submit-section {
        padding: 12px;
    }
}
  // å“åº”式调整
  @media (max-width: 768px) {
    .submit-section {
      padding: 12px;
    }
  }
.tip-text {
    padding: 4px 16px 0 16px;
    font-size: 12px;
    color: #888;
}
  .tip-text {
    padding: 4px 16px 0 16px;
    font-size: 12px;
    color: #888;
  }
</style>
src/pages/equipmentManagement/runManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,548 @@
<template>
  <view class="notice-page">
    <PageHeader title="运行管理"
                @back="goBack" />
    <!-- é€šçŸ¥å…¬å‘Šæ¿ -->
    <view class="notice-board">
      <!-- ç»Ÿä¸€é€šçŸ¥åŒºåŸŸ -->
      <view v-if="holidayNotices.length > 0"
            class="notice-section">
        <view class="section-header">
          <h3>设备运行记录</h3>
        </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 } from "vue";
  import PageHeader from "@/components/PageHeader.vue";
  import {
    getLedgerPage,
    editLedger,
  } from "@/api/equipmentManagement/runManagement.js";
  // èŽ·å–æ ‡ç­¾ç±»åž‹
  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 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 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 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 holidayNotices = ref([]);
  const maintenanceNotices = ref([]);
  const fetchHolidayNotices = (append = false) => {
    getLedgerPage({}).then(res => {
      holidayNotices.value = res?.data?.records || [];
    });
  };
  // ç”Ÿå‘½å‘¨æœŸ
  onMounted(() => {
    fetchHolidayNotices();
  });
</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/equipmentManagement/upkeep/fileList.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,562 @@
<template>
  <view class="file-list-page">
    <!-- é¡µé¢å¤´éƒ¨ -->
    <PageHeader title="附件管理"
                @back="goBack" />
    <!-- é™„件列表 -->
    <view class="file-list-container">
      <view v-if="fileList.length > 0"
            class="file-list">
        <view v-for="(file, index) in fileList"
              :key="file.id || index"
              class="file-item">
          <!-- æ–‡ä»¶å›¾æ ‡ -->
          <!-- <view class="file-icon"
                :class="getFileIconClass(file.fileType)">
            <up-icon :name="getFileIcon(file.fileType)"
                     size="24"
                     color="#ffffff" />
          </view> -->
          <!-- æ–‡ä»¶ä¿¡æ¯ -->
          <view class="file-info">
            <text class="file-name">{{ file.name }}</text>
            <!-- <text class="file-meta">{{ formatFileSize(file.fileSize) }} Â· {{ file.uploadTime || file.createTime }}</text> -->
          </view>
          <!-- æ“ä½œæŒ‰é’® -->
          <view class="file-actions">
            <!-- <u-button size="small"
                      type="primary"
                      plain
                      @click="previewFile(file)">预览</u-button> -->
            <u-button size="small"
                      type="info"
                      plain
                      @click="downloadFile(file)">下载并预览</u-button>
            <u-button size="small"
                      type="error"
                      plain
                      @click="confirmDelete(file, index)">删除</u-button>
          </view>
        </view>
      </view>
      <!-- ç©ºçŠ¶æ€ -->
      <view v-else
            class="empty-state">
        <up-icon name="document"
                 size="64"
                 color="#c0c4cc" />
        <text class="empty-text">暂无附件</text>
      </view>
    </view>
    <!-- <a rel="nofollow"
       id="downloadLink"
       href="#"
       style="display:none;">下载文本文件</a> -->
    <!-- ä¸Šä¼ æŒ‰é’® -->
    <view class="upload-button"
          @click="chooseFile">
      <up-icon name="plus"
               size="24"
               color="#ffffff" />
      <text class="upload-text">上传附件</text>
    </view>
  </view>
</template>
<script setup>
  import { ref, onMounted } from "vue";
  import PageHeader from "@/components/PageHeader.vue";
  import config from "@/config";
  import { getToken } from "@/utils/auth";
  // import { saveAs } from "file-saver";
  import {
    listMaintenanceTaskFiles,
    addMaintenanceTaskFile,
    delMaintenanceTaskFile,
  } from "@/api/equipmentManagement/upkeep";
  import { blobValidate } from "@/utils/ruoyi";
  // é™„件列表
  const fileList = ref([]);
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  // const request = axios.create({
  //   baseURL: "URL.com",
  //   adapter: axiosAdapterUniapp,
  // });
  // èŽ·å–æ–‡ä»¶å›¾æ ‡
  const getFileIcon = fileType => {
    const iconMap = {
      doc: "document",
      docx: "document",
      xls: "grid",
      xlsx: "grid",
      pdf: "document",
      ppt: "copy",
      pptx: "copy",
      txt: "document",
      jpg: "image",
      jpeg: "image",
      png: "image",
      gif: "image",
      zip: "folder",
      rar: "folder",
    };
    return iconMap[fileType.toLowerCase()] || "document";
  };
  // èŽ·å–æ–‡ä»¶å›¾æ ‡æ ·å¼ç±»
  const getFileIconClass = fileType => {
    const colorMap = {
      doc: "blue",
      docx: "blue",
      xls: "green",
      xlsx: "green",
      pdf: "red",
      ppt: "orange",
      pptx: "orange",
      txt: "gray",
      jpg: "purple",
      jpeg: "purple",
      png: "purple",
      gif: "purple",
      zip: "yellow",
      rar: "yellow",
    };
    return colorMap[fileType.toLowerCase()] || "gray";
  };
  // æ ¼å¼åŒ–文件大小
  const formatFileSize = bytes => {
    if (bytes === 0) return "0 B";
    const k = 1024;
    const sizes = ["B", "KB", "MB", "GB"];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
  };
  // é€‰æ‹©æ–‡ä»¶
  const chooseFile = () => {
    uni.chooseImage({
      count: 9,
      sizeType: ["original", "compressed"],
      sourceType: ["album", "camera"],
      success: res => {
        console.log(res, "选择图片成功");
        uploadFiles(res.tempFiles);
      },
      fail: err => {
        console.error("选择图片失败:", err);
        showToast("选择文件失败");
      },
    });
    // uni.chooseFile({
    //   count: 9,
    //   extension: [
    //     ".doc",
    //     ".docx",
    //     ".xls",
    //     ".xlsx",
    //     ".pdf",
    //     ".ppt",
    //     ".pptx",
    //     ".txt",
    //     ".jpg",
    //     ".jpeg",
    //     ".png",
    //     ".gif",
    //     ".zip",
    //     ".rar",
    //   ],
    //   success: res => {
    //     console.log(res, "选择文件成功");
    //     uploadFiles(res.tempFiles);
    //   },
    //   fail: err => {
    //     showToast("选择文件失败");
    //   },
    // });
  };
  // ä¸Šä¼ æ–‡ä»¶
  const uploadFiles = tempFiles => {
    console.log(tempFiles, "上传文件1");
    tempFiles.forEach((tempFile, index) => {
      // æ˜¾ç¤ºä¸Šä¼ ä¸­æç¤º
      uni.showLoading({
        title: "上传中...",
        mask: true,
      });
      console.log(tempFile, "上传文件2");
      // 1. ç›´æŽ¥ä½¿ç”¨ uni.uploadFile ä¸Šä¼ æ–‡ä»¶
      uni.uploadFile({
        url: config.baseUrl + "/file/upload",
        filePath: tempFile.path,
        name: "file",
        header: {
          Authorization: "Bearer " + getToken(),
        },
        success: uploadRes => {
          uni.hideLoading();
          console.log(uploadRes, "上传文件3");
          try {
            const res = JSON.parse(uploadRes.data);
            console.log(res, "上传文件4");
            if (res.code === 200) {
              // 2. æå–文件信息
              const fileName = tempFile.name
                ? tempFile.name
                : tempFile.path.split("/").pop();
              // const fileType = fileName.split(".").pop();
              // 3. æž„造保存文件信息的参数
              const saveData = {
                name: fileName,
                deviceMaintenanceId: upkeepId.value,
                url: res.data.tempPath || "",
              };
              console.log(saveData, "保存文件信息参数");
              // 4. è°ƒç”¨ addRuleFile æŽ¥å£ä¿å­˜æ–‡ä»¶ä¿¡æ¯
              addMaintenanceTaskFile(saveData)
                .then(addRes => {
                  if (addRes.code === 200) {
                    // 5. æ·»åŠ åˆ°æ–‡ä»¶åˆ—è¡¨
                    const newFile = {
                      ...addRes.data,
                      uploadTime: new Date().toLocaleString(),
                    };
                    // fileList.value.push(newFile);
                    getFileList();
                    showToast("上传成功");
                  } else {
                    showToast("保存文件信息失败");
                  }
                })
                .catch(err => {
                  console.error("保存文件信息失败:", err);
                  showToast("保存文件信息失败");
                });
            } else {
              showToast("文件上传失败");
            }
          } catch (e) {
            console.error("解析上传结果失败:", e);
            showToast("上传失败");
          }
        },
        fail: err => {
          uni.hideLoading();
          console.error("上传失败:", err);
          showToast("上传失败");
        },
      });
    });
  };
  // ä¸‹è½½æ–‡ä»¶
  const downloadFile = file => {
    var url =
      config.baseUrl +
      "/common/download?fileName=" +
      encodeURIComponent(file.url) +
      "&delete=true";
    console.log(url, "url");
    uni
      .downloadFile({
        url: url,
        responseType: "blob",
        header: { Authorization: "Bearer " + getToken() },
      })
      .then(res => {
        let osType = uni.getStorageSync("deviceInfo").osName;
        let filePath = res.tempFilePath;
        if (osType === "ios") {
          uni.openDocument({
            filePath: filePath,
            showMenu: true,
            success: res => {},
            fail: err => {
              console.log("uni.openDocument--fail");
              reject(err);
            },
          });
        } else {
          uni.saveFile({
            tempFilePath: filePath,
            success: fileRes => {
              uni.showToast({
                icon: "none",
                mask: true,
                title:
                  "文件已保存:Android/data/uni.UNI720216F/apps/__UNI__720216F/" +
                  fileRes.savedFilePath, //保存路径
                duration: 3000,
              });
              setTimeout(() => {
                //打开文档查看
                uni.openDocument({
                  filePath: fileRes.savedFilePath,
                  success: function (res) {},
                });
              }, 1000);
            },
            fail: err => {
              console.log("uni.save--fail");
              reject(err);
            },
          });
        }
        // const isBlob = blobValidate(res.data);
        // if (isBlob) {
        //   const blob = new Blob([res.data], { type: "text/plain" });
        //   const url = URL.createObjectURL(blob);
        //   const downloadLink = document.getElementById("downloadLink");
        //   downloadLink.href = url;
        //   downloadLink.download = file.name;
        //   downloadLink.click();
        //   showToast("下载成功");
        // } else {
        //   showToast("下载失败");
        // }
      })
      .catch(err => {
        console.error("下载失败:", err);
        showToast("下载失败");
      });
  };
  // ç¡®è®¤åˆ é™¤
  const confirmDelete = (file, index) => {
    uni.showModal({
      title: "删除确认",
      content: `确定要删除附件 "${file.name}" å—?`,
      success: res => {
        if (res.confirm) {
          deleteFile(file.id, index);
        }
      },
    });
  };
  // åˆ é™¤æ–‡ä»¶
  const deleteFile = (fileId, index) => {
    uni.showLoading({
      title: "删除中...",
      mask: true,
    });
    delMaintenanceTaskFile([fileId])
      .then(res => {
        uni.hideLoading();
        if (res.code === 200) {
          // fileList.value.splice(index, 1);
          getFileList();
          showToast("删除成功");
        } else {
          showToast("删除失败");
        }
      })
      .catch(err => {
        uni.hideLoading();
        showToast("删除失败");
      });
  };
  // æ˜¾ç¤ºæç¤º
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
  const rulesRegulationsManagementId = ref("");
  const upkeepId = ref("");
  // é¡µé¢åŠ è½½æ—¶
  onMounted(() => {
    // ä»Ž API èŽ·å–é™„ä»¶åˆ—è¡¨
    // ä»Žæœ¬åœ°å­˜å‚¨èŽ·å– rulesRegulationsManagementId
    rulesRegulationsManagementId.value = uni.getStorageSync(
      "rulesRegulationsManagement"
    );
    upkeepId.value = uni.getStorageSync("upkeepId");
    getFileList();
  });
  // èŽ·å–é™„ä»¶åˆ—è¡¨
  const getFileList = () => {
    uni.showLoading({
      title: "加载中...",
      mask: true,
    });
    listMaintenanceTaskFiles({
      current: 1,
      size: 100,
      deviceMaintenanceId: upkeepId.value,
      rulesRegulationsManagementId: upkeepId.value,
    })
      .then(res => {
        uni.hideLoading();
        if (res.code === 200) {
          fileList.value = res.data.records || [];
        } else {
          showToast("获取附件列表失败");
        }
      })
      .catch(err => {
        uni.hideLoading();
        showToast("获取附件列表失败");
      });
  };
</script>
<style scoped lang="scss">
  @import "../../../styles/sales-common.scss";
  .file-list-page {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 100rpx;
  }
  .file-list-container {
    padding: 20rpx;
  }
  .file-list {
    background: #ffffff;
    border-radius: 8rpx;
    overflow: hidden;
    box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
  }
  .file-item {
    display: flex;
    align-items: center;
    padding: 20rpx;
    border-bottom: 1rpx solid #f0f0f0;
    &:last-child {
      border-bottom: none;
    }
  }
  .file-icon {
    width: 56rpx;
    height: 56rpx;
    border-radius: 8rpx;
    display: flex;
    justify-content: center;
    align-items: center;
    margin-right: 20rpx;
    &.blue {
      background: #409eff;
    }
    &.green {
      background: #67c23a;
    }
    &.red {
      background: #f56c6c;
    }
    &.orange {
      background: #e6a23c;
    }
    &.gray {
      background: #909399;
    }
    &.purple {
      background: #909399;
    }
    &.yellow {
      background: #e6a23c;
    }
  }
  .file-info {
    flex: 1;
    min-width: 0;
  }
  .file-name {
    display: block;
    font-size: 16px;
    color: #303133;
    margin-bottom: 8rpx;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .file-meta {
    display: block;
    font-size: 12px;
    color: #909399;
  }
  .file-actions {
    display: flex;
    gap: 12rpx;
  }
  .empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 100rpx 0;
    background: #ffffff;
    border-radius: 8rpx;
    box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
  }
  .empty-text {
    font-size: 14px;
    color: #909399;
    margin-top: 20rpx;
  }
  .upload-button {
    position: fixed;
    bottom: 40rpx;
    right: 40rpx;
    width: 130rpx;
    height: 130rpx;
    border-radius: 50%;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    box-shadow: 0 4rpx 16rpx rgba(102, 126, 234, 0.4);
    z-index: 1000;
  }
  .upload-text {
    font-size: 10px;
    color: #ffffff;
    margin-top: 4rpx;
  }
  .upload-progress {
    padding: 40rpx 0;
  }
  .upload-progress-text {
    display: block;
    text-align: center;
    margin-top: 20rpx;
    font-size: 14px;
    color: #606266;
  }
</style>
src/pages/equipmentManagement/upkeep/index.vue
@@ -1,44 +1,50 @@
<template>
  <view class="sales-account">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="设备保养" @back="goBack" />
    <PageHeader title="设备保养"
                @back="goBack" />
    <!-- æœç´¢åŒºåŸŸ -->
    <view class="search-section">
      <view class="search-bar">
        <view class="search-input">
          <up-input
            class="search-text"
            placeholder="请输入设备名称搜索"
            v-model="searchKeyword"
            @change="getList"
            clearable
          />
          <up-input class="search-text"
                    placeholder="请输入设备名称搜索"
                    v-model="searchKeyword"
                    @change="getList"
                    clearable />
        </view>
        <view class="filter-button" @click="getList">
          <up-icon name="search" size="24" color="#999"></up-icon>
        <view class="filter-button"
              @click="getList">
          <up-icon name="search"
                   size="24"
                   color="#999"></up-icon>
        </view>
      </view>
    </view>
    <!-- è®¾å¤‡ä¿å…»åˆ—表 -->
    <view class="ledger-list" v-if="upkeepList.length > 0">
      <view v-for="(item, index) in upkeepList" :key="index">
        <view class="ledger-item" @click="toggleSelection(item)">
    <view class="ledger-list"
          v-if="upkeepList.length > 0">
      <view v-for="(item, index) in upkeepList"
            :key="index">
        <view class="ledger-item"
              @click="toggleSelection(item)">
          <view class="item-header">
            <view class="item-left">
              <view class="document-icon">
                <up-icon name="file-text" size="16" color="#ffffff"></up-icon>
                <up-icon name="file-text"
                         size="16"
                         color="#ffffff"></up-icon>
              </view>
              <text class="item-id">设备名称:{{ item.deviceName }}</text>
            </view>
            <view class="status-tag">
              <u-tag v-if="item.status === 1" type="success">完结</u-tag>
              <u-tag v-if="item.status === 0" type="error">待保养</u-tag>
              <u-tag v-if="item.status === 1"
                     type="success">完结</u-tag>
              <u-tag v-if="item.status === 0"
                     type="error">待保养</u-tag>
            </view>
          </view>
          <up-divider></up-divider>
          <view class="item-details">
            <view class="detail-row">
              <text class="detail-label">规格型号</text>
@@ -67,267 +73,282 @@
            <view class="detail-row">
              <text class="detail-label">保养结果</text>
              <view class="detail-value">
              <u-tag v-if="item.maintenanceResult === 1" type="success" size="mini">
                å®Œå¥½
              </u-tag>
              <u-tag v-if="item.maintenanceResult === 0" type="error" size="mini">
                ç»´ä¿®
              </u-tag>
              <text v-if="item.maintenanceResult === undefined || item.maintenanceResult === null">-</text>
            </view>
                <u-tag v-if="item.maintenanceResult === 1"
                       type="success"
                       size="mini">
                  å®Œå¥½
                </u-tag>
                <u-tag v-if="item.maintenanceResult === 0"
                       type="error"
                       size="mini">
                  ç»´ä¿®
                </u-tag>
                <text v-if="item.maintenanceResult === undefined || item.maintenanceResult === null">-</text>
              </view>
            </view>
          </view>
          <!-- æŒ‰é’®åŒºåŸŸ -->
          <view class="action-buttons">
            <u-button
              type="primary"
              size="small"
              class="action-btn"
              :disabled="item.status === 1"
              @click.stop="edit(item.id)"
            >
            <u-button type="primary"
                      size="small"
                      class="action-btn"
                      :disabled="item.status === 1"
                      @click.stop="edit(item.id)">
              ç¼–辑
            </u-button>
            <u-button
              type="warning"
              size="small"
              class="action-btn"
              :disabled="item.status === 1"
              @click.stop="addMaintain(item.id)"
            >
            <u-button type="warning"
                      size="small"
                      class="action-btn"
                      :disabled="item.status === 1"
                      @click.stop="addMaintain(item.id)">
              ä¿å…»
            </u-button>
            <u-button
              type="error"
              size="small"
              plain
              class="action-btn"
              @click.stop="delUpkeepByIds(item.id)"
            >
            <u-button type="error"
                      size="small"
                      plain
                      class="action-btn"
                      @click.stop="delUpkeepByIds(item.id)">
              åˆ é™¤
            </u-button>
            <u-button type="warning"
                      size="small"
                      class="action-btn"
                      @click.stop="addFile(item.id)">
              é™„ä»¶
            </u-button>
          </view>
        </view>
      </view>
    </view>
    <view v-else class="no-data">
    <view v-else
          class="no-data">
      <text>暂无设备保养数据</text>
    </view>
    <!-- æµ®åŠ¨æ–°å¢žæŒ‰é’® -->
    <view class="fab-button" @click="addPlan">
      <up-icon name="plus" size="24" color="#ffffff"></up-icon>
    <view class="fab-button"
          @click="addPlan">
      <up-icon name="plus"
               size="24"
               color="#ffffff"></up-icon>
    </view>
  </view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import PageHeader from '@/components/PageHeader.vue'
import { getUpkeepPage, delUpkeep } from '@/api/equipmentManagement/upkeep'
import useUserStore from "@/store/modules/user"
// æ˜¾ç¤ºæç¤ºä¿¡æ¯
const showToast = (message) => {
  uni.showToast({
    title: message,
    icon: 'none'
  })
};
import dayjs from "dayjs"
  import { ref, onMounted } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import { getUpkeepPage, delUpkeep } from "@/api/equipmentManagement/upkeep";
  import useUserStore from "@/store/modules/user";
  // æ˜¾ç¤ºæç¤ºä¿¡æ¯
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
  import dayjs from "dayjs";
const userStore = useUserStore()
  const userStore = useUserStore();
// æœç´¢å…³é”®è¯
const searchKeyword = ref('')
  // æœç´¢å…³é”®è¯
  const searchKeyword = ref("");
// è®¾å¤‡ä¿å…»æ•°æ®
const upkeepList = ref([])
  // è®¾å¤‡ä¿å…»æ•°æ®
  const upkeepList = ref([]);
// å¤šé€‰åˆ—表
const multipleList = ref([])
  // å¤šé€‰åˆ—表
  const multipleList = ref([]);
// è¿”回上一页
const goBack = () => {
  uni.navigateBack()
}
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
// æ ¼å¼åŒ–日期
const formatDate = (dateStr) => {
  if (!dateStr) return ''
  return dayjs(dateStr).format("YYYY-MM-DD")
}
  // æ ¼å¼åŒ–日期
  const formatDate = dateStr => {
    if (!dateStr) return "";
    return dayjs(dateStr).format("YYYY-MM-DD");
  };
// æ ¼å¼åŒ–日期时间
const formatDateTime = (dateStr) => {
  if (!dateStr) return ''
  return dayjs(dateStr).format("YYYY-MM-DD HH:mm:ss")
}
  // æ ¼å¼åŒ–日期时间
  const formatDateTime = dateStr => {
    if (!dateStr) return "";
    return dayjs(dateStr).format("YYYY-MM-DD HH:mm:ss");
  };
// æŸ¥è¯¢åˆ—表
const getList = () => {
  showLoadingToast('加载中...')
  const params = {
    current: -1,
    size: -1,
    deviceName: searchKeyword.value || undefined
  }
  getUpkeepPage(params)
    .then((res) => {
      // å¦‚æžœres.data不是数组,设置为空数组
      upkeepList.value = res.records || res.data?.records || []
      closeToast()
    })
    .catch(() => {
      closeToast()
      showToast('获取数据失败')
    })
}
  // æŸ¥è¯¢åˆ—表
  const getList = () => {
    showLoadingToast("加载中...");
    const params = {
      current: -1,
      size: -1,
      deviceName: searchKeyword.value || undefined,
    };
    getUpkeepPage(params)
      .then(res => {
        // å¦‚æžœres.data不是数组,设置为空数组
        upkeepList.value = res.records || res.data?.records || [];
        closeToast();
      })
      .catch(() => {
        closeToast();
        showToast("获取数据失败");
      });
  };
  // æ–°å¢žé™„ä»¶ - è·³è½¬åˆ°é™„件页面
  const addFile = id => {
    // ä½¿ç”¨æœ¬åœ°å­˜å‚¨ä¼ é€’id
    uni.setStorageSync("upkeepId", id);
    uni.navigateTo({
      url: "/pages/equipmentManagement/upkeep/fileList",
    });
  };
// æ˜¾ç¤ºåŠ è½½æç¤º
const showLoadingToast = (message) => {
  uni.showLoading({
    title: message,
    mask: true
  });
};
  // æ˜¾ç¤ºåŠ è½½æç¤º
  const showLoadingToast = message => {
    uni.showLoading({
      title: message,
      mask: true,
    });
  };
// å…³é—­æç¤º
const closeToast = () => {
  uni.hideLoading();
};
  // å…³é—­æç¤º
  const closeToast = () => {
    uni.hideLoading();
  };
// åˆ‡æ¢é€‰æ‹©çŠ¶æ€
const toggleSelection = (item) => {
  const index = multipleList.value.findIndex(selected => selected.id === item.id)
  if (index > -1) {
    multipleList.value.splice(index, 1)
  } else {
    multipleList.value.push(item)
  }
}
// æ£€æŸ¥æ˜¯å¦å·²é€‰æ‹©
const isSelected = (item) => {
  return multipleList.value.some(selected => selected.id === item.id)
}
// æ–°å¢žä¿å…» - è·³è½¬åˆ°ä¿å…»é¡µé¢
const addMaintain = (id) => {
  if (!id && multipleList.value.length !== 1) {
    showToast('请选择一条记录')
    return
  }
  const targetId = id || multipleList.value[0].id
  // ä½¿ç”¨æœ¬åœ°å­˜å‚¨ä¼ é€’id
  uni.setStorageSync('repairId', targetId)
  uni.navigateTo({
    url: '/pages/equipmentManagement/upkeep/maintain'
  })
}
// æ–°å¢žè®¡åˆ’ - è·³è½¬åˆ°æ–°å¢žé¡µé¢
const addPlan = () => {
  uni.navigateTo({
    url: '/pages/equipmentManagement/upkeep/add'
  })
}
// ç¼–辑 - è·³è½¬åˆ°add页面,通过id区分新增还是编辑
const edit = (id) => {
  if (!id) return
  // ä½¿ç”¨æœ¬åœ°å­˜å‚¨ä¼ é€’id
  uni.setStorageSync('repairId', id)
  uni.navigateTo({
    url: '/pages/equipmentManagement/upkeep/add'
  })
}
// åˆ é™¤ä¿å…»æ•°æ®
const delUpkeepByIds = async (ids) => {
  const deleteIds = Array.isArray(ids) ? ids : [ids]
  if (deleteIds.length === 0) {
    showToast('请选择要删除的记录')
    return
  }
  uni.showModal({
    title: '警告',
    content: '确认删除保养数据, æ­¤æ“ä½œä¸å¯é€†?',
    confirmText: '确定',
    cancelText: '取消',
    success: async (res) => {
      if (!res.confirm) return
      try {
        // é€ä¸ªåˆ é™¤
        for (const id of deleteIds) {
          const response = await delUpkeep(id)
          if (response.code !== 200) {
            showToast('删除失败')
            return
          }
        }
        showToast('删除成功')
        multipleList.value = []
        getList()
      } catch (e) {
        showToast('删除失败')
      }
  // åˆ‡æ¢é€‰æ‹©çŠ¶æ€
  const toggleSelection = item => {
    const index = multipleList.value.findIndex(
      selected => selected.id === item.id
    );
    if (index > -1) {
      multipleList.value.splice(index, 1);
    } else {
      multipleList.value.push(item);
    }
  })
}
  };
onMounted(() => {
  getList()
})
  // æ£€æŸ¥æ˜¯å¦å·²é€‰æ‹©
  const isSelected = item => {
    return multipleList.value.some(selected => selected.id === item.id);
  };
onShow(() => {
  getList()
})
  // æ–°å¢žä¿å…» - è·³è½¬åˆ°ä¿å…»é¡µé¢
  const addMaintain = id => {
    if (!id && multipleList.value.length !== 1) {
      showToast("请选择一条记录");
      return;
    }
    const targetId = id || multipleList.value[0].id;
    // ä½¿ç”¨æœ¬åœ°å­˜å‚¨ä¼ é€’id
    uni.setStorageSync("repairId", targetId);
    uni.navigateTo({
      url: "/pages/equipmentManagement/upkeep/maintain",
    });
  };
  // æ–°å¢žè®¡åˆ’ - è·³è½¬åˆ°æ–°å¢žé¡µé¢
  const addPlan = () => {
    uni.navigateTo({
      url: "/pages/equipmentManagement/upkeep/add",
    });
  };
  // ç¼–辑 - è·³è½¬åˆ°add页面,通过id区分新增还是编辑
  const edit = id => {
    if (!id) return;
    // ä½¿ç”¨æœ¬åœ°å­˜å‚¨ä¼ é€’id
    uni.setStorageSync("repairId", id);
    uni.navigateTo({
      url: "/pages/equipmentManagement/upkeep/add",
    });
  };
  // åˆ é™¤ä¿å…»æ•°æ®
  const delUpkeepByIds = async ids => {
    const deleteIds = Array.isArray(ids) ? ids : [ids];
    if (deleteIds.length === 0) {
      showToast("请选择要删除的记录");
      return;
    }
    uni.showModal({
      title: "警告",
      content: "确认删除保养数据, æ­¤æ“ä½œä¸å¯é€†?",
      confirmText: "确定",
      cancelText: "取消",
      success: async res => {
        if (!res.confirm) return;
        try {
          // é€ä¸ªåˆ é™¤
          for (const id of deleteIds) {
            const response = await delUpkeep(id);
            if (response.code !== 200) {
              showToast("删除失败");
              return;
            }
          }
          showToast("删除成功");
          multipleList.value = [];
          getList();
        } catch (e) {
          showToast("删除失败");
        }
      },
    });
  };
  onMounted(() => {
    getList();
  });
  onShow(() => {
    getList();
  });
</script>
<style scoped lang="scss">
@import '@/styles/sales-common.scss';
  @import "@/styles/sales-common.scss";
// è®¾å¤‡ä¿å…»ç‰¹æœ‰æ ·å¼
.sales-account {
  padding-bottom: 80px; // ä¸ºæµ®åŠ¨æŒ‰é’®ç•™å‡ºç©ºé—´
}
  // è®¾å¤‡ä¿å…»ç‰¹æœ‰æ ·å¼
  .sales-account {
    padding-bottom: 80px; // ä¸ºæµ®åŠ¨æŒ‰é’®ç•™å‡ºç©ºé—´
  }
.action-section {
  padding: 10px 20px;
  background: #ffffff;
  border-bottom: 1px solid #f0f0f0;
}
  .action-section {
    padding: 10px 20px;
    background: #ffffff;
    border-bottom: 1px solid #f0f0f0;
  }
.action-section .action-buttons {
  gap: 8px; // ä¸Žå…¬å…±æ ·å¼ä¸­çš„ 12px ä¸åŒ
  justify-content: flex-start;
}
  .action-section .action-buttons {
    gap: 8px; // ä¸Žå…¬å…±æ ·å¼ä¸­çš„ 12px ä¸åŒ
    justify-content: flex-start;
  }
.checkbox-wrapper {
  display: flex;
  align-items: center;
}
  .checkbox-wrapper {
    display: flex;
    align-items: center;
  }
.status-tag {
  display: flex;
  align-items: center;
}
  .status-tag {
    display: flex;
    align-items: center;
  }
.detail-label {
  min-width: 80px; // ä¸Žå…¬å…±æ ·å¼ä¸­çš„ 60px ä¸åŒ
}
  .detail-label {
    min-width: 80px; // ä¸Žå…¬å…±æ ·å¼ä¸­çš„ 60px ä¸åŒ
  }
.detail-value {
  display: flex;
  justify-content: flex-end;
  align-items: center;
}
  .detail-value {
    display: flex;
    justify-content: flex-end;
    align-items: center;
  }
.ledger-item .action-buttons {
  gap: 8px; // ä¸Žå…¬å…±æ ·å¼ä¸­çš„ 12px ä¸åŒ
}
  .ledger-item .action-buttons {
    gap: 8px; // ä¸Žå…¬å…±æ ·å¼ä¸­çš„ 12px ä¸åŒ
  }
</style>
src/pages/index.vue
@@ -86,29 +86,29 @@
          </view>
        </view>
        <!-- ååŒåŠžå…¬æ¨¡å— -->
        <view class="common-module collaboration-module">
          <view class="module-header">
            <view class="module-title-container">
              <text class="module-title">协同办公</text>
            </view>
          </view>
          <view class="module-content">
            <up-grid :border="false"
                     col="4">
              <up-grid-item v-for="(item, index) in collaborationItems"
                            :key="index"
                            @click="handleCommonItemClick(item)">
                <view class="icon-container"
                      :style="{ background: item.bgColor }">
                  <up-icon :name="item.icon"
                           :size="58"
                           color="#ffffff"></up-icon>
                </view>
                <text class="item-label">{{item.label}}</text>
              </up-grid-item>
            </up-grid>
          </view>
        </view>
        <view class="common-module collaboration-module">
            <view class="module-header">
                <view class="module-title-container">
                    <text class="module-title">协同办公</text>
                </view>
            </view>
            <view class="module-content">
                <up-grid :border="false"
                                 col="4">
                    <up-grid-item v-for="(item, index) in collaborationItems"
                                                :key="index"
                                                @click="handleCommonItemClick(item)">
                        <view class="icon-container"
                                    :style="{ background: item.bgColor }">
                            <up-icon :name="item.icon"
                                             :size="58"
                                             color="#ffffff"></up-icon>
                        </view>
                        <text class="item-label">{{item.label}}</text>
                    </up-grid-item>
                </up-grid>
            </view>
        </view>
    <!-- ç”Ÿäº§ç®¡æŽ§æ¨¡å— -->
    <!--        <view class="common-module production-module">-->
    <!--            <view class="module-header">-->
@@ -235,6 +235,10 @@
      label: "开票台账",
    },
    {
      icon: "/static/images/icon/kaipiaotaizhang@2x.png",
      label: "发货台账",
    },
    {
      icon: "/static/images/icon/huikuandengji@2x.png",
      label: "回款登记",
    },
@@ -277,16 +281,37 @@
  ]);
  // ååŒåŠžå…¬åŠŸèƒ½æ•°æ®
  const collaborationItems = reactive([
    {
      icon: "/static/images/icon/xietongshenpi@2x.png",
      label: "协同审批",
    },
    {
      icon: "/static/images/icon/kehubaifang@2x.png",
      label: "客户拜访",
    },
  ]);
    // ååŒåŠžå…¬åŠŸèƒ½æ•°æ®
    const collaborationItems = reactive([
        {
            icon: "/static/images/icon/baoxiaoguanli.png",
            label: "协同审批",
        },
        {
            icon: "/static/images/icon/huiyiliebiao@2x.png",
            label: "会议管理",
        },
        {
            icon: "/static/images/icon/tongzhigonggao@2x.png",
            label: "通知公告",
        },
        {
            icon: "/static/images/icon/zhishiku@2x.png",
            label: "知识库",
        },
        {
            icon: "/static/images/icon/yongyinguanli@2x.png",
            label: "用印管理",
        },
        {
            icon: "/static/images/icon/guizhangzhidu@2x.png",
            label: "规章制度",
        },
        {
            icon: "/static/images/icon/kehubaifang@2x.png",
            label: "客户拜访",
        },
    ]);
  // ç”Ÿäº§ç®¡æŽ§åŠŸèƒ½æ•°æ®
  const productionItems = reactive([
@@ -376,6 +401,11 @@
          url: "/pages/sales/invoiceLedger/index",
        });
        break;
      case "发货台账":
        uni.navigateTo({
          url: "/pages/sales/deliveryLedger/index",
        });
        break;
      case "回款登记":
        uni.navigateTo({
          url: "/pages/sales/receiptPayment/index",
@@ -421,16 +451,41 @@
          url: "/pages/procurementManagement/paymentLedger/index",
        });
        break;
      case "协同审批":
        uni.navigateTo({
          url: "/pages/cooperativeOffice/collaborativeApproval/index",
        });
        break;
      case "客户拜访":
        uni.navigateTo({
          url: "/pages/cooperativeOffice/clientVisit/index",
        });
        break;
            case "协同审批":
                uni.navigateTo({
                    url: "/pages/indexItem?label=协同审批",
                });
                break;
            case "会议管理":
                uni.navigateTo({
                    url: "/pages/indexItem?label=会议管理",
                });
                break;
            case "通知公告":
                uni.navigateTo({
                    url: "/pages/cooperativeOffice/noticeManagement/index",
                });
                break;
            case "知识库":
                uni.navigateTo({
                    url: "/pages/managementMeetings/knowledgeBase/index",
                });
                break;
            case "用印管理":
                uni.navigateTo({
                    url: "/pages/managementMeetings/sealManagement/index",
                });
                break;
            case "规章制度":
                uni.navigateTo({
                    url: "/pages/managementMeetings/rulesRegulationsManagement/index",
                });
                break;
            case "客户拜访":
                uni.navigateTo({
                    url: "/pages/cooperativeOffice/clientVisit/index",
                });
                break;
      case "生产订单":
        uni.navigateTo({
          url: "/pages/productionManagement/productionOrder/index",
src/pages/indexItem.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1124 @@
<template>
  <view class="content">
    <PageHeader :title="operationType"
                @back="goBack" />
    <view class="common-module collaboration-module">
      <view class="module-header">
        <view class="module-title-container">
          <text class="module-title">{{ operationType }}</text>
        </view>
      </view>
      <view class="module-content">
        <up-grid :border="false"
                 col="4">
          <up-grid-item v-for="(item, index) in commonItems"
                        :key="index"
                        @click="handleCommonItemClick(item)">
            <view class="icon-container"
                  :style="{ background: item.bgColor }">
              <up-icon :name="item.icon"
                       :size="58"
                       color="#ffffff"></up-icon>
            </view>
            <text class="item-label">{{item.label}}</text>
          </up-grid-item>
        </up-grid>
      </view>
    </view>
  </view>
</template>
<script setup>
  import { onMounted, reactive, ref } from "vue";
  import useUserStore from "@/store/modules/user";
  import { onLoad } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  const userStore = useUserStore();
  const kaoqin = 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: "出差管理",
    },
  ]);
  const caiwu = reactive([
    {
      icon: "/static/images/icon/baoxiaoguanli.png",
      label: "报销管理",
    },
    {
      icon: "/static/images/icon/caigouguanli.png",
      label: "采购审批",
    },
    {
      icon: "/static/images/icon/baojiaguanli.png",
      label: "报价审批",
    },
    {
      icon: "/static/images/icon/chukuguanli@2x.png",
      label: "发货审批",
    },
  ]);
  // è€ƒå‹¤è´¢åŠ¡åˆå¹¶èœå•ï¼ˆè€ƒå‹¤3个 + è´¢åŠ¡4个 = 7个)
  const kaoqinCaiwu = 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/baoxiaoguanli.png",
      label: "报销管理",
    },
    {
      icon: "/static/images/icon/caigouguanli.png",
      label: "采购审批",
    },
    {
      icon: "/static/images/icon/baojiaguanli.png",
      label: "报价审批",
    },
    {
      icon: "/static/images/icon/chukuguanli@2x.png",
      label: "发货审批",
    },
  ]);
  const huiyi = reactive([
    {
      icon: "/static/images/icon/huiyishezhi@2x.png",
      label: "会议设置",
    },
    {
      icon: "/static/images/icon/huiyiliebiao@2x.png",
      label: "会议列表",
    },
    {
      icon: "/static/images/icon/huiyishenqing@2x.png",
      label: "会议申请",
    },
    {
      icon: "/static/images/icon/huiyishenpi@2x.png",
      label: "会议审批",
    },
    {
      icon: "/static/images/icon/huiyifabu@2x.png",
      label: "会议发布",
    },
    {
      icon: "/static/images/icon/huiyizongjie@2x.png",
      label: "会议总结",
    },
    {
      icon: "/static/images/icon/huiyikanban@2x.png",
      label: "会议看板",
    },
  ]);
  const commonItems = ref([]);
  // å¤„理常用功能点击
  const handleCommonItemClick = item => {
    // æ ¹æ®ä¸åŒçš„功能项进行跳转
    switch (item.label) {
      case "销售台账":
        uni.navigateTo({
          url: "/pages/sales/salesAccount/index",
        });
        break;
      case "开票登记":
        uni.navigateTo({
          url: "/pages/sales/invoicingRegistration/index",
        });
        break;
      case "开票台账":
        uni.navigateTo({
          url: "/pages/sales/invoiceLedger/index",
        });
        break;
      case "回款登记":
        uni.navigateTo({
          url: "/pages/sales/receiptPayment/index",
        });
        break;
      case "回款流水":
        uni.navigateTo({
          url: "/pages/sales/receiptPaymentHistory/index",
        });
        break;
      case "客户往来":
        uni.navigateTo({
          url: "/pages/sales/receiptPaymentLedger/index",
        });
        break;
      case "采购台账":
        uni.navigateTo({
          url: "/pages/procurementManagement/procurementLedger/index",
        });
        break;
      case "来票登记":
        uni.navigateTo({
          url: "/pages/procurementManagement/invoiceEntry/index",
        });
        break;
      case "来票台账":
        uni.navigateTo({
          url: "/pages/procurementManagement/procurementInvoiceLedger/index",
        });
        break;
      case "付款登记":
        uni.navigateTo({
          url: "/pages/procurementManagement/paymentEntry/index",
        });
        break;
      case "付款流水":
        uni.navigateTo({
          url: "/pages/procurementManagement/receiptPaymentHistory/index",
        });
        break;
      case "供应商往来":
        uni.navigateTo({
          url: "/pages/procurementManagement/paymentLedger/index",
        });
        break;
      case "公出管理":
        uni.navigateTo({
          url: "/pages/cooperativeOffice/collaborativeApproval/index1",
        });
        break;
      case "请假管理":
        uni.navigateTo({
          url: "/pages/cooperativeOffice/collaborativeApproval/index2",
        });
        break;
      case "出差管理":
        uni.navigateTo({
          url: "/pages/cooperativeOffice/collaborativeApproval/index3",
        });
        break;
      case "报销管理":
        uni.navigateTo({
          url: "/pages/cooperativeOffice/collaborativeApproval/index4",
        });
        break;
      case "采购审批":
        uni.navigateTo({
          url: "/pages/cooperativeOffice/collaborativeApproval/index5",
        });
        break;
      case "报价审批":
        uni.navigateTo({
          url: "/pages/cooperativeOffice/collaborativeApproval/index6",
        });
        break;
      case "发货审批":
        uni.navigateTo({
          url: "/pages/cooperativeOffice/collaborativeApproval/index7",
        });
        break;
      case "会议设置":
        uni.navigateTo({
          url: "/pages/managementMeetings/meetingSettings/index",
        });
        break;
      case "会议列表":
        uni.navigateTo({
          url: "/pages/managementMeetings/meetingList/index",
        });
        break;
      case "会议申请":
        uni.navigateTo({
          url: "/pages/managementMeetings/meetApplication/index",
        });
        break;
      case "会议审批":
        uni.navigateTo({
          url: "/pages/managementMeetings/meetExamine/index",
        });
        break;
      case "会议发布":
        uni.navigateTo({
          url: "/pages/managementMeetings/meetPublish/index",
        });
        break;
      case "会议总结":
        uni.navigateTo({
          url: "/pages/managementMeetings/meetSummary/index",
        });
        break;
      case "会议看板":
        uni.navigateTo({
          url: "/pages/managementMeetings/meetingBoard/index",
        });
        break;
      case "通知公告":
        uni.navigateTo({
          url: "/pages/cooperativeOffice/noticeManagement/index",
        });
        break;
      case "知识库":
        uni.navigateTo({
          url: "/pages/managementMeetings/knowledgeBase/index",
        });
        break;
      case "用印管理":
        uni.navigateTo({
          url: "/pages/managementMeetings/sealManagement/index",
        });
        break;
      case "规章制度":
        uni.navigateTo({
          url: "/pages/managementMeetings/rulesRegulationsManagement/index",
        });
        break;
      case "协同审批":
        uni.navigateTo({
          url: "/pages/cooperativeOffice/collaborativeApproval/index",
        });
        break;
      case "客户拜访":
        uni.navigateTo({
          url: "/pages/cooperativeOffice/clientVisit/index",
        });
        break;
      case "生产订单":
        uni.navigateTo({
          url: "/pages/productionManagement/productionOrder/index",
        });
        break;
      case "生产派工":
        uni.navigateTo({
          url: "/pages/productionManagement/productionDispatching/index",
        });
        break;
      case "工序排产":
        uni.navigateTo({
          url: "/pages/productionManagement/processScheduling/index",
        });
        break;
      case "生产核算":
        uni.navigateTo({
          url: "/pages/productionManagement/productionAccounting/index",
        });
        break;
      case "设备台账":
        uni.navigateTo({
          url: "/pages/equipmentManagement/ledger/index",
        });
        break;
      case "设备报修":
        uni.navigateTo({
          url: "/pages/equipmentManagement/repair/index",
        });
        break;
      case "设备保养":
        uni.navigateTo({
          url: "/pages/equipmentManagement/upkeep/index",
        });
        break;
      case "设备巡检":
        uni.navigateTo({
          url: "/pages/inspectionUpload/index",
        });
        break;
      case "分析追溯":
        uni.navigateTo({
          url: "/pages/equipmentManagement/faultAnalysis/index",
        });
        break;
      case "智能派单":
        uni.navigateTo({
          url: "/pages/equipmentManagement/smartDispatch/index",
        });
        break;
      case "作业指导":
        uni.navigateTo({
          url: "/pages/equipmentManagement/sop/index",
        });
        break;
      case "结果验证":
        uni.navigateTo({
          url: "/pages/equipmentManagement/verification/index",
        });
        break;
      default:
        uni.showToast({
          title: `点击了${item.label}`,
          icon: "none",
        });
    }
  };
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  onMounted(() => {
    // è®¾ç½®ç”¨æˆ·ä¿¡æ¯
    userStore.getInfo();
  });
  const operationType = ref("");
  onLoad(options => {
    if (options.label) {
      // å¤„理 URL å‚数可能包含其他查询参数的情况(如:协同审批?approveType=6)
      // åªæå– label å‚数的值,去除可能附加的查询参数
      let labelValue = options.label;
      // å¦‚æžœ label åŒ…含 ? ç¬¦å·ï¼Œåªå– ? ä¹‹å‰çš„部分
      if (labelValue.includes("?")) {
        labelValue = labelValue.split("?")[0];
      }
      operationType.value = labelValue;
      if (operationType.value === "考勤管理") {
        commonItems.value = kaoqin;
      } else if (operationType.value === "会议管理") {
        commonItems.value = huiyi;
      } else if (operationType.value === "财务管理") {
        commonItems.value = caiwu;
      } else if (operationType.value === "协同审批") {
        commonItems.value = kaoqinCaiwu;
        operationType.value = "协同审批";
      }
    }
    console.log(operationType.value);
    console.log(commonItems.value);
  });
</script>
<style scoped lang="scss">
  .content {
    background: linear-gradient(135deg, #f8f9fa 0%, #e3f2fd 100%);
    min-height: 100vh;
    padding: 1.25rem;
    /* ä¸ºæ‰€æœ‰è®¾å¤‡è®¾ç½®åŸºç¡€padding-top */
    padding-top: 40px;
    position: relative;
    /* iOS设备使用env()函数处理安全区域 */
    padding-top: env(safe-area-inset-top);
    /* ä¸ºå®‰å“设备设置更大的顶部内边距 */
    /* #ifdef APP-PLUS && !MP && !H5 */
    padding-top: 45px;
    /* #endif */
    /* H5和小程序平台的通用样式 */
    /* #ifdef H5 || MP */
    padding-top: 30px;
    /* #endif */
    &::before {
      content: "";
      position: fixed;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="dots" width="20" height="20" patternUnits="userSpaceOnUse"><circle cx="10" cy="10" r="1" fill="rgba(41, 121, 255, 0.03)"/></pattern></defs><rect width="100" height="100" fill="url(%23dots)"/></svg>');
      pointer-events: none;
      z-index: -1;
    }
    &::after {
      content: "";
      position: fixed;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background: radial-gradient(
          circle at 20% 80%,
          rgba(41, 121, 255, 0.02) 0%,
          transparent 50%
        ),
        radial-gradient(
          circle at 80% 20%,
          rgba(156, 39, 176, 0.02) 0%,
          transparent 50%
        );
      pointer-events: none;
      z-index: -1;
    }
  }
  .header-section {
    margin-bottom: 1rem;
    animation: fadeInDown 0.6s ease-out;
    /* ä¸ºå®‰å“设备额外调整头部位置 */
    /* #ifdef APP-PLUS && !MP && !H5 */
    margin-top: 10px;
    /* #endif */
  }
  .currentFactory {
    margin-top: 0.5rem;
    margin-left: 0.25rem;
    font-weight: 500;
    display: flex;
  }
  .factoryName {
    width: auto;
  }
  :deep(.u-text) {
    align-items: center;
  }
  .hero-section {
    margin-bottom: 1rem;
    animation: fadeInUp 0.6s ease-out 0.1s both;
  }
  .bg-img {
    width: 100%;
    height: 8.75rem;
    background-image: url("../static/images/banner/backview.png");
    background-size: cover;
    border-radius: 0.75rem;
    position: relative;
    overflow: hidden;
    box-shadow: 0 0.25rem 1.25rem rgba(41, 121, 255, 0.15);
    &::before {
      content: "";
      position: absolute;
      top: -50%;
      left: -50%;
      width: 200%;
      height: 200%;
      background: conic-gradient(
        from 0deg,
        transparent,
        rgba(255, 255, 255, 0.1),
        transparent,
        rgba(255, 255, 255, 0.05),
        transparent
      );
      animation: rotate 20s linear infinite;
    }
    &::after {
      content: "";
      position: absolute;
      top: 0;
      right: 0;
      width: 7.5rem;
      height: 7.5rem;
      background: radial-gradient(
        circle,
        rgba(255, 255, 255, 0.15) 0%,
        transparent 70%
      );
      border-radius: 50%;
      transform: translate(2.5rem, -2.5rem);
    }
  }
  .hero-content {
    position: relative;
    z-index: 1;
    padding: 1.25rem 1.25rem 1.6rem 1.25rem;
    height: 100%;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-start;
  }
  .hero-title {
    color: #ffffff;
    font-size: 1.625rem;
    font-weight: 700;
    letter-spacing: 0.03125rem;
    text-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.5);
  }
  .hero-subtitle {
    font-size: 0.8125rem;
    margin-top: 0.375rem;
  }
  .hero-wave {
    height: 2.75rem;
  }
  .hero-subtitle {
    color: rgba(255, 255, 255, 0.9);
    font-size: 0.8125rem;
    margin-top: 0.375rem;
    font-weight: 400;
    text-shadow: 0 0.0625rem 0.125rem rgba(0, 0, 0, 0.5);
  }
  .hero-wave {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 2.75rem;
    background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1440 320' preserveAspectRatio='none'><path fill='%23ffffff' fill-opacity='0.2' d='M0,224L48,218.7C96,213,192,203,288,197.3C384,192,480,192,576,176C672,160,768,128,864,122.7C960,117,1056,139,1152,144C1248,149,1344,139,1392,133.3L1440,128L1440,320L1392,320C1344,320,1248,320,1152,320C1056,320,960,320,864,320C768,320,672,320,576,320C480,320,384,320,288,320C192,320,96,320,48,320L0,320Z'></path></svg>")
      no-repeat bottom center/cover;
    pointer-events: none;
  }
  .notice-section {
    margin-bottom: 1rem;
    animation: fadeInUp 0.6s ease-out 0.2s both;
  }
  .notice {
    width: 100%;
    background: linear-gradient(135deg, #eaf2ff 0%, #bbdefb 100%);
    border: 0.0625rem solid #e3f2fd;
    border-radius: 0.75rem;
    padding: 1rem;
    box-shadow: 0 0.25rem 1.25rem rgba(41, 121, 255, 0.08);
    position: relative;
    overflow: hidden;
    &::before {
      content: "";
      position: absolute;
      top: -50%;
      left: -50%;
      width: 200%;
      height: 200%;
      background: linear-gradient(
        45deg,
        transparent,
        rgba(255, 255, 255, 0.6),
        transparent
      );
      animation: shine 4s infinite;
    }
    &::after {
      content: "";
      position: absolute;
      top: 0;
      right: 0;
      width: 5rem;
      height: 5rem;
      background: radial-gradient(
        circle,
        rgba(255, 255, 255, 0.2) 0%,
        transparent 70%
      );
      border-radius: 50%;
      transform: translate(1.875rem, -1.875rem);
    }
    &:hover {
      transform: translateY(-0.125rem);
      box-shadow: 0 0.5rem 1.875rem rgba(0, 0, 0, 0.1);
    }
  }
  @keyframes shine {
    0% {
      transform: translateX(-100%) translateY(-100%) rotate(45deg);
    }
    100% {
      transform: translateX(100%) translateY(100%) rotate(45deg);
    }
  }
  @keyframes fadeInDown {
    from {
      opacity: 0;
      transform: translateY(-1.25rem);
    }
    to {
      opacity: 1;
      transform: translateY(0);
    }
  }
  @keyframes fadeInUp {
    from {
      opacity: 0;
      transform: translateY(1.25rem);
    }
    to {
      opacity: 1;
      transform: translateY(0);
    }
  }
  @keyframes fadeInScale {
    0% {
      opacity: 0;
      transform: translateY(0.5rem) scale(0.96);
    }
    100% {
      opacity: 1;
      transform: translateY(0) scale(1);
    }
  }
  .notice-content {
    display: flex;
    align-items: center;
    height: 100%;
    position: relative;
    z-index: 1;
  }
  .notice-left {
    margin-right: 1rem;
  }
  .notice-status {
    font-weight: 600;
    font-size: 1rem;
    color: #1976d2;
  }
  .notice-separator {
    width: 0.0625rem;
    height: 1.5rem;
    background: #e0e0e0;
    margin-right: 1rem;
  }
  .notice-right {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex: 1;
  }
  .notice-label {
    color: #333;
    font-size: 0.875rem;
    font-weight: 500;
    margin-right: 0.75rem;
  }
  .notice-text {
    font-weight: 400;
    font-size: 0.875rem;
    color: #666666;
  }
  .notice-number {
    font-weight: 600;
    font-size: 1rem;
    color: #1976d2;
    margin-left: 0.25rem;
  }
  .notice-unit {
    font-weight: 600;
    font-size: 1rem;
    color: #1976d2;
    margin-left: 0.25rem;
  }
  /* åŠŸèƒ½æ¨¡å—æ ·å¼ */
  .common-module {
    margin-bottom: 1.5rem;
    background: linear-gradient(135deg, #ffffff 0%, #fafbfc 100%);
    border-radius: 1rem;
    padding: 1rem;
    box-shadow: 0 0.25rem 1.25rem rgba(0, 0, 0, 0.06);
    border: none;
    position: relative;
    overflow: hidden;
    transition: all 0.3s ease;
    &::after {
      content: "";
      position: absolute;
      top: 0;
      right: 0;
      width: 3.75rem;
      height: 3.75rem;
      background: radial-gradient(
        circle,
        rgba(0, 0, 0, 0.02) 0%,
        transparent 70%
      );
      border-radius: 50%;
      transform: translate(1.875rem, -1.875rem);
    }
  }
  .marketing-module {
    --module-color: #2979ff;
  }
  .purchase-module {
    --module-color: #1976d2;
  }
  .collaboration-module {
    --module-color: #4caf50;
  }
  .production-module {
    --module-color: #ff9800;
  }
  .equipment-module {
    --module-color: #9c27b0;
  }
  .module-header {
    margin-bottom: 1.5rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
  .module-title-container {
    display: flex;
    align-items: center;
  }
  .module-title {
    color: #333333;
    font-size: 1.125rem;
    font-weight: 600;
    position: relative;
  }
  .module-subtitle {
    color: #666666;
    font-size: 0.75rem;
    font-weight: 400;
    margin-left: 0.5rem;
  }
  .module-content {
    width: 100%;
    display: grid;
    gap: 1rem;
  }
  .icon-container {
    width: 3.25rem;
    height: 3.25rem;
    border-radius: 0.75rem;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 0.375rem;
    box-shadow: 0 0.1875rem 0.75rem rgba(0, 0, 0, 0.12);
    transition: all 0.2s ease;
    position: relative;
    overflow: hidden;
    animation: fadeInScale 0.5s ease both;
    &::before {
      content: "";
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background: linear-gradient(
        135deg,
        rgba(255, 255, 255, 0.1) 0%,
        transparent 50%,
        rgba(255, 255, 255, 0.05) 100%
      );
      opacity: 0;
      transition: opacity 0.3s ease;
    }
    &::after {
      content: "";
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      border-radius: 0.75rem;
      background: linear-gradient(
        45deg,
        transparent,
        rgba(255, 255, 255, 0.2),
        transparent
      );
      opacity: 0;
      transition: opacity 0.3s ease;
    }
    &:hover {
      transform: translateY(-0.1875rem) scale(1.02);
      box-shadow: 0 0.5rem 1.5625rem rgba(0, 0, 0, 0.18);
      &::before,
      &::after {
        opacity: 1;
      }
    }
    &:active {
      transform: scale(0.97);
      box-shadow: 0 0.125rem 0.5rem rgba(0, 0, 0, 0.18);
    }
  }
  .item-label {
    font-size: 0.8125rem;
    color: #555555;
    text-align: center;
    display: block;
    line-height: 1.4;
    font-weight: 500;
    margin-top: 0.25rem;
    margin-bottom: 0.625rem;
  }
  .grid-text {
    font-size: 0.875rem;
    color: #909399;
    padding: 0.625rem 0 1.25rem 0;
    /* #ifndef APP-PLUS */
    box-sizing: border-box;
    /* #endif */
  }
  /* æš—色模式适配 */
  @media (prefers-color-scheme: dark) {
    .content {
      background: linear-gradient(135deg, #121317 0%, #161a20 100%);
    }
    .content::before {
      background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="dots" width="20" height="20" patternUnits="userSpaceOnUse"><circle cx="10" cy="10" r="1" fill="rgba(255, 255, 255, 0.05)"/></pattern></defs><rect width="100" height="100" fill="url(%23dots)"/></svg>');
    }
    .common-module {
      background: linear-gradient(135deg, #1e1f24 0%, #23252b 100%);
      box-shadow: 0 0.375rem 1.5rem rgba(0, 0, 0, 0.35);
    }
    .module-title {
      color: #e9edf3;
    }
    .module-subtitle,
    .item-label,
    .notice-text,
    .notice-unit,
    .notice-label {
      color: #c7cbd3;
    }
    .notice {
      background: linear-gradient(135deg, #1b2330 0%, #1a2432 100%);
      border-color: rgba(255, 255, 255, 0.06);
      box-shadow: 0 0.375rem 1.25rem rgba(0, 0, 0, 0.4);
    }
    .notice-status,
    .notice-number {
      color: #8ab4ff;
    }
    .bg-img {
      background: linear-gradient(135deg, #1f4fb9 0%, #0e3a8a 100%);
    }
  }
  @keyframes rotate {
    from {
      transform: rotate(0deg);
    }
    to {
      transform: rotate(360deg);
    }
  }
  @keyframes fadeInDown {
    from {
      opacity: 0;
      transform: translateY(-1.25rem);
    }
    to {
      opacity: 1;
      transform: translateY(0);
    }
  }
  @keyframes fadeInUp {
    from {
      opacity: 0;
      transform: translateY(1.25rem);
    }
    to {
      opacity: 1;
      transform: translateY(0);
    }
  }
  @keyframes fadeInScale {
    0% {
      opacity: 0;
      transform: translateY(0.5rem) scale(0.96);
    }
    100% {
      opacity: 1;
      transform: translateY(0) scale(1);
    }
  }
  .notice-left {
    margin-right: 1rem;
  }
  .notice-status {
    font-size: 1rem;
  }
  .notice-separator {
    width: 0.0625rem;
    height: 1.5rem;
    margin-right: 1rem;
  }
  .notice-label {
    font-size: 0.875rem;
    margin-right: 0.75rem;
  }
  .notice-text {
    font-size: 0.875rem;
  }
  .notice-number {
    font-size: 1rem;
    margin-left: 0.25rem;
  }
  .notice-unit {
    font-size: 0.875rem;
    margin-left: 0.125rem;
  }
  .common-module {
    margin-bottom: 1.5rem;
    background: linear-gradient(135deg, #ffffff 0%, #fafbfc 100%);
    border-radius: 1rem;
    padding: 1rem;
    box-shadow: 0 0.25rem 1.25rem rgba(0, 0, 0, 0.06);
    border: none;
    position: relative;
    overflow: hidden;
    transition: all 0.3s ease;
    &::after {
      content: "";
      position: absolute;
      top: 0;
      right: 0;
      width: 3.75rem;
      height: 3.75rem;
      background: radial-gradient(
        circle,
        rgba(0, 0, 0, 0.02) 0%,
        transparent 70%
      );
      border-radius: 50%;
      transform: translate(1.875rem, -1.875rem);
    }
  }
  .marketing-module {
    --module-color: #2979ff;
  }
  .purchase-module {
    --module-color: #1976d2;
  }
  .collaboration-module {
    --module-color: #4caf50;
  }
  .production-module {
    --module-color: #ff9800;
  }
  .equipment-module {
    --module-color: #9c27b0;
  }
  .module-header {
    margin-bottom: 1.5rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
  .module-title-container {
    display: flex;
    align-items: center;
  }
  .module-title {
    color: #333333;
    font-size: 1.125rem;
    font-weight: 600;
    position: relative;
  }
  .module-subtitle {
    color: #666666;
    font-size: 0.75rem;
    font-weight: 400;
    margin-left: 0.5rem;
  }
  .module-content {
    width: 100%;
    display: grid;
    gap: 1rem;
  }
  .icon-container {
    width: 3.25rem;
    height: 3.25rem;
    border-radius: 0.75rem;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 0.375rem;
    box-shadow: 0 0.1875rem 0.75rem rgba(0, 0, 0, 0.12);
    transition: all 0.2s ease;
    position: relative;
    overflow: hidden;
    animation: fadeInScale 0.5s ease both;
    &:hover {
      transform: translateY(-0.1875rem) scale(1.02);
      box-shadow: 0 0.5rem 1.5625rem rgba(0, 0, 0, 0.18);
    }
    &:active {
      transform: scale(0.97);
      box-shadow: 0 0.125rem 0.5rem rgba(0, 0, 0, 0.18);
    }
  }
  .item-label {
    font-size: 0.8125rem;
    margin-top: 0.25rem;
    margin-bottom: 0.625rem;
  }
  .grid-text {
    font-size: 0.875rem;
  }
  @media (prefers-color-scheme: dark) {
    .common-module {
      box-shadow: 0 0.375rem 1.5rem rgba(0, 0, 0, 0.35);
    }
    .notice {
      box-shadow: 0 0.375rem 1.25rem rgba(0, 0, 0, 0.4);
    }
  }
</style>
src/pages/managementMeetings/knowledgeBase/detail.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,500 @@
<template>
  <view class="client-visit-detail">
    <PageHeader :title="detailType === 1 ? '新增知识库' : '知识库详情'"
                @back="goBack" />
    <u-form ref="formRef"
            label-width="90">
      <!-- å®¢æˆ·ä¿¡æ¯ -->
      <!-- <u-cell-group title="知识信息"> -->
      <u-form-item label="知识标题"
                   prop="title"
                   required
                   border-bottom>
        <u-input v-model="form.title"
                 :readonly="readonly"
                 placeholder="请输入知识标题" />
      </u-form-item>
      <u-form-item label="知识类型"
                   prop="type"
                   required
                   border-bottom>
        <u-input v-model="equipmentname"
                 readonly
                 placeholder="请选择知识类型"
                 @click="showEquipmentSheet = true" />
        <template v-if="!readonly"
                  #right>
          <up-icon name="arrow-right"
                   @click="openEquipmentSheet"></up-icon>
        </template>
      </u-form-item>
      <u-form-item label="适用场景"
                   prop="scenario"
                   border-bottom>
        <u-input v-model="form.scenario"
                 :readonly="readonly"
                 placeholder="请输入适用场景" />
      </u-form-item>
      <u-form-item label="解决效率"
                   prop="status"
                   border-bottom>
        <u-input v-model="statusname"
                 readonly
                 placeholder="请选择解决效率"
                 @click="showStatusSheet = true" />
        <template v-if="!readonly"
                  #right>
          <up-icon name="arrow-right"
                   @click="showStatusSheet = true"></up-icon>
        </template>
      </u-form-item>
      <u-form-item label="问题描述"
                   required
                   prop="remark"
                   border-bottom>
        <u-textarea v-model="form.problem"
                    type="textarea"
                    rows="4"
                    :disabled="readonly"
                    placeholder="请输入问题描述" />
      </u-form-item>
      <u-form-item label="解决方案"
                   prop="solution"
                   required
                   border-bottom>
        <u-textarea v-model="form.solution"
                    type="textarea"
                    rows="4"
                    :disabled="readonly"
                    placeholder="请输入解决方案" />
      </u-form-item>
      <u-form-item label="关键要点"
                   prop="keyPoints"
                   border-bottom>
        <u-textarea v-model="form.keyPoints"
                    type="textarea"
                    rows="4"
                    :disabled="readonly"
                    placeholder="请输入关键要点,用逗号分隔" />
      </u-form-item>
      <u-form-item label="创建人"
                   prop="creator"
                   border-bottom>
        <u-input v-model="form.creator"
                 readonly
                 placeholder="请选择创建人"
                 @click="openCreatorSheet" />
        <template v-if="!readonly"
                  #right>
          <up-icon name="arrow-right"
                   @click="openCreatorSheet"></up-icon>
        </template>
        <!-- <u-input v-model="form.creator"
                 :readonly="readonly"
                 placeholder="请输入创建人" /> -->
      </u-form-item>
      <u-form-item label="使用次数"
                   prop="usageCount"
                   border-bottom>
        <uni-number-box v-model="form.usageCount"
                        :min="0"
                        :disabled="readonly"
                        placeholder="请输入使用次数" />
      </u-form-item>
      <!-- </u-cell-group> -->
      <!-- æäº¤æŒ‰é’® -->
      <view v-if="!readonly"
            class="footer-btns">
        <u-button class="cancel-btn"
                  @click="goBack">取消</u-button>
        <u-button class="sign-btn"
                  type="primary"
                  @click="handleSubmit"
                  :loading="loading">保存</u-button>
      </view>
    </u-form>
    <!-- è®¾å¤‡é…ç½®é€‰æ‹©å™¨ -->
    <up-action-sheet :show="showEquipmentSheet"
                     :actions="equipmentOptions"
                     @select="handleEquipmentChange"
                     @close="showEquipmentSheet = false" />
    <up-action-sheet :show="showCreatorSheet"
                     :actions="creatorOptions"
                     @select="handleCreatorChange"
                     @close="showCreatorSheet = false" />
    <!-- <u-popup :show="showEquipmentSheet"
             mode="bottom"
             @close="showEquipmentSheet = false"
             height="200px">
      <view class="popup-content">
        <view class="popup-body">
          <u-checkbox-group v-model="form.equipment"
                            @change="handleEquipmentChange"
                            icon-placement="right"
                            placement="row">
            <view style="width:100%;padding:10px;margin-top:20px;">
              <u-checkbox v-for="option in equipmentOptions"
                          :key="option.value"
                          :name="option.value"
                          :label="option.name"
                          class="checkbox-item"></u-checkbox>
            </view>
          </u-checkbox-group>
        </view>
      </view>
    </u-popup> -->
    <!-- çŠ¶æ€é€‰æ‹©å™¨ -->
    <up-action-sheet :show="showStatusSheet"
                     :actions="statusOptions"
                     @select="onStatusSelect"
                     @close="showStatusSheet = false" />
  </view>
</template>
<script setup>
  // æ›¿æ¢ toast æ–¹æ³•
  defineOptions({ name: "meeting-settings-detail" });
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
  import { ref, onMounted, computed } from "vue";
  import PageHeader from "@/components/PageHeader.vue";
  import useUserStore from "@/store/modules/user";
  import { useDict } from "@/utils/dict";
  import { onLoad } from "@dcloudio/uni-app";
  import {
    addKnowledgeBase,
    updateKnowledgeBase,
  } from "@/api/managementMeetings/knowledgeBase";
  import { userListNoPageByTenantId } from "@/api/system/user";
  const userStore = useUserStore();
  // è¡¨å•数据
  const form = ref({
    title: "",
    type: "",
    scenario: "",
    efficiency: "",
    problem: "",
    solution: "",
    keyPoints: "",
    creator: "",
    usageCount: 0,
  });
  const { knowledge_type } = useDict("knowledge_type");
  const knowledgeTypeOptions = computed(() => knowledge_type?.value || []);
  const equipmentOptions = ref([]);
  const statusOptions = ref([
    { value: "high", name: "显著提升" },
    { value: "medium", name: "一般提升" },
    { value: "low", name: "轻微提升" },
  ]);
  //// é¡µé¢çŠ¶æ€
  const loading = ref(false);
  const formRef = ref(null);
  const showEquipmentSheet = ref(false);
  const showStatusSheet = ref(false);
  const openEquipmentSheet = () => {
    showEquipmentSheet.value = true;
  };
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  const statusname = ref("");
  // çŠ¶æ€é€‰æ‹©
  const onStatusSelect = action => {
    form.value.efficiency = action.value;
    statusname.value = action.name;
    showStatusSheet.value = false;
  };
  const showCreatorSheet = ref(false);
  const creatorOptions = ref([]);
  const openCreatorSheet = () => {
    showCreatorSheet.value = true;
  };
  const getCreatorOptions = async () => {
    try {
      const res = await userListNoPageByTenantId();
      if (res.code === 200) {
        creatorOptions.value = res.data || [];
        creatorOptions.value.forEach(item => {
          item.name = item.nickName;
          item.value = item.userId;
        });
      } else {
        showToast("获取创建人列表失败");
      }
    } catch (e) {
      console.error("获取创建人列表失败:", e);
      showToast("获取创建人列表失败");
    }
  };
  // åˆ›å»ºäººé€‰æ‹©
  const handleCreatorChange = val => {
    form.value.creator = val.name;
  };
  const equipmentname = ref("");
  // è®¾å¤‡é…ç½®é€‰æ‹©
  const handleEquipmentChange = val => {
    form.value.type = val.value;
    equipmentname.value = val.name;
    showEquipmentSheet.value = false;
  };
  // æäº¤è¡¨å•
  const handleSubmit = async () => {
    if (!form.value.title) {
      showToast("请输入标题");
      return;
    }
    if (!form.value.scenario) {
      showToast("请输入适用场景");
      return;
    }
    if (!form.value.problem) {
      showToast("请输入问题描述");
      return;
    }
    if (!form.value.solution) {
      showToast("请输入解决方案");
      return;
    }
    try {
      loading.value = true;
      if (detailType.value === 1) {
        addKnowledgeBase(form.value).then(res => {
          if (res.code !== 200) {
            showToast("保存失败,请重试");
            return;
          }
          loading.value = false;
          showToast("保存成功");
          setTimeout(() => {
            goBack();
          }, 500);
        });
      } else if (detailType.value === 2) {
        updateKnowledgeBase(form.value).then(res => {
          if (res.code !== 200) {
            showToast("保存失败,请重试");
            return;
          }
          loading.value = false;
          showToast("保存成功");
          setTimeout(() => {
            goBack();
          }, 500);
        });
      }
    } catch (e) {
      loading.value = false;
      console.error("保存失败:", e);
      showToast("保存失败,请重试");
    }
  };
  // åˆå§‹åŒ–页面数据
  const initPageData = () => {
    // ä»Žæœ¬åœ°å­˜å‚¨ä¸­èŽ·å–ä¼šè®® room æ•°æ®
    const meetingRoom = uni.getStorageSync("meetingRoom");
    if (meetingRoom) {
      form.value = JSON.parse(JSON.stringify(meetingRoom));
      if (meetingRoom.equipment) {
        if (Array.isArray(meetingRoom.equipment)) {
          form.value.equipment = meetingRoom.equipment;
        } else {
          form.value.equipment = meetingRoom.equipment.split(",");
        }
      }
      statusname.value = meetingRoom.status === 1 ? "启用" : "禁用";
      // æ¸…除本地存储中的数据,避免下次打开时仍然显示
      uni.removeStorageSync("meetingRoom");
    }
  };
  const readonly = ref(false);
  const detailType = ref(1);
  const knowledgeId = ref("");
  onLoad(options => {
    detailType.value = Number(options.detailType);
    knowledgeId.value = options.id || "";
    // å¦‚果是编辑或查看模式,获取知识详情
    if (knowledgeId.value && (detailType.value === 2 || detailType.value === 3)) {
      // getKnowledgeDetail(knowledgeId.value);
      equipmentname.value =
        equipmentOptions.value.find(item => item.value === form.value.type)
          ?.name || "";
      statusname.value =
        statusOptions.value.find(item => item.value === form.value.efficiency)
          ?.name || "";
    }
    // æŸ¥çœ‹æ¨¡å¼è®¾ç½®åªè¯»
    if (detailType.value === 3) {
      readonly.value = true;
    }
  });
  onMounted(() => {
    getCreatorOptions();
    // ä»Žæœ¬åœ°å­˜å‚¨ä¸­èŽ·å–çŸ¥è¯†æ•°æ®
    const knowledgeBase = uni.getStorageSync("knowledgeBase");
    if (knowledgeBase) {
      form.value = JSON.parse(JSON.stringify(knowledgeBase));
    }
    initPageData();
    equipmentOptions.value = knowledgeTypeOptions.value.map(item => ({
      value: item.value,
      name: item.label,
    }));
    if (detailType.value === 1) {
      form.value = {
        title: "",
        type: "",
        scenario: "",
        efficiency: "",
        problem: "",
        solution: "",
        keyPoints: "",
        creator: "",
        usageCount: 0,
      };
      equipmentname.value = "";
      statusname.value = "";
    }
    if (detailType.value != 1) {
      equipmentname.value =
        equipmentOptions.value.find(item => item.value === form.value.type)
          ?.name || "";
      statusname.value =
        statusOptions.value.find(item => item.value === form.value.efficiency)
          ?.name || "";
    }
  });
</script>
<style scoped lang="scss">
  @import "@/static/scss/form-common.scss";
  .client-visit {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 5rem;
  }
  .footer-btns {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    background: #fff;
    display: flex;
    justify-content: space-around;
    align-items: center;
    padding: 0.75rem 0;
    box-shadow: 0 -0.125rem 0.5rem rgba(0, 0, 0, 0.05);
    z-index: 1000;
  }
  .cancel-btn {
    font-weight: 400;
    font-size: 1rem;
    color: #666;
    background: #f5f5f5;
    border: 1px solid #ddd;
    width: 45%;
    height: 2.5rem;
    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
  }
  .sign-btn {
    font-weight: 500;
    font-size: 1rem;
    color: #fff;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    border: none;
    width: 45%;
    height: 2.5rem;
    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
  }
  .location-icon {
    color: #1989fa;
    font-size: 1.2rem;
  }
  .selector-container {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    height: 100%;
  }
  .selector-text {
    font-size: 14px;
    color: #333;
  }
  .popup-content {
    padding: 20rpx;
  }
  .popup-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20rpx;
  }
  .popup-title {
    font-size: 16px;
    font-weight: bold;
    color: #333;
  }
  .close-icon {
    font-size: 20px;
    color: #999;
  }
  .popup-body {
    max-height: 60vh;
    overflow-y: auto;
    margin-bottom: 20rpx;
  }
  .checkbox-item {
    margin-bottom: 15rpx;
    font-size: 14px;
  }
  .popup-footer {
    display: flex;
    justify-content: space-between;
    gap: 15rpx;
  }
  .cancel-btn-popup {
    flex: 1;
    border-radius: 8rpx;
  }
  .confirm-btn-popup {
    flex: 1;
    border-radius: 8rpx;
  }
  .checkbox-item {
    margin-top: 40rpx;
  }
</style>
src/pages/managementMeetings/knowledgeBase/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,310 @@
<template>
  <view class="sales-accoun">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="知识库"
                @back="goBack" />
    <!-- æœç´¢å’Œç­›é€‰åŒºåŸŸ -->
    <view class="search-section">
      <view class="search-bar">
        <view class="search-input">
          <up-input class="search-text"
                    placeholder="请输入知识标题"
                    v-model="name"
                    @blur="getList"
                    clearable />
        </view>
        <view class="filter-button"
              @click="getList">
          <u-icon name="search"
                  size="24"
                  color="#999"></u-icon>
        </view>
      </view>
    </view>
    <!-- æ‹œè®¿è®°å½•列表 -->
    <view class="ledger-list"
          v-if="visitList.length > 0">
      <view v-for="(item, index) in visitList"
            :key="index">
        <view class="ledger-item">
          <view class="item-header">
            <view class="item-left">
              <view class="document-icon">
                <up-icon name="file-text"
                         size="16"
                         color="#ffffff"></up-icon>
              </view>
              <text class="item-id">知识标题:{{ item.title || '-' }}</text>
            </view>
          </view>
          <up-divider></up-divider>
          <view class="item-details">
            <view class="detail-row">
              <text class="detail-label">知识类型</text>
              <text class="detail-value">{{ formatReceiptType(item.type) }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">适用场景</text>
              <text class="detail-value">{{ item.scenario || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">解决效率</text>
              <u-tag size="mini"
                     :type="getTagClass(item.efficiency)">{{ formatReceiptType1(item.efficiency) }}</u-tag>
            </view>
            <view class="detail-row">
              <text class="detail-label">使用次数</text>
              <text class="detail-value">{{ item.usageCount }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">创建人</text>
              <text class="detail-value">{{ item.creator }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">创建时间</text>
              <text class="detail-value">{{ item.createTime }}</text>
            </view>
          </view>
          <!-- æŒ‰é’®åŒºåŸŸ -->
          <view class="action-buttons">
            <u-button type="info"
                      size="small"
                      class="action-btn"
                      @click="viewDetail(item,3)">
              æŸ¥çœ‹
            </u-button>
            <!-- <u-button type="error"
                      size="small"
                      class="action-btn"
                      @click="confirmDelete(item)">
              åˆ é™¤
            </u-button>
            <u-button type="primary"
                      size="small"
                      class="action-btn"
                      @click="viewDetail(item,2)">
              ç¼–辑
            </u-button> -->
          </view>
        </view>
      </view>
    </view>
    <view v-else
          class="no-data">
      <text>暂无知识记录</text>
    </view>
    <!-- æµ®åŠ¨æ–°å¢žæŒ‰é’® -->
    <!-- <view class="fab-button"
          @click="addVisit">
      <up-icon name="plus"
               size="24"
               color="#ffffff"></up-icon>
    </view> -->
  </view>
</template>
<script setup>
  import { ref, onMounted, computed } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import { useDict } from "@/utils/dict";
  import PageHeader from "@/components/PageHeader.vue";
  import {
    listKnowledgeBase,
    delKnowledgeBase,
  } from "@/api/managementMeetings/knowledgeBase";
  import useUserStore from "@/store/modules/user";
  // æ›¿æ¢ toast æ–¹æ³•
  defineOptions({ name: "client-visit-index" });
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
  import dayjs from "dayjs";
  const userStore = useUserStore();
  // æœç´¢å…³é”®è¯
  const name = ref("");
  // æ‹œè®¿è®°å½•数据
  const visitList = ref([]);
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  const { knowledge_type } = useDict("knowledge_type");
  // æ ¼å¼åŒ–回款方式
  const formatReceiptType = params => {
    return getKnowledgeTypeLabel(params);
  };
  const formatReceiptType1 = params => {
    if (params == "high") {
      return "显著提升";
    } else if (params == "medium") {
      return "一般提升";
    } else if (params == "low") {
      return "轻微提升";
    } else {
      return "未知";
    }
  };
  const getTagClass = type => {
    if (type == "high") {
      return "success";
    } else if (type == "medium") {
      return "warning";
    } else if (type == "low") {
      return "info";
    } else {
      return "info";
    }
  };
  const knowledgeTypeOptions = computed(() => knowledge_type?.value || []);
  // èŽ·å–çŸ¥è¯†ç±»åž‹æ ‡ç­¾
  const getKnowledgeTypeLabel = val => {
    console.log(knowledgeTypeOptions, "knowledgeTypeOptions");
    const item = knowledgeTypeOptions.value.find(
      i => String(i.value) === String(val)
    );
    return item ? item.label : val;
  };
  // æŸ¥è¯¢åˆ—表
  const getList = () => {
    showLoadingToast("加载中...");
    const params = {
      current: -1,
      size: -1,
      title: name.value,
    };
    listKnowledgeBase(params)
      .then(res => {
        visitList.value = res.data.records;
        closeToast();
      })
      .catch(() => {
        closeToast();
        showToast("获取数据失败");
      });
  };
  // æ˜¾ç¤ºåŠ è½½æç¤º
  const showLoadingToast = message => {
    uni.showLoading({
      title: message,
      mask: true,
    });
  };
  // å…³é—­æç¤º
  const closeToast = () => {
    uni.hideLoading();
  };
  // æ–°å¢žæ‹œè®¿ - è·³è½¬åˆ°ç™»è®°é¡µé¢
  const addVisit = () => {
    uni.navigateTo({
      url: "/pages/managementMeetings/knowledgeBase/detail?detailType=1",
    });
  };
  // ç¼–辑
  const viewDetail = (item, detailType) => {
    uni.setStorageSync("knowledgeBase", item);
    uni.navigateTo({
      url:
        "/pages/managementMeetings/knowledgeBase/detail?detailType=" +
        detailType +
        "&id=" +
        item.id,
    });
  };
  // åˆ é™¤ç¡®è®¤
  const confirmDelete = item => {
    uni.showModal({
      title: "删除确认",
      content: `确定要删除知识 "${item.title}" å—?`,
      success: res => {
        if (res.confirm) {
          deleteKnowledge(item.id);
        }
      },
    });
  };
  // æ‰§è¡Œåˆ é™¤
  const deleteKnowledge = id => {
    showLoadingToast("删除中...");
    delKnowledgeBase([id])
      .then(res => {
        closeToast();
        if (res.code === 200) {
          showToast("删除成功");
          getList(); // é‡æ–°èŽ·å–åˆ—è¡¨
        } else {
          showToast("删除失败");
        }
      })
      .catch(() => {
        closeToast();
        showToast("删除失败");
      });
  };
  onMounted(() => {
    getList();
  });
  onShow(() => {
    getList();
  });
</script>
<style scoped lang="scss">
  @import "../../../styles/sales-common.scss";
  // é¡µé¢ç‰¹å®šçš„æ ·å¼è¦†ç›–
  .sales-accoun {
    min-height: 100vh;
    background: #f8f9fa;
    position: relative;
    padding-bottom: 80px;
  }
  // ç‰¹å®šçš„图标样式
  .document-icon {
    background: #667eea; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„背景色
  }
  // ç‰¹æœ‰æ ·å¼
  .visit-status {
    display: flex;
    align-items: center;
  }
  .detail-value {
    word-break: break-all; // ä¿ç•™é¡µé¢ç‰¹æœ‰çš„æ–‡æœ¬æ¢è¡Œæ ·å¼
    color: #333; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„æ–‡æœ¬é¢œè‰²
  }
  // çŠ¶æ€æ ·å¼
  .status-enabled {
    color: #28a745; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„æˆåŠŸé¢œè‰²
  }
  .status-disabled {
    color: #dc3545; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„错误颜色
  }
  // ç‰¹å®šçš„æµ®åŠ¨æŒ‰é’®æ ·å¼
  .fab-button {
    background: #667eea; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„背景色
    box-shadow: 0 4px 16px rgba(102, 126, 234, 0.3); // ä¿æŒé¡µé¢ç‰¹æœ‰çš„阴影效果
  }
</style>
src/pages/managementMeetings/meetApplication/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,499 @@
<template>
  <view style="background-color: #fff;"
        class="client-visit-detail">
    <PageHeader title="会议申请"
                @back="goBack" />
    <view>
      <view v-for="item in applicationTypes"
            :key="item.value"
            class="application-type-item"
            :class="{ active: meetingForm.applicationType === item.value }"
            @click="selectApplicationType(item)">
        <view class="application-type-info">
          <view class="application-type-name">{{ item.name }}</view>
          <view class="application-type-desc">{{ item.desc }}</view>
        </view>
      </view>
    </view>
    <u-form ref="formRef"
            label-width="90">
      <!-- å®¢æˆ·ä¿¡æ¯ -->
      <u-cell-group title="会议申请">
        <u-form-item label="会议主题"
                     prop="title"
                     required
                     border-bottom>
          <u-input v-model="meetingForm.title"
                   placeholder="请输入会议主题" />
        </u-form-item>
        <u-form-item label="会议室"
                     prop="roomId"
                     required
                     border-bottom>
          <u-input v-model="meetingForm.roomName"
                   placeholder="请选择会议室"
                   readonly
                   @click="showRoomPicker = true" />
          <template #right>
            <up-icon name="arrow-right"
                     @click="showRoomPicker = true"></up-icon>
          </template>
        </u-form-item>
        <u-form-item label="主持人"
                     prop="host"
                     required
                     border-bottom>
          <u-input v-model="meetingForm.host"
                   placeholder="请输入主持人姓名" />
        </u-form-item>
        <u-form-item label="会议日期"
                     prop="meetingDate"
                     required
                     border-bottom>
          <u-input v-model="meetingForm.meetingDate"
                   placeholder="请选择会议日期"
                   readonly
                   @click="showDatePicker = true" />
          <template #right>
            <up-icon name="calendar"
                     @click="showDatePicker = true"></up-icon>
          </template>
        </u-form-item>
        <u-form-item label="开始时间"
                     prop="startTime"
                     required
                     border-bottom>
          <u-input v-model="meetingForm.startTime"
                   placeholder="请选择开始时间"
                   readonly
                   @click="showStartTimePicker = true" />
          <template #right>
            <up-icon name="clock"
                     @click="showStartTimePicker = true"></up-icon>
          </template>
        </u-form-item>
        <u-form-item label="结束时间"
                     prop="endTime"
                     required
                     border-bottom>
          <u-input v-model="meetingForm.endTime"
                   placeholder="请选择结束时间"
                   readonly
                   @click="showEndTimePicker = true" />
          <template #right>
            <up-icon name="clock"
                     @click="showEndTimePicker = true"></up-icon>
          </template>
        </u-form-item>
        <u-form-item label="参会人员"
                     prop="participants"
                     required
                     border-bottom>
          <u-input v-model="meetingForm.participantsNames"
                   placeholder="请选择参会人员"
                   readonly
                   @click="showEquipmentSheet = true" />
          <template #right>
            <up-icon name="arrow-right"
                     @click="openParticipantPicker"></up-icon>
          </template>
        </u-form-item>
        <u-form-item label="会议说明"
                     prop="description"
                     border-bottom>
          <u-input v-model="meetingForm.description"
                   placeholder="请输入会议说明" />
        </u-form-item>
      </u-cell-group>
      <!-- æäº¤æŒ‰é’® -->
    </u-form>
    <view class="footer-btns">
      <u-button class="cancel-btn"
                @click="resetForm">重置</u-button>
      <u-button class="save-btn"
                @click="handleSubmit">保存</u-button>
    </view>
    <!-- æ—¥æœŸé€‰æ‹©å™¨ -->
    <up-datetime-picker v-model="meetingForm.meetingDate"
                        mode="date"
                        :show="showDatePicker"
                        @confirm="onDateSelect"
                        @cancel="showDatePicker = false"
                        format="YYYY-MM-DD"
                        value-format="YYYY-MM-DD"
                        :min-date="minDate" />
    <!-- å¼€å§‹æ—¶é—´é€‰æ‹©å™¨ -->
    <up-action-sheet :show="showStartTimePicker"
                     :actions="timeOptions"
                     @select="onStartTimeSelect"
                     @close="showStartTimePicker = false" />
    <!-- ç»“束时间选择器 -->
    <up-action-sheet :show="showEndTimePicker"
                     :actions="timeOptions"
                     @select="onEndTimeSelect"
                     @close="showEndTimePicker = false" />
    <!-- ä¼šè®®å®¤é€‰æ‹©å™¨ -->
    <up-action-sheet :show="showRoomPicker"
                     :actions="meetingRooms"
                     @select="onRoomSelect"
                     @close="showRoomPicker = false" />
    <u-popup :show="showEquipmentSheet"
             mode="bottom"
             @close="showEquipmentSheet = false"
             height="200px">
      <view class="popup-content">
        <view class="popup-body">
          <u-checkbox-group v-model="meetingForm.participants"
                            @change="handleParticipantChange"
                            icon-placement="right"
                            placement="row">
            <view style="width:100%;padding:10px;margin-top:20px;">
              <u-checkbox v-for="option in employees"
                          :key="option.id"
                          :name="option.id"
                          :label="`${option.staffName} (${option.postName})`"
                          class="checkbox-item"></u-checkbox>
            </view>
          </u-checkbox-group>
        </view>
      </view>
    </u-popup>
  </view>
</template>
<script setup>
  // æ›¿æ¢ toast æ–¹æ³•
  defineOptions({ name: "meeting-settings-detail" });
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
  import { ref, reactive, onMounted, computed } from "vue";
  import PageHeader from "@/components/PageHeader.vue";
  import useUserStore from "@/store/modules/user";
  import {
    saveMeetingApplication,
    getRoomEnum,
  } from "@/api/managementMeetings/meeting";
  import { staffOnJobListPage } from "@/api/personnelManagement/onboarding";
  import dayjs from "dayjs";
  const userStore = useUserStore();
  // è¡¨å•数据
  const meetingForm = reactive({
    title: "",
    type: "",
    roomId: "",
    roomName: "",
    host: "",
    meetingDate: "",
    startTime: "",
    endTime: "",
    participants: [],
    description: "",
    participantsNames: [],
    applicationType: "department",
  });
  // ç”³è¯·ç±»åž‹é€‰é¡¹
  const applicationTypes = ref([
    {
      value: "approval",
      name: "审批流程会议",
      desc: "需要经过多级审批的会议申请",
      // icon: Document,
    },
    {
      value: "department",
      name: "部门级会议",
      desc: "部门内部会议申请流程",
      // icon: Promotion,
    },
    {
      value: "notification",
      name: "会议通知",
      desc: "无需审批直接发布的会议通知",
      // icon: Bell,
    },
  ]);
  // é¡µé¢çŠ¶æ€
  const loading = ref(false);
  const formRef = ref(null);
  const showDatePicker = ref(false);
  const showStartTimePicker = ref(false);
  const showEndTimePicker = ref(false);
  const showRoomPicker = ref(false);
  // æœ€å°æ—¥æœŸï¼ˆä»Šå¤©ï¼‰
  const minDate = computed(() => {
    return dayjs().format("YYYY-MM-DD");
  });
  // é€‰æ‹©ç”³è¯·ç±»åž‹
  const selectApplicationType = item => {
    meetingForm.applicationType = item.value;
  };
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  const showEquipmentSheet = ref(false);
  const openParticipantPicker = () => {
    showEquipmentSheet.value = true;
  };
  // é‡ç½®è¡¨å•
  const resetForm = () => {
    meetingForm.title = "";
    meetingForm.type = "";
    meetingForm.roomId = "";
    meetingForm.roomName = "";
    meetingForm.host = "";
    meetingForm.meetingDate = "";
    meetingForm.startTime = "";
    meetingForm.endTime = "";
    meetingForm.participants = [];
    meetingForm.participantsNames = [];
    meetingForm.description = "";
    meetingForm.applicationType = "department";
  };
  const handleParticipantChange = val => {
    console.log("val", val);
    meetingForm.participants = val;
    meetingForm.participantsNames = employees.value
      .filter(employee => val.includes(employee.id))
      .map(employee => employee.staffName);
  };
  // æäº¤è¡¨å•
  const handleSubmit = async () => {
    console.log("meetingForm", meetingForm);
    if (!meetingForm.title) {
      showToast("请输入会议主题");
      return;
    }
    if (!meetingForm.roomId) {
      showToast("请选择会议室");
      return;
    }
    if (!meetingForm.host) {
      showToast("请输入主持人");
      return;
    }
    if (!meetingForm.meetingDate) {
      showToast("请选择会议日期");
      return;
    }
    if (!meetingForm.startTime) {
      showToast("请选择开始时间");
      return;
    }
    if (!meetingForm.endTime) {
      showToast("请选择结束时间");
      return;
    }
    if (!meetingForm.participants) {
      showToast("请选择参会人员");
      return;
    }
    // éªŒè¯å¼€å§‹æ—¶é—´å¿…须小于结束时间
    if (meetingForm.startTime >= meetingForm.endTime) {
      showToast("开始时间必须小于结束时间");
      return;
    }
    try {
      loading.value = true;
      let formData = { ...meetingForm };
      formData.startTime = `${meetingForm.meetingDate} ${meetingForm.startTime}:00`;
      formData.endTime = `${meetingForm.meetingDate} ${meetingForm.endTime}:00`;
      formData.participants = JSON.stringify(meetingForm.participants);
      console.log(formData);
      // è°ƒç”¨å®žé™…çš„API
      const res = await saveMeetingApplication(formData);
      if (res.code === 200) {
        showToast("保存成功");
        setTimeout(() => {
          goBack();
        }, 500);
      } else {
        showToast(res.message || "保存失败,请重试");
      }
    } catch (e) {
      console.error("保存失败:", e);
      showToast("保存失败,请重试");
    } finally {
      loading.value = false;
    }
  };
  // æ—¥æœŸé€‰æ‹©å™¨ç¡®è®¤å›žè°ƒ
  const onDateSelect = action => {
    console.log(action.value);
    if (action.value) {
      meetingForm.meetingDate = dayjs(action.value).format("YYYY-MM-DD");
    } else {
      meetingForm.meetingDate = dayjs().format("YYYY-MM-DD");
    }
    showDatePicker.value = false;
  };
  const onStartTimeSelect = action => {
    meetingForm.startTime = action.value;
    showStartTimePicker.value = false;
  };
  const onEndTimeSelect = action => {
    meetingForm.endTime = action.value;
    showEndTimePicker.value = false;
  };
  // ä¼šè®®å®¤é€‰æ‹©å›žè°ƒ
  const onRoomSelect = action => {
    meetingForm.roomId = action.id;
    meetingForm.roomName = action.name;
    showRoomPicker.value = false;
  };
  // æ—¶é—´é€‰é¡¹ï¼ˆä»¥åŠå°æ—¶ä¸ºé—´éš”)
  const timeOptions = ref([]);
  // åˆå§‹åŒ–时间选项
  const initTimeOptions = () => {
    const options = [];
    for (let hour = 8; hour <= 18; hour++) {
      // æ¯ä¸ªå°æ—¶æ·»åŠ ä¸¤ä¸ªé€‰é¡¹ï¼šæ•´ç‚¹å’ŒåŠç‚¹
      options.push({
        value: `${hour.toString().padStart(2, "0")}:00`,
        name: `${hour.toString().padStart(2, "0")}:00`,
      });
      if (hour < 18) {
        // 18:00之后没有半点选项
        options.push({
          value: `${hour.toString().padStart(2, "0")}:30`,
          name: `${hour.toString().padStart(2, "0")}:30`,
        });
      }
    }
    timeOptions.value = options;
  };
  // ä¼šè®®å®¤åˆ—表
  const employees = ref([]);
  const meetingRooms = ref([]);
  const getMeetingRooms = async () => {
    try {
      const res = await getRoomEnum();
      if (res.code === 200) {
        meetingRooms.value = res.data || [];
      } else {
        showToast(res.message || "获取会议室列表失败");
      }
    } catch (e) {
      console.error("获取会议室列表失败:", e);
      showToast("获取会议室列表失败,请重试");
    }
  };
  onMounted(() => {
    initPageData();
    initTimeOptions();
    getMeetingRooms();
    staffOnJobListPage().then(res => {
      console.log(res.data.records, "res.data.records");
      employees.value = res.data.records.sort((a, b) =>
        a.postName.localeCompare(b.postName)
      );
      console.log(employees.value, "employees.value");
    });
  });
  const initPageData = () => {
    // åˆå§‹åŒ–数据
  };
</script>
<style scoped lang="scss">
  @import "@/static/scss/form-common.scss";
  .footer-btns {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    background: #fff;
    display: flex;
    justify-content: space-around;
    align-items: center;
    padding: 0.75rem 0;
    box-shadow: 0 -0.125rem 0.5rem rgba(0, 0, 0, 0.05);
    z-index: 1000;
  }
  .cancel-btn {
    font-weight: 400;
    font-size: 1rem;
    color: #ffffff;
    width: 6.375rem;
    background: #c7c9cc;
    box-shadow: 0 0.25rem 0.625rem 0 rgba(3, 88, 185, 0.2);
    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
  }
  .save-btn {
    font-weight: 400;
    font-size: 1rem;
    color: #ffffff;
    width: 14rem;
    background: linear-gradient(140deg, #00baff 0%, #006cfb 100%);
    box-shadow: 0 0.25rem 0.625rem 0 rgba(3, 88, 185, 0.2);
    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
  }
  .application-type-item {
    width: 94%;
    margin-left: 3%;
    // height: 120rpx;
    background-color: #f1f1f1;
    border-radius: 10rpx;
    margin-top: 20rpx;
    padding: 20rpx;
    box-shadow: 0 2rpx 12rpx 0 rgba(246, 244, 244, 0.1);
    transition: box-shadow 0.3s ease;
  }
  .application-type-item.active {
    box-shadow: 0 4rpx 16rpx 0 rgba(0, 0, 0, 0.15);
    background-color: #fff;
    border: 1rpx solid #0078a3;
  }
  .application-type-name {
    font-weight: 400;
    font-size: 1rem;
    color: #000000;
  }
  .application-type-item.active .application-type-name {
    color: #0078a3;
  }
  .application-type-desc {
    font-weight: 400;
    font-size: 0.875rem;
    margin-top: 0.5rem;
    color: #757575;
  }
  .application-type-item.active .application-type-desc {
    color: #0078a3;
  }
</style>
src/pages/managementMeetings/meetExamine/approve.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,470 @@
<template>
  <view class="approve-page">
    <PageHeader title="审批"
                @back="goBack" />
    <!-- ç”³è¯·ä¿¡æ¯ -->
    <view class="application-info">
      <view class="info-header">
        <text class="info-title">会议信息</text>
      </view>
      <view class="info-content">
        <view class="info-row">
          <text class="info-label">会议主题</text>
          <text class="info-value">{{ approvalData.title }}</text>
        </view>
        <view class="info-row">
          <text class="info-label">申请人</text>
          <text class="info-value">{{ approvalData.applicant }}</text>
        </view>
        <view class="info-row">
          <text class="info-label">主理人</text>
          <text class="info-value">{{ approvalData.host }}</text>
        </view>
        <view class="info-row">
          <text class="info-label">会议时间</text>
          <text class="info-value">{{ formatDateTime(approvalData.meetingTime) }}</text>
        </view>
        <view class="info-row">
          <text class="info-label">会议地点</text>
          <text class="info-value">{{ approvalData.location }}</text>
        </view>
        <view class="info-row">
          <text class="info-label">审批状态</text>
          <text class="info-value tag"
                :class="getTagClass(approvalData.approveNodeStatus)">
            {{ formatReceiptType(approvalData.approveNodeStatus) }}
          </text>
        </view>
        <view class="info-row">
          <text class="info-label">参会人数</text>
          <text class="info-value">{{ approvalData.participants.length }}</text>
        </view>
        <view class="info-row">
          <text class="info-label">参会人员</text>
          <text class="info-value">{{ approvalData.participants.map(it => it.name).join("、") }}</text>
        </view>
      </view>
    </view>
    <!-- åº•部操作按钮 -->
    <view v-if="isEdit"
          class="footer-actions">
      <u-button class="reject-btn"
                @click="handleReject">不通过</u-button>
      <u-button class="approve-btn"
                @click="handleApprove">通过</u-button>
    </view>
  </view>
</template>
<script setup>
  import { ref, onMounted, computed } from "vue";
  import { onLoad } from "@dcloudio/uni-app";
  import { saveMeetingApplication } from "@/api/managementMeetings/meetExamine";
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
  import PageHeader from "@/components/PageHeader.vue";
  const approvalData = ref({});
  const approvalSteps = ref([]);
  const isEdit = ref(false);
  onLoad(options => {
    console.log(options, "options");
    if (options.item) {
      approvalData.value = JSON.parse(options.item);
    }
    if (options.edit) {
      isEdit.value = options.edit === "true" ? true : false;
    }
  });
  const goBack = () => {
    uni.removeStorageSync("approveId");
    uni.navigateBack();
  };
  const formatDateTime = dateTime => {
    if (!dateTime) return "";
    return dateTime.replace(" ", "\n");
  };
  // æ ¼å¼åŒ–回款方式
  const formatReceiptType = params => {
    if (params == 0) {
      return "待审核";
    } else if (params == 1) {
      return "已通过";
    } else if (params == 2) {
      return "未通过";
    } else if (params == 3) {
      return "已取消";
    } else {
      return "未知";
    }
  };
  // èŽ·å–æ ‡ç­¾æ ·å¼ç±»
  const getTagClass = type => {
    if (type == 0) {
      return "info";
    } else if (type == 1) {
      return "success";
    } else if (type == 2) {
      return "warning";
    } else if (type == 3) {
      return "danger";
    } else {
      return "info";
    }
  };
  const submitForm = status => {
    // è°ƒç”¨åŽç«¯
    saveMeetingApplication({ id: approvalData.value.id, status: status })
      .then(res => {
        if (res.code === 200) {
          showToast("审批提交成功");
          // æç¤ºåŽè¿”回上一个页面
          setTimeout(() => {
            goBack(); // å†…部是 uni.navigateBack()
          }, 800);
        } else {
          showToast(res.message || "审批操作失败,请重试");
        }
      })
      .catch(error => {
        console.error("审批操作失败:", error);
        showToast("审批操作失败,请重试");
      });
  };
  const handleApprove = () => {
    uni.showModal({
      title: "确认操作",
      content: "确定要通过该会议申请吗?",
      success: res => {
        if (res.confirm) submitForm(1);
      },
    });
  };
  const handleReject = () => {
    uni.showModal({
      title: "确认操作",
      content: "确定不通过该会议申请吗?",
      success: res => {
        if (res.confirm) submitForm(2);
      },
    });
  };
  // åŽŸå§‹èŠ‚ç‚¹æ•°æ®ï¼ˆç”¨äºŽæäº¤é€»è¾‘ï¼‰
  const activities = ref([]);
</script>
<style scoped lang="scss">
  .approve-page {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 80px;
  }
  .header {
    display: flex;
    align-items: center;
    background: #fff;
    padding: 16px 20px;
    border-bottom: 1px solid #f0f0f0;
    position: sticky;
    top: 0;
    z-index: 100;
  }
  .title {
    flex: 1;
    text-align: center;
    font-size: 18px;
    font-weight: 600;
    color: #333;
  }
  .application-info {
    background: #fff;
    margin: 16px;
    border-radius: 12px;
    overflow: hidden;
  }
  .info-header {
    padding: 16px;
    border-bottom: 1px solid #f0f0f0;
    background: #f8f9fa;
  }
  .info-title {
    font-size: 16px;
    font-weight: 600;
    color: #333;
  }
  .info-content {
    padding: 16px;
  }
  .info-row {
    display: flex;
    align-items: center;
    margin-bottom: 12px;
    &:last-child {
      margin-bottom: 0;
    }
  }
  .info-label {
    font-size: 14px;
    color: #666;
    width: 80px;
    flex-shrink: 0;
  }
  .info-value {
    font-size: 14px;
    color: #333;
    flex: 1;
  }
  .approval-process {
    background: #fff;
    margin: 16px;
    border-radius: 12px;
    overflow: hidden;
  }
  .process-header {
    padding: 16px;
    border-bottom: 1px solid #f0f0f0;
    background: #f8f9fa;
  }
  .process-title {
    font-size: 16px;
    font-weight: 600;
    color: #333;
  }
  .process-steps {
    padding: 20px;
  }
  .process-step {
    display: flex;
    position: relative;
    margin-bottom: 24px;
    &:last-child {
      margin-bottom: 0;
      .step-line {
        display: none;
      }
    }
  }
  .step-indicator {
    display: flex;
    flex-direction: column;
    align-items: center;
    margin-right: 16px;
  }
  .step-dot {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 14px;
    font-weight: 600;
    position: relative;
    z-index: 2;
  }
  .process-step.completed .step-dot {
    background: #52c41a;
    color: #fff;
  }
  .process-step.current .step-dot {
    background: #1890ff;
    color: #fff;
    animation: pulse 2s infinite;
  }
  .process-step.pending .step-dot {
    background: #d9d9d9;
    color: #999;
  }
  .step-line {
    width: 2px;
    height: 40px;
    background: #d9d9d9;
    margin-top: 8px;
  }
  .process-step.completed .step-line {
    background: #52c41a;
  }
  .process-step.rejected .step-dot {
    background: #ff4d4f;
    color: #fff;
  }
  .process-step.rejected .step-line {
    background: #ff4d4f;
  }
  .step-content {
    flex: 1;
    padding-top: 4px;
  }
  .step-info {
    margin-bottom: 8px;
  }
  .step-title {
    font-size: 16px;
    font-weight: 600;
    color: #333;
    display: block;
    margin-bottom: 4px;
  }
  .step-approver {
    font-size: 14px;
    color: #666;
    display: block;
    margin-bottom: 4px;
  }
  .step-time {
    font-size: 12px;
    color: #999;
    display: block;
  }
  .step-opinion {
    background: #f8f9fa;
    padding: 12px;
    border-radius: 8px;
    border-left: 4px solid #52c41a;
  }
  .opinion-label {
    font-size: 12px;
    color: #666;
    display: block;
    margin-bottom: 4px;
  }
  .opinion-content {
    font-size: 14px;
    color: #333;
    line-height: 1.5;
  }
  .approval-input {
    background: #fff;
    margin: 16px;
    border-radius: 12px;
    overflow: hidden;
  }
  .input-header {
    padding: 16px;
    border-bottom: 1px solid #f0f0f0;
    background: #f8f9fa;
  }
  .input-title {
    font-size: 16px;
    font-weight: 600;
    color: #333;
  }
  .input-content {
    padding: 16px;
  }
  .footer-actions {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    background: #fff;
    display: flex;
    justify-content: space-around;
    align-items: center;
    padding: 16px;
    box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
    z-index: 1000;
  }
  .reject-btn {
    width: 120px;
    background: #ff4d4f;
    color: #fff;
  }
  .approve-btn {
    width: 120px;
    background: #52c41a;
    color: #fff;
  }
  /* é€‚配u-button样式 */
  :deep(.u-button) {
    border-radius: 6px;
  }
  @keyframes pulse {
    0% {
      box-shadow: 0 0 0 0 rgba(24, 144, 255, 0.7);
    }
    70% {
      box-shadow: 0 0 0 10px rgba(24, 144, 255, 0);
    }
    100% {
      box-shadow: 0 0 0 0 rgba(24, 144, 255, 0);
    }
  }
  .signature-section {
    background: #fff;
    padding: 12px 16px 16px;
    border-top: 1px solid #f0f0f0;
  }
  .signature-header {
    margin-bottom: 8px;
  }
  .signature-title {
    font-size: 14px;
    font-weight: 600;
    color: #333;
  }
  .signature-box {
    width: 100%;
    height: 180px;
    background: #fff;
    border: 1px dashed #d9d9d9;
    border-radius: 8px;
    overflow: hidden;
  }
  .signature-actions {
    margin-top: 8px;
    display: flex;
    justify-content: flex-end;
  }
</style>
src/pages/managementMeetings/meetExamine/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,352 @@
// å®¡æ‰¹ç®¡ç†ä¸»é¡µé¢
<template>
  <view class="sales-account">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="会议审批"
                @back="goBack" />
    <!-- æœç´¢å’Œç­›é€‰åŒºåŸŸ -->
    <view class="search-section">
      <view class="search-bar">
        <view class="search-input">
          <up-input class="search-text"
                    placeholder="请输入会议主题"
                    v-model="searchForm.title"
                    clearable />
        </view>
        <view class="search-button"
              @click="getList">
          <up-icon name="search"
                   size="24"
                   color="#999"></up-icon>
        </view>
      </view>
    </view>
    <!-- å®¡æ‰¹åˆ—表 -->
    <view class="ledger-list"
          v-if="ledgerList.length > 0">
      <view v-for="(item, index) in ledgerList"
            :key="index">
        <view class="ledger-item">
          <view class="item-header">
            <view class="item-left">
              <view class="document-icon">
                <up-icon name="file-text"
                         size="16"
                         color="#ffffff"></up-icon>
              </view>
              <text class="item-id">{{ item.title }}</text>
            </view>
            <view class="item-tag">
              <u-tag :type="getTagClass(item.status)">{{ formatReceiptType(item.status) }}</u-tag>
            </view>
          </view>
          <up-divider></up-divider>
          <view class="item-details">
            <view class="detail-row">
              <text class="detail-label">申请人</text>
              <text class="detail-value">{{ item.applicant }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">主理人</text>
              <text class="detail-value">{{ item.host }}</text>
            </view>
            <view class="detail-row-approveReason">
              <text class="detail-label">会议时间</text>
              <text class="detail-value highlightBlue">{{ formatDateTime(item.meetingTime) }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">会议地点</text>
              <text class="detail-value">{{ item.location }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">参会人数</text>
              <text class="detail-value">{{ item.participants.length }}</text>
            </view>
            <up-divider></up-divider>
            <view class="actions">
              <u-button type="primary"
                        size="small"
                        class="action-btn view"
                        @click="viewDetail(item)">
                è¯¦æƒ…
              </u-button>
              <u-button type="success"
                        size="small"
                        class="action-btn approve"
                        :disabled="item.status != 0"
                        @click="approve(item)">
                å®¡æ‰¹
              </u-button>
            </view>
            <!-- <view class="detail-info"
                  style="align-items: flex-end;">
              <view class="detail-row">
              </view>
            </view> -->
          </view>
        </view>
      </view>
    </view>
    <view v-else
          class="no-data">
      <text>暂无数据</text>
    </view>
  </view>
</template>
<script setup>
  import { ref, toRefs, reactive } from "vue";
  import PageHeader from "@/components/PageHeader.vue";
  import {
    getExamineList,
    getRoomEnum,
  } from "@/api/managementMeetings/meetExamine";
  import { staffOnJobListPage } from "@/api/personnelManagement/onboarding";
  import { onShow } from "@dcloudio/uni-app";
  import useUserStore from "@/store/modules/user";
  import dayjs from "dayjs";
  const userStore = useUserStore();
  // æ•°æ®
  const ledgerList = ref([]);
  const data = reactive({
    searchForm: {
      title: "",
    },
  });
  const { searchForm } = toRefs(data);
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  // æˆ¿é—´æžšä¸¾
  const roomEnum = ref([]);
  // æˆ¿é—´æžšä¸¾æŸ¥è¯¢
  const getRoomEnumList = () => {
    return getRoomEnum()
      .then(res => {
        console.log(res.data, "res.data");
        roomEnum.value = res.data;
      })
      .catch(() => {
        closeToast();
      });
  };
  // å‘˜å·¥åˆ—表
  const staffList = ref([]);
  // å‘˜å·¥åˆ—表查询
  const getStaffOnJobList = () => {
    return staffOnJobListPage()
      .then(res => {
        console.log(res.data, "res.data");
        staffList.value = res.data.records;
      })
      .catch(() => {
        closeToast();
      });
  };
  // æŸ¥è¯¢åˆ—表
  const getList = () => {
    showLoadingToast("加载中...");
    const page = {
      current: -1,
      size: -1,
    };
    getExamineList({
      ...page,
      ...searchForm.value,
    })
      .then(res => {
        console.log(res.data.records, "res.data.records");
        ledgerList.value = res.data.records.map(it => {
          console.log(it, "it1");
          let room = roomEnum.value.find(room => it.roomId === room.id);
          it.location = `${room.name}(${room.location})`;
          let staffs = JSON.parse(it.participants);
          it.staffCount = staffs.size;
          it.meetingTime = `${it.meetingDate} ${dayjs(it.startTime).format(
            "HH:mm:ss"
          )} ~ ${dayjs(it.endTime).format("HH:mm:ss")}`;
          it.participants = staffList.value
            .filter(staff => staffs.some(id => id == staff.id))
            .map(staff => {
              return {
                id: staff.id,
                name: `${staff.staffName}(${staff.postName})`,
              };
            });
          console.log(it, "it2");
          return it;
        });
        closeToast();
      })
      .catch(() => {
        closeToast();
      });
  };
  // æ˜¾ç¤ºåŠ è½½æç¤º
  const showLoadingToast = message => {
    uni.showLoading({
      title: message,
      mask: true,
    });
  };
  const formatDateTime = dateTime => {
    if (!dateTime) return "";
    return dateTime.replace(" ", "\n");
  };
  // å…³é—­æç¤º
  const closeToast = () => {
    uni.hideLoading();
  };
  // æ ¼å¼åŒ–回款方式
  const formatReceiptType = params => {
    if (params == 0) {
      return "待审核";
    } else if (params == 1) {
      return "已通过";
    } else if (params == 2) {
      return "未通过";
    } else if (params == 3) {
      return "已取消";
    } else {
      return "未知";
    }
  };
  // èŽ·å–æ ‡ç­¾æ ·å¼ç±»
  const getTagClass = type => {
    if (type == 0) {
      return "info";
    } else if (type == 1) {
      return "success";
    } else if (type == 2) {
      return "warning";
    } else if (type == 3) {
      return "danger";
    } else {
      return "info";
    }
  };
  // ç‚¹å‡»å®¡æ ¸
  const approve = item => {
    // uni.setStorageSync("approveId", item.approveId);
    uni.navigateTo({
      url:
        "/pages/managementMeetings/meetExamine/approve?item=" +
        JSON.stringify(item) +
        "&edit=true",
    });
  };
  // æŸ¥çœ‹è¯¦æƒ…
  const viewDetail = item => {
    uni.navigateTo({
      url:
        "/pages/managementMeetings/meetExamine/approve?item=" +
        JSON.stringify(item) +
        "&edit=false",
    });
  };
  onShow(async () => {
    // é¡µé¢åŠ è½½å®ŒæˆåŽçš„åˆå§‹åŒ–é€»è¾‘
    try {
      // ç­‰å¾…两个异步方法执行完成
      await Promise.all([getRoomEnumList(), getStaffOnJobList()]);
      // ä¸¤ä¸ªæ–¹æ³•执行完成后再执行 getList()
      getList();
    } catch (error) {
      console.error("初始化数据失败:", error);
      // å³ä½¿å‡ºé”™ä¹Ÿæ‰§è¡Œ getList(),确保页面能正常加载
      getList();
    }
  });
</script>
<style scoped lang="scss">
  @import "../../../styles/sales-common.scss";
  .u-divider {
    margin: 0 !important;
  }
  // æ–‡æ¡£å›¾æ ‡æ ·å¼ - è¦†ç›–公共样式中的背景色
  .document-icon {
    background: #ed8d05;
  }
  // æµ®åŠ¨æŒ‰é’®æ ·å¼ - è¦†ç›–公共样式中的背景色
  .fab-button {
    background: #ed8d05;
  }
  // ç‰¹æœ‰æ ·å¼
  .detail-row-user {
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
  .detail-row-approveReason {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 8px;
  }
  .detail-value.highlightBlue {
    color: #2979ff;
    font-weight: 500;
  }
  .detail-value.highlightYellow {
    color: #ed8d05;
    font-weight: 500;
  }
  .approver-value {
    display: flex;
    justify-content: flex-end;
  }
  .approver-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: #f0f6ff;
    color: #2b7cff;
    border: 1px solid #e0efff;
    border-radius: 999px;
    padding: 4px 10px;
    max-width: 100%;
  }
  .approver-name {
    font-size: 12px;
    color: #2b7cff;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .actions {
    display: flex;
    gap: 10px;
    align-items: center;
    justify-content: flex-end;
    margin-top: 18rpx;
  }
  .action-btn {
    border-radius: 16px;
    height: 28px;
    line-height: 28px;
    padding: 0 12px;
  }
</style>
src/pages/managementMeetings/meetPublish/approve.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,497 @@
<template>
  <view class="approve-page">
    <PageHeader title="发布"
                @back="goBack" />
    <!-- ç”³è¯·ä¿¡æ¯ -->
    <view class="application-info">
      <view class="info-header">
        <text class="info-title">会议信息</text>
      </view>
      <view class="info-content">
        <view class="info-row">
          <text class="info-label">会议主题</text>
          <text class="info-value">{{ approvalData.title }}</text>
        </view>
        <view class="info-row">
          <text class="info-label">申请人</text>
          <text class="info-value">{{ approvalData.applicant }}</text>
        </view>
        <view class="info-row">
          <text class="info-label">主理人</text>
          <text class="info-value">{{ approvalData.host }}</text>
        </view>
        <view class="info-row">
          <text class="info-label">会议时间</text>
          <text class="info-value">{{ formatDateTime(approvalData.meetingTime) }}</text>
        </view>
        <view class="info-row">
          <text class="info-label">会议地点</text>
          <text class="info-value">{{ approvalData.location }}</text>
        </view>
        <view class="info-row">
          <text class="info-label">审批状态</text>
          <text class="info-value tag"
                :class="getTagClass(approvalData.approveNodeStatus)">
            {{ formatReceiptType(approvalData.approveNodeStatus) }}
          </text>
        </view>
        <view class="info-row">
          <text class="info-label">会议说明</text>
          <text class="info-value">{{ approvalData.description }}</text>
        </view>
        <view class="info-row">
          <text class="info-label">参会人数</text>
          <text class="info-value">{{ approvalData.participants.length }}</text>
        </view>
        <view class="info-row">
          <text class="info-label">参会人员</text>
          <text class="info-value">{{ approvalData.participants.map(it => it.name).join("、") }}</text>
        </view>
      </view>
    </view>
    <!-- å‘布意见输入 -->
    <view v-if="isEdit"
          class="approval-input">
      <view class="input-header">
        <text class="input-title">发布意见</text>
      </view>
      <view class="input-content">
        <u-textarea v-model="approvalOpinion"
                    rows="4"
                    placeholder="请输入发布意见"
                    maxlength="200"
                    count />
      </view>
    </view>
    <!-- åº•部操作按钮 -->
    <view v-if="isEdit"
          class="footer-actions">
      <!-- <u-button class="reject-btn"
                @click="handleReject">不通过</u-button> -->
      <u-button class="approve-btn"
                @click="handleApprove">发布</u-button>
    </view>
  </view>
</template>
<script setup>
  import { ref, onMounted, computed } from "vue";
  import { onLoad } from "@dcloudio/uni-app";
  import { saveMeetingApplication } from "@/api/managementMeetings/meetExamine";
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
  import PageHeader from "@/components/PageHeader.vue";
  const approvalData = ref({});
  const approvalSteps = ref([]);
  const approvalOpinion = ref("");
  const isEdit = ref(false);
  onLoad(options => {
    console.log(options, "options");
    if (options.item) {
      approvalData.value = JSON.parse(options.item);
    }
    // ç¼–辑模式下,默认发布意见为当前审批意见
    if (options.edit) {
      isEdit.value = options.edit === "true" ? true : false;
    }
    console.log(approvalData.value, "approvalData.value");
  });
  const goBack = () => {
    uni.removeStorageSync("approveId");
    uni.navigateBack();
  };
  const formatDateTime = dateTime => {
    if (!dateTime) return "";
    return dateTime.replace(" ", "\n");
  };
  // æ ¼å¼åŒ–回款方式
  const formatReceiptType = params => {
    if (params == 0) {
      return "待发布";
    } else if (params == 1) {
      return "已发布";
    } else if (params == 2) {
      return "已取消";
    } else {
      return "未知";
    }
  };
  // èŽ·å–æ ‡ç­¾æ ·å¼ç±»
  const getTagClass = type => {
    if (type == 0) {
      return "info";
    } else if (type == 1) {
      return "success";
    } else if (type == 2) {
      return "danger";
    } else {
      return "info";
    }
  };
  const submitForm = status => {
    // æ ¡éªŒå‘布意见
    if (!approvalOpinion.value?.trim()) {
      showToast("请输入发布意见");
      return;
    }
    // è°ƒç”¨åŽç«¯
    saveMeetingApplication({
      id: approvalData.value.id,
      publishStatus: status,
      publishComment: approvalOpinion.value, // æ·»åŠ å‘å¸ƒæ„è§
    })
      .then(res => {
        if (res.code === 200) {
          showToast("发布成功");
          // æç¤ºåŽè¿”回上一个页面
          setTimeout(() => {
            goBack(); // å†…部是 uni.navigateBack()
          }, 800);
        } else {
          showToast(res.message || "发布操作失败,请重试");
        }
      })
      .catch(error => {
        console.error("发布操作失败:", error);
        showToast("发布操作失败,请重试");
      });
  };
  const handleApprove = () => {
    uni.showModal({
      title: "确认操作",
      content: "确定要发布该会议吗?",
      success: res => {
        if (res.confirm) submitForm(1);
      },
    });
  };
  const handleReject = () => {
    uni.showModal({
      title: "确认操作",
      content: "确定不通过该会议申请吗?",
      success: res => {
        if (res.confirm) submitForm(2);
      },
    });
  };
  // åŽŸå§‹èŠ‚ç‚¹æ•°æ®ï¼ˆç”¨äºŽæäº¤é€»è¾‘ï¼‰
  const activities = ref([]);
</script>
<style scoped lang="scss">
  .approve-page {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 80px;
  }
  .header {
    display: flex;
    align-items: center;
    background: #fff;
    padding: 16px 20px;
    border-bottom: 1px solid #f0f0f0;
    position: sticky;
    top: 0;
    z-index: 100;
  }
  .title {
    flex: 1;
    text-align: center;
    font-size: 18px;
    font-weight: 600;
    color: #333;
  }
  .application-info {
    background: #fff;
    margin: 16px;
    border-radius: 12px;
    overflow: hidden;
  }
  .info-header {
    padding: 16px;
    border-bottom: 1px solid #f0f0f0;
    background: #f8f9fa;
  }
  .info-title {
    font-size: 16px;
    font-weight: 600;
    color: #333;
  }
  .info-content {
    padding: 16px;
  }
  .info-row {
    display: flex;
    align-items: center;
    margin-bottom: 12px;
    &:last-child {
      margin-bottom: 0;
    }
  }
  .info-label {
    font-size: 14px;
    color: #666;
    width: 80px;
    flex-shrink: 0;
  }
  .info-value {
    font-size: 14px;
    color: #333;
    flex: 1;
  }
  .approval-process {
    background: #fff;
    margin: 16px;
    border-radius: 12px;
    overflow: hidden;
  }
  .process-header {
    padding: 16px;
    border-bottom: 1px solid #f0f0f0;
    background: #f8f9fa;
  }
  .process-title {
    font-size: 16px;
    font-weight: 600;
    color: #333;
  }
  .process-steps {
    padding: 20px;
  }
  .process-step {
    display: flex;
    position: relative;
    margin-bottom: 24px;
    &:last-child {
      margin-bottom: 0;
      .step-line {
        display: none;
      }
    }
  }
  .step-indicator {
    display: flex;
    flex-direction: column;
    align-items: center;
    margin-right: 16px;
  }
  .step-dot {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 14px;
    font-weight: 600;
    position: relative;
    z-index: 2;
  }
  .process-step.completed .step-dot {
    background: #52c41a;
    color: #fff;
  }
  .process-step.current .step-dot {
    background: #1890ff;
    color: #fff;
    animation: pulse 2s infinite;
  }
  .process-step.pending .step-dot {
    background: #d9d9d9;
    color: #999;
  }
  .step-line {
    width: 2px;
    height: 40px;
    background: #d9d9d9;
    margin-top: 8px;
  }
  .process-step.completed .step-line {
    background: #52c41a;
  }
  .process-step.rejected .step-dot {
    background: #ff4d4f;
    color: #fff;
  }
  .process-step.rejected .step-line {
    background: #ff4d4f;
  }
  .step-content {
    flex: 1;
    padding-top: 4px;
  }
  .step-info {
    margin-bottom: 8px;
  }
  .step-title {
    font-size: 16px;
    font-weight: 600;
    color: #333;
    display: block;
    margin-bottom: 4px;
  }
  .step-approver {
    font-size: 14px;
    color: #666;
    display: block;
    margin-bottom: 4px;
  }
  .step-time {
    font-size: 12px;
    color: #999;
    display: block;
  }
  .step-opinion {
    background: #f8f9fa;
    padding: 12px;
    border-radius: 8px;
    border-left: 4px solid #52c41a;
  }
  .opinion-label {
    font-size: 12px;
    color: #666;
    display: block;
    margin-bottom: 4px;
  }
  .opinion-content {
    font-size: 14px;
    color: #333;
    line-height: 1.5;
  }
  .approval-input {
    background: #fff;
    margin: 16px;
    border-radius: 12px;
    overflow: hidden;
  }
  .input-header {
    padding: 16px;
    border-bottom: 1px solid #f0f0f0;
    background: #f8f9fa;
  }
  .input-title {
    font-size: 16px;
    font-weight: 600;
    color: #333;
  }
  .input-content {
    padding: 16px;
  }
  .footer-actions {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    background: #fff;
    display: flex;
    justify-content: space-around;
    align-items: center;
    padding: 16px;
    box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
    z-index: 1000;
  }
  .reject-btn {
    width: 120px;
    background: #ff4d4f;
    color: #fff;
  }
  .approve-btn {
    width: 120px;
    background: #52c41a;
    color: #fff;
  }
  /* é€‚配u-button样式 */
  :deep(.u-button) {
    border-radius: 6px;
  }
  @keyframes pulse {
    0% {
      box-shadow: 0 0 0 0 rgba(24, 144, 255, 0.7);
    }
    70% {
      box-shadow: 0 0 0 10px rgba(24, 144, 255, 0);
    }
    100% {
      box-shadow: 0 0 0 0 rgba(24, 144, 255, 0);
    }
  }
  .signature-section {
    background: #fff;
    padding: 12px 16px 16px;
    border-top: 1px solid #f0f0f0;
  }
  .signature-header {
    margin-bottom: 8px;
  }
  .signature-title {
    font-size: 14px;
    font-weight: 600;
    color: #333;
  }
  .signature-box {
    width: 100%;
    height: 180px;
    background: #fff;
    border: 1px dashed #d9d9d9;
    border-radius: 8px;
    overflow: hidden;
  }
  .signature-actions {
    margin-top: 8px;
    display: flex;
    justify-content: flex-end;
  }
</style>
src/pages/managementMeetings/meetPublish/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,348 @@
// å®¡æ‰¹ç®¡ç†ä¸»é¡µé¢
<template>
  <view class="sales-account">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="会议发布"
                @back="goBack" />
    <!-- æœç´¢å’Œç­›é€‰åŒºåŸŸ -->
    <view class="search-section">
      <view class="search-bar">
        <view class="search-input">
          <up-input class="search-text"
                    placeholder="请输入会议主题"
                    v-model="searchForm.title"
                    clearable />
        </view>
        <view class="search-button"
              @click="getList">
          <up-icon name="search"
                   size="24"
                   color="#999"></up-icon>
        </view>
      </view>
    </view>
    <!-- å®¡æ‰¹åˆ—表 -->
    <view class="ledger-list"
          v-if="ledgerList.length > 0">
      <view v-for="(item, index) in ledgerList"
            :key="index">
        <view class="ledger-item">
          <view class="item-header">
            <view class="item-left">
              <view class="document-icon">
                <up-icon name="file-text"
                         size="16"
                         color="#ffffff"></up-icon>
              </view>
              <text class="item-id">{{ item.title }}</text>
            </view>
            <view class="item-tag">
              <u-tag :type="getTagClass(item.status)">{{ formatReceiptType(item.status) }}</u-tag>
            </view>
          </view>
          <up-divider></up-divider>
          <view class="item-details">
            <view class="detail-row">
              <text class="detail-label">申请人</text>
              <text class="detail-value">{{ item.applicant }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">主理人</text>
              <text class="detail-value">{{ item.host }}</text>
            </view>
            <view class="detail-row-approveReason">
              <text class="detail-label">会议时间</text>
              <text class="detail-value highlightBlue">{{ formatDateTime(item.meetingTime) }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">会议地点</text>
              <text class="detail-value">{{ item.location }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">参会人数</text>
              <text class="detail-value">{{ item.participants.length }}</text>
            </view>
            <up-divider></up-divider>
            <view class="actions">
              <u-button type="primary"
                        size="small"
                        class="action-btn view"
                        @click="viewDetail(item)">
                è¯¦æƒ…
              </u-button>
              <u-button type="success"
                        size="small"
                        class="action-btn approve"
                        :disabled="item.status != 0"
                        @click="approve(item)">
                å‘布
              </u-button>
            </view>
            <!-- <view class="detail-info"
                  style="align-items: flex-end;">
              <view class="detail-row">
              </view>
            </view> -->
          </view>
        </view>
      </view>
    </view>
    <view v-else
          class="no-data">
      <text>暂无数据</text>
    </view>
  </view>
</template>
<script setup>
  import { ref, toRefs, reactive } from "vue";
  import PageHeader from "@/components/PageHeader.vue";
  import {
    getMeetingPublish,
    getRoomEnum,
  } from "@/api/managementMeetings/meetExamine";
  import { staffOnJobListPage } from "@/api/personnelManagement/onboarding";
  import { onShow } from "@dcloudio/uni-app";
  import useUserStore from "@/store/modules/user";
  import dayjs from "dayjs";
  const userStore = useUserStore();
  // æ•°æ®
  const ledgerList = ref([]);
  const data = reactive({
    searchForm: {
      title: "",
    },
  });
  const { searchForm } = toRefs(data);
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  // æˆ¿é—´æžšä¸¾
  const roomEnum = ref([]);
  // æˆ¿é—´æžšä¸¾æŸ¥è¯¢
  const getRoomEnumList = () => {
    return getRoomEnum()
      .then(res => {
        console.log(res.data, "res.data");
        roomEnum.value = res.data;
      })
      .catch(() => {
        closeToast();
      });
  };
  // å‘˜å·¥åˆ—表
  const staffList = ref([]);
  // å‘˜å·¥åˆ—表查询
  const getStaffOnJobList = () => {
    return staffOnJobListPage()
      .then(res => {
        console.log(res.data, "res.data");
        staffList.value = res.data.records;
      })
      .catch(() => {
        closeToast();
      });
  };
  // æŸ¥è¯¢åˆ—表
  const getList = () => {
    showLoadingToast("加载中...");
    const page = {
      current: -1,
      size: -1,
    };
    getMeetingPublish({
      ...page,
      ...searchForm.value,
    })
      .then(res => {
        console.log(res.data.records, "res.data.records");
        ledgerList.value = res.data.records.map(it => {
          console.log(it, "it1");
          let room = roomEnum.value.find(room => it.roomId === room.id);
          it.location = `${room.name}(${room.location})`;
          let staffs = JSON.parse(it.participants);
          it.staffCount = staffs.size;
          it.meetingTime = `${it.meetingDate} ${dayjs(it.startTime).format(
            "HH:mm:ss"
          )} ~ ${dayjs(it.endTime).format("HH:mm:ss")}`;
          it.participants = staffList.value
            .filter(staff => staffs.some(id => id == staff.id))
            .map(staff => {
              return {
                id: staff.id,
                name: `${staff.staffName}(${staff.postName})`,
              };
            });
          console.log(it, "it2");
          return it;
        });
        closeToast();
      })
      .catch(() => {
        closeToast();
      });
  };
  // æ˜¾ç¤ºåŠ è½½æç¤º
  const showLoadingToast = message => {
    uni.showLoading({
      title: message,
      mask: true,
    });
  };
  const formatDateTime = dateTime => {
    if (!dateTime) return "";
    return dateTime.replace(" ", "\n");
  };
  // å…³é—­æç¤º
  const closeToast = () => {
    uni.hideLoading();
  };
  // æ ¼å¼åŒ–回款方式
  const formatReceiptType = params => {
    if (params == 0) {
      return "待发布";
    } else if (params == 1) {
      return "已发布";
    } else if (params == 2) {
      return "已取消";
    } else {
      return "未知";
    }
  };
  // èŽ·å–æ ‡ç­¾æ ·å¼ç±»
  const getTagClass = type => {
    if (type == 0) {
      return "info";
    } else if (type == 1) {
      return "success";
    } else if (type == 2) {
      return "danger";
    } else {
      return "info";
    }
  };
  // ç‚¹å‡»å®¡æ ¸
  const approve = item => {
    // uni.setStorageSync("approveId", item.approveId);
    uni.navigateTo({
      url:
        "/pages/managementMeetings/meetPublish/approve?item=" +
        JSON.stringify(item) +
        "&edit=true",
    });
  };
  // æŸ¥çœ‹è¯¦æƒ…
  const viewDetail = item => {
    uni.navigateTo({
      url:
        "/pages/managementMeetings/meetPublish/approve?item=" +
        JSON.stringify(item) +
        "&edit=false",
    });
  };
  onShow(async () => {
    // é¡µé¢åŠ è½½å®ŒæˆåŽçš„åˆå§‹åŒ–é€»è¾‘
    try {
      // ç­‰å¾…两个异步方法执行完成
      await Promise.all([getRoomEnumList(), getStaffOnJobList()]);
      // ä¸¤ä¸ªæ–¹æ³•执行完成后再执行 getList()
      getList();
    } catch (error) {
      console.error("初始化数据失败:", error);
      // å³ä½¿å‡ºé”™ä¹Ÿæ‰§è¡Œ getList(),确保页面能正常加载
      getList();
    }
  });
</script>
<style scoped lang="scss">
  @import "../../../styles/sales-common.scss";
  .u-divider {
    margin: 0 !important;
  }
  // æ–‡æ¡£å›¾æ ‡æ ·å¼ - è¦†ç›–公共样式中的背景色
  .document-icon {
    background: #ed8d05;
  }
  // æµ®åŠ¨æŒ‰é’®æ ·å¼ - è¦†ç›–公共样式中的背景色
  .fab-button {
    background: #ed8d05;
  }
  // ç‰¹æœ‰æ ·å¼
  .detail-row-user {
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
  .detail-row-approveReason {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 8px;
  }
  .detail-value.highlightBlue {
    color: #2979ff;
    font-weight: 500;
  }
  .detail-value.highlightYellow {
    color: #ed8d05;
    font-weight: 500;
  }
  .approver-value {
    display: flex;
    justify-content: flex-end;
  }
  .approver-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: #f0f6ff;
    color: #2b7cff;
    border: 1px solid #e0efff;
    border-radius: 999px;
    padding: 4px 10px;
    max-width: 100%;
  }
  .approver-name {
    font-size: 12px;
    color: #2b7cff;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .actions {
    display: flex;
    gap: 10px;
    align-items: center;
    justify-content: flex-end;
    margin-top: 18rpx;
  }
  .action-btn {
    border-radius: 16px;
    height: 28px;
    line-height: 28px;
    padding: 0 12px;
  }
</style>
src/pages/managementMeetings/meetSummary/approve.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,560 @@
<template>
  <view class="approve-page">
    <PageHeader title="总结"
                @back="goBack" />
    <!-- ç”³è¯·ä¿¡æ¯ -->
    <view class="application-info">
      <view class="info-header">
        <text class="info-title">会议信息</text>
      </view>
      <view class="info-content">
        <view class="info-row">
          <text class="info-label">会议主题</text>
          <text class="info-value">{{ approvalData.title }}</text>
        </view>
        <view class="info-row">
          <text class="info-label">申请人</text>
          <text class="info-value">{{ approvalData.applicant }}</text>
        </view>
        <view class="info-row">
          <text class="info-label">主理人</text>
          <text class="info-value">{{ approvalData.host }}</text>
        </view>
        <view class="info-row">
          <text class="info-label">会议时间</text>
          <text class="info-value">{{ formatDateTime(approvalData.meetingTime) }}</text>
        </view>
        <view class="info-row">
          <text class="info-label">会议地点</text>
          <text class="info-value">{{ approvalData.location }}</text>
        </view>
        <view class="info-row">
          <text class="info-label">审批状态</text>
          <text class="info-value tag"
                :class="getTagClass(approvalData.approveNodeStatus)">
            {{ formatReceiptType(approvalData.approveNodeStatus) }}
          </text>
        </view>
        <view class="info-row">
          <text class="info-label">会议说明</text>
          <text class="info-value">{{ approvalData.description }}</text>
        </view>
        <view class="info-row">
          <text class="info-label">参会人数</text>
          <text class="info-value">{{ approvalData.participants.length }}</text>
        </view>
        <view class="info-row">
          <text class="info-label">参会人员</text>
          <text class="info-value">{{ approvalData.participants.map(it => it.name).join("、") }}</text>
        </view>
      </view>
    </view>
    <!-- æäº¤æ„è§è¾“å…¥ -->
    <view v-if="isEdit"
          class="approval-input">
      <view class="input-header">
        <text class="input-title">会议纪要</text>
      </view>
      <view class="input-content">
        <Editor v-model:modelValue="minutesContent"
                :height="300" />
      </view>
    </view>
    <!-- åº•部操作按钮 -->
    <view v-if="isEdit"
          class="footer-actions">
      <u-button class="approve-btn"
                @click="handleApprove">提交</u-button>
    </view>
  </view>
</template>
<script setup>
  import { ref, onMounted, nextTick } from "vue";
  import { onLoad } from "@dcloudio/uni-app";
  import {
    saveMeetingMinutes,
    getMeetingMinutesByMeetingId,
  } from "@/api/managementMeetings/meetExamine";
  import { getToken } from "@/utils/auth";
  import PageHeader from "@/components/PageHeader.vue";
  import Editor from "@/components/Editor/index.vue";
  const approvalData = ref({});
  const approvalSteps = ref([]);
  const isEdit = ref(false);
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
  onLoad(options => {
    console.log(options, "options");
    if (options.item) {
      approvalData.value = JSON.parse(options.item);
    }
    // ç¼–辑模式下,默认提交意见为当前审批意见
    if (options.edit) {
      isEdit.value = options.edit === "true" ? true : false;
    }
    getMeetingMinutes();
    console.log(approvalData.value, "approvalData.value");
  });
  const goBack = () => {
    uni.removeStorageSync("approveId");
    uni.navigateBack();
  };
  const formatDateTime = dateTime => {
    if (!dateTime) return "";
    return dateTime.replace(" ", "\n");
  };
  // æ ¼å¼åŒ–回款方式
  const formatReceiptType = params => {
    if (params == 0) {
      return "待审批";
    } else if (params == 1) {
      return "已通过";
    } else if (params == 2) {
      return "未通过";
    } else if (params == 3) {
      return "已取消";
    } else {
      return "未知";
    }
  };
  // èŽ·å–æ ‡ç­¾æ ·å¼ç±»
  const getTagClass = type => {
    if (type == 0) {
      return "info";
    } else if (type == 1) {
      return "success";
    } else if (type == 2) {
      return "warning";
    } else if (type == 3) {
      return "danger";
    } else {
      return "info";
    }
  };
  const minutesContent = ref("");
  const minutesContentId = ref("");
  const getMeetingMinutes = () => {
    getMeetingMinutesByMeetingId(approvalData.value.id)
      .then(res => {
        console.log(res.data, "res.data");
        if (res.data) {
          minutesContent.value = res.data.content;
          minutesContentId.value = res.data.id;
        } else {
          minutesContent.value = `<h2>${approvalData.value.title}会议纪要</h2>
                                                                                                      <p><strong>会议时间:</strong>${
                                                                                                        approvalData
                                                                                                          .value
                                                                                                          .meetingTime
                                                                                                      }</p>
                                                                                                      <p><strong>会议地点:</strong>${
                                                                                                        approvalData
                                                                                                          .value
                                                                                                          .location
                                                                                                      }</p>
                                                                                                      <p><strong>主持人:</strong>${
                                                                                                        approvalData
                                                                                                          .value
                                                                                                          .host
                                                                                                      }</p>
                                                                                                      <p><strong>参会人员:</strong></p>
                                                                                                      <ol>
                                                                                                        ${approvalData.value.participants
                                                                                                          .map(
                                                                                                            p =>
                                                                                                              `<li>${p.name}</li>`
                                                                                                          )
                                                                                                          .join(
                                                                                                            ""
                                                                                                          )}
                                                                                                      </ol>
                                                                                                      <p><strong>会议内容:</strong></p>
                                                                                                      <ol>
                                                                                                        <li>议题一:
                                                                                                          <ul>
                                                                                                            <li>讨论内容:</li>
                                                                                                            <li>决议事项:</li>
                                                                                                          </ul>
                                                                                                        </li>
                                                                                                        <li>议题二:
                                                                                                          <ul>
                                                                                                            <li>讨论内容:</li>
                                                                                                            <li>决议事项:</li>
                                                                                                          </ul>
                                                                                                        </li>
                                                                                                      </ol>
                                                                                                      <p><strong>备注:</strong></p>`;
        }
      })
      .catch(error => {
        console.error("获取会议纪要失败:", error);
        showToast("获取会议纪要失败,请重试");
      });
  };
  const submitForm = status => {
    console.log(minutesContent.value, "富文本");
    if (!minutesContent.value) {
      ElMessage.warning("请输入会议纪要内容");
      return;
    }
    // è°ƒç”¨åŽç«¯
    saveMeetingMinutes({
      id: minutesContentId.value,
      content: minutesContent.value,
      meetingId: approvalData.value.id,
      title: approvalData.value.title,
    })
      .then(res => {
        if (res.code === 200) {
          showToast("提交成功");
          // æç¤ºåŽè¿”回上一个页面
          setTimeout(() => {
            goBack(); // å†…部是 uni.navigateBack()
          }, 800);
        } else {
          showToast(res.message || "提交操作失败,请重试");
        }
      })
      .catch(error => {
        console.error("提交操作失败:", error);
        showToast("提交操作失败,请重试");
      });
  };
  const handleApprove = () => {
    uni.showModal({
      title: "确认操作",
      content: "确定要提交该会议总结吗?",
      success: res => {
        if (res.confirm) submitForm(1);
      },
    });
  };
  // åŽŸå§‹èŠ‚ç‚¹æ•°æ®ï¼ˆç”¨äºŽæäº¤é€»è¾‘ï¼‰
  const activities = ref([]);
</script>
<style scoped lang="scss">
  .approve-page {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 80px;
  }
  .header {
    display: flex;
    align-items: center;
    background: #fff;
    padding: 16px 20px;
    border-bottom: 1px solid #f0f0f0;
    position: sticky;
    top: 0;
    z-index: 100;
  }
  .title {
    flex: 1;
    text-align: center;
    font-size: 18px;
    font-weight: 600;
    color: #333;
  }
  .application-info {
    background: #fff;
    margin: 16px;
    border-radius: 12px;
    overflow: hidden;
  }
  .info-header {
    padding: 16px;
    border-bottom: 1px solid #f0f0f0;
    background: #f8f9fa;
  }
  .info-title {
    font-size: 16px;
    font-weight: 600;
    color: #333;
  }
  .info-content {
    padding: 16px;
  }
  .info-row {
    display: flex;
    align-items: center;
    margin-bottom: 12px;
    &:last-child {
      margin-bottom: 0;
    }
  }
  .info-label {
    font-size: 14px;
    color: #666;
    width: 80px;
    flex-shrink: 0;
  }
  .info-value {
    font-size: 14px;
    color: #333;
    flex: 1;
  }
  .approval-process {
    background: #fff;
    margin: 16px;
    border-radius: 12px;
    overflow: hidden;
  }
  .process-header {
    padding: 16px;
    border-bottom: 1px solid #f0f0f0;
    background: #f8f9fa;
  }
  .process-title {
    font-size: 16px;
    font-weight: 600;
    color: #333;
  }
  .process-steps {
    padding: 20px;
  }
  .process-step {
    display: flex;
    position: relative;
    margin-bottom: 24px;
    &:last-child {
      margin-bottom: 0;
      .step-line {
        display: none;
      }
    }
  }
  .step-indicator {
    display: flex;
    flex-direction: column;
    align-items: center;
    margin-right: 16px;
  }
  .step-dot {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 14px;
    font-weight: 600;
    position: relative;
    z-index: 2;
  }
  .process-step.completed .step-dot {
    background: #52c41a;
    color: #fff;
  }
  .process-step.current .step-dot {
    background: #1890ff;
    color: #fff;
    animation: pulse 2s infinite;
  }
  .process-step.pending .step-dot {
    background: #d9d9d9;
    color: #999;
  }
  .step-line {
    width: 2px;
    height: 40px;
    background: #d9d9d9;
    margin-top: 8px;
  }
  .process-step.completed .step-line {
    background: #52c41a;
  }
  .process-step.rejected .step-dot {
    background: #ff4d4f;
    color: #fff;
  }
  .process-step.rejected .step-line {
    background: #ff4d4f;
  }
  .step-content {
    flex: 1;
    padding-top: 4px;
  }
  .step-info {
    margin-bottom: 8px;
  }
  .step-title {
    font-size: 16px;
    font-weight: 600;
    color: #333;
    display: block;
    margin-bottom: 4px;
  }
  .step-approver {
    font-size: 14px;
    color: #666;
    display: block;
    margin-bottom: 4px;
  }
  .step-time {
    font-size: 12px;
    color: #999;
    display: block;
  }
  .step-opinion {
    background: #f8f9fa;
    padding: 12px;
    border-radius: 8px;
    border-left: 4px solid #52c41a;
  }
  .opinion-label {
    font-size: 12px;
    color: #666;
    display: block;
    margin-bottom: 4px;
  }
  .opinion-content {
    font-size: 14px;
    color: #333;
    line-height: 1.5;
  }
  .approval-input {
    background: #fff;
    margin: 16px;
    border-radius: 12px;
    overflow: hidden;
  }
  .input-header {
    padding: 16px;
    border-bottom: 1px solid #f0f0f0;
    background: #f8f9fa;
  }
  .input-title {
    font-size: 16px;
    font-weight: 600;
    color: #333;
  }
  .input-content {
    padding: 16px;
  }
  .footer-actions {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    background: #fff;
    display: flex;
    justify-content: space-around;
    align-items: center;
    padding: 16px;
    box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
    z-index: 1000;
  }
  .reject-btn {
    width: 120px;
    background: #ff4d4f;
    color: #fff;
  }
  .approve-btn {
    width: 120px;
    background: #52c41a;
    color: #fff;
  }
  /* é€‚配u-button样式 */
  :deep(.u-button) {
    border-radius: 6px;
  }
  @keyframes pulse {
    0% {
      box-shadow: 0 0 0 0 rgba(24, 144, 255, 0.7);
    }
    70% {
      box-shadow: 0 0 0 10px rgba(24, 144, 255, 0);
    }
    100% {
      box-shadow: 0 0 0 0 rgba(24, 144, 255, 0);
    }
  }
  .signature-section {
    background: #fff;
    padding: 12px 16px 16px;
    border-top: 1px solid #f0f0f0;
  }
  .signature-header {
    margin-bottom: 8px;
  }
  .signature-title {
    font-size: 14px;
    font-weight: 600;
    color: #333;
  }
  .signature-box {
    width: 100%;
    height: 180px;
    background: #fff;
    border: 1px dashed #d9d9d9;
    border-radius: 8px;
    overflow: hidden;
  }
  .signature-actions {
    margin-top: 8px;
    display: flex;
    justify-content: flex-end;
  }
  /* å·¥å…·æ æŒ‰é’®æ ·å¼ */
  :deep(.ql-toolbar.ql-snow .ql-button) {
    height: 28px;
    width: 28px;
    padding: 4px;
  }
  :deep(.ql-toolbar.ql-snow .ql-picker-label) {
    height: 28px;
    padding: 4px 8px;
  }
</style>
src/pages/managementMeetings/meetSummary/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,353 @@
// å®¡æ‰¹ç®¡ç†ä¸»é¡µé¢
<template>
  <view class="sales-account">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="会议总结"
                @back="goBack" />
    <!-- æœç´¢å’Œç­›é€‰åŒºåŸŸ -->
    <view class="search-section">
      <view class="search-bar">
        <view class="search-input">
          <up-input class="search-text"
                    placeholder="请输入会议主题"
                    v-model="searchForm.title"
                    clearable />
        </view>
        <view class="search-button"
              @click="getList">
          <up-icon name="search"
                   size="24"
                   color="#999"></up-icon>
        </view>
      </view>
    </view>
    <!-- å®¡æ‰¹åˆ—表 -->
    <view class="ledger-list"
          v-if="ledgerList.length > 0">
      <view v-for="(item, index) in ledgerList"
            :key="index">
        <view class="ledger-item">
          <view class="item-header">
            <view class="item-left">
              <view class="document-icon">
                <up-icon name="file-text"
                         size="16"
                         color="#ffffff"></up-icon>
              </view>
              <text class="item-id">{{ item.title }}</text>
            </view>
            <view class="item-tag">
              <u-tag :type="getTagClass(item.status)">{{ formatReceiptType(item.status) }}</u-tag>
            </view>
          </view>
          <up-divider></up-divider>
          <view class="item-details">
            <view class="detail-row">
              <text class="detail-label">申请人</text>
              <text class="detail-value">{{ item.applicant }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">主理人</text>
              <text class="detail-value">{{ item.host }}</text>
            </view>
            <view class="detail-row-approveReason">
              <text class="detail-label">会议时间</text>
              <text class="detail-value highlightBlue">{{ formatDateTime(item.meetingTime) }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">会议地点</text>
              <text class="detail-value">{{ item.location }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">参会人数</text>
              <text class="detail-value">{{ item.participants.length }}</text>
            </view>
            <up-divider></up-divider>
            <view class="actions">
              <u-button type="primary"
                        size="small"
                        class="action-btn view"
                        @click="viewDetail(item)">
                è¯¦æƒ…
              </u-button>
              <u-button type="success"
                        size="small"
                        class="action-btn approve"
                        :disabled="item.status != 0"
                        @click="approve(item)">
                æ·»åŠ çºªè¦
              </u-button>
            </view>
            <!-- <view class="detail-info"
                  style="align-items: flex-end;">
              <view class="detail-row">
              </view>
            </view> -->
          </view>
        </view>
      </view>
    </view>
    <view v-else
          class="no-data">
      <text>暂无数据</text>
    </view>
  </view>
</template>
<script setup>
  import { ref, toRefs, reactive } from "vue";
  import PageHeader from "@/components/PageHeader.vue";
  import {
    getMeetingPublish,
    getRoomEnum,
  } from "@/api/managementMeetings/meetExamine";
  import { staffOnJobListPage } from "@/api/personnelManagement/onboarding";
  import { onShow } from "@dcloudio/uni-app";
  import useUserStore from "@/store/modules/user";
  import dayjs from "dayjs";
  const userStore = useUserStore();
  // æ•°æ®
  const ledgerList = ref([]);
  const data = reactive({
    searchForm: {
      title: "",
    },
  });
  const { searchForm } = toRefs(data);
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  // æˆ¿é—´æžšä¸¾
  const roomEnum = ref([]);
  // æˆ¿é—´æžšä¸¾æŸ¥è¯¢
  const getRoomEnumList = () => {
    return getRoomEnum()
      .then(res => {
        console.log(res.data, "res.data");
        roomEnum.value = res.data;
      })
      .catch(() => {
        closeToast();
      });
  };
  // å‘˜å·¥åˆ—表
  const staffList = ref([]);
  // å‘˜å·¥åˆ—表查询
  const getStaffOnJobList = () => {
    return staffOnJobListPage()
      .then(res => {
        console.log(res.data, "res.data");
        staffList.value = res.data.records;
      })
      .catch(() => {
        closeToast();
      });
  };
  // æŸ¥è¯¢åˆ—表
  const getList = () => {
    showLoadingToast("加载中...");
    const page = {
      current: -1,
      size: -1,
    };
    getMeetingPublish({
      ...page,
      ...searchForm.value,
    })
      .then(res => {
        console.log(res.data.records, "res.data.records");
        ledgerList.value = res.data.records.map(it => {
          console.log(it, "it1");
          let room = roomEnum.value.find(room => it.roomId === room.id);
          it.location = `${room.name}(${room.location})`;
          let staffs = JSON.parse(it.participants);
          it.staffCount = staffs.size;
          it.meetingTime = `${it.meetingDate} ${dayjs(it.startTime).format(
            "HH:mm:ss"
          )} ~ ${dayjs(it.endTime).format("HH:mm:ss")}`;
          console.log(staffList.value, "staffList.value");
          it.participants = staffList.value
            .filter(staff => staffs.some(id => id == staff.id))
            .map(staff => {
              return {
                id: staff.id,
                name: `${staff.staffName}(${staff.postName})`,
              };
            });
          console.log(it, "it2");
          return it;
        });
        closeToast();
      })
      .catch(() => {
        closeToast();
      });
  };
  // æ˜¾ç¤ºåŠ è½½æç¤º
  const showLoadingToast = message => {
    uni.showLoading({
      title: message,
      mask: true,
    });
  };
  const formatDateTime = dateTime => {
    if (!dateTime) return "";
    return dateTime.replace(" ", "\n");
  };
  // å…³é—­æç¤º
  const closeToast = () => {
    uni.hideLoading();
  };
  // æ ¼å¼åŒ–回款方式
  const formatReceiptType = params => {
    if (params == 0) {
      return "待审批";
    } else if (params == 1) {
      return "已通过";
    } else if (params == 2) {
      return "未通过";
    } else if (params == 3) {
      return "已取消";
    } else {
      return "未知";
    }
  };
  // èŽ·å–æ ‡ç­¾æ ·å¼ç±»
  const getTagClass = type => {
    if (type == 0) {
      return "info";
    } else if (type == 1) {
      return "success";
    } else if (type == 2) {
      return "warning";
    } else if (type == 3) {
      return "danger";
    } else {
      return "info";
    }
  };
  // ç‚¹å‡»å®¡æ ¸
  const approve = item => {
    // uni.setStorageSync("approveId", item.approveId);
    uni.navigateTo({
      url:
        "/pages/managementMeetings/meetSummary/approve?item=" +
        JSON.stringify(item) +
        "&edit=true",
    });
  };
  // æŸ¥çœ‹è¯¦æƒ…
  const viewDetail = item => {
    uni.navigateTo({
      url:
        "/pages/managementMeetings/meetSummary/approve?item=" +
        JSON.stringify(item) +
        "&edit=false",
    });
  };
  onShow(async () => {
    // é¡µé¢åŠ è½½å®ŒæˆåŽçš„åˆå§‹åŒ–é€»è¾‘
    try {
      // ç­‰å¾…两个异步方法执行完成
      await Promise.all([getRoomEnumList(), getStaffOnJobList()]);
      // ä¸¤ä¸ªæ–¹æ³•执行完成后再执行 getList()
      getList();
    } catch (error) {
      console.error("初始化数据失败:", error);
      // å³ä½¿å‡ºé”™ä¹Ÿæ‰§è¡Œ getList(),确保页面能正常加载
      getList();
    }
  });
</script>
<style scoped lang="scss">
  @import "../../../styles/sales-common.scss";
  .u-divider {
    margin: 0 !important;
  }
  // æ–‡æ¡£å›¾æ ‡æ ·å¼ - è¦†ç›–公共样式中的背景色
  .document-icon {
    background: #ed8d05;
  }
  // æµ®åŠ¨æŒ‰é’®æ ·å¼ - è¦†ç›–公共样式中的背景色
  .fab-button {
    background: #ed8d05;
  }
  // ç‰¹æœ‰æ ·å¼
  .detail-row-user {
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
  .detail-row-approveReason {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 8px;
  }
  .detail-value.highlightBlue {
    color: #2979ff;
    font-weight: 500;
  }
  .detail-value.highlightYellow {
    color: #ed8d05;
    font-weight: 500;
  }
  .approver-value {
    display: flex;
    justify-content: flex-end;
  }
  .approver-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: #f0f6ff;
    color: #2b7cff;
    border: 1px solid #e0efff;
    border-radius: 999px;
    padding: 4px 10px;
    max-width: 100%;
  }
  .approver-name {
    font-size: 12px;
    color: #2b7cff;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .actions {
    display: flex;
    gap: 10px;
    align-items: center;
    justify-content: flex-end;
    margin-top: 18rpx;
  }
  .action-btn {
    border-radius: 16px;
    height: 28px;
    line-height: 28px;
    padding: 0 12px;
  }
</style>
src/pages/managementMeetings/meetingBoard/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,393 @@
// å®¡æ‰¹ç®¡ç†ä¸»é¡µé¢
<template>
  <view class="sales-account">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="会议看板"
                @back="goBack" />
    <!-- å®¡æ‰¹åˆ—表 -->
    <view class="topbox">
      <view class="boxItem">
        <view class="boxItem-num">
          {{stats.total ? stats.total : 0}}
        </view>
        <view class="boxItem-title">
          æ€»ä¼šè®®æ•°
        </view>
      </view>
      <view class="boxItem">
        <view class="boxItem-num">
          {{stats.underWay ? stats.underWay : 0}}
        </view>
        <view class="boxItem-title">
          è¿›è¡Œä¸­
        </view>
      </view>
      <view class="boxItem">
        <view class="boxItem-num">
          {{stats.completed ? stats.completed : 0}}
        </view>
        <view class="boxItem-title">
          å·²å®Œæˆ
        </view>
      </view>
      <view class="boxItem">
        <view class="boxItem-num">
          {{stats.toStart ? stats.toStart : 0}}
        </view>
        <view class="boxItem-title">
          å³å°†å¼€å§‹
        </view>
      </view>
    </view>
    <view class="ledger-list"
          v-if="ledgerList.length > 0">
      <view v-for="(item, index) in ledgerList"
            :key="index">
        <view class="ledger-item">
          <view class="item-header">
            <view class="item-left">
              <view class="document-icon">
                <up-icon name="file-text"
                         size="16"
                         color="#ffffff"></up-icon>
              </view>
              <text class="item-id">{{ item.title }}</text>
            </view>
            <view class="item-tag">
              <u-tag :type="getTagClass(item.status)">{{ formatReceiptType(item.status) }}</u-tag>
            </view>
          </view>
          <up-divider></up-divider>
          <view class="item-details">
            <view class="detail-row">
              <text class="detail-label">主持人</text>
              <text class="detail-value">{{ item.host }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">参会人数</text>
              <text class="detail-value">{{ item.participants.length }}</text>
            </view>
            <view class="detail-row-approveReason">
              <text class="detail-label">会议时间</text>
              <text class="detail-value highlightBlue">{{dayjs(item.startTime).format("YYYY-MM-DD")}}
                {{ formatTime(item.startTime) }} - {{ formatTime(item.endTime) }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">会议地点</text>
              <text class="detail-value">{{ item.location }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">参会人数</text>
              <text class="detail-value">{{ item.participants.length }}</text>
            </view>
            <!-- <up-divider></up-divider> -->
            <u-collapse @change="change"
                        @close="close"
                        @open="open"
                        accordion
                        border
                        :duration="300">
              <u-collapse-item :title="`会议纪要 ${item.content ? '' : '(无)'}`"
                               :name="`meeting-${index}`">
                <view class="meeting-content">
                  <view v-if="item.content"
                        class="content-html"><rich-text :nodes="item.content"></rich-text></view>
                  <view v-else
                        class="no-content">
                    <text>暂无会议纪要内容</text>
                  </view>
                </view>
              </u-collapse-item>
            </u-collapse>
          </view>
        </view>
      </view>
    </view>
    <view v-else
          class="no-data">
      <text>暂无数据</text>
    </view>
  </view>
</template>
<script setup>
  import { ref, toRefs, reactive } from "vue";
  import PageHeader from "@/components/PageHeader.vue";
  import {
    getMeetSummaryItems,
    getMeetSummary,
  } from "@/api/managementMeetings/meetExamine";
  import { onShow } from "@dcloudio/uni-app";
  import dayjs from "dayjs";
  import useUserStore from "@/store/modules/user";
  // æ•°æ®
  const ledgerList = ref([]);
  const data = reactive({
    searchForm: {
      title: "",
    },
  });
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  // ç»Ÿè®¡æ•°æ®
  const stats = ref({});
  const getnum = () => {
    getMeetSummary().then(res => {
      stats.value = res.data;
    });
  };
  // æ ¼å¼åŒ–æ—¶é—´
  const formatTime = timeStr => {
    const date = new Date(timeStr);
    return date.toLocaleTimeString("zh-CN", {
      hour: "2-digit",
      minute: "2-digit",
    });
  };
  // æŸ¥è¯¢åˆ—表
  const getList = () => {
    showLoadingToast("加载中...");
    getMeetSummaryItems()
      .then(res => {
        ledgerList.value = res.data.map(item => {
          return {
            ...item,
            participants: JSON.parse(item.participants),
          };
        });
        closeToast();
      })
      .catch(() => {
        closeToast();
      });
  };
  // æ˜¾ç¤ºåŠ è½½æç¤º
  const showLoadingToast = message => {
    uni.showLoading({
      title: message,
      mask: true,
    });
  };
  const formatDateTime = dateTime => {
    if (!dateTime) return "";
    return dateTime.replace(" ", "\n");
  };
  // å…³é—­æç¤º
  const closeToast = () => {
    uni.hideLoading();
  };
  // æ ¼å¼åŒ–回款方式
  const formatReceiptType = params => {
    if (params == 0) {
      return "已完成";
    } else if (params == 1) {
      return "即将开始";
    } else if (params == 2) {
      return "进行中";
    } else {
      return "未知";
    }
  };
  // èŽ·å–æ ‡ç­¾æ ·å¼ç±»
  const getTagClass = type => {
    if (type == 0) {
      return "info";
    } else if (type == 1) {
      return "warning";
    } else if (type == 2) {
      return "success";
    } else {
      return "info";
    }
  };
  // u-collapse äº‹ä»¶å¤„理函数
  const change = name => {
    console.log("展开的面板名:", name);
  };
  const close = name => {
    console.log("关闭的面板名:", name);
  };
  const open = name => {
    console.log("打开的面板名:", name);
  };
  onShow(async () => {
    // é¡µé¢åŠ è½½å®ŒæˆåŽçš„åˆå§‹åŒ–é€»è¾‘
    try {
      // ä¸¤ä¸ªæ–¹æ³•执行完成后再执行 getList()
      getList();
      getnum();
    } catch (error) {
      console.error("初始化数据失败:", error);
      // å³ä½¿å‡ºé”™ä¹Ÿæ‰§è¡Œ getList(),确保页面能正常加载
      getList();
      getnum();
    }
  });
</script>
<style scoped lang="scss">
  @import "../../../styles/sales-common.scss";
  .u-divider {
    margin: 0 !important;
  }
  // æ–‡æ¡£å›¾æ ‡æ ·å¼ - è¦†ç›–公共样式中的背景色
  .document-icon {
    background: #ed8d05;
  }
  // æµ®åŠ¨æŒ‰é’®æ ·å¼ - è¦†ç›–公共样式中的背景色
  .fab-button {
    background: #ed8d05;
  }
  // ç‰¹æœ‰æ ·å¼
  .detail-row-user {
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
  .detail-row-approveReason {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 8px;
  }
  .detail-value.highlightBlue {
    color: #2979ff;
    font-weight: 500;
  }
  .detail-value.highlightYellow {
    color: #ed8d05;
    font-weight: 500;
  }
  .approver-value {
    display: flex;
    justify-content: flex-end;
  }
  .approver-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: #f0f6ff;
    color: #2b7cff;
    border: 1px solid #e0efff;
    border-radius: 999px;
    padding: 4px 10px;
    max-width: 100%;
  }
  .approver-name {
    font-size: 12px;
    color: #2b7cff;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .actions {
    display: flex;
    gap: 10px;
    align-items: center;
    justify-content: flex-end;
    margin-top: 18rpx;
  }
  .action-btn {
    border-radius: 16px;
    height: 28px;
    line-height: 28px;
    padding: 0 12px;
  }
  .topbox {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-left: 20px;
    margin-right: 20px;
    margin-top: 10px;
  }
  .boxItem {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    background-color: #fff;
    width: 24%;
    padding-top: 10px;
    padding-bottom: 10px;
    border-radius: 6px;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
  }
  .boxItem-num {
    margin-bottom: 5px;
    font-weight: 500;
    color: #2979ff;
  }
  // ä¼šè®®çºªè¦æ ·å¼
  .meeting-content {
    padding: 15px;
    line-height: 1.6;
    font-size: 14px;
  }
  .content-html {
    color: #333;
    word-break: break-word;
  }
  .content-html :deep(p) {
    margin-bottom: 10px;
  }
  .content-html :deep(ul),
  .content-html :deep(ol) {
    margin-bottom: 10px;
    padding-left: 20px;
  }
  .content-html :deep(li) {
    margin-bottom: 5px;
  }
  .no-content {
    color: #999;
    text-align: center;
    padding: 20px 0;
    font-size: 14px;
  }
  // u-collapse æ ·å¼ä¼˜åŒ–
  :deep(.u-collapse) {
    margin-top: 10px;
    border-radius: 6px;
    overflow: hidden;
  }
  :deep(.u-collapse-item__header) {
    font-size: 14px;
    font-weight: 500;
  }
  :deep(.u-collapse-item__content) {
    background-color: #fafafa;
    border-top: 1px solid #f0f0f0;
  }
</style>
src/pages/managementMeetings/meetingList/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,537 @@
<template>
  <view class="meeting-list">
    <PageHeader title="会议列表"
                @back="goBack" />
    <!-- æŸ¥è¯¢è¡¨å• -->
    <view class="search-section">
      <view class="search-bar">
        <view class="search-input">
          <up-input class="search-text"
                    placeholder="查询日期"
                    @click.stop="showDatePicker"
                    v-model="queryForm.meetingDate"
                    clearable />
        </view>
        <view class="filter-button"
              @click="clearDate">
          <u-icon name="close-circle-fill"
                  size="24"
                  color="#999"></u-icon>
        </view>
      </view>
    </view>
    <!-- æ—¥æœŸé€‰æ‹©å™¨ -->
    <up-datetime-picker v-model="datePickerValue"
                        mode="date"
                        :show="showDatePickerDialog"
                        @confirm="handleDateConfirm"
                        @cancel="showDatePickerDialog = false"
                        format="YYYY-MM-DD"
                        value-format="YYYY-MM-DD" />
    <!-- ä¼šè®®å®¤ä½¿ç”¨æƒ…况 -->
    <view class="table-container">
      <scroll-view scroll-x="true"
                   style="width: 100%;">
        <view class="time-table">
          <!-- è¡¨å¤´ -->
          <view class="table-header">
            <view class="header-cell room-header">会议室</view>
            <view v-for="timeSlot in timeSlots"
                  :key="timeSlot.value"
                  class="header-cell time-header">
              {{ timeSlot.label }}
            </view>
          </view>
          <!-- è¡¨æ ¼å†…容 -->
          <view class="table-body">
            <view v-for="room in roomUsage"
                  :key="room.id"
                  class="table-row">
              <view class="cell room-cell">{{ room.name }}</view>
              <view class="cells-container">
                <template v-for="(cell, index) in generateMeetingCells(room)"
                          :key="index">
                  <view class="cell content-cell"
                        :class="[cell.type, `status-${cell.meeting?.status || '0'}`]"
                        :style="{ width: `${cell.span * 120}rpx` }"
                        @click="viewMeetingDetails(cell)">
                    <view v-if="cell.type === 'meeting'"
                          class="meeting-content">
                      <view class="meeting-title">{{ cell.meeting.title }}</view>
                      <view class="meeting-time">{{ cell.startTime }}-{{ cell.endTime }}</view>
                    </view>
                    <view v-else
                          class="free-content">
                      ç©ºé—²
                    </view>
                  </view>
                </template>
              </view>
            </view>
          </view>
        </view>
      </scroll-view>
    </view>
    <!-- ä¼šè®®è¯¦æƒ…对话框 -->
    <u-popup :show="detailDialogVisible"
             mode="center"
             customStyle="width: 80%;"
             :round="10">
      <view class="dialog-content">
        <view class="dialog-header">
          <text class="dialog-title">会议详情</text>
          <up-icon name="close"
                   @click="detailDialogVisible = false"
                   class="close-icon"></up-icon>
        </view>
        <view v-if="currentMeeting"
              class="dialog-body">
          <view class="detail-item">
            <text class="detail-label">会议主题</text>
            <text class="detail-value">{{ currentMeeting.title }}</text>
          </view>
          <view class="detail-item">
            <text class="detail-label">会议室</text>
            <text class="detail-value">{{ currentMeeting.room }}</text>
          </view>
          <view class="detail-item">
            <text class="detail-label">会议时间</text>
            <text class="detail-value">{{ currentMeeting.time }}</text>
          </view>
          <view class="detail-item">
            <text class="detail-label">主持人</text>
            <text class="detail-value">{{ currentMeeting.host }}</text>
          </view>
          <view class="detail-item">
            <text class="detail-label">参会人数</text>
            <text class="detail-value">{{ currentMeeting.participants }}人</text>
          </view>
          <view class="detail-item">
            <text class="detail-label">会议说明</text>
            <text class="detail-value">{{ currentMeeting.description }}</text>
          </view>
        </view>
        <view class="dialog-footer">
          <u-button @click="detailDialogVisible = false">关闭</u-button>
        </view>
      </view>
    </u-popup>
  </view>
</template>
<script setup>
  import { ref, reactive, onMounted } from "vue";
  import PageHeader from "@/components/PageHeader.vue";
  import { getMeetingUseList } from "@/api/managementMeetings/meeting.js";
  import dayjs from "dayjs";
  // æŸ¥è¯¢è¡¨å•
  const queryForm = reactive({
    meetingDate: dayjs().format("YYYY-MM-DD"),
  });
  const loading = ref(false);
  const timeSlots = ref([]);
  const roomUsage = ref([]);
  const currentMeeting = ref(null);
  const detailDialogVisible = ref(false);
  const showDatePickerDialog = ref(false);
  const datePickerValue = ref(Date.now());
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  // æ¸…空日期
  const clearDate = () => {
    datePickerValue.value = Date.now();
    queryForm.meetingDate = dayjs().format("YYYY-MM-DD");
    getList();
  };
  // æ˜¾ç¤ºæ—¥æœŸé€‰æ‹©å™¨
  const showDatePicker = () => {
    if (queryForm.meetingDate) {
      datePickerValue.value = new Date(queryForm.meetingDate).getTime();
    } else {
      datePickerValue.value = Date.now();
    }
    console.log(datePickerValue.value, "datePickerValue.value");
    showDatePickerDialog.value = true;
  };
  // å¤„理日期选择确认
  const handleDateConfirm = value => {
    // dayjs().format("YYYY-MM-DD")
    console.log(value, "value");
    queryForm.meetingDate = dayjs(value.value).format("YYYY-MM-DD");
    showDatePickerDialog.value = false;
    getList();
  };
  // èŽ·å–åˆ—è¡¨æ•°æ®
  const getList = () => {
    handleSearch();
  };
  // åˆå§‹åŒ–时间槽(以半小时为间隔,从8:00到17:30)
  const initTimeSlots = () => {
    const slots = [];
    // ç”Ÿæˆ8:00到17:00的时间段
    for (let hour = 8; hour <= 17; hour++) {
      // æ·»åŠ æ•´ç‚¹
      slots.push({
        label: `${hour.toString().padStart(2, "0")}:00`,
        value: `${hour.toString().padStart(2, "0")}:00`,
      });
      // æ·»åŠ åŠç‚¹ï¼Œç›´åˆ°17:30
      if (hour <= 17) {
        slots.push({
          label: `${hour.toString().padStart(2, "0")}:30`,
          value: `${hour.toString().padStart(2, "0")}:30`,
        });
      }
    }
    // ç§»é™¤æœ€åŽä¸€ä¸ª18:00的时间段
    if (slots.length > 0 && slots[slots.length - 1].value === "18:00") {
      slots.pop();
    }
    timeSlots.value = slots;
    console.log(timeSlots.value, "timeSlots.value");
  };
  // ç”Ÿæˆä¼šè®®å®¤çš„æ—¶é—´å•元格
  const generateMeetingCells = room => {
    const cells = [];
    const meetings = room.meetings || [];
    const occupiedSlots = new Set();
    // å¤„理每个会议
    for (const meeting of meetings) {
      const startIdx = timeSlots.value.findIndex(
        slot => slot.value === meeting.startTime
      );
      let endIdx = timeSlots.value.findIndex(
        slot => slot.value === meeting.endTime
      );
      if (endIdx === -1) {
        endIdx = timeSlots.value.length;
      }
      if (startIdx !== -1) {
        // æ ‡è®°è¢«å ç”¨çš„æ—¶é—´æ®µ
        for (let i = startIdx; i < endIdx; i++) {
          if (timeSlots.value[i]) {
            occupiedSlots.add(timeSlots.value[i].value);
          }
        }
        // åˆ›å»ºä¼šè®®å•元格
        cells.push({
          type: "meeting",
          meeting: meeting,
          span: endIdx - startIdx,
          startTime: meeting.startTime,
          endTime: meeting.endTime,
        });
      }
    }
    // å¤„理空闲时间段
    for (let i = 0; i < timeSlots.value.length; i++) {
      const slot = timeSlots.value[i];
      if (!occupiedSlots.has(slot.value)) {
        // æŸ¥æ‰¾è¿žç»­çš„空闲时间段
        let span = 1;
        while (
          i + span < timeSlots.value.length &&
          !occupiedSlots.has(timeSlots.value[i + span].value)
        ) {
          occupiedSlots.add(timeSlots.value[i + span].value);
          span++;
        }
        cells.push({
          type: "free",
          span: span,
          time: slot.value,
        });
      }
    }
    // æŒ‰æ—¶é—´æŽ’序
    cells.sort((a, b) => {
      const timeA = a.startTime || a.time;
      const timeB = b.startTime || b.time;
      return (
        timeSlots.value.findIndex(s => s.value === timeA) -
        timeSlots.value.findIndex(s => s.value === timeB)
      );
    });
    console.log(cells, "cells");
    return cells;
  };
  // æŸ¥çœ‹ä¼šè®®è¯¦æƒ…
  const viewMeetingDetails = cell => {
    console.log(cell, "cell");
    if (cell && cell.type === "meeting") {
      currentMeeting.value = cell.meeting;
      detailDialogVisible.value = true;
    } else {
      uni.showToast({
        title: "该时间段会议室空闲",
        icon: "info",
      });
    }
  };
  // æŸ¥è¯¢æŒ‰é’®æ“ä½œ
  const handleSearch = async () => {
    loading.value = true;
    try {
      const resp = await getMeetingUseList({ ...queryForm });
      roomUsage.value = resp.data;
    } catch (error) {
      uni.showToast({
        title: "获取数据失败",
        icon: "error",
      });
    } finally {
      loading.value = false;
    }
  };
  // é‡ç½®æœç´¢è¡¨å•
  const resetSearch = () => {
    queryForm.meetingDate = dayjs().format("YYYY-MM-DD");
  };
  // é¡µé¢åŠ è½½æ—¶èŽ·å–æ•°æ®
  onMounted(() => {
    // åˆå§‹åŒ–æ—¶é—´æ§½
    initTimeSlots();
    // é»˜è®¤æŸ¥è¯¢ä»Šå¤©çš„æ•°æ®
    handleSearch();
  });
</script>
<style scoped lang="scss">
  @import "../../../styles/sales-common.scss";
  .meeting-list {
    min-height: 100vh;
    background: #f8f9fa;
    padding: 16rpx;
  }
  .search-section {
    background: #fff;
    padding: 16rpx;
    border-radius: 8rpx;
    margin-bottom: 16rpx;
    box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
  }
  .search-bar {
    display: flex;
    align-items: center;
    gap: 12rpx;
  }
  .search-input {
    flex: 1;
  }
  .filter-button {
    padding: 12rpx 16rpx;
    background: #f5f7fa;
    border-radius: 4rpx;
  }
  .form-buttons {
    display: flex;
    gap: 12rpx;
    margin-top: 16rpx;
    justify-content: center;
  }
  .table-container {
    margin-top: 16rpx;
    overflow-x: auto;
  }
  .time-table {
    width: 100%;
  }
  .table-header {
    display: flex;
    border: 1rpx solid #e4e7ed;
    background: #f5f7fa;
  }
  .header-cell {
    padding: 12rpx 8rpx;
    text-align: center;
    font-weight: bold;
    border-right: 1rpx solid #e4e7ed;
  }
  .room-header {
    width: 120rpx;
    flex-shrink: 0;
  }
  .time-header {
    width: 120rpx;
    flex-shrink: 0;
  }
  .table-body {
    border: 1rpx solid #e4e7ed;
    border-top: none;
  }
  .table-row {
    display: flex;
    border-top: 1rpx solid #e4e7ed;
  }
  .table-row:first-child {
    border-top: none;
  }
  .cell {
    padding: 16rpx 8rpx;
    text-align: center;
    border-right: 1rpx solid #e4e7ed;
    display: flex;
    align-items: center;
    justify-content: center;
    word-break: break-word;
    line-height: 1.2;
  }
  .room-cell {
    width: 120rpx;
    font-weight: bold;
    flex-shrink: 0;
    background: #f9fafc;
  }
  .cells-container {
    display: flex;
  }
  .content-cell {
    min-height: 120rpx;
    cursor: pointer;
    transition: all 0.3s;
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: 16rpx;
    box-sizing: border-box;
  }
  .content-cell:active {
    opacity: 0.8;
  }
  .free {
    color: #f56c6c;
  }
  .meeting {
    display: flex;
    flex-direction: column;
    justify-content: center;
  }
  .status-1 {
    background-color: #fef0f0;
    color: #d14646;
  }
  .status-0 {
    background-color: #ecf5ff;
    color: #409eff;
  }
  .meeting-content {
    width: 100%;
  }
  .meeting-title {
    font-weight: bold;
    margin-bottom: 8rpx;
    font-size: 24rpx;
  }
  .meeting-time {
    font-size: 20rpx;
    opacity: 0.8;
  }
  .free-content {
    color: #909399;
  }
  /* å¯¹è¯æ¡†æ ·å¼ */
  .dialog-content {
    padding: 24rpx;
  }
  .dialog-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 24rpx;
    padding-bottom: 16rpx;
    border-bottom: 1rpx solid #e4e7ed;
  }
  .dialog-title {
    font-size: 32rpx;
    font-weight: bold;
    color: #303133;
  }
  .close-icon {
    font-size: 32rpx;
    color: #909399;
  }
  .dialog-body {
    margin-bottom: 24rpx;
  }
  .detail-item {
    display: flex;
    margin-bottom: 16rpx;
    padding: 8rpx 0;
    border-bottom: 1rpx solid #f0f0f0;
  }
  .detail-label {
    width: 140rpx;
    font-weight: bold;
    color: #606266;
  }
  .detail-value {
    flex: 1;
    color: #303133;
  }
  .dialog-footer {
    display: flex;
    justify-content: center;
    margin-top: 16rpx;
  }
</style>
src/pages/managementMeetings/meetingSettings/detail.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,344 @@
<template>
  <view class="client-visit-detail">
    <PageHeader title="会议室详情"
                @back="goBack" />
    <u-form ref="formRef"
            label-width="90">
      <!-- å®¢æˆ·ä¿¡æ¯ -->
      <u-cell-group title="会议室信息">
        <u-form-item label="会议室名称"
                     prop="name"
                     required
                     border-bottom>
          <u-input v-model="form.name"
                   placeholder="请输入会议室名称" />
        </u-form-item>
        <u-form-item label="位置"
                     required
                     prop="location"
                     border-bottom>
          <u-input v-model="form.location"
                   placeholder="请输入位置" />
        </u-form-item>
        <u-form-item label="容纳人数"
                     required
                     prop="capacity"
                     border-bottom>
          <u-input v-model="form.capacity"
                   placeholder="请输入容纳人数" />
        </u-form-item>
        <u-form-item label="设备配置"
                     prop="equipment"
                     border-bottom>
          <u-input v-model="form.equipment"
                   readonly
                   placeholder="请选择设备配置"
                   @click="showEquipmentSheet = true" />
          <template #right>
            <up-icon name="arrow-right"
                     @click="openEquipmentSheet"></up-icon>
          </template>
        </u-form-item>
        <u-form-item label="状态"
                     prop="status"
                     border-bottom>
          <u-input v-model="statusname"
                   readonly
                   placeholder="请选择状态"
                   @click="showStatusSheet = true" />
          <template #right>
            <up-icon name="arrow-right"
                     @click="showStatusSheet = true"></up-icon>
          </template>
        </u-form-item>
        <u-form-item label="备注"
                     prop="remark"
                     border-bottom>
          <u-input v-model="form.remark"
                   placeholder="请输入备注" />
        </u-form-item>
      </u-cell-group>
      <!-- æäº¤æŒ‰é’® -->
      <view class="footer-btns">
        <u-button class="cancel-btn"
                  @click="goBack">取消</u-button>
        <u-button class="sign-btn"
                  type="primary"
                  @click="handleSubmit"
                  :loading="loading">保存</u-button>
      </view>
    </u-form>
    <!-- è®¾å¤‡é…ç½®é€‰æ‹©å™¨ -->
    <u-popup :show="showEquipmentSheet"
             mode="bottom"
             @close="showEquipmentSheet = false"
             height="200px">
      <view class="popup-content">
        <view class="popup-body">
          <u-checkbox-group v-model="form.equipment"
                            @change="handleEquipmentChange"
                            icon-placement="right"
                            placement="row">
            <view style="width:100%;padding:10px;margin-top:20px;">
              <u-checkbox v-for="option in equipmentOptions"
                          :key="option.value"
                          :name="option.value"
                          :label="option.name"
                          class="checkbox-item"></u-checkbox>
            </view>
          </u-checkbox-group>
        </view>
      </view>
    </u-popup>
    <!-- çŠ¶æ€é€‰æ‹©å™¨ -->
    <up-action-sheet :show="showStatusSheet"
                     :actions="statusOptions"
                     @select="onStatusSelect"
                     @close="showStatusSheet = false" />
  </view>
</template>
<script setup>
  // æ›¿æ¢ toast æ–¹æ³•
  defineOptions({ name: "meeting-settings-detail" });
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
  import { ref, onMounted } from "vue";
  import PageHeader from "@/components/PageHeader.vue";
  import useUserStore from "@/store/modules/user";
  import { saveRoom } from "@/api/managementMeetings/meetingSettings";
  const userStore = useUserStore();
  // è¡¨å•数据
  const form = ref({
    name: "",
    location: "",
    capacity: "",
    equipment: [],
    status: "",
    remark: "",
  });
  const equipmentOptions = ref([
    { value: "投影仪", name: "投影仪" },
    { value: "电视", name: "电视" },
    { value: "音响", name: "音响" },
    { value: "电话", name: "电话" },
    { value: "视频会议系统", name: "视频会议系统" },
    { value: "白板", name: "白板" },
    { value: "写字板", name: "写字板" },
    { value: "无线网络", name: "无线网络" },
  ]);
  const statusOptions = ref([
    { value: "1", name: "启用" },
    { value: "0", name: "禁用" },
  ]);
  //// é¡µé¢çŠ¶æ€
  const loading = ref(false);
  const formRef = ref(null);
  const showEquipmentSheet = ref(false);
  const showStatusSheet = ref(false);
  const openEquipmentSheet = () => {
    showEquipmentSheet.value = true;
  };
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  const statusname = ref("");
  // çŠ¶æ€é€‰æ‹©
  const onStatusSelect = action => {
    form.value.status = action.value;
    statusname.value = action.name;
    showStatusSheet.value = false;
  };
  // è®¾å¤‡é…ç½®é€‰æ‹©
  const handleEquipmentChange = val => {
    form.value.equipment = val;
    console.log("form.value.equipment", form.value.equipment);
  };
  // æäº¤è¡¨å•
  const handleSubmit = async () => {
    if (!form.value.name) {
      showToast("请输入会议室名称");
      return;
    }
    if (!form.value.location) {
      showToast("请输入位置");
      return;
    }
    if (!form.value.capacity) {
      showToast("请输入容纳人数");
      return;
    }
    try {
      loading.value = true;
      form.value.equipment = form.value.equipment.join(",");
      form.value.status = Number(form.value.status);
      form.value.capacity = Number(form.value.capacity);
      // ä½¿ç”¨å®‰å…¨æµ…拷贝,避免对象展开在某些运行时抛错
      console.log("form.value", form.value);
      saveRoom(form.value).then(res => {
        if (res.code !== 200) {
          showToast("保存失败,请重试");
          return;
        }
        loading.value = false;
        showToast("保存成功");
        setTimeout(() => {
          goBack();
        }, 500);
      });
    } catch (e) {
      loading.value = false;
      console.error("保存失败:", e);
      showToast("保存失败,请重试");
    }
  };
  // åˆå§‹åŒ–页面数据
  const initPageData = () => {
    // ä»Žæœ¬åœ°å­˜å‚¨ä¸­èŽ·å–ä¼šè®® room æ•°æ®
    const meetingRoom = uni.getStorageSync("meetingRoom");
    if (meetingRoom) {
      form.value = JSON.parse(JSON.stringify(meetingRoom));
      if (meetingRoom.equipment) {
        if (Array.isArray(meetingRoom.equipment)) {
          form.value.equipment = meetingRoom.equipment;
        } else {
          form.value.equipment = meetingRoom.equipment.split(",");
        }
      }
      statusname.value = meetingRoom.status === 1 ? "启用" : "禁用";
      // æ¸…除本地存储中的数据,避免下次打开时仍然显示
      uni.removeStorageSync("meetingRoom");
    }
  };
  onMounted(() => {
    initPageData();
  });
</script>
<style scoped lang="scss">
  @import "@/static/scss/form-common.scss";
  .client-visit {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 5rem;
  }
  .footer-btns {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    background: #fff;
    display: flex;
    justify-content: space-around;
    align-items: center;
    padding: 0.75rem 0;
    box-shadow: 0 -0.125rem 0.5rem rgba(0, 0, 0, 0.05);
    z-index: 1000;
  }
  .cancel-btn {
    font-weight: 400;
    font-size: 1rem;
    color: #666;
    background: #f5f5f5;
    border: 1px solid #ddd;
    width: 45%;
    height: 2.5rem;
    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
  }
  .sign-btn {
    font-weight: 500;
    font-size: 1rem;
    color: #fff;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    border: none;
    width: 45%;
    height: 2.5rem;
    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
  }
  .location-icon {
    color: #1989fa;
    font-size: 1.2rem;
  }
  .selector-container {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    height: 100%;
  }
  .selector-text {
    font-size: 14px;
    color: #333;
  }
  .popup-content {
    padding: 20rpx;
  }
  .popup-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20rpx;
  }
  .popup-title {
    font-size: 16px;
    font-weight: bold;
    color: #333;
  }
  .close-icon {
    font-size: 20px;
    color: #999;
  }
  .popup-body {
    max-height: 60vh;
    overflow-y: auto;
    margin-bottom: 20rpx;
  }
  .checkbox-item {
    margin-bottom: 15rpx;
    font-size: 14px;
  }
  .popup-footer {
    display: flex;
    justify-content: space-between;
    gap: 15rpx;
  }
  .cancel-btn-popup {
    flex: 1;
    border-radius: 8rpx;
  }
  .confirm-btn-popup {
    flex: 1;
    border-radius: 8rpx;
  }
  .checkbox-item {
    margin-top: 40rpx;
  }
</style>
src/pages/managementMeetings/meetingSettings/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,244 @@
<template>
  <view class="sales-accoun">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="会议设置"
                @back="goBack" />
    <!-- æœç´¢å’Œç­›é€‰åŒºåŸŸ -->
    <view class="search-section">
      <view class="search-bar">
        <view class="search-input">
          <up-input class="search-text"
                    placeholder="请输入客户名称"
                    v-model="name"
                    @blur="getList"
                    clearable />
        </view>
        <view class="filter-button"
              @click="getList">
          <u-icon name="search"
                  size="24"
                  color="#999"></u-icon>
        </view>
      </view>
    </view>
    <!-- æ‹œè®¿è®°å½•列表 -->
    <view class="ledger-list"
          v-if="visitList.length > 0">
      <view v-for="(item, index) in visitList"
            :key="index">
        <view class="ledger-item">
          <view class="item-header">
            <view class="item-left">
              <view class="document-icon">
                <up-icon name="file-text"
                         size="16"
                         color="#ffffff"></up-icon>
              </view>
              <text class="item-id">会议室名称:{{ item.name || '-' }}</text>
            </view>
          </view>
          <up-divider></up-divider>
          <view class="item-details">
            <!-- <view class="detail-row">
              <text class="detail-label">会议室名称</text>
              <text class="detail-value">{{ item.name || '-' }}</text>
            </view> -->
            <view class="detail-row">
              <text class="detail-label">位置</text>
              <text class="detail-value">{{ item.location || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">容纳人数</text>
              <text class="detail-value">{{ item.capacity || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">设备配置</text>
              <text class="detail-value">{{ item.equipment }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">状态</text>
              <text class="detail-value"
                    :class="{'status-enabled': item.status == 1, 'status-disabled': item.status == 0}">{{ item.status == 1 ? '启用' : '禁用' }}</text>
            </view>
          </view>
          <!-- æŒ‰é’®åŒºåŸŸ -->
          <view class="action-buttons">
            <!-- <u-button type="primary"
                      size="small"
                      class="action-btn"
                      @click="viewDetail(item)">
              ç¼–辑
            </u-button>
            <u-button type="error"
                      size="small"
                      class="action-btn"
                      @click="confirmDelete(item)">
              åˆ é™¤
            </u-button> -->
          </view>
        </view>
      </view>
    </view>
    <view v-else
          class="no-data">
      <text>暂无会议室记录</text>
    </view>
    <!-- æµ®åŠ¨æ–°å¢žæŒ‰é’® -->
    <!-- <view class="fab-button"
          @click="addVisit">
      <up-icon name="plus"
               size="24"
               color="#ffffff"></up-icon>
    </view> -->
  </view>
</template>
<script setup>
  import { ref, onMounted } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import {
    getMeetingRoomList,
    delRoom,
  } from "@/api/managementMeetings/meetingSettings";
  import useUserStore from "@/store/modules/user";
  // æ›¿æ¢ toast æ–¹æ³•
  defineOptions({ name: "client-visit-index" });
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
  import dayjs from "dayjs";
  const userStore = useUserStore();
  // æœç´¢å…³é”®è¯
  const name = ref("");
  // æ‹œè®¿è®°å½•数据
  const visitList = ref([]);
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  const confirmDelete = item => {
    uni.showModal({
      title: "确认删除",
      content: `是否确认删除会议室 ${item.name}?`,
      success: res => {
        if (res.confirm) {
          delRoom(item.id)
            .then(() => {
              showToast("删除成功");
              getList();
            })
            .catch(() => {
              showToast("删除失败");
            });
        }
      },
    });
  };
  // æŸ¥è¯¢åˆ—表
  const getList = () => {
    showLoadingToast("加载中...");
    const params = {
      current: -1,
      size: -1,
      name: name.value,
    };
    getMeetingRoomList(params)
      .then(res => {
        visitList.value = res.data.records;
        closeToast();
      })
      .catch(() => {
        closeToast();
        showToast("获取数据失败");
      });
  };
  // æ˜¾ç¤ºåŠ è½½æç¤º
  const showLoadingToast = message => {
    uni.showLoading({
      title: message,
      mask: true,
    });
  };
  // å…³é—­æç¤º
  const closeToast = () => {
    uni.hideLoading();
  };
  // æ–°å¢žæ‹œè®¿ - è·³è½¬åˆ°ç™»è®°é¡µé¢
  const addVisit = () => {
    uni.navigateTo({
      url: "/pages/managementMeetings/meetingSettings/detail",
    });
  };
  // ç¼–辑
  const viewDetail = item => {
    uni.setStorageSync("meetingRoom", item);
    uni.navigateTo({
      url: "/pages/managementMeetings/meetingSettings/detail",
    });
  };
  onMounted(() => {
    getList();
  });
  onShow(() => {
    getList();
  });
</script>
<style scoped lang="scss">
  @import "../../../styles/sales-common.scss";
  // é¡µé¢ç‰¹å®šçš„æ ·å¼è¦†ç›–
  .sales-accoun {
    min-height: 100vh;
    background: #f8f9fa;
    position: relative;
    padding-bottom: 80px;
  }
  // ç‰¹å®šçš„图标样式
  .document-icon {
    background: #667eea; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„背景色
  }
  // ç‰¹æœ‰æ ·å¼
  .visit-status {
    display: flex;
    align-items: center;
  }
  .detail-value {
    word-break: break-all; // ä¿ç•™é¡µé¢ç‰¹æœ‰çš„æ–‡æœ¬æ¢è¡Œæ ·å¼
    color: #333; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„æ–‡æœ¬é¢œè‰²
  }
  // çŠ¶æ€æ ·å¼
  .status-enabled {
    color: #28a745; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„æˆåŠŸé¢œè‰²
  }
  .status-disabled {
    color: #dc3545; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„错误颜色
  }
  // ç‰¹å®šçš„æµ®åŠ¨æŒ‰é’®æ ·å¼
  .fab-button {
    background: #667eea; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„背景色
    box-shadow: 0 4px 16px rgba(102, 126, 234, 0.3); // ä¿æŒé¡µé¢ç‰¹æœ‰çš„阴影效果
  }
</style>
src/pages/managementMeetings/meetingSettings/view.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,178 @@
<template>
  <view class="client-visit-detail">
    <PageHeader title="客户拜访详情" @back="goBack" />
    <!-- å†…容容器 -->
    <view class="content-container">
      <!-- å®¢æˆ·ä¿¡æ¯ -->
      <view class="section">
        <view class="section-title">客户信息</view>
        <view class="info-item">
          <text class="info-label">客户名称</text>
          <text class="info-value">{{ form.customerName || '-' }}</text>
        </view>
        <view class="info-item">
          <text class="info-label">联系人</text>
          <text class="info-value">{{ form.contact || '-' }}</text>
        </view>
        <view class="info-item">
          <text class="info-label">联系电话</text>
          <text class="info-value">{{ form.contactPhone || '-' }}</text>
        </view>
      </view>
      <!-- æ‹œè®¿ä¿¡æ¯ -->
      <view class="section">
        <view class="section-title">拜访信息</view>
        <view class="info-item">
          <text class="info-label">拜访目的</text>
          <text class="info-value">{{ form.purposeVisit || '-' }}</text>
        </view>
        <view class="info-item">
          <text class="info-label">拜访时间</text>
          <text class="info-value">{{ form.purposeDate || '-' }}</text>
        </view>
        <view class="info-item">
          <text class="info-label">拜访地点</text>
          <text class="info-value multi-line">{{ form.visitAddress || '-' }}</text>
        </view>
        <view class="info-item">
          <text class="info-label">拜访人</text>
          <text class="info-value">{{ form.visitingPeople || '-' }}</text>
        </view>
        <view class="info-item" v-if="form.latitude && form.longitude">
          <text class="info-label">经纬度</text>
          <text class="info-value">{{ form.latitude }}, {{ form.longitude }}</text>
        </view>
      </view>
      <!-- å¤‡æ³¨ä¿¡æ¯ -->
      <view class="section">
        <view class="section-title">备注信息</view>
        <view class="info-item remark-item">
          <text class="info-label">备注</text>
          <text class="info-value multi-line">{{ form.remark }}</text>
        </view>
      </view>
    </view>
  </view>
</template>
<script setup>
// æ›¿æ¢ toast æ–¹æ³•
const showToast = (message) => {
  uni.showToast({
    title: message,
    icon: 'none'
  })
}
import { ref, onMounted } from 'vue'
import PageHeader from '@/components/PageHeader.vue'
import useUserStore from "@/store/modules/user"
const userStore = useUserStore()
// è¡¨å•数据
const form = ref({
  customerName: '',
  contact: '',
  contactPhone: '',
  visitingPeople: '',
  purposeVisit: '',
  purposeDate: '',
  visitAddress: '',
  latitude: '',
  longitude: '',
  locationAddress: '',
  remark: ''
})
// è¿”回上一页
const goBack = () => {
  // è¿”回时清除本地存储的ID
  uni.removeStorageSync('clientVisit')
  uni.navigateBack()
}
// åˆå§‹åŒ–页面数据
const initPageData = () => {
  // ä»Žæœ¬åœ°å­˜å‚¨èŽ·å–æ‹œè®¿è®°å½•è¯¦æƒ…
  const row = uni.getStorageSync('clientVisit')
  if (row) {
    form.value = { ...row }
  } else {
    showToast('暂无拜访记录数据')
  }
}
onMounted(() => {
  initPageData()
})
</script>
<style scoped lang="scss">
@import '@/static/scss/form-common.scss';
.client-visit-detail {
  min-height: 100vh;
  background-color: #f8f9fa;
}
.content-container {
  padding: 16px;
}
.section {
  background-color: #ffffff;
  border-radius: 12px;
  margin-bottom: 16px;
  overflow: hidden;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.section-title {
  font-size: 16px;
  font-weight: 600;
  color: #333333;
  padding: 16px 16px 12px;
  border-bottom: 1px solid #f0f0f0;
}
.info-item {
  display: flex;
  padding: 14px 16px;
  border-bottom: 1px solid #f8f8f8;
  align-items: flex-start;
}
.info-item:last-child {
  border-bottom: none;
}
.info-label {
  font-size: 14px;
  color: #666666;
  min-width: 80px;
  flex-shrink: 0;
  line-height: 22px;
}
.info-value {
  font-size: 14px;
  color: #333333;
  flex: 1;
  line-height: 22px;
  text-align: right;
}
.multi-line {
  text-align: left;
  word-break: break-all;
  line-height: 1.6;
}
.remark-item {
  padding-bottom: 16px;
}
</style>
src/pages/managementMeetings/rulesRegulationsManagement/detail.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,455 @@
<template>
  <view class="client-visit-detail">
    <PageHeader :title="detailType === 1 ? '发布制度' : '制度详情'"
                @back="goBack" />
    <u-form ref="formRef"
            label-width="90">
      <!-- å®¢æˆ·ä¿¡æ¯ -->
      <!-- <u-cell-group title="知识信息"> -->
      <u-form-item label="制度编号"
                   prop="regulationNum"
                   required
                   border-bottom>
        <u-input v-model="form.regulationNum"
                 :readonly="readonly"
                 placeholder="请输入制度编号" />
      </u-form-item>
      <u-form-item label="制度标题"
                   prop="title"
                   required
                   border-bottom>
        <u-input v-model="form.title"
                 :readonly="readonly"
                 placeholder="请输入制度标题" />
      </u-form-item>
      <u-form-item label="制度分类"
                   prop="type"
                   required
                   border-bottom>
        <u-input v-model="equipmentname"
                 readonly
                 placeholder="请选择制度分类"
                 @click="showEquipmentSheet = true" />
        <template v-if="!readonly"
                  #right>
          <up-icon name="arrow-right"
                   @click="openEquipmentSheet"></up-icon>
        </template>
      </u-form-item>
      <u-form-item label="制度内容"
                   prop="scenario"
                   required
                   border-bottom>
        <u-textarea v-model="form.content"
                    type="textarea"
                    rows="4"
                    :disabled="readonly"
                    placeholder="请输入制度内容" />
      </u-form-item>
      <u-form-item label="制度版本"
                   prop="title"
                   border-bottom>
        <u-input v-model="form.version"
                 :readonly="readonly"
                 placeholder="请输入制度版本" />
      </u-form-item>
      <u-form-item label="生效时间"
                   prop="status"
                   required
                   border-bottom>
        <u-input v-model="form.effectiveTime"
                 readonly
                 placeholder="请选择生效时间"
                 @click="showEffectiveTimeSheet = true" />
        <template v-if="!readonly"
                  #right>
          <up-icon name="arrow-right"
                   @click="showEffectiveTimeSheet = true"></up-icon>
        </template>
      </u-form-item>
      <u-form-item label="适用范围"
                   required
                   prop="remark"
                   border-bottom>
        <up-checkbox-group v-model="form.scope"
                           :disabled="readonly"
                           placement="column"
                           @change="checkboxChange">
          <up-checkbox :customStyle="{marginBottom: '8px'}"
                       v-for="(item, index) in checkboxList1"
                       :key="index"
                       :label="item.name"
                       :name="item.value">
          </up-checkbox>
        </up-checkbox-group>
      </u-form-item>
      <u-form-item label="需要确认"
                   prop="solution"
                   border-bottom>
        <up-switch :disabled="readonly"
                   v-model="form.requireConfirm"></up-switch>
      </u-form-item>
      <!-- </u-cell-group> -->
      <!-- æäº¤æŒ‰é’® -->
      <view v-if="!readonly"
            class="footer-btns">
        <u-button class="cancel-btn"
                  @click="goBack">取消</u-button>
        <u-button class="sign-btn"
                  type="primary"
                  @click="handleSubmit"
                  :loading="loading">保存</u-button>
      </view>
    </u-form>
    <!-- è®¾å¤‡é…ç½®é€‰æ‹©å™¨ -->
    <up-action-sheet :show="showEquipmentSheet"
                     :actions="equipmentOptions"
                     @select="handleEquipmentChange"
                     @close="showEquipmentSheet = false" />
    <!-- çŠ¶æ€é€‰æ‹©å™¨ -->
    <up-action-sheet :show="showStatusSheet"
                     :actions="statusOptions"
                     @select="onStatusSelect"
                     @close="showStatusSheet = false" />
    <up-datetime-picker :show="showEffectiveTimeSheet"
                        @confirm="handleEffectiveTimeConfirm"
                        @cancel="showEffectiveTimeSheet = false"
                        v-model="effectiveTime"
                        mode="datetime"></up-datetime-picker>
  </view>
</template>
<script setup>
  // æ›¿æ¢ toast æ–¹æ³•
  defineOptions({ name: "meeting-settings-detail" });
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
  import { ref, onMounted, computed } from "vue";
  import PageHeader from "@/components/PageHeader.vue";
  import useUserStore from "@/store/modules/user";
  import { onLoad } from "@dcloudio/uni-app";
  import {
    addRuleManagement,
    updateRuleManagement,
  } from "@/api/managementMeetings/rulesRegulationsManagement";
  import dayjs from "dayjs";
  const userStore = useUserStore();
  const showEffectiveTimeSheet = ref(false);
  const effectiveTime = ref(new Date());
  // è¡¨å•数据
  const form = ref({
    id: "",
    regulationNum: "",
    title: "",
    category: "",
    content: "",
    version: "",
    status: "active",
    readCount: 0,
    effectiveTime: "",
    scope: [],
    requireConfirm: false,
  });
  const checkboxList1 = ref([
    { name: "全体员工", value: "all" },
    { name: "管理层", value: "manager" },
    { name: "人事部门", value: "hr" },
    { name: "财务部门", value: "finance" },
    { name: "技术部门", value: "tech" },
  ]);
  const equipmentOptions = ref([
    { value: "hr", name: "人事制度" },
    { value: "finance", name: "财务制度" },
    { value: "safety", name: "安全制度" },
    { value: "tech", name: "技术制度" },
  ]);
  const statusOptions = ref([
    { value: "high", name: "显著提升" },
    { value: "medium", name: "一般提升" },
    { value: "low", name: "轻微提升" },
  ]);
  //// é¡µé¢çŠ¶æ€
  const loading = ref(false);
  const formRef = ref(null);
  const showEquipmentSheet = ref(false);
  const showStatusSheet = ref(false);
  const openEquipmentSheet = () => {
    showEquipmentSheet.value = true;
  };
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  const statusname = ref("");
  // çŠ¶æ€é€‰æ‹©
  const onStatusSelect = action => {
    form.value.efficiency = action.value;
    statusname.value = action.name;
    showStatusSheet.value = false;
  };
  const equipmentname = ref("");
  const handleEffectiveTimeConfirm = () => {
    form.value.effectiveTime = dayjs(effectiveTime.value).format(
      "YYYY-MM-DD HH:mm:ss"
    );
    showEffectiveTimeSheet.value = false;
  };
  // åˆ¶åº¦åˆ†ç±»é€‰æ‹©
  const handleEquipmentChange = val => {
    form.value.category = val.value;
    equipmentname.value = val.name;
    showEquipmentSheet.value = false;
  };
  // æäº¤è¡¨å•
  const handleSubmit = async () => {
    if (!form.value.regulationNum) {
      showToast("请输入制度编号");
      return;
    }
    if (!form.value.title) {
      showToast("请输入制度标题");
      return;
    }
    if (!form.value.category) {
      showToast("请选择制度分类");
      return;
    }
    if (!form.value.content) {
      showToast("请输入制度内容");
      return;
    }
    if (!form.value.effectiveTime) {
      showToast("请选择生效时间");
      return;
    }
    if (!form.value.scope.length) {
      showToast("请选择生效范围");
      return;
    }
    try {
      loading.value = true;
      if (detailType.value === 1) {
        addRuleManagement(form.value).then(res => {
          if (res.code !== 200) {
            showToast("保存失败,请重试");
            return;
          }
          loading.value = false;
          showToast("保存成功");
          setTimeout(() => {
            goBack();
          }, 500);
        });
      } else if (detailType.value === 2) {
        updateRuleManagement(form.value).then(res => {
          if (res.code !== 200) {
            showToast("保存失败,请重试");
            return;
          }
          loading.value = false;
          showToast("保存成功");
          setTimeout(() => {
            goBack();
          }, 500);
        });
      }
    } catch (e) {
      loading.value = false;
      console.error("保存失败:", e);
      showToast("保存失败,请重试");
    }
  };
  // åˆå§‹åŒ–页面数据
  const initPageData = () => {
    // ä»Žæœ¬åœ°å­˜å‚¨ä¸­èŽ·å–ä¼šè®® room æ•°æ®
    const meetingRoom = uni.getStorageSync("meetingRoom");
    if (meetingRoom) {
      form.value = JSON.parse(JSON.stringify(meetingRoom));
      if (meetingRoom.equipment) {
        if (Array.isArray(meetingRoom.equipment)) {
          form.value.equipment = meetingRoom.equipment;
        } else {
          form.value.equipment = meetingRoom.equipment.split(",");
        }
      }
      statusname.value = meetingRoom.status === 1 ? "启用" : "禁用";
      // æ¸…除本地存储中的数据,避免下次打开时仍然显示
      uni.removeStorageSync("meetingRoom");
    }
  };
  const readonly = ref(false);
  const detailType = ref(1);
  const knowledgeId = ref("");
  onLoad(options => {
    detailType.value = Number(options.detailType);
    knowledgeId.value = options.id || "";
    // æŸ¥çœ‹æ¨¡å¼è®¾ç½®åªè¯»
    if (detailType.value === 3) {
      readonly.value = true;
    }
  });
  // é‡ç½®è¡¨å•
  const resetForm = () => {
    form.value = {
      id: "",
      regulationNum: "",
      title: "",
      category: "",
      content: "",
      version: "",
      status: "active",
      readCount: 0,
      effectiveTime: "",
      scope: [],
      requireConfirm: false,
    };
    equipmentname.value = "";
    statusname.value = "";
  };
  onMounted(() => {
    console.log(effectiveTime.value, "生效时间");
    // ä»Žæœ¬åœ°å­˜å‚¨ä¸­èŽ·å–çŸ¥è¯†æ•°æ®
    const rulesRegulations = uni.getStorageSync("rulesRegulations");
    initPageData();
    if (rulesRegulations) {
      form.value = JSON.parse(JSON.stringify(rulesRegulations));
    }
    if (detailType.value === 1) {
      resetForm();
    }
    if (detailType.value != 1) {
      equipmentname.value =
        equipmentOptions.value.find(item => item.value == form.value.category)
          ?.name || "";
    }
  });
</script>
<style scoped lang="scss">
  @import "@/static/scss/form-common.scss";
  .client-visit {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 5rem;
  }
  .footer-btns {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    background: #fff;
    display: flex;
    justify-content: space-around;
    align-items: center;
    padding: 0.75rem 0;
    box-shadow: 0 -0.125rem 0.5rem rgba(0, 0, 0, 0.05);
    z-index: 1000;
  }
  .cancel-btn {
    font-weight: 400;
    font-size: 1rem;
    color: #666;
    background: #f5f5f5;
    border: 1px solid #ddd;
    width: 45%;
    height: 2.5rem;
    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
  }
  .sign-btn {
    font-weight: 500;
    font-size: 1rem;
    color: #fff;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    border: none;
    width: 45%;
    height: 2.5rem;
    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
  }
  .location-icon {
    color: #1989fa;
    font-size: 1.2rem;
  }
  .selector-container {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    height: 100%;
  }
  .selector-text {
    font-size: 14px;
    color: #333;
  }
  .popup-content {
    padding: 20rpx;
  }
  .popup-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20rpx;
  }
  .popup-title {
    font-size: 16px;
    font-weight: bold;
    color: #333;
  }
  .close-icon {
    font-size: 20px;
    color: #999;
  }
  .popup-body {
    max-height: 60vh;
    overflow-y: auto;
    margin-bottom: 20rpx;
  }
  .checkbox-item {
    margin-bottom: 15rpx;
    font-size: 14px;
  }
  .popup-footer {
    display: flex;
    justify-content: space-between;
    gap: 15rpx;
  }
  .cancel-btn-popup {
    flex: 1;
    border-radius: 8rpx;
  }
  .confirm-btn-popup {
    flex: 1;
    border-radius: 8rpx;
  }
  .checkbox-item {
    margin-top: 40rpx;
  }
</style>
src/pages/managementMeetings/rulesRegulationsManagement/fileList.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,559 @@
<template>
  <view class="file-list-page">
    <!-- é¡µé¢å¤´éƒ¨ -->
    <PageHeader title="附件管理"
                @back="goBack" />
    <!-- é™„件列表 -->
    <view class="file-list-container">
      <view v-if="fileList.length > 0"
            class="file-list">
        <view v-for="(file, index) in fileList"
              :key="file.id || index"
              class="file-item">
          <!-- æ–‡ä»¶å›¾æ ‡ -->
          <!-- <view class="file-icon"
                :class="getFileIconClass(file.fileType)">
            <up-icon :name="getFileIcon(file.fileType)"
                     size="24"
                     color="#ffffff" />
          </view> -->
          <!-- æ–‡ä»¶ä¿¡æ¯ -->
          <view class="file-info">
            <text class="file-name">{{ file.name }}</text>
            <!-- <text class="file-meta">{{ formatFileSize(file.fileSize) }} Â· {{ file.uploadTime || file.createTime }}</text> -->
          </view>
          <!-- æ“ä½œæŒ‰é’® -->
          <view class="file-actions">
            <!-- <u-button size="small"
                      type="primary"
                      plain
                      @click="previewFile(file)">预览</u-button> -->
            <u-button size="small"
                      type="info"
                      plain
                      @click="downloadFile(file)">下载并预览</u-button>
            <u-button size="small"
                      type="error"
                      plain
                      @click="confirmDelete(file, index)">删除</u-button>
          </view>
        </view>
      </view>
      <!-- ç©ºçŠ¶æ€ -->
      <view v-else
            class="empty-state">
        <up-icon name="document"
                 size="64"
                 color="#c0c4cc" />
        <text class="empty-text">暂无附件</text>
      </view>
    </view>
    <!-- <a rel="nofollow"
       id="downloadLink"
       href="#"
       style="display:none;">下载文本文件</a> -->
    <!-- ä¸Šä¼ æŒ‰é’® -->
    <view class="upload-button"
          @click="chooseFile">
      <up-icon name="plus"
               size="24"
               color="#ffffff" />
      <text class="upload-text">上传附件</text>
    </view>
  </view>
</template>
<script setup>
  import { ref, onMounted } from "vue";
  import PageHeader from "@/components/PageHeader.vue";
  import config from "@/config";
  import { getToken } from "@/utils/auth";
  // import { saveAs } from "file-saver";
  import {
    listRuleFiles,
    addRuleFile,
    delRuleFile,
    upload,
  } from "@/api/managementMeetings/rulesRegulationsManagement";
  import { blobValidate } from "@/utils/ruoyi";
  // é™„件列表
  const fileList = ref([]);
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  // const request = axios.create({
  //   baseURL: "URL.com",
  //   adapter: axiosAdapterUniapp,
  // });
  // èŽ·å–æ–‡ä»¶å›¾æ ‡
  const getFileIcon = fileType => {
    const iconMap = {
      doc: "document",
      docx: "document",
      xls: "grid",
      xlsx: "grid",
      pdf: "document",
      ppt: "copy",
      pptx: "copy",
      txt: "document",
      jpg: "image",
      jpeg: "image",
      png: "image",
      gif: "image",
      zip: "folder",
      rar: "folder",
    };
    return iconMap[fileType.toLowerCase()] || "document";
  };
  // èŽ·å–æ–‡ä»¶å›¾æ ‡æ ·å¼ç±»
  const getFileIconClass = fileType => {
    const colorMap = {
      doc: "blue",
      docx: "blue",
      xls: "green",
      xlsx: "green",
      pdf: "red",
      ppt: "orange",
      pptx: "orange",
      txt: "gray",
      jpg: "purple",
      jpeg: "purple",
      png: "purple",
      gif: "purple",
      zip: "yellow",
      rar: "yellow",
    };
    return colorMap[fileType.toLowerCase()] || "gray";
  };
  // æ ¼å¼åŒ–文件大小
  const formatFileSize = bytes => {
    if (bytes === 0) return "0 B";
    const k = 1024;
    const sizes = ["B", "KB", "MB", "GB"];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
  };
  // é€‰æ‹©æ–‡ä»¶
  const chooseFile = () => {
    uni.chooseImage({
      count: 9,
      sizeType: ["original", "compressed"],
      sourceType: ["album", "camera"],
      success: res => {
        console.log(res, "选择图片成功");
        uploadFiles(res.tempFiles);
      },
      fail: err => {
        console.error("选择图片失败:", err);
        showToast("选择文件失败");
      },
    });
    // uni.chooseFile({
    //   count: 9,
    //   extension: [
    //     ".doc",
    //     ".docx",
    //     ".xls",
    //     ".xlsx",
    //     ".pdf",
    //     ".ppt",
    //     ".pptx",
    //     ".txt",
    //     ".jpg",
    //     ".jpeg",
    //     ".png",
    //     ".gif",
    //     ".zip",
    //     ".rar",
    //   ],
    //   success: res => {
    //     console.log(res, "选择文件成功");
    //     uploadFiles(res.tempFiles);
    //   },
    //   fail: err => {
    //     showToast("选择文件失败");
    //   },
    // });
  };
  // ä¸Šä¼ æ–‡ä»¶
  const uploadFiles = tempFiles => {
    console.log(tempFiles, "上传文件1");
    tempFiles.forEach((tempFile, index) => {
      // æ˜¾ç¤ºä¸Šä¼ ä¸­æç¤º
      uni.showLoading({
        title: "上传中...",
        mask: true,
      });
      console.log(tempFile, "上传文件2");
      // 1. ç›´æŽ¥ä½¿ç”¨ uni.uploadFile ä¸Šä¼ æ–‡ä»¶
      uni.uploadFile({
        url: config.baseUrl + "/file/upload",
        filePath: tempFile.path,
        name: "file",
        header: {
          Authorization: "Bearer " + getToken(),
        },
        success: uploadRes => {
          uni.hideLoading();
          console.log(uploadRes, "上传文件3");
          try {
            const res = JSON.parse(uploadRes.data);
            console.log(res, "上传文件4");
            if (res.code === 200) {
              // 2. æå–文件信息
              const fileName = tempFile.name
                ? tempFile.name
                : tempFile.path.split("/").pop();
              // const fileType = fileName.split(".").pop();
              // 3. æž„造保存文件信息的参数
              const saveData = {
                name: fileName,
                rulesRegulationsManagementId: rulesRegulationsManagementId.value,
                url: res.data.tempPath || "",
              };
              console.log(saveData, "保存文件信息参数");
              // 4. è°ƒç”¨ addRuleFile æŽ¥å£ä¿å­˜æ–‡ä»¶ä¿¡æ¯
              addRuleFile(saveData)
                .then(addRes => {
                  if (addRes.code === 200) {
                    // 5. æ·»åŠ åˆ°æ–‡ä»¶åˆ—è¡¨
                    const newFile = {
                      ...addRes.data,
                      uploadTime: new Date().toLocaleString(),
                    };
                    // fileList.value.push(newFile);
                    getFileList();
                    showToast("上传成功");
                  } else {
                    showToast("保存文件信息失败");
                  }
                })
                .catch(err => {
                  console.error("保存文件信息失败:", err);
                  showToast("保存文件信息失败");
                });
            } else {
              showToast("文件上传失败");
            }
          } catch (e) {
            console.error("解析上传结果失败:", e);
            showToast("上传失败");
          }
        },
        fail: err => {
          uni.hideLoading();
          console.error("上传失败:", err);
          showToast("上传失败");
        },
      });
    });
  };
  // ä¸‹è½½æ–‡ä»¶
  const downloadFile = file => {
    var url =
      config.baseUrl +
      "/common/download?fileName=" +
      encodeURIComponent(file.url) +
      "&delete=true";
    console.log(url, "url");
    uni
      .downloadFile({
        url: url,
        responseType: "blob",
        header: { Authorization: "Bearer " + getToken() },
      })
      .then(res => {
        let osType = uni.getStorageSync("deviceInfo").osName;
        let filePath = res.tempFilePath;
        if (osType === "ios") {
          uni.openDocument({
            filePath: filePath,
            showMenu: true,
            success: res => {
              resolve(res);
            },
            fail: err => {
              console.log("uni.openDocument--fail");
              reject(err);
            },
          });
        } else {
          uni.saveFile({
            tempFilePath: filePath,
            success: fileRes => {
              uni.showToast({
                icon: "none",
                mask: true,
                title:
                  "文件已保存:Android/data/uni.UNI720216F/apps/__UNI__720216F/" +
                  fileRes.savedFilePath, //保存路径
                duration: 3000,
              });
              setTimeout(() => {
                //打开文档查看
                uni.openDocument({
                  filePath: fileRes.savedFilePath,
                  success: function (res) {
                    resolve(fileRes);
                  },
                });
              }, 3000);
            },
            fail: err => {
              console.log("uni.save--fail");
              reject(err);
            },
          });
        }
        // const isBlob = blobValidate(res.data);
        // if (isBlob) {
        //   const blob = new Blob([res.data], { type: "text/plain" });
        //   const url = URL.createObjectURL(blob);
        //   const downloadLink = document.getElementById("downloadLink");
        //   downloadLink.href = url;
        //   downloadLink.download = file.name;
        //   downloadLink.click();
        //   showToast("下载成功");
        // } else {
        //   showToast("下载失败");
        // }
      })
      .catch(err => {
        console.error("下载失败:", err);
        showToast("下载失败");
      });
  };
  // ç¡®è®¤åˆ é™¤
  const confirmDelete = (file, index) => {
    uni.showModal({
      title: "删除确认",
      content: `确定要删除附件 "${file.name}" å—?`,
      success: res => {
        if (res.confirm) {
          deleteFile(file.id, index);
        }
      },
    });
  };
  // åˆ é™¤æ–‡ä»¶
  const deleteFile = (fileId, index) => {
    uni.showLoading({
      title: "删除中...",
      mask: true,
    });
    delRuleFile([fileId])
      .then(res => {
        uni.hideLoading();
        if (res.code === 200) {
          // fileList.value.splice(index, 1);
          getFileList();
          showToast("删除成功");
        } else {
          showToast("删除失败");
        }
      })
      .catch(err => {
        uni.hideLoading();
        showToast("删除失败");
      });
  };
  // æ˜¾ç¤ºæç¤º
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
  const rulesRegulationsManagementId = ref("");
  // é¡µé¢åŠ è½½æ—¶
  onMounted(() => {
    // ä»Ž API èŽ·å–é™„ä»¶åˆ—è¡¨
    getFileList();
    // ä»Žæœ¬åœ°å­˜å‚¨èŽ·å– rulesRegulationsManagementId
    rulesRegulationsManagementId.value = uni.getStorageSync(
      "rulesRegulationsManagement"
    );
  });
  // èŽ·å–é™„ä»¶åˆ—è¡¨
  const getFileList = () => {
    uni.showLoading({
      title: "加载中...",
      mask: true,
    });
    listRuleFiles()
      .then(res => {
        uni.hideLoading();
        if (res.code === 200) {
          fileList.value = res.data.records || [];
        } else {
          showToast("获取附件列表失败");
        }
      })
      .catch(err => {
        uni.hideLoading();
        showToast("获取附件列表失败");
      });
  };
</script>
<style scoped lang="scss">
  @import "../../../styles/sales-common.scss";
  .file-list-page {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 100rpx;
  }
  .file-list-container {
    padding: 20rpx;
  }
  .file-list {
    background: #ffffff;
    border-radius: 8rpx;
    overflow: hidden;
    box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
  }
  .file-item {
    display: flex;
    align-items: center;
    padding: 20rpx;
    border-bottom: 1rpx solid #f0f0f0;
    &:last-child {
      border-bottom: none;
    }
  }
  .file-icon {
    width: 56rpx;
    height: 56rpx;
    border-radius: 8rpx;
    display: flex;
    justify-content: center;
    align-items: center;
    margin-right: 20rpx;
    &.blue {
      background: #409eff;
    }
    &.green {
      background: #67c23a;
    }
    &.red {
      background: #f56c6c;
    }
    &.orange {
      background: #e6a23c;
    }
    &.gray {
      background: #909399;
    }
    &.purple {
      background: #909399;
    }
    &.yellow {
      background: #e6a23c;
    }
  }
  .file-info {
    flex: 1;
    min-width: 0;
  }
  .file-name {
    display: block;
    font-size: 16px;
    color: #303133;
    margin-bottom: 8rpx;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .file-meta {
    display: block;
    font-size: 12px;
    color: #909399;
  }
  .file-actions {
    display: flex;
    gap: 12rpx;
  }
  .empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 100rpx 0;
    background: #ffffff;
    border-radius: 8rpx;
    box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
  }
  .empty-text {
    font-size: 14px;
    color: #909399;
    margin-top: 20rpx;
  }
  .upload-button {
    position: fixed;
    bottom: 40rpx;
    right: 40rpx;
    width: 130rpx;
    height: 130rpx;
    border-radius: 50%;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    box-shadow: 0 4rpx 16rpx rgba(102, 126, 234, 0.4);
    z-index: 1000;
  }
  .upload-text {
    font-size: 10px;
    color: #ffffff;
    margin-top: 4rpx;
  }
  .upload-progress {
    padding: 40rpx 0;
  }
  .upload-progress-text {
    display: block;
    text-align: center;
    margin-top: 20rpx;
    font-size: 14px;
    color: #606266;
  }
</style>
src/pages/managementMeetings/rulesRegulationsManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,451 @@
<template>
  <view class="sales-accoun">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="规章制度管理"
                @back="goBack" />
    <!-- æœç´¢å’Œç­›é€‰åŒºåŸŸ -->
    <view class="search-section">
      <view class="search-bar">
        <view class="search-input">
          <up-input class="search-text"
                    placeholder="请输入制度标题"
                    v-model="name"
                    @blur="getList"
                    clearable />
        </view>
        <view class="filter-button"
              @click="getList">
          <u-icon name="search"
                  size="24"
                  color="#999"></u-icon>
        </view>
      </view>
    </view>
    <!-- æ‹œè®¿è®°å½•列表 -->
    <view class="ledger-list"
          v-if="visitList.length > 0">
      <view v-for="(item, index) in visitList"
            :key="index">
        <view class="ledger-item">
          <view class="item-header">
            <view class="item-left">
              <view class="document-icon">
                <up-icon name="file-text"
                         size="16"
                         color="#ffffff"></up-icon>
              </view>
              <text class="item-id">制度标题:{{ item.title || '-' }}</text>
            </view>
          </view>
          <up-divider></up-divider>
          <view class="item-details">
            <view class="detail-row">
              <text class="detail-label">制度编号</text>
              <text class="detail-value">{{ item.regulationNum || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">分类</text>
              <u-tag size="mini">{{ formatReceiptType(item.category) }}</u-tag>
            </view>
            <view class="detail-row">
              <text class="detail-label">版本</text>
              <text class="detail-value">{{ item.version || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">发布人</text>
              <text class="detail-value">{{ item.createUserName || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">发布时间</text>
              <text class="detail-value">{{ item.createTime || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">状态</text>
              <u-tag size="mini"
                     :type="item.status === 'active' ? 'success' : 'info'">
                {{ item.status === "active" ? '生效中' : '已废止' }}
              </u-tag>
            </view>
            <view class="detail-row">
              <text class="detail-label">已读人数</text>
              <text class="detail-value">{{ item.readCount || '-' }}</text>
            </view>
            <u-collapse border="false"
                        accordion
                        @open="(value) => handleOpen(value, index)">
              <u-collapse-item title="版本历史"
                               border="false"
                               :name="item.category">
                <view class="table-container">
                  <u-table2 :data="item.tableData1"
                            :columns="columns"
                            stripe
                            border />
                </view>
              </u-collapse-item>
              <!-- <u-collapse-item title="阅读状态"
                               border="false"
                               :name="item.id">
                <view class="table-container">
                  <u-table2 :data="item.tableData2"
                            :columns="columns2"
                            stripe
                            border />
                </view>
              </u-collapse-item> -->
            </u-collapse>
          </view>
          <!-- æŒ‰é’®åŒºåŸŸ -->
          <view class="action-buttons">
            <u-button type="info"
                      size="small"
                      class="action-btn"
                      @click="viewDetail(item,3)">
              æŸ¥çœ‹
            </u-button>
            <!-- <u-button type="error"
                      size="small"
                      class="action-btn"
                      @click="handleAbrogate(item)">
              åºŸå¼ƒ
            </u-button>
            <u-button type="primary"
                      size="small"
                      class="action-btn"
                      @click="viewDetail(item,2)">
              ç¼–辑
            </u-button> -->
            <u-button type="primary"
                      size="small"
                      class="action-btn"
                      @click="fileList(item,2)">
              é™„ä»¶
            </u-button>
          </view>
        </view>
      </view>
    </view>
    <view v-else
          class="no-data">
      <text>暂无规章制度</text>
    </view>
    <!-- æµ®åŠ¨æ–°å¢žæŒ‰é’® -->
    <!-- <view class="fab-button"
          @click="addVisit">
      <up-icon name="plus"
               size="24"
               color="#ffffff"></up-icon>
    </view> -->
  </view>
</template>
<script setup>
  import { ref, onMounted, computed } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import { useDict } from "@/utils/dict";
  import PageHeader from "@/components/PageHeader.vue";
  import {
    listRuleManagement,
    getReadingStatusList,
    getReadingStatusByRuleId,
    updateRuleManagement,
    // delKnowledgeBase,
  } from "@/api/managementMeetings/rulesRegulationsManagement";
  import useUserStore from "@/store/modules/user";
  // æ›¿æ¢ toast æ–¹æ³•
  defineOptions({ name: "client-visit-index" });
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
  import dayjs from "dayjs";
  const userStore = useUserStore();
  // åºŸå¼ƒè§„章制度
  const handleAbrogate = item => {
    uni.showModal({
      title: "废弃确认",
      content: `确定要废弃该规章制度吗?`,
      success: res => {
        if (res.confirm) {
          item.status = "repealed";
          updateRuleManagement(item).then(() => {
            showToast("废弃成功");
            getList();
          });
        }
      },
    });
  };
  // é™„件列表
  const fileList = item => {
    console.log(item.id, "item");
    uni.setStorageSync("rulesRegulationsManagement", item.id);
    // // é™„件列表跳转到详情页面
    uni.navigateTo({
      url: "/pages/managementMeetings/rulesRegulationsManagement/fileList",
    });
  };
  const columns = ref([
    { title: "版本号", key: "version", width: 100 },
    { title: "更新时间", key: "updateTime", width: 200 },
    { title: "更新人", key: "createUserName", width: 150 },
    { title: "变更说明", key: "status", width: 100 },
  ]);
  const columns2 = ref([
    { title: "员工姓名", key: "employee", width: 150 },
    { title: "所属部门", key: "department", width: 150 },
    { title: "阅读时间", key: "createTime", width: 200 },
    { title: "确认时间", key: "confirmTime", width: 200 },
    { title: "状态", key: "status", width: 100 },
  ]);
  // æœç´¢å…³é”®è¯
  const name = ref("");
  // æ‹œè®¿è®°å½•数据
  const visitList = ref([]);
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  const { knowledge_type } = useDict("knowledge_type");
  // æ ¼å¼åŒ–回款方式
  const formatReceiptType = params => {
    if (params == "hr") {
      return "人事制度";
    } else if (params == "finance") {
      return "财务制度";
    } else if (params == "safety") {
      return "安全制度";
    } else if (params == "tech") {
      return "技术制度";
    } else {
      return "未知";
    }
  };
  const getTagClass = type => {
    if (type == "high") {
      return "success";
    } else if (type == "medium") {
      return "warning";
    } else if (type == "low") {
      return "info";
    } else {
      return "info";
    }
  };
  const knowledgeTypeOptions = computed(() => knowledge_type?.value || []);
  // èŽ·å–çŸ¥è¯†ç±»åž‹æ ‡ç­¾
  const getKnowledgeTypeLabel = val => {
    console.log(knowledgeTypeOptions, "knowledgeTypeOptions");
    const item = knowledgeTypeOptions.value.find(
      i => String(i.value) === String(val)
    );
    return item ? item.label : val;
  };
  const handleOpen = (value, index) => {
    if (
      value == "hr" ||
      value == "finance" ||
      value == "safety" ||
      value == "tech"
    ) {
      // åŽ†å²ç‰ˆæœ¬
      const params = {
        current: -1,
        size: -1,
        category: value,
      };
      listRuleManagement(params)
        .then(res => {
          visitList.value[index].tableData1 = res.data.records;
          visitList.value[index].tableData1.forEach(item => {
            item.status = item.status == "active" ? "生效中" : "已废止";
          });
        })
        .catch(() => {
          closeToast();
          showToast("获取数据失败");
        });
    } else {
      // é˜…读状态
      getReadingStatusByRuleId(value)
        .then(res => {
          visitList.value[index].tableData2 = res.data;
          visitList.value[index].tableData2.forEach(item => {
            item.status = item.status == "confirmed" ? "已确认" : "未确认";
          });
        })
        .catch(() => {
          closeToast();
          showToast("获取数据失败");
        });
    }
  };
  // æŸ¥è¯¢åˆ—表
  const getList = () => {
    showLoadingToast("加载中...");
    const params = {
      current: -1,
      size: -1,
      title: name.value,
    };
    listRuleManagement(params)
      .then(res => {
        visitList.value = res.data.records;
        closeToast();
      })
      .catch(() => {
        closeToast();
        showToast("获取数据失败");
      });
  };
  // æ˜¾ç¤ºåŠ è½½æç¤º
  const showLoadingToast = message => {
    uni.showLoading({
      title: message,
      mask: true,
    });
  };
  // å…³é—­æç¤º
  const closeToast = () => {
    uni.hideLoading();
  };
  // æ–°å¢žæ‹œè®¿ - è·³è½¬åˆ°ç™»è®°é¡µé¢
  const addVisit = () => {
    uni.navigateTo({
      url: "/pages/managementMeetings/rulesRegulationsManagement/detail?detailType=1",
    });
  };
  // ç¼–辑
  const viewDetail = (item, detailType) => {
    uni.setStorageSync("rulesRegulations", item);
    uni.navigateTo({
      url:
        "/pages/managementMeetings/rulesRegulationsManagement/detail?detailType=" +
        detailType +
        "&id=" +
        item.id,
    });
  };
  // åˆ é™¤ç¡®è®¤
  const confirmDelete = item => {
    uni.showModal({
      title: "删除确认",
      content: `确定要删除知识 "${item.title}" å—?`,
      success: res => {
        if (res.confirm) {
          deleteKnowledge(item.id);
        }
      },
    });
  };
  // æ‰§è¡Œåˆ é™¤
  const deleteKnowledge = id => {
    showLoadingToast("删除中...");
    delKnowledgeBase([id])
      .then(res => {
        closeToast();
        if (res.code === 200) {
          showToast("删除成功");
          getList(); // é‡æ–°èŽ·å–åˆ—è¡¨
        } else {
          showToast("删除失败");
        }
      })
      .catch(() => {
        closeToast();
        showToast("删除失败");
      });
  };
  onMounted(() => {
    getList();
  });
  onShow(() => {
    getList();
  });
</script>
<style scoped lang="scss">
  @import "../../../styles/sales-common.scss";
  // é¡µé¢ç‰¹å®šçš„æ ·å¼è¦†ç›–
  .sales-accoun {
    min-height: 100vh;
    background: #f8f9fa;
    position: relative;
    padding-bottom: 80px;
  }
  // ç‰¹å®šçš„图标样式
  .document-icon {
    background: #667eea; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„背景色
  }
  // ç‰¹æœ‰æ ·å¼
  .visit-status {
    display: flex;
    align-items: center;
  }
  .detail-value {
    word-break: break-all; // ä¿ç•™é¡µé¢ç‰¹æœ‰çš„æ–‡æœ¬æ¢è¡Œæ ·å¼
    color: #333; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„æ–‡æœ¬é¢œè‰²
  }
  // çŠ¶æ€æ ·å¼
  .status-enabled {
    color: #28a745; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„æˆåŠŸé¢œè‰²
  }
  .status-disabled {
    color: #dc3545; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„错误颜色
  }
  // ç‰¹å®šçš„æµ®åŠ¨æŒ‰é’®æ ·å¼
  .fab-button {
    background: #667eea; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„背景色
    box-shadow: 0 4px 16px rgba(102, 126, 234, 0.3); // ä¿æŒé¡µé¢ç‰¹æœ‰çš„阴影效果
  }
  // è¡¨æ ¼å®¹å™¨ï¼Œå®žçŽ°æ¨ªå‘æ»šåŠ¨
  .table-container {
    overflow-x: auto;
    margin: 0 -20rpx;
    padding: 0 20rpx;
  }
  .table-container::-webkit-scrollbar {
    height: 6rpx;
  }
  .table-container::-webkit-scrollbar-track {
    background: #f1f1f1;
    border-radius: 3rpx;
  }
  .table-container::-webkit-scrollbar-thumb {
    background: #c1c1c1;
    border-radius: 3rpx;
  }
  .table-container::-webkit-scrollbar-thumb:hover {
    background: #a8a8a8;
  }
  // .u-table2 {
  //   width: 500px;
  // }
</style>
在上述文件截断后对比
src/pages/managementMeetings/sealManagement/detail.vue src/pages/managementMeetings/sealManagement/index.vue src/pages/procurementManagement/invoiceEntry/add.vue src/pages/procurementManagement/invoiceEntry/index.vue src/pages/procurementManagement/paymentEntry/add.vue src/pages/procurementManagement/paymentEntry/index.vue src/pages/procurementManagement/paymentLedger/detail.vue src/pages/procurementManagement/paymentLedger/index.vue src/pages/procurementManagement/procurementInvoiceLedger/detail.vue src/pages/procurementManagement/procurementInvoiceLedger/index.vue src/pages/procurementManagement/procurementLedger/detail.vue src/pages/procurementManagement/procurementLedger/index.vue src/pages/procurementManagement/receiptPaymentHistory/index.vue src/pages/sales/deliveryLedger/index.vue src/pages/sales/invoiceLedger/detail.vue src/pages/sales/invoiceLedger/index.vue src/pages/sales/invoicingRegistration/add.vue src/pages/sales/invoicingRegistration/index.vue src/pages/sales/invoicingRegistration/view.vue src/pages/sales/receiptPayment/add.vue src/pages/sales/receiptPayment/index.vue src/pages/sales/receiptPaymentHistory/index.vue src/pages/sales/receiptPaymentLedger/detail.vue src/pages/sales/receiptPaymentLedger/index.vue src/pages/sales/salesAccount/detail.vue src/pages/sales/salesAccount/goOut.vue src/pages/sales/salesAccount/index.vue src/pages/sales/salesAccount/out.vue src/pages/sales/salesAccount/view.vue src/static/images/icon/baojiaguanli.png src/static/images/icon/baoxiaoguanli.png src/static/images/icon/caigouguanli.png src/static/images/icon/chuchaiguanli@2x.png src/static/images/icon/chukuguanli@2x.png src/static/images/icon/gongchuguanli@2x.png src/static/images/icon/guizhangzhidu@2x.png src/static/images/icon/huiyifabu@2x.png src/static/images/icon/huiyikanban@2x.png src/static/images/icon/huiyiliebiao@2x.png src/static/images/icon/huiyishenpi@2x.png src/static/images/icon/huiyishenqing@2x.png src/static/images/icon/huiyishezhi@2x.png src/static/images/icon/huiyizongjie@2x.png src/static/images/icon/qingjiaguanli@2x.png src/static/images/icon/rukuguanli@2x.png src/static/images/icon/tongzhigonggao@2x.png src/static/images/icon/yongyinguanli@2x.png src/static/images/icon/zhishiku@2x.png src/static/images/icon/zidingyichuku.png src/static/images/index/caigoushuju.png src/static/images/index/kucunshuju.png src/static/images/index/num1.png src/static/images/index/num2.png src/static/images/index/num3.png src/static/images/index/xiaoshoushuju.png src/static/images/tabbar/home.png src/static/images/tabbar/homeBlue.png src/static/images/tabbar/home_.png (已删除) src/static/images/tabbar/mine.png (已删除) src/static/images/tabbar/mine_.png (已删除) src/static/images/tabbar/my.png src/static/images/tabbar/myBlue.png src/static/images/tabbar/new.png src/static/images/tabbar/newBlue.png src/static/images/tabbar/work.png src/static/images/tabbar/workBlue.png src/static/images/tabbar/work_.png (已删除)