zhangwencui
2026-01-16 00b5cbce9f587931c7dc5455df707b0acc753e69
会议设置,会议申请,会议列表
已添加7个文件
已修改3个文件
2562 ■■■■ 文件已修改
src/api/managementMeetings/meeting.js 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/managementMeetings/meetingSettings.js 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages.json 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/collaborativeApproval/index.vue 668 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/index.vue 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/managementMeetings/meetApplication/index.vue 497 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/managementMeetings/meetingList/index.vue 529 ●●●●● 补丁 | 查看 | 原始文档 | 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/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/pages.json
@@ -296,9 +296,30 @@
      }
    },
    {
      "path": "pages/cooperativeOffice/collaborativeApproval/index",
      "path": "pages/managementMeetings/meetingSettings/index",
      "style": {
        "navigationBarTitleText": "审批管理",
        "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"
      }
    },
src/pages/cooperativeOffice/collaborativeApproval/index.vue
@@ -1,351 +1,367 @@
// å®¡æ‰¹ç®¡ç†ä¸»é¡µé¢
<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>
                        <!-- 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"
                                        @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"
                            @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>
</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";
    // æŽ¥æ”¶çˆ¶ç»„件传递的 approveType å‚æ•°
    const props = defineProps({
        approveType: {
            type: Number,
            default: 0
        }
    });
    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 { 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: props.approveType,...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: "出库管理",
    };
    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.setStorageSync('approveType', props.approveType);
        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.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
        })
    }
  // è¿”回上一页
  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,
    });
  };
  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/index.vue
@@ -303,6 +303,18 @@
      label: "出库管理",
    },
    {
      icon: "/static/images/icon/qingjiaguanli@2x.png",
      label: "会议设置",
    },
    {
      icon: "/static/images/icon/qingjiaguanli@2x.png",
      label: "会议列表",
    },
    {
      icon: "/static/images/icon/qingjiaguanli@2x.png",
      label: "会议申请",
    },
    {
      icon: "/static/images/icon/xietongshenpi@2x.png",
      label: "协同审批",
    },
@@ -495,6 +507,21 @@
          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/cooperativeOffice/collaborativeApproval/index",
src/pages/managementMeetings/meetApplication/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,497 @@
<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.postJob})`"
                          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 { getStaffOnJob } 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();
    getStaffOnJob().then(res => {
      employees.value = res.data.sort((a, b) =>
        a.postJob.localeCompare(b.postJob)
      );
    });
  });
  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/meetingList/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,529 @@
<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到18:00)
  const initTimeSlots = () => {
    const slots = [];
    for (let hour = 8; hour < 18; hour++) {
      // æ¯ä¸ªå°æ—¶æ·»åŠ ä¸¤ä¸ªæ—¶é—´æ®µï¼šæ•´ç‚¹å’ŒåŠç‚¹
      slots.push({
        label: `${hour.toString().padStart(2, "0")}:00`,
        value: `${hour.toString().padStart(2, "0")}:00`,
      });
      if (hour < 17) {
        // åˆ°17:30为止
        slots.push({
          label: `${hour.toString().padStart(2, "0")}:30`,
          value: `${hour.toString().padStart(2, "0")}:30`,
        });
      }
    }
    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)
      );
    });
    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;
    flex-direction: column;
    justify-content: center;
  }
  .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>