已添加1个文件
已修改11个文件
681 ■■■■■ 文件已修改
src/api/consumablesLogistics/consumablesInRecord.js 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/consumablesLogistics/consumablesOutRecord.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/consumablesLogistics/dispatchLog/index.vue 145 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/consumablesLogistics/receiptManagement/Record.vue 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/consumablesLogistics/receiptManagement/index.vue 162 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/consumablesLogistics/receiptManagement/view.vue 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/consumablesLogistics/stockReport/index.vue 125 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/inventoryManagement/stockReport/index.vue 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/qualityManagement/nonconformingManagement/form.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/qualityManagement/nonconformingManagement/index.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/qualityManagement/rawMaterial/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/ruoyi.js 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/consumablesLogistics/consumablesInRecord.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
import request from '@/utils/request'
// è€—材入库管理-查询入库记录列表
export const getConsumablesInRecordListPage = (params) => {
  return request({
    url: '/consumablesInRecord/listPage',
    method: 'get',
    params
  })
}
// ç¼–辑耗材入库(仅用于台账编辑)
export const editStockInStock = (data) => {
  return request({
    url: '/consumablesInRecord/editStockInStock',
    method: 'post',
    data
  })
}
// æ‰¹é‡åˆ é™¤å…¥åº“记录
export const batchDeleteConsumablesInRecords = (ids) => {
  return request({
    url: '/consumablesInRecord',
    method: 'delete',
    data: ids
  })
}
src/api/consumablesLogistics/consumablesOutRecord.js
@@ -16,3 +16,12 @@
  });
};
// ç¼–辑耗材出库(仅用于台账编辑)
export const editStockOut = (data) => {
  return request({
    url: "/consumablesOutRecord/editStockOut",
    method: "post",
    data,
  });
};
src/pages/consumablesLogistics/dispatchLog/index.vue
@@ -35,7 +35,8 @@
            </view>
          </view>
          <view class="card-actions">
            <view class="btn-delete" @click.stop="handleDeleteSingle(item)">删除</view>
            <view v-if="hasCDispatchEdit" class="btn-edit" @click.stop="handleEdit(item)">编辑</view>
            <view v-if="hasCDispatchCancel" class="btn-delete" @click.stop="handleDeleteSingle(item)">删除</view>
          </view>
        </view>
      </view>
@@ -44,15 +45,38 @@
    <view class="load-more-wrap" v-if="tableData.length > 0">
      <u-loadmore :status="loadStatus" @loadmore="loadMore" />
    </view>
    <!-- ç¼–辑弹窗 -->
    <up-popup :show="showEditModal" mode="bottom" @close="showEditModal = false">
      <view class="edit-popup">
        <view class="popup-header">
          <text class="popup-title">编辑出库</text>
        </view>
        <scroll-view class="popup-body" scroll-y>
          <view class="form-row">
            <text class="form-label required">数量</text>
            <up-input v-model="editForm.qualitity" type="number" placeholder="请输入数量" />
          </view>
        </scroll-view>
        <view class="popup-footer">
          <view class="btn-cancel" @click="showEditModal = false">取消</view>
          <view class="btn-ok" @click="handleEditSubmit">确认</view>
        </view>
      </view>
    </up-popup>
  </view>
</template>
<script setup>
import { reactive, ref, toRefs } from "vue";
import { computed, reactive, ref, toRefs } from "vue";
import { onReachBottom, onShow } from "@dcloudio/uni-app";
import PageHeader from "@/components/PageHeader.vue";
import { getConsumablesOutRecordPage, delConsumablesOutRecord } from "@/api/consumablesLogistics/consumablesOutRecord.js";
import { getConsumablesOutRecordPage, delConsumablesOutRecord, editStockOut } from "@/api/consumablesLogistics/consumablesOutRecord.js";
import { findAllQualifiedStockOutRecordTypeOptions } from "@/api/basicData/enum.js";
import { checkPermi } from "@/utils/permission";
const hasCDispatchEdit = computed(() => checkPermi(["c_dispatch_edit"]));
const hasCDispatchCancel = computed(() => checkPermi(["c_dispatch_cancel"]));
const stockRecordTypeOptions = ref([]);
const tableData = ref([]);
@@ -142,6 +166,7 @@
const handleDeleteSingle = (item) => {
  if (!item?.id) return;
  if (!hasCDispatchCancel.value) return;
  uni.showModal({
    title: "删除",
    content: "确认删除该条出库记录?",
@@ -157,6 +182,45 @@
        });
    },
  });
};
// ---------------- ç¼–辑出库 ----------------
const showEditModal = ref(false);
const editForm = reactive({
  id: null,
  qualitity: "",
});
const handleEdit = (row) => {
  if (!hasCDispatchEdit.value) return;
  editForm.id = row?.id ?? null;
  editForm.qualitity = row?.qualitity ?? row?.stockOutNum ?? "";
  showEditModal.value = true;
};
const handleEditSubmit = () => {
  if (!hasCDispatchEdit.value) return;
  const q = Number(editForm.qualitity);
  if (!Number.isFinite(q) || q <= 0) {
    uni.showToast({ title: "请输入正确数量", icon: "none" });
    return;
  }
  uni.showLoading({ title: "保存中...", mask: true });
  editStockOut({
    id: editForm.id,
    qualitity: q,
  })
    .then(() => {
      uni.showToast({ title: "编辑成功", icon: "success" });
      showEditModal.value = false;
      getList();
    })
    .catch(() => {
      uni.showToast({ title: "编辑失败", icon: "none" });
    })
    .finally(() => {
      uni.hideLoading();
    });
};
const goBack = () => uni.navigateBack();
@@ -188,14 +252,83 @@
  margin-top: 16rpx;
  padding-top: 16rpx;
  border-top: 1rpx solid #eee;
  width: 100%;
  text-align: center;
}
.btn-edit {
  color: #2979ff;
  font-size: 28rpx;
  padding: 12rpx 32rpx;
  margin: 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.btn-delete {
  color: #f56c6c;
  font-size: 28rpx;
  padding: 12rpx 36rpx;
  padding: 12rpx 32rpx;
  margin: 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.edit-popup {
  background: #fff;
  border-radius: 24rpx 24rpx 0 0;
  padding-bottom: env(safe-area-inset-bottom);
}
.popup-header {
  padding: 24rpx;
  border-bottom: 1rpx solid #eee;
  text-align: center;
}
.popup-title {
  font-size: 30rpx;
  font-weight: 500;
  color: #333;
}
.popup-body {
  padding: 24rpx 24rpx 0;
  max-height: 60vh;
}
.form-row {
  margin-bottom: 24rpx;
}
.form-label {
  display: block;
  font-size: 26rpx;
  color: #666;
  margin-bottom: 12rpx;
}
.form-label.required:before {
  content: "*";
  color: #f56c6c;
  margin-right: 6rpx;
}
.popup-footer {
  display: flex;
  gap: 24rpx;
  padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom));
  border-top: 1rpx solid #eee;
}
.btn-cancel,
.btn-ok {
  flex: 1;
  height: 88rpx;
  border-radius: 999rpx;
  border: 1rpx solid rgba(245, 108, 108, 0.55);
  background: rgba(245, 108, 108, 0.08);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 30rpx;
}
.btn-cancel {
  background: #f0f0f0;
  color: #666;
}
.btn-ok {
  background: #2979ff;
  color: #fff;
}
.no-data { text-align: center; padding: 60rpx 0; color: #999; font-size: 28rpx; }
.load-more-wrap { padding: 24rpx 24rpx 8rpx; }
src/pages/consumablesLogistics/receiptManagement/Record.vue
@@ -37,7 +37,12 @@
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">搜索</el-button>
      </div>
      <div>
        <el-button type="danger" plain @click="handleDelete">删除</el-button>
        <el-button
          v-if="hasCReceiptCancel"
          type="danger"
          plain
          @click="handleDelete"
        >删除</el-button>
      </div>
    </div>
@@ -56,11 +61,21 @@
        <el-table-column label="产品名称" prop="productName" min-width="160" show-overflow-tooltip />
        <el-table-column label="规格型号" prop="model" min-width="160" show-overflow-tooltip />
        <el-table-column label="单位" prop="unit" width="100" show-overflow-tooltip />
        <el-table-column label="入库数量" prop="stockInNum" width="110" show-overflow-tooltip />
        <el-table-column label="入库数量" prop="qualitity" width="110" show-overflow-tooltip />
        <el-table-column label="入库人" prop="createBy" width="120" show-overflow-tooltip />
        <el-table-column label="来源" prop="recordType" width="140" show-overflow-tooltip>
          <template #default="scope">
            {{ getRecordType(scope.row.recordType) }}
          </template>
        </el-table-column>
        <el-table-column label="操作" width="120" align="center">
          <template #default="scope">
            <el-button
              v-if="hasCReceiptEdit"
              type="primary"
              size="mini"
              @click="handleEdit(scope.row)"
            >编辑</el-button>
          </template>
        </el-table-column>
      </el-table>
@@ -73,20 +88,67 @@
        @pagination="paginationChange"
      />
    </div>
    <el-dialog
      v-model="isShowEditModal"
      title="编辑入库"
      width="600"
      @close="closeEditModal"
    >
      <el-form
        label-width="100px"
        :model="editForm"
        label-position="top"
        ref="editFormRef"
      >
        <el-form-item
          label="数量"
          prop="qualitity"
          :rules="[{ required: true, message: '请输入数量', trigger: ['blur', 'change'] }]"
        >
          <el-input-number
            v-model="editForm.qualitity"
            :min="0"
            :step="1"
            :precision="0"
            controls-position="right"
            style="width: 100%"
            placeholder="请输入数量"
          />
        </el-form-item>
        <el-form-item
          label="采购员"
          prop="purchaser"
          :rules="[{ required: true, message: '请输入采购员', trigger: ['blur', 'change'] }]"
        >
          <el-input v-model="editForm.purchaser" placeholder="请输入采购员" />
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="handleEditSubmit">确认</el-button>
          <el-button @click="closeEditModal">取消</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { onMounted, reactive, ref, toRefs, watch } from "vue";
import { computed, onMounted, reactive, ref, toRefs, watch } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import Pagination from "@/components/PIMTable/Pagination.vue";
import {
  batchDeleteConsumablesInRecords,
  getConsumablesInRecordListPage,
  editStockInStock,
} from "@/api/consumablesLogistics/consumablesInRecord.js";
import {
  findAllQualifiedStockInRecordTypeOptions,
} from "@/api/basicData/enum.js";
import { checkPermi } from "@/utils/permission.js";
const props = defineProps({
  type: {
@@ -95,6 +157,9 @@
    default: "0",
  },
});
const hasCReceiptEdit = computed(() => checkPermi(['c_receipt_edit']))
const hasCReceiptCancel = computed(() => checkPermi(['c_receipt_cancel']))
const tableData = ref([]);
const selectedRows = ref([]);
@@ -158,6 +223,7 @@
};
const handleDelete = () => {
  if (!hasCReceiptCancel.value) return
  const ids = selectedRows.value.map(i => i.id).filter(Boolean);
  if (ids.length === 0) {
    ElMessage.warning("请选择数据");
@@ -176,6 +242,38 @@
    .catch(() => {});
};
// ç¼–辑入库
const isShowEditModal = ref(false);
const editFormRef = ref(null);
const editForm = ref({});
const handleEdit = (row) => {
  if (!hasCReceiptEdit.value) return
  editForm.value = {
    id: row?.id,
    qualitity: row?.qualitity,
    purchaser: row?.purchaser,
  };
  isShowEditModal.value = true;
};
const closeEditModal = () => {
  isShowEditModal.value = false;
  editForm.value = {};
  editFormRef.value?.clearValidate?.();
};
const handleEditSubmit = () => {
  editFormRef.value?.validate?.((valid) => {
    if (!valid) return;
    editStockInStock(editForm.value).then(() => {
      closeEditModal();
      ElMessage.success("编辑成功");
      getList();
    });
  });
};
watch(
  () => props.type,
  () => {
src/pages/consumablesLogistics/receiptManagement/index.vue
@@ -28,14 +28,15 @@
            <view class="card-body">
              <view class="row"><text class="l">规格型号</text><text class="r">{{ item.model }}</text></view>
              <view class="row"><text class="l">单位</text><text class="r">{{ item.unit }}</text></view>
              <view class="row"><text class="l">入库数量</text><text class="r highlight">{{ item.stockInNum }}</text></view>
              <view class="row"><text class="l">入库数量</text><text class="r highlight">{{ item.qualitity ?? item.stockInNum }}</text></view>
              <view class="row"><text class="l">采购员</text><text class="r">{{ item.purchaser || '-' }}</text></view>
              <view class="row"><text class="l">入库人</text><text class="r">{{ item.createBy }}</text></view>
              <view class="row" v-if="item.recordType !== undefined"><text class="l">来源</text><text class="r">{{ getRecordType(item.recordType) || item.recordType }}</text></view>
            </view>
          </view>
          <view class="card-actions">
            <view class="btn-delete" @click.stop="handleDeleteSingle(item)">删除</view>
            <view v-if="hasCReceiptEdit" class="btn-edit" @click.stop="handleEdit(item)">编辑</view>
            <view v-if="hasCReceiptCancel" class="btn-delete" @click.stop="handleDeleteSingle(item)">删除</view>
          </view>
        </view>
      </view>
@@ -44,15 +45,43 @@
    <view class="load-more-wrap" v-if="tableData.length > 0">
      <u-loadmore :status="loadStatus" @loadmore="loadMore" />
    </view>
    <!-- ç¼–辑弹窗 -->
    <up-popup :show="showEditModal" mode="bottom" @close="showEditModal = false">
      <view class="edit-popup">
        <view class="popup-header">
          <text class="popup-title">编辑入库</text>
        </view>
        <scroll-view class="popup-body" scroll-y>
          <view class="form-row">
            <text class="form-label required">数量</text>
            <up-input v-model="editForm.qualitity" type="number" placeholder="请输入数量" />
          </view>
          <view class="form-row">
            <text class="form-label required">采购员</text>
            <up-input v-model="editForm.purchaser" placeholder="请输入采购员" />
          </view>
        </scroll-view>
        <view class="popup-footer">
          <view class="btn-cancel" @click="showEditModal = false">取消</view>
          <view class="btn-ok" @click="handleEditSubmit">确认</view>
        </view>
      </view>
    </up-popup>
  </view>
</template>
<script setup>
import { reactive, ref, toRefs } from "vue";
import { computed, reactive, ref, toRefs } from "vue";
import { onReachBottom, onShow } from "@dcloudio/uni-app";
import PageHeader from "@/components/PageHeader.vue";
import request from "@/utils/request";
import { findAllQualifiedStockInRecordTypeOptions } from "@/api/basicData/enum.js";
import { checkPermi } from "@/utils/permission";
import { editStockInStock } from "@/api/consumablesLogistics/consumablesInRecord.js";
const hasCReceiptEdit = computed(() => checkPermi(['c_receipt_edit']))
const hasCReceiptCancel = computed(() => checkPermi(['c_receipt_cancel']))
const stockRecordTypeOptions = ref([]);
const tableData = ref([]);
@@ -146,6 +175,7 @@
const handleDeleteSingle = (item) => {
  if (!item?.id) return;
  if (!hasCReceiptCancel.value) return
  uni.showModal({
    title: "删除",
    content: "确认删除该条入库记录?",
@@ -166,6 +196,53 @@
    },
  });
};
// ---------------- ç¼–辑入库 ----------------
const showEditModal = ref(false)
const editForm = reactive({
  id: null,
  qualitity: '',
  purchaser: ''
})
const handleEdit = (row) => {
  if (!hasCReceiptEdit.value) return
  editForm.id = row?.id ?? null
  editForm.qualitity = row?.qualitity ?? row?.stockInNum ?? ''
  editForm.purchaser = row?.purchaser ?? ''
  showEditModal.value = true
}
const handleEditSubmit = () => {
  if (!hasCReceiptEdit.value) return
  const q = Number(editForm.qualitity)
  if (!Number.isFinite(q) || q <= 0) {
    uni.showToast({ title: "请输入正确数量", icon: "none" })
    return
  }
  if (!editForm.purchaser) {
    uni.showToast({ title: "请输入采购员", icon: "none" })
    return
  }
  uni.showLoading({ title: "保存中...", mask: true })
  editStockInStock({
    id: editForm.id,
    qualitity: q,
    purchaser: editForm.purchaser
  })
    .then(() => {
      uni.showToast({ title: "编辑成功", icon: "success" })
      showEditModal.value = false
      getList()
    })
    .catch(() => {
      uni.showToast({ title: "编辑失败", icon: "none" })
    })
    .finally(() => {
      uni.hideLoading()
    })
}
const goBack = () => uni.navigateBack();
@@ -201,14 +278,91 @@
  margin-top: 16rpx;
  padding-top: 16rpx;
  border-top: 1rpx solid #eee;
  width: 100%;
  text-align: center;
}
.btn-edit {
  color: #2979ff;
  font-size: 28rpx;
  padding: 12rpx 32rpx;
  border-radius: 999rpx;
  border: 1rpx solid rgba(41, 121, 255, 0.25);
  background: rgba(41, 121, 255, 0.06);
  margin: 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.btn-delete {
  color: #f56c6c;
  font-size: 28rpx;
  padding: 12rpx 36rpx;
  padding: 12rpx 32rpx;
  border-radius: 999rpx;
  border: 1rpx solid rgba(245, 108, 108, 0.55);
  background: rgba(245, 108, 108, 0.08);
  margin: 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
/* ç¼–辑弹窗样式 */
.edit-popup {
  background: #fff;
  border-radius: 24rpx 24rpx 0 0;
  padding-bottom: env(safe-area-inset-bottom);
}
.popup-header {
  padding: 24rpx;
  border-bottom: 1rpx solid #eee;
  text-align: center;
}
.popup-title {
  font-size: 30rpx;
  font-weight: 500;
  color: #333;
}
.popup-body {
  padding: 24rpx 24rpx 0;
  max-height: 60vh;
}
.form-row {
  margin-bottom: 24rpx;
}
.form-label {
  display: block;
  font-size: 26rpx;
  color: #666;
  margin-bottom: 12rpx;
}
.form-label.required:before {
  content: '*';
  color: #f56c6c;
  margin-right: 6rpx;
}
.popup-footer {
  display: flex;
  gap: 24rpx;
  padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom));
  border-top: 1rpx solid #eee;
}
.btn-cancel,
.btn-ok {
  flex: 1;
  height: 88rpx;
  border-radius: 999rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 30rpx;
}
.btn-cancel {
  background: #f0f0f0;
  color: #666;
}
.btn-ok {
  background: #2979ff;
  color: #fff;
}
.no-data { text-align: center; padding: 60rpx 0; color: #999; font-size: 28rpx; }
.load-more-wrap { padding: 24rpx 24rpx 8rpx; }
src/pages/consumablesLogistics/receiptManagement/view.vue
@@ -37,7 +37,7 @@
          </view>
          <view class="detail-row detail-row-highlight">
            <text class="label">入库数量</text>
            <text class="value value-num">{{ detail.stockInNum ?? '-' }}</text>
            <text class="value value-num">{{ detail.qualitity ?? detail.stockInNum ?? '-' }}</text>
          </view>
          <view class="detail-row">
            <text class="label">入库人</text>
@@ -89,6 +89,7 @@
    model: d.model,
    unit: d.unit,
    stockInNum: d.stockInNum,
    qualitity: d.qualitity,
    createBy: d.createBy,
    recordType: d.recordType,
    purchaser: d.purchaser,
src/pages/consumablesLogistics/stockReport/index.vue
@@ -12,6 +12,37 @@
        <text>{{ t.label }}</text>
      </view>
    </view>
    <!-- æ—¶é—´é€‰æ‹©åŒºåŸŸ -->
    <view class="search-section">
      <!-- æ—¥æŠ¥ï¼šé€‰æ‹©å¹´æœˆæ—¥ -->
      <view v-if="searchForm.reportType === 'daily'" class="search-row">
        <view class="date-picker" @click="openDatePicker('single')">
          <text>{{ searchForm.singleDate || '请选择日期' }}</text>
        </view>
      </view>
      <!-- æœˆæŠ¥ï¼šé€‰æ‹©å¹´æœˆ -->
      <view v-else-if="searchForm.reportType === 'monthly'" class="search-row">
        <view class="date-picker" @click="openDatePicker('startMonth')">
          <text>{{ searchForm.startMonth || '请选择开始月份' }}</text>
        </view>
        ~
        <view class="date-picker" @click="openDatePicker('endMonth')">
          <text>{{ searchForm.endMonth || '请选择结束月份' }}</text>
        </view>
      </view>
      <!-- è¿›å‡ºå­˜ï¼šé€‰æ‹©å¹´æœˆæ—¥æ—¶é—´èŒƒå›´ -->
      <view v-else-if="searchForm.reportType === 'inout'" class="search-row">
        <view class="date-picker" @click="openDatePicker('startDate')">
          <text>{{ searchForm.startDate || '请选择开始日期' }}</text>
        </view>
        ~
        <view class="date-picker" @click="openDatePicker('endDate')">
          <text>{{ searchForm.endDate || '请选择结束日期' }}</text>
        </view>
      </view>
    </view>
    <view class="list-section">
      <view class="section-header">
        <text class="table-title">{{ tableTitle }}</text>
@@ -43,14 +74,13 @@
      </view>
      <view v-else class="no-data">暂无数据</view>
    </view>
    <up-popup :show="showDatePicker" mode="bottom" @close="showDatePicker = false">
      <up-datetime-picker
        v-model="dateValue"
        :mode="datePickerMode"
        @confirm="onDateConfirm"
        @cancel="showDatePicker = false"
      />
    </up-popup>
    <up-datetime-picker
      v-model="dateValue"
      :mode="datePickerMode"
      :show="showDatePicker"
      @confirm="onDateConfirm"
      @cancel="showDatePicker = false"
    />
  </view>
</template>
@@ -58,7 +88,6 @@
import { ref, reactive, toRefs, computed, watch } from "vue";
import dayjs from "dayjs";
import PageHeader from "@/components/PageHeader.vue";
import { formatDateToYMD } from "@/utils/ruoyi";
import { onShow, onReachBottom } from "@dcloudio/uni-app";
import { getConsumablesInReportList, getConsumablesInInAndOutReportList } from "@/api/consumablesLogistics/consumablesIn.js";
import {
@@ -88,7 +117,7 @@
const { searchForm } = toRefs(data);
const datePickerMode = computed(() => {
  if (datePickerTarget.value === "startMonth" || datePickerTarget.value === "endMonth") return "month";
  if (datePickerTarget.value === "startMonth" || datePickerTarget.value === "endMonth") return "year-month";
  return "date";
});
@@ -106,11 +135,8 @@
  if (searchForm.value.reportType === "daily") {
    p.reportDate = searchForm.value.singleDate;
  } else if (searchForm.value.reportType === "monthly") {
    p.startMonth = searchForm.value.startMonth;
    p.endMonth = searchForm.value.endMonth;
  } else if (searchForm.value.reportType === "monthly") {
    p.startMonth = searchForm.value.startMonth;
    p.endMonth = searchForm.value.endMonth;
    p.startMonth = searchForm.value.startMonth ? `${searchForm.value.startMonth}-01` : "";
    p.endMonth = searchForm.value.endMonth ? `${searchForm.value.endMonth}-01` : "";
  } else {
    p.startDate = searchForm.value.startDate;
    p.endDate = searchForm.value.endDate;
@@ -178,24 +204,68 @@
  getList();
};
const toPickerTimestamp = (val, target) => {
  if (!val) return Date.now();
  let parsed;
  if (target === "startMonth" || target === "endMonth") {
    parsed = dayjs(`${val}-01`).valueOf();
  } else {
    parsed = dayjs(val).valueOf();
  }
  return Number.isNaN(parsed) ? Date.now() : parsed;
};
const formatPickerDate = (value, isMonth) => {
  const parsed = dayjs(value);
  if (!parsed.isValid()) return "";
  return parsed.format(isMonth ? "YYYY-MM" : "YYYY-MM-DD");
};
const openDatePicker = (target) => {
  datePickerTarget.value = target;
  let val = "";
  if (target === "single") val = searchForm.value.singleDate;
  else if (target === "startMonth") val = searchForm.value.startMonth;
  else if (target === "endMonth") val = searchForm.value.endMonth;
  dateValue.value = val ? new Date(val).getTime() : Date.now();
  else if (target === "startDate") val = searchForm.value.startDate;
  else if (target === "endDate") val = searchForm.value.endDate;
  dateValue.value = toPickerTimestamp(val, target);
  showDatePicker.value = true;
};
const onDateConfirm = (e) => {
  const isMonth = datePickerTarget.value === "startMonth" || datePickerTarget.value === "endMonth";
  const str = isMonth ? dayjs(e.value).format("YYYY-MM") : formatDateToYMD(e.value);
  if (datePickerTarget.value === "single") searchForm.value.singleDate = str;
  else if (datePickerTarget.value === "startMonth") searchForm.value.startMonth = str;
  else if (datePickerTarget.value === "endMonth") searchForm.value.endMonth = str;
  showDatePicker.value = false;
  handleQuery();
  const str = formatPickerDate(e.value, isMonth);
  if (!str) {
    showDatePicker.value = false;
    uni.showToast({ title: "日期格式无效", icon: "none" });
    return;
  }
  if (datePickerTarget.value === "single") {
    searchForm.value.singleDate = str;
    showDatePicker.value = false;
    handleQuery();
  } else if (datePickerTarget.value === "startMonth") {
    searchForm.value.startMonth = str;
    showDatePicker.value = false;
    setTimeout(() => {
      openDatePicker("endMonth");
    }, 300);
  } else if (datePickerTarget.value === "endMonth") {
    searchForm.value.endMonth = str;
    showDatePicker.value = false;
    handleQuery();
  } else if (datePickerTarget.value === "startDate") {
    searchForm.value.startDate = str;
    showDatePicker.value = false;
    setTimeout(() => {
      openDatePicker("endDate");
    }, 300);
  } else if (datePickerTarget.value === "endDate") {
    searchForm.value.endDate = str;
    showDatePicker.value = false;
    handleQuery();
  }
};
const initDefaultDates = () => {
@@ -204,14 +274,15 @@
    searchForm.value.singleDate = today.format("YYYY-MM-DD");
  }
  if (!searchForm.value.startMonth || !searchForm.value.endMonth) {
    const startOfMonth = today.startOf("month").format("YYYY-MM-DD");
    const endOfMonth = today.endOf("month").format("YYYY-MM-DD");
    searchForm.value.startMonth = startOfMonth;
    searchForm.value.endMonth = endOfMonth;
    searchForm.value.startMonth = today.format("YYYY-MM");
    searchForm.value.endMonth = today.add(1, "month").format("YYYY-MM");
  }
  if (!searchForm.value.startDate || !searchForm.value.endDate) {
    searchForm.value.endDate = today.format("YYYY-MM-DD");
    searchForm.value.startDate = today.subtract(6, "day").format("YYYY-MM-DD");
  }
  if (!datePickerTarget.value) {
    dateValue.value = toPickerTimestamp(searchForm.value.singleDate, "single");
  }
};
@@ -228,6 +299,8 @@
  fetchStockRecordTypeOptions();
});
initDefaultDates();
onReachBottom(() => loadMore());
const goBack = () => uni.navigateBack();
src/pages/inventoryManagement/stockReport/index.vue
@@ -80,15 +80,13 @@
      <view v-else class="no-data">暂无数据</view>
    </view>
    <up-popup :show="showDatePicker" mode="bottom" @close="showDatePicker = false">
      <up-datetime-picker
        v-model="dateValue"
        :mode="datePickerMode"
        :show="showDatePicker"
        @confirm="onDateConfirm"
        @cancel="showDatePicker = false"
      />
    </up-popup>
    <up-datetime-picker
      v-model="dateValue"
      :mode="datePickerMode"
      :show="showDatePicker"
      @confirm="onDateConfirm"
      @cancel="showDatePicker = false"
    />
  </view>
</template>
@@ -96,7 +94,6 @@
import { ref, reactive, toRefs, computed, watch } from 'vue'
import dayjs from 'dayjs'
import PageHeader from '@/components/PageHeader.vue'
import { formatDateToYMD } from '@/utils/ruoyi'
import { onShow } from '@dcloudio/uni-app'
import {
  getStockInventoryReportList,
@@ -216,6 +213,23 @@
  getList()
}
const toPickerTimestamp = (val, target) => {
  if (!val) return Date.now()
  let parsed
  if (target === 'startMonth' || target === 'endMonth') {
    parsed = dayjs(`${val}-01`).valueOf()
  } else {
    parsed = dayjs(val).valueOf()
  }
  return Number.isNaN(parsed) ? Date.now() : parsed
}
const formatPickerDate = (value, isMonth) => {
  const parsed = dayjs(value)
  if (!parsed.isValid()) return ''
  return parsed.format(isMonth ? 'YYYY-MM' : 'YYYY-MM-DD')
}
const openDatePicker = (target) => {
  let val = ''
  datePickerTarget.value = target
@@ -236,13 +250,18 @@
      val = searchForm.value.endDate
      break
  }
  dateValue.value = val ? new Date(val).getTime() : Date.now()
  dateValue.value = toPickerTimestamp(val, target)
  showDatePicker.value = true
}
const onDateConfirm = (e) => {
  const isMonth = datePickerTarget.value === 'startMonth' || datePickerTarget.value === 'endMonth'
  const str = isMonth ? dayjs(e.value).format('YYYY-MM') : formatDateToYMD(e.value)
  const str = formatPickerDate(e.value, isMonth)
  if (!str) {
    showDatePicker.value = false
    uni.showToast({ title: '日期格式无效', icon: 'none' })
    return
  }
  
  if (datePickerTarget.value === 'single') {
    searchForm.value.singleDate = str
@@ -286,6 +305,9 @@
    searchForm.value.endDate = today.format('YYYY-MM-DD')
    searchForm.value.startDate = today.subtract(6, 'day').format('YYYY-MM-DD')
  }
  if (!datePickerTarget.value) {
    dateValue.value = toPickerTimestamp(searchForm.value.singleDate, 'single')
  }
}
watch(
@@ -302,6 +324,8 @@
  fetchStockRecordTypeOptions()
})
initDefaultDates()
const goBack = () => uni.navigateBack()
</script>
src/pages/qualityManagement/nonconformingManagement/form.vue
@@ -370,7 +370,7 @@
    id: d.id,
    productId: d.productId,
    productName: d.productName,
    productModelId: d.productModelId,
    productModelId: d.productModelId ?? d.modelId ?? d.productModeId ?? '',
    model: d.model,
    unit: d.unit,
    batchNo: d.batchNo,
@@ -387,7 +387,9 @@
const validate = () => {
  if (!form.productId) return toast('请选择产品名称'), false
  if (!form.productModelId) return toast('请选择规格型号'), false
  // å¯¹é½ PC ç«¯ï¼šç¼–辑态允许仅回显型号文本(历史数据可能缺少 productModelId)
  if (!isEdit.value && !form.productModelId) return toast('请选择规格型号'), false
  if (isEdit.value && !form.productModelId && !form.model) return toast('请选择规格型号'), false
  if (!form.batchNo) return toast('请输入批号'), false
  if (form.checkType === undefined || form.checkType === '') return toast('请选择检验类型'), false
  if (!form.checkName) return toast('请选择检验员'), false
src/pages/qualityManagement/nonconformingManagement/index.vue
@@ -54,8 +54,8 @@
          </view>
          <view class="card-actions">
            <view class="btn-link btn-link-primary" v-if="item.inspectState == 0" @click.stop="openDealDialog(item)">处理</view>
            <view class="btn-link btn-link-plain" v-if="item.inspectState == 0" @click.stop="openForm('edit', item)">编辑</view>
            <view class="btn-link btn-link-warn" v-if="item.inspectState == 0" @click.stop="handleDelete(item)">删除</view>
            <view class="btn-link btn-link-plain" v-if="hasNonconformingEdit" @click.stop="openForm('edit', item)">编辑</view>
            <view class="btn-link btn-link-warn" v-if="hasNonconformingCancel" @click.stop="handleDelete(item)">删除</view>
          </view>
        </view>
        <view class="load-more-wrap">
@@ -136,7 +136,7 @@
    <up-datetime-picker
      :show="showEntryStartPicker"
      v-model="entryStartValue"
      mode="date"
      mode="year-month"
      @confirm="confirmEntryStart"
      @cancel="showEntryStartPicker = false"
    />
@@ -144,7 +144,7 @@
    <up-datetime-picker
      :show="showEntryEndPicker"
      v-model="entryEndValue"
      mode="date"
      mode="year-month"
      @confirm="confirmEntryEnd"
      @cancel="showEntryEndPicker = false"
    />
@@ -168,6 +168,10 @@
import dayjs from 'dayjs';
import PageHeader from '@/components/PageHeader.vue'
import { onReachBottom, onShow } from '@dcloudio/uni-app'
import { checkPermi } from '@/utils/permission'
const hasNonconformingEdit = computed(() => checkPermi(['nonconforming_edit']))
const hasNonconformingCancel = computed(() => checkPermi(['nonconforming_cancel']))
const searchForm = reactive({
  productName: '',
@@ -302,19 +306,19 @@
};
const openDateRange = () => {
  entryStartValue.value = searchForm.entryDateStart ? dayjs(searchForm.entryDateStart, 'YYYY-MM-DD').valueOf() : Date.now()
  entryStartValue.value = searchForm.entryDateStart ? dayjs(searchForm.entryDateStart).valueOf() : Date.now()
  showEntryStartPicker.value = true
}
const confirmEntryStart = (e) => {
  const ts = e?.value ?? entryStartValue.value
  searchForm.entryDateStart = dayjs(ts).format('YYYY-MM-DD')
  searchForm.entryDateStart = `${dayjs(ts).format('YYYY-MM')}-01`
  showEntryStartPicker.value = false
  entryEndValue.value = searchForm.entryDateEnd ? dayjs(searchForm.entryDateEnd, 'YYYY-MM-DD').valueOf() : Date.now()
  entryEndValue.value = searchForm.entryDateEnd ? dayjs(searchForm.entryDateEnd).valueOf() : Date.now()
  showEntryEndPicker.value = true
}
const confirmEntryEnd = (e) => {
  const ts = e?.value ?? entryEndValue.value
  searchForm.entryDateEnd = dayjs(ts).format('YYYY-MM-DD')
  searchForm.entryDateEnd = `${dayjs(ts).format('YYYY-MM')}-01`
  showEntryEndPicker.value = false
  handleQuery()
}
@@ -358,6 +362,7 @@
};
const handleDelete = (row) => {
  if (!hasNonconformingCancel.value) return
  showConfirm('确认删除该不合格记录吗?').then(async res => {
    if (!res.confirm) return
    await qualityUnqualifiedDel([row.id])
@@ -372,6 +377,7 @@
};
const openForm = (type, row) => {
  if (type === 'edit' && !hasNonconformingEdit.value) return
  if (type !== 'add' && row?.inspectState == 1) {
    toast('已处理的数据不能再编辑')
    return
src/pages/qualityManagement/rawMaterial/index.vue
@@ -63,7 +63,7 @@
          <up-button v-if="item.inspectState != 1" type="primary" size="mini" @click.stop="openForm('edit', item)">编辑</up-button>
          <up-button type="info" size="mini" @click.stop="openFiles(item)">附件</up-button>
          <up-button v-if="item.inspectState != 1" type="success" size="mini" @click.stop="handleConfirmSubmit(item)">提交</up-button>
          <up-button type="error" size="mini" @click.stop="handleDelete(item)">删除</up-button>
          <up-button v-if="hasRawCancel" type="error" size="mini" @click.stop="handleDelete(item)">删除</up-button>
        </view>
      </view>
      <view class="pagination-container">
@@ -110,8 +110,10 @@
} from '@/api/qualityManagement/rawMaterial.js';
import { toast, showConfirm } from '@/utils/common';
import useUserStore from '@/store/modules/user';
import { checkPermi } from '@/utils/permission';
const userStore = useUserStore();
const hasRawCancel = computed(() => checkPermi(['raw_cancel']));
const searchForm = reactive({
  batchNo: '',
@@ -201,6 +203,7 @@
};
const openForm = (type, item) => {
  if (type === 'edit' && !hasRawEdit.value) return
  const id = item?.id
  uni.navigateTo({
    url: `/pages/qualityManagement/rawMaterial/form?type=${type}${id ? `&id=${id}` : ''}`
@@ -219,6 +222,7 @@
};
const handleDelete = (row) => {
  if (!hasRawCancel.value) return
  showConfirm('确认删除该记录吗?').then(res => {
    if (res.confirm) {
      // å¯¹é½ PC ç«¯ï¼šåˆ é™¤æŽ¥å£æŽ¥æ”¶ id æ•°ç»„
src/utils/ruoyi.js
@@ -234,16 +234,36 @@
    let date;
    // å¤„理:如果是时间戳,先转为Date对象
    if (typeof dateSource === 'number') {
        date = new Date(dateSource);
        const timestamp = dateSource.toString().length === 10 ? dateSource * 1000 : dateSource;
        date = new Date(timestamp);
    }
    // å¤„理:如果是Date对象,直接使用
    else if (dateSource instanceof Date) {
        date = dateSource;
    }
    // å¤„理:如果是字符串,做兼容转换
    else if (typeof dateSource === 'string') {
        const raw = dateSource.trim();
        if (!raw) return '';
        if (/^[0-9]+$/.test(raw)) {
            const n = parseInt(raw, 10);
            const timestamp = raw.length === 10 ? n * 1000 : n;
            date = new Date(timestamp);
        } else {
            // iOS / éƒ¨åˆ† WebView å¯¹ YYYY-MM-DD å…¼å®¹è¾ƒå·®ï¼Œç»Ÿä¸€è½¬ /
            const normalized = raw
                .replace(new RegExp(/-/gm), '/')
                .replace('T', ' ')
                .replace(new RegExp(/\.[\d]{3}/gm), '');
            date = new Date(normalized);
        }
    }
    // å¼‚常情况:返回空
    else {
        return '';
    }
    if (!(date instanceof Date) || Number.isNaN(date.getTime())) return '';
    
    // è¡¥é›¶å‡½æ•°ï¼šç¡®ä¿æœˆ/日是两位数
    const padZero = (num) => num.toString().padStart(2, '0');