spring
2025-09-17 265820c7f0df74a1c69308dcc2b1b61812ccd892
Merge remote-tracking branch 'origin/dev' into dev
已添加12个文件
已修改6个文件
4482 ■■■■■ 文件已修改
src/api/procurementManagement/advancedPriceManagement.js 302 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/procurementManagement/arrivalManagement.js 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/procurementManagement/procurementPlan.js 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/procurementManagement/returnManagement.js 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/biaoti.png 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/user.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inspectionManagement/components/formDia.vue 174 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inspectionManagement/components/qrCodeDia.vue 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inspectionManagement/components/viewFiles.vue 246 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inspectionManagement/components/viewQrCodeFiles.vue 169 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inspectionManagement/index.vue 391 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/advancedPriceManagement/index.vue 874 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/arrivalManagement/index.vue 149 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementPlan/index.vue 821 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementReport/index.vue 847 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/returnManagement/index.vue 144 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/index.vue 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/paymentShipping/index.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/procurementManagement/advancedPriceManagement.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,302 @@
// é«˜çº§é‡‡è´­ä»·æ ¼ç®¡ç†API接口
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢ä»·æ ¼åˆ—表
export function getPriceList(query) {
  return request({
    url: "/procurement/price/list",
    method: "get",
    params: query,
  });
}
// èŽ·å–ä»·æ ¼è¯¦æƒ…
export function getPriceDetail(id) {
  return request({
    url: `/procurement/price/detail/${id}`,
    method: "get",
  });
}
// æ–°å¢žä»·æ ¼
export function addPrice(data) {
  return request({
    url: "/procurement/price/add",
    method: "post",
    data: data,
  });
}
// æ›´æ–°ä»·æ ¼
export function updatePrice(data) {
  return request({
    url: "/procurement/price/update",
    method: "put",
    data: data,
  });
}
// åˆ é™¤ä»·æ ¼
export function deletePrice(id) {
  return request({
    url: `/procurement/price/delete/${id}`,
    method: "delete",
  });
}
// æ‰¹é‡åˆ é™¤ä»·æ ¼
export function batchDeletePrice(ids) {
  return request({
    url: "/procurement/price/batchDelete",
    method: "delete",
    data: { ids },
  });
}
// å¤åˆ¶ä»·æ ¼
export function copyPrice(id) {
  return request({
    url: `/procurement/price/copy/${id}`,
    method: "post",
  });
}
// åº”用价格(将待生效状态改为有效)
export function applyPrice(id) {
  return request({
    url: `/procurement/price/apply/${id}`,
    method: "put",
  });
}
// æš‚停价格
export function suspendPrice(id) {
  return request({
    url: `/procurement/price/suspend/${id}`,
    method: "put",
  });
}
// æ‰¹é‡è®¾ç½®æŠ˜æ‰£
export function batchSetDiscount(data) {
  return request({
    url: "/procurement/price/batchDiscount",
    method: "post",
    data: data,
  });
}
// èŽ·å–æŠ˜æ‰£é…ç½®
export function getDiscountConfig(id) {
  return request({
    url: `/procurement/price/discount/${id}`,
    method: "get",
  });
}
// è®¾ç½®å•个商品折扣
export function setDiscount(data) {
  return request({
    url: "/procurement/price/setDiscount",
    method: "post",
    data: data,
  });
}
// èŽ·å–é˜¶æ¢¯æŠ˜æ‰£é…ç½®
export function getTieredDiscount(id) {
  return request({
    url: `/procurement/price/tieredDiscount/${id}`,
    method: "get",
  });
}
// è®¾ç½®é˜¶æ¢¯æŠ˜æ‰£
export function setTieredDiscount(data) {
  return request({
    url: "/procurement/price/setTieredDiscount",
    method: "post",
    data: data,
  });
}
// èŽ·å–ä»·æ ¼æŽ§åˆ¶è®¾ç½®
export function getPriceControlConfig() {
  return request({
    url: "/procurement/price/controlConfig",
    method: "get",
  });
}
// æ›´æ–°ä»·æ ¼æŽ§åˆ¶è®¾ç½®
export function updatePriceControlConfig(data) {
  return request({
    url: "/procurement/price/controlConfig",
    method: "put",
    data: data,
  });
}
// èŽ·å–ä»·æ ¼é¢„è­¦åˆ—è¡¨
export function getPriceWarnings(query) {
  return request({
    url: "/procurement/price/warnings",
    method: "get",
    params: query,
  });
}
// å¤„理价格预警
export function handlePriceWarning(id, action) {
  return request({
    url: `/procurement/price/warning/${id}`,
    method: "put",
    data: { action },
  });
}
// èŽ·å–ä»·æ ¼åŽ†å²è®°å½•
export function getPriceHistory(id, query) {
  return request({
    url: `/procurement/price/history/${id}`,
    method: "get",
    params: query,
  });
}
// èŽ·å–ä»·æ ¼ç»Ÿè®¡æ•°æ®
export function getPriceStatistics(query) {
  return request({
    url: "/procurement/price/statistics",
    method: "get",
    params: query,
  });
}
// å¯¼å‡ºä»·æ ¼æ•°æ®
export function exportPriceData(query) {
  return request({
    url: "/procurement/price/export",
    method: "get",
    params: query,
    responseType: 'blob',
  });
}
// å¯¼å…¥ä»·æ ¼æ•°æ®
export function importPriceData(file) {
  const formData = new FormData();
  formData.append('file', file);
  return request({
    url: "/procurement/price/import",
    method: "post",
    data: formData,
    headers: {
      'Content-Type': 'multipart/form-data',
    },
  });
}
// èŽ·å–ä»·æ ¼æ¨¡æ¿
export function downloadPriceTemplate() {
  return request({
    url: "/procurement/price/template",
    method: "get",
    responseType: 'blob',
  });
}
// ä»·æ ¼å®¡æ‰¹
export function approvePrice(id, data) {
  return request({
    url: `/procurement/price/approve/${id}`,
    method: "put",
    data: data,
  });
}
// ä»·æ ¼é©³å›ž
export function rejectPrice(id, data) {
  return request({
    url: `/procurement/price/reject/${id}`,
    method: "put",
    data: data,
  });
}
// èŽ·å–ä¾›åº”å•†åˆ—è¡¨ï¼ˆç”¨äºŽä¸‹æ‹‰é€‰æ‹©ï¼‰
export function getSupplierOptions() {
  return request({
    url: "/procurement/price/suppliers",
    method: "get",
  });
}
// èŽ·å–å•†å“åˆ—è¡¨ï¼ˆç”¨äºŽä¸‹æ‹‰é€‰æ‹©ï¼‰
export function getProductOptions(query) {
  return request({
    url: "/procurement/price/products",
    method: "get",
    params: query,
  });
}
// èŽ·å–å•†å“è¯¦ç»†ä¿¡æ¯
export function getProductInfo(productId) {
  return request({
    url: `/procurement/price/productInfo/${productId}`,
    method: "get",
  });
}
// ä»·æ ¼æ¯”较分析
export function comparePrices(data) {
  return request({
    url: "/procurement/price/compare",
    method: "post",
    data: data,
  });
}
// èŽ·å–ä»·æ ¼è¶‹åŠ¿æ•°æ®
export function getPriceTrend(id, period) {
  return request({
    url: `/procurement/price/trend/${id}`,
    method: "get",
    params: { period },
  });
}
// ä»·æ ¼é¢„测
export function predictPrice(id, data) {
  return request({
    url: `/procurement/price/predict/${id}`,
    method: "post",
    data: data,
  });
}
// èŽ·å–å¸‚åœºä»·æ ¼å‚è€ƒ
export function getMarketPriceReference(productCode) {
  return request({
    url: `/procurement/price/marketRef/${productCode}`,
    method: "get",
  });
}
// ä»·æ ¼å˜åŠ¨é€šçŸ¥è®¾ç½®
export function updateNotificationSettings(data) {
  return request({
    url: "/procurement/price/notifications",
    method: "put",
    data: data,
  });
}
// èŽ·å–ä»·æ ¼å˜åŠ¨é€šçŸ¥è®¾ç½®
export function getNotificationSettings() {
  return request({
    url: "/procurement/price/notifications",
    method: "get",
  });
}
src/api/procurementManagement/arrivalManagement.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
// é”€å”®å°è´¦é¡µé¢æŽ¥å£
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function listPage(query) {
    return request({
        url: "/inboundManagement/listPage",
        method: "get",
        params: query,
    });
}
export function listPageCopy(query) {
    return request({
        url: "/inboundManagement/listPage",
        method: "get",
        params: query,
    });
}
// æ–°å¢ž
export function add(data) {
    return request({
        url: "/inboundManagement/add",
        method: "post",
        data
    });
}
// ä¿®æ”¹
export function update(data) {
    return request({
        url: "/inboundManagement/update",
        method: "post",
        data
    });
}
// åˆ é™¤é”€å”®å°è´¦
export function del(data) {
    return request({
        url: "/inboundManagement/del",
        method: "delete",
        data
    });
}
src/api/procurementManagement/procurementPlan.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,127 @@
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢é‡‡è´­è®¡åˆ’列表
export function getProcurementPlanList(query) {
  return request({
    url: "/procurement/plan/list",
    method: "get",
    params: query,
  });
}
// æ–°å¢žé‡‡è´­è®¡åˆ’
export function addProcurementPlan(data) {
  return request({
    url: "/procurement/plan/add",
    method: "post",
    data: data,
  });
}
// ä¿®æ”¹é‡‡è´­è®¡åˆ’
export function updateProcurementPlan(data) {
  return request({
    url: "/procurement/plan/update",
    method: "put",
    data: data,
  });
}
// åˆ é™¤é‡‡è´­è®¡åˆ’
export function deleteProcurementPlan(ids) {
  return request({
    url: "/procurement/plan/delete",
    method: "delete",
    data: ids,
  });
}
// æ ¹æ®ID获取采购计划详情
export function getProcurementPlanById(id) {
  return request({
    url: `/procurement/plan/${id}`,
    method: "get",
  });
}
// æ‰§è¡Œé‡‡è´­è®¡åˆ’计算
export function calculateProcurementPlan(data) {
  return request({
    url: "/procurement/plan/calculate",
    method: "post",
    data: data,
  });
}
// èŽ·å–äº§å“çŽ°æœ‰åº“å­˜ä¿¡æ¯
export function getProductExistingStock(productIds) {
  return request({
    url: "/inventory/existingStock/productInfo",
    method: "post",
    data: productIds,
  });
}
// èŽ·å–äº§å“å®‰å…¨åº“å­˜ä¿¡æ¯
export function getProductSafetyStock(productIds) {
  return request({
    url: "/inventory/safetyStock/productInfo",
    method: "post",
    data: productIds,
  });
}
// èŽ·å–äº§å“é¢„è®¡å‡ºåº“ä¿¡æ¯
export function getProductExpectedOutbound(productIds) {
  return request({
    url: "/inventory/expectedOutbound/productInfo",
    method: "post",
    data: productIds,
  });
}
// èŽ·å–äº§å“é¢„è®¡å…¥åº“ä¿¡æ¯
export function getProductExpectedInbound(productIds) {
  return request({
    url: "/inventory/expectedInbound/productInfo",
    method: "post",
    data: productIds,
  });
}
// å¯¼å‡ºé‡‡è´­è®¡åˆ’
export function exportProcurementPlan(query) {
  return request({
    url: "/procurement/plan/export",
    method: "get",
    params: query,
    responseType: "blob",
  });
}
// ç”Ÿæˆé‡‡è´­è®¢å•
export function generatePurchaseOrder(data) {
  return request({
    url: "/procurement/plan/generateOrder",
    method: "post",
    data: data,
  });
}
// éªŒè¯è®¡ç®—公式
export function validateFormula(formula) {
  return request({
    url: "/procurement/plan/validateFormula",
    method: "post",
    data: { formula },
  });
}
// èŽ·å–è®¡ç®—å…¬å¼æ¨¡æ¿
export function getFormulaTemplates() {
  return request({
    url: "/procurement/plan/formulaTemplates",
    method: "get",
  });
}
src/api/procurementManagement/returnManagement.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
// é”€å”®å°è´¦é¡µé¢æŽ¥å£
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function listPage(query) {
    return request({
        url: "/returnManagement/listPage",
        method: "get",
        params: query,
    });
}
// æ–°å¢ž
export function add(data) {
    return request({
        url: "/returnManagement/add",
        method: "post",
        data
    });
}
// ä¿®æ”¹
export function update(data) {
    return request({
        url: "/returnManagement/update",
        method: "post",
        data
    });
}
// åˆ é™¤é”€å”®å°è´¦
export function del(data) {
    return request({
        url: "/returnManagement/del",
        method: "delete",
        data
    });
}
src/assets/BI/biaoti.png

src/store/modules/user.js
@@ -2,6 +2,7 @@
import { getToken, setToken, removeToken } from '@/utils/auth'
import { isHttp, isEmpty } from "@/utils/validate"
import defAva from '@/assets/images/profile.jpg'
import { defineStore } from 'pinia'
const useUserStore = defineStore(
  'user',
src/views/inspectionManagement/components/formDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,174 @@
<template>
  <div>
    <el-dialog :title="operationType === 'add' ? '新增巡检任务' : '编辑巡检任务'"
               v-model="dialogVisitable" width="800px" @close="cancel">
      <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
        <el-row>
          <el-col :span="12">
            <el-form-item label="任务名称" prop="taskName">
              <el-input v-model="form.taskName" placeholder="请输入任务名称" maxlength="30" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="地点" prop="inspectionLocation">
              <el-input v-model="form.inspectionLocation" placeholder="请输入地点" maxlength="30" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="12">
            <el-form-item label="巡检人" prop="inspector">
              <el-select v-model="form.inspector" placeholder="请选择" multiple clearable>
                <el-option v-for="item in userList" :label="item.nickName" :value="item.userId" :key="item.userId"/>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="备注" prop="remarks">
              <el-input v-model="form.remarks" placeholder="请输入备注" type="textarea" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="12">
            <el-form-item label="任务频率" prop="frequencyType">
              <el-select v-model="form.frequencyType" placeholder="请选择" clearable>
                <el-option label="每日" value="DAILY"/>
                <el-option label="每周" value="WEEKLY"/>
                <el-option label="每月" value="MONTHLY"/>
                <el-option label="季度" value="QUARTERLY"/>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12" v-if="form.frequencyType === 'DAILY' && form.frequencyType">
            <el-form-item label="日期" prop="frequencyDetail">
              <el-time-picker v-model="form.frequencyDetail" placeholder="选择时间" format="HH:mm"
                              value-format="HH:mm" />
            </el-form-item>
          </el-col>
          <el-col :span="12" v-if="form.frequencyType === 'WEEKLY' && form.frequencyType">
            <el-form-item label="日期" prop="frequencyDetail">
              <el-select v-model="form.week" placeholder="请选择" clearable style="width: 50%">
                <el-option label="周一" value="MON"/>
                <el-option label="周二" value="TUE"/>
                <el-option label="周三" value="WED"/>
                <el-option label="周四" value="THU"/>
                <el-option label="周五" value="FRI"/>
                <el-option label="周六" value="SAT"/>
                <el-option label="周日" value="SUN"/>
              </el-select>
              <el-time-picker v-model="form.time" placeholder="选择时间" format="HH:mm"
                              value-format="HH:mm"  style="width: 50%"/>
            </el-form-item>
          </el-col>
          <el-col :span="12" v-if="form.frequencyType === 'MONTHLY' && form.frequencyType">
            <el-form-item label="日期" prop="frequencyDetail">
              <el-date-picker
                  v-model="form.frequencyDetail"
                  type="datetime"
                  clearable
                  placeholder="选择开始日期"
                  format="DD,HH:mm"
                  value-format="DD,HH:mm"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12" v-if="form.frequencyType === 'QUARTERLY' && form.frequencyType">
            <el-form-item label="日期" prop="frequencyDetail">
              <el-date-picker
                  v-model="form.frequencyDetail"
                  type="datetime"
                  clearable
                  placeholder="选择开始日期"
                  format="MM,DD,HH:mm"
                  value-format="MM,DD,HH:mm"
              />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="cancel">取消</el-button>
          <el-button type="primary" @click="submitForm">保存</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import {reactive, ref} from "vue";
import useUserStore from '@/store/modules/user'
import {addOrEditTimingTask} from "@/api/inspectionManagement/index.js";
import {userListAll} from "@/api/publicApi/index.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits()
const userStore = useUserStore()
const dialogVisitable = ref(false);
const operationType = ref('add');
const data = reactive({
  form: {
    taskName: '',
    inspectionLocation: '',
    inspector: '',
    inspectorIds: '',
    remarks: '',
    frequencyType: '',
    frequencyDetail: '',
  },
  rules: {
    taskName: [{ required: true, message: "请输入任务名称", trigger: "blur" },],
    inspectionLocation: [{ required: true, message: "请输入地点", trigger: "blur" },],
    inspector: [{ required: true, message: "请输入巡检人", trigger: "blur" },],
  }
})
const { form, rules } = toRefs(data)
const userList = ref([])
// æ‰“开弹框
const openDialog = async (type, row) => {
  dialogVisitable.value = true
  userListAll().then(res => {
    userList.value = res.data
  })
  if (type === 'edit') {
    form.value = {...row}
    form.value.inspector = form.value.inspectorIds.split(',').map(Number)
  }
}
// å…³é—­åˆå¹¶è¡¨å•
const cancel = () => {
  proxy.resetForm("formRef")
  dialogVisitable.value = false
  emit('closeDia')
}
// æäº¤åˆå¹¶è¡¨å•
const submitForm = () => {
  proxy.$refs["formRef"].validate(async valid => {
    if (valid) {
      form.value.inspectorIds = form.value.inspector.join(',')
            delete form.value.inspector
      if (form.value.frequencyType === 'WEEKLY') {
        let frequencyDetail = ''
        frequencyDetail = form.value.week + ',' + form.value.time
        form.value.frequencyDetail = frequencyDetail
      }
      let res = await userStore.getInfo()
      form.value.registrantId = res.user.userId
      addOrEditTimingTask(form.value).then(() => {
        cancel()
        proxy.$modal.msgSuccess('提交成功')
      })
    }
  })
}
defineExpose({ openDialog })
</script>
<style scoped>
</style>
src/views/inspectionManagement/components/qrCodeDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,132 @@
<template>
  <div>
    <el-dialog :title="operationType === 'add' ? '新增二维码' : '编辑二维码'"
               v-model="dialogVisitable" width="500px" @close="cancel">
      <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
        <el-row>
          <el-col :span="24">
            <el-form-item label="设备名称" prop="deviceName">
              <el-input v-model="form.deviceName" placeholder="请输入设备名称" maxlength="30" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="24">
            <el-form-item label="所在位置描述" prop="location">
              <el-input v-model="form.location" placeholder="请输入所在位置描述" maxlength="30"/>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <div>
        <el-button type="primary" @click="submitForm">生成并打印二维码</el-button>
      </div>
      <div v-if="isShowQrCode" class="print-section" ref="qrCodeContainer" id="qrCodeContainer">
        <vue-qrcode :value="qrCodeValue" :width="qrCodeSize"></vue-qrcode>
      </div>
    </el-dialog>
  </div>
</template>
<script setup>
import useUserStore from "@/store/modules/user.js";
import {reactive, ref} from "vue";
import printJS from 'print-js';
import {addOrEditQrCode} from "@/api/inspectionUpload/index.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits()
const userStore = useUserStore()
const dialogVisitable = ref(false);
const isShowQrCode = ref(false);
const operationType = ref('add');
const qrCodeValue = ref('');
const qrCodeSize = ref(100);
const data = reactive({
  form: {
    deviceName: '',
    location: '',
    qrCodeId: '',
    id: ''
  },
  rules: {
    deviceName: [{ required: true, message: '请输入设备名称', trigger: 'blur' }],
    location: [{ required: true, message: '请输入地点', trigger: 'blur' }]
  }
})
const { form, rules } = toRefs(data)
// æ‰“开弹框
const openDialog = async (type, row) => {
  dialogVisitable.value = true
  qrCodeValue.value = ''
  isShowQrCode.value = false;
  if (type === 'edit') {
    form.value.id = row.id
    form.value.qrCodeId = row.id
    form.value.deviceName = row.deviceName
    form.value.location = row.location
    // å°†è¡¨å•数据转为 JSON å­—符串作为二维码内容
    qrCodeValue.value = JSON.stringify(form.value);
    isShowQrCode.value = true;
  }
}
// æäº¤åˆå¹¶è¡¨å•
const submitForm = () => {
  proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      addOrEditQrCode(form.value).then((res) => {
        form.value.qrCodeId = res.data
      })
      // å°†è¡¨å•数据转为 JSON å­—符串作为二维码内容
      qrCodeValue.value = JSON.stringify(form.value);
      isShowQrCode.value = true;
      showQrCode()
    }
  })
}
const showQrCode = () => {
  // å»¶è¿Ÿæ‰§è¡Œæ‰“印,避免 DOM æ›´æ–°å‰å°±è°ƒç”¨æ‰“印
  setTimeout(() => {
    printJS({
      printable: 'qrCodeContainer',//页面
      type: "html",//文档类型
      maxWidth: 360,
      style: `@page {
                margin:0;
                size: 400px 75px collapse;
                margin-top:3px;
                &:first-of-type{
                  margin-top:0 !important;
                }
              }
              html{
                zoom:100%;
              }
              @media print{
                width: 400px;
                height: 75px;
                margin:0;
              }`,
      targetStyles: ["*"], // ä½¿ç”¨dom的所有样式,很重要
      font_size: '0.20cm',
    });
  }, 300);
}
// å…³é—­åˆå¹¶è¡¨å•
const cancel = () => {
  proxy.resetForm("formRef")
  dialogVisitable.value = false
  emit('closeDia')
}
defineExpose({ openDialog })
</script>
<style scoped>
.print-section {
  text-align: center;
  margin-top: 30px;
}
</style>
src/views/inspectionManagement/components/viewFiles.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,246 @@
<template>
  <div>
    <el-dialog title="查看附件"
               v-model="dialogVisitable" width="800px" @close="cancel">
      <div class="upload-container">
        <!-- ç”Ÿäº§å‰ -->
        <div class="form-container">
          <div class="title">生产前</div>
          <!-- å›¾ç‰‡åˆ—表 -->
          <div style="display: flex; flex-wrap: wrap;">
            <img v-for="(item, index) in beforeProductionImgs" :key="index"
                 @click="showMedia(beforeProductionImgs, index, 'image')"
                 :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
          </div>
          <!-- è§†é¢‘列表 -->
          <div style="display: flex; flex-wrap: wrap;">
            <div
                v-for="(videoUrl, index) in beforeProductionVideos"
                :key="index"
                @click="showMedia(beforeProductionVideos, index, 'video')"
                style="position: relative; margin: 10px; cursor: pointer;"
            >
              <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
                <img src="@/assets/images/video.png" alt="播放" style="width: 30px; height: 30px; opacity: 0.8;" />
              </div>
              <div style="text-align: center; font-size: 12px; color: #666;">点击播放</div>
            </div>
          </div>
        </div>
        <!-- ç”Ÿäº§åŽ -->
        <div class="form-container">
          <div class="title">生产后</div>
          <!-- å›¾ç‰‡åˆ—表 -->
          <div style="display: flex; flex-wrap: wrap;">
            <img v-for="(item, index) in afterProductionImgs" :key="index"
                 @click="showMedia(afterProductionImgs, index, 'image')"
                 :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
          </div>
          <!-- è§†é¢‘列表 -->
          <div style="display: flex; flex-wrap: wrap;">
            <div
                v-for="(videoUrl, index) in afterProductionVideos"
                :key="index"
                @click="showMedia(afterProductionVideos, index, 'video')"
                style="position: relative; margin: 10px; cursor: pointer;"
            >
              <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
                <img src="@/assets/images/video.png" alt="播放" style="width: 30px; height: 30px; opacity: 0.8;" />
              </div>
              <div style="text-align: center; font-size: 12px; color: #666;">点击播放</div>
            </div>
          </div>
        </div>
        <!-- ç”Ÿäº§é—®é¢˜ -->
        <div class="form-container">
          <div class="title">生产问题</div>
          <!-- å›¾ç‰‡åˆ—表 -->
          <div style="display: flex; flex-wrap: wrap;">
            <img v-for="(item, index) in productionIssuesImgs" :key="index"
                 @click="showMedia(productionIssuesImgs, index, 'image')"
                 :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
          </div>
          <!-- è§†é¢‘列表 -->
          <div style="display: flex; flex-wrap: wrap;">
            <div
                v-for="(videoUrl, index) in productionIssuesVideos"
                :key="index"
                @click="showMedia(productionIssuesVideos, index, 'video')"
                style="position: relative; margin: 10px; cursor: pointer;"
            >
              <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
                <img src="@/assets/images/video.png" alt="播放" style="width: 30px; height: 30px; opacity: 0.8;" />
              </div>
              <div style="text-align: center; font-size: 12px; color: #666;">点击播放</div>
            </div>
          </div>
        </div>
      </div>
    </el-dialog>
    <!-- ç»Ÿä¸€åª’体查看器 -->
    <div v-if="isMediaViewerVisible" class="media-viewer-overlay" @click.self="closeMediaViewer">
      <div class="media-viewer-content" @click.stop>
        <!-- å›¾ç‰‡ -->
        <vue-easy-lightbox
            v-if="mediaType === 'image'"
            :visible="isMediaViewerVisible"
            :imgs="mediaList"
            :index="currentMediaIndex"
            @hide="closeMediaViewer"
        ></vue-easy-lightbox>
        <!-- è§†é¢‘ -->
        <div v-else-if="mediaType === 'video'" style="position: relative;">
          <Video
              :src="mediaList[currentMediaIndex]"
              autoplay
              controls
              style="max-width: 90vw; max-height: 80vh;"
          />
        </div>
      </div>
    </div>
  </div>
</template>
<script setup>
import { ref } from 'vue';
import VueEasyLightbox from 'vue-easy-lightbox';
// æŽ§åˆ¶å¼¹çª—显示
const dialogVisitable = ref(false);
// å›¾ç‰‡æ•°ç»„
const beforeProductionImgs = ref([]);
const afterProductionImgs = ref([]);
const productionIssuesImgs = ref([]);
// è§†é¢‘数组
const beforeProductionVideos = ref([]);
const afterProductionVideos = ref([]);
const productionIssuesVideos = ref([]);
// åª’体查看器状态
const isMediaViewerVisible = ref(false);
const currentMediaIndex = ref(0);
const mediaList = ref([]); // å­˜å‚¨å½“前要查看的媒体列表(含图片和视频对象)
const mediaType = ref('image'); // image | video
// å¤„理每一类数据:分离图片和视频
function processItems(items) {
  const images = [];
  const videos = [];
  items.forEach(item => {
    if (item.contentType?.startsWith('image/')) {
      images.push(item.url);
    } else if (item.contentType?.startsWith('video/')) {
      videos.push(item.url);
    }
  });
  return { images, videos };
}
// æ‰“开弹窗并加载数据
const openDialog = async (row) => {
  const { images: beforeImgs, videos: beforeVids } = processItems(row.beforeProduction);
  const { images: afterImgs, videos: afterVids } = processItems(row.afterProduction);
  const { images: issueImgs, videos: issueVids } = processItems(row.productionIssues);
  beforeProductionImgs.value = beforeImgs;
  beforeProductionVideos.value = beforeVids;
  afterProductionImgs.value = afterImgs;
  afterProductionVideos.value = afterVids;
  productionIssuesImgs.value = issueImgs;
  productionIssuesVideos.value = issueVids;
  dialogVisitable.value = true;
};
// æ˜¾ç¤ºåª’体(图片 or è§†é¢‘)
function showMedia(mediaArray, index, type) {
  mediaList.value = mediaArray;
  currentMediaIndex.value = index;
  mediaType.value = type;
  isMediaViewerVisible.value = true;
}
// å…³é—­åª’体查看器
function closeMediaViewer() {
  isMediaViewerVisible.value = false;
  mediaList.value = [];
  mediaType.value = 'image';
}
// è¡¨å•关闭方法
const cancel = () => {
  dialogVisitable.value = false;
};
defineExpose({ openDialog });
</script>
<style scoped lang="scss">
.upload-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 20px;
  border: 1px solid #dcdfe6;
  box-sizing: border-box;
  .form-container {
    flex: 1;
    width: 100%;
    margin-bottom: 20px;
  }
}
.title {
  font-size: 14px;
  color: #165dff;
  line-height: 20px;
  font-weight: 600;
  padding-left: 10px;
  position: relative;
  margin: 6px 0;
  &::before {
    content: "";
    position: absolute;
    left: 0;
    top: 3px;
    width: 4px;
    height: 14px;
    background-color: #165dff;
  }
}
.media-viewer-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.8);
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
}
.media-viewer-content {
  position: relative;
  max-width: 90vw;
  max-height: 90vh;
  overflow: hidden;
}
</style>
src/views/inspectionManagement/components/viewQrCodeFiles.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,169 @@
<template>
  <div>
    <el-dialog title="查看附件"
               v-model="dialogVisitable" width="800px" @close="cancel">
      <div class="upload-container">
        <div class="form-container">
          <div class="title">巡检附件</div>
          <!-- å›¾ç‰‡åˆ—表 -->
          <div style="display: flex; flex-wrap: wrap;">
            <img v-for="(item, index) in beforeProductionImgs" :key="index"
                 @click="showMedia(beforeProductionImgs, index, 'image')"
                 :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
          </div>
          <!-- è§†é¢‘列表 -->
          <div style="display: flex; flex-wrap: wrap;">
            <div
                v-for="(videoUrl, index) in beforeProductionVideos"
                :key="index"
                @click="showMedia(beforeProductionVideos, index, 'video')"
                style="position: relative; margin: 10px; cursor: pointer;"
            >
              <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
                <img src="@/assets/images/video.png" alt="播放" style="width: 30px; height: 30px; opacity: 0.8;" />
              </div>
              <div style="text-align: center; font-size: 12px; color: #666;">点击播放</div>
            </div>
          </div>
        </div>
      </div>
    </el-dialog>
    <!-- ç»Ÿä¸€åª’体查看器 -->
    <div v-if="isMediaViewerVisible" class="media-viewer-overlay" @click.self="closeMediaViewer">
      <div class="media-viewer-content" @click.stop>
        <!-- å›¾ç‰‡ -->
        <vue-easy-lightbox
            v-if="mediaType === 'image'"
            :visible="isMediaViewerVisible"
            :imgs="mediaList"
            :index="currentMediaIndex"
            @hide="closeMediaViewer"
        ></vue-easy-lightbox>
        <!-- è§†é¢‘ -->
        <div v-else-if="mediaType === 'video'" style="position: relative;">
          <Video
              :src="mediaList[currentMediaIndex]"
              autoplay
              controls
              style="max-width: 90vw; max-height: 80vh;"
          />
        </div>
      </div>
    </div>
  </div>
</template>
<script setup>
// æŽ§åˆ¶å¼¹çª—显示
import VueEasyLightbox from "vue-easy-lightbox";
const dialogVisitable = ref(false);
// å›¾ç‰‡æ•°ç»„
const beforeProductionImgs = ref([]);
// è§†é¢‘数组
const beforeProductionVideos = ref([]);
// åª’体查看器状态
const isMediaViewerVisible = ref(false);
const currentMediaIndex = ref(0);
const mediaList = ref([]); // å­˜å‚¨å½“前要查看的媒体列表(含图片和视频对象)
const mediaType = ref('image'); // image | video
// æ‰“开弹窗并加载数据
const openDialog = async (row) => {
  const { images: beforeImgs, videos: beforeVids } = processItems(row.storageBlobDTO);
  beforeProductionImgs.value = beforeImgs;
  beforeProductionVideos.value = beforeVids;
  dialogVisitable.value = true;
};
// æ˜¾ç¤ºåª’体(图片 or è§†é¢‘)
function showMedia(mediaArray, index, type) {
  mediaList.value = mediaArray;
  currentMediaIndex.value = index;
  mediaType.value = type;
  isMediaViewerVisible.value = true;
}
// å…³é—­åª’体查看器
function closeMediaViewer() {
  isMediaViewerVisible.value = false;
  mediaList.value = [];
  mediaType.value = 'image';
}
// è¡¨å•关闭方法
const cancel = () => {
  dialogVisitable.value = false;
};
// å¤„理每一类数据:分离图片和视频
function processItems(items) {
  const images = [];
  const videos = [];
  items.forEach(item => {
    if (item.contentType?.startsWith('image/')) {
      images.push(item.url);
    } else if (item.contentType?.startsWith('video/')) {
      videos.push(item.url);
    }
  });
  return { images, videos };
}
defineExpose({ openDialog });
</script>
<style scoped lang="scss">
.upload-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 20px;
  border: 1px solid #dcdfe6;
  box-sizing: border-box;
  .form-container {
    flex: 1;
    width: 100%;
    margin-bottom: 20px;
  }
}
.title {
  font-size: 14px;
  color: #165dff;
  line-height: 20px;
  font-weight: 600;
  padding-left: 10px;
  position: relative;
  margin: 6px 0;
  &::before {
    content: "";
    position: absolute;
    left: 0;
    top: 3px;
    width: 4px;
    height: 14px;
    background-color: #165dff;
  }
}
.media-viewer-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.8);
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
}
.media-viewer-content {
  position: relative;
  max-width: 90vw;
  max-height: 90vh;
  overflow: hidden;
}
</style>
src/views/inspectionManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,391 @@
<template>
  <div class="app-container">
    <el-form :inline="true" :model="queryParams" class="search-form">
      <el-form-item label="搜索">
        <el-input
            v-model="queryParams.searchAll"
            placeholder="请输入关键字"
            clearable
            :style="{ width: '100%' }"
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="handleQuery">查询</el-button>
        <el-button @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>
    <el-card>
      <!-- æ ‡ç­¾é¡µ -->
      <el-tabs
          v-model="activeTab"
          class="info-tabs"
          @tab-click="handleTabClick"
      >
        <el-tab-pane
            v-for="tab in tabs"
            :key="tab.name"
            :label="tab.label"
            :name="tab.name"
        />
      </el-tabs>
      <div style="display: flex;flex-direction: row;justify-content: space-between;" v-if="tabName === 'task'">
        <el-radio-group v-model="activeRadio" @change="radioChange">
          <el-radio-button v-for="tab in radios"
                           :key="tab.name"
                           :label="tab.label"
                           :value="tab.name"/>
        </el-radio-group>
        <!-- æ“ä½œæŒ‰é’®åŒº -->
        <el-space v-if="activeRadio !== 'task'">
          <el-button type="primary" :icon="Plus" @click="handleAdd(undefined)">新建</el-button>
          <el-button type="danger" :icon="Delete" @click="handleDelete">删除</el-button>
          <!-- <el-button type="info" plain :icon="Download">导出</el-button> -->
        </el-space>
      </div>
      <div>
        <div>
          <ETable :loading="tableLoading"
                  :table-data="tableData"
                  :columns="tableColumns"
                  @selection-change="handleSelectionChange"
                  :show-selection="true"
                  :border="true"
                  style="width: 100%;height: calc(100vh - 30em)"
                  operationsWidth="130"
                  :operations="operationsArr"
                  @edit="handleAdd"
                  @viewFile="viewFile"
                  v-if="tabName === 'task'"
          >
          <template #inspector="{ row }">
            <div class="person-tags">
              <!-- è°ƒè¯•信息,上线时删除 -->
              <!-- {{ console.log('inspector data:', row.inspector) }} -->
              <template v-if="row.inspector && row.inspector.length > 0">
                <el-tag
                  v-for="(person, index) in row.inspector"
                  :key="index"
                  size="small"
                  type="primary"
                  class="person-tag"
                >
                  {{ person }}
                </el-tag>
              </template>
              <span v-else class="no-data">--</span>
            </div>
          </template>
          </ETable>
          <el-table ref="table" :data="tableData" height="480" v-loading="tableLoading" border v-else style="width: 100%;height: calc(100vh - 25em)">
            <el-table-column label="序号" type="index" width="60" align="center" />
            <el-table-column prop="deviceName" label="设备名称" :show-overflow-tooltip="true">
              <template #default="scope">
                {{scope.row.qrCode.deviceName}}
              </template>
            </el-table-column>
            <el-table-column prop="location" label="所在位置描述" :show-overflow-tooltip="true">
              <template #default="scope">
                {{scope.row.qrCode.location}}
              </template>
            </el-table-column>
            <el-table-column prop="scanner" label="巡检人"></el-table-column>
            <el-table-column prop="scanTime" label="巡检时间"></el-table-column>
            <el-table-column fixed="right" label="操作">
              <template #default="scope">
                <el-button link type="primary" @click="handleAdd(scope.row)">查看附件</el-button>
              </template>
            </el-table-column>
          </el-table>
        </div>
        <pagination
            v-if="total>0"
            :page="pageNum"
            :limit="pageSize"
            :total="total"
            @pagination="handlePagination"
            :layout="'total, prev, pager, next, jumper'"
        />
      </div>
    </el-card>
    <form-dia ref="formDia" @closeDia="handleQuery"></form-dia>
    <qr-code-dia ref="qrCodeDia" @closeDia="handleQuery"></qr-code-dia>
    <view-files ref="viewFiles"></view-files>
    <view-qr-code-files ref="viewQrCodeFiles"></view-qr-code-files>
  </div>
</template>
<script setup>
import { Delete, Plus } from "@element-plus/icons-vue";
import { onMounted, ref, reactive, getCurrentInstance, nextTick } from "vue";
// ç»„件引入
import Pagination from "@/components/Pagination/index.vue";
import ETable from "@/components/Table/ETable.vue";
import FormDia from "@/views/inspectionManagement/components/formDia.vue";
import QrCodeDia from "@/views/inspectionManagement/components/qrCodeDia.vue";
import ViewFiles from "@/views/inspectionManagement/components/viewFiles.vue";
import ViewQrCodeFiles from "@/views/inspectionManagement/components/viewQrCodeFiles.vue";
// æŽ¥å£å¼•å…¥
import {
  delTimingTask,
  inspectionTaskList,
  timingTaskList
} from "@/api/inspectionManagement/index.js";
import {
  delQrCode,
  qrCodeList,
  qrCodeScanRecordList
} from "@/api/inspectionUpload/index.js";
// å…¨å±€å˜é‡
const { proxy } = getCurrentInstance();
const formDia = ref();
const qrCodeDia = ref();
const viewFiles = ref();
const viewQrCodeFiles = ref();
// æŸ¥è¯¢å‚æ•°
const queryParams = reactive({
  searchAll: "",
});
// æ ‡ç­¾é¡µé…ç½®
const activeTab = ref("task");
const tabName = ref("task");
const tabs = reactive([
  { name: "task", label: "生产巡检" },
  { name: "qrCodeScanRecord", label: "现场巡检记录" },
]);
// å•选框配置
const activeRadio = ref("taskManage");
const radios = reactive([
  { name: "taskManage", label: "定时任务管理" },
  { name: "task", label: "定时任务记录" },
  { name: "qrCode", label: "二维码管理" },
]);
// è¡¨æ ¼æ•°æ®
const selectedRows = ref([]);
const tableData = ref([]);
const operationsArr = ref([]);
const tableColumns = ref([]);
const tableLoading = ref(false);
const total = ref(0);
const pageNum = ref(1);
const pageSize = ref(10);
// åˆ—配置
const columns = ref([
  { prop: "taskName", label: "巡检任务名称", minWidth: 160 },
  { prop: "inspectionLocation", label: "地点", minWidth: 120 },
  { prop: "remarks", label: "备注", minWidth: 150 },
  { prop: "inspector", label: "执行巡检人", minWidth: 150, slot: "inspector" },
  {
    prop: "frequencyType",
    label: "频次",
    minWidth: 150,
    formatter: (_, __, val) => ({
      DAILY: "每日",
      WEEKLY: "每周",
      MONTHLY: "每月",
      QUARTERLY: "季度"
    }[val] || "")
  },
  {
    prop: "frequencyDetail",
    label: "开始日期与时间",
    minWidth: 150,
    formatter: (row, column, cellValue) => {
      // å…ˆåˆ¤æ–­æ˜¯å¦æ˜¯å­—符串
      if (typeof cellValue !== 'string') return '';
      let val = cellValue;
      const replacements = {
        MON: '周一',
        TUE: '周二',
        WED: '周三',
        THU: '周四',
        FRI: '周五',
        SAT: '周六',
        SUN: '周日'
      };
      // ä½¿ç”¨æ­£åˆ™ä¸€æ¬¡æ€§æ›¿æ¢æ‰€æœ‰åŒ¹é…é¡¹
      return val.replace(/MON|TUE|WED|THU|FRI|SAT|SUN/g, match => replacements[match]);
    }
  },
  { prop: "registrant", label: "登记人", minWidth: 100 },
  { prop: "createTime", label: "登记日期", minWidth: 100 },
]);
const columns1 = ref([
  { prop: "deviceName", label: "设备名称", minWidth: 160 },
  { prop: "location", label: "所在位置描述", minWidth: 120 },
  { prop: "createBy", label: "创建者", minWidth: 100 },
  { prop: "createTime", label: "创建时间", minWidth: 100 },
]);
onMounted(() => {
  radioChange('taskManage');
});
// æ ‡ç­¾é¡µç‚¹å‡»äº‹ä»¶
const handleTabClick = (tab) => {
  tabName.value = tab.props.name;
  tableData.value = [];
  getList();
};
// å•选变化
const radioChange = (value) => {
  if (value === "taskManage") {
    tableColumns.value = columns.value;
    operationsArr.value = ['edit'];
  } else if (value === "task") {
    tableColumns.value = columns.value;
    operationsArr.value = ['viewFile'];
  } else {
    tableColumns.value = columns1.value;
    operationsArr.value = ['edit'];
  }
  pageNum.value = 1;
  pageSize.value = 10;
  getList();
};
// æŸ¥è¯¢æ“ä½œ
const handleQuery = () => {
  pageNum.value = 1;
  pageSize.value = 10;
  getList();
};
// åˆ†é¡µå¤„理
const handlePagination = (val) => {
    pageNum.value = val.page;
    pageSize.value = val.limit;
    getList();
};
// èŽ·å–åˆ—è¡¨æ•°æ®
const getList = () => {
  tableLoading.value = true;
  const params = { ...queryParams, size: pageSize.value, current: pageNum.value };
  let apiCall;
  if (tabName.value === 'task') {
    switch (activeRadio.value) {
      case "task":
        apiCall = inspectionTaskList(params);
        break;
      case "qrCode":
        apiCall = qrCodeList(params);
        break;
      default:
        apiCall = timingTaskList(params);
    }
  } else {
    apiCall = qrCodeScanRecordList(params);
  }
  apiCall.then(res => {
    const rawData = res.data.records || [];
    // å¤„理 inspector å­—段,将字符串转换为数组(适用于所有情况)
    tableData.value = rawData.map(item => {
      const processedItem = { ...item };
      // å¤„理 inspector å­—段
      if (processedItem.inspector) {
        if (typeof processedItem.inspector === 'string') {
          // å­—符串按逗号分割
          processedItem.inspector = processedItem.inspector.split(',').map(s => s.trim()).filter(s => s);
        } else if (!Array.isArray(processedItem.inspector)) {
          // éžæ•°ç»„转为数组
          processedItem.inspector = [processedItem.inspector];
        }
      } else {
        // ç©ºå€¼è®¾ä¸ºç©ºæ•°ç»„
        processedItem.inspector = [];
      }
      return processedItem;
    });
    total.value = res.data.total || 0;
  }).finally(() => {
    tableLoading.value = false;
  });
};
// é‡ç½®æŸ¥è¯¢
const resetQuery = () => {
  for (const key in queryParams) {
    if (!["pageNum", "pageSize"].includes(key)) {
      queryParams[key] = "";
    }
  }
  handleQuery();
};
// æ–°å¢ž / ç¼–辑
const handleAdd = (row) => {
  const type = row ? 'edit' : 'add';
  nextTick(() => {
    if (tabName.value === 'task') {
      if (activeRadio.value === "taskManage") {
        formDia.value?.openDialog(type, row);
      } else if (activeRadio.value === "qrCode") {
        qrCodeDia.value?.openDialog(type, row);
      }
    } else {
      viewQrCodeFiles.value?.openDialog(row);
    }
  });
};
// æŸ¥çœ‹é™„ä»¶
const viewFile = (row) => {
  nextTick(() => {
    viewFiles.value?.openDialog(row);
  });
};
// åˆ é™¤æ“ä½œ
const handleDelete = () => {
  if (!selectedRows.value.length) {
    proxy.$modal.msgWarning("请选择要删除的数据");
    return;
  }
  const deleteIds = selectedRows.value.map(item => item.id);
  const api = activeRadio.value === "taskManage" ? delTimingTask : delQrCode;
  proxy.$modal.confirm('是否确认删除所选数据项?').then(() => {
    return api(deleteIds);
  }).then(() => {
    proxy.$modal.msgSuccess("删除成功");
    handleQuery();
  }).catch(() => {});
};
// å¤šé€‰å˜æ›´
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
</script>
<style scoped>
.person-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.person-tag {
  margin-right: 4px;
  margin-bottom: 2px;
}
.no-data {
  color: #909399;
  font-size: 14px;
}
</style>
src/views/procurementManagement/advancedPriceManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,874 @@
<template>
  <div class="app-container">
    <!-- æœç´¢åŒºåŸŸ -->
    <el-card class="search-card" shadow="never">
      <el-form :model="searchForm" :inline="true" label-width="100px">
        <el-form-item label="商品名称:">
          <el-input v-model="searchForm.productName" placeholder="请输入商品名称" clearable style="width: 200px" />
        </el-form-item>
        <el-form-item label="供应商:">
          <el-select v-model="searchForm.supplierId" placeholder="请选择供应商" clearable style="width: 200px">
            <el-option v-for="supplier in supplierList" :key="supplier.id" :label="supplier.name" :value="supplier.id" />
          </el-select>
        </el-form-item>
        <el-form-item label="价格状态:">
          <el-select v-model="searchForm.priceStatus" placeholder="请选择状态" clearable style="width: 150px">
            <el-option label="有效" value="active" />
            <el-option label="待生效" value="pending" />
            <el-option label="已过期" value="expired" />
            <el-option label="已暂停" value="suspended" />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleSearch" :loading="loading">
            <el-icon><Search /></el-icon>
            æœç´¢
          </el-button>
          <el-button @click="resetSearch">
            <el-icon><Refresh /></el-icon>
            é‡ç½®
          </el-button>
        </el-form-item>
      </el-form>
    </el-card>
    <!-- åŠŸèƒ½æŒ‰é’®åŒºåŸŸ -->
    <el-card class="action-card" shadow="never">
      <div class="action-buttons">
        <el-button type="primary" @click="openDialog('add')">
          <el-icon><Plus /></el-icon>
          æ–°å¢žä»·æ ¼
        </el-button>
        <el-button type="success" @click="openBatchDiscountDialog">
          <el-icon><Discount /></el-icon>
          æ‰¹é‡æŠ˜æ‰£
        </el-button>
        <el-button type="warning" @click="openPriceControlDialog">
          <el-icon><Setting /></el-icon>
          ä»·æ ¼æŽ§åˆ¶
        </el-button>
        <el-button type="info" @click="exportData">
          <el-icon><Download /></el-icon>
          å¯¼å‡ºæ•°æ®
        </el-button>
        <el-button type="danger" @click="handleBatchDelete" :disabled="selectedRows.length === 0">
          <el-icon><Delete /></el-icon>
          æ‰¹é‡åˆ é™¤
        </el-button>
      </div>
    </el-card>
    <!-- ä¸»è¡¨æ ¼ -->
    <el-card class="table-card" shadow="never">
      <el-table
        :data="tableData"
        border
        v-loading="loading"
        @selection-change="handleSelectionChange"
        :default-sort="{ prop: 'updateTime', order: 'descending' }"
      >
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="商品信息" min-width="200">
          <template #default="{ row }">
            <div class="product-info">
              <div class="product-name">{{ row.productName }}</div>
              <div class="product-spec">{{ row.specification }}</div>
              <div class="product-code">编码: {{ row.productCode }}</div>
            </div>
          </template>
        </el-table-column>
        <el-table-column label="供应商" prop="supplierName" width="150" />
        <el-table-column label="基础价格" width="120" align="right">
          <template #default="{ row }">
            <span class="price-text">Â¥{{ row.basePrice.toFixed(2) }}</span>
          </template>
        </el-table-column>
        <el-table-column label="折扣信息" width="150">
          <template #default="{ row }">
            <div v-if="row.discountType">
              <el-tag :type="getDiscountTagType(row.discountType)" size="small">
                {{ getDiscountText(row.discountType) }}
              </el-tag>
              <div class="discount-value">{{ row.discountValue }}{{ row.discountType === 'percentage' ? '%' : '元' }}</div>
            </div>
            <span v-else class="no-discount">无折扣</span>
          </template>
        </el-table-column>
        <el-table-column label="实际价格" width="120" align="right">
          <template #default="{ row }">
            <span class="final-price">Â¥{{ calculateFinalPrice(row).toFixed(2) }}</span>
          </template>
        </el-table-column>
        <el-table-column label="价格控制" width="120">
          <template #default="{ row }">
            <div class="price-control">
              <div v-if="row.priceControl?.minPrice" class="control-item">
                æœ€ä½Ž: Â¥{{ row.priceControl.minPrice.toFixed(2) }}
              </div>
              <div v-if="row.priceControl?.maxPrice" class="control-item">
                æœ€é«˜: Â¥{{ row.priceControl.maxPrice.toFixed(2) }}
              </div>
            </div>
          </template>
        </el-table-column>
        <el-table-column label="状态" width="100" align="center">
          <template #default="{ row }">
            <el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag>
            <div v-if="isPriceWarning(row)" class="warning-indicator">
              <el-icon color="#F56C6C"><Warning /></el-icon>
            </div>
          </template>
        </el-table-column>
        <el-table-column label="生效时间" prop="effectiveTime" width="180" />
        <el-table-column label="更新时间" prop="updateTime" width="180" sortable />
        <el-table-column label="操作" width="250" align="center" fixed="right">
          <template #default="{ row }">
            <el-button type="primary" link @click="openDialog('edit', row)">
              <el-icon><Edit /></el-icon>
              ç¼–辑
            </el-button>
            <el-button type="success" link @click="openDiscountDialog(row)">
              <el-icon><Discount /></el-icon>
              æŠ˜æ‰£
            </el-button>
            <el-button type="danger" link @click="handleDelete(row)">
              <el-icon><Delete /></el-icon>
              åˆ é™¤
            </el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- åˆ†é¡µ -->
      <div class="pagination-wrapper">
        <el-pagination
          v-model:current-page="pagination.currentPage"
          v-model:page-size="pagination.pageSize"
          :page-sizes="[10, 20, 50, 100]"
          :total="pagination.total"
          layout="total, sizes, prev, pager, next, jumper"
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
        />
      </div>
    </el-card>
    <!-- æ–°å¢ž/编辑对话框 -->
    <el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? '新增价格' : '编辑价格'" width="800px">
      <el-form :model="formData" :rules="formRules" ref="formRef" label-width="120px">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="商品名称" prop="productName">
              <el-select v-model="formData.productName" placeholder="请选择商品" style="width: 100%" filterable>
                <el-option v-for="product in productList" :key="product.id" :label="product.name" :value="product.name" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="商品编码" prop="productCode">
              <el-input v-model="formData.productCode" placeholder="请输入商品编码" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="规格型号" prop="specification">
              <el-input v-model="formData.specification" placeholder="请输入规格型号" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="供应商" prop="supplierName">
              <el-select v-model="formData.supplierName" placeholder="请选择供应商" style="width: 100%">
                <el-option v-for="supplier in supplierList" :key="supplier.id" :label="supplier.name" :value="supplier.name" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="基础价格" prop="basePrice">
              <el-input-number v-model="formData.basePrice" :min="0" :precision="2" placeholder="请输入基础价格" style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="单位">
              <el-input v-model="formData.unit" placeholder="请输入单位" />
            </el-form-item>
          </el-col>
        </el-row>
        <!-- æŠ˜æ‰£è®¾ç½® -->
        <el-divider content-position="left">折扣设置</el-divider>
        <el-row :gutter="20">
          <el-col :span="8">
            <el-form-item label="折扣类型">
              <el-select v-model="formData.discountType" placeholder="请选择折扣类型" style="width: 100%">
                <el-option label="无折扣" value="" />
                <el-option label="百分比折扣" value="percentage" />
                <el-option label="固定金额" value="fixed" />
                <el-option label="阶梯折扣" value="tiered" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="折扣值" v-if="formData.discountType && formData.discountType !== 'tiered'">
              <el-input-number
                v-model="formData.discountValue"
                :min="0"
                :max="formData.discountType === 'percentage' ? 100 : undefined"
                :precision="2"
                placeholder="请输入折扣值"
                style="width: 100%"
              />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="折扣有效期">
              <el-date-picker
                v-model="formData.discountEndTime"
                type="datetime"
                placeholder="选择结束时间"
                style="width: 100%"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <!-- é˜¶æ¢¯æŠ˜æ‰£è®¾ç½® -->
        <div v-if="formData.discountType === 'tiered'">
          <el-form-item label="阶梯折扣">
            <el-table :data="formData.tieredDiscount" border size="small">
              <el-table-column label="最小数量" width="120">
                <template #default="{ row, $index }">
                  <el-input-number v-model="row.minQty" :min="0" size="small" />
                </template>
              </el-table-column>
              <el-table-column label="最大数量" width="120">
                <template #default="{ row, $index }">
                  <el-input-number v-model="row.maxQty" :min="0" size="small" />
                </template>
              </el-table-column>
              <el-table-column label="折扣率(%)" width="120">
                <template #default="{ row, $index }">
                  <el-input-number v-model="row.discount" :min="0" :max="100" :precision="2" size="small" />
                </template>
              </el-table-column>
              <el-table-column label="操作" width="80">
                <template #default="{ row, $index }">
                  <el-button type="danger" link @click="removeTieredRow($index)">删除</el-button>
                </template>
              </el-table-column>
            </el-table>
            <el-button type="primary" link @click="addTieredRow" class="mt-2">添加阶梯</el-button>
          </el-form-item>
        </div>
        <!-- ä»·æ ¼æŽ§åˆ¶ -->
        <el-divider content-position="left">价格控制</el-divider>
        <el-row :gutter="20">
          <el-col :span="8">
            <el-form-item label="最低价格">
              <el-input-number v-model="formData.minPrice" :min="0" :precision="2" placeholder="最低价格" style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="最高价格">
              <el-input-number v-model="formData.maxPrice" :min="0" :precision="2" placeholder="最高价格" style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="预警阈值(%)">
              <el-input-number v-model="formData.warningThreshold" :min="0" :max="100" :precision="1" placeholder="预警阈值" style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="生效时间" prop="effectiveTime">
              <el-date-picker v-model="formData.effectiveTime" type="datetime" placeholder="选择生效时间" style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="失效时间">
              <el-date-picker v-model="formData.expireTime" type="datetime" placeholder="选择失效时间" style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-form-item label="调价原因" prop="reason">
          <el-select v-model="formData.reason" placeholder="请选择调价原因" style="width: 100%">
            <el-option label="市场价格变动" value="market" />
            <el-option label="成本变化" value="cost" />
            <el-option label="供应商调整" value="supplier" />
            <el-option label="季节性调整" value="seasonal" />
            <el-option label="促销活动" value="promotion" />
            <el-option label="其他原因" value="other" />
          </el-select>
        </el-form-item>
        <el-form-item label="备注">
          <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请输入备注信息" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="dialogVisible = false">取消</el-button>
        <el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
      </template>
    </el-dialog>
    <!-- æ‰¹é‡æŠ˜æ‰£å¯¹è¯æ¡† -->
    <el-dialog v-model="batchDiscountVisible" title="批量设置折扣" width="600px">
      <el-form :model="batchDiscountForm" label-width="120px">
        <el-form-item label="折扣类型">
          <el-select v-model="batchDiscountForm.discountType" placeholder="请选择折扣类型" style="width: 100%">
            <el-option label="百分比折扣" value="percentage" />
            <el-option label="固定金额" value="fixed" />
          </el-select>
        </el-form-item>
        <el-form-item label="折扣值">
          <el-input-number
            v-model="batchDiscountForm.discountValue"
            :min="0"
            :max="batchDiscountForm.discountType === 'percentage' ? 100 : undefined"
            :precision="2"
            placeholder="请输入折扣值"
            style="width: 100%"
          />
        </el-form-item>
        <el-form-item label="生效时间">
          <el-date-picker v-model="batchDiscountForm.effectiveTime" type="datetime" placeholder="选择生效时间" style="width: 100%" />
        </el-form-item>
        <el-form-item label="失效时间">
          <el-date-picker v-model="batchDiscountForm.expireTime" type="datetime" placeholder="选择失效时间" style="width: 100%" />
        </el-form-item>
        <el-form-item label="适用商品">
          <div class="selected-items">
            å·²é€‰æ‹© {{ selectedRows.length }} ä¸ªå•†å“
          </div>
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="batchDiscountVisible = false">取消</el-button>
        <el-button type="primary" @click="handleBatchDiscount">确定</el-button>
      </template>
    </el-dialog>
    <!-- ä»·æ ¼æŽ§åˆ¶å¯¹è¯æ¡† -->
    <el-dialog v-model="priceControlVisible" title="价格控制设置" width="700px">
      <el-form :model="priceControlForm" label-width="120px">
        <el-form-item label="默认最低价格">
          <el-input-number v-model="priceControlForm.defaultMinPrice" :min="0" :precision="2" style="width: 200px" />
        </el-form-item>
        <el-form-item label="默认最高价格">
          <el-input-number v-model="priceControlForm.defaultMaxPrice" :min="0" :precision="2" style="width: 200px" />
        </el-form-item>
        <el-form-item label="价格变动阈值">
          <el-input-number v-model="priceControlForm.changeThreshold" :min="0" :max="100" :precision="1" style="width: 200px" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="priceControlVisible = false">取消</el-button>
        <el-button type="primary" @click="handlePriceControl">保存设置</el-button>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
  Search, Refresh, Plus, Discount, Setting, Download, Delete, Edit,
  Warning
} from '@element-plus/icons-vue'
// å“åº”式数据
const loading = ref(false)
const submitLoading = ref(false)
const dialogVisible = ref(false)
const batchDiscountVisible = ref(false)
const priceControlVisible = ref(false)
const dialogType = ref('add')
const selectedRows = ref([])
const formRef = ref()
// æœç´¢è¡¨å•
const searchForm = reactive({
  productName: '',
  supplierId: '',
  priceStatus: ''
})
// åˆ†é¡µ
const pagination = reactive({
  currentPage: 1,
  pageSize: 20,
  total: 0
})
// è¡¨å•数据
const formData = reactive({
  productName: '',
  productCode: '',
  specification: '',
  supplierName: '',
  basePrice: 0,
  unit: '',
  discountType: '',
  discountValue: 0,
  discountEndTime: '',
  tieredDiscount: [],
  minPrice: null,
  maxPrice: null,
  warningThreshold: 10,
  effectiveTime: '',
  expireTime: '',
  reason: '',
  remark: ''
})
// æ‰¹é‡æŠ˜æ‰£è¡¨å•
const batchDiscountForm = reactive({
  discountType: 'percentage',
  discountValue: 0,
  effectiveTime: '',
  expireTime: ''
})
// ä»·æ ¼æŽ§åˆ¶è¡¨å•
const priceControlForm = reactive({
  defaultMinPrice: 0,
  defaultMaxPrice: 0,
  changeThreshold: 10,
})
// è¡¨å•验证规则
const formRules = {
  productName: [{ required: true, message: '请选择商品名称', trigger: 'change' }],
  productCode: [{ required: true, message: '请输入商品编码', trigger: 'blur' }],
  supplierName: [{ required: true, message: '请选择供应商', trigger: 'change' }],
  basePrice: [{ required: true, message: '请输入基础价格', trigger: 'blur' }],
  effectiveTime: [{ required: true, message: '请选择生效时间', trigger: 'change' }],
  reason: [{ required: true, message: '请选择调价原因', trigger: 'change' }]
}
// æ¨¡æ‹Ÿæ•°æ®
const tableData = ref([
  {
    id: 1,
    productName: '高强度螺栓',
    productCode: 'HQ001',
    specification: 'M12×80',
    supplierName: '优质五金供应商',
    basePrice: 2.50,
    discountType: 'percentage',
    discountValue: 10,
    priceControl: { minPrice: 2.00, maxPrice: 3.00 },
    status: 'active',
    effectiveTime: '2025-01-01 00:00:00',
    updateTime: '2025-09-17 10:30:00',
    unit: '个',
    reason: 'market',
    remark: '市场价格调整'
  },
  {
    id: 2,
    productName: '不锈钢管',
    productCode: 'BXG002',
    specification: 'Φ25×2.0',
    supplierName: '钢材贸易公司',
    basePrice: 45.80,
    discountType: 'fixed',
    discountValue: 5,
    priceControl: { minPrice: 40.00, maxPrice: 50.00 },
    status: 'pending',
    effectiveTime: '2025-10-01 00:00:00',
    updateTime: '2025-09-16 14:20:00',
    unit: 'ç±³',
    reason: 'cost',
    remark: '原材料成本上涨'
  }
])
const supplierList = ref([
  { id: 1, name: '优质五金供应商' },
  { id: 2, name: '钢材贸易公司' },
  { id: 3, name: '建材批发商' }
])
const productList = ref([
  { id: 1, name: '高强度螺栓' },
  { id: 2, name: '不锈钢管' },
  { id: 3, name: '铝合金型材' }
])
// è®¡ç®—属性
const finalTableData = computed(() => {
  return tableData.value.filter(item => {
    if (searchForm.productName && !item.productName.includes(searchForm.productName)) return false
    if (searchForm.supplierId && item.supplierId !== searchForm.supplierId) return false
    if (searchForm.priceStatus && item.status !== searchForm.priceStatus) return false
    return true
  })
})
// æ–¹æ³•
const calculateFinalPrice = (row) => {
  let finalPrice = row.basePrice
  if (row.discountType === 'percentage') {
    finalPrice = row.basePrice * (1 - row.discountValue / 100)
  } else if (row.discountType === 'fixed') {
    finalPrice = row.basePrice - row.discountValue
  }
  return Math.max(finalPrice, 0)
}
const getDiscountTagType = (discountType) => {
  const typeMap = {
    percentage: 'success',
    fixed: 'warning',
    tiered: 'info'
  }
  return typeMap[discountType] || 'info'
}
const getDiscountText = (discountType) => {
  const textMap = {
    percentage: '百分比',
    fixed: '固定金额',
    tiered: '阶梯折扣'
  }
  return textMap[discountType] || '未知'
}
const getStatusType = (status) => {
  const statusMap = {
    active: 'success',
    pending: 'warning',
    expired: 'info',
    suspended: 'danger'
  }
  return statusMap[status] || 'info'
}
const getStatusText = (status) => {
  const statusMap = {
    active: '有效',
    pending: '待生效',
    expired: '已过期',
    suspended: '已暂停'
  }
  return statusMap[status] || '未知'
}
const isPriceWarning = (row) => {
  if (!row.priceControl) return false
  const finalPrice = calculateFinalPrice(row)
  return finalPrice < row.priceControl.minPrice || finalPrice > row.priceControl.maxPrice
}
const handleSearch = () => {
  loading.value = true
  // æ¨¡æ‹ŸAPI调用
  setTimeout(() => {
    loading.value = false
  }, 500)
}
const resetSearch = () => {
  Object.assign(searchForm, {
    productName: '',
    supplierId: '',
    priceStatus: ''
  })
  handleSearch()
}
const openDialog = (type, row = {}) => {
  dialogType.value = type
  if (type === 'edit' && row.id) {
    Object.assign(formData, {
      ...row,
      minPrice: row.priceControl?.minPrice,
      maxPrice: row.priceControl?.maxPrice,
      tieredDiscount: row.tieredDiscount || []
    })
  } else {
    resetFormData()
  }
  dialogVisible.value = true
}
const resetFormData = () => {
  Object.assign(formData, {
    productName: '',
    productCode: '',
    specification: '',
    supplierName: '',
    basePrice: 0,
    unit: '',
    discountType: '',
    discountValue: 0,
    discountEndTime: '',
    tieredDiscount: [],
    minPrice: null,
    maxPrice: null,
    warningThreshold: 10,
    effectiveTime: '',
    expireTime: '',
    reason: '',
    remark: ''
  })
}
const addTieredRow = () => {
  formData.tieredDiscount.push({
    minQty: 0,
    maxQty: 0,
    discount: 0
  })
}
const removeTieredRow = (index) => {
  formData.tieredDiscount.splice(index, 1)
}
const handleSubmit = async () => {
  if (!formRef.value) return
  try {
    await formRef.value.validate()
    submitLoading.value = true
    // æ¨¡æ‹ŸAPI调用
    setTimeout(() => {
      if (dialogType.value === 'add') {
        const newItem = {
          id: Date.now(),
          ...formData,
          priceControl: {
            minPrice: formData.minPrice,
            maxPrice: formData.maxPrice
          },
          status: 'pending',
          updateTime: new Date().toLocaleString()
        }
        tableData.value.unshift(newItem)
        ElMessage.success('新增成功')
      } else {
        // ç¼–辑逻辑
        ElMessage.success('编辑成功')
      }
      dialogVisible.value = false
      submitLoading.value = false
    }, 1000)
  } catch (error) {
    console.error('表单验证失败:', error)
  }
}
const openBatchDiscountDialog = () => {
  if (selectedRows.value.length === 0) {
    ElMessage.warning('请先选择要设置折扣的商品')
    return
  }
  batchDiscountVisible.value = true
}
const handleBatchDiscount = () => {
  // æ‰¹é‡è®¾ç½®æŠ˜æ‰£é€»è¾‘
  selectedRows.value.forEach(row => {
    row.discountType = batchDiscountForm.discountType
    row.discountValue = batchDiscountForm.discountValue
  })
  ElMessage.success(`已为 ${selectedRows.value.length} ä¸ªå•†å“è®¾ç½®æŠ˜æ‰£`)
  batchDiscountVisible.value = false
}
const openPriceControlDialog = () => {
  priceControlVisible.value = true
}
const handlePriceControl = () => {
  ElMessage.success('价格控制设置已保存')
  priceControlVisible.value = false
}
const openDiscountDialog = (row) => {
  // å•个商品折扣设置
  openDialog('edit', row)
}
const handleDelete = (row) => {
  ElMessageBox.confirm('确定要删除这条记录吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    const index = tableData.value.findIndex(item => item.id === row.id)
    if (index !== -1) {
      tableData.value.splice(index, 1)
      ElMessage.success('删除成功')
    }
  })
}
const handleBatchDelete = () => {
  if (selectedRows.value.length === 0) {
    ElMessage.warning('请先选择要删除的记录')
    return
  }
  ElMessageBox.confirm(`确定要删除选中的 ${selectedRows.value.length} æ¡è®°å½•吗?`, '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    selectedRows.value.forEach(row => {
      const index = tableData.value.findIndex(item => item.id === row.id)
      if (index !== -1) {
        tableData.value.splice(index, 1)
      }
    })
    ElMessage.success('批量删除成功')
    selectedRows.value = []
  })
}
const handleSelectionChange = (rows) => {
  selectedRows.value = rows
}
const handleSizeChange = (size) => {
  pagination.pageSize = size
  handleSearch()
}
const handleCurrentChange = (page) => {
  pagination.currentPage = page
  handleSearch()
}
const exportData = () => {
  ElMessage.success('数据导出功能开发中...')
}
// ç”Ÿå‘½å‘¨æœŸ
onMounted(() => {
  handleSearch()
})
</script>
<style scoped>
.app-container {
  padding: 20px;
}
.search-card, .action-card, .table-card {
  margin-bottom: 20px;
}
.action-buttons {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}
.product-info {
  line-height: 1.4;
}
.product-name {
  font-weight: bold;
  color: #303133;
}
.product-spec, .product-code {
  font-size: 12px;
  color: #909399;
}
.price-text {
  font-weight: bold;
  color: #409EFF;
}
.final-price {
  font-weight: bold;
  color: #67C23A;
  font-size: 16px;
}
.discount-value {
  font-size: 12px;
  color: #E6A23C;
  margin-top: 2px;
}
.no-discount {
  color: #C0C4CC;
  font-size: 12px;
}
.price-control {
  font-size: 12px;
  line-height: 1.3;
}
.control-item {
  color: #909399;
}
.warning-indicator {
  margin-top: 2px;
}
.pagination-wrapper {
  display: flex;
  justify-content: end;
  margin-top: 20px;
}
.selected-items {
  color: #409EFF;
  font-weight: bold;
}
.mt-2 {
  margin-top: 8px;
}
.ml-2 {
  margin-left: 8px;
}
:deep(.el-table) {
  font-size: 13px;
}
:deep(.el-table th) {
  background-color: #fafafa;
}
:deep(.el-card__body) {
  padding: 15px;
}
:deep(.el-divider__text) {
  font-weight: bold;
  color: #409EFF;
}
</style>
src/views/procurementManagement/arrivalManagement/index.vue
@@ -18,7 +18,6 @@
    <el-card class="table-card" shadow="never">
      <div class="table-header">
        <el-button type="primary" @click="openDialog('add')">新增到货</el-button>
        <el-button type="success" @click="handleBatchReceive">批量收货</el-button>
        <el-button type="danger" @click="handleBatchDelete">批量删除</el-button>
      </div>
@@ -37,23 +36,34 @@
        <el-table-column label="操作" width="200" align="center">
          <template #default="{ row }">
            <el-button type="primary" link @click="openDialog('edit', row)">编辑</el-button>
            <el-button type="success" link @click="handleReceive(row)">收货</el-button>
            <el-button type="success" v-if="row.status === 'pending'" link @click="handleReceive(row)">收货</el-button>
            <el-button type="danger" link @click="handleDelete(row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- åˆ†é¡µ -->
      <pagination
          :total="total"
          layout="total, sizes, prev, pager, next, jumper"
          :page="pagination.current"
          :limit="pagination.size"
          @pagination="handleCurrentChange"
      />
    </el-card>
    <el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? '新增到货' : '编辑到货'" width="600px">
      <el-form :model="formData" label-width="120px">
        <el-form-item label="到货单号">
          <el-input v-model="formData.arrivalNo" placeholder="到货单号" />
        </el-form-item>
        <el-form-item label="采购订单号">
          <el-select v-model="formData.orderNo" placeholder="请选择采购订单" style="width: 100%">
            <el-option label="PO20241201001" value="PO20241201001" />
            <el-option label="PO20241201002" value="PO20241201002" />
          </el-select>
          <el-input v-model="formData.orderNo" placeholder="采购订单号" />
        </el-form-item>
        <el-form-item label="供应商名称">
          <el-input v-model="formData.supplierName" placeholder="供应商名称" />
        </el-form-item>
        <el-form-item label="到货数量">
          <el-input-number  :min="0" v-model="formData.arrivalQuantity" placeholder="到货数量" />
        </el-form-item>
        <el-form-item label="备注">
          <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请输入备注信息" />
@@ -68,8 +78,40 @@
</template>
<script setup>
import { ref, reactive } from 'vue'
import { ref, reactive,onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {listPage,add,update,del} from "@/api/procurementManagement/arrivalManagement.js"
import Pagination from '@/components/PIMTable/Pagination.vue'
onMounted(() => {
    getList()
})
const tableData = ref([])
const getList = () => {
  loading.value = true
  listPage({...searchForm,...pagination}).then(res =>{
    if(res.code === 200){
      tableData.value = res.data.records
      total.value = res.data.total
      loading.value = false
    }
  })
}
const pagination = reactive({
  current: 1,
  size: 10
})
const total = ref(0)
const handleCurrentChange = (val) => {
  pagination.current = val.page
  pagination.size = val.limit
  getList()
}
const loading = ref(false)
const dialogVisible = ref(false)
@@ -82,25 +124,13 @@
})
const formData = reactive({
  arrivalNo: '',
  arrivalQuantity: 0,
  orderNo: '',
  supplierName: '',
  remark: ''
  remark: '',
  status: 'pending'
})
const mockData = [
  {
    id: 1,
    arrivalNo: 'AR20241201001',
    orderNo: 'PO20241201001',
    supplierName: '供应商A',
    status: 'received',
    arrivalQuantity: 250,
    arrivalTime: '2025-12-01 15:30:00',
    remark: '正常到货'
  }
]
const tableData = ref([...mockData])
const getStatusType = (status) => {
  const statusMap = { pending: 'warning', received: 'success', stored: 'info' }
@@ -114,7 +144,7 @@
const handleSearch = () => {
  loading.value = true
  setTimeout(() => { loading.value = false }, 500)
  getList()
}
const resetSearch = () => {
@@ -124,34 +154,45 @@
const openDialog = (type, row = {}) => {
  dialogType.value = type
  if (type === 'edit' && row.id) {
    Object.assign(formData, { orderNo: row.orderNo, supplierName: row.supplierName, remark: row.remark })
    obj.id = row.id
    Object.assign(formData, { orderNo: row.orderNo, supplierName: row.supplierName, remark: row.remark, arrivalQuantity: row.arrivalQuantity,arrivalNo: row.arrivalNo })
  } else {
    Object.assign(formData, { orderNo: '', supplierName: '', remark: '' })
    Object.assign(formData, { orderNo: '', supplierName: '', remark: '',arrivalQuantity: 0,arrivalNo: '' })
  }
  dialogVisible.value = true
}
const obj = reactive({
  id:''
})
const handleSubmit = () => {
  if (dialogType.value === 'add') {
    const newArrival = {
      id: Date.now(),
      arrivalNo: `AR${Date.now()}`,
      orderNo: formData.orderNo,
      supplierName: formData.supplierName,
      status: 'pending',
      arrivalQuantity: 0,
      arrivalTime: new Date().toLocaleString(),
      remark: formData.remark
    }
    tableData.value.unshift(newArrival)
    ElMessage.success('新增成功')
    add(formData).then(res => {
      if(res.code === 200){
        ElMessage.success('新增成功')
        getList()
      }
    })
  }else{
    update({...formData, ...obj}).then(res => {
      if(res.code === 200){
        ElMessage.success('编辑成功')
        getList()
      }
    })
  }
  dialogVisible.value = false
}
const handleReceive = (row) => {
  row.status = 'received'
  ElMessage.success('收货成功')
  update(row).then(res => {
    if(res.code === 200){
      ElMessage.success('收货成功')
      getList()
    }
  })
}
const handleDelete = (row) => {
@@ -160,20 +201,30 @@
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    const index = tableData.value.findIndex(item => item.id === row.id)
    if (index !== -1) {
      tableData.value.splice(index, 1)
      ElMessage.success('删除成功')
    }
    let ids = [row.id]
    del(ids).then(res => {
      if(res.code === 200){
        ElMessage.success('删除成功')
        getList()
      }
    })
  })
}
const handleBatchReceive = () => {
  ElMessage.success('批量收货成功')
}
const handleBatchDelete = () => {
  ElMessage.success('批量删除成功')
  ElMessageBox.confirm('确定要删除选中的记录吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    let ids = selectedRows.value.map(item => item.id)
    del(ids).then(res => {
      if(res.code === 200){
        ElMessage.success('删除成功')
        getList()
      }
    })
  })
}
const handleSelectionChange = (rows) => {
src/views/procurementManagement/procurementPlan/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,821 @@
<template>
  <div class="app-container">
    <!-- æœç´¢åŒºåŸŸ -->
    <el-card class="search-card" shadow="never">
      <el-form :model="searchForm" :inline="true" class="search-form">
        <el-form-item label="计划名称">
          <el-input v-model="searchForm.planName" placeholder="请输入计划名称" clearable />
        </el-form-item>
        <el-form-item label="状态">
          <el-select v-model="searchForm.status" placeholder="请选择状态" clearable style="width: 150px">
            <el-option label="启用" value="active" />
            <el-option label="禁用" value="inactive" />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleSearch">
            <el-icon><Search /></el-icon>
            æœç´¢
          </el-button>
          <el-button @click="handleReset">
            <el-icon><Refresh /></el-icon>
            é‡ç½®
          </el-button>
        </el-form-item>
      </el-form>
    </el-card>
    <!-- æ“ä½œæŒ‰é’® -->
    <el-card class="table-card" shadow="never">
      <div class="table-header">
        <div class="table-title">采购计划列表</div>
        <div class="table-actions">
          <el-button type="primary" @click="handleAdd">
            <el-icon><Plus /></el-icon>
            æ–°å¢žè®¡åˆ’
          </el-button>
          <el-button type="info" @click="handleExport">
            <el-icon><Download /></el-icon>
            å¯¼å‡º
          </el-button>
        </div>
      </div>
      <!-- æ•°æ®è¡¨æ ¼ -->
      <el-table
        v-loading="loading"
        :data="tableData"
        stripe
        border
        style="width: 100%"
      >
        <el-table-column prop="planName" label="计划名称" min-width="150" />
        <el-table-column prop="description" label="描述" min-width="200" show-overflow-tooltip />
        <el-table-column prop="formula" label="计算公式" min-width="200" show-overflow-tooltip>
          <template #default="{ row }">
            <el-tag type="info" size="small">{{ row.formula }}</el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="status" label="状态" width="80" align="center">
          <template #default="{ row }">
            <el-tag :type="row.status === 'active' ? 'success' : 'info'" size="small">
              {{ row.status === 'active' ? '启用' : '禁用' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="lastCalculateTime" label="最后计算时间" width="160" />
        <el-table-column label="操作" width="200" fixed="right" align="center">
          <template #default="{ row }">
            <el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
            <el-button type="success" link @click="handleCalculate(row)">计算</el-button>
            <el-button type="danger" link @click="handleDelete(row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- åˆ†é¡µ -->
      <div class="pagination-container">
        <el-pagination
          v-model:current-page="pagination.current"
          v-model:page-size="pagination.size"
          :page-sizes="[10, 20, 50, 100]"
          :total="pagination.total"
          layout="total, sizes, prev, pager, next, jumper"
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
        />
      </div>
    </el-card>
    <!-- æ–°å¢ž/编辑对话框 -->
    <el-dialog
      v-model="dialogVisible"
      :title="dialogType === 'add' ? '新增采购计划' : '编辑采购计划'"
      width="1000px"
      :close-on-click-modal="false"
    >
      <div class="form-container">
        <!-- åŸºæœ¬ä¿¡æ¯ -->
        <div class="form-section">
          <div class="section-title">基本信息</div>
          <el-form
            ref="formRef"
            :model="formData"
            :rules="formRules"
            label-width="120px"
          >
            <el-row :gutter="20">
              <el-col :span="12">
                <el-form-item label="编码" prop="code">
                  <el-input v-model="formData.code" placeholder="系统自动生成" disabled />
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item label="名称" prop="planName" required>
                  <el-input v-model="formData.planName" placeholder="请输入计划名称" />
                </el-form-item>
              </el-col>
            </el-row>
            <el-form-item label="描述" prop="description">
              <el-input
                v-model="formData.description"
                type="textarea"
                :rows="3"
                placeholder="请输入计划描述"
              />
            </el-form-item>
            <el-row :gutter="20">
              <el-col :span="12">
                <el-form-item label="数据状态" prop="dataStatus">
                  <el-select v-model="formData.dataStatus" placeholder="请选择状态" style="width: 100%">
                    <el-option label="草稿" value="draft" />
                    <el-option label="已审核" value="approved" />
                    <el-option label="已禁用" value="disabled" />
                  </el-select>
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item label="是否系统预置">
                  <el-checkbox v-model="formData.isSystemPreset">是</el-checkbox>
                </el-form-item>
              </el-col>
            </el-row>
          </el-form>
        </div>
        <!-- è®¡ç®—参数 -->
        <div class="form-section">
          <div class="section-title">计算参数</div>
          <el-tabs v-model="activeTab" class="param-tabs">
            <el-tab-pane label="需求参数" name="demand">
              <div class="checkbox-group">
                <el-checkbox v-model="formData.considerExistingStock">考虑现有库存</el-checkbox>
                <el-checkbox v-model="formData.warehouseMRPControl">仓库运行MRP的控制</el-checkbox>
                <el-checkbox v-model="formData.calculateTotalDemand">计算总需求</el-checkbox>
                <el-checkbox v-model="formData.considerSafetyStock">考虑安全库存</el-checkbox>
                <el-checkbox v-model="formData.considerLockedStock">考虑锁库</el-checkbox>
                <el-checkbox v-model="formData.notConsiderMaterialAux">不考虑物料辅助属性</el-checkbox>
                <el-checkbox v-model="formData.negativeStockAsDemand">负库存作为需求</el-checkbox>
              </div>
            </el-tab-pane>
            <el-tab-pane label="计算参数" name="calculation">
              <div class="checkbox-group">
                <el-checkbox v-model="formData.considerExistingStock">考虑现有库存</el-checkbox>
                <el-checkbox v-model="formData.warehouseMRPControl">仓库运行MRP的控制</el-checkbox>
                <el-checkbox v-model="formData.calculateTotalDemand">计算总需求</el-checkbox>
                <el-checkbox v-model="formData.considerSafetyStock">考虑安全库存</el-checkbox>
                <el-checkbox v-model="formData.considerLockedStock">考虑锁库</el-checkbox>
                <el-checkbox v-model="formData.notConsiderMaterialAux">不考虑物料辅助属性</el-checkbox>
                <el-checkbox v-model="formData.negativeStockAsDemand">负库存作为需求</el-checkbox>
              </div>
            </el-tab-pane>
          </el-tabs>
        </div>
        <!-- æ±‡æ€»åˆå¹¶é€‰é¡¹ -->
        <div class="form-section">
          <div class="section-title">汇总合并选项</div>
          <div class="checkbox-group">
            <el-checkbox v-model="formData.summaryMaterial">物料</el-checkbox>
            <el-checkbox v-model="formData.summaryAuxAttributes">辅助属性</el-checkbox>
            <el-checkbox v-model="formData.summaryDemandDate">需求日期</el-checkbox>
          </div>
        </div>
        <!-- è®¡ç®—公式 -->
        <div class="form-section">
          <div class="section-title">计算公式</div>
          <div class="formula-input-section">
            <el-form-item label="计算公式" prop="formula" required>
              <el-input
                v-model="formData.formula"
                placeholder="例如: é¢„计出库数量 - çŽ°æœ‰åº“å­˜ + å®‰å…¨åº“å­˜ - é¢„计入库数量"
                @input="validateFormula"
              />
            </el-form-item>
            <div class="formula-help">
              <el-text type="info" size="small">
                æ”¯æŒå˜é‡ï¼šé¢„计出库数量、现有库存、安全库存、预计入库数量
              </el-text>
            </div>
          </div>
        </div>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="dialogVisible = false">取消</el-button>
          <el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- äº§å“é€‰æ‹©å¯¹è¯æ¡† -->
    <el-dialog
      v-model="productSelectDialogVisible"
      title="选择产品"
      width="800px"
      :close-on-click-modal="false"
    >
      <div class="product-select">
        <el-alert
          title="请选择要计算的产品"
          type="info"
          :closable="false"
          show-icon
        >
          <template #default>
            <p>选择产品后,系统将根据当前计算公式和产品库存情况进行计算。</p>
          </template>
        </el-alert>
        <el-table
          v-loading="productLoading"
          :data="productList"
          @selection-change="handleProductSelectionChange"
          stripe
          border
          style="width: 100%; margin-top: 20px;"
        >
          <el-table-column type="selection" width="55" />
          <el-table-column prop="productName" label="产品名称" min-width="150" />
          <el-table-column prop="productCode" label="产品编码" width="120" />
          <el-table-column prop="existingStock" label="现有库存" width="100" align="right" />
          <el-table-column prop="safetyStock" label="安全库存" width="100" align="right" />
          <el-table-column prop="expectedOutbound" label="预计出库" width="100" align="right" />
          <el-table-column prop="expectedInbound" label="预计入库" width="100" align="right" />
        </el-table>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="productSelectDialogVisible = false">取消</el-button>
          <el-button type="primary" @click="handleConfirmProductSelection" :disabled="selectedProducts.length === 0">
            ç¡®è®¤è®¡ç®—
          </el-button>
        </div>
      </template>
    </el-dialog>
    <!-- è®¡ç®—结果对话框 -->
    <el-dialog
      v-model="calculateDialogVisible"
      title="采购计算结果"
      width="1000px"
      :close-on-click-modal="false"
    >
      <div class="calculate-result">
        <el-alert
          title="计算结果"
          type="success"
          :closable="false"
          show-icon
        >
          <template #default>
            <p>基于当前配置的计算公式和库存情况,系统已计算出各产品的采购需求。</p>
          </template>
        </el-alert>
        <el-table :data="calculateResult" stripe border style="width: 100%; margin-top: 20px;">
          <el-table-column prop="productName" label="产品名称" min-width="150" />
          <el-table-column prop="productCode" label="产品编码" width="120" />
          <el-table-column prop="existingStock" label="现有库存" width="100" align="right" />
          <el-table-column prop="safetyStock" label="安全库存" width="100" align="right" />
          <el-table-column prop="expectedOutbound" label="预计出库数量" width="120" align="right" />
          <el-table-column prop="expectedInbound" label="预计入库数量" width="120" align="right" />
          <el-table-column prop="weeklyNetDemand" label="按周净需求" width="120" align="right">
            <template #default="{ row }">
              <el-tag :type="row.weeklyNetDemand > 0 ? 'warning' : 'success'" size="small">
                {{ row.weeklyNetDemand }}
              </el-tag>
            </template>
          </el-table-column>
          <el-table-column prop="suggestedPurchase" label="建议采购" width="100" align="right">
            <template #default="{ row }">
              <el-tag :type="row.suggestedPurchase > 0 ? 'danger' : 'success'" size="small">
                {{ row.suggestedPurchase }}
              </el-tag>
            </template>
          </el-table-column>
        </el-table>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="calculateDialogVisible = false">关闭</el-button>
          <el-button type="primary" @click="handleCreatePurchaseOrder">生成采购订单</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search, Refresh, Plus, Download } from '@element-plus/icons-vue'
// å“åº”式数据
const loading = ref(false)
const submitLoading = ref(false)
const dialogVisible = ref(false)
const productSelectDialogVisible = ref(false)
const calculateDialogVisible = ref(false)
const dialogType = ref('add')
const productLoading = ref(false)
const selectedProducts = ref([])
const currentPlan = ref(null)
// æœç´¢è¡¨å•
const searchForm = reactive({
  planName: '',
  status: ''
})
// åˆ†é¡µæ•°æ®
const pagination = reactive({
  current: 1,
  size: 20,
  total: 0
})
// è¡¨å•数据
const formData = reactive({
  code: '',
  planName: '',
  description: '',
  dataStatus: '',
  isSystemPreset: false,
  formula: '',
  // è®¡ç®—参数
  considerExistingStock: false,
  warehouseMRPControl: false,
  calculateTotalDemand: false,
  considerSafetyStock: false,
  considerLockedStock: false,
  notConsiderMaterialAux: false,
  negativeStockAsDemand: false,
  // æ±‡æ€»åˆå¹¶é€‰é¡¹
  summaryMaterial: false,
  summaryAuxAttributes: false,
  summaryDemandDate: false
})
// å½“前激活的标签页
const activeTab = ref('demand')
// è¡¨å•验证规则
const formRules = {
  planName: [
    { required: true, message: '请输入计划名称', trigger: 'blur' }
  ],
  dataStatus: [
    { required: true, message: '请选择数据状态', trigger: 'change' }
  ],
  formula: [
    { required: true, message: '请输入计算公式', trigger: 'blur' }
  ]
}
// è¡¨æ ¼æ•°æ®
const tableData = ref([
  {
    id: 1,
    planName: '常规产品采购计划',
    description: '基于历史销售数据的常规产品采购计划',
    formula: '预计出库数量 - çŽ°æœ‰åº“å­˜ + å®‰å…¨åº“å­˜ - é¢„计入库数量',
    status: 'active',
    lastCalculateTime: '2024-12-01 10:30:00',
    remark: '适用于常规产品'
  },
  {
    id: 2,
    planName: '季节性产品采购计划',
    description: '考虑季节因素的季节性产品采购计划',
    formula: '预计出库数量 - çŽ°æœ‰åº“å­˜ + å®‰å…¨åº“å­˜ * 1.2 - é¢„计入库数量',
    status: 'active',
    lastCalculateTime: '2024-11-28 15:20:00',
    remark: '适用于季节性产品'
  }
])
// äº§å“åˆ—表数据
const productList = ref([
  {
    id: 1,
    productName: '产品A',
    productCode: 'PA001',
    existingStock: 100,
    safetyStock: 50,
    expectedOutbound: 200,
    expectedInbound: 30
  },
  {
    id: 2,
    productName: '产品B',
    productCode: 'PB002',
    existingStock: 80,
    safetyStock: 30,
    expectedOutbound: 150,
    expectedInbound: 20
  },
  {
    id: 3,
    productName: '产品C',
    productCode: 'PC003',
    existingStock: 120,
    safetyStock: 60,
    expectedOutbound: 180,
    expectedInbound: 25
  },
  {
    id: 4,
    productName: '产品D',
    productCode: 'PD004',
    existingStock: 90,
    safetyStock: 40,
    expectedOutbound: 160,
    expectedInbound: 35
  }
])
// è®¡ç®—结果数据
const calculateResult = ref([
  {
    productName: '产品A',
    existingStock: 100,
    safetyStock: 50,
    expectedOutbound: 200,
    expectedInbound: 30,
    weeklyNetDemand: 120,
    suggestedPurchase: 150
  },
  {
    productName: '产品B',
    existingStock: 80,
    safetyStock: 30,
    expectedOutbound: 150,
    expectedInbound: 20,
    weeklyNetDemand: 100,
    suggestedPurchase: 120
  }
])
// æ–¹æ³•
const handleSearch = () => {
  pagination.current = 1
  loadData()
}
const handleReset = () => {
  Object.assign(searchForm, {
    planName: '',
    status: ''
  })
  handleSearch()
}
const loadData = () => {
  loading.value = true
  // æ¨¡æ‹ŸAPI调用
  setTimeout(() => {
    pagination.total = tableData.value.length
    loading.value = false
  }, 500)
}
const handleAdd = () => {
  dialogType.value = 'add'
  resetForm()
  // è‡ªåŠ¨ç”Ÿæˆç¼–ç 
  formData.code = 'CGJH' + String(Date.now()).slice(-4)
  dialogVisible.value = true
}
const handleEdit = (row) => {
  dialogType.value = 'edit'
  Object.assign(formData, row)
  dialogVisible.value = true
}
const handleDelete = async (row) => {
  try {
    await ElMessageBox.confirm('确定要删除这个采购计划吗?', '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    })
    const index = tableData.value.findIndex(item => item.id === row.id)
    if (index > -1) {
      tableData.value.splice(index, 1)
      ElMessage.success('删除成功')
      loadData()
    }
  } catch {
    // ç”¨æˆ·å–消删除
  }
}
const handleSubmit = async () => {
  try {
    // è¡¨å•验证
    if (!formData.planName || !formData.formula) {
      ElMessage.error('请填写必填项')
      return
    }
    submitLoading.value = true
    if (dialogType.value === 'add') {
      // æ–°å¢ž
      const newItem = {
        ...formData,
        id: Date.now(),
        lastCalculateTime: '-'
      }
      tableData.value.unshift(newItem)
      ElMessage.success('新增成功')
    } else {
      // ç¼–辑
      const index = tableData.value.findIndex(item => item.id === formData.id)
      if (index > -1) {
        tableData.value[index] = { ...formData }
        ElMessage.success('编辑成功')
      }
    }
    dialogVisible.value = false
    loadData()
  } catch (error) {
    ElMessage.error('操作失败')
  } finally {
    submitLoading.value = false
  }
}
const resetForm = () => {
  Object.assign(formData, {
    code: '',
    planName: '',
    description: '',
    dataStatus: '',
    isSystemPreset: false,
    formula: '',
    // è®¡ç®—参数
    considerExistingStock: false,
    warehouseMRPControl: false,
    calculateTotalDemand: false,
    considerSafetyStock: false,
    considerLockedStock: false,
    notConsiderMaterialAux: false,
    negativeStockAsDemand: false,
    // æ±‡æ€»åˆå¹¶é€‰é¡¹
    summaryMaterial: false,
    summaryAuxAttributes: false,
    summaryDemandDate: false
  })
  activeTab.value = 'demand'
}
const validateFormula = () => {
  // ç®€å•的公式验证
  const formula = formData.formula
  if (formula && !/^[a-zA-Z\u4e00-\u9fa5\s\*\+\-\/\(\)\d\.]+$/.test(formula)) {
    ElMessage.warning('公式格式可能不正确,请检查')
  }
}
const handleCalculate = (row) => {
  currentPlan.value = row
  productSelectDialogVisible.value = true
  loadProductList()
}
const loadProductList = () => {
  productLoading.value = true
  // æ¨¡æ‹ŸåŠ è½½äº§å“æ•°æ®
  setTimeout(() => {
    productLoading.value = false
  }, 500)
}
const handleProductSelectionChange = (selection) => {
  selectedProducts.value = selection
}
const handleConfirmProductSelection = () => {
  if (selectedProducts.value.length === 0) {
    ElMessage.warning('请选择要计算的产品')
    return
  }
  ElMessage.success(`正在计算 ${currentPlan.value.planName} çš„采购需求...`)
  productSelectDialogVisible.value = false
  // æ ¹æ®é€‰æ‹©çš„产品和计算公式进行计算
  calculateWithSelectedProducts()
}
const calculateWithSelectedProducts = () => {
  // æ¨¡æ‹Ÿè®¡ç®—过程
  setTimeout(() => {
    // æ ¹æ®é€‰æ‹©çš„产品更新计算结果
    const result = selectedProducts.value.map(product => {
      // è¿™é‡Œåº”该根据实际的计算公式进行计算
      // ç¤ºä¾‹ï¼šé¢„计出库数量 - çŽ°æœ‰åº“å­˜ + å®‰å…¨åº“å­˜ - é¢„计入库数量
      const weeklyNetDemand = product.expectedOutbound - product.existingStock + product.safetyStock - product.expectedInbound
      const suggestedPurchase = Math.max(0, weeklyNetDemand)
      return {
        productName: product.productName,
        productCode: product.productCode,
        existingStock: product.existingStock,
        safetyStock: product.safetyStock,
        expectedOutbound: product.expectedOutbound,
        expectedInbound: product.expectedInbound,
        weeklyNetDemand: weeklyNetDemand,
        suggestedPurchase: suggestedPurchase
      }
    })
    calculateResult.value = result
    calculateDialogVisible.value = true
  }, 1000)
}
const handleCreatePurchaseOrder = () => {
  ElMessage.success('正在生成采购订单...')
  calculateDialogVisible.value = false
}
const handleExport = () => {
  ElMessage.success('正在导出数据...')
}
const handleSizeChange = (size) => {
  pagination.size = size
  loadData()
}
const handleCurrentChange = (current) => {
  pagination.current = current
  loadData()
}
// ç”Ÿå‘½å‘¨æœŸ
onMounted(() => {
  loadData()
})
</script>
<style scoped>
.app-container {
  padding: 20px;
}
.page-header {
  margin-bottom: 20px;
}
.page-header h2 {
  margin: 0 0 8px 0;
  color: #303133;
  font-size: 24px;
  font-weight: 600;
}
.page-header p {
  margin: 0;
  color: #909399;
  font-size: 14px;
}
.search-card {
  margin-bottom: 20px;
}
.search-form {
  margin-bottom: 0;
}
.table-card {
  margin-bottom: 20px;
}
.table-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}
.table-title {
  font-size: 16px;
  font-weight: 600;
  color: #303133;
}
.table-actions {
  display: flex;
  gap: 10px;
}
.pagination-container {
  margin-top: 20px;
  display: flex;
  justify-content: end;
}
.form-container {
  padding: 0 20px;
}
.formula-help {
  margin-top: 5px;
}
.calculate-result {
  padding: 20px 0;
}
.dialog-footer {
  text-align: right;
}
:deep(.el-card__body) {
  padding: 20px;
}
:deep(.el-table) {
  font-size: 14px;
}
:deep(.el-form-item__label) {
  font-weight: 500;
}
.form-container {
  padding: 0;
}
.form-section {
  margin-bottom: 24px;
  border: 1px solid #e4e7ed;
  border-radius: 6px;
  overflow: hidden;
}
.section-title {
  background-color: #f5f7fa;
  padding: 12px 16px;
  font-weight: 600;
  color: #303133;
  border-bottom: 1px solid #e4e7ed;
}
.form-section .el-form {
  padding: 20px;
}
.param-tabs {
  padding: 20px;
}
.param-tabs :deep(.el-tabs__header) {
  margin-bottom: 20px;
}
.param-tabs :deep(.el-tabs__item.is-active) {
  color: #f56c6c;
  border-bottom-color: #f56c6c;
}
.checkbox-group {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
}
.checkbox-group .el-checkbox {
  margin-right: 0;
  margin-bottom: 8px;
}
.formula-input-section {
  padding: 20px;
}
.formula-input-section .el-form-item {
  margin-bottom: 12px;
}
.formula-help {
  text-align: center;
  margin-top: 8px;
}
</style>
src/views/procurementManagement/procurementReport/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,847 @@
<template>
  <div class="app-container">
    <!-- æŠ¥è¡¨é€‰æ‹©å™¨ -->
    <el-card class="report-selector" shadow="never">
      <el-tabs v-model="activeReport" @tab-change="handleReportChange">
        <el-tab-pane label="采购订单执行汇总表" name="orderSummary">
          <template #label>
            <span class="tab-label">
              <el-icon><Document /></el-icon>
              é‡‡è´­è®¢å•执行汇总表
            </span>
          </template>
        </el-tab-pane>
        <el-tab-pane label="采购订单执行明细表" name="orderDetail">
          <template #label>
            <span class="tab-label">
              <el-icon><List /></el-icon>
              é‡‡è´­è®¢å•执行明细表
            </span>
          </template>
        </el-tab-pane>
        <el-tab-pane label="采购业务汇总表" name="businessSummary">
          <template #label>
            <span class="tab-label">
              <el-icon><TrendCharts /></el-icon>
              é‡‡è´­ä¸šåŠ¡æ±‡æ€»è¡¨
            </span>
          </template>
        </el-tab-pane>
        <el-tab-pane label="供应商供货汇总表" name="supplierSummary">
          <template #label>
            <span class="tab-label">
              <el-icon><Shop /></el-icon>
              ä¾›åº”商供货汇总表
            </span>
          </template>
        </el-tab-pane>
      </el-tabs>
    </el-card>
    <!-- æŸ¥è¯¢æ¡ä»¶ -->
    <el-card class="search-card" shadow="never">
      <el-form :model="searchForm" :inline="true" class="search-form">
        <el-form-item label="时间范围:">
          <el-date-picker
            v-model="searchForm.dateRange"
            type="daterange"
            range-separator="至"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
            format="YYYY-MM-DD"
            value-format="YYYY-MM-DD"
            style="width: 240px"
          />
        </el-form-item>
        <el-form-item label="供应商:" v-if="activeReport === 'supplierSummary'">
          <el-select v-model="searchForm.supplierId" placeholder="请选择供应商" clearable style="width: 200px">
            <el-option
              v-for="supplier in supplierList"
              :key="supplier.id"
              :label="supplier.name"
              :value="supplier.id"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="商品类别:" v-if="activeReport === 'businessSummary'">
          <el-select v-model="searchForm.categoryId" placeholder="请选择商品类别" clearable style="width: 200px">
            <el-option
              v-for="category in categoryList"
              :key="category.id"
              :label="category.name"
              :value="category.id"
            />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleSearch" :loading="loading">
            <el-icon><Search /></el-icon>
            æŸ¥è¯¢
          </el-button>
          <el-button @click="resetSearch">
            <el-icon><Refresh /></el-icon>
            é‡ç½®
          </el-button>
          <el-button type="success" @click="exportReport">
            <el-icon><Download /></el-icon>
            å¯¼å‡º
          </el-button>
        </el-form-item>
      </el-form>
    </el-card>
    <!-- æŠ¥è¡¨å†…容 -->
    <el-card class="report-content" shadow="never">
      <!-- é‡‡è´­è®¢å•执行汇总表 -->
      <div v-if="activeReport === 'orderSummary'" class="report-section">
        <div class="section-header">
          <h3>采购订单执行汇总表</h3>
          <div class="summary-stats">
            <div class="stat-item">
              <span class="stat-label">总订单数:</span>
              <span class="stat-value">{{ orderSummaryStats.totalOrders }}</span>
            </div>
            <div class="stat-item">
              <span class="stat-label">总金额:</span>
              <span class="stat-value">Â¥{{ orderSummaryStats.totalAmount.toLocaleString() }}</span>
            </div>
            <div class="stat-item">
              <span class="stat-label">完成率:</span>
              <span class="stat-value">{{ orderSummaryStats.completionRate }}%</span>
            </div>
          </div>
        </div>
        <el-table :data="orderSummaryData" border v-loading="loading" stripe>
          <el-table-column label="订单编号" prop="orderNo" width="180" fixed="left" />
          <el-table-column label="供应商名称" prop="supplierName" width="150" />
          <el-table-column label="订单日期" prop="orderDate" width="120" />
          <el-table-column label="计划交期" prop="plannedDelivery" width="120" />
          <el-table-column label="实际交期" prop="actualDelivery" width="120" />
          <el-table-column label="订单金额" prop="orderAmount" width="120">
            <template #default="{ row }">Â¥{{ row.orderAmount.toLocaleString() }}</template>
          </el-table-column>
          <el-table-column label="已付金额" prop="paidAmount" width="120">
            <template #default="{ row }">Â¥{{ row.paidAmount.toLocaleString() }}</template>
          </el-table-column>
          <el-table-column label="完成状态" prop="status" width="100">
            <template #default="{ row }">
              <el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag>
            </template>
          </el-table-column>
          <el-table-column label="完成率" prop="completionRate" width="100">
            <template #default="{ row }">{{ row.completionRate }}%</template>
          </el-table-column>
          <el-table-column label="延迟天数" prop="delayDays" width="100">
            <template #default="{ row }">
              <span :class="{ 'delay-text': row.delayDays > 0 }">{{ row.delayDays }}</span>
            </template>
          </el-table-column>
        </el-table>
      </div>
      <!-- é‡‡è´­è®¢å•执行明细表 -->
      <div v-if="activeReport === 'orderDetail'" class="report-section">
        <div class="section-header">
          <h3>采购订单执行明细表</h3>
          <div class="summary-stats">
            <div class="stat-item">
              <span class="stat-label">明细条数:</span>
              <span class="stat-value">{{ orderDetailStats.totalItems }}</span>
            </div>
            <div class="stat-item">
              <span class="stat-label">已收货:</span>
              <span class="stat-value">{{ orderDetailStats.receivedItems }}</span>
            </div>
            <div class="stat-item">
              <span class="stat-label">待收货:</span>
              <span class="stat-value">{{ orderDetailStats.pendingItems }}</span>
            </div>
          </div>
        </div>
        <el-table :data="orderDetailData" border v-loading="loading" stripe>
          <el-table-column label="订单编号" prop="orderNo" width="150" fixed="left" />
          <el-table-column label="商品编码" prop="productCode" width="120" />
          <el-table-column label="商品名称" prop="productName" width="200" />
          <el-table-column label="规格型号" prop="specification" width="150" />
          <el-table-column label="单位" prop="unit" width="80" />
          <el-table-column label="计划数量" prop="plannedQuantity" width="100" />
          <el-table-column label="已收货数量" prop="receivedQuantity" width="120" />
          <el-table-column label="待收货数量" prop="pendingQuantity" width="120" />
          <el-table-column label="单价" prop="unitPrice" width="100">
            <template #default="{ row }">Â¥{{ row.unitPrice.toFixed(2) }}</template>
          </el-table-column>
          <el-table-column label="小计" prop="subtotal" width="120">
            <template #default="{ row }">Â¥{{ row.subtotal.toLocaleString() }}</template>
          </el-table-column>
          <el-table-column label="收货状态" prop="status" width="100">
            <template #default="{ row }">
              <el-tag :type="getReceiptStatusType(row.status)">{{ getReceiptStatusText(row.status) }}</el-tag>
            </template>
          </el-table-column>
          <el-table-column label="最后收货日期" prop="lastReceiptDate" width="120" />
        </el-table>
      </div>
      <!-- é‡‡è´­ä¸šåŠ¡æ±‡æ€»è¡¨ -->
      <div v-if="activeReport === 'businessSummary'" class="report-section">
        <div class="section-header">
          <h3>采购业务汇总表</h3>
          <div class="summary-stats">
            <div class="stat-item">
              <span class="stat-label">采购总额:</span>
              <span class="stat-value">Â¥{{ businessSummaryStats.totalAmount.toLocaleString() }}</span>
            </div>
            <div class="stat-item">
              <span class="stat-label">商品种类:</span>
              <span class="stat-value">{{ businessSummaryStats.productTypes }}</span>
            </div>
            <div class="stat-item">
              <span class="stat-label">供应商数:</span>
              <span class="stat-value">{{ businessSummaryStats.supplierCount }}</span>
            </div>
          </div>
        </div>
        <el-table :data="businessSummaryData" border v-loading="loading" stripe>
          <el-table-column label="商品类别" prop="category" width="150" fixed="left" />
          <el-table-column label="商品编码" prop="productCode" width="120" />
          <el-table-column label="商品名称" prop="productName" width="200" />
          <el-table-column label="规格型号" prop="specification" width="150" />
          <el-table-column label="采购数量" prop="purchaseQuantity" width="120" />
          <el-table-column label="采购金额" prop="purchaseAmount" width="120">
            <template #default="{ row }">Â¥{{ row.purchaseAmount.toLocaleString() }}</template>
          </el-table-column>
          <el-table-column label="平均单价" prop="avgPrice" width="100">
            <template #default="{ row }">Â¥{{ row.avgPrice.toFixed(2) }}</template>
          </el-table-column>
          <el-table-column label="采购次数" prop="purchaseCount" width="100" />
          <el-table-column label="主要供应商" prop="mainSupplier" width="150" />
          <el-table-column label="最后采购日期" prop="lastPurchaseDate" width="120" />
        </el-table>
      </div>
      <!-- ä¾›åº”商供货汇总表 -->
      <div v-if="activeReport === 'supplierSummary'" class="report-section">
        <div class="section-header">
          <h3>供应商供货汇总表</h3>
          <div class="summary-stats">
            <div class="stat-item">
              <span class="stat-label">供应商总数:</span>
              <span class="stat-value">{{ supplierSummaryStats.totalSuppliers }}</span>
            </div>
            <div class="stat-item">
              <span class="stat-label">供货总额:</span>
              <span class="stat-value">Â¥{{ supplierSummaryStats.totalAmount.toLocaleString() }}</span>
            </div>
            <div class="stat-item">
              <span class="stat-label">平均评分:</span>
              <span class="stat-value">{{ supplierSummaryStats.avgRating.toFixed(1) }}</span>
            </div>
          </div>
        </div>
        <el-table :data="supplierSummaryData" border v-loading="loading" stripe>
          <el-table-column label="供应商编码" prop="supplierCode" width="120" fixed="left" />
          <el-table-column label="供应商名称" prop="supplierName" width="200" />
          <el-table-column label="联系人" prop="contactPerson" width="120" />
          <el-table-column label="联系电话" prop="phone" width="130" />
          <el-table-column label="供货订单数" prop="orderCount" width="120" />
          <el-table-column label="供货金额" prop="supplyAmount" width="120">
            <template #default="{ row }">Â¥{{ row.supplyAmount.toLocaleString() }}</template>
          </el-table-column>
          <el-table-column label="已付金额" prop="paidAmount" width="120">
            <template #default="{ row }">Â¥{{ row.paidAmount.toLocaleString() }}</template>
          </el-table-column>
          <el-table-column label="未付金额" prop="unpaidAmount" width="120">
            <template #default="{ row }">Â¥{{ row.unpaidAmount.toLocaleString() }}</template>
          </el-table-column>
          <el-table-column label="按时交货率" prop="onTimeRate" width="120">
            <template #default="{ row }">{{ row.onTimeRate }}%</template>
          </el-table-column>
          <el-table-column label="质量评分" prop="qualityRating" width="100">
            <template #default="{ row }">
              <el-rate v-model="row.qualityRating" disabled show-score text-color="#ff9900" />
            </template>
          </el-table-column>
          <el-table-column label="合作状态" prop="status" width="100">
            <template #default="{ row }">
              <el-tag :type="getSupplierStatusType(row.status)">{{ getSupplierStatusText(row.status) }}</el-tag>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </el-card>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { Document, List, TrendCharts, Shop, Search, Refresh, Download } from '@element-plus/icons-vue'
// å“åº”式数据
const loading = ref(false)
const activeReport = ref('orderSummary')
// æœç´¢è¡¨å•
const searchForm = reactive({
  dateRange: [],
  supplierId: '',
  categoryId: ''
})
// ä¾›åº”商列表
const supplierList = ref([
  { id: 1, name: '江苏华联电子科技有限公司' },
  { id: 2, name: '上海精密机械制造有限公司' },
  { id: 3, name: '深圳智能设备有限公司' },
  { id: 4, name: '北京新材料科技有限公司' },
  { id: 5, name: '广州电子元器件有限公司' }
])
// å•†å“ç±»åˆ«åˆ—表
const categoryList = ref([
  { id: 1, name: '电子元器件' },
  { id: 2, name: '机械设备' },
  { id: 3, name: '原材料' },
  { id: 4, name: '办公用品' },
  { id: 5, name: '包装材料' }
])
// ç»Ÿè®¡æ•°æ®
const orderSummaryStats = ref({
  totalOrders: 156,
  totalAmount: 2580000,
  completionRate: 87.5
})
const orderDetailStats = ref({
  totalItems: 1248,
  receivedItems: 1089,
  pendingItems: 159
})
const businessSummaryStats = ref({
  totalAmount: 2580000,
  productTypes: 89,
  supplierCount: 25
})
const supplierSummaryStats = ref({
  totalSuppliers: 25,
  totalAmount: 2580000,
  avgRating: 4.2
})
// é‡‡è´­è®¢å•执行汇总表数据
const orderSummaryData = ref([
  {
    orderNo: 'PO20241201001',
    supplierName: '江苏华联电子科技有限公司',
    orderDate: '2024-12-01',
    plannedDelivery: '2024-12-15',
    actualDelivery: '2024-12-14',
    orderAmount: 125000,
    paidAmount: 100000,
    status: 'completed',
    completionRate: 100,
    delayDays: -1
  },
  {
    orderNo: 'PO20241201002',
    supplierName: '上海精密机械制造有限公司',
    orderDate: '2024-12-02',
    plannedDelivery: '2024-12-20',
    actualDelivery: '2024-12-22',
    orderAmount: 280000,
    paidAmount: 140000,
    status: 'partial',
    completionRate: 75,
    delayDays: 2
  },
  {
    orderNo: 'PO20241201003',
    supplierName: '深圳智能设备有限公司',
    orderDate: '2024-12-03',
    plannedDelivery: '2024-12-25',
    actualDelivery: '',
    orderAmount: 180000,
    paidAmount: 0,
    status: 'pending',
    completionRate: 0,
    delayDays: 0
  },
  {
    orderNo: 'PO20241201004',
    supplierName: '北京新材料科技有限公司',
    orderDate: '2024-12-04',
    plannedDelivery: '2024-12-18',
    actualDelivery: '2024-12-18',
    orderAmount: 95000,
    paidAmount: 95000,
    status: 'completed',
    completionRate: 100,
    delayDays: 0
  },
  {
    orderNo: 'PO20241201005',
    supplierName: '广州电子元器件有限公司',
    orderDate: '2024-12-05',
    plannedDelivery: '2024-12-28',
    actualDelivery: '',
    orderAmount: 220000,
    paidAmount: 0,
    status: 'pending',
    completionRate: 0,
    delayDays: 0
  }
])
// é‡‡è´­è®¢å•执行明细表数据
const orderDetailData = ref([
  {
    orderNo: 'PO20241201001',
    productCode: 'EL001',
    productName: '电阻器 1KΩ Â±5%',
    specification: '1/4W ç¢³è†œç”µé˜»',
    unit: '个',
    plannedQuantity: 1000,
    receivedQuantity: 1000,
    pendingQuantity: 0,
    unitPrice: 0.15,
    subtotal: 150,
    status: 'completed',
    lastReceiptDate: '2024-12-14'
  },
  {
    orderNo: 'PO20241201001',
    productCode: 'EL002',
    productName: '电容器 100μF',
    specification: '25V é“ç”µè§£ç”µå®¹',
    unit: '个',
    plannedQuantity: 500,
    receivedQuantity: 500,
    pendingQuantity: 0,
    unitPrice: 0.85,
    subtotal: 425,
    status: 'completed',
    lastReceiptDate: '2024-12-14'
  },
  {
    orderNo: 'PO20241201002',
    productCode: 'ME001',
    productName: '精密轴承',
    specification: '6205-2RS æ·±æ²Ÿçƒè½´æ‰¿',
    unit: '个',
    plannedQuantity: 200,
    receivedQuantity: 150,
    pendingQuantity: 50,
    unitPrice: 25.5,
    subtotal: 5100,
    status: 'partial',
    lastReceiptDate: '2024-12-20'
  },
  {
    orderNo: 'PO20241201002',
    productCode: 'ME002',
    productName: '不锈钢螺丝',
    specification: 'M8×20 304不锈钢',
    unit: '个',
    plannedQuantity: 1000,
    receivedQuantity: 1000,
    pendingQuantity: 0,
    unitPrice: 0.8,
    subtotal: 800,
    status: 'completed',
    lastReceiptDate: '2024-12-20'
  },
  {
    orderNo: 'PO20241201003',
    productCode: 'SM001',
    productName: '智能传感器',
    specification: '温度传感器 DS18B20',
    unit: '个',
    plannedQuantity: 300,
    receivedQuantity: 0,
    pendingQuantity: 300,
    unitPrice: 12.5,
    subtotal: 3750,
    status: 'pending',
    lastReceiptDate: ''
  }
])
// é‡‡è´­ä¸šåŠ¡æ±‡æ€»è¡¨æ•°æ®
const businessSummaryData = ref([
  {
    category: '电子元器件',
    productCode: 'EL001',
    productName: '电阻器 1KΩ Â±5%',
    specification: '1/4W ç¢³è†œç”µé˜»',
    purchaseQuantity: 5000,
    purchaseAmount: 750,
    avgPrice: 0.15,
    purchaseCount: 8,
    mainSupplier: '江苏华联电子科技有限公司',
    lastPurchaseDate: '2024-12-01'
  },
  {
    category: '电子元器件',
    productCode: 'EL002',
    productName: '电容器 100μF',
    specification: '25V é“ç”µè§£ç”µå®¹',
    purchaseQuantity: 2500,
    purchaseAmount: 2125,
    avgPrice: 0.85,
    purchaseCount: 6,
    mainSupplier: '江苏华联电子科技有限公司',
    lastPurchaseDate: '2024-12-01'
  },
  {
    category: '机械设备',
    productCode: 'ME001',
    productName: '精密轴承',
    specification: '6205-2RS æ·±æ²Ÿçƒè½´æ‰¿',
    purchaseQuantity: 800,
    purchaseAmount: 20400,
    avgPrice: 25.5,
    purchaseCount: 4,
    mainSupplier: '上海精密机械制造有限公司',
    lastPurchaseDate: '2024-12-02'
  },
  {
    category: '机械设备',
    productCode: 'ME002',
    productName: '不锈钢螺丝',
    specification: 'M8×20 304不锈钢',
    purchaseQuantity: 5000,
    purchaseAmount: 4000,
    avgPrice: 0.8,
    purchaseCount: 12,
    mainSupplier: '上海精密机械制造有限公司',
    lastPurchaseDate: '2024-12-02'
  },
  {
    category: '智能设备',
    productCode: 'SM001',
    productName: '智能传感器',
    specification: '温度传感器 DS18B20',
    purchaseQuantity: 1200,
    purchaseAmount: 15000,
    avgPrice: 12.5,
    purchaseCount: 5,
    mainSupplier: '深圳智能设备有限公司',
    lastPurchaseDate: '2024-12-03'
  }
])
// ä¾›åº”商供货汇总表数据
const supplierSummaryData = ref([
  {
    supplierCode: 'SUP001',
    supplierName: '江苏华联电子科技有限公司',
    contactPerson: '张经理',
    phone: '0512-88888888',
    orderCount: 45,
    supplyAmount: 850000,
    paidAmount: 680000,
    unpaidAmount: 170000,
    onTimeRate: 95,
    qualityRating: 4.5,
    status: 'active'
  },
  {
    supplierCode: 'SUP002',
    supplierName: '上海精密机械制造有限公司',
    contactPerson: '李总',
    phone: '021-66666666',
    orderCount: 32,
    supplyAmount: 1200000,
    paidAmount: 900000,
    unpaidAmount: 300000,
    onTimeRate: 88,
    qualityRating: 4.2,
    status: 'active'
  },
  {
    supplierCode: 'SUP003',
    supplierName: '深圳智能设备有限公司',
    contactPerson: '王工程师',
    phone: '0755-77777777',
    orderCount: 28,
    supplyAmount: 680000,
    paidAmount: 400000,
    unpaidAmount: 280000,
    onTimeRate: 92,
    qualityRating: 4.3,
    status: 'active'
  },
  {
    supplierCode: 'SUP004',
    supplierName: '北京新材料科技有限公司',
    contactPerson: '陈博士',
    phone: '010-55555555',
    orderCount: 18,
    supplyAmount: 320000,
    paidAmount: 250000,
    unpaidAmount: 70000,
    onTimeRate: 85,
    qualityRating: 4.0,
    status: 'active'
  },
  {
    supplierCode: 'SUP005',
    supplierName: '广州电子元器件有限公司',
    contactPerson: '刘经理',
    phone: '020-44444444',
    orderCount: 22,
    supplyAmount: 480000,
    paidAmount: 200000,
    unpaidAmount: 280000,
    onTimeRate: 78,
    qualityRating: 3.8,
    status: 'warning'
  }
])
// æ–¹æ³•
const handleReportChange = (tabName) => {
  activeReport.value = tabName
  handleSearch()
}
const handleSearch = () => {
  loading.value = true
  // æ¨¡æ‹ŸAPI调用
  setTimeout(() => {
    loading.value = false
    ElMessage.success('查询完成')
  }, 1000)
}
const resetSearch = () => {
  Object.assign(searchForm, {
    dateRange: [],
    supplierId: '',
    categoryId: ''
  })
  handleSearch()
}
const exportReport = () => {
  ElMessage.success('导出功能开发中...')
}
// çŠ¶æ€ç›¸å…³æ–¹æ³•
const getStatusType = (status) => {
  const statusMap = {
    completed: 'success',
    partial: 'warning',
    pending: 'info'
  }
  return statusMap[status] || 'info'
}
const getStatusText = (status) => {
  const statusMap = {
    completed: '已完成',
    partial: '部分完成',
    pending: '待执行'
  }
  return statusMap[status] || '未知'
}
const getReceiptStatusType = (status) => {
  const statusMap = {
    completed: 'success',
    partial: 'warning',
    pending: 'info'
  }
  return statusMap[status] || 'info'
}
const getReceiptStatusText = (status) => {
  const statusMap = {
    completed: '已收货',
    partial: '部分收货',
    pending: '待收货'
  }
  return statusMap[status] || '未知'
}
const getSupplierStatusType = (status) => {
  const statusMap = {
    active: 'success',
    warning: 'warning',
    inactive: 'info'
  }
  return statusMap[status] || 'info'
}
const getSupplierStatusText = (status) => {
  const statusMap = {
    active: '正常合作',
    warning: '需关注',
    inactive: '暂停合作'
  }
  return statusMap[status] || '未知'
}
onMounted(() => {
  // è®¾ç½®é»˜è®¤æ—¶é—´èŒƒå›´ä¸ºæœ€è¿‘30天
  const endDate = new Date()
  const startDate = new Date()
  startDate.setDate(startDate.getDate() - 30)
  searchForm.dateRange = [
    startDate.toISOString().split('T')[0],
    endDate.toISOString().split('T')[0]
  ]
})
</script>
<style scoped>
.app-container {
  padding: 20px;
  background-color: #f5f7fa;
  min-height: 100vh;
}
.page-header {
  text-align: center;
  margin-bottom: 20px;
  padding: 20px;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  border-radius: 10px;
  color: white;
}
.page-header h2 {
  margin: 0 0 10px 0;
  font-size: 28px;
  font-weight: 600;
}
.page-header p {
  margin: 0;
  font-size: 16px;
  opacity: 0.9;
}
.report-selector {
  margin-bottom: 20px;
  border-radius: 8px;
}
.tab-label {
  display: flex;
  align-items: center;
  gap: 8px;
}
.search-card {
  margin-bottom: 20px;
  border-radius: 8px;
}
.search-form {
  margin-bottom: 0;
}
.report-content {
  border-radius: 8px;
}
.report-section {
  min-height: 400px;
}
.section-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
  padding-bottom: 15px;
  border-bottom: 2px solid #e4e7ed;
}
.section-header h3 {
  margin: 0;
  font-size: 20px;
  font-weight: 600;
  color: #303133;
}
.summary-stats {
  display: flex;
  gap: 30px;
}
.stat-item {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.stat-label {
  font-size: 14px;
  color: #606266;
  margin-bottom: 5px;
}
.stat-value {
  font-size: 18px;
  font-weight: 600;
  color: #409EFF;
}
.delay-text {
  color: #F56C6C;
  font-weight: 600;
}
:deep(.el-table) {
  border-radius: 8px;
  overflow: hidden;
}
:deep(.el-table th) {
  background-color: #f8f9fa;
  color: #606266;
  font-weight: 600;
}
:deep(.el-table--striped .el-table__body tr.el-table__row--striped td) {
  background-color: #fafafa;
}
:deep(.el-tabs__header) {
  margin-bottom: 0;
}
:deep(.el-tabs__nav-wrap) {
  padding: 0 20px;
}
:deep(.el-tabs__item) {
  font-size: 16px;
  font-weight: 500;
}
:deep(.el-tabs__item.is-active) {
  color: #409EFF;
}
:deep(.el-rate) {
  display: flex;
  align-items: center;
}
:deep(.el-rate__text) {
  margin-left: 8px;
  font-size: 14px;
  color: #606266;
}
</style>
src/views/procurementManagement/returnManagement/index.vue
@@ -21,7 +21,6 @@
    <el-card class="table-card" shadow="never">
      <div class="table-header">
        <el-button type="primary" @click="openDialog('add')">新增退货单</el-button>
        <el-button type="success" @click="handleBatchApprove">批量审核</el-button>
        <el-button type="danger" @click="handleBatchDelete">批量删除</el-button>
      </div>
@@ -42,9 +41,6 @@
            <el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="退货金额" prop="returnAmount" width="120">
          <template #default="{ row }">Â¥{{ row.returnAmount.toFixed(2) }}</template>
        </el-table-column>
        <el-table-column label="创建时间" prop="createTime" width="180" />
        <el-table-column label="操作" width="200" align="center">
          <template #default="{ row }">
@@ -54,6 +50,14 @@
          </template>
        </el-table-column>
      </el-table>
      <!-- åˆ†é¡µ -->
      <pagination
          :total="total"
          layout="total, sizes, prev, pager, next, jumper"
          :page="pagination.current"
          :limit="pagination.size"
          @pagination="handleCurrentChange"
      />
    </el-card>
    <el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? '新增退货单' : '编辑退货单'" width="600px">
@@ -65,7 +69,9 @@
          </el-select>
        </el-form-item>
        <el-form-item label="关联单号">
          <el-input v-model="formData.relatedNo" placeholder="请输入关联单号" />
          <el-select v-model="formData.relatedNo" placeholder="请选择关联单号" style="width: 100%">
            <el-option v-for="item in onList" :key="item.arrivalNo" :label="item.arrivalNo" :value="item.arrivalNo" />
          </el-select>
        </el-form-item>
        <el-form-item label="供应商名称">
          <el-input v-model="formData.supplierName" placeholder="请输入供应商名称" />
@@ -90,13 +96,49 @@
</template>
<script setup>
import { ref, reactive } from 'vue'
import { ref, reactive,onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import Pagination from '@/components/PIMTable/Pagination.vue'
import {listPage,add,update,del} from "@/api/procurementManagement/returnManagement.js"
import {listPageCopy} from "@/api/procurementManagement/arrivalManagement.js"
onMounted(() => {
  getList()
  list()
})
const onList = ref([])
const list = () =>{
  listPageCopy({current:-1}).then(res=>{
    if(res.code === 200){
      onList.value = res.data.records
    }
  })
}
const tableData = ref([])
const getList = () => {
  loading.value = true
  listPage({...searchForm,...pagination}).then(res =>{
    if(res.code === 200){
      tableData.value = res.data.records
      console.log(tableData.value)
      total.value = res.data.total
      loading.value = false
    }
  })
}
const loading = ref(false)
const dialogVisible = ref(false)
const dialogType = ref('add')
const selectedRows = ref([])
const pagination = reactive({
  current: 1,
  size: 10
})
const total = ref(0)
const searchForm = reactive({
  returnNo: '',
@@ -108,25 +150,9 @@
  relatedNo: '',
  supplierName: '',
  returnReason: '',
  remark: ''
  remark: '',
  status: ''
})
const mockData = [
  {
    id: 1,
    returnNo: 'RT20241201001',
    relatedNo: 'PO20241201001',
    returnType: 'purchase',
    supplierName: '供应商A',
    status: 'approved',
    returnAmount: 500.00,
    createTime: '2025-12-01 17:30:00',
    returnReason: '质量问题',
    remark: '商品存在质量问题'
  }
]
const tableData = ref([...mockData])
const getReturnTypeText = (type) => {
  const typeMap = { purchase: '采购退货', quality: '质检退货' }
@@ -145,7 +171,7 @@
const handleSearch = () => {
  loading.value = true
  setTimeout(() => { loading.value = false }, 500)
  getList()
}
const resetSearch = () => {
@@ -154,13 +180,15 @@
const openDialog = (type, row = {}) => {
  dialogType.value = type
  obj.id = row.id
  if (type === 'edit' && row.id) {
    Object.assign(formData, {
      returnType: row.returnType,
      relatedNo: row.relatedNo,
      supplierName: row.supplierName,
      returnReason: row.returnReason,
      remark: row.remark
      remark: row.remark,
      status: row.status
    })
  } else {
    Object.assign(formData, {
@@ -168,35 +196,43 @@
      relatedNo: '',
      supplierName: '',
      returnReason: '',
      remark: ''
      remark: '',
      status: 'pending'
    })
  }
  dialogVisible.value = true
}
const obj = reactive({
  id: ''
})
const handleSubmit = () => {
  if (dialogType.value === 'add') {
    const newReturn = {
      id: Date.now(),
      returnNo: `RT${Date.now()}`,
      relatedNo: formData.relatedNo,
      returnType: formData.returnType,
      supplierName: formData.supplierName,
      status: 'pending',
      returnAmount: 0,
      createTime: new Date().toLocaleString(),
      returnReason: formData.returnReason,
      remark: formData.remark
    formData.status = 'pending'
   add(formData).then(res => {
    if(res.code === 200){
      ElMessage.success('新增成功')
      getList()
    }
    tableData.value.unshift(newReturn)
    ElMessage.success('新增成功')
  })
  }else{
    update({...formData,...obj}).then(res => {
      if(res.code === 200){
        ElMessage.success('修改成功')
        getList()
      }
    })
  }
  dialogVisible.value = false
}
const handleApprove = (row) => {
  row.status = 'approved'
  ElMessage.success('审核通过')
  update(row).then(res => {
    if(res.code === 200){
      ElMessage.success('审核成功')
      getList()
    }
  })
}
const handleDelete = (row) => {
@@ -205,20 +241,24 @@
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    const index = tableData.value.findIndex(item => item.id === row.id)
    if (index !== -1) {
      tableData.value.splice(index, 1)
      ElMessage.success('删除成功')
    }
    let ids = [row.id]
    del(ids).then(res => {
      if(res.code === 200){
        ElMessage.success('删除成功')
        getList()
      }
    })
  })
}
const handleBatchApprove = () => {
  ElMessage.success('批量审核成功')
}
const handleBatchDelete = () => {
  ElMessage.success('批量删除成功')
  let ids = selectedRows.value.map(item => item.id)
  del(ids).then(res => {
    if(res.code === 200){
      ElMessage.success('批量删除成功')
      getList()
    }
  })
}
const handleSelectionChange = (rows) => {
src/views/reportAnalysis/dataDashboard/index.vue
@@ -12,6 +12,7 @@
      <!-- é¡¶éƒ¨æ ‡é¢˜æ  -->
      <div class="dashboard-header">
        <div class="factory-name">{{ userStore.currentFactoryName }}</div>
      </div>
      <!-- ä¸»è¦å†…容区域 -->
@@ -241,6 +242,7 @@
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue'
import autofit from 'autofit.js'
import Echarts from "@/components/Echarts/echarts.vue";
import useUserStore from '@/store/modules/user'
import {
    analysisCustomerContractAmounts, getAmountHalfYear,
    homeTodos,
@@ -258,6 +260,9 @@
// å…¨å±ç›¸å…³çŠ¶æ€
const isFullscreen = ref(false);
// ç”¨æˆ·store
const userStore = useUserStore()
// å“åº”式数据
const currentTime = ref('')
@@ -915,7 +920,7 @@
  // ä½¿ç”¨nextTick确保DOM完全渲染后再初始化图表
  nextTick(() => {
    // åˆå§‹åŒ–autofit自适应
    autofit.init({ dh: 1440, dw: 2560, el: '.data-dashboard', resize: true }, false)
    autofit.init({ dh: 1080, dw: 1920, el: '.data-dashboard', resize: true }, false)
    
    // æ·»åŠ è‡ªåŠ¨æ»šåŠ¨åŠ¨ç”»æ•ˆæžœ - å®¢æˆ·ä¿¡æ¯åˆ—表
    const contractList = refContractList.value
@@ -1044,7 +1049,6 @@
  position: relative;
  width: 100%;
    height: 100%;
  overflow: hidden;
    background-image: url("@/assets/BI/backImage@2x.png");
    background-size: cover;
    background-position: center;
@@ -1090,6 +1094,17 @@
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
  display: flex;
  align-items: center;
  justify-content: center;
}
.factory-name {
  font-weight: 600;
font-size: 52px;
color: #FFFFFF;
top: 32px;
position: absolute;
}
.fullscreen-btn {
src/views/salesManagement/paymentShipping/index.vue
@@ -91,8 +91,8 @@
      <pagination
        :total="total"
        layout="total, sizes, prev, pager, next, jumper"
        :page="pagination.currentPage"
        :limit="pagination.pageSize"
        :page="pagination.current"
        :limit="pagination.size"
        @pagination="handleCurrentChange"
      />
    </el-card>
@@ -496,8 +496,8 @@
}
const handleCurrentChange = (val) => {
  pagination.currentPage = val.page
  pagination.pageSize = val.limit
  pagination.current = val.page
  pagination.size = val.limit
}
</script>