gaoluyang
3 天以前 f2770f03e7251b32eb576113c522bfbe96e5e385
君歌app
1.依照web端功能修改
已添加4个文件
已修改7个文件
1480 ■■■■ 文件已修改
src/api/personnelManagement/staffOnJob.js 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionProcess.js 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/workOrder.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/config.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/productionDesign/processManagement/edit.vue 231 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/productionDesign/processManagement/index.vue 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/productionManagement/productionReport/components/MaterialDialog.vue 437 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/productionManagement/productionReport/components/filesDia.vue 262 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/productionManagement/productionReport/index.vue 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/productionManagement/workOrder/index.vue 186 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/works.vue 103 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/staffOnJob.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,63 @@
import request from '@/utils/request'
// æŸ¥è¯¢åœ¨èŒå‘˜å·¥å°è´¦
export function staffOnJobListPage(query) {
    return request({
        url: '/staff/staffOnJob/listPage',
        method: 'get',
        params: query,
    })
}
// æŸ¥è¯¢å‘˜å·¥å…¥èŒä¿¡æ¯
export function staffOnJobInfo(id, query) {
    return request({
        url: '/staff/staffOnJob/' + id,
        method: 'get',
        params: query,
    })
}
// æŸ¥è¯¢å‘˜å·¥å…¥èŒä¿¡æ¯
export function getStaffOnJobInfoByUserName(query) {
    return request({
        url: '/staff/staffOnJob/byUserName',
        method: 'get',
        params: query,
    })
}
// æ–°å¢žå‘˜å·¥
export function createStaffOnJob(params) {
    return request({
        url: "/staff/staffOnJob",
        method: "post",
        data: params,
    });
}
// ä¿®æ”¹å‘˜å·¥
export function updateStaffOnJob(id, params) {
    return request({
        url: "/staff/staffOnJob/" + id,
        method: "put",
        data: params,
    });
}
// åˆ é™¤å‘˜å·¥
export function batchDeleteStaffOnJobs(query) {
    return request({
        url: "/staff/staffOnJob/del",
        method: "delete",
        data: query,
    });
}
// ç»­ç­¾åˆåŒ
export function renewContract(id, params) {
    return request({
        url: "/staff/staffOnJob/renewContract/" + id,
        method: "post",
        data: params,
    });
}
src/api/productionManagement/productionProcess.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,104 @@
// å·¥åºé¡µé¢æŽ¥å£
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function listPage(query) {
  return request({
    url: "/technologyOperation/listPage",
    method: "get",
    params: query,
  });
}
export function processList(query) {
  return request({
    url: "/technologyOperation/listPage",
    method: "get",
    params: query,
  });
}
// å·¥åºæŸ¥è¯¢
export function list(query) {
  return request({
    url: "/technologyOperation/listPage",
    method: "get",
    params: query,
  });
}
export function add(data) {
  return request({
    url: "/technologyOperation/add",
    method: "post",
    data: data,
  });
}
export function del(data) {
  return request({
    url: "/technologyOperation/batchDelete",
    method: "delete",
    data: data,
  });
}
export function update(data) {
  return request({
    url: "/technologyOperation/update",
    method: "put",
    data: data,
  });
}
// å¯¼å…¥æ•°æ®
export function importData(data) {
  return request({
    url: "/technologyOperation/importData",
    method: "post",
    data: data,
  });
}
// ä¸‹è½½æ¨¡æ¿
export function downloadTemplate() {
  return request({
    url: "/technologyOperation/downloadTemplate",
    method: "post",
    responseType: "blob",
  });
}
// èŽ·å–å·¥åºå‚æ•°åˆ—è¡¨
export function getProcessParamList(params) {
  return request({
    url: `/technologyOperationParam/list`,
    method: "get",
    params,
  });
}
// æ·»åŠ å·¥åºå‚æ•°
export function addProcessParam(data) {
  return request({
    url: "/technologyOperationParam/",
    method: "post",
    data: data,
  });
}
// ç¼–辑工序参数
export function editProcessParam(data) {
  return request({
    url: "/technologyOperationParam/",
    method: "post",
    data: data,
  });
}
// åˆ é™¤å·¥åºå‚æ•°
export function deleteProcessParam(id) {
  return request({
    url: `/technologyOperationParam/batchDelete/${id}`,
    method: "delete",
  });
}
src/api/productionManagement/workOrder.js
@@ -42,6 +42,15 @@
  });
}
// å¼€å§‹æŠ¥å·¥
export function startWork(data) {
  return request({
    url: "/productionProductMain/startWork",
    method: "post",
    data: data,
  });
}
// èŽ·å–å·¥åºç»Ÿè®¡æ•°æ®
export function getOperationStatistics(query) {
  return request({
src/config.js
@@ -1,7 +1,7 @@
// åº”用全局配置
const config = {
  baseUrl: "http://1.15.17.182:9048",
  fileUrl: "http://1.15.17.182:9049",
  baseUrl: "http://1.15.17.182:9098",
  fileUrl: "http://1.15.17.182:9097",
  // åº”用信息
  appInfo: {
    // åº”用名称
src/pages/productionDesign/processManagement/edit.vue
@@ -6,36 +6,59 @@
             :model="form"
             :rules="rules"
             :errorType="['none']"
             label-width="110">
      <up-form-item label="工序编码"
                    prop="no">
        <up-input v-model="form.no"
                  placeholder="请输入工序编码"
                  clearable />
      </up-form-item>
      <up-form-item label="工序名称"
             label-width="130">
      <up-form-item label="部件"
                    prop="name"
                    required>
        <up-input v-model="form.name"
                  placeholder="请输入工序名称"
                  placeholder="请输入部件"
                  clearable />
      </up-form-item>
      <up-form-item label="工资定额"
      <up-form-item label="工序编号"
                    prop="no">
        <up-input v-model="form.no"
                  placeholder="请输入工序编号"
                  clearable />
      </up-form-item>
      <up-form-item label="工序类型"
                    prop="processType"
                    required>
        <up-input v-model="processTypeText"
                  placeholder="请选择工序类型"
                  readonly
                  @click="showProcessTypeSheet = true" />
        <template #right>
          <up-icon name="arrow-right"
                   @click="showProcessTypeSheet = true"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="计划工时(小时)"
                    prop="salaryQuota">
        <up-input v-model="form.salaryQuota"
                  type="number"
                  placeholder="请输入工资定额"
                  placeholder="请输入计划工时"
                  clearable />
      </up-form-item>
      <up-form-item label="计费类型"
                    prop="type">
        <up-input v-model="typeText"
                  placeholder="请选择计费类型"
      <up-form-item label="计划人员"
                    prop="planPerson">
        <up-input v-model="planPersonText"
                  placeholder="请选择计划人员"
                  readonly
                  @click="showTypeSheet = true" />
                  @click="showPlanPersonSheet = true" />
        <template #right>
          <up-icon name="arrow-right"
                   @click="showTypeSheet = true"></up-icon>
                   @click="showPlanPersonSheet = true"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="计划执行人员"
                    prop="executor">
        <up-input v-model="executorText"
                  placeholder="请选择计划执行人员"
                  readonly
                  @click="showExecutorSheet = true" />
        <template #right>
          <up-icon name="arrow-right"
                   @click="showExecutorSheet = true"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="是否质检"
@@ -44,27 +67,22 @@
          <up-switch v-model="form.isQuality" />
        </view>
      </up-form-item>
      <up-form-item label="是否生产"
                    prop="isProduction">
      <up-form-item label="是否入库"
                    prop="inbound">
        <view style="display: flex; justify-content: flex-end; width: 100%;">
          <up-switch v-model="form.isProduction" />
          <up-switch v-model="form.inbound" />
        </view>
      </up-form-item>
      <up-form-item label="关联设备"
                    prop="deviceLedgerId">
        <up-input v-model="deviceText"
                  placeholder="请选择关联设备"
                  readonly
                  @click="showDeviceSheet = true" />
        <template #right>
          <up-icon name="arrow-right"
                   @click="showDeviceSheet = true"></up-icon>
        </template>
      <up-form-item label="是否报工"
                    prop="reportWork">
        <view style="display: flex; justify-content: flex-end; width: 100%;">
          <up-switch v-model="form.reportWork" />
        </view>
      </up-form-item>
      <up-form-item label="工序描述"
      <up-form-item label="备注"
                    prop="remark">
        <up-textarea v-model="form.remark"
                     placeholder="请输入工序描述"
                     placeholder="请输入备注"
                     autoHeight />
      </up-form-item>
    </up-form>
@@ -72,18 +90,24 @@
                   :confirmText="processId ? '保存' : '新增'"
                   @cancel="goBack"
                   @confirm="handleSubmit" />
    <!-- è®¡è´¹ç±»åž‹é€‰æ‹© -->
    <up-action-sheet :show="showTypeSheet"
                     title="选择计费类型"
                     :actions="typeActions"
                     @select="onSelectType"
                     @close="showTypeSheet = false" />
    <!-- è®¾å¤‡é€‰æ‹© -->
    <up-action-sheet :show="showDeviceSheet"
                     title="选择关联设备"
                     :actions="deviceActions"
                     @select="onSelectDevice"
                     @close="showDeviceSheet = false" />
    <!-- å·¥åºç±»åž‹é€‰æ‹© -->
    <up-action-sheet :show="showProcessTypeSheet"
                     title="选择工序类型"
                     :actions="processTypeActions"
                     @select="onSelectProcessType"
                     @close="showProcessTypeSheet = false" />
    <!-- è®¡åˆ’人员选择 -->
    <up-action-sheet :show="showPlanPersonSheet"
                     title="选择计划人员"
                     :actions="employeeActions"
                     @select="onSelectPlanPerson"
                     @close="showPlanPersonSheet = false" />
    <!-- è®¡åˆ’执行人员选择 -->
    <up-action-sheet :show="showExecutorSheet"
                     title="选择计划执行人员"
                     :actions="employeeActions"
                     @select="onSelectExecutor"
                     @close="showExecutorSheet = false" />
  </view>
</template>
@@ -91,35 +115,49 @@
  import { reactive, ref, computed, onMounted } from "vue";
  import { onLoad, onReady } from "@dcloudio/uni-app";
  import FooterButtons from "@/components/FooterButtons.vue";
  import {
    add,
    update,
    getDeviceLedger,
  } from "@/api/productionManagement/processManagement";
  import { add, update } from "@/api/productionManagement/processManagement";
  import { staffOnJobListPage } from "@/api/personnelManagement/onboarding";
  const formRef = ref(null);
  const loading = ref(false);
  const processId = ref(null);
  const pageTitle = computed(() => (processId.value ? "编辑工序" : "新增工序"));
  const processTypeOptions = [
    "机加工",
    "刮板冷芯制作",
    "管路组对",
    "罐体连接及调试",
    "测试打压",
    "其他",
  ];
  const employeeList = ref([]);
  const form = ref({
    no: "",
    name: "",
    processType: "",
    salaryQuota: "",
    planPerson: null,
    executor: null,
    isQuality: false,
    isProduction: false,
    inbound: false,
    reportWork: false,
    remark: "",
    deviceLedgerId: null,
    type: 0,
  });
  const rules = {
    name: [{ required: true, message: "请输入工序名称" }],
    name: [
      { required: true, message: "请输入部件" },
      { max: 100, message: "最多100个字符" },
    ],
    processType: [{ required: true, message: "请选择工序类型" }],
    salaryQuota: [
      {
        validator: (rule, value, callback) => {
          if (value !== "" && value !== null && (isNaN(value) || value < 0)) {
            callback(new Error("工资定额必须是非负数字"));
            callback(new Error("计划工时必须是非负数字"));
          } else {
            callback();
          }
@@ -128,46 +166,60 @@
    ],
  };
  const showTypeSheet = ref(false);
  const typeActions = [
    { name: "计时", value: 0 },
    { name: "计件", value: 1 },
  ];
  const typeText = computed(() => {
    const action = typeActions.find(a => a.value === form.value.type);
    return action ? action.name : "";
  });
  const showProcessTypeSheet = ref(false);
  const processTypeActions = processTypeOptions.map(item => ({ name: item, value: item }));
  const processTypeText = ref("");
  const showDeviceSheet = ref(false);
  const deviceActions = ref([]);
  const deviceText = ref("");
  const showPlanPersonSheet = ref(false);
  const showExecutorSheet = ref(false);
  const planPersonText = ref("");
  const executorText = ref("");
  const onSelectType = e => {
    form.value.type = e.value;
    showTypeSheet.value = false;
  const employeeActions = computed(() =>
    employeeList.value.map(item => ({
      name: item.staffName,
      id: item.id,
    }))
  );
  const onSelectProcessType = e => {
    form.value.processType = e.value;
    processTypeText.value = e.name;
    showProcessTypeSheet.value = false;
  };
  const onSelectDevice = e => {
    form.value.deviceLedgerId = e.id;
    deviceText.value = e.name;
    showDeviceSheet.value = false;
  const onSelectPlanPerson = e => {
    form.value.planPerson = e.id;
    planPersonText.value = e.name;
    showPlanPersonSheet.value = false;
  };
  const loadDevices = async () => {
  const onSelectExecutor = e => {
    form.value.executor = e.id;
    executorText.value = e.name;
    showExecutorSheet.value = false;
  };
  const loadEmployees = async () => {
    try {
      const { data } = await getDeviceLedger();
      deviceActions.value = (data || []).map(item => ({
        name: item.deviceName,
        id: item.id,
      }));
      if (form.value.deviceLedgerId) {
        const device = deviceActions.value.find(
          d => d.id === Number(form.value.deviceLedgerId)
        );
        if (device) deviceText.value = device.name;
      }
      const res = await staffOnJobListPage({ current: -1, size: -1, staffState: 1 });
      employeeList.value = res.data?.records || [];
    } catch (error) {
      console.error("加载设备失败", error);
      console.error("加载员工列表失败", error);
    }
  };
  const resolveDisplayTexts = () => {
    if (form.value.processType) {
      processTypeText.value = form.value.processType;
    }
    if (form.value.planPerson) {
      const emp = employeeList.value.find(e => e.id === form.value.planPerson);
      if (emp) planPersonText.value = emp.staffName;
    }
    if (form.value.executor) {
      const emp = employeeList.value.find(e => e.id === form.value.executor);
      if (emp) executorText.value = emp.staffName;
    }
  };
@@ -210,10 +262,9 @@
      const item = JSON.parse(decodeURIComponent(option.item));
      processId.value = item.id;
      Object.assign(form.value, item);
      // å¤„理类型转换,确保是数字
      form.value.type = Number(form.value.type);
      form.value.isQuality = !!form.value.isQuality;
      form.value.isProduction = !!form.value.isProduction;
      form.value.inbound = !!form.value.inbound;
      form.value.reportWork = !!form.value.reportWork;
    }
  });
@@ -222,7 +273,7 @@
  });
  onMounted(() => {
    loadDevices();
    loadEmployees().then(() => resolveDisplayTexts());
  });
</script>
src/pages/productionDesign/processManagement/index.vue
@@ -7,7 +7,7 @@
        <view class="search-input">
          <up-input class="search-text"
                    v-model="queryParams.name"
                    placeholder="请输入工序名称"
                    placeholder="请输入部件名称"
                    clearable
                    @change="handleSearch" />
        </view>
@@ -38,35 +38,38 @@
        <up-divider></up-divider>
        <view class="item-details">
          <view class="detail-row">
            <text class="detail-label">部件类型</text>
            <text class="detail-value">{{ item.processType || "-" }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">关联设备</text>
            <text class="detail-value">{{ getDeviceName(item.deviceLedgerId) }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">工资定额</text>
            <text class="detail-value highlight">Â¥{{ item.salaryQuota || 0 }}</text>
            <text class="detail-label">计划工时</text>
            <text class="detail-value highlight">{{ item.salaryQuota || 0 }}小时</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">工序状态</text>
            <view class="detail-value">
              <up-tag :text="item.isQuality ? '质检' : '非质检'"
                      :type="item.isQuality ? 'warning' : 'info'"
                      size="mini"
                      style="margin-left: 8rpx" />
              <up-tag :text="item.isProduction ? '生产' : '不生产'"
                      :type="item.isProduction ? 'warning' : 'info'"
                      size="mini"
                      style="margin-left: 8rpx" />
              <up-tag v-if="item.type !== null && item.type !== undefined"
                      :text="item.type == 0 ? '计时' : '计件'"
                      :type="item.type == 1 ? 'primary' : 'success'"
                      size="mini"
                      style="margin-left: 8rpx" />
            </view>
            <text class="detail-label">计划人员</text>
            <text class="detail-value">{{ getEmployeeName(item.planPerson) }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">计划执行人员</text>
            <text class="detail-value">{{ getEmployeeName(item.executor) }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">备注</text>
            <text class="detail-value">{{ item.remark || "-" }}</text>
          </view>
        </view>
        <view class="status-tags">
          <up-tag :text="item.isQuality ? '质检' : '非质检'"
                  :type="item.isQuality ? 'warning' : 'info'"
                  size="mini" />
          <up-tag :text="item.isProduction ? '生产' : '不生产'"
                  :type="item.isProduction ? 'warning' : 'info'"
                  size="mini"
                  style="margin-left: 8rpx" />
        </view>
        <view class="action-buttons">
          <up-button class="action-btn"
@@ -87,7 +90,7 @@
    </view>
    <view v-else
          class="no-data">
      <text>暂无工序数据</text>
      <text>暂无部件数据</text>
    </view>
    <view class="fab-button"
          @click="goAdd">
@@ -106,12 +109,14 @@
    del,
    getDeviceLedger,
  } from "@/api/productionManagement/processManagement";
  import { staffOnJobListPage } from "@/api/personnelManagement/onboarding";
  const queryParams = reactive({
    name: "",
  });
  const list = ref([]);
  const deviceOptions = ref([]);
  const employeeOptions = ref([]);
  const pageStatus = ref("loadmore");
  const page = reactive({
@@ -130,12 +135,27 @@
    return device?.deviceName || "未关联";
  };
  const getEmployeeName = employeeId => {
    if (!employeeId) return "未指定";
    const emp = employeeOptions.value.find(item => item.id === Number(employeeId));
    return emp?.staffName || "未指定";
  };
  const loadDevices = async () => {
    try {
      const { data } = await getDeviceLedger();
      deviceOptions.value = data || [];
    } catch (error) {
      console.error("加载设备列表失败", error);
    }
  };
  const loadEmployees = async () => {
    try {
      const res = await staffOnJobListPage({ current: -1, size: -1, staffState: 1 });
      employeeOptions.value = res.data?.records || [];
    } catch (error) {
      console.error("加载员工列表失败", error);
    }
  };
@@ -217,7 +237,7 @@
  });
  onShow(async () => {
    await loadDevices();
    await Promise.all([loadDevices(), loadEmployees()]);
    handleSearch();
  });
</script>
@@ -232,6 +252,12 @@
    font-size: 28rpx;
  }
  .status-tags {
    display: flex;
    align-items: center;
    padding-bottom: 16rpx;
  }
  .action-buttons {
    display: flex;
    justify-content: flex-end;
src/pages/productionManagement/productionReport/components/MaterialDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,437 @@
<template>
  <view>
    <!-- ä¸»ç‰©æ–™å¼¹çª— -->
    <view v-if="dialogVisible" class="material-overlay">
      <view class="material-container">
        <view class="material-header">
          <text class="material-title">物料</text>
          <view class="close-btn" @click="dialogVisible = false">
            <up-icon name="close" size="20" color="#666" />
          </view>
        </view>
        <scroll-view class="material-body" scroll-y>
          <view v-if="materialTableData.length === 0" class="empty-tip">
            <text>暂无物料数据</text>
          </view>
          <view v-for="item in materialTableData" :key="item.id" class="material-card">
            <view class="material-row">
              <text class="mc-label">工序名称</text>
              <text class="mc-value">{{ item.processName || '-' }}</text>
            </view>
            <view class="material-row">
              <text class="mc-label">原料名称</text>
              <text class="mc-value">{{ item.materialName || '-' }}</text>
            </view>
            <view class="material-row">
              <text class="mc-label">原料型号</text>
              <text class="mc-value">{{ item.materialModel || '-' }}</text>
            </view>
            <view class="material-row">
              <text class="mc-label">计量单位</text>
              <text class="mc-value">{{ item.unit || '-' }}</text>
            </view>
            <view class="material-row">
              <text class="mc-label">线边仓数量</text>
              <text class="mc-value">{{ item.pickQty || 0 }}</text>
            </view>
            <view class="material-row">
              <text class="mc-label">补料数量</text>
              <text class="mc-value">{{ item.supplementQty || 0 }}</text>
            </view>
            <view class="material-row">
              <text class="mc-label">实际数量</text>
              <view class="mc-value">
                <up-input v-model="item.actualQty"
                          type="number"
                          placeholder="请输入实际数量"
                          clearable
                          style="width: 200rpx" />
              </view>
            </view>
            <view class="material-actions">
              <up-button size="small" type="primary" @click="openSupplementDialog(item)">补料</up-button>
              <up-button size="small" type="info" @click="openSupplementRecordDialog(item)">补料记录</up-button>
            </view>
          </view>
        </scroll-view>
        <view class="material-footer">
          <up-button type="primary" :loading="pickSubmitting" @click="handleSubmitPick">领用</up-button>
          <up-button @click="dialogVisible = false">取消</up-button>
        </view>
      </view>
    </view>
    <!-- è¡¥æ–™å¼¹çª— -->
    <view v-if="supplementDialogVisible" class="material-overlay">
      <view class="material-container supplement-container">
        <view class="material-header">
          <text class="material-title">补料</text>
          <view class="close-btn" @click="supplementDialogVisible = false">
            <up-icon name="close" size="20" color="#666" />
          </view>
        </view>
        <view class="material-body">
          <up-form :model="supplementForm" ref="supplementFormRef" label-width="140">
            <up-form-item label="补料数量" prop="supplementQty" required>
              <up-input v-model="supplementForm.supplementQty"
                        type="number"
                        placeholder="请输入补料数量"
                        clearable />
            </up-form-item>
            <up-form-item label="补料原因" prop="supplementReason" required>
              <up-textarea v-model="supplementForm.supplementReason"
                           placeholder="请输入补料原因"
                           :maxlength="200"
                           autoHeight />
            </up-form-item>
          </up-form>
        </view>
        <view class="material-footer">
          <up-button type="primary" :loading="supplementSubmitting" @click="handleSubmitSupplement">确定</up-button>
          <up-button @click="supplementDialogVisible = false">取消</up-button>
        </view>
      </view>
    </view>
    <!-- è¡¥æ–™è®°å½•弹窗 -->
    <view v-if="supplementRecordDialogVisible" class="material-overlay">
      <view class="material-container supplement-record-container">
        <view class="material-header">
          <text class="material-title">补料记录</text>
          <view class="close-btn" @click="supplementRecordDialogVisible = false">
            <up-icon name="close" size="20" color="#666" />
          </view>
        </view>
        <scroll-view class="material-body" scroll-y>
          <view v-if="supplementRecordTableData.length === 0" class="empty-tip">
            <text>暂无补料记录</text>
          </view>
          <view v-for="item in supplementRecordTableData" :key="item.id" class="record-card">
            <view class="material-row">
              <text class="mc-label">补料数量</text>
              <text class="mc-value">{{ item.supplementQty }}</text>
            </view>
            <view class="material-row">
              <text class="mc-label">补料原因</text>
              <text class="mc-value">{{ item.supplementReason }}</text>
            </view>
            <view class="material-row">
              <text class="mc-label">补料人</text>
              <text class="mc-value">{{ item.supplementUserName }}</text>
            </view>
            <view class="material-row">
              <text class="mc-label">补料日期</text>
              <text class="mc-value">{{ item.supplementTime }}</text>
            </view>
          </view>
        </scroll-view>
        <view class="material-footer">
          <up-button @click="supplementRecordDialogVisible = false">关闭</up-button>
        </view>
      </view>
    </view>
  </view>
</template>
<script setup>
  import { computed, nextTick, reactive, ref, watch } from "vue";
  import {
    listWorkOrderMaterialLedger,
    addWorkOrderMaterialSupplement,
    listWorkOrderMaterialSupplementRecord,
    pickWorkOrderMaterial,
  } from "@/api/productionManagement/workOrder.js";
  const props = defineProps({
    modelValue: {
      type: Boolean,
      default: false,
    },
    rowData: {
      type: Object,
      default: () => null,
    },
  });
  const emit = defineEmits(["update:modelValue", "refresh"]);
  const dialogVisible = computed({
    get: () => props.modelValue,
    set: val => emit("update:modelValue", val),
  });
  const materialTableLoading = ref(false);
  const materialTableData = ref([]);
  const currentMaterialRow = ref(null);
  const currentMaterialOrderRow = ref(null);
  const pickSubmitting = ref(false);
  const supplementDialogVisible = ref(false);
  const supplementSubmitting = ref(false);
  const supplementFormRef = ref(null);
  const supplementForm = reactive({
    supplementQty: null,
    supplementReason: "",
  });
  const supplementRecordDialogVisible = ref(false);
  const supplementRecordLoading = ref(false);
  const supplementRecordTableData = ref([]);
  const loadMaterialTable = async row => {
    if (!row?.id) return;
    currentMaterialOrderRow.value = row;
    materialTableLoading.value = true;
    materialTableData.value = [];
    try {
      const res = await listWorkOrderMaterialLedger({
        workOrderId: row.id,
        processId: row.processId,
        productProcessRouteItemId: row.productProcessRouteItemId,
      });
      materialTableData.value = res.data || [];
    } catch (e) {
      console.error("获取物料台账失败", e);
      uni.showToast({ title: "获取物料台账失败", icon: "error" });
    } finally {
      materialTableLoading.value = false;
    }
  };
  watch(
    () => props.modelValue,
    visible => {
      if (visible && props.rowData) {
        loadMaterialTable(props.rowData);
      }
    }
  );
  const resetSupplementForm = () => {
    supplementForm.supplementQty = null;
    supplementForm.supplementReason = "";
  };
  const openSupplementDialog = row => {
    currentMaterialRow.value = row;
    resetSupplementForm();
    supplementDialogVisible.value = true;
  };
  const handleSubmitSupplement = () => {
    if (!supplementForm.supplementQty) {
      uni.showToast({ title: "请输入补料数量", icon: "none" });
      return;
    }
    if (!supplementForm.supplementReason) {
      uni.showToast({ title: "请输入补料原因", icon: "none" });
      return;
    }
    if (!currentMaterialRow.value?.id) {
      uni.showToast({ title: "缺少物料明细ID", icon: "none" });
      return;
    }
    supplementSubmitting.value = true;
    addWorkOrderMaterialSupplement({
      materialLedgerId: currentMaterialRow.value.id,
      supplementQty: Number(supplementForm.supplementQty),
      supplementReason: supplementForm.supplementReason,
      workOrderId: currentMaterialOrderRow.value?.id,
    })
      .then(async () => {
        supplementDialogVisible.value = false;
        await loadMaterialTable(currentMaterialOrderRow.value);
        uni.showToast({ title: "补料成功" });
        emit("refresh");
      })
      .catch(e => {
        console.error("补料失败", e);
        uni.showToast({ title: "补料失败", icon: "error" });
      })
      .finally(() => {
        supplementSubmitting.value = false;
      });
  };
  const openSupplementRecordDialog = async row => {
    supplementRecordDialogVisible.value = true;
    supplementRecordLoading.value = true;
    supplementRecordTableData.value = [];
    try {
      const res = await listWorkOrderMaterialSupplementRecord({
        materialLedgerId: row.id,
      });
      supplementRecordTableData.value = res.data || [];
    } catch (e) {
      console.error("获取补料记录失败", e);
      uni.showToast({ title: "获取补料记录失败", icon: "error" });
    } finally {
      supplementRecordLoading.value = false;
    }
  };
  const validatePickRows = () => {
    if (materialTableData.value.length === 0) {
      return { valid: false, message: "暂无可领用物料" };
    }
    const invalidRow = materialTableData.value.find(
      item =>
        item.actualQty === null ||
        item.actualQty === undefined ||
        item.actualQty === ""
    );
    if (invalidRow) {
      return { valid: false, message: "请填写实际数量后再领用" };
    }
    const exceedRow = materialTableData.value.find(item => {
      const maxQty = Number(item.pickQty || 0) + Number(item.supplementQty || 0);
      return Number(item.actualQty || 0) > maxQty;
    });
    if (exceedRow) {
      return { valid: false, message: "实际数量不能大于领用数量+补料数量" };
    }
    return { valid: true, message: "" };
  };
  const handleSubmitPick = async () => {
    if (!currentMaterialOrderRow.value?.id) return;
    const validateResult = validatePickRows();
    if (!validateResult.valid) {
      uni.showToast({ title: validateResult.message, icon: "none" });
      return;
    }
    pickSubmitting.value = true;
    try {
      await pickWorkOrderMaterial({
        workOrderId: currentMaterialOrderRow.value.id,
        items: materialTableData.value.map(item => ({
          materialLedgerId: item.id,
          actualQty: Number(item.actualQty || 0),
        })),
      });
      uni.showToast({ title: "领用成功" });
      await loadMaterialTable(currentMaterialOrderRow.value);
      emit("refresh");
    } catch (e) {
      console.error("领用失败", e);
      uni.showToast({ title: "领用失败", icon: "error" });
    } finally {
      pickSubmitting.value = false;
    }
  };
</script>
<style scoped lang="scss">
  .material-overlay {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.5);
    z-index: 999;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .material-container {
    width: 92%;
    max-height: 85vh;
    background: #fff;
    border-radius: 16rpx;
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }
  .supplement-container {
    max-height: 60vh;
  }
  .supplement-record-container {
    max-height: 75vh;
  }
  .material-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 24rpx 32rpx;
    border-bottom: 1px solid #f0f0f0;
    flex-shrink: 0;
  }
  .material-title {
    font-size: 32rpx;
    font-weight: 600;
    color: #303133;
  }
  .close-btn {
    padding: 8rpx;
  }
  .material-body {
    flex: 1;
    padding: 16rpx 24rpx;
    overflow-y: auto;
  }
  .empty-tip {
    text-align: center;
    padding: 60rpx 0;
    color: #999;
    font-size: 28rpx;
  }
  .material-card {
    background: #f9fafb;
    border-radius: 12rpx;
    padding: 20rpx;
    margin-bottom: 16rpx;
  }
  .material-row {
    display: flex;
    align-items: center;
    padding: 10rpx 0;
  }
  .mc-label {
    width: 160rpx;
    font-size: 26rpx;
    color: #909399;
    flex-shrink: 0;
  }
  .mc-value {
    flex: 1;
    font-size: 26rpx;
    color: #303133;
  }
  .material-actions {
    display: flex;
    gap: 16rpx;
    margin-top: 16rpx;
    padding-top: 16rpx;
    border-top: 1px solid #ebeef5;
  }
  .record-card {
    background: #f9fafb;
    border-radius: 12rpx;
    padding: 20rpx;
    margin-bottom: 16rpx;
  }
  .material-footer {
    display: flex;
    justify-content: flex-end;
    gap: 16rpx;
    padding: 20rpx 32rpx;
    border-top: 1px solid #f0f0f0;
    flex-shrink: 0;
  }
</style>
src/pages/productionManagement/productionReport/components/filesDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,262 @@
<template>
  <view v-if="visible" class="files-overlay">
    <view class="files-container">
      <view class="files-header">
        <text class="files-title">工单附件</text>
        <view class="close-btn" @click="closeDia">
          <up-icon name="close" size="20" color="#666" />
        </view>
      </view>
      <view class="files-toolbar">
        <up-button type="primary" size="small" @click="handleUpload">上传图片</up-button>
        <up-button type="error" size="small" plain @click="handleDelete">删除</up-button>
      </view>
      <scroll-view class="files-list" scroll-y>
        <view v-if="tableData.length === 0" class="empty-tip">
          <text>暂无附件</text>
        </view>
        <view v-for="item in tableData" :key="item.id" class="file-item">
          <view class="file-left" @click="toggleSelect(item)">
            <up-icon :name="selectedIds.includes(item.id) ? 'checkbox-mark' : 'checkbox'"
                     size="20"
                     :color="selectedIds.includes(item.id) ? '#2979ff' : '#c0c4cc'" />
          </view>
          <view class="file-info" @click="handlePreview(item)">
            <text class="file-name">{{ item.name }}</text>
          </view>
          <view class="file-actions">
            <text class="action-link" @click="handleDownload(item)">下载</text>
            <text class="action-link" @click="handlePreview(item)">预览</text>
          </view>
        </view>
      </scroll-view>
    </view>
  </view>
</template>
<script setup>
  import { ref, getCurrentInstance } from "vue";
  import { getToken } from "@/utils/auth.js";
  import {
    productWorkOrderFileAdd,
    productWorkOrderFileDel,
    productWorkOrderFileListPage,
  } from "@/api/productionManagement/productWorkOrderFile.js";
  const { proxy } = getCurrentInstance();
  const visible = ref(false);
  const currentWorkOrderId = ref("");
  const selectedIds = ref([]);
  const tableData = ref([]);
  const tableLoading = ref(false);
  const uploadUrl = import.meta.env.VITE_APP_BASE_API + "/file/upload";
  const openDialog = row => {
    visible.value = true;
    currentWorkOrderId.value = row.id;
    selectedIds.value = [];
    getList();
  };
  const closeDia = () => {
    visible.value = false;
  };
  const getList = () => {
    tableLoading.value = true;
    productWorkOrderFileListPage({
      workOrderId: currentWorkOrderId.value,
      current: 1,
      size: 100,
    })
      .then(res => {
        tableData.value = res.data.records || [];
      })
      .finally(() => {
        tableLoading.value = false;
      });
  };
  const toggleSelect = item => {
    const idx = selectedIds.value.indexOf(item.id);
    if (idx > -1) {
      selectedIds.value.splice(idx, 1);
    } else {
      selectedIds.value.push(item.id);
    }
  };
  const handleUpload = () => {
    uni.chooseImage({
      count: 1,
      sizeType: ["compressed"],
      sourceType: ["album", "camera"],
      success: res => {
        const tempFilePath = res.tempFilePaths[0];
        uni.showLoading({ title: "上传中..." });
        uni.uploadFile({
          url: uploadUrl,
          filePath: tempFilePath,
          name: "file",
          header: {
            Authorization: "Bearer " + getToken(),
          },
          success: uploadRes => {
            const data = JSON.parse(uploadRes.data);
            if (data.code === 200) {
              const fileRow = {
                name: data.data.originalName,
                url: data.data.tempPath,
                workOrderId: currentWorkOrderId.value,
              };
              productWorkOrderFileAdd(fileRow).then(() => {
                uni.showToast({ title: "上传成功" });
                getList();
              });
            } else {
              uni.showToast({ title: "上传失败", icon: "error" });
            }
          },
          fail: () => {
            uni.showToast({ title: "上传失败", icon: "error" });
          },
          complete: () => {
            uni.hideLoading();
          },
        });
      },
    });
  };
  const handleDelete = () => {
    if (selectedIds.value.length === 0) {
      uni.showToast({ title: "请选择数据", icon: "none" });
      return;
    }
    uni.showModal({
      title: "删除",
      content: "选中的内容将被删除,是否确认删除?",
      success: res => {
        if (res.confirm) {
          productWorkOrderFileDel(selectedIds.value).then(() => {
            uni.showToast({ title: "删除成功" });
            selectedIds.value = [];
            getList();
          });
        }
      },
    });
  };
  const handleDownload = row => {
    proxy.$download.byUrl(row.url, row.originalFilename);
  };
  const handlePreview = row => {
    uni.previewImage({
      urls: [row.url],
      current: row.url,
    });
  };
  defineExpose({
    openDialog,
  });
</script>
<style scoped lang="scss">
  .files-overlay {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.5);
    z-index: 999;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .files-container {
    width: 90%;
    max-height: 80vh;
    background: #fff;
    border-radius: 16rpx;
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }
  .files-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 24rpx 32rpx;
    border-bottom: 1px solid #f0f0f0;
  }
  .files-title {
    font-size: 32rpx;
    font-weight: 600;
    color: #303133;
  }
  .close-btn {
    padding: 8rpx;
  }
  .files-toolbar {
    display: flex;
    justify-content: flex-end;
    gap: 16rpx;
    padding: 16rpx 32rpx;
    border-bottom: 1px solid #f0f0f0;
  }
  .files-list {
    flex: 1;
    padding: 16rpx 32rpx;
    overflow-y: auto;
  }
  .empty-tip {
    text-align: center;
    padding: 60rpx 0;
    color: #999;
    font-size: 28rpx;
  }
  .file-item {
    display: flex;
    align-items: center;
    padding: 20rpx 0;
    border-bottom: 1px solid #f5f5f5;
  }
  .file-left {
    margin-right: 16rpx;
  }
  .file-info {
    flex: 1;
  }
  .file-name {
    font-size: 28rpx;
    color: #303133;
  }
  .file-actions {
    display: flex;
    gap: 24rpx;
    flex-shrink: 0;
  }
  .action-link {
    font-size: 26rpx;
    color: #2979ff;
  }
</style>
src/pages/productionManagement/productionReport/index.vue
@@ -164,6 +164,8 @@
    reportWork: "",
    productionOrderRoutingOperationId: "",
    productionOrderId: "",
    productMainId: null,
    productProcessRouteItemId: "",
    workHour: 0,
    type: null,
    paramGroups: {},
@@ -352,11 +354,13 @@
    const submitData = {
      quantity: quantity,
      scrapQty: scrapQty,
      scrapQty: isNaN(scrapQty) ? 0 : scrapQty,
      userId: form.value.userId,
      userName: form.value.userName,
      productionOperationTaskId: form.value.workOrderId,
      productProcessRouteItemId: form.value.productProcessRouteItemId,
      reportWork: form.value.reportWork,
      productMainId: form.value.productMainId,
      productionOrderRoutingOperationId:
        form.value.productionOrderRoutingOperationId,
      productionOrderId: form.value.productionOrderId,
@@ -429,7 +433,14 @@
        Math.max(0, planQuantity - completeQuantity)
      );
      form.value.workOrderId = orderRow.id || "";
      form.value.productProcessRouteItemId =
        orderRow.productProcessRouteItemId || "";
      form.value.reportWork = orderRow.reportWork || "";
      form.value.productMainId = orderRow.productMainId || null;
      form.value.scrapQty =
        orderRow.scrapQty !== undefined && orderRow.scrapQty !== null
          ? orderRow.scrapQty
          : "";
      form.value.productionOrderRoutingOperationId =
        orderRow.productionOrderRoutingOperationId || "";
      form.value.productionOrderId = orderRow.productionOrderId || "";
src/pages/productionManagement/workOrder/index.vue
@@ -1,27 +1,34 @@
<template>
  <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
          />
          <up-input class="search-text"
                    v-model="data.searchForm.workOrderNo"
                    placeholder="工单编号"
                    @confirm="handleQuery"
                    clearable />
        </view>
        <view class="search-input">
          <up-input class="search-text"
                    v-model="data.searchForm.npsNo"
                    placeholder="生产订单号"
                    @confirm="handleQuery"
                    clearable />
        </view>
        <view class="filter-button" @click="handleQuery">
          <up-icon name="search" size="24" color="#999"></up-icon>
        </view>
      </view>
      <view class="switch-row">
        <text class="switch-label">仅看我的</text>
        <up-switch v-model="filterMine" @change="handleQuery" size="18" />
      </view>
    </view>
    <!-- å·¥å•列表 -->
    <scroll-view scroll-y class="ledger-list" v-if="tableData.length > 0" @scrolltolower="loadMore">
      <view v-for="(item, index) in tableData" :key="item.id || index" class="ledger-item">
@@ -36,10 +43,14 @@
            <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.npsNo || '-' }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">产品名称</text>
            <text class="detail-value">{{ item.productName }}</text>
@@ -50,24 +61,24 @@
          </view>
          <view class="detail-row">
            <text class="detail-label">工序名称</text>
            <text class="detail-value">{{ item.processName }}</text>
            <text class="detail-value">{{ item.operationName }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">需求/完成数量</text>
            <text class="detail-value">{{ item.planQuantity }} / {{ item.completeQuantity }} {{ item.unit }}</text>
          </view>
          <view class="progress-section">
            <text class="detail-label">完成进度</text>
            <view class="progress-bar">
              <up-line-progress
                :percentage="toProgressPercentage(item.completionStatus)"
              <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.planStartTime }}</text>
@@ -76,51 +87,52 @@
            <text class="detail-label">计划结束</text>
            <text class="detail-value">{{ item.planEndTime }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">实际开始</text>
            <text class="detail-value">{{ item.actualStartTime || '-' }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">实际结束</text>
            <text class="detail-value">{{ item.actualEndTime || '-' }}</text>
          </view>
        </view>
        <view class="item-actions">
          <up-button v-if="showStartReport(item)"
                     class="action-btn"
                     size="small"
                     type="primary"
                     @click="handleStartWork(item)">开始报工</up-button>
          <up-button v-if="showEndReport(item)"
                     class="action-btn"
                     size="small"
                     type="success"
                     @click="goReport(item)">结束报工</up-button>
        </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-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>
    <!-- é™„件组件 -->
    <FilesDia ref="workOrderFilesRef" />
  </view>
</template>
<script setup>
import { ref, reactive, toRefs, getCurrentInstance } from "vue";
import { ref, reactive } from "vue";
import { onShow } from '@dcloudio/uni-app';
import { productWorkOrderPage } from "@/api/productionManagement/workOrder.js";
import { productWorkOrderPage, startWork } from "@/api/productionManagement/workOrder.js";
import PageHeader from "@/components/PageHeader.vue";
import FilesDia from "./components/filesDia.vue";
import useUserStore from "@/store/modules/user";
const { proxy } = getCurrentInstance();
const userStore = useUserStore();
const loading = ref(false);
const tableData = ref([]);
const loadStatus = ref('loadmore');
const transferCardVisible = ref(false);
const transferCardRowData = ref(null);
const workOrderFilesRef = ref(null);
const filterMine = ref(false);
const page = reactive({
  current: 1,
@@ -131,9 +143,20 @@
const data = reactive({
  searchForm: {
    workOrderNo: "",
    npsNo: "",
  },
});
const { searchForm } = toRefs(data);
const isCompleted = row => {
  const status = Number(row?.completionStatus);
  return Number.isFinite(status) && status >= 100;
};
const canOperate = row => !row.endOrder && !isCompleted(row);
const showStartReport = row => canOperate(row) && !row.actualStartTime;
const showEndReport = row => canOperate(row) && !!row.actualStartTime;
const goBack = () => {
  uni.navigateBack();
@@ -148,15 +171,18 @@
const getList = () => {
  if (loading.value) return;
  loading.value = true;
  const params = { ...searchForm.value, ...page };
  const params = { ...data.searchForm, ...page };
  if (filterMine.value) {
    params.filterMine = true;
  }
  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 {
@@ -182,13 +208,34 @@
  return Math.round(n);
};
const showTransferCard = (row) => {
  transferCardRowData.value = row;
  transferCardVisible.value = true;
const handleStartWork = (row) => {
  uni.showModal({
    title: "提示",
    content: "确认开始报工?",
    success: res => {
      if (res.confirm) {
        startWork({
          productionOperationTaskId: row.id,
          userId: userStore.id,
        })
          .then(() => {
            uni.showToast({ title: "开始报工成功" });
            handleQuery();
          })
          .catch(() => {
            uni.showToast({ title: "开始报工失败", icon: "error" });
          });
      }
    },
  });
};
const openWorkOrderFiles = (row) => {
  workOrderFilesRef.value?.openDialog(row);
const goReport = (row) => {
  uni.navigateTo({
    url: `/pages/productionManagement/productionReport/index?orderRow=${encodeURIComponent(
      JSON.stringify(row)
    )}`,
  });
};
onShow(() => {
@@ -228,37 +275,10 @@
  gap: 10px;
  padding: 12px 0;
  border-top: 1px solid #f5f5f5;
  :deep(.up-button) {
  .action-btn {
    margin: 0;
    width: auto;
  }
}
.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;
  }
}
src/pages/works.vue
@@ -306,7 +306,6 @@
<script setup>
  import { ref, onMounted, nextTick, reactive, computed } from "vue";
  import { userLoginFacotryList } from "@/api/login";
  import { getProductWorkOrderById } from "@/api/productionManagement/productionReporting";
  import DownloadProgressMask from "@/components/DownloadProgressMask.vue";
  import modal from "@/plugins/modal";
  import useUserStore from "@/store/modules/user";
@@ -343,10 +342,10 @@
      icon: "/static/images/icon/gongxuguanli.svg",
      label: "工序管理",
    },
    {
      icon: "/static/images/icon/bom.svg",
      label: "BOM",
    },
    // {
    //   icon: "/static/images/icon/bom.svg",
    //   label: "BOM",
    // },
    {
      icon: "/static/images/icon/gongyiluxian.svg",
      label: "工艺路线",
@@ -359,10 +358,10 @@
      icon: "/static/images/icon/kehudangan.svg",
      label: "客户档案",
    },
    {
      icon: "/static/images/icon/xiaoshoubaojia.svg",
      label: "销售报价",
    },
    // {
    //   icon: "/static/images/icon/xiaoshoubaojia.svg",
    //   label: "销售报价",
    // },
    {
      icon: "/static/images/icon/xiaoshoutaizhang.svg",
      label: "销售台账",
@@ -912,7 +911,9 @@
        });
        break;
      case "生产报工":
        getcode();
        uni.navigateTo({
          url: "/pages/productionManagement/workOrder/index",
        });
        break;
      case "报工台账":
        uni.navigateTo({
@@ -1120,88 +1121,6 @@
        factoryList.value = [];
      });
  }
  const getcode = async () => {
    uni.scanCode({
      success: async res => {
        // è§£æžäºŒç»´ç å†…容
        const scanResult = res.result;
        let orderRow = "";
        // åˆ¤æ–­æ‰«æç»“果是否为纯数字(id)
        const isNumericId = /^\d+$/.test(scanResult.trim());
        if (isNumericId) {
          // å¦‚果是纯数字,根据 id èŽ·å–å·¥å•æ•°æ®
          const workOrderId = scanResult.trim();
          modal.loading("正在获取工单信息...");
          try {
            const workRes = await getProductWorkOrderById({ id: workOrderId });
            modal.closeLoading();
            console.log("工单查询结果:", workRes);
            if (workRes.code === 200 && workRes.data) {
              // æ–°æŽ¥å£è¿”回的是单个对象,不是数组
              const workData = workRes.data;
              console.log("工单数据:", workData);
              if (workData.endOrder === true) {
                modal.msgError("该订单已结束,无法报工");
                return;
              }
              orderRow = JSON.stringify(workData);
              console.log("构造的orderRow:", orderRow);
            } else {
              modal.msgError("未找到对应的工单信息");
              return;
            }
          } catch (error) {
            modal.closeLoading();
            console.error("获取工单信息失败:", error);
            modal.msgError("获取工单信息失败: " + (error.message || "未知错误"));
            return;
          }
        } else {
          // å¦‚果不是纯数字,尝试从扫码结果中提取orderRow参数
          try {
            // å¤„理混合格式: http://...?orderRow={...}
            const orderRowStart = scanResult.indexOf("orderRow={");
            if (orderRowStart !== -1) {
              // æå–从orderRow={开始的JSON内容
              const jsonPart = scanResult.substring(orderRowStart + 9); // 9是"orderRow=".length
              orderRow = jsonPart;
            } else {
              // å¦‚果直接是JSON字符串,尝试解析
              orderRow = scanResult;
            }
          } catch (e) {
            console.error(e, "解析失败====????=====");
            orderRow = "";
          }
          // éªŒè¯æ˜¯å¦ä¸ºæœ‰æ•ˆçš„JSON
          try {
            JSON.parse(orderRow);
          } catch (error) {
            modal.msgError("订单解析失败,请检查二维码格式");
            return;
          }
        }
        // æ‰«ç æˆåŠŸåŽè·³è½¬åˆ°ç”Ÿäº§æŠ¥å·¥é¡µé¢ï¼Œå¹¶ä¼ é€’orderRow参数
        uni.navigateTo({
          url: `/pages/productionManagement/productionReport/index?orderRow=${orderRow}`,
        });
      },
      fail: err => {
        uni.showToast({
          title: "扫码失败",
          icon: "none",
        });
      },
    });
  };
  const changeFactory = async arr => {
    show.value = false;
    const factoryId = factoryListTem.value[arr.indexs[0]].deptId;