已添加2个文件
已修改15个文件
567 ■■■■ 文件已修改
src/pages.json 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/consumablesLogistics/stockManagement/add.vue 36 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/consumablesLogistics/stockManagement/index.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/consumablesLogistics/stockManagement/subtract.vue 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/consumablesLogistics/stockManagement/view.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/consumablesLogistics/stockReport/index.vue 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/collaborativeApproval/index.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/collaborativeApproval/index9.vue 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/index.vue 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/indexItem.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/inventoryManagement/stockManagement/add.vue 98 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/inventoryManagement/stockManagement/subtract.vue 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/inventoryManagement/stockManagement/view.vue 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/inventoryManagement/stockReport/index.vue 131 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/qualityManagement/nonconformingManagement/index.vue 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/qualityManagement/rawMaterial/files.vue 130 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/icon/yuanliaoguanli@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/pages.json
@@ -317,6 +317,13 @@
      }
    },
    {
      "path": "pages/cooperativeOffice/collaborativeApproval/index9",
      "style": {
        "navigationBarTitleText": "原料管理",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/managementMeetings/meetingSettings/index",
      "style": {
        "navigationBarTitleText": "会议设置",
src/pages/consumablesLogistics/stockManagement/add.vue
@@ -26,23 +26,23 @@
      <view class="form-section">
        <view class="section-title">过磅信息</view>
        <view class="form-row">
          <text class="form-label">车牌号</text>
          <text class="form-label required">车牌号</text>
          <up-input v-model="form.licensePlateNo" placeholder="请输入车牌号" />
        </view>
        <view class="form-row">
          <text class="form-label">毛重(吨)</text>
          <up-input v-model="form.grossWeight" type="number" placeholder="请输入毛重" />
          <text class="form-label required">毛重(吨)</text>
          <up-input v-model="form.grossWeight" type="digit" placeholder="请输入毛重" />
        </view>
        <view class="form-row">
          <text class="form-label">皮重(吨)</text>
          <up-input v-model="form.tareWeight" type="number" placeholder="请输入皮重" />
          <text class="form-label required">皮重(吨)</text>
          <up-input v-model="form.tareWeight" type="digit" placeholder="请输入皮重" />
        </view>
        <view class="form-row">
          <text class="form-label">净重(吨)</text>
          <up-input v-model="form.netWeight" type="number" disabled placeholder="自动计算" />
          <up-input v-model="form.netWeight" type="digit" disabled placeholder="自动计算" />
        </view>
        <view class="form-row">
          <text class="form-label">过磅日期</text>
          <text class="form-label required">过磅日期</text>
          <view class="selector-trigger" @click="openWeighingDatePicker">
            <text class="selector-text" :class="{ placeholder: !form.weighingDate }">
              {{ form.weighingDate || "请选择过磅日期" }}
@@ -51,7 +51,7 @@
          </view>
        </view>
        <view class="form-row">
          <text class="form-label">过磅员</text>
          <text class="form-label required">过磅员</text>
          <up-input v-model="form.weighingOperator" placeholder="请输入过磅员" />
        </view>
      </view>
@@ -220,6 +220,26 @@
    uni.showToast({ title: "请选择产品", icon: "none" });
    return;
  }
  if (!form.licensePlateNo) {
    uni.showToast({ title: "请输入车牌号", icon: "none" });
    return;
  }
  if (!form.grossWeight || Number(form.grossWeight) <= 0) {
    uni.showToast({ title: "请输入毛重", icon: "none" });
    return;
  }
  if (!form.tareWeight || Number(form.tareWeight) <= 0) {
    uni.showToast({ title: "请输入皮重", icon: "none" });
    return;
  }
  if (!form.weighingDate) {
    uni.showToast({ title: "请选择过磅日期", icon: "none" });
    return;
  }
  if (!form.weighingOperator) {
    uni.showToast({ title: "请输入过磅员", icon: "none" });
    return;
  }
  const payload = {
    productId: form.productId,
    productModelId: form.productModelId,
src/pages/consumablesLogistics/stockManagement/index.vue
@@ -231,13 +231,10 @@
const goDetail = (item) => {
  if (!item) return;
  try {
    uni.setStorageSync(
      "stockDetailItem",
      JSON.stringify({
        item,
        type: isQualified() ? "0" : "1",
      })
    );
    uni.setStorageSync('stockDetailItem', JSON.stringify({
      item,
      type: '0'
    }))
  } catch (e) {}
  if (!item.id) {
    uni.navigateTo({ url: "/pages/consumablesLogistics/stockManagement/view" });
src/pages/consumablesLogistics/stockManagement/subtract.vue
@@ -31,15 +31,15 @@
        </view>
        <view class="form-row">
          <text class="form-label">毛重(吨)</text>
          <up-input v-model="form.grossWeight" type="number" placeholder="请输入毛重" />
          <up-input v-model="form.grossWeight" type="digit" placeholder="请输入毛重" />
        </view>
        <view class="form-row">
          <text class="form-label">皮重(吨)</text>
          <up-input v-model="form.tareWeight" type="number" placeholder="请输入皮重" />
          <up-input v-model="form.tareWeight" type="digit" placeholder="请输入皮重" />
        </view>
        <view class="form-row">
          <text class="form-label">净重(吨)</text>
          <up-input v-model="form.netWeight" type="number" disabled placeholder="自动计算" />
          <up-input v-model="form.netWeight" type="digit" disabled placeholder="自动计算" />
        </view>
        <view class="form-row">
          <text class="form-label">过磅日期</text>
@@ -160,18 +160,6 @@
  if (!outNum || outNum <= 0 || outNum > Number(stockRecord.unLockedQuantity)) {
    uni.showToast({ title: `请输入 1~${stockRecord.unLockedQuantity} ä¹‹é—´çš„æ•°é‡`, icon: "none" });
    return;
  }
  const net = Number(form.netWeight);
  if (!isNaN(net) && net > 0) {
    const max = Number(stockRecord.unLockedQuantity) || 0;
    if (max > 0 && net > max) {
      uni.showToast({ title: `净重不能大于可用库存 ${max}`, icon: "none" });
      return;
    }
    if (net > outNum) {
      uni.showToast({ title: `净重不能大于出库数量 ${outNum}`, icon: "none" });
      return;
    }
  }
  subtractConsumablesIn({
    id: stockRecord.id,
src/pages/consumablesLogistics/stockManagement/view.vue
@@ -12,8 +12,8 @@
        </view>
        <view class="section-body">
          <view class="detail-row">
            <text class="label">序号</text>
            <text class="value">{{ detail.index ?? '-' }}</text>
            <text class="label">产品类型</text>
            <text class="value">{{ detail.parentName ?? '-' }}</text>
          </view>
          <view class="detail-row">
            <text class="label">产品大类</text>
@@ -47,6 +47,10 @@
            <text class="label">最近更新时间</text>
            <text class="value">{{ detail.updateTime || '-' }}</text>
          </view>
          <view class="detail-row">
            <text class="label">备注</text>
            <text class="value">{{ detail.remark || '-' }}</text>
          </view>
        </view>
      </view>
    </view>
@@ -69,6 +73,7 @@
  const d = typeof raw === "object" ? raw : {};
  return {
    index: d.index ?? 1,
    parentName: d.parentName,
    productName: d.productName,
    model: d.model,
    unit: d.unit,
@@ -77,6 +82,7 @@
    unLockedQuantity: d.unLockedQuantity ?? (d.qualitity - (d.lockedQuantity || 0)),
    updateTime: d.updateTime,
    typeLabel: "合格库存",
    remark: d.remark,
  };
}
src/pages/consumablesLogistics/stockReport/index.vue
@@ -33,6 +33,8 @@
            <view class="row" v-if="searchForm.reportType === 'inout'"><text class="l">出库数量</text><text class="r">{{ item.totalStockOut }}</text></view>
            <view class="row"><text class="l">现在库存</text><text class="r highlight">{{ item.currentStock }}</text></view>
            <view class="row" v-if="item.createBy"><text class="l">入库人</text><text class="r">{{ item.createBy }}</text></view>
            <view class="row" v-if="item.currentWeight"><text class="l">现净重(吨)</text><text class="r">{{ item.currentWeight }}</text></view>
            <view class="row" v-if="item.recordType"><text class="l">来源</text><text class="r">{{ getRecordType(item.recordType) }}</text></view>
          </view>
        </view>
        <view class="load-more-wrap">
@@ -59,7 +61,9 @@
import { formatDateToYMD } from "@/utils/ruoyi";
import { onShow, onReachBottom } from "@dcloudio/uni-app";
import { getConsumablesInReportList, getConsumablesInInAndOutReportList } from "@/api/consumablesLogistics/consumablesIn.js";
import {
  findAllQualifiedStockInRecordTypeOptions,
} from "@/api/basicData/enum.js";
const reportTypes = [
  { label: "日报", value: "daily" },
  { label: "月报", value: "monthly" },
@@ -113,6 +117,20 @@
  }
  return p;
};
const stockRecordTypeOptions = ref([])
const getRecordType = (recordType) => {
  return stockRecordTypeOptions.value.find(item => item.value === recordType)?.label || ''
}
// èŽ·å–æ¥æºç±»åž‹é€‰é¡¹
const fetchStockRecordTypeOptions = () => {
  findAllQualifiedStockInRecordTypeOptions()
      .then(res => {
        stockRecordTypeOptions.value = res.data;
      })
}
const getList = () => {
  const isFirstPage = page.current === 1;
@@ -207,6 +225,7 @@
onShow(() => {
  initDefaultDates();
  handleQuery();
  fetchStockRecordTypeOptions();
});
onReachBottom(() => loadMore());
src/pages/cooperativeOffice/collaborativeApproval/index.vue
@@ -100,7 +100,7 @@
                  <u-button type="primary"
                            size="small"
                            class="action-btn edit"
                            :disabled="item.approveStatus == 2 || item.approveStatus == 1 || item.approveStatus == 4 || item.approveStatus == 8"
                            :disabled="item.approveStatus == 2 || item.approveStatus == 1 || item.approveStatus == 4 || item.approveStatus == 8 || approveType == 9"
                            @click="handleItemClick(item)">
                    ç¼–辑
                  </u-button>
@@ -124,7 +124,7 @@
    </view>
    <!-- æµ®åŠ¨æ“ä½œæŒ‰é’® -->
    <view class="fab-button"
          v-if="props.approveType != 5 && props.approveType != 6 && props.approveType != 7"
          v-if="props.approveType != 5 && props.approveType != 6 && props.approveType != 7 && props.approveType != 9"
          @click="handleAdd">
      <up-icon name="plus"
               size="24"
@@ -159,6 +159,7 @@
      6: "报价管理",
      7: "发货审批",
      8: "危险作业审批",
      9: "原料管理",
    };
    return titleMap[type] || "审批管理";
  };
src/pages/cooperativeOffice/collaborativeApproval/index9.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
<template>
  <view class="container">
    <!-- å¼•å…¥index.vue组件并传递参数 -->
    <ApprovalProcessIndex :approveType="9" />
  </view>
</template>
<script setup>
import ApprovalProcessIndex from './index.vue'
</script>
<style scoped>
.container {
  width: 100%;
  height: 100%;
}
</style>
src/pages/index.vue
@@ -709,6 +709,11 @@
          url: "/pages/cooperativeOffice/collaborativeApproval/index7",
        });
        break;
      case "原料管理":
        uni.navigateTo({
          url: "/pages/cooperativeOffice/collaborativeApproval/index9",
        });
        break;
      case "会议设置":
        uni.navigateTo({
          url: "/pages/managementMeetings/meetingSettings/index",
@@ -1208,9 +1213,9 @@
    // è´¨é‡ç®¡ç†èœå•:固定只展示 3 ä¸ªå…¥å£
    const originalQuality = [
      { icon: "/static/images/icon/caigoutaizhang@2x.png", label: "检测项维护" },
      { icon: "/static/images/icon/shengchanhesuan@2x.png", label: "检测项维护" },
      { icon: "/static/images/icon/caigoutaizhang@2x.png", label: "原料检" },
      { icon: "/static/images/icon/caigoutaizhang@2x.png", label: "不合格品管理" },
      { icon: "/static/images/icon/shengchandingdan@2x.png", label: "不合格品管理" },
    ];
    qualityItems.splice(0, qualityItems.length, ...originalQuality);
src/pages/indexItem.vue
@@ -85,6 +85,10 @@
      icon: "/static/images/icon/baoxiaoguanli.png",
      label: "报销管理",
    },
    {
      icon: "/static/images/icon/yuanliaoguanli@2x.png",
      label: "原料管理",
    },
    // {
    //   icon: "/static/images/icon/caigouguanli.png",
    //   label: "采购审批",
@@ -229,6 +233,11 @@
          url: "/pages/cooperativeOffice/collaborativeApproval/index7",
        });
        break;
      case "原料管理":
        uni.navigateTo({
          url: "/pages/cooperativeOffice/collaborativeApproval/index9",
        });
        break;
      case "会议设置":
        uni.navigateTo({
          url: "/pages/managementMeetings/meetingSettings/index",
src/pages/inventoryManagement/stockManagement/add.vue
@@ -23,26 +23,34 @@
        </view>
      </view>
      <!-- è¿‡ç£…相关字段 -->
      <view class="form-section">
      <!-- æˆå“ï¼šåªéœ€è¦æ•°é‡ -->
      <view v-if="isFinishedProduct" class="form-section">
        <view class="form-row">
          <text class="form-label required">数量</text>
          <up-input v-model="form.qualitity" type="number" placeholder="请输入数量" />
        </view>
      </view>
      <!-- åŽŸææ–™ï¼šè¿‡ç£…ç›¸å…³å­—æ®µ -->
      <view v-else class="form-section">
        <view class="section-title">过磅信息</view>
        <view class="form-row">
          <text class="form-label">车牌号</text>
          <text class="form-label required">车牌号</text>
          <up-input v-model="form.licensePlateNo" placeholder="请输入车牌号" />
        </view>
        <view class="form-row">
          <text class="form-label">毛重(吨)</text>
          <text class="form-label required">毛重(吨)</text>
          <up-input
            v-model="form.grossWeight"
            type="number"
            type="digit"
            placeholder="请输入毛重"
          />
        </view>
        <view class="form-row">
          <text class="form-label">皮重(吨)</text>
          <text class="form-label required">皮重(吨)</text>
          <up-input
            v-model="form.tareWeight"
            type="number"
            type="digit"
            placeholder="请输入皮重"
          />
        </view>
@@ -50,13 +58,13 @@
          <text class="form-label">净重(吨)</text>
          <up-input
            v-model="form.netWeight"
            type="number"
            type="digit"
            disabled
            placeholder="自动计算"
          />
        </view>
        <view class="form-row">
          <text class="form-label">过磅日期</text>
          <text class="form-label required">过磅日期</text>
          <view class="selector-trigger" @click="openWeighingDatePicker">
            <text class="selector-text" :class="{ placeholder: !form.weighingDate }">
              {{ form.weighingDate || '请选择过磅日期' }}
@@ -65,16 +73,12 @@
          </view>
        </view>
        <view class="form-row">
          <text class="form-label">过磅员</text>
          <text class="form-label required">过磅员</text>
          <up-input v-model="form.weighingOperator" placeholder="请输入过磅员" />
        </view>
      </view>
      <view class="form-section">
        <!-- <view class="form-row">
          <text class="form-label required">数量</text>
          <up-input v-model="form.qualitity" type="number" placeholder="请输入数量" />
        </view> -->
        <view class="form-row">
          <text class="form-label">备注</text>
          <up-input v-model="form.remark" type="textarea" placeholder="选填" />
@@ -154,6 +158,7 @@
  productModelName: '',
  unit: '',
  productType: undefined,
  parentName: '',
  licensePlateNo: '',
  grossWeight: '',
  tareWeight: '',
@@ -166,6 +171,7 @@
const type = ref('0') // å›ºå®šåˆæ ¼åº“å­˜
const isQualified = computed(() => true)
const isFinishedProduct = computed(() => form.parentName === '成品')
const showProductPopup = ref(false)
const productQuery = reactive({
@@ -223,6 +229,19 @@
  form.productModelName = item.model
  form.unit = item.unit
  form.productType = item.productType
  form.parentName = parentName
  // åˆ‡æ¢äº§å“åŽï¼ŒæŒ‰ç±»åž‹æ¸…理无关字段
  if (parentName === '成品') {
    form.licensePlateNo = ''
    form.grossWeight = ''
    form.tareWeight = ''
    form.netWeight = ''
    form.weighingDate = ''
    form.weighingOperator = ''
  } else {
    form.qualitity = ''
  }
  showProductPopup.value = false
}
@@ -264,25 +283,54 @@
    uni.showToast({ title: '请选择产品', icon: 'none' })
    return
  }
  // if (!form.qualitity || Number(form.qualitity) <= 0) {
  //   uni.showToast({ title: '请输入数量', icon: 'none' })
  //   return
  // }
  const payload = {
  if (isFinishedProduct.value) {
    if (!form.qualitity || Number(form.qualitity) <= 0) {
      uni.showToast({ title: '请输入数量', icon: 'none' })
      return
    }
  } else {
    if (!form.licensePlateNo) {
      uni.showToast({ title: '请输入车牌号', icon: 'none' })
      return
    }
    if (!form.grossWeight || Number(form.grossWeight) <= 0) {
      uni.showToast({ title: '请输入毛重', icon: 'none' })
      return
    }
    if (!form.tareWeight || Number(form.tareWeight) <= 0) {
      uni.showToast({ title: '请输入皮重', icon: 'none' })
      return
    }
    if (!form.weighingDate) {
      uni.showToast({ title: '请选择过磅日期', icon: 'none' })
      return
    }
    if (!form.weighingOperator) {
      uni.showToast({ title: '请输入过磅员', icon: 'none' })
      return
    }
  }
  const base = {
    productId: form.productId,
    productModelId: form.productModelId,
    productName: form.productName,
    productModelName: form.productModelName,
    unit: form.unit,
    productType: form.productType,
    licensePlateNo: form.licensePlateNo,
    grossWeight: form.grossWeight,
    tareWeight: form.tareWeight,
    netWeight: form.netWeight,
    weighingDate: form.weighingDate,
    weighingOperator: form.weighingOperator,
    remark: form.remark
  }
  const payload = isFinishedProduct.value
    ? { ...base, qualitity: Number(form.qualitity) }
    : {
        ...base,
        licensePlateNo: form.licensePlateNo,
        grossWeight: form.grossWeight,
        tareWeight: form.tareWeight,
        netWeight: form.netWeight,
        weighingDate: form.weighingDate,
        weighingOperator: form.weighingOperator
      }
  createStockInventory(payload)
    .then(() => {
      uni.showToast({ title: '新增成功', icon: 'success' })
src/pages/inventoryManagement/stockManagement/subtract.vue
@@ -28,7 +28,7 @@
          <text class="form-label">毛重(吨)</text>
          <up-input
            v-model="form.grossWeight"
            type="number"
            type="digit"
            placeholder="请输入毛重"
          />
        </view>
@@ -36,7 +36,7 @@
          <text class="form-label">皮重(吨)</text>
          <up-input
            v-model="form.tareWeight"
            type="number"
            type="digit"
            placeholder="请输入皮重"
          />
        </view>
@@ -44,7 +44,7 @@
          <text class="form-label">净重(吨)</text>
          <up-input
            v-model="form.netWeight"
            type="number"
            type="digit"
            disabled
            placeholder="自动计算"
          />
@@ -127,12 +127,6 @@
const showWeighingDatePicker = ref(false)
const weighingDateValue = ref(Date.now())
const maxAllowedNetWeight = computed(() => {
  const v = form.unLockedQuantity ?? form.qualitity
  const n = Number(v)
  return !isNaN(n) ? n : 0
})
onLoad((options) => {
  type.value = '0'
  const cached = uni.getStorageSync('stockSubtractRecord')
@@ -184,11 +178,6 @@
const handleSubmit = () => {
  if (!form.id) {
    uni.showToast({ title: '记录信息缺失,无法出库', icon: 'none' })
    return
  }
  const net = Number(form.netWeight)
  if (!isNaN(net) && net > 0 && maxAllowedNetWeight.value > 0 && net > maxAllowedNetWeight.value) {
    uni.showToast({ title: `净重不能大于可用库存 ${maxAllowedNetWeight.value}`, icon: 'none' })
    return
  }
  const payload = { ...form }
src/pages/inventoryManagement/stockManagement/view.vue
@@ -13,8 +13,8 @@
        </view>
        <view class="section-body">
          <view class="detail-row">
            <text class="label">序号</text>
            <text class="value">{{ detail.index ?? '-' }}</text>
            <text class="label">产品类型</text>
            <text class="value">{{ detail.parentName || '-' }}</text>
          </view>
          <view class="detail-row">
            <text class="label">产品大类</text>
@@ -55,6 +55,19 @@
          </view>
        </view>
      </view>
      <view class="section-card">
        <view class="section-head">
          <view class="section-dot"></view>
          <text class="section-title">其他信息</text>
        </view>
        <view class="section-body">
          <view class="detail-row">
            <text class="label">备注</text>
            <text class="value value-num">{{ detail.remark ?? '-' }}</text>
          </view>
        </view>
      </view>
    </view>
    <view v-else class="empty">
      <text class="empty-text">暂无详情数据</text>
@@ -75,13 +88,15 @@
  const d = typeof raw === 'object' ? raw : {}
  return {
    index: d.index ?? 1,
    parentName: d.parentName,
    productName: d.productName,
    model: d.model,
    unit: d.unit,
    qualitity: d.qualitity,
    lockedQuantity: d.lockedQuantity,
    unLockedQuantity: d.unLockedQuantity,
    updateTime: d.updateTime
    updateTime: d.updateTime,
    remark: d.remark,
  }
}
src/pages/inventoryManagement/stockReport/index.vue
@@ -15,7 +15,37 @@
      </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">
@@ -39,6 +69,8 @@
            <view class="row" v-if="searchForm.reportType === 'inout'"><text class="l">出库数量</text><text class="r">{{ item.totalStockOut }}</text></view>
            <view class="row"><text class="l">现在库存</text><text class="r highlight">{{ item.currentStock }}</text></view>
            <view class="row" v-if="item.createBy"><text class="l">入库人</text><text class="r">{{ item.createBy }}</text></view>
            <view class="row" v-if="item.currentWeight"><text class="l">现净重(吨)</text><text class="r">{{ item.currentWeight }}</text></view>
            <view class="row" v-if="item.recordType"><text class="l">来源</text><text class="r">{{ getRecordType(item.recordType) }}</text></view>
          </view>
        </view>
        <view class="load-more-wrap">
@@ -52,6 +84,7 @@
      <up-datetime-picker
        v-model="dateValue"
        :mode="datePickerMode"
        :show="showDatePicker"
        @confirm="onDateConfirm"
        @cancel="showDatePicker = false"
      />
@@ -64,11 +97,12 @@
import dayjs from 'dayjs'
import PageHeader from '@/components/PageHeader.vue'
import { formatDateToYMD } from '@/utils/ruoyi'
import { onShow, onReachBottom } from '@dcloudio/uni-app'
import { onShow } from '@dcloudio/uni-app'
import {
  getStockInventoryReportList,
  getStockInventoryInAndOutReportList
} from '@/api/inventoryManagement/stockInventory.js'
import {findAllQualifiedStockInRecordTypeOptions} from "@/api/basicData/enum";
const reportTypes = [
  { label: '日报', value: 'daily' },
@@ -88,13 +122,14 @@
    startMonth: '',
    endMonth: '',
    startDate: '',
    endDate: ''
    endDate: '',
  }
})
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'
})
@@ -112,16 +147,27 @@
  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 + '-01'
    p.endMonth = searchForm.value.endMonth + '-01'
  } else {
    p.startDate = searchForm.value.startDate
    p.endDate = searchForm.value.endDate
    p.startMonth = searchForm.value.startDate
    p.endMonth = searchForm.value.endDate
  }
  return p
}
const stockRecordTypeOptions = ref([])
const getRecordType = (recordType) => {
  return stockRecordTypeOptions.value.find(item => item.value === recordType)?.label || ''
}
// èŽ·å–æ¥æºç±»åž‹é€‰é¡¹
const fetchStockRecordTypeOptions = () => {
  findAllQualifiedStockInRecordTypeOptions()
      .then(res => {
        stockRecordTypeOptions.value = res.data;
      })
}
const getList = () => {
@@ -171,11 +217,25 @@
}
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
  datePickerTarget.value = target
  switch (target) {
    case 'single':
      val = searchForm.value.singleDate
      break
    case 'startMonth':
      val = searchForm.value.startMonth
      break
    case 'endMonth':
      val = searchForm.value.endMonth
      break
    case 'startDate':
      val = searchForm.value.startDate
      break
    case 'endDate':
      val = searchForm.value.endDate
      break
  }
  dateValue.value = val ? new Date(val).getTime() : Date.now()
  showDatePicker.value = true
}
@@ -183,11 +243,33 @@
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()
  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()
  }
}
// åˆå§‹åŒ–:日报默认今天,月报默认本月,进出存默认最近7天
@@ -197,10 +279,8 @@
    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')
@@ -219,6 +299,7 @@
onShow(() => {
  initDefaultDates()
  handleQuery()
  fetchStockRecordTypeOptions()
})
const goBack = () => uni.navigateBack()
@@ -271,4 +352,4 @@
.card-body .l { color: #666; } .card-body .r { color: #333; } .card-body .r.highlight { color: #2979ff; font-weight: 500; }
.no-data { text-align: center; padding: 60rpx 0; color: #999; font-size: 28rpx; }
.load-more-wrap { padding: 24rpx 0 8rpx; }
</style>
</style>
src/pages/qualityManagement/nonconformingManagement/index.vue
@@ -55,7 +55,7 @@
          <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" @click.stop="handleDelete(item)">删除</view>
            <view class="btn-link btn-link-warn" v-if="item.inspectState == 0" @click.stop="handleDelete(item)">删除</view>
          </view>
        </view>
        <view class="load-more-wrap">
@@ -243,9 +243,14 @@
  return types[String(type ?? '')] || '-';
};
const getList = () => {
const getList = (force = false) => {
  const isFirstPage = page.current === 1
  if (loadStatus.value === 'loading' || (!isFirstPage && page.total > 0 && tableData.value.length >= page.total)) return
  if (
    !force &&
    (loadStatus.value === 'loading' ||
      (!isFirstPage && page.total > 0 && tableData.value.length >= page.total))
  )
    return
  loadStatus.value = 'loading'
  const params = {
@@ -288,7 +293,7 @@
  page.total = 0;
  tableData.value = [];
  loadStatus.value = 'loadmore';
  getList();
  getList(true);
};
const selectType = (e) => {
@@ -353,14 +358,12 @@
};
const handleDelete = (row) => {
  showConfirm('确认删除该不合格记录吗?').then(res => {
    if (res.confirm) {
      qualityUnqualifiedDel([row.id]).then(() => {
        toast('删除成功');
        handleQuery();
      });
    }
  });
  showConfirm('确认删除该不合格记录吗?').then(async res => {
    if (!res.confirm) return
    await qualityUnqualifiedDel([row.id])
    toast('删除成功')
    handleQuery()
  })
};
const confirmDate = (e) => {
src/pages/qualityManagement/rawMaterial/files.vue
@@ -18,7 +18,7 @@
    </view>
    <view class="upload-bar">
      <view class="btn-upload" @click="chooseFile">上传附件</view>
      <view class="btn-upload" @tap="chooseFile">上传附件</view>
    </view>
  </view>
</template>
@@ -48,15 +48,46 @@
    uni.showToast({ title: '缺少检验记录ID', icon: 'none' })
    return
  }
  uni.chooseFile({
    count: 1,
    success: (res) => {
      const path = res?.tempFiles?.[0]?.path
      const name = res?.tempFiles?.[0]?.name
      if (!path) return
      uploadOne(path, name)
  const pickByChooseFile = () => {
    if (typeof uni.chooseFile !== 'function') return false
    uni.chooseFile({
      count: 1,
      success: (res) => {
        const file = res?.tempFiles?.[0]
        const path = file?.path
        const name = file?.name
        if (!path) return
        uploadOne(path, name)
      },
      fail: () => {
        // chooseFile åœ¨éƒ¨åˆ† App æœºåž‹/基座可能会失败,做降级
        pickByChooseImage()
      }
    })
    return true
  }
  const pickByChooseImage = () => {
    if (typeof uni.chooseImage !== 'function') {
      uni.showToast({ title: '当前版本不支持选择文件', icon: 'none' })
      return
    }
  })
    uni.chooseImage({
      count: 1,
      success: (res) => {
        const path = res?.tempFilePaths?.[0]
        if (!path) return
        uploadOne(path, '图片附件')
      },
      fail: () => {
        uni.showToast({ title: '选择失败', icon: 'none' })
      }
    })
  }
  // App/H5 ç»Ÿä¸€èµ° chooseFile,失败时降级 chooseImage
  const ok = pickByChooseFile()
  if (!ok) pickByChooseImage()
}
const uploadOne = (filePath, originalName) => {
@@ -97,12 +128,69 @@
}
const previewFile = (f) => {
  const url = f?.url
  const rawUrl = f?.url
  if (!rawUrl) return
  const url = normalizeUrl(rawUrl)
  const name = f?.name || '附件'
  if (!url) return
  // H5/APP ç»Ÿä¸€ç”¨å¤–部打开
  uni.navigateTo({
    url: `/pages/inspectionUpload/filePreview?url=${encodeURIComponent(url)}`
  // å›¾ç‰‡ä¼˜å…ˆç”¨åŽŸç”Ÿé¢„è§ˆ
  if (isImageUrl(url) || isImageName(name)) {
    uni.previewImage({ urls: [url] })
    return
  }
  // éžå›¾ç‰‡ï¼šä¸‹è½½åŽæ‰“å¼€
  const token = getToken()
  uni.showLoading({ title: '打开中...', mask: true })
  uni.downloadFile({
    url,
    header: token ? { Authorization: 'Bearer ' + token } : undefined,
    success: (res) => {
      const filePath = res?.tempFilePath
      if (!filePath) {
        uni.hideLoading()
        uni.showToast({ title: '打开失败', icon: 'none' })
        return
      }
      uni.openDocument({
        filePath,
        showMenu: true,
        success: () => {
          uni.hideLoading()
        },
        fail: () => {
          uni.hideLoading()
          uni.showToast({ title: '打开失败', icon: 'none' })
        }
      })
    },
    fail: () => {
      uni.hideLoading()
      uni.showToast({ title: '下载失败', icon: 'none' })
    }
  })
}
const normalizeUrl = (u) => {
  const s = String(u || '').trim()
  if (!s) return ''
  if (s.startsWith('http://') || s.startsWith('https://')) return s
  if (s.startsWith('//')) return 'https:' + s
  // ç³»ç»Ÿé‡Œé™„件预览/下载一般走 fileUrl
  if (s.startsWith('/')) return `${config.fileUrl}${s}`
  return `${config.fileUrl}/${s.replace(/^\//, '')}`
}
const isImageName = (name) => {
  const n = String(name || '').toLowerCase()
  return /\.(png|jpe?g|gif|bmp|webp|heic)$/.test(n)
}
const isImageUrl = (url) => {
  const u = String(url || '').toLowerCase()
  return /\.(png|jpe?g|gif|bmp|webp|heic)(\?|#|$)/.test(u)
}
const confirmDelete = (f) => {
@@ -148,12 +236,22 @@
.file-list { margin: 24rpx; background: #fff; border-radius: 16rpx; padding: 12rpx 24rpx; }
.file-item { padding: 20rpx 0; border-bottom: 1rpx solid #eee; display: flex; justify-content: space-between; align-items: center; gap: 16rpx; }
.file-item:last-child { border-bottom: 0; }
.file-name { font-size: 28rpx; color: #333; }
.file-actions { display: flex; gap: 20rpx; }
.file-info { flex: 1; min-width: 0; }
.file-name {
  display: block;
  width: 420rpx;
  max-width: 100%;
  font-size: 28rpx;
  color: #333;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.file-actions { display: flex; gap: 20rpx; flex-shrink: 0; }
.btn-link { color: #2979ff; font-size: 26rpx; }
.btn-link.danger { color: #f56c6c; }
.empty { text-align: center; padding: 60rpx 0; color: #999; font-size: 28rpx; }
.upload-bar { position: fixed; left: 0; right: 0; bottom: 0; padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom)); background: #fff; box-shadow: 0 -4rpx 16rpx rgba(0,0,0,0.04); }
.upload-bar { position: fixed; left: 0; right: 0; bottom: 0; padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom)); background: #fff; box-shadow: 0 -4rpx 16rpx rgba(0,0,0,0.04); z-index: 20; }
.btn-upload { height: 88rpx; border-radius: 999rpx; background: #2979ff; color: #fff; font-size: 30rpx; display: flex; align-items: center; justify-content: center; }
</style>
src/static/images/icon/yuanliaoguanli@2x.png