张诺
3 小时以前 b9d3432b9994a3a050cb913d6137a8a7f0be9f67
feat(production): 重构生产报工为工单列表和上下机流程

- 将生产报工从表单提交改为工单列表展示,支持搜索和分页
- 添加工单上下机功能:开始报工和结束报工
- 新增工单附件预览组件,支持图片和文档预览
- 更新API接口,添加工单分页、开始报工和结束报工接口
- 调整首页和工作台导航,隐藏部分未启用模块
- 优化用户体验,提供更直观的工单状态展示和操作流程
已添加2个文件
已修改4个文件
1741 ■■■■ 文件已修改
src/api/productionManagement/productionReporting.js 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/productionManagement/productionReport/components/filesDia.vue 175 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/productionManagement/productionReport/index old.vue 326 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/productionManagement/productionReport/index.vue 1185 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/works.vue 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionReporting.js
@@ -41,11 +41,32 @@
    data: query,
  });
}
// æ–°å¢žäº§å“ä¸»è¡¨
// ç”Ÿäº§æŠ¥å·¥ä¸Šä¸‹æœº
// /productWorkOrder/page
export function productWorkOrderPage(query) {
  return request({
    url: "/productWorkOrder/page",
    method: "get",
    params: query,
  });
}
// å¼€å§‹æŠ¥å·¥
// /productOrder/startProduction/{id}
export function startProduction(query) {
  return request({
    url: "/productOrder/startProduction/" + query.id,
    method: "post",
    data: query,
  });
}
// ç»“束报工/新增产品主表
// /productOrder/endProduction/{id}
export function addProductMain(data) {
  return request({
    url: "/productionProductMain/addProductMain",
    method: "post",
    data: data,
  });
}
}
src/pages/index.vue
@@ -212,7 +212,7 @@
  {
    label: "生产报工",
    icon: "/static/images/icon/shengchanbaogong.svg",
    action: "scan",
    route: "/pages/productionManagement/productionReport/index",
  },
  {
    label: "设备巡检",
src/pages/productionManagement/productionReport/components/filesDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,175 @@
<template>
  <up-popup :show="show" mode="bottom" @close="close" round="20">
    <view class="files-container">
      <view class="header">
        <text class="title">工单附件</text>
        <up-icon name="close" size="20" @click="close"></up-icon>
      </view>
      <scroll-view scroll-y class="file-list">
        <view v-if="tableData.length > 0">
          <view v-for="(item, index) in tableData" :key="item.id || index" class="file-item">
            <view class="file-info">
              <up-icon name="file-text" size="24" color="#2979ff"></up-icon>
              <text class="file-name">{{ item.name }}</text>
            </view>
            <view class="file-actions">
              <up-button
                text="预览"
                size="mini"
                type="primary"
                plain
                @click="handlePreview(item)"
              ></up-button>
            </view>
          </view>
        </view>
        <view v-else class="no-data">
          <text>暂无附件</text>
        </view>
      </scroll-view>
      <view class="footer">
        <up-button text="关闭" @click="close"></up-button>
      </view>
    </view>
  </up-popup>
</template>
<script setup>
import { ref, reactive } from "vue";
import { productWorkOrderFileListPage } from "@/api/productionManagement/productWorkOrderFile.js";
const show = ref(false);
const currentWorkOrderId = ref("");
const tableData = ref([]);
const loading = ref(false);
const openDialog = (row) => {
  show.value = true;
  currentWorkOrderId.value = row.id;
  getList();
};
const close = () => {
  show.value = false;
};
const getList = () => {
  loading.value = true;
  productWorkOrderFileListPage({
    workOrderId: currentWorkOrderId.value,
    current: 1,
    size: 100,
  })
    .then((res) => {
      tableData.value = res.data.records || [];
    })
    .finally(() => {
      loading.value = false;
    });
};
const handlePreview = (item) => {
  const url = item.url;
  if (!url) return;
  // åˆ¤æ–­æ˜¯å¦ä¸ºå›¾ç‰‡
  const isImage = /\.(jpg|jpeg|png|gif|webp)$/i.test(url);
  if (isImage) {
    uni.previewImage({
      urls: [url],
      current: url
    });
  } else {
    // éžå›¾ç‰‡æ–‡ä»¶å°è¯•打开文档
    uni.showLoading({ title: '正在打开...' });
    uni.downloadFile({
      url: url,
      success: (res) => {
        if (res.statusCode === 200) {
          uni.openDocument({
            filePath: res.tempFilePath,
            success: () => {
              uni.hideLoading();
            },
            fail: () => {
              uni.hideLoading();
              uni.showToast({ title: '暂不支持预览该类型文件', icon: 'none' });
            }
          });
        }
      },
      fail: () => {
        uni.hideLoading();
        uni.showToast({ title: '下载失败', icon: 'none' });
      }
    });
  }
};
defineExpose({
  openDialog,
});
</script>
<style scoped lang="scss">
.files-container {
  background-color: #fff;
  padding: 20px;
  height: 60vh;
  display: flex;
  flex-direction: column;
}
.header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
  .title {
    font-size: 18px;
    font-weight: bold;
  }
}
.file-list {
  flex: 1;
  overflow: hidden;
}
.file-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 15px 0;
  border-bottom: 1px solid #f5f5f5;
  .file-info {
    display: flex;
    align-items: center;
    gap: 10px;
    flex: 1;
    margin-right: 10px;
    .file-name {
      font-size: 14px;
      color: #333;
      word-break: break-all;
    }
  }
}
.no-data {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 200px;
  color: #999;
}
.footer {
  margin-top: 20px;
}
</style>
src/pages/productionManagement/productionReport/index old.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,326 @@
<template>
  <view class="invoice-add">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="生产报工"
                @back="goBack" />
    <!-- è¡¨å•内容 -->
    <u-form @submit="submitForm"
            ref="formRef"
            label-width="110"
            input-align="right"
            error-message-align="right">
      <!-- åŸºæœ¬ä¿¡æ¯ -->
      <view class="form-section">
        <u-form-item label="机台"
                     prop="deviceName"
                     required>
          <u-input v-model="form.deviceName"
          disabled
                   placeholder="请输入机台" />
        </u-form-item>
        <u-form-item label="计划开始时间"
                     prop="planStartTime"
                     required>
          <u-input v-model="form.planStartTime"
                   placeholder="请选择计划开始时间"
                   readonly
                   @click="showStartTimePicker = true"
                   suffix-icon="calendar" />
        </u-form-item>
        <u-form-item label="计划结束时间"
                     prop="planEndTime"
                     required>
          <u-input v-model="form.planEndTime"
                   placeholder="请选择计划结束时间"
                   readonly
                   @click="showEndTimePicker = true"
                   suffix-icon="calendar" />
        </u-form-item>
        <u-form-item label="待生产数量"
                     prop="planQuantity"
                     required>
          <u-input v-model="form.planQuantity"
                   placeholder="自动填充"
                   disabled />
        </u-form-item>
        <u-form-item label="本次生产数量"
                     prop="quantity"
                     required>
          <u-input v-model="form.quantity"
                   placeholder="请输入"
                   type="number" />
        </u-form-item>
        <u-form-item label="报废数量"
                     prop="scrapQty">
          <u-input v-model="form.scrapQty"
                   placeholder="请输入"
                   type="number" />
        </u-form-item>
        <!-- ç­ç»„信息和审核人 -->
        <u-form-item v-for="(item, key) in pickerFields"
                     :key="key"
                     :label="item.label"
                     :prop="key + '.name'"
                     required>
          <u-input v-model="form[key].name"
                   :placeholder="form[key].name ? '已选择: ' + form[key].name : '请选择' + item.label"
                   readonly
                   @click="openProducerPicker(key)"
                   suffix-icon="arrow-down" />
        </u-form-item>
      </view>
      <!-- ä½¿ç”¨FooterButtons组件 -->
      <FooterButtons @cancel="goBack"
                     @confirm="submitForm"
                     :loading="submitting" />
      <!-- ä¸ºåº•部按钮留出空间 -->
      <view style="height: 80px;"></view>
    </u-form>
    <!-- ç”Ÿäº§äººé€‰æ‹©å™¨ -->
    <up-action-sheet :show="showProducerPicker"
                     :actions="producerList"
                     title="选择生产人"
                     @select="onProducerConfirm"
                     @close="showProducerPicker = false" />
    <!-- å¼€å§‹æ—¶é—´é€‰æ‹©å™¨ -->
    <up-datetime-picker :show="showStartTimePicker"
                        v-model="startTimeValue"
                        mode="datetime"
                        @confirm="onStartTimeConfirm"
                        @cancel="showStartTimePicker = false" />
    <!-- ç»“束时间选择器 -->
    <up-datetime-picker :show="showEndTimePicker"
                        v-model="endTimeValue"
                        mode="datetime"
                        @confirm="onEndTimeConfirm"
                        @cancel="showEndTimePicker = false" />
  </view>
</template>
<script setup>
  import { ref, nextTick } from "vue";
  import { onLoad } from "@dcloudio/uni-app";
  import FooterButtons from "@/components/FooterButtons.vue";
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
  import { addProductMain } from "@/api/productionManagement/productionReporting";
  import { getInfo } from "@/api/login";
  import { userListNoPageByTenantId } from "@/api/system/user";
  // è¡¨å•引用
  const formRef = ref();
  // è¡¨å•数据
  const form = ref({
    deviceName: "",
    planStartTime: "",
    planEndTime: "",
    planQuantity: "",
    quantity: "",
    scrapQty: "",
    workOrderId: "",
    productProcessRouteItemId: "",
    userId: { value: "", name: "" }, // ç­ç»„信息
    auditUserId: { value: "", name: "" }, // å®¡æ ¸äºº
  });
  // è¿™é‡Œçš„字段配置用于模版循环
  const pickerFields = {
    userId: { label: "班组信息" },
    auditUserId: { label: "审核人" },
  };
  // ç”Ÿäº§äººé€‰æ‹©å™¨çŠ¶æ€
  const showProducerPicker = ref(false);
  // æ—¶é—´é€‰æ‹©å™¨çŠ¶æ€
  const showStartTimePicker = ref(false);
  const showEndTimePicker = ref(false);
  const startTimeValue = ref(Number(new Date()));
  const endTimeValue = ref(Number(new Date()));
  const producerList = ref([]);
  const currentField = ref(""); // å½“前选择的字段
  // æ‰“开生产人选择器
  const openProducerPicker = async (field) => {
    if (producerList.value.length === 0) {
      // å¦‚果列表为空,先加载用户列表
      try {
        const res = await userListNoPageByTenantId();
        const users = res.data || [];
        // è½¬æ¢ä¸º action-sheet éœ€è¦çš„æ ¼å¼
        producerList.value = users.map(user => ({
          name: user.nickName || "",
          value: user.userId,
        }));
      } catch (error) {
        console.error("加载用户列表失败:", error);
        showToast("加载用户列表失败");
        return;
      }
    }
    showProducerPicker.value = true;
    currentField.value = field; // ä¿å­˜å½“前字段
  };
  // ç”Ÿäº§äººé€‰æ‹©ç¡®è®¤
  const onProducerConfirm = e => {
    if (currentField.value && form.value[currentField.value]) {
      form.value[currentField.value].value = e.value;
      form.value[currentField.value].name = e.name ;
    }
    showProducerPicker.value = false;
  };
  // æ ¼å¼åŒ–日期
  const formatDateTime = (timestamp) => {
    const date = new Date(timestamp);
    const y = date.getFullYear();
    const m = (date.getMonth() + 1).toString().padStart(2, '0');
    const d = date.getDate().toString().padStart(2, '0');
    const h = date.getHours().toString().padStart(2, '0');
    const min = date.getMinutes().toString().padStart(2, '0');
    const s = date.getSeconds().toString().padStart(2, '0');
    return `${y}-${m}-${d} ${h}:${min}:${s}`;
  };
  // å¼€å§‹æ—¶é—´ç¡®è®¤
  const onStartTimeConfirm = (e) => {
    form.value.planStartTime = formatDateTime(e.value);
    showStartTimePicker.value = false;
  };
  // ç»“束时间确认
  const onEndTimeConfirm = (e) => {
    form.value.planEndTime = formatDateTime(e.value);
    showEndTimePicker.value = false;
  };
  // æäº¤çŠ¶æ€
  const submitting = ref(false);
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  // æäº¤è¡¨å•
  const submitForm = async () => {
    submitting.value = true;
    // æ ¡éªŒè¡¨å•
    if (!form.value.deviceName) {
      submitting.value = false;
      showToast("请输入机台");
      return;
    }
    if (!form.value.planStartTime) {
      submitting.value = false;
      showToast("请选择计划开始时间");
      return;
    }
    if (!form.value.planEndTime) {
      submitting.value = false;
      showToast("请选择计划结束时间");
      return;
    }
    if (!form.value.quantity) {
      submitting.value = false;
      showToast("请输入本次生产数量");
      return;
    }
    if (!form.value.userId.value) {
      submitting.value = false;
      showToast("请选择班组信息");
      return;
    }
    if (!form.value.auditUserId.value) {
      submitting.value = false;
      showToast("请选择审核人");
      return;
    }
    // è½¬æ¢ä¸ºæ•°å­—进行比较
    const quantity = Number(form.value.quantity) || 0;
    const scrapQty = Number(form.value.scrapQty) || 0;
    const planQuantity = Number(form.value.planQuantity);
    // éªŒè¯ç”Ÿäº§æ•°é‡å’ŒæŠ¥åºŸæ•°é‡çš„和不能超过待生产数量
    if (quantity + scrapQty > planQuantity) {
      submitting.value = false;
      showToast("生产数量和报废数量的和不能超过待生产数量");
      return;
    }
    if (quantity > planQuantity) {
      submitting.value = false;
      showToast("本次生产数量不能大于待生产数量");
      return;
    }
    // å‡†å¤‡æäº¤æ•°æ®ï¼Œç¡®ä¿æ•°é‡å­—段为数字类型
    const submitData = {
      ...form.value,
      quantity: Number(form.value.quantity),
      scrapQty: Number(form.value.scrapQty) || 0,
      planQuantity: Number(form.value.planQuantity) || 0,
      userId: form.value.userId.value,
      auditUserId: form.value.auditUserId.value,
      auditUserName: form.value.auditUserId.name,
      schedulingUserId: form.value.userId.value, // å…¼å®¹æ—§å­—段
    };
    console.log(submitData, "submitData");
    addProductMain(submitData).then(res => {
      if (res.code === 200) {
        showToast("报工成功");
        submitting.value = false;
        setTimeout(() => {
          goBack();
        }, 1000);
      } else {
        showToast(res.msg || "报工失败");
        submitting.value = false;
      }
    }).catch(err => {
      submitting.value = false;
    })
    ;
  };
  // é¡µé¢åŠ è½½æ—¶åˆå§‹åŒ–æ•°æ®
  onLoad(options => {
    try {
      const orderRow = JSON.parse(options.orderRow);
      console.log(orderRow, "orderRow");
      // ç¡®ä¿ planQuantity è½¬æ¢ä¸ºå­—符串,以便在 u-input ä¸­æ­£ç¡®æ˜¾ç¤º
      form.value.planQuantity = orderRow.planQuantity != null ? String(orderRow.planQuantity) : "";
      form.value.productProcessRouteItemId = orderRow.productProcessRouteItemId || "";
      form.value.workOrderId = orderRow.id || "";
      form.value.deviceName = orderRow.deviceName || "";
      form.value.planStartTime = orderRow.planStartTime || "";
      form.value.planEndTime = orderRow.planEndTime || "";
      getInfo().then(res => {
        // é»˜è®¤ä½¿ç”¨å½“前登录用户,但允许用户修改
        form.value.userId.value = res.user.userId;
        form.value.userId.name = res.user.nickName;
      });
      // ä½¿ç”¨ nextTick ç¡®ä¿ DOM æ›´æ–°
      nextTick(() => {
        console.log("form.value after assignment:", form.value);
      });
    } catch (error) {
      console.error("订单解析失败:", error);
      showToast("订单解析失败");
      goBack();
      return;
    }
  });
</script>
<style scoped lang="scss">
  @import "@/static/scss/form-common.scss";
</style>
src/pages/productionManagement/productionReport/index.vue
@@ -1,326 +1,907 @@
<template>
  <view class="invoice-add">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="生产报工"
                @back="goBack" />
    <!-- è¡¨å•内容 -->
    <u-form @submit="submitForm"
            ref="formRef"
            label-width="110"
            input-align="right"
            error-message-align="right">
      <!-- åŸºæœ¬ä¿¡æ¯ -->
      <view class="form-section">
        <u-form-item label="机台"
                     prop="deviceName"
                     required>
          <u-input v-model="form.deviceName"
          disabled
                   placeholder="请输入机台" />
        </u-form-item>
        <u-form-item label="计划开始时间"
                     prop="planStartTime"
                     required>
          <u-input v-model="form.planStartTime"
                   placeholder="请选择计划开始时间"
                   readonly
                   @click="showStartTimePicker = true"
                   suffix-icon="calendar" />
        </u-form-item>
        <u-form-item label="计划结束时间"
                     prop="planEndTime"
                     required>
          <u-input v-model="form.planEndTime"
                   placeholder="请选择计划结束时间"
                   readonly
                   @click="showEndTimePicker = true"
                   suffix-icon="calendar" />
        </u-form-item>
        <u-form-item label="待生产数量"
                     prop="planQuantity"
                     required>
          <u-input v-model="form.planQuantity"
                   placeholder="自动填充"
                   disabled />
        </u-form-item>
        <u-form-item label="本次生产数量"
                     prop="quantity"
                     required>
          <u-input v-model="form.quantity"
                   placeholder="请输入"
                   type="number" />
        </u-form-item>
        <u-form-item label="报废数量"
                     prop="scrapQty">
          <u-input v-model="form.scrapQty"
                   placeholder="请输入"
                   type="number" />
        </u-form-item>
        <!-- ç­ç»„信息和审核人 -->
        <u-form-item v-for="(item, key) in pickerFields"
                     :key="key"
                     :label="item.label"
                     :prop="key + '.name'"
                     required>
          <u-input v-model="form[key].name"
                   :placeholder="form[key].name ? '已选择: ' + form[key].name : '请选择' + item.label"
                   readonly
                   @click="openProducerPicker(key)"
                   suffix-icon="arrow-down" />
        </u-form-item>
  <view class="work-order">
    <!-- é€šç”¨é¡µé¢å¤´éƒ¨ -->
    <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.workOrderNo"
            @confirm="handleQuery"
            clearable
          />
        </view>
        <view class="filter-button" @click="handleQuery">
          <up-icon name="search" size="24" color="#999"></up-icon>
        </view>
      </view>
      <!-- ä½¿ç”¨FooterButtons组件 -->
      <FooterButtons @cancel="goBack"
                     @confirm="submitForm"
                     :loading="submitting" />
      <!-- ä¸ºåº•部按钮留出空间 -->
      <view style="height: 80px;"></view>
    </u-form>
    <!-- ç”Ÿäº§äººé€‰æ‹©å™¨ -->
    <up-action-sheet :show="showProducerPicker"
                     :actions="producerList"
                     title="选择生产人"
                     @select="onProducerConfirm"
                     @close="showProducerPicker = false" />
    </view>
    <!-- å·¥å•列表 -->
    <scroll-view
      scroll-y
      class="ledger-list"
      v-if="tableData.length > 0"
      lower-threshold="80"
      @scrolltolower="loadMore"
    >
      <view v-for="(item, index) in tableData" :key="item.id || index" 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.workOrderNo }}</text>
          </view>
          <view class="item-right">
            <text class="item-tag tag-type">{{ item.workOrderType }}</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.productName }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">规格型号</text>
            <text class="detail-value">{{ item.model }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">机台名称</text>
            <text class="detail-value">{{ item.deviceName }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">工序名称 / è®¡é‡å•位</text>
            <text class="detail-value">{{ item.processName }} / {{ item.unit }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">需求/完成数量</text>
            <text class="detail-value">{{ item.planQuantity }} / {{ item.completeQuantity }}</text>
          </view>
          <view class="progress-section">
            <text class="detail-label">完成进度</text>
            <view class="progress-bar">
              <up-line-progress
                :percentage="toProgressPercentage(item.completionStatus)"
                activeColor="#2979ff"
                :showText="true"
              ></up-line-progress>
            </view>
          </view>
          <view class="detail-row">
            <text class="detail-label">实际开始</text>
            <text class="detail-value">{{ item.startProductTime || '-' }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">实际结束</text>
            <text class="detail-value">{{ item.endProductTime || '-' }}</text>
          </view>
        </view>
    <!-- å¼€å§‹æ—¶é—´é€‰æ‹©å™¨ -->
    <up-datetime-picker :show="showStartTimePicker"
                        v-model="startTimeValue"
                        mode="datetime"
                        @confirm="onStartTimeConfirm"
                        @cancel="showStartTimePicker = false" />
        <view class="item-actions" v-if="!item.endProductTime">
          <up-button
            text="开始报工"
            size="mini"
            type="primary"
            :disabled="!canStartProduction(item) || startSubmittingId === item.id"
            @click="handleStartProduction(item)"
          />
          <up-button
            text="结束报工"
            size="mini"
            type="success"
            :disabled="!canEndProduction(item)"
            @click="openEndReport(item)"
          />
        </view>
      </view>
      <up-loadmore :status="loadStatus" />
    </scroll-view>
    <view v-else-if="!loading" class="no-data">
      <up-empty mode="data" text="暂无工单数据"></up-empty>
    </view>
    <!-- ç»“束时间选择器 -->
    <up-datetime-picker :show="showEndTimePicker"
                        v-model="endTimeValue"
                        mode="datetime"
                        @confirm="onEndTimeConfirm"
                        @cancel="showEndTimePicker = false" />
    <!-- æµè½¬å¡å¼¹çª— -->
    <up-popup :show="transferCardVisible" mode="center" @close="transferCardVisible = false" round="10">
      <view class="qr-popup">
        <text class="qr-title">工单流转卡二维码</text>
        <view class="qr-box">
          <geek-qrcode
            v-if="transferCardRowData"
            :val="String(transferCardRowData.id)"
            :size="200"
          />
        </view>
        <text class="qr-info" v-if="transferCardRowData">{{ transferCardRowData.workOrderNo }}</text>
        <up-button text="关闭" @click="transferCardVisible = false" style="margin-top: 20px;"></up-button>
      </view>
    </up-popup>
    <!-- ç»“束报工弹窗 -->
    <up-popup
      v-model:show="endReportVisible"
      mode="bottom"
      :round="20"
      :safeAreaInsetBottom="true"
      @close="closeEndReport"
    >
      <view class="report-modal">
        <view class="modal-header">
          <view class="modal-header-left">
            <text class="modal-title">结束报工</text>
            <text class="modal-subtitle" v-if="endReportRow">
              {{ endReportRow.workOrderNo || '-' }} Â· {{ endReportRow.deviceName || '-' }}
            </text>
          </view>
          <view class="close-btn" @click="closeEndReport">
            <up-icon name="close" size="20" color="#999"></up-icon>
          </view>
        </view>
        <scroll-view class="modal-content" scroll-y>
          <view class="report-summary" v-if="endReportRow">
            <view class="summary-left">
              <text class="summary-title">{{ endReportRow.productName || '-' }}</text>
              <text class="summary-sub">{{ endReportRow.processName || '-' }} Â· {{ endReportRow.model || '-' }}</text>
            </view>
            <view class="summary-right">
              <text class="summary-num">{{ endReportForm.planQuantity || '0' }}</text>
              <text class="summary-label">待生产</text>
            </view>
          </view>
          <up-form :model="endReportForm" labelWidth="120">
            <view class="form-section">
              <text class="section-title">数量信息</text>
              <up-form-item label="待生产数量">
                <up-input v-model="endReportForm.planQuantity" disabled />
              </up-form-item>
              <up-form-item label="本次生产数量" required>
                <up-input v-model="endReportForm.quantity" disabled />
              </up-form-item>
              <up-form-item label="补产数量">
                <up-input v-model="endReportForm.replenishQty" type="number" placeholder="请输入" />
              </up-form-item>
              <up-form-item label="报废数量">
                <up-input v-model="endReportForm.scrapQty" type="number" placeholder="请输入" />
              </up-form-item>
            </view>
            <view class="form-section">
              <text class="section-title">班组信息</text>
              <up-form-item label="班组人员">
                <up-input
                  v-model="teamDisplayText"
                  readonly
                  placeholder="请选择(可多选)"
                  @click="openTeamPicker"
                  suffixIcon="arrow-down"
                />
              </up-form-item>
            </view>
            <view class="form-section">
              <text class="section-title">审核信息</text>
              <up-form-item label="审核人" required>
                <up-input
                  v-model="endReportForm.auditUserName"
                  readonly
                  placeholder="请选择"
                  @click="openAuditPicker"
                  suffixIcon="arrow-down"
                />
              </up-form-item>
            </view>
          </up-form>
          <view style="height: 24px;"></view>
        </scroll-view>
        <view class="modal-footer">
          <up-button
            text="取消"
            type="info"
            plain
            @click="closeEndReport"
            :customStyle="{ marginRight: '12px', flex: 1 }"
          />
          <up-button text="提交" type="primary" @click="submitEndReport" :customStyle="{ flex: 1 }" />
        </view>
      </view>
    </up-popup>
    <!-- ç­ç»„选择弹窗 -->
    <up-popup v-model:show="teamPickerVisible" mode="bottom" :round="20" :safeAreaInsetBottom="true">
      <view class="team-modal">
        <view class="modal-header">
          <text class="modal-title">选择班组成员</text>
          <view class="close-btn" @click="teamPickerVisible = false">
            <up-icon name="close" size="20" color="#999"></up-icon>
          </view>
        </view>
        <scroll-view class="team-content" scroll-y>
          <up-checkbox-group v-model="teamCheckedIds" placement="column">
            <up-checkbox
              v-for="u in userOptions"
              :key="u.value"
              :label="u.name"
              :name="String(u.value)"
              :customStyle="{ marginBottom: '10px' }"
            />
          </up-checkbox-group>
        </scroll-view>
        <view class="modal-footer">
          <up-button text="取消" type="info" plain @click="teamPickerVisible = false" />
          <up-button text="确定" type="primary" @click="confirmTeamPicker" />
        </view>
      </view>
    </up-popup>
    <!-- å®¡æ ¸äººé€‰æ‹©å™¨ -->
    <up-action-sheet
      :show="auditPickerVisible"
      :actions="auditActions"
      title="选择审核人"
      @select="onAuditSelect"
      @close="auditPickerVisible = false"
    />
    <!-- æ—¶é—´é€‰æ‹©å™¨ -->
    <up-datetime-picker
      :show="startTimePickerVisible"
      v-model="startTimeValue"
      mode="datetime"
      @confirm="onStartTimeConfirm"
      @cancel="startTimePickerVisible = false"
    />
    <up-datetime-picker
      :show="endTimePickerVisible"
      v-model="endTimeValue"
      mode="datetime"
      @confirm="onEndTimeConfirm"
      @cancel="endTimePickerVisible = false"
    />
    <!-- é™„件组件 -->
    <FilesDia ref="workOrderFilesRef" />
  </view>
</template>
<script setup>
  import { ref, nextTick } from "vue";
  import { onLoad } from "@dcloudio/uni-app";
  import FooterButtons from "@/components/FooterButtons.vue";
import { ref, reactive, toRefs, computed, getCurrentInstance } from "vue";
import { onShow, onLoad, onReachBottom } from "@dcloudio/uni-app";
import { productWorkOrderPage, addProductMain, startProduction, getProductWorkOrderById } from "@/api/productionManagement/productionReporting.js";
import { userListNoPageByTenantId } from "@/api/system/user.js";
import PageHeader from "@/components/PageHeader.vue";
import FilesDia from "./components/filesDia.vue";
import useUserStore from "@/store/modules/user";
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
  import { addProductMain } from "@/api/productionManagement/productionReporting";
  import { getInfo } from "@/api/login";
  import { userListNoPageByTenantId } from "@/api/system/user";
const userStore = useUserStore();
  // è¡¨å•引用
  const formRef = ref();
const loading = ref(false);
const tableData = ref([]);
const loadStatus = ref('loadmore');
const transferCardVisible = ref(false);
const transferCardRowData = ref(null);
const workOrderFilesRef = ref(null);
const startSubmittingId = ref(null);
  // è¡¨å•数据
  const form = ref({
    deviceName: "",
    planStartTime: "",
    planEndTime: "",
    planQuantity: "",
    quantity: "",
    scrapQty: "",
    workOrderId: "",
    productProcessRouteItemId: "",
    userId: { value: "", name: "" }, // ç­ç»„信息
    auditUserId: { value: "", name: "" }, // å®¡æ ¸äºº
const endReportVisible = ref(false);
const endReportRow = ref(null);
const endReportForm = reactive({
  planQuantity: "",
  quantity: "",
  replenishQty: "0",
  scrapQty: "0",
  teamList: [],
  startTime: "",
  endTime: "",
  auditUserId: "",
  auditUserName: "",
  userId: "",
  userName: "",
  workOrderId: "",
  reportWork: "",
  productProcessRouteItemId: "",
  productMainId: null,
});
const userOptions = ref([]);
const auditPickerVisible = ref(false);
const auditActions = computed(() => userOptions.value.map(u => ({ name: u.name, value: u.value })));
const teamPickerVisible = ref(false);
const teamCheckedIds = ref([]);
const teamDisplayText = computed(() => {
  const list = Array.isArray(endReportForm.teamList) ? endReportForm.teamList : [];
  const names = list.map(i => String(i?.userName ?? "")).filter(Boolean);
  if (names.length === 0) return "";
  if (names.length <= 2) return names.join("、");
  return `${names.slice(0, 2).join("、")}等${names.length}人`;
});
const startTimePickerVisible = ref(false);
const endTimePickerVisible = ref(false);
const startTimeValue = ref(Date.now());
const endTimeValue = ref(Date.now());
const routeOrderRow = ref(null);
const page = reactive({
  current: 1,
  size: 10,
  total: 0,
});
const data = reactive({
  searchForm: {
    workOrderNo: "",
  },
});
const { searchForm } = toRefs(data);
const goBack = () => {
  uni.navigateBack();
};
const handleQuery = () => {
  page.current = 1;
  tableData.value = [];
  loadStatus.value = "loadmore";
  getList();
};
const showToast = (message) => {
  uni.showToast({ title: message, icon: "none" });
};
const ensureUserInfo = async () => {
  if (userStore.id) return;
  try {
    await userStore.getInfo();
  } catch {
  }
};
const ensureUserOptions = async () => {
  if (userOptions.value.length > 0) return;
  try {
    const res = await userListNoPageByTenantId();
    const users = res?.data || [];
    users.unshift({
            nickName:"任意用户",
            userId:"-1",
          })
    userOptions.value = users.map(u => ({
      name: u?.nickName || "",
      value: String(u?.userId ?? ""),
    })).filter(u => u.value);
  } catch {
    userOptions.value = [];
  }
};
const getList = () => {
  console.log(searchForm.value);
  if (loading.value) return;
  loading.value = true;
  const params = { ...searchForm.value, ...page };
  productWorkOrderPage(params).then((res) => {
       loading.value = false;
    const records = res.data.records || [];
    tableData.value = page.current === 1 ? records : [...tableData.value, ...records];
    page.total = res.data.total;
    if (tableData.value.length >= page.total) {
      loadStatus.value = 'nomore';
    } else {
      loadStatus.value = 'loadmore';
    }
  }).catch(() => {
    loading.value = false;
    loadStatus.value = "loadmore";
    uni.showToast({ title: '加载失败', icon: 'error' });
  });
};
  // è¿™é‡Œçš„字段配置用于模版循环
  const pickerFields = {
    userId: { label: "班组信息" },
    auditUserId: { label: "审核人" },
  };
const loadSingleWorkOrder = async (row) => {
  if (!row?.id) {
    handleQuery();
    return;
  }
  loading.value = true;
  try {
    const res = await getProductWorkOrderById({ id: row.id });
    const data = res?.data;
    tableData.value = data ? [data] : [];
    page.total = tableData.value.length;
    loadStatus.value = 'nomore';
  } catch {
    tableData.value = [];
    showToast("加载工单失败");
  } finally {
    loading.value = false;
  }
};
  // ç”Ÿäº§äººé€‰æ‹©å™¨çŠ¶æ€
  const showProducerPicker = ref(false);
  // æ—¶é—´é€‰æ‹©å™¨çŠ¶æ€
  const showStartTimePicker = ref(false);
  const showEndTimePicker = ref(false);
  const startTimeValue = ref(Number(new Date()));
  const endTimeValue = ref(Number(new Date()));
const loadMore = () => {
  console.log(loadStatus.value);
  if (loadStatus.value === 'nomore' || loading.value) return;
  loadStatus.value = "loading";
  page.current++;
  getList();
};
  const producerList = ref([]);
  const currentField = ref(""); // å½“前选择的字段
onReachBottom(() => {
  loadMore();
});
  // æ‰“开生产人选择器
  const openProducerPicker = async (field) => {
    if (producerList.value.length === 0) {
      // å¦‚果列表为空,先加载用户列表
const toProgressPercentage = (val) => {
  const n = Number(val);
  if (!Number.isFinite(n)) return 0;
  if (n <= 0) return 0;
  if (n >= 100) return 100;
  return Math.round(n);
};
const showTransferCard = (row) => {
  transferCardRowData.value = row;
  transferCardVisible.value = true;
};
const openWorkOrderFiles = (row) => {
  workOrderFilesRef.value?.openDialog(row);
};
const getPendingQty = (row) => {
  const plan = Number(row?.planQuantity) || 0;
  const complete = Number(row?.completeQuantity) || 0;
  return plan - complete;
};
const isStarted = (row) => {
  if (row?.startProductTime && !row?.endProductTime) return true;
  if (String(row?.reportWork) === "1" && !row?.endProductTime) return true;
  return false;
};
const isEnded = (row) => {
  return Boolean(row?.endProductTime);
};
const canEndProduction = (row) => {
  if (!row?.id) return false;
  if (getPendingQty(row) <= 0) return false;
  if (isEnded(row)) return false;
  if (!isStarted(row)) return false;
  return true;
};
// åˆ¤æ–­æ˜¯å¦å¯ä»¥å¼€å§‹æŠ¥å·¥
const canStartProduction = (row) => {
  if (!row?.id) return false;
  if (getPendingQty(row) <= 0) return false;
  if (isEnded(row)) return false;
  if (isStarted(row)) return false;
  if (!canStartProductionByUserIds(userStore.id, row)) return false;
  return true;
};
// æ ¹æ®userIds判断是否有用户可以报工
const canStartProductionByUserIds = (userId, row) => {
  const team = row?.userIds || "";
  return team.includes(userId);
};
const handleStartProduction = (row) => {
  console.log(userStore.id)
  if (!canStartProduction(row)) return;
  if (startSubmittingId.value) return;
  uni.showModal({
    title: "提示",
    content: `确定开始报工?\n工单:${row.workOrderNo || "-"}`,
    success: async (res) => {
      if (!res.confirm) return;
      startSubmittingId.value = row.id;
      await ensureUserInfo();
      uni.showLoading({ title: "提交中...", mask: true });
      try {
        const res = await userListNoPageByTenantId();
        const users = res.data || [];
        // è½¬æ¢ä¸º action-sheet éœ€è¦çš„æ ¼å¼
        producerList.value = users.map(user => ({
          name: user.nickName || "",
          value: user.userId,
        }));
      } catch (error) {
        console.error("加载用户列表失败:", error);
        showToast("加载用户列表失败");
        return;
        const payload = { id: row.id, userId: userStore.id, userName: userStore.nickName };
        const apiRes = await startProduction(payload);
        if (apiRes?.code === 200) {
          showToast("开始报工成功");
          handleQuery();
        } else {
          showToast(apiRes?.msg || "开始报工失败");
        }
      } catch {
        showToast("开始报工失败");
      } finally {
        uni.hideLoading();
        startSubmittingId.value = null;
      }
    }
    showProducerPicker.value = true;
    currentField.value = field; // ä¿å­˜å½“前字段
  };
  // ç”Ÿäº§äººé€‰æ‹©ç¡®è®¤
  const onProducerConfirm = e => {
    if (currentField.value && form.value[currentField.value]) {
      form.value[currentField.value].value = e.value;
      form.value[currentField.value].name = e.name ;
    }
    showProducerPicker.value = false;
  };
  // æ ¼å¼åŒ–日期
  const formatDateTime = (timestamp) => {
    const date = new Date(timestamp);
    const y = date.getFullYear();
    const m = (date.getMonth() + 1).toString().padStart(2, '0');
    const d = date.getDate().toString().padStart(2, '0');
    const h = date.getHours().toString().padStart(2, '0');
    const min = date.getMinutes().toString().padStart(2, '0');
    const s = date.getSeconds().toString().padStart(2, '0');
    return `${y}-${m}-${d} ${h}:${min}:${s}`;
  };
  // å¼€å§‹æ—¶é—´ç¡®è®¤
  const onStartTimeConfirm = (e) => {
    form.value.planStartTime = formatDateTime(e.value);
    showStartTimePicker.value = false;
  };
  // ç»“束时间确认
  const onEndTimeConfirm = (e) => {
    form.value.planEndTime = formatDateTime(e.value);
    showEndTimePicker.value = false;
  };
  // æäº¤çŠ¶æ€
  const submitting = ref(false);
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  // æäº¤è¡¨å•
  const submitForm = async () => {
    submitting.value = true;
    // æ ¡éªŒè¡¨å•
    if (!form.value.deviceName) {
      submitting.value = false;
      showToast("请输入机台");
      return;
    }
    if (!form.value.planStartTime) {
      submitting.value = false;
      showToast("请选择计划开始时间");
      return;
    }
    if (!form.value.planEndTime) {
      submitting.value = false;
      showToast("请选择计划结束时间");
      return;
    }
    if (!form.value.quantity) {
      submitting.value = false;
      showToast("请输入本次生产数量");
      return;
    }
    if (!form.value.userId.value) {
      submitting.value = false;
      showToast("请选择班组信息");
      return;
    }
    if (!form.value.auditUserId.value) {
      submitting.value = false;
      showToast("请选择审核人");
      return;
    }
    // è½¬æ¢ä¸ºæ•°å­—进行比较
    const quantity = Number(form.value.quantity) || 0;
    const scrapQty = Number(form.value.scrapQty) || 0;
    const planQuantity = Number(form.value.planQuantity);
    // éªŒè¯ç”Ÿäº§æ•°é‡å’ŒæŠ¥åºŸæ•°é‡çš„和不能超过待生产数量
    if (quantity + scrapQty > planQuantity) {
      submitting.value = false;
      showToast("生产数量和报废数量的和不能超过待生产数量");
      return;
    }
    if (quantity > planQuantity) {
      submitting.value = false;
      showToast("本次生产数量不能大于待生产数量");
      return;
    }
    // å‡†å¤‡æäº¤æ•°æ®ï¼Œç¡®ä¿æ•°é‡å­—段为数字类型
    const submitData = {
      ...form.value,
      quantity: Number(form.value.quantity),
      scrapQty: Number(form.value.scrapQty) || 0,
      planQuantity: Number(form.value.planQuantity) || 0,
      userId: form.value.userId.value,
      auditUserId: form.value.auditUserId.value,
      auditUserName: form.value.auditUserId.name,
      schedulingUserId: form.value.userId.value, // å…¼å®¹æ—§å­—段
    };
    console.log(submitData, "submitData");
    addProductMain(submitData).then(res => {
      if (res.code === 200) {
        showToast("报工成功");
        submitting.value = false;
        setTimeout(() => {
          goBack();
        }, 1000);
      } else {
        showToast(res.msg || "报工失败");
        submitting.value = false;
      }
    }).catch(err => {
      submitting.value = false;
    })
    ;
  };
  // é¡µé¢åŠ è½½æ—¶åˆå§‹åŒ–æ•°æ®
  onLoad(options => {
    try {
      const orderRow = JSON.parse(options.orderRow);
      console.log(orderRow, "orderRow");
      // ç¡®ä¿ planQuantity è½¬æ¢ä¸ºå­—符串,以便在 u-input ä¸­æ­£ç¡®æ˜¾ç¤º
      form.value.planQuantity = orderRow.planQuantity != null ? String(orderRow.planQuantity) : "";
      form.value.productProcessRouteItemId = orderRow.productProcessRouteItemId || "";
      form.value.workOrderId = orderRow.id || "";
      form.value.deviceName = orderRow.deviceName || "";
      form.value.planStartTime = orderRow.planStartTime || "";
      form.value.planEndTime = orderRow.planEndTime || "";
      getInfo().then(res => {
        // é»˜è®¤ä½¿ç”¨å½“前登录用户,但允许用户修改
        form.value.userId.value = res.user.userId;
        form.value.userId.name = res.user.nickName;
      });
      // ä½¿ç”¨ nextTick ç¡®ä¿ DOM æ›´æ–°
      nextTick(() => {
        console.log("form.value after assignment:", form.value);
      });
    } catch (error) {
      console.error("订单解析失败:", error);
      showToast("订单解析失败");
      goBack();
      return;
    }
  });
};
const formatDateTime = (timestamp) => {
  const date = new Date(timestamp);
  const y = date.getFullYear();
  const m = String(date.getMonth() + 1).padStart(2, "0");
  const d = String(date.getDate()).padStart(2, "0");
  const h = String(date.getHours()).padStart(2, "0");
  const min = String(date.getMinutes()).padStart(2, "0");
  const s = String(date.getSeconds()).padStart(2, "0");
  return `${y}-${m}-${d} ${h}:${min}:${s}`;
};
const openEndReport = async (row) => {
  if (!canEndProduction(row)) return;
  endReportRow.value = row;
  await ensureUserInfo();
  await ensureUserOptions();
  endReportForm.planQuantity = String(getPendingQty(row));
  endReportForm.quantity = String(getPendingQty(row));
  endReportForm.replenishQty = "0";
  endReportForm.scrapQty = "0";
  endReportForm.teamList = [];
  teamCheckedIds.value = [];
  endReportForm.userId = userStore.id || "";
  endReportForm.userName = userStore.nickName || "";
  endReportForm.workOrderId = row.id;
  endReportForm.reportWork = row.reportWork;
  endReportForm.productProcessRouteItemId = row.productProcessRouteItemId || "";
  endReportForm.productMainId = row.productMainId ?? null;
  endReportForm.auditUserId = "";
  endReportForm.auditUserName = "";
  endReportForm.startTime = row.startProductTime || "";
  endReportForm.endTime = "";
  startTimeValue.value = endReportForm.startTime ? new Date(endReportForm.startTime).getTime() : Date.now();
  if (!endReportForm.startTime) {
    endReportForm.startTime = formatDateTime(startTimeValue.value);
  }
  endTimeValue.value = Date.now();
  endReportForm.endTime = formatDateTime(endTimeValue.value);
  endReportVisible.value = true;
};
const closeEndReport = () => {
  endReportVisible.value = false;
  endReportRow.value = null;
};
const openAuditPicker = async () => {
  await ensureUserOptions();
  auditPickerVisible.value = true;
};
const onAuditSelect = (e) => {
  endReportForm.auditUserId = String(e?.value ?? "");
  endReportForm.auditUserName = String(e?.name ?? "");
  auditPickerVisible.value = false;
};
const openTeamPicker = async () => {
  await ensureUserOptions();
  teamCheckedIds.value = (endReportForm.teamList || []).map(i => String(i.userId));
  teamPickerVisible.value = true;
};
const confirmTeamPicker = () => {
  const ids = teamCheckedIds.value || [];
  endReportForm.teamList = ids
    .map(id => {
      const u = userOptions.value.find(x => String(x.value) === String(id));
      return { userId: String(id), userName: u?.name || "" };
    })
    .filter(i => i.userId);
  teamPickerVisible.value = false;
};
const onStartTimeConfirm = (e) => {
  endReportForm.startTime = formatDateTime(e.value);
  startTimePickerVisible.value = false;
};
const onEndTimeConfirm = (e) => {
  endReportForm.endTime = formatDateTime(e.value);
  endTimePickerVisible.value = false;
};
const submitEndReport = async () => {
  if (!endReportRow.value) return;
  const pendingQty = Number(endReportForm.planQuantity) || 0;
  if (pendingQty <= 0) {
    showToast("待生产数量为0,无法报工");
    return;
  }
  const quantity = Number(endReportForm.quantity);
  if (!Number.isFinite(quantity) || !Number.isInteger(quantity) || quantity < 1) {
    showToast("本次生产数量必须为大于等于1的整数");
    return;
  }
  if (quantity > pendingQty) {
    showToast("本次生产数量不能超过待生产数量");
    return;
  }
  const replenishQty = endReportForm.replenishQty === "" ? 0 : Number(endReportForm.replenishQty);
  if (!Number.isFinite(replenishQty) || !Number.isInteger(replenishQty) || replenishQty < 0) {
    showToast("补产数量必须为大于等于0的整数");
    return;
  }
  const scrapQty = endReportForm.scrapQty === "" ? 0 : Number(endReportForm.scrapQty);
  if (!Number.isFinite(scrapQty) || !Number.isInteger(scrapQty) || scrapQty < 0) {
    showToast("报废数量必须为大于等于0的整数");
    return;
  }
  if (!endReportForm.auditUserId) {
    showToast("请选择审核人");
    return;
  }
  await ensureUserInfo();
  uni.showLoading({ title: "提交中...", mask: true });
  try {
    const submitData = {
      ...endReportForm,
      planQuantity: pendingQty,
      quantity,
      replenishQty,
      scrapQty,
      userId: endReportForm.userId || userStore.id,
      userName: endReportForm.userName || userStore.nickName,
      auditUserId: endReportForm.auditUserId,
      auditUserName: endReportForm.auditUserName,
    };
    const res = await addProductMain(submitData);
    if (res?.code === 200) {
      showToast("结束报工成功");
      closeEndReport();
      handleQuery();
    } else {
      showToast(res?.msg || "结束报工失败");
    }
  } catch {
    showToast("结束报工失败");
  } finally {
    uni.hideLoading();
  }
};
onLoad((options) => {
  if (!options?.orderRow) return;
  try {
    const raw = decodeURIComponent(options.orderRow);
    routeOrderRow.value = JSON.parse(raw);
  } catch {
    try {
      routeOrderRow.value = JSON.parse(options.orderRow);
    } catch {
      routeOrderRow.value = null;
    }
  }
});
onShow(() => {
  if (routeOrderRow.value?.id) {
    loadSingleWorkOrder(routeOrderRow.value);
  } else {
    handleQuery();
  }
});
</script>
<style scoped lang="scss">
  @import "@/static/scss/form-common.scss";
@import '@/styles/sales-common.scss';
.work-order {
  min-height: 100vh;
  background: #f8f9fa;
}
.tag-type {
  background-color: #e3f2fd;
  color: #2196f3;
  padding: 2px 8px;
  border-radius: 4px;
  font-size: 12px;
}
.progress-section {
  margin: 15px 0;
  .detail-label {
    display: block;
    margin-bottom: 8px;
    font-size: 13px;
    color: #666;
  }
}
.item-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  padding: 12px 0;
  border-top: 1px solid #f5f5f5;
  :deep(.up-button) {
    margin: 0;
    width: auto;
  }
}
.report-modal {
  background: #fff;
  max-height: 85vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.modal-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 14px 16px;
  border-bottom: 1px solid #f0f0f0;
}
.modal-header-left {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}
.modal-title {
  font-size: 16px;
  font-weight: 600;
}
.modal-subtitle {
  font-size: 12px;
  color: #8a8a8a;
  max-width: 260px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.close-btn {
  padding: 6px;
}
.modal-content {
  flex: 1;
  height: 0;
  padding: 12px 12px 0;
  background: #f7f8fa;
}
.modal-footer {
  display: flex;
  gap: 12px;
  padding: 12px 16px;
  border-top: 1px solid #f0f0f0;
  background: #fff;
}
.report-summary {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 12px 14px;
  background: #fff;
  border-radius: 12px;
  margin-bottom: 12px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.04);
}
.summary-left {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}
.summary-title {
  font-size: 14px;
  font-weight: 600;
  color: #222;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.summary-sub {
  font-size: 12px;
  color: #8a8a8a;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.summary-right {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
  padding-left: 12px;
}
.summary-num {
  font-size: 18px;
  font-weight: 700;
  color: #2979ff;
}
.summary-label {
  font-size: 11px;
  color: #8a8a8a;
}
.form-section {
  background: #fff;
  border-radius: 12px;
  padding: 10px 12px;
  margin-bottom: 12px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.04);
}
.section-title {
  display: block;
  font-size: 13px;
  font-weight: 600;
  color: #333;
  margin: 2px 0 10px;
}
.team-modal {
  background: #fff;
  max-height: 80vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.team-content {
  flex: 1;
  height: 0;
  padding: 12px 16px;
}
.qr-popup {
  padding: 30px;
  background-color: #fff;
  display: flex;
  flex-direction: column;
  align-items: center;
  .qr-title {
    font-size: 18px;
    font-weight: bold;
    margin-bottom: 20px;
  }
  .qr-box {
    padding: 20px;
    background-color: #fff;
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
    border-radius: 8px;
    margin-bottom: 15px;
  }
  .qr-info {
    font-size: 14px;
    color: #666;
  }
}
.no-data {
  padding-top: 100px;
}
</style>
src/pages/works.vue
@@ -133,7 +133,7 @@
      </view>
    </view>
    <!-- è´¢åŠ¡ç®¡ç†æ¨¡å— -->
    <view class="common-module finance-module"
    <!-- <view class="common-module finance-module"
          v-if="hasFinanceManagementItems">
      <view class="module-header">
        <view class="module-title-container">
@@ -153,7 +153,7 @@
          </up-grid-item>
        </up-grid>
      </view>
    </view>
    </view> -->
    <!-- æ¡£æ¡ˆç®¡ç†æ¨¡å— -->
    <view class="common-module archive-module"
          v-if="hasArchiveManagementItems">
@@ -243,7 +243,7 @@
      </view>
    </view>
    <!-- å®‰å…¨ç”Ÿäº§æ¨¡å— -->
    <view class="common-module collaboration-module"
    <!-- <view class="common-module collaboration-module"
          v-if="hasSafetyItems">
      <view class="module-header">
        <view class="module-title-container">
@@ -263,7 +263,7 @@
          </up-grid-item>
        </up-grid>
      </view>
    </view>
    </view> -->
  </view>
</template>
@@ -319,10 +319,10 @@
      icon: "/static/images/icon/gongyingshangwanglai.svg",
      label: "供应商往来",
    },
    {
      icon: "/static/images/icon/caigouguanli.svg",
      label: "采购退货",
    },
    // {
    //   icon: "/static/images/icon/caigouguanli.svg",
    //   label: "采购退货",
    // },
  ]);
  // è´¢åŠ¡ç®¡ç†åŠŸèƒ½æ•°æ®
@@ -406,10 +406,10 @@
      icon: "/static/images/icon/dakaqiandao.svg",
      label: "打卡签到",
    },
    {
      icon: "/static/images/icon/renyuanxinzi.svg",
      label: "人员薪资",
    },
    // {
    //   icon: "/static/images/icon/renyuanxinzi.svg",
    //   label: "人员薪资",
    // },
    {
      icon: "/static/images/icon/hetongguanli.svg",
      label: "合同管理",
@@ -762,7 +762,9 @@
        });
        break;
      case "生产报工":
        getcode();
        uni.navigateTo({
          url: "/pages/productionManagement/productionReport/index",
        });
        break;
      case "生产核算":
        uni.navigateTo({