gaoluyang
2025-11-15 47e1cecb6f5cd01c029ff1a2f1658326a33025f4
生产管控-左右页面逻辑修改
已添加6个文件
已修改10个文件
2066 ■■■■ 文件已修改
src/api/productionManagement/productionOrder.js 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/config.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages.json 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/login.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/productionManagement/operationScheduling/components/formDia.vue 282 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/productionManagement/operationScheduling/index.vue 282 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/productionManagement/productionCosting/index.vue 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/productionManagement/productionDispatching/components/autoDispatchDia.vue 381 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/productionManagement/productionDispatching/index.vue 534 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/productionManagement/productionReporting/components/formDia.vue 160 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/productionManagement/productionReporting/index.vue 202 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/salesAccount/detail.vue 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/salesAccount/index.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/salesAccount/view.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/request.ts 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionOrder.js
@@ -17,6 +17,14 @@
    data: query,
  });
}
// è‡ªåŠ¨æ´¾å·¥
export function productionDispatchList(query) {
  return request({
    url: "/salesLedger/scheduling/productionDispatchList",
    method: "post",
    data: query,
  });
}
// èŽ·å–ç‚’æœºæ­£åœ¨å·¥ä½œé‡æ•°æ®
export function schedulingList(query) {
  return request({
@@ -43,3 +51,29 @@
    data: data,
  });
}
// æŸ¥è¯¢æŸè€—率
export function getLossRate() {
  return request({
    url: "/salesLedger/scheduling/loss",
    method: "get",
  });
}
// æ–°å¢žæŸè€—率
export function addLossRate(data) {
  return request({
    url: "/salesLedger/scheduling/addLoss",
    method: "post",
    data: data,
  });
}
// ä¿®æ”¹æŸè€—率
export function updateLossRate(data) {
  return request({
    url: "/salesLedger/scheduling/updateLoss",
    method: "post",
    data: data,
  });
}
src/config.js
@@ -2,9 +2,8 @@
const config = {
  //  baseUrl: 'https://vue.ruoyi.vip/prod-api',
  // baseUrl: 'http://localhost/prod-api',
  // baseUrl: 'http://114.132.189.42:9066', // å®å¤æ¶¦æ³°
  // baseUrl: 'http://114.132.189.42:9068', // æ–°ç–†æµ·å·å¼€å¿ƒ
  baseUrl: 'http://192.168.1.147:8080', // æœ¬åœ°æµ‹è¯•
  baseUrl: 'http://114.132.189.42:9068', // æ–°ç–†æµ·å·å¼€å¿ƒ
  // baseUrl: 'http://192.168.1.185:9988', // æœ¬åœ°æµ‹è¯•
   //cloud后台网关地址
  //  baseUrl: 'http://192.168.10.3:8080',
   // åº”用信息
src/pages.json
@@ -413,6 +413,27 @@
        "navigationBarTitleText": "生产派工",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/productionManagement/operationScheduling/index",
      "style": {
        "navigationBarTitleText": "工序排产",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/productionManagement/productionReporting/index",
      "style": {
        "navigationBarTitleText": "生产报工",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/productionManagement/productionCosting/index",
      "style": {
        "navigationBarTitleText": "生产核算",
        "navigationStyle": "custom"
      }
    }
  ],
  "subPackages": [
src/pages/index.vue
@@ -437,17 +437,17 @@
            break;
        case '工序排产':
            uni.navigateTo({
                url: '/pages/productionManagement/processScheduling/index'
                url: '/pages/productionManagement/operationScheduling/index'
            });
            break;
        case '生产报工':
            uni.navigateTo({
                url: '/pages/productionManagement/productionReport/index'
                url: '/pages/productionManagement/productionReporting/index'
            });
            break;
        case '生产核算':
            uni.navigateTo({
                url: '/pages/productionManagement/productionAccounting/index'
                url: '/pages/productionManagement/productionCosting/index'
            });
            break;
        case '设备台账':
src/pages/login.vue
@@ -138,8 +138,6 @@
        showToast("请输入您的账号")
    } else if (loginForm.value.password === "") {
        showToast("请输入您的密码")
    } else if (loginForm.value.factoryId === "") {
        showToast("请选择公司")
    } else {
        showToast("登录中,请耐心等待...")
        pwdLogin()
src/pages/productionManagement/operationScheduling/components/formDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,282 @@
<template>
  <view>
    <up-popup v-model:show="dialogFormVisible" mode="bottom" round="12" @close="closeDia" :customStyle="{ height: '85vh' }">
      <view class="dia-container">
        <view class="dia-header">
          <text class="title">工序排产</text>
          <up-button size="mini" @click="addRow" type="primary">新增</up-button>
          <text class="pending">待排产数量:{{ pendingNum }}</text>
        </view>
        <scroll-view class="rows" scroll-y>
          <view v-for="(row, index) in tableData" :key="index" class="row-card">
            <view class="row-header">
              <text class="row-index">#{{ index + 1 }}</text>
              <up-button size="mini" type="error" plain @click="removeRow(index)">删除</up-button>
            </view>
            <up-form>
              <up-form-item label="工序" label-width="80">
                <up-input v-model="row.process" placeholder="请输入工序" />
              </up-form-item>
              <up-form-item label="单位" label-width="80">
                <up-input v-model="row.unit" placeholder="请输入单位" />
              </up-form-item>
              <up-form-item label="口味/品名/规格" label-width="110">
                <up-input v-model="row.type" placeholder="请输入" />
              </up-form-item>
              <up-form-item label="排产数量" label-width="80">
                <up-input v-model.number="row.schedulingNum" type="number" placeholder="请输入" />
              </up-form-item>
              <up-form-item label="工时定额" label-width="80">
                <up-input v-model.number="row.workHours" type="number" placeholder="请输入" />
              </up-form-item>
              <up-form-item label="排产日期" label-width="80" @click="openDatePicker(index)">
                <up-input v-model="row.schedulingDate" placeholder="选择日期" readonly @click="openDatePicker(index)" />
                <template #right>
                  <up-icon name="calendar" @click="openDatePicker(index)"></up-icon>
                </template>
              </up-form-item>
              <up-form-item label="排产人" label-width="80" @click="openUserPicker(index)">
                <up-input v-model="row.schedulingUserName" placeholder="选择人员" readonly @click="openUserPicker(index)" />
                <template #right>
                  <up-icon name="arrow-right" @click="openUserPicker(index)"></up-icon>
                </template>
              </up-form-item>
              <up-form-item label="备注" label-width="80">
                <up-input v-model="row.remark" placeholder="请输入备注" />
              </up-form-item>
            </up-form>
          </view>
        </scroll-view>
        <view class="summary">
          <text>排产数量合计:{{ totalSchedulingNum }}</text>
        </view>
        <view class="dia-footer">
          <up-button type="primary" @click="submitForm">确认</up-button>
          <up-button @click="closeDia">取消</up-button>
        </view>
      </view>
    </up-popup>
    <!-- æ—¥æœŸé€‰æ‹©å™¨ï¼ˆup ç³»åˆ—) -->
    <up-popup :show="datePicker.show" mode="bottom" @close="datePicker.show = false">
      <up-datetime-picker :show="true" v-model="datePicker.valueData" mode="date" @confirm="onDateConfirm" @cancel="datePicker.show = false" />
    </up-popup>
    <!-- äººå‘˜é€‰æ‹©å™¨ï¼ˆup ç³»åˆ— action-sheet) -->
    <up-action-sheet
      :show="userPicker.show"
      :actions="userActionList"
      title="选择人员"
      @select="onUserSelect"
      @close="userPicker.show = false"
    />
  </view>
</template>
<script setup>
import { ref, getCurrentInstance, computed } from 'vue'
import { userListNoPageByTenantId } from '@/api/system/user.js'
import { processScheduling } from '@/api/productionManagement/operationScheduling.js'
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false)
const operationType = ref('')
const tableData = ref([])
const unitFromRow = ref('')
const idFromRow = ref('')
const specificationModelFromRow = ref('')
const pendingNum = ref(0)
const userList = ref([])
const receive = ref('')
// pickers
const datePicker = ref({ show: false, valueData: Date.now(), rowIndex: -1 })
const userPicker = ref({ show: false, rowIndex: -1 })
// ActionSheet æ•°æ®
const userActionList = computed(() => {
  return userList.value.map(u => ({ name: u.nickName, value: u.userId }))
})
const totalSchedulingNum = computed(() => {
  return tableData.value.reduce((sum, r) => sum + (Number(r.schedulingNum || 0)), 0)
})
const userLabel = (uid) => {
  const u = userList.value.find(u => u.userId === uid)
  return u ? u.nickName : ''
}
// æ‰“开弹框
const openDialog = (type, row) => {
  operationType.value = type
  dialogFormVisible.value = true
  userListNoPageByTenantId().then((res) => {
    userList.value = res.data
  })
  pendingNum.value = row?.pendingNum ?? 0
  unitFromRow.value = row?.unit ?? ''
  idFromRow.value = row?.id ?? ''
  specificationModelFromRow.value = row?.specificationModel ?? ''
  tableData.value = [createRow()]
}
const createRow = () => ({
  id: idFromRow.value,
  process: '',
  schedulingDate: '',
  schedulingNum: null,
  schedulingUserId: '',
  schedulingUserName: '',
  workHours: null,
  unit: unitFromRow.value,
  remark: '',
  type: specificationModelFromRow.value,
})
const openDatePicker = (idx) => {
  datePicker.value.rowIndex = idx
  datePicker.value.valueData = Date.now()
  datePicker.value.show = true
}
const onDateConfirm = (e) => {
  const val = e.value
  const d = new Date(val)
  const y = d.getFullYear()
  const m = String(d.getMonth() + 1).padStart(2, '0')
  const day = String(d.getDate()).padStart(2, '0')
  const str = `${y}-${m}-${day}`
  if (datePicker.value.rowIndex > -1) {
    tableData.value[datePicker.value.rowIndex].schedulingDate = str
  }
  datePicker.value.show = false
}
const openUserPicker = (idx) => {
  userPicker.value.rowIndex = idx
  userPicker.value.show = true
}
const onUserSelect = (item) => {
  if (item && userPicker.value.rowIndex > -1) {
    const row = tableData.value[userPicker.value.rowIndex]
    row.schedulingUserId = item.value
    row.schedulingUserName = item.name
  }
  userPicker.value.show = false
}
const submitForm = () => {
  // 1. æ£€æŸ¥æ¯ä¸€è¡Œæ˜¯å¦å¡«å†™å®Œæ•´
  for (let i = 0; i < tableData.value.length; i++) {
    const row = tableData.value[i]
    if (!row.process || !row.schedulingDate || row.schedulingNum === '' || row.schedulingNum === null || !row.schedulingUserId || row.workHours === '' || row.workHours === null || !row.unit) {
      uni.showToast({ title: `第${i + 1}行数据未填写完整`, icon: 'none' })
      return
    }
  }
  // 2. åˆè®¡æŽ’产数量
  const total = tableData.value.reduce((sum, row) => sum + Number(row.schedulingNum || 0), 0)
  if (total > Number(pendingNum.value)) {
    uni.showToast({ title: '排产数量合计不能超过待排产数量', icon: 'none' })
    return
  }
  // 3. æ‹¼è£…数据
  const submitData = tableData.value.map(row => {
    const { loss, ...rest } = row
    return { ...rest, receive: receive.value }
  })
  processScheduling(submitData).then(() => {
    uni.showToast({ title: '提交成功', icon: 'success' })
    closeDia()
  })
}
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  dialogFormVisible.value = false
  receive.value = ''
  tableData.value = []
  unitFromRow.value = ''
  idFromRow.value = ''
  specificationModelFromRow.value = ''
  pendingNum.value = 0
  emit('close')
}
defineExpose({ openDialog })
const addRow = () => {
  tableData.value.push(createRow())
}
const removeRow = (index) => {
  tableData.value.splice(index, 1)
}
</script>
<style scoped lang="scss">
.dia-container {
  padding: 12px;
  height: 100%;
  display: flex;
  flex-direction: column;
}
.dia-header {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
}
.dia-header .title {
  font-weight: 600;
  font-size: 16px;
  color: #333;
}
.dia-header .pending {
  margin-left: auto;
  color: #666;
  font-size: 12px;
}
.rows {
  display: flex;
  flex-direction: column;
  gap: 10px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  flex: 1;
  min-height: 0;
}
.row-card {
  background: #fff;
  border-radius: 10px;
  padding: 8px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}
.row-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 6px;
}
.row-index {
  color: #999;
  font-size: 12px;
}
.summary {
  padding: 10px 0;
  color: #333;
  font-size: 14px;
  text-align: right;
}
.dia-footer {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
  padding-top: 8px;
}
</style>
src/pages/productionManagement/operationScheduling/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,282 @@
<template>
  <view class="op-scheduling">
    <PageHeader title="工序排产" />
    <view class="search_form">
      <u-form>
        <view class="form-row">
          <u-form-item label="客户名称" label-width="80">
            <up-input v-model="searchForm.customerName" placeholder="请输入" clearable @change="handleQuery" />
          </u-form-item>
          <u-form-item label="项目名称" label-width="80">
            <up-input v-model="searchForm.projectName" placeholder="请输入" clearable @change="handleQuery" />
          </u-form-item>
        </view>
        <view class="form-row">
          <u-form-item label="状态" label-width="80">
            <up-input v-model="statusDisplay" placeholder="请选择状态" readonly @click="showStatusPicker = true" />
          </u-form-item>
        </view>
        <view class="form-actions">
          <u-button type="primary" @click="handleQuery" size="small">搜索</u-button>
        </view>
      </u-form>
    </view>
    <!-- é¡¶éƒ¨æ“ä½œå·²ç§»é™¤ -->
    <view class="list_container">
      <u-loading-icon v-if="tableLoading" text="加载中..."></u-loading-icon>
      <view v-else>
        <view v-if="!tableData || tableData.length === 0" class="empty">暂无数据</view>
        <view v-else class="card_list">
          <view v-for="item in tableData" :key="item.id" class="card_item">
            <view class="card_header">
              <u-tag :type="statusType(item.status)" size="mini">{{ statusText(item.status) }}</u-tag>
              <text class="card_title">{{ item.projectName }}</text>
            </view>
            <view class="card_body">
              <view class="row"><text class="label">派工日期</text><text class="value">{{ item.schedulingDate }}</text></view>
              <view class="row"><text class="label">派工人</text><text class="value">{{ item.schedulingUserName }}</text></view>
              <view class="row"><text class="label">合同号</text><text class="value">{{ item.salesContractNo }}</text></view>
              <view class="row"><text class="label">客户合同号</text><text class="value">{{ item.customerContractNo }}</text></view>
              <view class="row"><text class="label">客户名称</text><text class="value">{{ item.customerName }}</text></view>
              <view class="row"><text class="label">产品大类</text><text class="value">{{ item.productCategory }}</text></view>
              <view class="row"><text class="label">规格型号</text><text class="value">{{ item.specificationModel }}</text></view>
              <view class="row"><text class="label">绑定机器</text><text class="value">{{ item.speculativeTradingName }}</text></view>
              <view class="row inline">
                <view class="col"><text class="label">单位</text><text class="value">{{ item.unit }}</text></view>
                <view class="col"><text class="label">排产总数</text><text class="value">{{ item.schedulingNum }}</text></view>
                <view class="col"><text class="label">已排产数量</text><text class="value">{{ item.successNum }}</text></view>
                <view class="col"><text class="label">待排产数量</text><text class="value">{{ item.pendingNum }}</text></view>
              </view>
            </view>
            <view class="card_actions">
              <u-button
                type="primary"
                size="small"
                @click="openForm('add', item)"
                :disabled="item.pendingNum == 0"
              >工序排产</u-button>
              <u-button
                type="error"
                plain
                size="small"
                class="ml8"
                @click="handleCancel(item)"
                :disabled="item.status == 3"
              >取消排产</u-button>
            </view>
          </view>
        </view>
      </view>
    </view>
    <form-dia ref="formDia" @close="handleQuery"></form-dia>
    <!-- çŠ¶æ€é€‰æ‹©å™¨ï¼ˆup ç³»åˆ—) -->
    <up-action-sheet
      :show="showStatusPicker"
      :actions="statusActions"
      title="选择状态"
      @select="onStatusSelect"
      @close="showStatusPicker = false"
    />
  </view>
</template>
<script setup>
import { onMounted, ref, reactive, toRefs, nextTick, getCurrentInstance, computed } from 'vue'
import PageHeader from '@/components/PageHeader.vue'
import FormDia from './components/formDia.vue'
import dayjs from 'dayjs'
import { listPageProcess, productionDispatchDelete } from '@/api/productionManagement/operationScheduling.js'
const data = reactive({
  searchForm: {
    staffName: "",
    status: 1,
    entryDate: null, // å½•入日期
    entryDateStart: undefined,
    entryDateEnd: undefined,
  },
});
const { searchForm } = toRefs(data);
const tableData = ref([])
const tableLoading = ref(false)
const page = reactive({
  current: -1,
  size: -1,
});
const formDia = ref()
const { proxy } = getCurrentInstance()
// çŠ¶æ€é€‰æ‹©å™¨
const showStatusPicker = ref(false)
const statusOptions = ref([
  { label: '待排产', value: 1 },
  { label: '排产中', value: 2 },
  { label: '已排产', value: 3 }
])
const statusActions = computed(() => statusOptions.value.map(o => ({ name: o.label, value: o.value })))
const statusDisplay = ref('')
// æ—¥æœŸèŒƒå›´ç­›é€‰å·²ç§»é™¤
// picker handlers
const onStatusSelect = (item) => {
  if (item) {
    searchForm.value.status = item.value
    statusDisplay.value = item.name
    handleQuery()
  }
  showStatusPicker.value = false
}
// æ—¥æœŸèŒƒå›´å›žè°ƒå·²ç§»é™¤
// status display helpers
const statusText = (s) => {
  if (s == 3) return '已排产'
  if (s == 1) return '待排产'
  return '排产中'
}
const statusType = (s) => {
  if (s == 3) return 'success'
  if (s == 1) return 'primary'
  return 'warning'
}
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    page.current = -1;
    page.size = -1;
    getList();
};
// changeDaterange å·²ç§»é™¤
const getList = () => {
    tableLoading.value = true;
    const params = { ...searchForm.value, ...page };
    params.entryDate = undefined
    listPageProcess(params).then(res => {
        tableLoading.value = false;
        tableData.value = res.data.records.map(item => ({
            ...item,
            pendingNum: (Number(item.schedulingNum) || 0) - (Number(item.successNum) || 0)
        }));
    }).catch(err => {
        tableLoading.value = false;
    })
};
// å–消多选相关逻辑,采用卡片内单条操作
// æ‰“开弹框
const openForm = (type, row) => {
    if (!row) {
        uni.showToast({ title: '未找到数据', icon: 'none' })
        return;
    }
    if ((Number(row.pendingNum) || 0) === 0) {
        uni.showToast({ title: '无需再排产', icon: 'none' })
        return;
    }
    nextTick(() => {
        formDia.value?.openDialog(type, row)
    })
};
// å•条取消排产
const handleCancel = (row) => {
  if (!row) return
  if (row.status == 3) {
    uni.showToast({ title: '已排产数据不能取消排产', icon: 'none' })
    return
  }
  uni.showModal({
    title: '删除提示',
    content: '是否确认取消排产?',
    success: (res) => {
      if (res.confirm) {
        tableLoading.value = true
        productionDispatchDelete([row.id])
          .then(() => {
            uni.showToast({ title: '取消排产成功', icon: 'success' })
            getList()
          })
          .finally(() => {
            tableLoading.value = false
          })
      }
    }
  })
}
onMounted(() => {
    getList();
  // åˆå§‹åŒ–显示字段
  const cur = statusOptions.value.find(o => o.value === searchForm.value.status)
  statusDisplay.value = cur ? cur.label : ''
});
</script>
<style scoped lang="scss">
.op-scheduling {
  padding-bottom: 12px;
}
.search_form {
  margin: 12px;
  background: #fff;
  border-radius: 8px;
  padding: 10px;
}
.form-row {
  display: flex;
  gap: 12px;
}
.form-actions {
  display: flex;
  justify-content: flex-end;
}
.table_actions {
  display: flex;
  gap: 8px;
  padding: 0 12px 10px 12px;
  justify-content: flex-end;
}
.list_container {
  padding: 0 12px;
}
.empty {
  text-align: center;
  color: #888;
  padding: 20px 0;
}
.card_list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.card_item {
  background: #fff;
  border-radius: 10px;
  padding: 10px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}
.card_header {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
}
.card_title { font-weight: 500; color: #333; margin-left: auto; }
.card_body .row { display: flex; justify-content: space-between; padding: 4px 0; }
.card_body .row.inline { display: flex; gap: 10px; flex-wrap: wrap; }
.card_body .row.inline .col { min-width: 45%; display: flex; justify-content: space-between; }
.label { color: #666; font-size: 12px; }
.value { color: #333; font-size: 12px; }
.card_actions { display: flex; justify-content: flex-end; gap: 8px; padding-top: 8px; }
.ml8 { margin-left: 8px; }
</style>
src/pages/productionManagement/productionCosting/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,107 @@
<template>
  <view class="prod-costing">
    <PageHeader title="生产核算" />
    <view class="search_form">
      <u-form>
        <view class="form-row">
          <u-form-item label="生产人" label-width="80">
            <up-input v-model="searchForm.schedulingUserName" placeholder="请输入" clearable @change="handleQuery" />
          </u-form-item>
        </view>
        <view class="form-actions">
          <u-button type="primary" size="small" @click="handleQuery">搜索</u-button>
        </view>
      </u-form>
    </view>
    <view class="list_container">
      <u-loading-icon v-if="tableLoading" text="加载中..." />
      <view v-else>
        <view v-if="!tableData || tableData.length === 0" class="empty">暂无数据</view>
        <view v-else class="card_list">
          <view v-for="item in tableData" :key="item.id" class="card_item">
            <view class="card_header">
              <text class="card_title">{{ item.projectName }}</text>
            </view>
            <view class="card_body">
              <view class="row"><text class="label">生产日期</text><text class="value">{{ item.schedulingDate }}</text></view>
              <view class="row"><text class="label">生产人</text><text class="value">{{ item.schedulingUserName }}</text></view>
              <view class="row"><text class="label">合同号</text><text class="value">{{ item.salesContractNo }}</text></view>
              <view class="row"><text class="label">客户合同号</text><text class="value">{{ item.customerContractNo }}</text></view>
              <view class="row"><text class="label">客户名称</text><text class="value">{{ item.customerName }}</text></view>
              <view class="row"><text class="label">产品大类</text><text class="value">{{ item.productCategory }}</text></view>
              <view class="row"><text class="label">规格型号</text><text class="value">{{ item.specificationModel }}</text></view>
              <view class="row inline">
                <view class="col"><text class="label">单位</text><text class="value">{{ item.unit }}</text></view>
                <view class="col"><text class="label">工序</text><text class="value">{{ item.process }}</text></view>
                <view class="col"><text class="label">生产数量</text><text class="value">{{ item.finishedNum }}</text></view>
                <view class="col"><text class="label">工时定额</text><text class="value">{{ item.workHours }}</text></view>
                <view class="col"><text class="label">工资</text><text class="value">{{ item.wages }}</text></view>
              </view>
            </view>
          </view>
        </view>
      </view>
    </view>
  </view>
</template>
<script setup>
import { onMounted, ref, reactive, toRefs } from "vue";
import PageHeader from '@/components/PageHeader.vue'
import { productionAccountingListPage } from "@/api/productionManagement/productionCosting.js";
// state
const tableData = ref([]);
const tableLoading = ref(false);
const page = reactive({ current: -1, size: -1 });
const data = reactive({
  searchForm: {
    schedulingUserName: "",
  },
});
const { searchForm } = toRefs(data);
// æ— æ—¥æœŸç­›é€‰
// æŸ¥è¯¢ï¼ˆä¸åˆ†é¡µï¼Œå›ºå®šä¼  -1)
const handleQuery = () => { page.current = -1; page.size = -1; getList(); };
const getList = () => {
  tableLoading.value = true;
  const params = { ...searchForm.value, ...page };
  productionAccountingListPage(params).then((res) => {
    tableLoading.value = false;
    tableData.value = res.data.records || [];
  }).catch(() => { tableLoading.value = false; })
};
onMounted(() => { getList(); });
</script>
<style scoped lang="scss">
.prod-costing { padding-bottom: 12px; }
.search_form { margin: 12px; background: #fff; border-radius: 8px; padding: 10px; }
.form-row { display: flex; gap: 12px; }
.form-actions { display: flex; justify-content: flex-end; }
.ml8 { margin-left: 8px; }
.list_container { padding: 0 12px; }
.empty { text-align: center; color: #888; padding: 20px 0; }
.card_list { display: flex; flex-direction: column; gap: 10px; }
.card_item { background: #fff; border-radius: 10px; padding: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); }
.card_header { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.card_title { font-weight: 500; color: #333; }
.card_body .row { display: flex; justify-content: space-between; padding: 4px 0; }
.card_body .row.inline { display: flex; gap: 10px; flex-wrap: wrap; }
.card_body .row.inline .col { min-width: 45%; display: flex; justify-content: space-between; }
.label { color: #666; font-size: 12px; }
.value { color: #333; font-size: 12px; }
.pagination { display: flex; align-items: center; justify-content: center; gap: 10px; padding: 12px 0; }
.page_text { color: #666; font-size: 12px; }
</style>
src/pages/productionManagement/productionDispatching/components/autoDispatchDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,381 @@
<template>
  <view>
    <up-popup
        v-model:show="dialogFormVisible"
        mode="center"
        border-radius="20"
        :closeable="true"
        @close="closeDia"
    >
      <view class="popup-content">
        <view class="popup-header">
          <text class="popup-title">自动派工</text>
        </view>
        <view class="popup-body">
          <view class="section-title">派工列表</view>
          <view class="card-list">
            <view
                v-for="(item, index) in dispatchList"
                :key="index"
                class="dispatch-card"
                :class="{ 'even-card': index % 2 === 1 }"
            >
              <view class="card-header">
                <text class="card-index">{{ index + 1 }}</text>
                <text class="card-project">{{ item.projectName }}</text>
              </view>
              <view class="card-content">
                <view class="info-row">
                  <text class="info-label">合同号:</text>
                  <text class="info-value">{{ item.salesContractNo }}</text>
                </view>
                <view class="info-row">
                  <text class="info-label">客户:</text>
                  <text class="info-value">{{ item.customerName }}</text>
                </view>
                <view class="info-row">
                  <text class="info-label">产品类别:</text>
                  <text class="info-value">{{ item.productCategory }}</text>
                </view>
                <view class="info-row">
                  <text class="info-label">规格型号:</text>
                  <text class="info-value">{{ item.specificationModel }}</text>
                </view>
                <view class="info-row">
                  <text class="info-label">绑定机器:</text>
                  <text class="info-value">{{ item.speculativeTradingName }}</text>
                </view>
                <view class="quantity-row">
                  <view class="quantity-item">
                    <text class="quantity-label">总数量:</text>
                    <text class="quantity-value">{{ item.quantity }}</text>
                  </view>
                  <view class="quantity-item">
                    <text class="quantity-label">已排产:</text>
                    <text class="quantity-value">{{ item.schedulingNum }}</text>
                  </view>
                  <view class="quantity-item">
                    <text class="quantity-label">待排产:</text>
                    <text class="quantity-value">{{ item.pendingQuantity }}</text>
                  </view>
                </view>
                <view class="scheduling-row">
                  <text class="scheduling-label">本次排产:</text>
                  <up-number-box
                      v-model="item.schedulingNum"
                      :min="0"
                      :max="item.pendingQuantity"
                      :step="1"
                      :precision="0"
                      size="mini"
                      @change="(value) => changeCurrentNum(value, item)"
                      class="scheduling-input"
                  />
                </view>
              </view>
            </view>
          </view>
          <view v-if="dispatchList.length === 0" class="empty-state">
            <text class="empty-text">暂无派工数据</text>
          </view>
        </view>
        <view class="popup-footer">
          <up-button type="primary" @click="submitForm" class="confirm-btn">确认派工</up-button>
          <up-button @click="closeDia" class="cancel-btn">取消</up-button>
        </view>
      </view>
    </up-popup>
  </view>
</template>
<script setup>
import { ref, reactive, toRefs } from "vue";
import { productionDispatchList } from "@/api/productionManagement/productionOrder.js";
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
const operationType = ref('')
const data = reactive({
  form: {},
  dispatchList: [], // æ´¾å·¥åˆ—表数据
});
const { form, dispatchList } = toRefs(data);
// è¡¨æ ¼è¡Œæ ·å¼
const tableRowClassName = ({ rowIndex }) => {
  if (rowIndex % 2 === 1) {
    return 'even-row'
  }
  return ''
}
// ä¿®æ”¹æœ¬æ¬¡æŽ’产数量
const changeCurrentNum = (value, row) => {
  if (value > row.pendingQuantity) {
    row.schedulingNum = row.pendingQuantity
    uni.$u.toast('排产数量不可大于待排产数量')
  }
}
// æ‰“开弹框
const openDialog = (rows) => {
  dialogFormVisible.value = true;
  console.log('接收到传入的数据:', rows);
  console.log('传入数据数量:', rows.length);
  // å¤„理传入的数据
  dispatchList.value = rows.map((row, index) => ({
    ...row,
    schedulingNum: 0, // åˆå§‹åŒ–本次排产数量为0
    pendingQuantity: (Number(row.quantity) || 0) - (Number(row.schedulingNum) || 0) // è®¡ç®—待排产数量
  }))
  console.log('处理后的派工列表:', dispatchList.value);
  console.log('派工列表数量:', dispatchList.value.length);
}
// æäº¤è¡¨å•
const submitForm = () => {
  // æ£€æŸ¥æ˜¯å¦æœ‰æŽ’产数据
  const hasSchedulingData = dispatchList.value.some(item => item.schedulingNum > 0)
  if (!hasSchedulingData) {
    uni.$u.toast('请至少为一条记录设置排产数量')
    return
  }
  // æž„造提交数据 - ç›´æŽ¥ä¼ é€’数组,不过滤
  const submitData = dispatchList.value
  console.log('提交自动派工数据:', submitData)
  // è°ƒç”¨API(这里需要根据实际接口调整)
  productionDispatchList(submitData).then(res => {
    uni.$u.toast(res.msg || '派工成功');
    closeDia();
  }).catch(err => {
    uni.$u.toast('派工失败');
    console.error('派工失败:', err);
  })
}
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  dialogFormVisible.value = false;
  dispatchList.value = []
  emit('close')
};
defineExpose({
  openDialog,
});
</script>
<style lang="scss" scoped>
.popup-content {
  width: 90vw;
  max-width: 1200px;
  background: #fff;
  border-radius: 20rpx;
  overflow: hidden;
}
.popup-header {
  padding: 30rpx;
  border-bottom: 1rpx solid #f0f0f0;
  text-align: center;
}
.popup-title {
  font-size: 36rpx;
  font-weight: bold;
  color: #333;
}
.popup-body {
  padding: 30rpx;
  height: 60vh;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  max-height: 60vh;
}
.section-title {
  font-size: 32rpx;
  font-weight: bold;
  color: #333;
  margin-bottom: 20rpx;
  flex-shrink: 0;
}
.card-list {
  display: flex;
  flex-direction: column;
  gap: 20rpx;
  min-height: 0;
  overflow-y: auto;
  max-height: 60vh;
}
.dispatch-card {
  background: #f8f9fa;
  border-radius: 12rpx;
  padding: 24rpx;
  border: 1rpx solid #e9ecef;
  transition: all 0.3s ease;
  flex-shrink: 0;
}
.dispatch-card:hover {
  box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
  transform: translateY(-2rpx);
}
.even-card {
  background: #ffffff;
}
.card-header {
  display: flex;
  align-items: center;
  margin-bottom: 20rpx;
  padding-bottom: 16rpx;
  border-bottom: 1rpx solid #e9ecef;
}
.card-index {
  background: #1890ff;
  color: white;
  width: 40rpx;
  height: 40rpx;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 24rpx;
  font-weight: bold;
  margin-right: 16rpx;
}
.card-project {
  font-size: 28rpx;
  font-weight: bold;
  color: #333;
  flex: 1;
}
.card-content {
  display: flex;
  flex-direction: column;
  gap: 12rpx;
}
.info-row {
  display: flex;
  align-items: center;
}
.info-label {
  font-size: 26rpx;
  color: #666;
  width: 140rpx;
  flex-shrink: 0;
}
.info-value {
  font-size: 26rpx;
  color: #333;
  flex: 1;
}
.quantity-row {
  display: flex;
  gap: 20rpx;
  margin: 8rpx 0;
}
.quantity-item {
  display: flex;
  align-items: center;
  background: #f8f9fa;
  padding: 8rpx 16rpx;
  border-radius: 6rpx;
  border: 1rpx solid #e9ecef;
}
.quantity-label {
  font-size: 24rpx;
  color: #666;
  margin-right: 8rpx;
}
.quantity-value {
  font-size: 24rpx;
  color: #1890ff;
  font-weight: bold;
}
.scheduling-row {
  display: flex;
  align-items: center;
  margin-top: 12rpx;
  padding-top: 12rpx;
  border-top: 1rpx dashed #e9ecef;
}
.scheduling-label {
  font-size: 26rpx;
  color: #333;
  font-weight: bold;
  margin-right: 16rpx;
  width: 140rpx;
  flex-shrink: 0;
}
.scheduling-input {
  flex: 1;
}
.empty-state {
  text-align: center;
  padding: 60rpx 30rpx;
  color: #999;
}
.empty-text {
  font-size: 28rpx;
}
.popup-footer {
  padding: 30rpx;
  border-top: 1rpx solid #f0f0f0;
  display: flex;
  justify-content: center;
  gap: 20rpx;
}
.confirm-btn {
  width: 200rpx;
}
.cancel-btn {
  width: 200rpx;
}
</style>
src/pages/productionManagement/productionDispatching/index.vue
@@ -33,6 +33,28 @@
                    </view>
                </view>
            </view>
            <!-- æŸè€—率设置 -->
        <view class="loss-rate-section">
            <view class="section-title">损耗率设置</view>
            <view class="loss-rate-content">
                <view class="loss-rate-item">
                    <up-button
                        class="loss-rate-btn"
                        type="primary"
                        plain
                        size="small"
                        @click="showLossRateSheet = true"
                    >{{ lossRate ? `损耗率: ${lossRate}%` : '请选择损耗率' }}</up-button>
                    <up-action-sheet
                    :show="showLossRateSheet"
                    :actions="lossRateOptions"
                    @select="onLossRateSelect"
                    title="选择损耗率"
                    @close="showLossRateSheet = false"
                />
                </view>
            </view>
        </view>
            <view class="save-section">
                <up-button type="primary" @click="saveMachineTotals" size="normal" class="save-btn">保存炒机设置</up-button>
            </view>
@@ -71,33 +93,27 @@
        </view>
        
        <!-- æ‰¹é‡æ“ä½œåŒºåŸŸ -->
        <view v-if="showBatchActions" class="batch-actions-section">
        <view class="batch-actions-section" v-if="showBatchActions">
            <view class="batch-info">
                <text class="batch-count">已选择 {{ selectedItems.length }} ä¸ªé¡¹ç›®</text>
                <text class="batch-text">已选择 {{ selectedItems.length }} ä¸ªé¡¹ç›®</text>
            </view>
            <view class="batch-buttons">
                <up-button type="primary" size="small" @click="handleAutoDispatch" class="batch-btn">
                    <up-icon name="play-circle" size="16" color="#ffffff"></up-icon>
                    è‡ªåŠ¨æ´¾å•
                </up-button>
                <up-button type="default" size="small" @click="clearSelection" class="batch-btn">
                    <up-icon name="close-circle" size="16" color="#6c757d"></up-icon>
                    å–消选择
                </up-button>
                <up-button type="primary" size="small" @click="handleAutoDispatch" class="batch-btn">自动派单</up-button>
                <up-button type="default" size="small" @click="clearSelection" class="batch-btn">取消选择</up-button>
            </view>
        </view>
        
        <!-- å…¨é€‰æ“ä½œåŒºåŸŸ -->
        <view v-if="tableData.length > 0" class="select-all-section">
            <view class="select-all-checkbox" @click="toggleAllSelection">
                <up-icon
                    :name="isAllSelected ? 'checkbox-mark' : 'circle'"
                    :color="isAllSelected ? '#409eff' : '#c0c4cc'"
                    size="18"
                ></up-icon>
                <text class="select-all-text">{{ isAllSelected ? '取消全选' : '全选' }}</text>
        <view class="select-all-section" v-if="tableData.length > 0">
            <view class="select-all-content">
                <up-checkbox
                v-model="isAllSelected"
                @change="toggleAllSelection"
                label="全选"
                class="select-all-checkbox"
                :disabled="tableData.length === 0 || tableData.filter(item => item.pendingQuantity > 0 && item.speculativeTradingName).length === 0"
            />
            </view>
            <text class="select-all-hint">(仅选择待排数量大于0的项目)</text>
        </view>
        
        <!-- ç”Ÿäº§æ´¾å·¥åˆ—表 -->
@@ -105,12 +121,13 @@
            <view v-for="(item, index) in tableData" :key="item.id || index" class="list-item">
                <view class="ledger-item">
                    <!-- é€‰æ‹©å¤é€‰æ¡† -->
                    <view class="item-checkbox" @click="toggleItemSelection(item)">
                        <up-icon
                            :name="selectedItems.includes(item.id) ? 'checkbox-mark' : 'circle'"
                            :color="selectedItems.includes(item.id) ? '#409eff' : '#c0c4cc'"
                            size="18"
                        ></up-icon>
                    <view class="item-checkbox">
                        <up-checkbox
                    :model-value="selectedItems.some(selected => selected.id === item.id)"
                    @change="(checked) => toggleItemSelection(item, checked)"
                    :disabled="item.pendingQuantity <= 0 || !item.speculativeTradingName"
                    shape="circle"
                />
                    </view>
                    
                    <view class="item-content">
@@ -146,6 +163,10 @@
                                <text class="detail-value">{{ item.specificationModel }}</text>
                            </view>
                            <view class="detail-row">
                                <text class="detail-label">绑定机器</text>
                                <text class="detail-value">{{ item.speculativeTradingName }}</text>
                            </view>
                            <view class="detail-row">
                                <text class="detail-label">单位</text>
                                <text class="detail-value">{{ item.unit }}</text>
                            </view>
@@ -169,9 +190,9 @@
                                    size="small"
                                    @click="handleDispatch(item)"
                                    class="action-btn"
                                    :disabled="item.pendingQuantity <= 0"
                        :disabled="item.pendingQuantity <= 0 || !item.speculativeTradingName"
                                >
                                    {{ item.pendingQuantity <= 0 ? '无需派工' : '生产派工' }}
                        {{ item.pendingQuantity <= 0 ? '无需派工' : !item.speculativeTradingName ? '未绑定机器' : '生产派工' }}
                                </up-button>
                            </view>
                        </view>
@@ -188,16 +209,20 @@
        
        <!-- æ´¾å·¥å¼¹çª— -->
        <DispatchModal ref="dispatchModalRef" @confirm="handleDispatchConfirm" />
        <!-- è‡ªåŠ¨æ´¾å•å¼¹çª— -->
        <AutoDispatchDia ref="autoDispatchDia" />
    </view>
</template>
<script setup>
import { ref, reactive, toRefs, getCurrentInstance } from "vue";
import { ref, reactive, toRefs, getCurrentInstance, nextTick } from "vue";
import { onShow } from '@dcloudio/uni-app';
import dayjs from "dayjs";
import {schedulingListPage, schedulingList, addSpeculatTrading, updateSpeculatTrading} from "@/api/productionManagement/productionOrder.js";
import {schedulingListPage, schedulingList, addSpeculatTrading, updateSpeculatTrading, getLossRate, addLossRate, updateLossRate} from "@/api/productionManagement/productionOrder.js";
import PageHeader from "@/components/PageHeader.vue";
import DispatchModal from "./components/DispatchModal.vue";
import AutoDispatchDia from "./components/autoDispatchDia.vue";
const { proxy } = getCurrentInstance();
@@ -207,10 +232,10 @@
// åˆ—表数据
const tableData = ref([]);
// æ‰¹é‡é€‰æ‹©ç›¸å…³æ•°æ®
const selectedItems = ref([]); // é€‰ä¸­çš„项目ID数组
const isAllSelected = ref(false); // æ˜¯å¦å…¨é€‰
const showBatchActions = ref(false); // æ˜¯å¦æ˜¾ç¤ºæ‰¹é‡æ“ä½œåŒºåŸŸ
// é€‰æ‹©ç›¸å…³æ•°æ®
const selectedItems = ref([]);
const isAllSelected = ref(false);
const showBatchActions = ref(false);
// æœç´¢è¡¨å•数据
const data = reactive({
@@ -261,8 +286,23 @@
// æ˜¯å¦æœ‰æŸ¥è¯¢æ•°æ®ï¼ˆç”¨äºŽåˆ¤æ–­æ˜¯æ–°å¢žè¿˜æ˜¯ä¿®æ”¹ï¼‰
const hasQueryData = ref(false);
// æŸè€—率相关数据
const lossRate = ref(""); // å½“前选择的损耗率
const showLossRateSheet = ref(false); // æŽ§åˆ¶æŸè€—率选择面板显示
const lossRateOptions = ref([
    { name: "6%", value: "6" },
    { name: "7%", value: "7" },
    { name: "8%", value: "8" },
    { name: "9%", value: "9" },
    { name: "10%", value: "10" }
]);
const lossRateData = ref(null); // æŸè€—率查询返回的数据
// æ´¾å·¥å¼¹çª—引用
const dispatchModalRef = ref();
// è‡ªåŠ¨æ´¾å•å¼¹çª—å¼•ç”¨
const autoDispatchDia = ref();
// é€šç”¨æç¤ºå‡½æ•°
const showLoadingToast = (message) => {
@@ -328,6 +368,28 @@
    });
};
// æŸè€—率选择事件
const onLossRateSelect = (action) => {
    lossRate.value = action.value;
    showLossRateSheet.value = false;
    console.log('选择了损耗率:', action.name, '值:', action.value);
};
// èŽ·å–æŸè€—çŽ‡æ•°æ®
const getLossRateData = () => {
    getLossRate().then((res) => {
        if (res.data) {
            lossRateData.value = res.data;
            // è®¾ç½®å½“前选择的损耗率
            if (res.data.rate !== null && res.data.rate !== undefined) {
                lossRate.value = res.data.rate.toString();
            }
        }
    }).catch(err => {
        console.error('获取损耗率失败:', err);
    });
};
// èŽ·å–åˆ—è¡¨æ•°æ®
const getList = () => {
    loading.value = true;
@@ -342,12 +404,15 @@
        tableData.value = (res.data.records || []).map(item => ({
            ...item,
            pendingQuantity: (Number(item.quantity) || 0) - (Number(item.schedulingNum) || 0)
        }));
            })).filter(item => item.pendingQuantity > 0);
        
        page.total = res.data.total || 0;
        
        // èŽ·å–ç‚’æœºæ•°æ®
        getMachineProductionData();
        // èŽ·å–æŸè€—çŽ‡æ•°æ®
        getLossRateData();
        
    }).catch(() => {
        loading.value = false;
@@ -364,6 +429,14 @@
    if (item.pendingQuantity <= 0) {
        uni.showToast({
            title: '该项目无需再派工',
            icon: 'none'
        });
        return;
    }
    if (!item.speculativeTradingName) {
        uni.showToast({
            title: '该项目未绑定机器,无法派工',
            icon: 'none'
        });
        return;
@@ -392,6 +465,51 @@
        workLoad: machineTotal[`m${machineId}`] || 0,
        currentWorkLoad: machineInProduction[`m${machineId}`] || 0
    };
};
// ä¿å­˜æŸè€—率设置
const saveLossRate = () => {
    if (!lossRate.value) {
        console.log('未选择损耗率,跳过保存');
        return Promise.resolve();
    }
    const lossRateDataToSave = {
        rate: parseFloat(lossRate.value) || 0
    };
    // å¦‚果有查询到的损耗率数据,说明是修改操作,需要传递id
    if (lossRateData.value && lossRateData.value.id) {
        lossRateDataToSave.id = lossRateData.value.id;
    }
    console.log('保存损耗率数据:', lossRateDataToSave);
    // æ ¹æ®æ˜¯å¦æœ‰æŸè€—率数据决定调用新增接口还是修改接口
    const saveLossApi = lossRateData.value && lossRateData.value.id ? updateLossRate : addLossRate;
    const successMessage = lossRateData.value && lossRateData.value.id ? '损耗率修改成功' : '损耗率新增成功';
    return saveLossApi(lossRateDataToSave).then(res => {
        console.log('损耗率保存成功:', res);
        uni.showToast({
            title: successMessage,
            icon: 'success'
        });
        // æ›´æ–°æŸè€—率数据
        if (res.data) {
            lossRateData.value = res.data;
        }
        return res;
    }).catch(err => {
        console.error('损耗率保存失败:', err);
        uni.showToast({
            title: '损耗率保存失败',
            icon: 'none'
        });
        throw err;
    });
};
// ä¿å­˜ç‚’机总量设置
@@ -425,9 +543,15 @@
    
    console.log(`调用接口: ${hasQueryData.value ? '修改' : '新增'}`);
    
    // è°ƒç”¨åŽç«¯API保存
    saveApi(saveData).then(res => {
        proxy.$message.success(successMessage);
    // å…ˆä¿å­˜æŸè€—率,再保存炒机设置
    saveLossRate().then(() => {
        // è°ƒç”¨åŽç«¯API保存炒机设置
        return saveApi(saveData);
    }).then(res => {
        uni.showToast({
            title: successMessage,
            icon: 'success'
        });
        console.log('保存成功:', res);
        
        // ä¿å­˜æˆåŠŸåŽï¼Œè®¾ç½®hasQueryData为true,下次保存将调用修改接口
@@ -435,61 +559,69 @@
            hasQueryData.value = true;
        }
    }).catch(err => {
        proxy.$message.error('保存失败');
        uni.showToast({
            title: '保存失败',
            icon: 'none'
        });
        console.error('保存失败:', err);
    });
};
// æ‰¹é‡é€‰æ‹©ç›¸å…³å‡½æ•°
// åˆ‡æ¢å•个项目选择状态
const toggleItemSelection = (item, checked) => {
    // ä»…允许选择已绑定机器且待派数量>0的项目
    if (!item.speculativeTradingName || item.pendingQuantity <= 0) return;
// åˆ‡æ¢å•个项目的选择状态
const toggleItemSelection = (item) => {
    const itemId = item.id;
    const index = selectedItems.value.indexOf(itemId);
    console.log('切换选择状态:', item.id, checked);
    
    if (index > -1) {
        // å¦‚果已选中,则取消选择
        selectedItems.value.splice(index, 1);
    // ä½¿ç”¨æ›´ä¸¥æ ¼çš„æ¯”较逻辑,确保ID唯一性
    const index = selectedItems.value.findIndex(selected => {
        // æ·±åº¦æ¯”较对象,确保是同一个项目
        return JSON.stringify(selected) === JSON.stringify(item);
    });
    if (checked) {
        // å¦‚果选中且不在选中列表中,则添加
        if (index === -1) {
            selectedItems.value.push({...item}); // åˆ›å»ºæ–°å¯¹è±¡ï¼Œé¿å…å¼•用问题
            console.log('添加项目后选中数量:', selectedItems.value.length);
        }
    } else {
        // å¦‚果未选中,则添加选择
        selectedItems.value.push(itemId);
        // å¦‚果取消选中且在选中列表中,则移除
        if (index > -1) {
            selectedItems.value.splice(index, 1);
            console.log('移除项目后选中数量:', selectedItems.value.length);
        }
    }
    
    // æ›´æ–°å…¨é€‰çŠ¶æ€
    console.log('当前选中项目列表:', selectedItems.value.map(s => s.id));
    updateAllSelectedStatus();
    // æ›´æ–°æ‰¹é‡æ“ä½œåŒºåŸŸæ˜¾ç¤ºçŠ¶æ€
    updateBatchActionsVisibility();
};
// åˆ‡æ¢å…¨é€‰çŠ¶æ€
const toggleAllSelection = () => {
    if (isAllSelected.value) {
        // å–消全选
        selectedItems.value = [];
    } else {
        // å…¨é€‰
        selectedItems.value = tableData.value
            .filter(item => item.pendingQuantity > 0) // åªé€‰æ‹©å¾…排数量大于0的项目
            .map(item => item.id);
        selectedItems.value = tableData.value.filter(item => item.pendingQuantity > 0 && item.speculativeTradingName).map(item => ({ ...item }));
    }
    isAllSelected.value = !isAllSelected.value;
    updateBatchActionsVisibility();
};
// æ›´æ–°å…¨é€‰çŠ¶æ€
const updateAllSelectedStatus = () => {
    const selectableItems = tableData.value.filter(item => item.pendingQuantity > 0);
    if (selectableItems.length === 0) {
    const selectableItems = tableData.value.filter(item => item.pendingQuantity > 0 && item.speculativeTradingName);
    if (selectableItems.length > 0 && selectedItems.value.length === selectableItems.length &&
        selectableItems.every(item => selectedItems.value.some(selected => selected.id === item.id))) {
        isAllSelected.value = true;
    } else {
        isAllSelected.value = false;
        return;
    }
    isAllSelected.value = selectedItems.value.length === selectableItems.length &&
        selectableItems.every(item => selectedItems.value.includes(item.id));
};
// æ›´æ–°æ‰¹é‡æ“ä½œåŒºåŸŸæ˜¾ç¤ºçŠ¶æ€
// æ›´æ–°æ‰¹é‡æ“ä½œæ˜¾ç¤ºçŠ¶æ€
const updateBatchActionsVisibility = () => {
    showBatchActions.value = selectedItems.value.length > 0;
};
@@ -503,80 +635,36 @@
// èŽ·å–é€‰ä¸­çš„é¡¹ç›®
const getSelectedItems = () => {
    return tableData.value.filter(item => selectedItems.value.includes(item.id));
    return selectedItems.value;
};
// è‡ªåŠ¨æ´¾å•åŠŸèƒ½
// å¤„理自动派单
const handleAutoDispatch = () => {
    const selectedItemsList = getSelectedItems();
    if (selectedItemsList.length === 0) {
    if (selectedItems.value.length === 0) {
        uni.showToast({
            title: '请先选择要派工的项目',
            title: '请选择要派工的项目',
            icon: 'none'
        });
        return;
    }
    
    // æ£€æŸ¥æ˜¯å¦æœ‰é¡¹ç›®å¾…排数量不足
    const invalidItems = selectedItemsList.filter(item => item.pendingQuantity <= 0);
    if (invalidItems.length > 0) {
    // æ£€æŸ¥æ˜¯å¦æ‰€æœ‰é€‰ä¸­é¡¹ç›®éƒ½æœ‰ç»‘定机器
    const unboundItems = selectedItems.value.filter(item => !item.speculativeTradingName);
    if (unboundItems.length > 0) {
        uni.showToast({
            title: `有${invalidItems.length}个项目无需派工,已自动过滤`,
            icon: 'none'
        });
    }
    // è¿‡æ»¤æŽ‰å¾…排数量不足的项目
    const validItems = selectedItemsList.filter(item => item.pendingQuantity > 0);
    if (validItems.length === 0) {
        uni.showToast({
            title: '没有可派工的项目',
            title: '所选项目中有未绑定机器的项目,无法自动派单',
            icon: 'none'
        });
        return;
    }
    
    uni.showModal({
        title: '确认自动派单',
        content: `确定要对选中的${validItems.length}个项目进行自动派单吗?`,
        success: (res) => {
            if (res.confirm) {
                executeAutoDispatch(validItems);
            }
        }
    });
};
// æ‰§è¡Œè‡ªåŠ¨æ´¾å•
const executeAutoDispatch = (items) => {
    showLoadingToast('自动派单中...');
    // æ¨¡æ‹Ÿè‡ªåŠ¨æ´¾å•è¿‡ç¨‹
    setTimeout(() => {
        closeToast();
        // è¿™é‡Œåº”该调用实际的自动派单API
        // æš‚时使用模拟成功
        uni.showToast({
            title: `成功为${items.length}个项目完成自动派单`,
            icon: 'success'
        });
        // æ¸…空选择
        clearSelection();
        // åˆ·æ–°åˆ—表
        getList();
        console.log('自动派单项目:', items);
    }, 1500);
    // ç¡®ä¿ä¼ é€’的是完整的选中项目数组
    autoDispatchDia.value?.openDialog([...selectedItems.value]);
};
// é¡µé¢æ˜¾ç¤ºæ—¶åŠ è½½æ•°æ®
onShow(() => {
    getList();
    // æ¸…空选择状态
    clearSelection();
});
</script>
@@ -588,16 +676,63 @@
    padding: 20rpx;
}
// ç‚’机状态区域
.machines-section {
    margin-bottom: 30rpx;
// æŸè€—率设置区域
.loss-rate-section {
    background: #ffffff;
    border: 1rpx solid #e4e7ed;
    border-radius: 12rpx;
    padding: 32rpx;
    margin-top: 24rpx;
    margin-bottom: 32rpx;
    box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
}
    
    .section-title {
.loss-rate-section .section-title {
        font-size: 32rpx;
        font-weight: 600;
        color: #303133;
        margin-bottom: 20rpx;
    }
.loss-rate-section .loss-rate-content {
    display: flex;
    flex-direction: column;
    gap: 24rpx;
}
.loss-rate-section .loss-rate-content .loss-rate-item {
    display: flex;
    align-items: center;
    gap: 24rpx;
}
.loss-rate-section .loss-rate-content .loss-rate-label {
    font-size: 30rpx;
    font-weight: 500;
    color: #303133;
    min-width: 140rpx;
    white-space: nowrap;
}
.loss-rate-section .loss-rate-content .loss-rate-btn {
    min-width: 260rpx;
    font-size: 28rpx;
    height: 64rpx;
    line-height: 64rpx;
    border-radius: 8rpx;
    font-weight: 500;
}
// ç‚’机状态区域
.machines-section {
    margin-bottom: 30rpx;
}
.machines-section .section-title {
    font-size: 32rpx;
    font-weight: 600;
    color: #303133;
    margin-bottom: 20rpx;
}
.machines-grid {
@@ -771,10 +906,10 @@
    align-items: center;
    padding: 12rpx 0;
    border-bottom: 1rpx solid #f5f5f5;
    &:last-child {
        border-bottom: none;
    }
.detail-row:last-child {
    border-bottom: none;
}
.detail-label {
@@ -786,16 +921,16 @@
    font-size: 26rpx;
    color: #303133;
    font-weight: 500;
}
    
    &.highlight {
.detail-value.highlight {
        color: #ff6b35;
        font-weight: 600;
    }
    
    &.danger {
.detail-value.danger {
        color: #ee0a24;
        font-weight: 600;
    }
}
.action-buttons {
@@ -808,6 +943,69 @@
    min-width: 180rpx;
}
// æ‰¹é‡æ“ä½œåŒºåŸŸæ ·å¼
.batch-actions-section {
    background: #e8f4ff;
    border: 1rpx solid #409eff;
    border-radius: 12rpx;
    padding: 20rpx 24rpx;
    margin-bottom: 24rpx;
    display: flex;
    justify-content: space-between;
    align-items: center;
}
.batch-actions-section .batch-text {
    font-size: 28rpx;
    font-weight: 600;
    color: #409eff;
}
.batch-actions-section .batch-buttons {
    display: flex;
    gap: 16rpx;
}
.batch-actions-section .batch-btn {
    min-width: 140rpx;
}
// å…¨é€‰æ“ä½œåŒºåŸŸæ ·å¼
.select-all-section {
    background: #ffffff;
    border-radius: 12rpx;
    padding: 20rpx 24rpx;
    margin-bottom: 16rpx;
    border: 1rpx solid #e4e7ed;
}
.select-all-section .select-all-content {
    display: flex;
    align-items: center;
}
.select-all-section .select-all-checkbox {
    font-size: 28rpx;
    font-weight: 500;
}
// åˆ—表项选择框样式
.ledger-item {
    display: flex;
    align-items: flex-start;
    padding: 0;
}
.item-checkbox {
    padding: 24rpx 16rpx 0 24rpx;
    display: flex;
    align-items: center;
}
.item-content {
    flex: 1;
}
// ç©ºçŠ¶æ€
.no-data {
    padding: 100rpx 0;
@@ -818,88 +1016,6 @@
    font-size: 28rpx;
    color: #909399;
    margin-top: 20rpx;
}
// æ‰¹é‡æ“ä½œåŒºåŸŸæ ·å¼
.batch-actions-section {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    border-radius: 16rpx;
    padding: 24rpx;
    margin-bottom: 24rpx;
    box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.3);
    color: #ffffff;
}
.batch-info {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20rpx;
}
.batch-count {
    font-size: 28rpx;
    font-weight: 600;
}
.batch-buttons {
    display: flex;
    gap: 20rpx;
    justify-content: flex-end;
}
.batch-btn {
    min-width: 180rpx;
}
// å…¨é€‰æ“ä½œåŒºåŸŸæ ·å¼
.select-all-section {
    display: flex;
    justify-content: space-between;
    align-items: center;
    background: #f8f9fa;
    border-radius: 12rpx;
    padding: 20rpx 24rpx;
    margin-bottom: 20rpx;
    border: 1rpx solid #e9ecef;
}
.select-all-checkbox {
    display: flex;
    align-items: center;
    gap: 12rpx;
    cursor: pointer;
}
.select-all-text {
    font-size: 26rpx;
    color: #606266;
    font-weight: 500;
}
.select-all-hint {
    font-size: 22rpx;
    color: #909399;
}
// åˆ—表项选择样式
.ledger-item {
    display: flex;
    align-items: flex-start;
    padding: 0;
}
.item-checkbox {
    padding: 24rpx 16rpx 0 24rpx;
    cursor: pointer;
    display: flex;
    align-items: center;
    min-height: 48rpx;
}
.item-content {
    flex: 1;
    padding: 0;
}
// ç‚¹å‡»ç¼–辑区域样式
src/pages/productionManagement/productionReporting/components/formDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,160 @@
<template>
  <view>
    <up-popup v-model:show="dialogFormVisible" mode="bottom" round="12" :customStyle="{ height: '70vh' }" @close="closeDia">
      <view class="dia-container">
        <view class="dia-header">
          <text class="title">生产报工</text>
        </view>
        <scroll-view class="rows" scroll-y>
          <up-form :model="form" label-width="120" ref="formRef">
            <up-form-item label="排产数量">
              <up-input v-model="form.schedulingNum" placeholder="请输入" disabled />
            </up-form-item>
            <up-form-item label="本次生产数量" prop="finishedNum">
              <up-input v-model="form.finishedNum" type="number" placeholder="请输入" @change="changeNum" />
            </up-form-item>
            <up-form-item label="待生产数量">
              <up-input v-model="form.pendingNum" placeholder="请输入" disabled />
            </up-form-item>
            <up-form-item label="生产人" @click="openUserPicker">
              <up-input v-model="form.schedulingUserName" placeholder="选择人员" readonly @click="openUserPicker" />
              <template #right>
                <up-icon name="arrow-right" @click="openUserPicker"></up-icon>
              </template>
            </up-form-item>
            <up-form-item label="生产日期" @click="openDatePicker">
              <up-input v-model="form.schedulingDate" placeholder="请选择日期" readonly @click="openDatePicker" />
              <template #right>
                <up-icon name="calendar" @click="openDatePicker"></up-icon>
              </template>
            </up-form-item>
          </up-form>
        </scroll-view>
        <view class="dia-footer">
          <up-button type="primary" @click="submitForm">确认</up-button>
          <up-button @click="closeDia">取消</up-button>
        </view>
      </view>
    </up-popup>
    <!-- æ—¥æœŸé€‰æ‹©å™¨ -->
    <up-popup :show="datePicker.show" mode="bottom" @close="datePicker.show = false">
      <up-datetime-picker :show="true" v-model="datePicker.value" mode="date" @confirm="onDateConfirm" @cancel="datePicker.show = false" />
    </up-popup>
    <!-- äººå‘˜é€‰æ‹©å™¨ -->
    <up-action-sheet :show="userPicker.show" :actions="userActionList" title="选择人员" @select="onUserSelect" @close="userPicker.show = false" />
  </view>
</template>
<script setup>
import { ref, reactive, toRefs, getCurrentInstance, computed } from "vue";
import { userListNoPageByTenantId } from "@/api/system/user.js";
import { productionReport, productionReportUpdate } from "@/api/productionManagement/productionReporting.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const userList = ref([])
const dialogFormVisible = ref(false);
const operationType = ref('')
const data = reactive({
  form: {
        successNum: "",
        schedulingNum: "",
        finishedNum: "",
        schedulingUserId: "",
        schedulingUserName: "",
        schedulingDate: "",
  },
  rules: {
        schedulingNum: [{ required: true, message: "请输入", trigger: "blur" },],
  },
});
const { form, rules } = toRefs(data);
// pickers
const datePicker = ref({ show: false, value: Date.now() })
const userPicker = ref({ show: false })
const userActionList = computed(() => userList.value.map(u => ({ name: u.nickName, value: u.userId })))
// æ‰“开弹框
const openDialog = (type, row) => {
  operationType.value = type;
  dialogFormVisible.value = true;
    userListNoPageByTenantId().then((res) => {
        userList.value = res.data;
    });
    form.value = { ...row, schedulingUserName: row.schedulingUserName || '' }
    // åˆå§‹åŒ–:本次生产数量置空,待生产 = æŽ’产数量
    const sched = Number(form.value.schedulingNum) || 0
    form.value.finishedNum = ''
    form.value.pendingNum = sched
}
const changeNum = (value) => {
  const sched = Number(form.value.schedulingNum) || 0
  let num = Number(value)
  if (isNaN(num) || num < 0) num = 0
  if (num > sched) {
    num = sched
    uni.showToast({ title: '本次生产数量不可大于排产数量', icon: 'none' })
  }
  form.value.finishedNum = String(num)
  form.value.pendingNum = sched - num
}
const openDatePicker = () => {
  datePicker.value.value = Date.now()
  datePicker.value.show = true
}
const onDateConfirm = (e) => {
  const d = new Date(e.value)
  const y = d.getFullYear(); const m = String(d.getMonth()+1).padStart(2, '0'); const day = String(d.getDate()).padStart(2, '0')
  form.value.schedulingDate = `${y}-${m}-${day}`
  datePicker.value.show = false
}
const openUserPicker = () => { userPicker.value.show = true }
const onUserSelect = (item) => {
  if (item) {
    form.value.schedulingUserId = item.value
    form.value.schedulingUserName = item.name
  }
  userPicker.value.show = false
}
// æäº¤äº§å“è¡¨å•
const submitForm = () => {
  // ç®€åŒ–校验:仅确保必填字段
  if (!form.value.finishedNum || !form.value.schedulingUserId || !form.value.schedulingDate) {
    uni.showToast({ title: '请完善必填信息', icon: 'none' })
    return
  }
  form.value.staffState = 1
  const req = operationType.value === 'add' ? productionReport : productionReportUpdate
  req(form.value).then(() => {
    uni.showToast({ title: '提交成功', icon: 'success' })
    closeDia()
  })
}
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  dialogFormVisible.value = false;
  emit('close')
};
defineExpose({
  openDialog,
});
</script>
<style scoped lang="scss">
.dia-container { padding: 12px; height: 100%; display: flex; flex-direction: column; }
.dia-header { display: flex; align-items: center; justify-content: center; margin-bottom: 10px; }
.title { font-weight: 600; font-size: 16px; color: #333; }
.rows { flex: 1; min-height: 0; overflow-y: auto; }
.dia-footer { display: flex; gap: 10px; justify-content: flex-end; padding-top: 8px; }
</style>
src/pages/productionManagement/productionReporting/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,202 @@
<template>
  <view class="prod-reporting">
    <PageHeader title="生产报工" />
    <view class="search_form">
      <u-form>
        <view class="form-row">
          <u-form-item label="客户名称" label-width="80">
            <up-input v-model="searchForm.customerName" placeholder="请输入" clearable @change="handleQuery" />
          </u-form-item>
          <u-form-item label="项目名称" label-width="80">
            <up-input v-model="searchForm.projectName" placeholder="请输入" clearable @change="handleQuery" />
          </u-form-item>
        </view>
        <view class="form-row">
          <u-form-item label="状态" label-width="80">
            <up-input v-model="statusDisplay" placeholder="请选择状态" readonly @click="showStatusPicker = true" />
          </u-form-item>
        </view>
        <view class="form-actions">
          <u-button type="primary" size="small" @click="handleQuery">搜索</u-button>
        </view>
      </u-form>
    </view>
    <view class="list_container">
      <u-loading-icon v-if="tableLoading" text="加载中..." />
      <view v-else>
        <view v-if="!tableData || tableData.length === 0" class="empty">暂无数据</view>
        <view v-else class="card_list">
          <view v-for="item in tableData" :key="item.id" class="card_item">
            <view class="card_header">
              <u-tag :type="statusType(item.status)" size="mini">{{ statusText(item.status) }}</u-tag>
              <text class="card_title">{{ item.projectName }}</text>
            </view>
            <view class="card_body">
              <view class="row"><text class="label">排产日期</text><text class="value">{{ item.schedulingDate }}</text></view>
              <view class="row"><text class="label">排产人</text><text class="value">{{ item.schedulingUserName }}</text></view>
              <view class="row"><text class="label">合同号</text><text class="value">{{ item.salesContractNo }}</text></view>
              <view class="row"><text class="label">客户合同号</text><text class="value">{{ item.customerContractNo }}</text></view>
              <view class="row"><text class="label">客户名称</text><text class="value">{{ item.customerName }}</text></view>
              <view class="row"><text class="label">产品大类</text><text class="value">{{ item.productCategory }}</text></view>
              <view class="row"><text class="label">规格型号</text><text class="value">{{ item.specificationModel }}</text></view>
              <view class="row inline">
                <view class="col"><text class="label">单位</text><text class="value">{{ item.unit }}</text></view>
                <view class="col"><text class="label">排产数量</text><text class="value">{{ item.schedulingNum }}</text></view>
                <view class="col"><text class="label">生产数量</text><text class="value">{{ item.finishedNum }}</text></view>
                <view class="col"><text class="label">待生产数量</text><text class="value">{{ item.pendingFinishNum }}</text></view>
              </view>
            </view>
            <view class="card_actions">
              <u-button type="primary" size="small" @click="openForm('add', item)" :disabled="item.pendingFinishNum == 0">生产报工</u-button>
            </view>
          </view>
        </view>
      </view>
    </view>
    <form-dia ref="formDia" @close="handleQuery"></form-dia>
    <!-- çŠ¶æ€é€‰æ‹©å™¨ -->
    <up-action-sheet :show="showStatusPicker" :actions="statusActions" title="选择状态" @select="onStatusSelect" @close="showStatusPicker = false" />
  </view>
</template>
<script setup>
import { onMounted, ref, reactive, toRefs, nextTick, computed } from "vue";
import PageHeader from '@/components/PageHeader.vue'
import FormDia from './components/formDia.vue'
import { workListPage } from "@/api/productionManagement/productionReporting.js";
const data = reactive({
  searchForm: {
    customerName: "",
    projectName: "",
    status: undefined,
  },
});
const { searchForm } = toRefs(data);
const showStatusPicker = ref(false)
const statusOptions = ref([
  { label: '待生产', value: 1 },
  { label: '生产中', value: 2 },
  { label: '已报工', value: 3 },
])
const statusActions = computed(() => statusOptions.value.map(o => ({ name: o.label, value: o.value })))
const statusDisplay = ref('')
const tableData = ref([]);
const tableLoading = ref(false);
const page = reactive({
  current: -1,
  size: -1,
});
const formDia = ref()
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = -1;
  page.size = -1;
  getList();
};
const getList = () => {
  tableLoading.value = true;
  const params = { ...searchForm.value, ...page };
  workListPage(params).then(res => {
    tableLoading.value = false;
    tableData.value = res.data.records.map(item => ({
      ...item,
      pendingFinishNum: (Number(item.schedulingNum) || 0) - (Number(item.finishedNum) || 0)
    }));
  }).catch(err => {
    tableLoading.value = false;
  })
};
// çŠ¶æ€é€‰æ‹©
const onStatusSelect = (item) => {
  searchForm.value.status = item?.value
  statusDisplay.value = item?.name || ''
  showStatusPicker.value = false
  handleQuery()
}
// æ‰“开弹框
const openForm = (type, row) => {
  if (!row) return
  if ((Number(row.pendingFinishNum) || 0) === 0) {
    uni.showToast({ title: '无需再报工', icon: 'none' })
    return
  }
  nextTick(() => { formDia.value?.openDialog(type, row) })
};
// çŠ¶æ€æ–‡æœ¬/类型
const statusText = (s) => {
  if (s == 3) return '已报工'
  if (s == 1) return '待生产'
  return '生产中'
}
const statusType = (s) => {
  if (s == 3) return 'success'
  if (s == 1) return 'primary'
  return 'warning'
}
// æ— åˆ†é¡µ
// æ˜Žç»†äººå‘˜/日期选择
const openChildUserPicker = (index) => {
  if (!expandData.value[index]?.editType) return
  childUserPicker.value.index = index
  childUserPicker.value.show = true
}
const onChildUserSelect = (item) => {
  if (item && childUserPicker.value.index > -1) {
    const row = expandData.value[childUserPicker.value.index]
    row.schedulingUserId = item.value
    row.schedulingUserName = item.name
  }
  childUserPicker.value.show = false
}
const openChildDatePicker = (index) => {
  if (!expandData.value[index]?.editType) return
  childDatePicker.value.index = index
  childDatePicker.value.value = Date.now()
  childDatePicker.value.show = true
}
const onChildDateConfirm = (e) => {
  const d = new Date(e.value)
  const y = d.getFullYear(); const m = String(d.getMonth()+1).padStart(2, '0'); const day = String(d.getDate()).padStart(2, '0')
  const str = `${y}-${m}-${day}`
  if (childDatePicker.value.index > -1) {
    expandData.value[childDatePicker.value.index].schedulingDate = str
  }
  childDatePicker.value.show = false
}
onMounted(() => {
  getList();
});
</script>
<style scoped lang="scss">
.prod-reporting { padding-bottom: 12px; }
.search_form { margin: 12px; background: #fff; border-radius: 8px; padding: 10px; }
.form-row { display: flex; gap: 12px; }
.form-actions { display: flex; justify-content: flex-end; }
.list_container { padding: 0 12px; }
.empty { text-align: center; color: #888; padding: 20px 0; }
.card_list { display: flex; flex-direction: column; gap: 10px; }
.card_item { background: #fff; border-radius: 10px; padding: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); }
.card_header { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.card_title { font-weight: 500; color: #333; margin-left: auto; }
.card_body .row { display: flex; justify-content: space-between; padding: 4px 0; }
.card_body .row.inline { display: flex; gap: 10px; flex-wrap: wrap; }
.card_body .row.inline .col { min-width: 45%; display: flex; justify-content: space-between; }
.label { color: #666; font-size: 12px; }
.value { color: #333; font-size: 12px; }
.card_actions { display: flex; justify-content: flex-end; gap: 8px; padding-top: 8px; }
.ml8 { margin-left: 8px; }
</style>
src/pages/sales/salesAccount/detail.vue
@@ -223,6 +223,18 @@
                                ></up-icon>
                            </template>
                        </up-form-item>
                        <!-- ç»‘定机器 -->
                        <up-form-item
                            label="绑定机器"
                            prop="speculativeTradingName"
                            required
                        >
                            <up-input
                            disabled
                                v-model="product.speculativeTradingName"
                                placeholder="请输入"
                            />
                        </up-form-item>
                        
                        <!-- å•位 -->
                        <up-form-item
@@ -437,7 +449,8 @@
    return modelOptions.value.map(model => ({
        name: model.text,
        value: model.value,
        unit: model.unit
        unit: model.unit,
        speculativeTradingName: model.speculativeTradingName
    }))
})
@@ -521,6 +534,7 @@
    specificationModel: '',
        productModelId: '',
    unit: '',
    speculativeTradingName: '',
    taxRate: '',
    taxInclusiveUnitPrice: '',
    quantity: '',
@@ -603,14 +617,17 @@
            text: user.model,
            value: user.id,
            unit: user.unit,
            speculativeTradingName: user.speculativeTradingName,
        }));
    });
};
// è§„格型号选择事件
const onSpecificationSelect = (item) => {
console.log('selected item---', item);
    productData.value[currentProductIndex.value].specificationModel = item.name
    productData.value[currentProductIndex.value].productModelId = item.value
    productData.value[currentProductIndex.value].unit = item.unit
    productData.value[currentProductIndex.value].speculativeTradingName = item.speculativeTradingName
}
// ç¨ŽçŽ‡é€‰æ‹©äº‹ä»¶
const onTaxRateSelect = (item) => {
src/pages/sales/salesAccount/index.vue
@@ -22,7 +22,7 @@
        </view>
        
        <!-- é”€å”®å°è´¦ç€‘布流 -->
        <view class="ledger-list" v-if="total > 0">
        <view class="ledger-list" v-if="ledgerList.length > 0">
            <view v-for="(item, index) in ledgerList" :key="index">
                <view class="ledger-item" @click="handleInfo('edit', item)">
                    <view class="item-header">
@@ -115,7 +115,6 @@
// é”€å”®å°è´¦æ•°æ®
const ledgerList = ref([]);
const total = ref(0);
// è¿”回上一页
const goBack = () => {
@@ -129,8 +128,8 @@
        size: -1
    }
    ledgerListPage({...page, salesContractNo: salesContractNo.value}).then((res) => {
        console.log('销售台账----', res);
        ledgerList.value = res.records;
        total.value = res.total;
        closeToast()
    }).catch(() => {
        closeToast()
src/pages/sales/salesAccount/view.vue
@@ -69,6 +69,10 @@
              <text class="info-value">{{ product.specificationModel }}</text>
            </view>
            <view class="info-item">
              <text class="info-label">绑定机器</text>
              <text class="info-value">{{ product.speculativeTradingName }}</text>
            </view>
            <view class="info-item">
              <text class="info-label">单位</text>
              <text class="info-value">{{ product.unit }}</text>
            </view>
src/utils/request.ts
@@ -22,12 +22,12 @@
    config.url = url
  }
  // è®°å½•请求参数
  console.log('请求发送参数:', {
    url: (config.baseUrl || baseUrl) + config.url,
    method: config.method || 'GET',
    headers: config.header,
    data: config.data
  })
  // console.log('请求发送参数:', {
  //   url: (config.baseUrl || baseUrl) + config.url,
  //   method: config.method || 'GET',
  //   headers: config.header,
  //   data: config.data
  // })
  return new Promise((resolve, reject) => {
    uni.request({
      method: config.method || 'GET',
@@ -49,11 +49,11 @@
      // @ts-ignore
      const msg: string = errorCode[code] || data.msg || errorCode['default']
      // è®°å½•接收到的参数
      console.log('接收到的参数:', {
        url: (config.baseUrl || baseUrl) + config.url,
        code: code,
        data: data
      })
      // console.log('接收到的参数:', {
      //   url: (config.baseUrl || baseUrl) + config.url,
      //   code: code,
      //   data: data
      // })
      if (code === 401) {
        showConfirm('登录状态已过期,您可以继续留在该页面,或者重新登录?').then(res => {
          if (res.confirm) {