董国庆
2026-01-22 31513fa5c8248d3b45a692a3736f877c537bea09
人员在岗率
2个文件已添加
2个文件已修改
498 ■■■■■ 已修改文件
screen/src/view/personnelOnDuty/api.txt 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/personnelOnDuty/index.vue 346 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/personnelOnDuty/service.js 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/projectHome/index.vue 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/personnelOnDuty/api.txt
New file
@@ -0,0 +1,16 @@
1、岗位下拉框信息 是get,无请求参,响应有:name    岗位名称;id
2、获取计划下拉框信息 是get,无请求参,响应有:name    计划名称;id
3、获取人员在岗列表 是post ,json传参,
       请求参数有:endTime    结束时间;pageNum    分页参数,当前页码;pageSize    分页参数,每页数量,默认为10;
       startTime    开始时间;planId    计划id;positionId    岗位id;projectId    项目id;
       remark    搜索内容
       响应参数有:
                checkContent    检查内容    string
                id        integer
                img    检查结果图片    string
                planName    检查计划名称    string
                postName    岗位名称    string
                remark    问题备注    string
                state    在岗情况 1在岗 2未在岗 3请假    integer
                time    检查时间    string
screen/src/view/personnelOnDuty/index.vue
@@ -17,15 +17,17 @@
        >
          <el-select
            filterable
            v-model="projectId"
            v-model="planId"
            placeholder="请选择检查计划"
            class="flex1"
            clearable
            @change="handleSearch"
          >
            <el-option
              v-for="item in [1, 2]"
              :key="item"
              :label="item"
              :value="item"
              v-for="item in planList"
              :key="item.id"
              :label="item.name"
              :value="item.id"
            >
            </el-option>
          </el-select>
@@ -39,15 +41,17 @@
        >
          <el-select
            filterable
            v-model="projectId"
            v-model="positionId"
            placeholder="请选择岗位"
            class="flex1"
            clearable
            @change="handleSearch"
          >
            <el-option
              v-for="item in [1, 2]"
              :key="item"
              :label="item"
              :value="item"
              v-for="item in positionList"
              :key="item.id"
              :label="item.name"
              :value="item.id"
            >
            </el-option>
          </el-select>
@@ -61,24 +65,25 @@
          class="flex a-center j-between px--20 w--300 h--40 relative bgcolor1 picker"
        >
          <el-date-picker
            v-model="dateTime"
            v-model="startTime"
            ref="datetime1"
            type="datetime"
            placeholder=""
            @change="handleSearch"
          >
          </el-date-picker>
          <div
            class="absolute w100 h100 flex a-center c-fff fs--14 SHS"
            style="width: calc(100% - 20px)"
            v-if="dateTxt"
            v-if="startTimeTxt"
          >
            {{ dateTxt }}
            {{ startTimeTxt }}
          </div>
          <div
            v-else
            class="absolute w100 h100 flex a-center c-fff fs--14 SHSR"
          >
            请选择日期
            请选择开始时间
          </div>
          <img
            src="../../assets/img/home/Shape@2x (6).png"
@@ -90,24 +95,25 @@
          class="flex a-center j-between px--20 w--300 h--40 relative bgcolor1 picker"
        >
          <el-date-picker
            v-model="dateTime"
            v-model="endTime"
            ref="datetime2"
            type="datetime"
            placeholder=""
            @change="handleSearch"
          >
          </el-date-picker>
          <div
            class="absolute w100 h100 flex a-center c-fff fs--14 SHS"
            style="width: calc(100% - 20px)"
            v-if="dateTxt"
            v-if="endTimeTxt"
          >
            {{ dateTxt }}
            {{ endTimeTxt }}
          </div>
          <div
            v-else
            class="absolute w100 h100 flex a-center c-fff fs--14 SHSR"
          >
            请选择日期
            请选择结束时间
          </div>
          <img
            src="../../assets/img/home/Shape@2x (6).png"
@@ -117,74 +123,65 @@
        <div
          class="flex a-center j-between w--300 pl--20 h--40 relative bgcolor1 searchBox"
        >
          <el-select
            filterable
            v-model="projectId"
          <el-input
            v-model="searchText"
            placeholder="请输入搜索内容"
            class="flex1"
            clearable
            @keyup.enter.native="handleSearch"
          >
            <el-option
              v-for="item in [1, 2]"
              :key="item"
              :label="item"
              :value="item"
            >
            </el-option>
          </el-select>
          <div class="btn SHSR">搜索</div>
          </el-input>
          <div class="btn SHSR" @click="handleSearch">搜索</div>
        </div>
      </div>
      <!-- 表格 -->
      <CustomTable
        :data="[
          { projectName: '项目777' },
          { projectName: '项目2' },
          { projectName: '项目4' },
        ]"
        :data="tableData"
        height="800"
        style="border: 2px solid #3d7fff"
        v-loading="loading"
      >
        <el-table-column label="计划名称" prop="projectName" />
        <el-table-column label="检查内容" prop="projectName" />
        <el-table-column label="岗位" prop="projectName" />
        <el-table-column label="问题备注" prop="projectName" />
        <el-table-column label="图片" prop="projectName">
          <template #default="{ row }"
            ><div
        <el-table-column label="计划名称" prop="planName" min-width="120" />
        <el-table-column label="检查内容" prop="checkContent" min-width="150" show-overflow-tooltip />
        <el-table-column label="岗位" prop="postName" min-width="100" />
        <el-table-column label="问题备注" prop="remark" min-width="150" show-overflow-tooltip />
        <el-table-column label="图片" min-width="80">
          <template #default="{ row }">
            <div
              class="btnColor cursor-pointer"
              :class="false && 'disableColor'"
              @click="previewVisible = true"
              :class="!row.img && 'disableColor'"
              @click="row.img && previewImage(row)"
            >
              1张
              {{ getImageCount(row.img) }}张
            </div>
          </template>
        </el-table-column>
        <el-table-column label="检查时间" prop="projectName" />
        <el-table-column label="在岗情况" prop="projectName">
          <template #default="{ row, $index }">
        <el-table-column label="检查时间" prop="time" min-width="160" />
        <el-table-column label="在岗情况" min-width="120">
          <template #default="{ row }">
            <div class="flex a-center j-center">
              <div class="ripple">
                <div
                  class="ripple-wave wave-1"
                  :class="['bgColor33', 'bgColor66'][$index + 1]"
                  :class="stateMap[row.state] && stateMap[row.state].colorClass[0]"
                ></div>
                <div
                  class="ripple-wave wave-2"
                  :class="['bgColor44', 'bgColor77'][$index + 1]"
                  :class="stateMap[row.state] && stateMap[row.state].colorClass[1]"
                ></div>
                <div
                  class="ripple-wave wave-3"
                  :class="['bgColor55', 'bgColor88'][$index + 1]"
                  :class="stateMap[row.state] && stateMap[row.state].colorClass[2]"
                ></div>
              </div>
              <div class="ml--6 SHSR">{{ $index }}</div>
              <div class="ml--6 SHSR">{{ stateMap[row.state] ? stateMap[row.state].text : '-' }}</div>
            </div>
          </template>
        </el-table-column>
        <el-table-column label="操作">
        <el-table-column label="操作" width="100">
          <template #default="{ row }">
            <el-button
              @click="detailVisible = true"
              @click="showDetail(row)"
              type="text"
              class="btnColor"
              >详情</el-button
@@ -196,25 +193,25 @@
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
        :current-page="currentPage"
        :page-sizes="[100, 200, 300, 400]"
        :page-size="10"
        :total="400"
        :page-sizes="[10, 20, 50, 100]"
        :page-size="pageSize"
        :total="total"
      />
    </div>
    <!-- 详情弹窗 -->
    <CustomDialog title="项目详情" :visible.sync="detailVisible" width="1420px">
      <div class="oa" style="height: 700px">
    <CustomDialog title="人员在岗详情" :visible.sync="detailVisible" width="1420px">
      <div class="oa" style="height: 700px" v-loading="detailLoading">
        <div class="flex j-between gap--30 mt--30">
          <div class="flex1">
            <div class="SHSR fs--16 lh--16 mb--17 color1">计划名称:</div>
            <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor8">
              计划名称计划名称计划名称
              {{ detailData.planName || '-' }}
            </div>
          </div>
          <div class="flex1">
            <div class="SHSR fs--16 lh--16 mb--17 color1">项目名称:</div>
            <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor8">
              计划名称计划名称计划名称
              {{ projectName || '-' }}
            </div>
          </div>
        </div>
@@ -222,13 +219,13 @@
          <div class="flex1">
            <div class="SHSR fs--16 lh--16 mb--17 color1">检查内容:</div>
            <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor8">
              计划名称计划名称计划名称
              {{ detailData.checkContent || '-' }}
            </div>
          </div>
          <div class="flex1">
            <div class="SHSR fs--16 lh--16 mb--17 color1">岗位:</div>
            <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor8">
              计划名称计划名称计划名称
              {{ detailData.postName || '-' }}
            </div>
          </div>
        </div>
@@ -236,13 +233,27 @@
          <div class="flex1">
            <div class="SHSR fs--16 lh--16 mb--17 color1">问题备注:</div>
            <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor8">
              计划名称计划名称计划名称
              {{ detailData.remark || '-' }}
            </div>
          </div>
          <div class="flex1">
            <div class="SHSR fs--16 lh--16 mb--17 color1">在岗情况:</div>
            <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor8">
              计划名称计划名称计划名称
            <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor8 flex a-center">
              <div class="ripple mr--10" v-if="detailData.state">
                <div
                  class="ripple-wave wave-1"
                  :class="stateMap[detailData.state] && stateMap[detailData.state].colorClass[0]"
                ></div>
                <div
                  class="ripple-wave wave-2"
                  :class="stateMap[detailData.state] && stateMap[detailData.state].colorClass[1]"
                ></div>
                <div
                  class="ripple-wave wave-3"
                  :class="stateMap[detailData.state] && stateMap[detailData.state].colorClass[2]"
                ></div>
              </div>
              {{ stateMap[detailData.state] ? stateMap[detailData.state].text : '-' }}
            </div>
          </div>
        </div>
@@ -250,7 +261,7 @@
          <div class="flex1">
            <div class="SHSR fs--16 lh--16 mb--17 color1">检查时间:</div>
            <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor8">
              2020-12-12
              {{ detailData.time || '-' }}
            </div>
          </div>
        </div>
@@ -259,11 +270,14 @@
            <div class="SHSR fs--16 lh--16 mb--17 color1">图片:</div>
            <div class="flex a-center oa gap--14">
              <img
                v-for="item in 6"
                :key="item"
                src="../../assets/img/home/@3x@2x.png"
                class="w--150 h--120"
                v-for="(item, index) in detailData.img ? detailData.img.split(',').filter(i => i) : []"
                :key="index"
                :src="item"
                class="w--150 h--120 cursor-pointer"
                style="object-fit: cover; border-radius: 4px;"
                @click="previewImage(detailData)"
              />
              <div v-if="!detailData.img || !detailData.img.split(',').filter(i => i).length" class="c-fff SHSR">暂无图片</div>
            </div>
          </div>
        </div>
@@ -272,7 +286,7 @@
    <!-- 图片预览 -->
    <CustomPreview
      :show="previewVisible"
      :imgArr="[]"
      :imgArr="previewImages"
      @close="previewVisible = false"
    />
  </div>
@@ -281,26 +295,67 @@
<script>
import HeaderBox from "../header/index.vue";
import moment from "moment";
import { getPositionList, getPlanList, getProjectOnDutyList, getProjectOnDutyDetail } from "./service";
export default {
  components: { HeaderBox },
  props: {},
  data() {
    return {
      dateTime: null,
      startTime: null,
      endTime: null,
      projectId: null,
      projectName: null,
      dateTxt: "",
      startTimeTxt: "",
      endTimeTxt: "",
      currentPage: 1,
      pageSize: 10,
      total: 0,
      detailVisible: false,
      previewVisible: false,
      // 下拉框数据
      planList: [], // 计划列表
      positionList: [], // 岗位列表
      planId: null, // 选中的计划ID
      positionId: null, // 选中的岗位ID
      searchText: "", // 搜索内容
      // 表格数据
      tableData: [],
      loading: false,
      // 详情数据
      detailData: {},
      detailLoading: false,
      // 图片预览数据
      previewImages: [],
    };
  },
  computed: {},
  computed: {
    // 在岗情况映射
    stateMap() {
      return {
        1: { text: "在岗", colorClass: ["bgColor33", "bgColor44", "bgColor55"] },
        2: { text: "未在岗", colorClass: ["wave-1", "wave-2", "wave-3"] },
        3: { text: "请假", colorClass: ["bgColor66", "bgColor77", "bgColor88"] },
      };
    },
  },
  watch: {
    dateTime: {
      handler(newVal, oldVal) {
    startTime: {
      handler(newVal) {
        if (newVal) {
          this.dateTxt = moment(newVal).format("YYYY年MM月DD日 HH:mm:ss");
          this.startTimeTxt = moment(newVal).format("YYYY年MM月DD日 HH:mm:ss");
        } else {
          this.startTimeTxt = "";
        }
      },
      immediate: true,
    },
    endTime: {
      handler(newVal) {
        if (newVal) {
          this.endTimeTxt = moment(newVal).format("YYYY年MM月DD日 HH:mm:ss");
        } else {
          this.endTimeTxt = "";
        }
      },
      immediate: true,
@@ -310,20 +365,144 @@
    this.projectId = this.$route.query.projectId;
    this.projectName = this.$route.query.projectName;
  },
  mounted() {},
  mounted() {
    this.initData();
  },
  methods: {
    // 初始化数据
    async initData() {
      await this.getPlanListData();
      await this.getPositionListData();
      await this.getTableData();
    },
    // 获取计划下拉框数据
    async getPlanListData() {
      try {
        const res = await getPlanList();
        if (res) {
          this.planList = res || [];
        }
      } catch (error) {
        console.error("获取计划列表失败:", error);
      }
    },
    // 获取岗位下拉框数据
    async getPositionListData() {
      try {
        const res = await getPositionList();
        if (res) {
          this.positionList = res || [];
        }
      } catch (error) {
        console.error("获取岗位列表失败:", error);
      }
    },
    // 获取表格数据
    async getTableData() {
      this.loading = true;
      try {
        const params = {
          pageNum: this.currentPage,
          pageSize: this.pageSize,
          projectId: this.projectId,
        };
        // 添加可选参数
        if (this.planId) params.planId = this.planId;
        if (this.positionId) params.positionId = this.positionId;
        if (this.startTime) {
          params.startTime = moment(this.startTime).format(
            "YYYY-MM-DD HH:mm:ss"
          );
        }
        if (this.endTime) {
          params.endTime = moment(this.endTime).format("YYYY-MM-DD HH:mm:ss");
        }
        if (this.searchText) params.remark = this.searchText;
        const res = await getProjectOnDutyList(params);
        if (res) {
          this.tableData = res.records || res.list || [];
          this.total = res.total || 0;
        }
      } catch (error) {
        console.error("获取列表数据失败:", error);
        this.$message.error("获取列表数据失败");
      } finally {
        this.loading = false;
      }
    },
    // 搜索
    handleSearch() {
      this.currentPage = 1;
      this.getTableData();
    },
    // 分页大小改变
    handleSizeChange(val) {
      console.log(`每页 ${val} 条`);
      this.pageSize = val;
      this.currentPage = 1;
      this.getTableData();
    },
    // 当前页改变
    handleCurrentChange(val) {
      console.log(`当前页: ${val}`);
      this.currentPage = val;
      this.getTableData();
    },
    // 点击日期选择器
    handleClick(type) {
      this.$refs[`datetime${type}`].focus();
    },
    // 查看详情
    async showDetail(row) {
      if (!row.id) {
        this.$message.warning("数据异常,无法查看详情");
        return;
      }
      this.detailVisible = true;
      this.detailLoading = true;
      try {
        const res = await getProjectOnDutyDetail(row.id);
        if (res) {
          this.detailData = res;
        } else {
          this.$message.error("获取详情失败");
        }
      } catch (error) {
        console.error("获取详情失败:", error);
        this.$message.error("获取详情失败");
      } finally {
        this.detailLoading = false;
      }
    },
    // 预览图片
    previewImage(row) {
      if (row.img) {
        // 如果img是字符串,可能是逗号分隔的多张图片
        this.previewImages = row.img.split(",").filter((item) => item);
        this.previewVisible = true;
      } else {
        this.$message.warning("暂无图片");
      }
    },
    // 获取图片数量
    getImageCount(img) {
      if (!img) return 0;
      return img.split(",").filter((item) => item).length;
    },
    // 返回
    backFun() {
      this.$router.go(-1);
    },
@@ -433,6 +612,11 @@
  }
}
.disableColor {
  color: #666 !important;
  cursor: not-allowed !important;
}
.btnColor {
  color: #00ebfc;
}
screen/src/view/personnelOnDuty/service.js
New file
@@ -0,0 +1,26 @@
import request from '@/utils/request'
// 获取岗位下拉框信息
export const getPositionList = async (id) => {
  return request(`/project/getPositionList`, {
    method: 'get'
  })
}
// 获取计划下拉框信息
export const getPlanList = async (id) => {
  return request(`/project/getPlanList`, {
    method: 'get'
  })
}
// 获取人员在岗列表
export const getProjectOnDutyList = async (data) => {
  return request(`/project/getProjectOnDutyList`, {
    method: 'post', data
  })
}
// 获取人员在岗详情
export const getProjectOnDutyDetail = async (id) => {
  return request(`/project/getProjectOnDutyDetail/${id}`, {
    method: 'get'
  })
}
screen/src/view/projectHome/index.vue
@@ -133,7 +133,7 @@
              </div>
              <div class="info-body">
                <div class="info-item" v-for="(item, index) in dangerTrendList" :key="index">
                  <div class="item-type">{{ item.name || '-' }}</div>
                  <div class="item-type" :title="item.name || '-'">{{ item.name || '-' }}</div>
                  <div class="item-number">
                    <div class="flex a--center">
                      <div class="flex a--center">
@@ -207,62 +207,52 @@
          <template #content>
            <div class="people-info">
              <div class="people-card">
                <div class="people-item" v-if="peopleList.length > 0">
                <div
                  class="people-item"
                  v-for="(item, index) in peopleList.slice(0, 3)"
                  :key="index"
                >
                  <div class="people-chart">
                    <el-progress :width="74" type="circle" :show-text="false" :percentage="25" define-back-color="#000"
                      color="#32C5FF" stroke-linecap="square"></el-progress>
                    <el-progress
                      :width="74"
                      type="circle"
                      :show-text="false"
                      :percentage="parseFloat(item.rate) || 0"
                      define-back-color="#000"
                      color="#32C5FF"
                      stroke-linecap="square"
                    ></el-progress>
                    <div class="progress-card">
                      <div class="progress-num">{{ peopleList[0].rate }}</div>
                      <div class="progress-num">{{ item.rate }}</div>
                      <div class="progress-unit">%</div>
                    </div>
                  </div>
                  <div class="people-name">{{ peopleList[0].postName }}</div>
                </div>
                <div class="people-item" v-if="peopleList.length > 1">
                  <div class="people-chart">
                    <el-progress :width="74" type="circle" :show-text="false" :percentage="25" define-back-color="#000"
                      color="#32C5FF" stroke-linecap="square"></el-progress>
                    <div class="progress-card">
                      <div class="progress-num">{{ peopleList[1].rate }}</div>
                      <div class="progress-unit">%</div>
                    </div>
                  </div>
                  <div class="people-name">{{ peopleList[1].name }}</div>
                </div>
                <div class="people-item" v-if="peopleList.length > 2">
                  <div class="people-chart">
                    <el-progress :width="74" type="circle" :show-text="false" :percentage="25" define-back-color="#000"
                      color="#32C5FF" stroke-linecap="square"></el-progress>
                    <div class="progress-card">
                      <div class="progress-num">{{ peopleList[2].rate }}</div>
                      <div class="progress-unit">%</div>
                    </div>
                  </div>
                  <div class="people-name">{{ peopleList[1].name }}</div>
                  <div class="people-name">{{ item.postName || item.name }}</div>
                </div>
              </div>
              <div class="people-card2">
                <div class="people-item mr--67" v-if="peopleList.length > 3">
                <div
                  class="people-item"
                  :class="{ 'mr--67': index === 0 }"
                  v-for="(item, index) in peopleList.slice(3, 5)"
                  :key="index + 3"
                >
                  <div class="people-chart">
                    <el-progress :width="74" type="circle" :show-text="false" :percentage="25" define-back-color="#000"
                      color="#32C5FF" stroke-linecap="square"></el-progress>
                    <el-progress
                      :width="74"
                      type="circle"
                      :show-text="false"
                      :percentage="parseFloat(item.rate) || 0"
                      define-back-color="#000"
                      color="#32C5FF"
                      stroke-linecap="square"
                    ></el-progress>
                    <div class="progress-card">
                      <div class="progress-num">{{ peopleList[3].rate }}</div>
                      <div class="progress-num">{{ item.rate }}</div>
                      <div class="progress-unit">%</div>
                    </div>
                  </div>
                  <div class="people-name">{{ peopleList[1].name }}</div>
                </div>
                <div class="people-item" v-if="peopleList.length > 4">
                  <div class="people-chart">
                    <el-progress :width="74" type="circle" :show-text="false" :percentage="25" define-back-color="#000"
                      color="#32C5FF" stroke-linecap="square"></el-progress>
                    <div class="progress-card">
                      <div class="progress-num">{{ peopleList[4].rate }}</div>
                      <div class="progress-unit">%</div>
                    </div>
                  </div>
                  <div class="people-name">{{ peopleList[1].name }}</div>
                  <div class="people-name">{{ item.name }}</div>
                </div>
              </div>
            </div>
@@ -1246,14 +1236,35 @@
  position: absolute;
  top: 109px;
  left: 20px;
  z-index: 2;
  .project-info {
    padding: 14px;
    height: 204px;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    overflow: auto;
    gap: 8px;
    overflow-y: auto;
    overflow-x: hidden;
    // 自定义滚动条样式
    &::-webkit-scrollbar {
      width: 4px;
    }
    &::-webkit-scrollbar-track {
      background: rgba(255, 255, 255, 0.1);
      border-radius: 2px;
    }
    &::-webkit-scrollbar-thumb {
      background: rgba(50, 197, 255, 0.6);
      border-radius: 2px;
      &:hover {
        background: rgba(50, 197, 255, 0.8);
      }
    }
    .flex1 {
      flex: 1;
@@ -1266,6 +1277,7 @@
    .info-item {
      display: flex;
      gap: 10px;
      flex-shrink: 0;
    }
    .label {
@@ -1479,6 +1491,10 @@
        font-size: 12px;
        color: #ffffff;
        line-height: 18px;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        cursor: pointer;
      }
      .item-number {
@@ -1580,6 +1596,7 @@
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1;
  .mid-info {
    cursor: pointer;
@@ -1718,6 +1735,7 @@
  position: absolute;
  top: 109px;
  right: 20px;
  z-index: 3;
  .people-info {
    height: 280px;