cd6eb9089e893cf7fa998543af1125dbef81a355..c7b4b9a2f4c0f05aeb60a9e3f5fba5d9a3676f3f
5 天以前 gaoluyang
中强恒兴设备管理页面添加
c7b4b9 对比 | 目录
5 天以前 gaoluyang
中强恒兴质量管理页面添加
d5d3f5 对比 | 目录
5 天以前 gaoluyang
中强恒兴销售管理,采购管理页面添加
465c27 对比 | 目录
已修改4个文件
已添加20个文件
8119 ■■■■■ 文件已修改
src/api/equipmentManagement/brand.js 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/PIMTable/PIMTable.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/QRCodeGenerator/index.vue 541 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/brand/index.vue 217 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/ledger/Form.vue 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/ledger/index.vue 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/arrivalManagement/index.vue 189 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/index.vue 378 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/priceManagement/index.vue 276 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/purchaseOrder/index.vue 188 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/qualityInspection/index.vue 285 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/returnManagement/index.vue 234 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productManagement/productIdentifier/index.vue 708 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/nonconformingManagement/index.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/visualization/qualityDashboard.vue 307 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/reportManagement.vue 733 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/reportManagement/index.vue 716 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/customerManagement/index.vue 423 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/orderManagement/index.vue 495 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/paymentShipping/index.vue 534 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salespersonManagement/index.vue 392 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/qrCodeDemo/index.vue 405 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/qrCodeGenerator/index.vue 433 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/qrCodeSimple/index.vue 526 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/equipmentManagement/brand.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,92 @@
// è®¾å¤‡å“ç‰Œç®¡ç† - æœ¬åœ°å‡æ•°æ® API(使用 localStorage æŒä¹…化)
const STORAGE_KEY = 'EQUIPMENT_BRANDS';
function readStore() {
  try {
    const raw = localStorage.getItem(STORAGE_KEY);
    if (raw) {
      const parsed = JSON.parse(raw);
      if (Array.isArray(parsed)) return parsed;
    }
  } catch (e) {
    // ignore
  }
  // åˆå§‹åŒ–一些示例数据
  const initial = [
    { id: 1, name: '西门子', country: '德国', description: '工业自动化与电气工程品牌', createdAt: Date.now() - 86400000 * 10 },
    { id: 2, name: '施耐德', country: '法国', description: '能源管理与自动化', createdAt: Date.now() - 86400000 * 7 },
    { id: 3, name: '三菱电机', country: '日本', description: '电气与自动化设备', createdAt: Date.now() - 86400000 * 3 },
  ];
  localStorage.setItem(STORAGE_KEY, JSON.stringify(initial));
  return initial;
}
function writeStore(list) {
  localStorage.setItem(STORAGE_KEY, JSON.stringify(list));
}
function nextId(list) {
  const maxId = list.reduce((max, item) => Math.max(max, Number(item.id) || 0), 0);
  return maxId + 1;
}
export function getBrandPage(params = {}) {
  const { current = 1, size = 10, name } = params;
  const list = readStore();
  let filtered = list;
  if (name) {
    const kw = String(name).trim();
    filtered = filtered.filter((b) =>
      (b.name && b.name.includes(kw)) || (b.country && b.country.includes(kw))
    );
  }
  const start = (current - 1) * size;
  const end = start + Number(size);
  const records = filtered.slice(start, end);
  return Promise.resolve({
    code: 200,
    data: {
      total: filtered.length,
      records,
    },
    msg: 'ok',
  });
}
export function getBrandById(id) {
  const list = readStore();
  const item = list.find((i) => String(i.id) === String(id));
  return Promise.resolve({ code: 200, data: item || null, msg: 'ok' });
}
export function addBrand(data) {
  const list = readStore();
  const item = { ...data };
  item.id = nextId(list);
  item.createdAt = Date.now();
  list.unshift(item);
  writeStore(list);
  return Promise.resolve({ code: 200, data: item, msg: '新增成功' });
}
export function editBrand(data) {
  const list = readStore();
  const index = list.findIndex((i) => String(i.id) === String(data.id));
  if (index !== -1) {
    list[index] = { ...list[index], ...data };
    writeStore(list);
    return Promise.resolve({ code: 200, data: list[index], msg: '修改成功' });
  }
  return Promise.resolve({ code: 404, data: null, msg: '未找到该品牌' });
}
export function delBrand(idOrIds) {
  const list = readStore();
  const ids = Array.isArray(idOrIds) ? idOrIds.map(String) : [String(idOrIds)];
  const newList = list.filter((i) => !ids.includes(String(i.id)));
  writeStore(newList);
  return Promise.resolve({ code: 200, data: null, msg: '删除成功' });
}
src/components/PIMTable/PIMTable.vue
@@ -124,7 +124,6 @@
          <template v-for="(o, key) in item.operation" :key="key">
            <el-button
              v-show="o.type != 'upload'"
              size="small"
              v-if="o.showHide ? o.showHide(scope.row) : true"
              :disabled="o.disabled ? o.disabled(scope.row) : false"
              :plain="o.plain"
@@ -149,7 +148,6 @@
                (o.uploadIdFun ? o.uploadIdFun(scope.row) : scope.row.id)
              "
              ref="uploadRef"
              size="small"
              :multiple="o.multiple ? o.multiple : false"
              :limit="1"
              :disabled="o.disabled ? o.disabled(scope.row) : false"
@@ -178,7 +176,6 @@
              :show-file-list="false"
            >
              <el-button
                :size="o.size ? o.size : 'small'"
                link
                type="primary"
                :disabled="o.disabled ? o.disabled(scope.row) : false"
src/components/QRCodeGenerator/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,541 @@
<template>
  <div class="qr-code-generator">
    <!-- äºŒç»´ç ç”Ÿæˆè¡¨å• -->
    <el-form :model="form" :rules="rules" ref="formRef" label-width="120px" class="qr-form">
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item label="标识类型" prop="type">
            <el-select v-model="form.type" placeholder="请选择标识类型" style="width: 100%">
              <el-option label="二维码" value="qrcode"></el-option>
              <el-option label="防伪码" value="security"></el-option>
            </el-select>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="内容" prop="content">
            <el-input
              v-model="form.content"
              placeholder="请输入要编码的内容"
              :type="form.type === 'security' ? 'textarea' : 'text'"
              :rows="form.type === 'security' ? 3 : 1"
            ></el-input>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item label="尺寸" prop="size">
            <el-input-number
              v-model="form.size"
              :min="100"
              :max="500"
              :step="50"
              style="width: 100%"
            ></el-input-number>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="边距" prop="margin">
            <el-input-number
              v-model="form.margin"
              :min="0"
              :max="10"
              :step="1"
              style="width: 100%"
            ></el-input-number>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item label="前景色" prop="foregroundColor">
            <el-color-picker v-model="form.foregroundColor" style="width: 100%"></el-color-picker>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="背景色" prop="backgroundColor">
            <el-color-picker v-model="form.backgroundColor" style="width: 100%"></el-color-picker>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="20">
        <el-col :span="24">
          <el-form-item>
            <el-button type="primary" @click="generateCode" :loading="generating">
              ç”Ÿæˆ{{ form.type === 'qrcode' ? '二维码' : '防伪码' }}
            </el-button>
            <el-button @click="resetForm">重置</el-button>
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
    <!-- ç”Ÿæˆçš„码显示区域 -->
    <div v-if="generatedCodeUrl" class="code-display">
      <el-divider content-position="center">
        {{ form.type === 'qrcode' ? '生成的二维码' : '生成的防伪码' }}
      </el-divider>
      <div class="code-container">
        <div class="code-image">
          <img :src="generatedCodeUrl" :alt="form.type === 'qrcode' ? '二维码' : '防伪码'" />
        </div>
        <div class="code-info">
          <p><strong>内容:</strong>{{ form.content }}</p>
          <p><strong>类型:</strong>{{ form.type === 'qrcode' ? '二维码' : '防伪码' }}</p>
          <p><strong>尺寸:</strong>{{ form.size }}x{{ form.size }}px</p>
          <p><strong>生成时间:</strong>{{ generateTime }}</p>
        </div>
      </div>
      <div class="code-actions">
        <el-button type="success" @click="downloadCode" icon="Download">
          ä¸‹è½½å›¾ç‰‡
        </el-button>
        <el-button type="primary" @click="copyToClipboard" icon="CopyDocument">
          å¤åˆ¶å†…容
        </el-button>
        <el-button @click="printCode" icon="Printer">
          æ‰“印
        </el-button>
      </div>
    </div>
    <!-- æ‰¹é‡ç”Ÿæˆå¯¹è¯æ¡† -->
    <el-dialog v-model="batchDialogVisible" title="批量生成" width="600px">
      <el-form :model="batchForm" label-width="120px">
        <el-form-item label="生成数量">
          <el-input-number v-model="batchForm.quantity" :min="1" :max="100" style="width: 100%"></el-input-number>
        </el-form-item>
        <el-form-item label="前缀">
          <el-input v-model="batchForm.prefix" placeholder="请输入前缀,如:PROD_"></el-input>
        </el-form-item>
        <el-form-item label="起始编号">
          <el-input-number v-model="batchForm.startNumber" :min="1" style="width: 100%"></el-input-number>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="batchDialogVisible = false">取消</el-button>
          <el-button type="primary" @click="generateBatchCodes">开始生成</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- æ‰¹é‡ç”Ÿæˆç»“æžœ -->
    <div v-if="batchCodes.length > 0" class="batch-results">
      <el-divider content-position="center">批量生成结果</el-divider>
      <div class="batch-grid">
        <div
          v-for="(code, index) in batchCodes"
          :key="index"
          class="batch-item"
        >
          <img :src="code.url" :alt="code.content" />
          <p class="batch-content">{{ code.content }}</p>
          <el-button size="small" @click="downloadSingleCode(code)">下载</el-button>
        </div>
      </div>
      <div class="batch-actions">
        <el-button type="success" @click="downloadAllCodes">下载全部</el-button>
        <el-button @click="clearBatchCodes">清空结果</el-button>
      </div>
    </div>
  </div>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
import QRCode from 'qrcode'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Download, CopyDocument, Printer } from '@element-plus/icons-vue'
// å®šä¹‰ç»„件名称
defineOptions({
  name: 'QRCodeGenerator'
})
// è¡¨å•数据
const form = reactive({
  type: 'qrcode',
  content: '',
  size: 200,
  margin: 2,
  foregroundColor: '#000000',
  backgroundColor: '#FFFFFF'
})
// è¡¨å•验证规则
const rules = {
  type: [{ required: true, message: '请选择标识类型', trigger: 'change' }],
  content: [{ required: true, message: '请输入内容', trigger: 'blur' }]
}
// å“åº”式数据
const formRef = ref()
const generating = ref(false)
const generatedCodeUrl = ref('')
const generateTime = ref('')
const batchDialogVisible = ref(false)
const batchForm = reactive({
  quantity: 10,
  prefix: '',
  startNumber: 1
})
const batchCodes = ref([])
// ç”ŸæˆäºŒç»´ç æˆ–防伪码
const generateCode = async () => {
  try {
    await formRef.value.validate()
    if (!form.content.trim()) {
      ElMessage.warning('请输入要编码的内容')
      return
    }
    generating.value = true
    if (form.type === 'qrcode') {
      // ç”ŸæˆäºŒç»´ç 
      generatedCodeUrl.value = await QRCode.toDataURL(form.content, {
        width: form.size,
        margin: form.margin,
        color: {
          dark: form.foregroundColor,
          light: form.backgroundColor
        },
        errorCorrectionLevel: 'M'
      })
    } else {
      // ç”Ÿæˆé˜²ä¼ªç ï¼ˆä½¿ç”¨äºŒç»´ç æŠ€æœ¯ï¼Œä½†å†…容格式不同)
      const securityContent = generateSecurityCode(form.content)
      generatedCodeUrl.value = await QRCode.toDataURL(securityContent, {
        width: form.size,
        margin: form.margin,
        color: {
          dark: form.foregroundColor,
          light: form.backgroundColor
        },
        errorCorrectionLevel: 'H' // é˜²ä¼ªç ä½¿ç”¨æœ€é«˜çº é”™çº§åˆ«
      })
    }
    generateTime.value = new Date().toLocaleString()
    ElMessage.success('生成成功!')
  } catch (error) {
    console.error('生成失败:', error)
    ElMessage.error('生成失败:' + error.message)
  } finally {
    generating.value = false
  }
}
// ç”Ÿæˆé˜²ä¼ªç å†…容
const generateSecurityCode = (content) => {
  const timestamp = Date.now()
  const random = Math.random().toString(36).substr(2, 8)
  return `SEC_${content}_${timestamp}_${random}`
}
// ä¸‹è½½ç”Ÿæˆçš„码
const downloadCode = () => {
  if (!generatedCodeUrl.value) {
    ElMessage.warning('请先生成码')
    return
  }
  const a = document.createElement('a')
  a.href = generatedCodeUrl.value
  a.download = `${form.type === 'qrcode' ? '二维码' : '防伪码'}_${new Date().getTime()}.png`
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
  ElMessage.success('下载成功!')
}
// å¤åˆ¶å†…容到剪贴板
const copyToClipboard = async () => {
  try {
    await navigator.clipboard.writeText(form.content)
    ElMessage.success('内容已复制到剪贴板')
  } catch (error) {
    // é™çº§æ–¹æ¡ˆ
    const textArea = document.createElement('textarea')
    textArea.value = form.content
    document.body.appendChild(textArea)
    textArea.select()
    document.execCommand('copy')
    document.body.removeChild(textArea)
    ElMessage.success('内容已复制到剪贴板')
  }
}
// æ‰“印码
const printCode = () => {
  if (!generatedCodeUrl.value) {
    ElMessage.warning('请先生成码')
    return
  }
  const printWindow = window.open('', '_blank')
  printWindow.document.write(`
    <html>
      <head>
        <title>打印${form.type === 'qrcode' ? '二维码' : '防伪码'}</title>
        <style>
          body { text-align: center; padding: 20px; }
          img { max-width: 100%; height: auto; }
          .info { margin: 20px 0; }
        </style>
      </head>
      <body>
        <h2>${form.type === 'qrcode' ? '二维码' : '防伪码'}</h2>
        <img src="${generatedCodeUrl.value}" alt="${form.type === 'qrcode' ? '二维码' : '防伪码'}" />
        <div class="info">
          <p><strong>内容:</strong>${form.content}</p>
          <p><strong>生成时间:</strong>${generateTime.value}</p>
        </div>
      </body>
    </html>
  `)
  printWindow.document.close()
  printWindow.print()
}
// é‡ç½®è¡¨å•
const resetForm = () => {
  formRef.value.resetFields()
  generatedCodeUrl.value = ''
  generateTime.value = ''
  batchCodes.value = []
}
// æ‰¹é‡ç”Ÿæˆ
const generateBatchCodes = async () => {
  if (!batchForm.prefix.trim()) {
    ElMessage.warning('请输入前缀')
    return
  }
  batchCodes.value = []
  generating.value = true
  try {
    for (let i = 0; i < batchForm.quantity; i++) {
      const number = batchForm.startNumber + i
      const content = `${batchForm.prefix}${number.toString().padStart(6, '0')}`
      let codeUrl
      if (form.type === 'qrcode') {
        codeUrl = await QRCode.toDataURL(content, {
          width: form.size,
          margin: form.margin,
          color: {
            dark: form.foregroundColor,
            light: form.backgroundColor
          }
        })
      } else {
        const securityContent = generateSecurityCode(content)
        codeUrl = await QRCode.toDataURL(securityContent, {
          width: form.size,
          margin: form.margin,
          color: {
            dark: form.foregroundColor,
            light: form.backgroundColor
          }
        })
      }
      batchCodes.value.push({
        content,
        url: codeUrl
      })
    }
    ElMessage.success(`批量生成完成,共生成 ${batchForm.quantity} ä¸ªç `)
    batchDialogVisible.value = false
  } catch (error) {
    console.error('批量生成失败:', error)
    ElMessage.error('批量生成失败:' + error.message)
  } finally {
    generating.value = false
  }
}
// ä¸‹è½½å•个批量生成的码
const downloadSingleCode = (code) => {
  const a = document.createElement('a')
  a.href = code.url
  a.download = `${code.content}.png`
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
}
// ä¸‹è½½æ‰€æœ‰æ‰¹é‡ç”Ÿæˆçš„码
const downloadAllCodes = async () => {
  if (batchCodes.value.length === 0) {
    ElMessage.warning('没有可下载的码')
    return
  }
  try {
    // ä½¿ç”¨JSZip打包下载
    const JSZip = await import('jszip')
    const zip = new JSZip.default()
    batchCodes.value.forEach((code, index) => {
      // å°†base64转换为blob
      const base64Data = code.url.split(',')[1]
      const byteCharacters = atob(base64Data)
      const byteNumbers = new Array(byteCharacters.length)
      for (let i = 0; i < byteCharacters.length; i++) {
        byteNumbers[i] = byteCharacters.charCodeAt(i)
      }
      const byteArray = new Uint8Array(byteNumbers)
      zip.file(`${code.content}.png`, byteArray)
    })
    const content = await zip.generateAsync({ type: 'blob' })
    const a = document.createElement('a')
    a.href = URL.createObjectURL(content)
    a.download = `批量${form.type === 'qrcode' ? '二维码' : '防伪码'}_${new Date().getTime()}.zip`
    document.body.appendChild(a)
    a.click()
    document.body.removeChild(a)
    URL.revokeObjectURL(a.href)
    ElMessage.success('批量下载完成!')
  } catch (error) {
    console.error('批量下载失败:', error)
    ElMessage.error('批量下载失败,请逐个下载')
  }
}
// æ¸…空批量生成结果
const clearBatchCodes = () => {
  batchCodes.value = []
}
// æš´éœ²æ–¹æ³•给父组件
defineExpose({
  generateCode,
  downloadCode,
  resetForm,
  form
})
</script>
<style scoped>
.qr-code-generator {
  padding: 20px;
}
.qr-form {
  background: #f8f9fa;
  padding: 20px;
  border-radius: 8px;
  margin-bottom: 20px;
}
.code-display {
  margin-top: 30px;
}
.code-container {
  display: flex;
  justify-content: center;
  align-items: flex-start;
  gap: 40px;
  margin: 20px 0;
}
.code-image img {
  border: 2px solid #e0e0e0;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.code-info {
  text-align: left;
  min-width: 200px;
}
.code-info p {
  margin: 8px 0;
  color: #666;
}
.code-actions {
  text-align: center;
  margin: 20px 0;
}
.code-actions .el-button {
  margin: 0 10px;
}
.batch-results {
  margin-top: 30px;
}
.batch-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 20px;
  margin: 20px 0;
}
.batch-item {
  text-align: center;
  padding: 15px;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  background: #fff;
}
.batch-item img {
  width: 100px;
  height: 100px;
  margin-bottom: 10px;
}
.batch-content {
  font-size: 12px;
  color: #666;
  margin: 10px 0;
  word-break: break-all;
}
.batch-actions {
  text-align: center;
  margin: 20px 0;
}
.batch-actions .el-button {
  margin: 0 10px;
}
@media (max-width: 768px) {
  .code-container {
    flex-direction: column;
    align-items: center;
  }
  .batch-grid {
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  }
}
</style>
src/views/equipmentManagement/brand/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,217 @@
<template>
  <div class="app-container">
    <el-form :model="filters" :inline="true">
      <el-form-item label="品牌名称/国家">
        <el-input
          v-model="filters.name"
          style="width: 240px"
          placeholder="请输入关键词"
          clearable
          prefix-icon="Search"
          @change="getTableData"
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="getTableData">搜索</el-button>
        <el-button @click="resetFilters">重置</el-button>
      </el-form-item>
    </el-form>
    <div class="table_list">
      <div class="actions">
        <div></div>
        <div>
          <el-button type="primary" @click="openAdd" icon="Plus"> æ–°å¢ž </el-button>
          <el-button
            type="danger"
            icon="Delete"
            :disabled="multipleSelection.length <= 0"
            @click="handleBatchDelete"
          >批量删除</el-button>
        </div>
      </div>
      <PIMTable
        rowKey="id"
        isSelection
        :column="columns"
        :tableData="dataList"
        :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
          total: pagination.total,
        }"
        @selection-change="handleSelectionChange"
        @pagination="changePage"
      >
      </PIMTable>
    </div>
    <el-dialog v-model="visible" :title="dialogTitle" width="520px" destroy-on-close>
      <el-form :model="form" ref="formRef" :rules="rules" label-width="90px">
        <el-form-item label="品牌名称" prop="name">
          <el-input v-model="form.name" placeholder="请输入品牌名称" />
        </el-form-item>
        <el-form-item label="所属国家" prop="country">
          <el-input v-model="form.country" placeholder="请输入国家/地区" />
        </el-form-item>
        <el-form-item label="描述" prop="description">
          <el-input v-model="form.description" type="textarea" :rows="3" placeholder="可填写品牌简介" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="visible = false">取消</el-button>
        <el-button type="primary" @click="handleSubmit">确定</el-button>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, getCurrentInstance, onMounted } from 'vue'
import { ElMessageBox, ElMessage } from 'element-plus'
import { usePaginationApi } from '@/hooks/usePaginationApi'
import { getBrandPage, addBrand, editBrand, delBrand } from '@/api/equipmentManagement/brand'
defineOptions({ name: '设备品牌管理' })
const { proxy } = getCurrentInstance()
const multipleSelection = ref([])
const formRef = ref()
const visible = ref(false)
const dialogTitle = ref('新增品牌')
const form = ref({ id: undefined, name: '', country: '', description: '' })
const rules = {
  name: [{ required: true, message: '请输入品牌名称', trigger: 'blur' }],
  country: [{ required: true, message: '请输入所属国家', trigger: 'blur' }]
}
const {
  filters,
  columns,
  dataList,
  pagination,
  getTableData,
  resetFilters,
  onCurrentChange,
} = usePaginationApi(
  getBrandPage,
  { name: undefined },
  [
    { label: '品牌名称', align: 'center', prop: 'name' },
    { label: '所属国家', align: 'center', prop: 'country' },
    { label: '描述', align: 'center', prop: 'description' },
    { label: '创建时间', align: 'center', prop: 'createdAt' },
    {
      dataType: 'action',
      label: '操作',
      align: 'center',
      fixed: 'right',
      width: 140,
      operation: [
        {
          name: '编辑',
          type: 'text',
          clickFun: (row) => openEdit(row),
        },
        {
          name: '删除',
          type: 'text',
          clickFun: (row) => handleDelete(row.id),
        }
      ]
    }
  ]
)
const handleSelectionChange = (list) => {
  multipleSelection.value = list
}
const changePage = ({ page, limit }) => {
  pagination.currentPage = page
  pagination.pageSize = limit
  onCurrentChange(page)
}
function resetForm() {
  form.value = { id: undefined, name: '', country: '', description: '' }
}
function openAdd() {
  resetForm()
  dialogTitle.value = '新增品牌'
  visible.value = true
}
function openEdit(row) {
  form.value = { id: row.id, name: row.name, country: row.country, description: row.description }
  dialogTitle.value = '编辑品牌'
  visible.value = true
}
function handleSubmit() {
  formRef.value.validate(async (valid) => {
    if (!valid) return
    const isEdit = Boolean(form.value.id)
    const api = isEdit ? editBrand : addBrand
    const { code, msg } = await api({ ...form.value })
    if (code === 200) {
      ElMessage.success(isEdit ? '修改成功' : '新增成功')
      visible.value = false
      getTableData()
    } else {
      ElMessage.error(msg || '操作失败')
    }
  })
}
function handleDelete(id) {
  ElMessageBox.confirm('此操作将永久删除该品牌, æ˜¯å¦ç»§ç»­?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  }).then(async () => {
    const { code } = await delBrand(id)
    if (code === 200) {
      ElMessage.success('删除成功')
      getTableData()
    }
  })
}
function handleBatchDelete() {
  if (multipleSelection.value.length === 0) return
  ElMessageBox.confirm('将删除选中的品牌,是否继续?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  }).then(async () => {
    const ids = multipleSelection.value.map((i) => i.id)
    const { code } = await delBrand(ids)
    if (code === 200) {
      ElMessage.success('删除成功')
      getTableData()
    }
  })
}
onMounted(() => {
  getTableData()
})
</script>
<style scoped lang="scss">
.table_list { margin-top: unset; }
.actions {
  display: flex;
  justify-content: space-between;
  margin-bottom: 10px;
}
</style>
src/views/equipmentManagement/ledger/Form.vue
@@ -12,13 +12,28 @@
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="设备品牌" prop="deviceBrand">
          <el-input v-model="form.deviceBrand" placeholder="请输入设备品牌" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="供应商" prop="supplierName">
          <el-input v-model="form.supplierName" placeholder="请输入供应商" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="存放位置" prop="storageLocation">
          <el-input v-model="form.storageLocation" placeholder="请输入存放位置" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="单位" prop="unit">
          <el-input v-model="form.unit" placeholder="请输入单位" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="启用折旧" prop="enableDepreciation">
          <el-switch v-model="form.enableDepreciation" :active-value="true" :inactive-value="false" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
@@ -133,7 +148,10 @@
const { form, resetForm } = useFormData({
  deviceName: undefined, // è®¾å¤‡åç§°
  deviceModel: undefined, // è§„格型号
  deviceBrand: undefined, // è®¾å¤‡å“ç‰Œ
  supplierName: undefined, // ä¾›åº”商
  storageLocation: undefined, // å­˜æ”¾ä½ç½®
  enableDepreciation: false, // æ˜¯å¦å¯ç”¨æŠ˜æ—§
  unit: undefined, // å•位
  number: undefined, // æ•°é‡
  taxIncludingPriceUnit: undefined, // å«ç¨Žå•ä»·
@@ -152,7 +170,10 @@
  if (code == 200) {
    form.deviceName = data.deviceName;
    form.deviceModel = data.deviceModel;
    form.deviceBrand = data.deviceBrand;
    form.supplierName = data.supplierName;
    form.storageLocation = data.storageLocation;
    form.enableDepreciation = data.enableDepreciation;
    form.unit = data.unit;
    form.number = data.number;
    form.taxIncludingPriceUnit = data.taxIncludingPriceUnit;
src/views/equipmentManagement/ledger/index.vue
@@ -146,6 +146,11 @@
      prop: "deviceModel",
    },
    {
      label: "设备品牌",
      align: "center",
      prop: "deviceBrand",
    },
    {
      label: "供应商",
      align: "center",
      prop: "supplierName",
@@ -154,6 +159,11 @@
      label: "单位",
      align: "center",
      prop: "unit",
    },
    {
      label: "存放位置",
      align: "center",
      prop: "storageLocation",
    },
    {
      label: "数量",
@@ -181,6 +191,12 @@
      prop: "unTaxIncludingPriceTotal",
    },
    {
      label: "启用折旧",
      align: "center",
      prop: "enableDepreciation",
      formatData: (v) => (v ? "是" : "否"),
    },
    {
      label: "录入人",
      align: "center",
      prop: "createUser",
src/views/procurementManagement/arrivalManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,189 @@
<template>
  <div class="app-container">
    <el-card class="search-card" shadow="never">
      <el-form :model="searchForm" :inline="true">
        <el-form-item label="采购订单号:">
          <el-input v-model="searchForm.orderNo" placeholder="请输入订单号" clearable />
        </el-form-item>
        <el-form-item label="供应商名称:">
          <el-input v-model="searchForm.supplierName" placeholder="请输入供应商名称" clearable />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleSearch">搜索</el-button>
          <el-button @click="resetSearch">重置</el-button>
        </el-form-item>
      </el-form>
    </el-card>
    <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>
      <el-table :data="tableData" border v-loading="loading" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="到货单号" prop="arrivalNo" width="180" />
        <el-table-column label="采购订单号" prop="orderNo" width="180" />
        <el-table-column label="供应商名称" prop="supplierName" />
        <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="arrivalQuantity" width="100" />
        <el-table-column label="到货时间" prop="arrivalTime" width="180" />
        <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="danger" link @click="handleDelete(row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
    </el-card>
    <el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? '新增到货' : '编辑到货'" width="600px">
      <el-form :model="formData" label-width="120px">
        <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-form-item>
        <el-form-item label="供应商名称">
          <el-input v-model="formData.supplierName" placeholder="供应商名称" readonly />
        </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">确定</el-button>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
const loading = ref(false)
const dialogVisible = ref(false)
const dialogType = ref('add')
const selectedRows = ref([])
const searchForm = reactive({
  orderNo: '',
  supplierName: ''
})
const formData = reactive({
  orderNo: '',
  supplierName: '',
  remark: ''
})
const mockData = [
  {
    id: 1,
    arrivalNo: 'AR20241201001',
    orderNo: 'PO20241201001',
    supplierName: '供应商A',
    status: 'received',
    arrivalQuantity: 250,
    arrivalTime: '2024-12-01 15:30:00',
    remark: '正常到货'
  }
]
const tableData = ref([...mockData])
const getStatusType = (status) => {
  const statusMap = { pending: 'warning', received: 'success', stored: 'info' }
  return statusMap[status] || 'info'
}
const getStatusText = (status) => {
  const statusMap = { pending: '待收货', received: '已收货', stored: '已入库' }
  return statusMap[status] || '未知'
}
const handleSearch = () => {
  loading.value = true
  setTimeout(() => { loading.value = false }, 500)
}
const resetSearch = () => {
  Object.assign(searchForm, { orderNo: '', supplierName: '' })
}
const openDialog = (type, row = {}) => {
  dialogType.value = type
  if (type === 'edit' && row.id) {
    Object.assign(formData, { orderNo: row.orderNo, supplierName: row.supplierName, remark: row.remark })
  } else {
    Object.assign(formData, { orderNo: '', supplierName: '', remark: '' })
  }
  dialogVisible.value = true
}
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('新增成功')
  }
  dialogVisible.value = false
}
const handleReceive = (row) => {
  row.status = 'received'
  ElMessage.success('收货成功')
}
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 handleBatchReceive = () => {
  ElMessage.success('批量收货成功')
}
const handleBatchDelete = () => {
  ElMessage.success('批量删除成功')
}
const handleSelectionChange = (rows) => {
  selectedRows.value = rows
}
</script>
<style scoped>
.app-container { padding: 20px; }
.search-card { margin-bottom: 20px; }
.table-card { margin-bottom: 20px; }
.table-header { margin-bottom: 20px; }
</style>
src/views/procurementManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,378 @@
<template>
  <div class="app-container">
    <!-- é¡µé¢æ ‡é¢˜ -->
    <div class="page-header">
      <h2>采购管理系统</h2>
      <p>统一管理采购全流程,提升采购效率与质量</p>
    </div>
    <!-- åŠŸèƒ½æ¨¡å—å¡ç‰‡ -->
    <el-row :gutter="20" class="module-cards">
      <el-col :span="8">
        <el-card class="module-card" shadow="hover" @click="navigateTo('/procurementManagement/purchaseOrder')">
          <div class="card-content">
            <div class="card-icon">
              <el-icon size="48" color="#409EFF"><Document /></el-icon>
            </div>
            <div class="card-info">
              <h3>采购订单管理</h3>
              <p>新建、编辑、删除采购订单,选择供应商,填写商品明细</p>
              <div class="card-stats">
                <span>待审核: {{ stats.pendingOrders }}</span>
                <span>已审核: {{ stats.approvedOrders }}</span>
              </div>
            </div>
          </div>
        </el-card>
      </el-col>
      <el-col :span="8">
        <el-card class="module-card" shadow="hover" @click="navigateTo('/procurementManagement/arrivalManagement')">
          <div class="card-content">
            <div class="card-icon">
              <el-icon size="48" color="#67C23A"><Box /></el-icon>
            </div>
            <div class="card-info">
              <h3>到货管理</h3>
              <p>自动关联采购订单,录入到货商品信息,支持打印查看</p>
              <div class="card-stats">
                <span>待收货: {{ stats.pendingArrivals }}</span>
                <span>已收货: {{ stats.receivedArrivals }}</span>
              </div>
            </div>
          </div>
        </el-card>
      </el-col>
      <el-col :span="8">
        <el-card class="module-card" shadow="hover" @click="navigateTo('/procurementManagement/qualityInspection')">
          <div class="card-content">
            <div class="card-icon">
              <el-icon size="48" color="#E6A23C"><Search /></el-icon>
            </div>
            <div class="card-info">
              <h3>质检管理</h3>
              <p>到货后自动生成质检单,填写合格与不合格商品数量及原因</p>
              <div class="card-stats">
                <span>待质检: {{ stats.pendingInspections }}</span>
                <span>已完成: {{ stats.completedInspections }}</span>
              </div>
            </div>
          </div>
        </el-card>
      </el-col>
    </el-row>
    <el-row :gutter="20" class="module-cards">
      <el-col :span="8">
        <el-card class="module-card" shadow="hover" @click="navigateTo('/procurementManagement/returnManagement')">
          <div class="card-content">
            <div class="card-icon">
              <el-icon size="48" color="#F56C6C"><RefreshLeft /></el-icon>
            </div>
            <div class="card-info">
              <h3>退货管理</h3>
              <p>生成采购退货单和质检退货单,支持筛选查询与单据详情</p>
              <div class="card-stats">
                <span>待审核: {{ stats.pendingReturns }}</span>
                <span>已审核: {{ stats.approvedReturns }}</span>
              </div>
            </div>
          </div>
        </el-card>
      </el-col>
      <el-col :span="8">
        <el-card class="module-card" shadow="hover" @click="navigateTo('/procurementManagement/priceManagement')">
          <div class="card-content">
            <div class="card-icon">
              <el-icon size="48" color="#909399"><Money /></el-icon>
            </div>
            <div class="card-info">
              <h3>价格管理</h3>
              <p>根据商品及市场价格变化进行采购价调整,自动更新采购单据</p>
              <div class="card-stats">
                <span>有效价格: {{ stats.activePrices }}</span>
                <span>待生效: {{ stats.pendingPrices }}</span>
              </div>
            </div>
          </div>
        </el-card>
      </el-col>
      <el-col :span="8">
        <el-card class="module-card" shadow="hover" @click="navigateTo('/procurementManagement/procurementLedger')">
          <div class="card-content">
            <div class="card-icon">
              <el-icon size="48" color="#9C27B0"><List /></el-icon>
            </div>
            <div class="card-info">
              <h3>采购台账</h3>
              <p>查看采购历史记录,统计分析采购数据,生成采购报表</p>
              <div class="card-stats">
                <span>总订单: {{ stats.totalOrders }}</span>
                <span>总金额: Â¥{{ stats.totalAmount.toFixed(2) }}</span>
              </div>
            </div>
          </div>
        </el-card>
      </el-col>
    </el-row>
    <!-- ç»Ÿè®¡æ¦‚览 -->
    <el-card class="stats-card" shadow="never">
      <template #header>
        <div class="card-header">
          <span>采购统计概览</span>
          <el-button type="primary" size="small" @click="refreshStats">刷新数据</el-button>
        </div>
      </template>
      <el-row :gutter="20">
        <el-col :span="6">
          <div class="stat-item">
            <div class="stat-number">{{ stats.totalOrders }}</div>
            <div class="stat-label">采购订单总数</div>
          </div>
        </el-col>
        <el-col :span="6">
          <div class="stat-item">
            <div class="stat-number">{{ stats.totalAmount.toFixed(2) }}</div>
            <div class="stat-label">采购总金额(万元)</div>
          </div>
        </el-col>
        <el-col :span="6">
          <div class="stat-item">
            <div class="stat-number">{{ stats.avgDeliveryTime }}</div>
            <div class="stat-label">平均交付天数</div>
          </div>
        </el-col>
        <el-col :span="6">
          <div class="stat-item">
            <div class="stat-number">{{ stats.qualityRate }}%</div>
            <div class="stat-label">质检合格率</div>
          </div>
        </el-col>
      </el-row>
    </el-card>
    <!-- æœ€è¿‘活动 -->
    <el-card class="activity-card" shadow="never">
      <template #header>
        <span>最近活动</span>
      </template>
      <el-timeline>
        <el-timeline-item
          v-for="(activity, index) in recentActivities"
          :key="index"
          :timestamp="activity.time"
          :type="activity.type"
        >
          {{ activity.content }}
        </el-timeline-item>
      </el-timeline>
    </el-card>
  </div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { Document, Box, Search, RefreshLeft, Money, List } from '@element-plus/icons-vue'
const router = useRouter()
// ç»Ÿè®¡æ•°æ®
const stats = ref({
  pendingOrders: 5,
  approvedOrders: 25,
  pendingArrivals: 3,
  receivedArrivals: 18,
  pendingInspections: 2,
  completedInspections: 15,
  pendingReturns: 1,
  approvedReturns: 3,
  activePrices: 45,
  pendingPrices: 2,
  totalOrders: 30,
  totalAmount: 125.8,
  avgDeliveryTime: 7,
  qualityRate: 96.5
})
// æœ€è¿‘活动
const recentActivities = ref([
  {
    time: '2024-12-01 18:30',
    content: '新增采购订单 PO20241201004',
    type: 'primary'
  },
  {
    time: '2024-12-01 17:45',
    content: '完成质检单 QI20241201002',
    type: 'success'
  },
  {
    time: '2024-12-01 16:20',
    content: '到货单 AR20241201003 å·²æ”¶è´§',
    type: 'success'
  },
  {
    time: '2024-12-01 15:15',
    content: '价格调整:商品B ä»Ž Â¥80 è°ƒæ•´ä¸º Â¥75',
    type: 'warning'
  },
  {
    time: '2024-12-01 14:30',
    content: '退货单 RT20241201003 å·²å®¡æ ¸',
    type: 'info'
  }
])
// å¯¼èˆªåˆ°æŒ‡å®šé¡µé¢
const navigateTo = (path) => {
  router.push(path)
}
// åˆ·æ–°ç»Ÿè®¡æ•°æ®
const refreshStats = () => {
  // æ¨¡æ‹Ÿåˆ·æ–°æ•°æ®
  stats.value.pendingOrders = Math.floor(Math.random() * 10) + 1
  stats.value.totalAmount = (Math.random() * 100 + 100).toFixed(1)
}
onMounted(() => {
  // é¡µé¢åŠ è½½å®ŒæˆåŽçš„åˆå§‹åŒ–é€»è¾‘
})
</script>
<style scoped>
.app-container {
  padding: 20px;
  background-color: #f5f7fa;
  min-height: 100vh;
}
.page-header {
  text-align: center;
  margin-bottom: 30px;
  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;
}
.module-cards {
  margin-bottom: 20px;
}
.module-card {
  cursor: pointer;
  transition: all 0.3s ease;
  border: none;
  border-radius: 12px;
}
.module-card:hover {
  transform: translateY(-5px);
  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
}
.card-content {
  display: flex;
  align-items: center;
  padding: 20px;
}
.card-icon {
  margin-right: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 80px;
  height: 80px;
  background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
  border-radius: 50%;
  color: white;
}
.card-info h3 {
  margin: 0 0 10px 0;
  font-size: 18px;
  font-weight: 600;
  color: #303133;
}
.card-info p {
  margin: 0 0 15px 0;
  font-size: 14px;
  color: #606266;
  line-height: 1.5;
}
.card-stats {
  display: flex;
  gap: 15px;
}
.card-stats span {
  font-size: 12px;
  color: #909399;
  background-color: #f5f7fa;
  padding: 4px 8px;
  border-radius: 4px;
}
.stats-card {
  margin-bottom: 20px;
  border-radius: 12px;
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.stat-item {
  text-align: center;
  padding: 20px;
}
.stat-number {
  font-size: 32px;
  font-weight: 600;
  color: #409EFF;
  margin-bottom: 8px;
}
.stat-label {
  font-size: 14px;
  color: #606266;
}
.activity-card {
  border-radius: 12px;
}
.el-timeline-item {
  padding-bottom: 20px;
}
.el-timeline-item:last-child {
  padding-bottom: 0;
}
</style>
src/views/procurementManagement/priceManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,276 @@
<template>
  <div class="app-container">
    <el-card class="search-card" shadow="never">
      <el-form :model="searchForm" :inline="true">
        <el-form-item label="商品名称:">
          <el-input v-model="searchForm.productName" placeholder="请输入商品名称" clearable />
        </el-form-item>
        <el-form-item label="供应商名称:">
          <el-input v-model="searchForm.supplierName" placeholder="请输入供应商名称" clearable />
        </el-form-item>
        <el-form-item label="价格状态:">
          <el-select v-model="searchForm.status" placeholder="请选择状态" clearable>
            <el-option label="有效" value="active" />
            <el-option label="已过期" value="expired" />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleSearch">搜索</el-button>
          <el-button @click="resetSearch">重置</el-button>
        </el-form-item>
      </el-form>
    </el-card>
    <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="handleBatchUpdate">批量更新</el-button>
        <el-button type="danger" @click="handleBatchDelete">批量删除</el-button>
      </div>
      <el-table :data="tableData" border v-loading="loading" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="商品名称" prop="productName" />
        <el-table-column label="规格型号" prop="specification" />
        <el-table-column label="供应商名称" prop="supplierName" />
        <el-table-column label="原价格" prop="oldPrice" width="120">
          <template #default="{ row }">Â¥{{ row.oldPrice.toFixed(2) }}</template>
        </el-table-column>
        <el-table-column label="新价格" prop="newPrice" width="120">
          <template #default="{ row }">Â¥{{ row.newPrice.toFixed(2) }}</template>
        </el-table-column>
        <el-table-column label="调价幅度" prop="priceChange" width="120">
          <template #default="{ row }">
            <span :style="{ color: row.priceChange >= 0 ? '#f56c6c' : '#67c23a' }">
              {{ row.priceChange >= 0 ? '+' : '' }}{{ row.priceChange.toFixed(2) }}%
            </span>
          </template>
        </el-table-column>
        <el-table-column label="生效时间" prop="effectiveTime" width="180" />
        <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="操作" width="200" align="center">
          <template #default="{ row }">
            <el-button type="primary" link @click="openDialog('edit', row)">编辑</el-button>
            <el-button type="success" link @click="handleApply(row)" v-if="row.status === 'pending'">应用</el-button>
            <el-button type="danger" link @click="handleDelete(row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
    </el-card>
    <el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? '新增价格' : '编辑价格'" width="600px">
      <el-form :model="formData" label-width="120px">
        <el-form-item label="商品名称">
          <el-select v-model="formData.productName" placeholder="请选择商品" style="width: 100%">
            <el-option label="商品A" value="商品A" />
            <el-option label="商品B" value="商品B" />
            <el-option label="商品C" value="商品C" />
          </el-select>
        </el-form-item>
        <el-form-item label="规格型号">
          <el-input v-model="formData.specification" placeholder="请输入规格型号" />
        </el-form-item>
        <el-form-item label="供应商名称">
          <el-select v-model="formData.supplierName" placeholder="请选择供应商" style="width: 100%">
            <el-option label="供应商A" value="供应商A" />
            <el-option label="供应商B" value="供应商B" />
            <el-option label="供应商C" value="供应商C" />
          </el-select>
        </el-form-item>
        <el-form-item label="原价格">
          <el-input v-model="formData.oldPrice" placeholder="请输入原价格" type="number" />
        </el-form-item>
        <el-form-item label="新价格">
          <el-input v-model="formData.newPrice" placeholder="请输入新价格" type="number" />
        </el-form-item>
        <el-form-item label="生效时间">
          <el-date-picker v-model="formData.effectiveTime" type="datetime" placeholder="选择生效时间" style="width: 100%" />
        </el-form-item>
        <el-form-item label="调价原因">
          <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="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">确定</el-button>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
const loading = ref(false)
const dialogVisible = ref(false)
const dialogType = ref('add')
const selectedRows = ref([])
const searchForm = reactive({
  productName: '',
  supplierName: '',
  status: ''
})
const formData = reactive({
  productName: '',
  specification: '',
  supplierName: '',
  oldPrice: 0,
  newPrice: 0,
  effectiveTime: '',
  reason: '',
  remark: ''
})
const mockData = [
  {
    id: 1,
    productName: '商品A',
    specification: '规格1',
    supplierName: '供应商A',
    oldPrice: 50.00,
    newPrice: 55.00,
    priceChange: 10.00,
    effectiveTime: '2024-12-01 00:00:00',
    status: 'active',
    reason: '市场价格变动',
    remark: '市场价格上涨'
  },
  {
    id: 2,
    productName: '商品B',
    specification: '规格2',
    supplierName: '供应商B',
    oldPrice: 80.00,
    newPrice: 75.00,
    priceChange: -6.25,
    effectiveTime: '2024-12-01 00:00:00',
    status: 'active',
    reason: '成本变化',
    remark: '成本下降'
  }
]
const tableData = ref([...mockData])
const getStatusType = (status) => {
  const statusMap = { active: 'success', expired: 'info', pending: 'warning' }
  return statusMap[status] || 'info'
}
const getStatusText = (status) => {
  const statusMap = { active: '有效', expired: '已过期', pending: '待生效' }
  return statusMap[status] || '未知'
}
const handleSearch = () => {
  loading.value = true
  setTimeout(() => { loading.value = false }, 500)
}
const resetSearch = () => {
  Object.assign(searchForm, { productName: '', supplierName: '', status: '' })
}
const openDialog = (type, row = {}) => {
  dialogType.value = type
  if (type === 'edit' && row.id) {
    Object.assign(formData, {
      productName: row.productName,
      specification: row.specification,
      supplierName: row.supplierName,
      oldPrice: row.oldPrice,
      newPrice: row.newPrice,
      effectiveTime: row.effectiveTime,
      reason: row.reason,
      remark: row.remark
    })
  } else {
    Object.assign(formData, {
      productName: '',
      specification: '',
      supplierName: '',
      oldPrice: 0,
      newPrice: 0,
      effectiveTime: '',
      reason: '',
      remark: ''
    })
  }
  dialogVisible.value = true
}
const handleSubmit = () => {
  if (dialogType.value === 'add') {
    const priceChange = ((formData.newPrice - formData.oldPrice) / formData.oldPrice) * 100
    const newPrice = {
      id: Date.now(),
      productName: formData.productName,
      specification: formData.specification,
      supplierName: formData.supplierName,
      oldPrice: formData.oldPrice,
      newPrice: formData.newPrice,
      priceChange: priceChange,
      effectiveTime: formData.effectiveTime,
      status: 'pending',
      reason: formData.reason,
      remark: formData.remark
    }
    tableData.value.unshift(newPrice)
    ElMessage.success('新增成功')
  }
  dialogVisible.value = false
}
const handleApply = (row) => {
  row.status = 'active'
  ElMessage.success('价格已应用')
}
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 handleBatchUpdate = () => {
  ElMessage.success('批量更新成功')
}
const handleBatchDelete = () => {
  ElMessage.success('批量删除成功')
}
const handleSelectionChange = (rows) => {
  selectedRows.value = rows
}
</script>
<style scoped>
.app-container { padding: 20px; }
.search-card { margin-bottom: 20px; }
.table-card { margin-bottom: 20px; }
.table-header { margin-bottom: 20px; }
</style>
src/views/procurementManagement/purchaseOrder/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,188 @@
<template>
  <div class="app-container">
    <el-card class="search-card" shadow="never">
      <el-form :model="searchForm" :inline="true">
        <el-form-item label="供应商名称:">
          <el-input v-model="searchForm.supplierName" placeholder="请输入供应商名称" clearable />
        </el-form-item>
        <el-form-item label="订单状态:">
          <el-select v-model="searchForm.status" placeholder="请选择状态" clearable>
            <el-option label="草稿" value="draft" />
            <el-option label="待审核" value="pending" />
            <el-option label="已审核" value="approved" />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleSearch">搜索</el-button>
          <el-button @click="resetSearch">重置</el-button>
        </el-form-item>
      </el-form>
    </el-card>
    <el-card class="table-card" shadow="never">
      <div class="table-header">
        <el-button type="primary" @click="openDialog('add')">新增订单</el-button>
        <el-button type="danger" @click="handleBatchDelete" :disabled="!selectedRows.length">批量删除</el-button>
      </div>
      <el-table :data="tableData" border v-loading="loading" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="订单编号" prop="orderNo" width="180" />
        <el-table-column label="供应商名称" prop="supplierName" />
        <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="totalAmount" width="120">
          <template #default="{ row }">Â¥{{ row.totalAmount.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 }">
            <el-button type="primary" size="small" @click="openDialog('edit', row)">编辑</el-button>
            <el-button type="success" size="small" @click="viewDetails(row)">查看</el-button>
            <el-button type="danger" size="small" @click="handleDelete(row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
    </el-card>
    <el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? '新增采购订单' : '编辑采购订单'" width="800px">
      <el-form :model="formData" ref="formRef" label-width="120px">
        <el-form-item label="供应商名称" prop="supplierName">
          <el-select v-model="formData.supplierName" placeholder="请选择供应商" style="width: 100%">
            <el-option label="供应商A" value="供应商A" />
            <el-option label="供应商B" value="供应商B" />
          </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">确定</el-button>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
const loading = ref(false)
const dialogVisible = ref(false)
const dialogType = ref('add')
const selectedRows = ref([])
const searchForm = reactive({
  supplierName: '',
  status: ''
})
const formData = reactive({
  supplierName: '',
  remark: ''
})
const mockData = [
  {
    id: 1,
    orderNo: 'PO20241201001',
    supplierName: '供应商A',
    status: 'approved',
    totalAmount: 12500.00,
    createTime: '2024-12-01 10:30:00',
    remark: '常规采购'
  }
]
const tableData = ref([...mockData])
const getStatusType = (status) => {
  const statusMap = { draft: 'info', pending: 'warning', approved: 'success' }
  return statusMap[status] || 'info'
}
const getStatusText = (status) => {
  const statusMap = { draft: '草稿', pending: '待审核', approved: '已审核' }
  return statusMap[status] || '未知'
}
const handleSearch = () => {
  loading.value = true
  setTimeout(() => {
    loading.value = false
  }, 500)
}
const resetSearch = () => {
  Object.assign(searchForm, { supplierName: '', status: '' })
}
const openDialog = (type, row = {}) => {
  dialogType.value = type
  if (type === 'edit' && row.id) {
    Object.assign(formData, { supplierName: row.supplierName, remark: row.remark })
  } else {
    Object.assign(formData, { supplierName: '', remark: '' })
  }
  dialogVisible.value = true
}
const handleSubmit = () => {
  if (dialogType.value === 'add') {
    const newOrder = {
      id: Date.now(),
      orderNo: `PO${Date.now()}`,
      supplierName: formData.supplierName,
      status: 'draft',
      totalAmount: 0,
      createTime: new Date().toLocaleString(),
      remark: formData.remark
    }
    tableData.value.unshift(newOrder)
    ElMessage.success('新增成功')
  }
  dialogVisible.value = false
}
const viewDetails = (row) => {
  ElMessage.info('查看详情功能')
}
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
  }
  ElMessage.success('批量删除成功')
}
const handleSelectionChange = (rows) => {
  selectedRows.value = rows
}
</script>
<style scoped>
.app-container { padding: 20px; }
.search-card { margin-bottom: 20px; }
.table-card { margin-bottom: 20px; }
.table-header { margin-bottom: 20px; }
</style>
src/views/procurementManagement/qualityInspection/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,285 @@
<template>
  <div class="app-container">
    <el-card class="search-card" shadow="never">
      <el-form :model="searchForm" :inline="true">
        <el-form-item label="质检单号:" style="width: 300px;">
          <el-input v-model="searchForm.inspectionNo" placeholder="请输入质检单号" clearable />
        </el-form-item>
        <el-form-item label="质检状态:" style="width: 300px;">
          <el-select v-model="searchForm.status" placeholder="请选择状态" clearable>
            <el-option label="待质检" value="pending" />
            <el-option label="质检中" value="inspecting" />
            <el-option label="已完成" value="completed" />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleSearch">搜索</el-button>
          <el-button @click="resetSearch">重置</el-button>
        </el-form-item>
      </el-form>
    </el-card>
    <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="handleBatchComplete">批量完成</el-button>
        <el-button type="danger" @click="handleBatchDelete">批量删除</el-button>
      </div>
      <el-table :data="tableData" border v-loading="loading" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="质检单号" prop="inspectionNo" width="180" />
        <el-table-column label="到货单号" prop="arrivalNo" width="180" />
        <el-table-column label="供应商名称" prop="supplierName" />
        <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="qualifiedQuantity" width="100" />
        <el-table-column label="不合格数量" prop="unqualifiedQuantity" width="100" />
        <el-table-column label="质检时间" prop="inspectionTime" width="180" />
        <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="handleComplete(row)" v-if="row.status !== 'completed'">完成</el-button>
            <el-button type="danger" link @click="handleDelete(row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
    </el-card>
    <el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? '新增质检单' : '编辑质检单'" width="1000px">
      <el-form :model="formData" label-width="120px">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="到货单号">
              <el-select v-model="formData.arrivalNo" placeholder="请选择到货单" style="width: 100%">
                <el-option label="AR20241201001" value="AR20241201001" />
                <el-option label="AR20241201002" value="AR20241201002" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="供应商名称">
              <el-input v-model="formData.supplierName" placeholder="供应商名称" readonly />
            </el-form-item>
          </el-col>
        </el-row>
        <el-form-item label="质检商品">
          <div class="product-list" style="width: 100%;">
            <el-table :data="formData.products" border width="100%">
              <el-table-column label="商品名称" width="150">
                <template #default="{ row }">
                  <el-input v-model="row.productName" placeholder="请输入商品名称" />
                </template>
              </el-table-column>
              <el-table-column label="规格型号" width="150">
                <template #default="{ row }">
                  <el-input v-model="row.specification" placeholder="请输入规格型号" />
                </template>
              </el-table-column>
              <el-table-column label="到货数量" width="150">
                <template #default="{ row }">
                  <el-input-number v-model="row.arrivalQuantity" :min="0" placeholder="数量" style="width: 100%;"/>
                </template>
              </el-table-column>
              <el-table-column label="合格数量" width="150">
                <template #default="{ row }">
                  <el-input-number v-model="row.qualifiedQuantity" :min="0" placeholder="数量"  style="width: 100%;"/>
                </template>
              </el-table-column>
              <el-table-column label="不合格数量" width="150">
                <template #default="{ row }">
                  <el-input-number v-model="row.unqualifiedQuantity" :min="0" placeholder="数量"  style="width: 100%;"/>
                </template>
              </el-table-column>
              <el-table-column label="不合格原因" width="200">
                <template #default="{ row }">
                  <el-input v-model="row.unqualifiedReason" placeholder="请输入不合格原因" />
                </template>
              </el-table-column>
              <el-table-column label="操作" width="100">
                <template #default="{ $index }">
                  <el-button type="danger" link @click="removeProduct($index)">删除</el-button>
                </template>
              </el-table-column>
            </el-table>
            <div class="add-product-btn">
              <el-button type="primary" @click="addProduct">添加商品</el-button>
            </div>
          </div>
        </el-form-item>
        <el-form-item label="质检员">
          <el-input v-model="formData.inspector" placeholder="请输入质检员姓名" />
        </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">确定</el-button>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
const loading = ref(false)
const dialogVisible = ref(false)
const dialogType = ref('add')
const selectedRows = ref([])
const searchForm = reactive({
  inspectionNo: '',
  status: ''
})
const formData = reactive({
  arrivalNo: '',
  supplierName: '',
  products: [],
  inspector: '',
  remark: ''
})
const mockData = [
  {
    id: 1,
    inspectionNo: 'QI20241201001',
    arrivalNo: 'AR20241201001',
    supplierName: '供应商A',
    status: 'completed',
    qualifiedQuantity: 240,
    unqualifiedQuantity: 10,
    inspectionTime: '2024-12-01 16:30:00',
    inspector: '张三',
    remark: '质检完成'
  }
]
const tableData = ref([...mockData])
const getStatusType = (status) => {
  const statusMap = { pending: 'info', inspecting: 'warning', completed: 'success' }
  return statusMap[status] || 'info'
}
const getStatusText = (status) => {
  const statusMap = { pending: '待质检', inspecting: '质检中', completed: '已完成' }
  return statusMap[status] || '未知'
}
const handleSearch = () => {
  loading.value = true
  setTimeout(() => { loading.value = false }, 500)
}
const resetSearch = () => {
  Object.assign(searchForm, { inspectionNo: '', status: '' })
}
const openDialog = (type, row = {}) => {
  dialogType.value = type
  if (type === 'edit' && row.id) {
    Object.assign(formData, {
      arrivalNo: row.arrivalNo,
      supplierName: row.supplierName,
      inspector: row.inspector,
      remark: row.remark
    })
  } else {
    Object.assign(formData, {
      arrivalNo: '',
      supplierName: '',
      products: [],
      inspector: '',
      remark: ''
    })
  }
  dialogVisible.value = true
}
const handleSubmit = () => {
  if (dialogType.value === 'add') {
    const newInspection = {
      id: Date.now(),
      inspectionNo: `QI${Date.now()}`,
      arrivalNo: formData.arrivalNo,
      supplierName: formData.supplierName,
      status: 'pending',
      qualifiedQuantity: 0,
      unqualifiedQuantity: 0,
      inspectionTime: new Date().toLocaleString(),
      inspector: formData.inspector,
      remark: formData.remark
    }
    tableData.value.unshift(newInspection)
    ElMessage.success('新增成功')
  }
  dialogVisible.value = false
}
const handleComplete = (row) => {
  row.status = 'completed'
  ElMessage.success('质检完成')
}
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 handleBatchComplete = () => {
  ElMessage.success('批量完成成功')
}
const handleBatchDelete = () => {
  ElMessage.success('批量删除成功')
}
const handleSelectionChange = (rows) => {
  selectedRows.value = rows
}
const addProduct = () => {
  formData.products.push({
    productName: '',
    specification: '',
    arrivalQuantity: 0,
    qualifiedQuantity: 0,
    unqualifiedQuantity: 0,
    unqualifiedReason: ''
  })
}
const removeProduct = (index) => {
  formData.products.splice(index, 1)
}
</script>
<style scoped>
.app-container { padding: 20px; }
.search-card { margin-bottom: 20px; }
.table-card { margin-bottom: 20px; }
.table-header { margin-bottom: 20px; }
.product-list { border: 1px solid #dcdfe6; border-radius: 4px; padding: 15px; }
.product-item { margin-bottom: 15px; padding: 10px; background-color: #f5f7fa; border-radius: 4px; }
.add-product-btn { margin-top: 15px; text-align: center; }
</style>
src/views/procurementManagement/returnManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,234 @@
<template>
  <div class="app-container">
    <el-card class="search-card" shadow="never">
      <el-form :model="searchForm" :inline="true">
        <el-form-item label="退货单号:" style="width: 300px;">
          <el-input v-model="searchForm.returnNo" placeholder="请输入退货单号" clearable />
        </el-form-item>
        <el-form-item label="退货类型:" style="width: 300px;">
          <el-select v-model="searchForm.returnType" placeholder="请选择类型" clearable>
            <el-option label="采购退货" value="purchase" />
            <el-option label="质检退货" value="quality" />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleSearch">搜索</el-button>
          <el-button @click="resetSearch">重置</el-button>
        </el-form-item>
      </el-form>
    </el-card>
    <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>
      <el-table :data="tableData" border v-loading="loading" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="退货单号" prop="returnNo" width="180" />
        <el-table-column label="关联单号" prop="relatedNo" width="180" />
        <el-table-column label="退货类型" prop="returnType" width="100">
          <template #default="{ row }">
            <el-tag :type="row.returnType === 'purchase' ? 'danger' : 'warning'">
              {{ getReturnTypeText(row.returnType) }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="供应商名称" prop="supplierName" />
        <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="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 }">
            <el-button type="primary" link @click="openDialog('edit', row)">编辑</el-button>
            <el-button type="success" link @click="handleApprove(row)" v-if="row.status === 'pending'">审核</el-button>
            <el-button type="danger" link @click="handleDelete(row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
    </el-card>
    <el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? '新增退货单' : '编辑退货单'" width="600px">
      <el-form :model="formData" label-width="120px">
        <el-form-item label="退货类型">
          <el-select v-model="formData.returnType" placeholder="请选择退货类型" style="width: 100%">
            <el-option label="采购退货" value="purchase" />
            <el-option label="质检退货" value="quality" />
          </el-select>
        </el-form-item>
        <el-form-item label="关联单号">
          <el-input v-model="formData.relatedNo" placeholder="请输入关联单号" />
        </el-form-item>
        <el-form-item label="供应商名称">
          <el-input v-model="formData.supplierName" placeholder="请输入供应商名称" />
        </el-form-item>
        <el-form-item label="退货原因">
          <el-select v-model="formData.returnReason" placeholder="请选择退货原因" style="width: 100%">
            <el-option label="质量问题" value="quality" />
            <el-option label="规格不符" value="specification" />
            <el-option label="数量错误" value="quantity" />
          </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">确定</el-button>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
const loading = ref(false)
const dialogVisible = ref(false)
const dialogType = ref('add')
const selectedRows = ref([])
const searchForm = reactive({
  returnNo: '',
  returnType: ''
})
const formData = reactive({
  returnType: '',
  relatedNo: '',
  supplierName: '',
  returnReason: '',
  remark: ''
})
const mockData = [
  {
    id: 1,
    returnNo: 'RT20241201001',
    relatedNo: 'PO20241201001',
    returnType: 'purchase',
    supplierName: '供应商A',
    status: 'approved',
    returnAmount: 500.00,
    createTime: '2024-12-01 17:30:00',
    returnReason: '质量问题',
    remark: '商品存在质量问题'
  }
]
const tableData = ref([...mockData])
const getReturnTypeText = (type) => {
  const typeMap = { purchase: '采购退货', quality: '质检退货' }
  return typeMap[type] || '未知'
}
const getStatusType = (status) => {
  const statusMap = { pending: 'warning', approved: 'success', returned: 'info' }
  return statusMap[status] || 'info'
}
const getStatusText = (status) => {
  const statusMap = { pending: '待审核', approved: '已审核', returned: '已退货' }
  return statusMap[status] || '未知'
}
const handleSearch = () => {
  loading.value = true
  setTimeout(() => { loading.value = false }, 500)
}
const resetSearch = () => {
  Object.assign(searchForm, { returnNo: '', returnType: '' })
}
const openDialog = (type, row = {}) => {
  dialogType.value = type
  if (type === 'edit' && row.id) {
    Object.assign(formData, {
      returnType: row.returnType,
      relatedNo: row.relatedNo,
      supplierName: row.supplierName,
      returnReason: row.returnReason,
      remark: row.remark
    })
  } else {
    Object.assign(formData, {
      returnType: '',
      relatedNo: '',
      supplierName: '',
      returnReason: '',
      remark: ''
    })
  }
  dialogVisible.value = true
}
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
    }
    tableData.value.unshift(newReturn)
    ElMessage.success('新增成功')
  }
  dialogVisible.value = false
}
const handleApprove = (row) => {
  row.status = 'approved'
  ElMessage.success('审核通过')
}
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 handleBatchApprove = () => {
  ElMessage.success('批量审核成功')
}
const handleBatchDelete = () => {
  ElMessage.success('批量删除成功')
}
const handleSelectionChange = (rows) => {
  selectedRows.value = rows
}
</script>
<style scoped>
.app-container { padding: 20px; }
.search-card { margin-bottom: 20px; }
.table-card { margin-bottom: 20px; }
.table-header { margin-bottom: 20px; }
</style>
src/views/productManagement/productIdentifier/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,708 @@
<template>
  <div class="app-container">
    <el-card class="box-card">
             <!-- æœç´¢åŒºåŸŸ -->
       <el-row :gutter="20" class="search-row">
         <el-col :span="6">
           <el-input
             v-model="searchForm.productName"
             placeholder="请输入产品名称"
             clearable
             @keyup.enter="handleSearch"
           >
             <template #prefix>
               <el-icon><Search /></el-icon>
             </template>
           </el-input>
         </el-col>
         <el-col :span="6">
           <el-select v-model="searchForm.identifierType" placeholder="请选择标识类型" clearable>
             <el-option label="二维码" value="二维码"></el-option>
             <el-option label="防伪码" value="防伪码"></el-option>
           </el-select>
         </el-col>
         <el-col :span="6">
           <el-select v-model="searchForm.status" placeholder="请选择状态" clearable>
             <el-option label="已生成" value="已生成"></el-option>
             <el-option label="已分配" value="已分配"></el-option>
             <el-option label="已使用" value="已使用"></el-option>
             <el-option label="已作废" value="已作废"></el-option>
           </el-select>
         </el-col>
         <el-col :span="6">
           <el-button type="primary" @click="handleSearch">搜索</el-button>
           <el-button @click="resetSearch">重置</el-button>
           <el-button style="float: right;" type="primary" @click="handleAdd">
             æ–°å¢žæ ‡è¯†
           </el-button>
         </el-col>
       </el-row>
      <!-- äº§å“æ ‡è¯†åˆ—表 -->
      <el-table
        :data="filteredList"
        style="width: 100%"
        v-loading="loading"
        border
        stripe
        height="calc(100vh - 22em)"
      >
        <el-table-column prop="id" label="ID" width="80" align="center"/>
        <el-table-column prop="productName" label="产品名称" width="150" />
        <el-table-column prop="productCode" label="产品编码" width="120" />
        <el-table-column prop="batchNo" label="批次号" width="120" />
        <el-table-column prop="identifierType" label="标识类型" width="100">
          <template #default="scope">
            <el-tag :type="getIdentifierTypeType(scope.row.identifierType)">
              {{ scope.row.identifierType }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="identifierCode" label="标识码" />
        <el-table-column prop="status" label="状态" width="100">
          <template #default="scope">
            <el-tag :type="getStatusType(scope.row.status)">
              {{ scope.row.status }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="generateTime" label="生成时间" width="160" />
        <el-table-column label="操作" fixed="right" align="center" width="280">
          <template #default="scope">
            <el-button link type="primary" @click="handleView(scope.row)">查看</el-button>
            <el-button link type="primary" @click="handleEdit(scope.row)">编辑</el-button>
            <el-button link type="success" @click="generateQRCode(scope.row)">生成二维码</el-button>
            <el-button link type="primary" @click="handleExport(scope.row)">导出</el-button>
            <el-button link type="primary" @click="handleReassign(scope.row)" v-if="scope.row.status === '已分配'">重新分配</el-button>
            <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- åˆ†é¡µ -->
      <pagination
        :total="pagination.total"
        layout="total, sizes, prev, pager, next, jumper"
        :page="pagination.currentPage"
        :limit="pagination.pageSize"
        @pagination="handleCurrentChange"
      />
    </el-card>
    <!-- æ–°å¢ž/编辑对话框 -->
    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="700px">
      <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="产品名称" prop="productName">
              <el-input v-model="form.productName" placeholder="请输入产品名称"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="产品编码" prop="productCode">
              <el-input v-model="form.productCode" placeholder="请输入产品编码"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="批次号" prop="batchNo">
              <el-input v-model="form.batchNo" placeholder="请输入批次号"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="标识类型" prop="identifierType">
              <el-select v-model="form.identifierType" placeholder="请选择标识类型" style="width: 100%">
                <el-option label="二维码" value="二维码"></el-option>
                <el-option label="防伪码" value="防伪码"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="生成数量" prop="quantity">
              <el-input-number v-model="form.quantity" :min="1" :max="10000" style="width: 100%"></el-input-number>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="状态" prop="status">
              <el-select v-model="form.status" placeholder="请选择状态" style="width: 100%">
                <el-option label="已生成" value="已生成"></el-option>
                <el-option label="已分配" value="已分配"></el-option>
                <el-option label="已使用" value="已使用"></el-option>
                <el-option label="已作废" value="已作废"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="备注" prop="remark">
              <el-input type="textarea" v-model="form.remark" placeholder="请输入备注信息" rows="3"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="dialogVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="handleSubmit">ç¡® å®š</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- æ ‡è¯†ç”Ÿæˆå¯¹è¯æ¡† -->
    <el-dialog v-model="generateDialogVisible" title="标识生成" width="500px">
      <el-form label-width="100px">
        <el-form-item label="产品名称">
          <span>{{ currentProduct.productName }}</span>
        </el-form-item>
        <el-form-item label="产品编码">
          <span>{{ currentProduct.productCode }}</span>
        </el-form-item>
        <el-form-item label="批次号">
          <span>{{ currentProduct.batchNo }}</span>
        </el-form-item>
        <el-form-item label="标识类型">
          <span>{{ currentProduct.identifierType }}</span>
        </el-form-item>
        <el-form-item label="生成数量" prop="generateQuantity">
          <el-input-number v-model="generateQuantity" :min="1" :max="10000" style="width: 100%"></el-input-number>
        </el-form-item>
        <el-form-item label="编码规则" prop="codeRule">
          <el-select v-model="codeRule" placeholder="请选择编码规则" style="width: 100%">
            <el-option label="产品编码+批次号+序号" value="产品编码+批次号+序号"></el-option>
            <el-option label="时间戳+随机数" value="时间戳+随机数"></el-option>
            <el-option label="自定义规则" value="自定义规则"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="自定义前缀" prop="customPrefix" v-if="codeRule === '自定义规则'">
          <el-input v-model="customPrefix" placeholder="请输入自定义前缀"></el-input>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="generateDialogVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="generateIdentifiers">生 æˆ</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- é‡æ–°åˆ†é…å¯¹è¯æ¡† -->
    <el-dialog v-model="reassignDialogVisible" title="重新分配标识" width="500px">
      <el-form label-width="100px">
        <el-form-item label="产品名称">
          <span>{{ currentProduct.productName }}</span>
        </el-form-item>
        <el-form-item label="标识码">
          <span>{{ currentProduct.identifierCode }}</span>
        </el-form-item>
        <el-form-item label="新批次号" prop="newBatchNo">
          <el-input v-model="newBatchNo" placeholder="请输入新批次号"></el-input>
        </el-form-item>
        <el-form-item label="分配原因" prop="reassignReason">
          <el-input type="textarea" v-model="reassignReason" rows="3" placeholder="请输入重新分配原因"></el-input>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="reassignDialogVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="saveReassign">ç¡® å®š</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- äºŒç»´ç é¢„览对话框 -->
    <el-dialog v-model="qrCodeDialogVisible" title="二维码预览" width="500px" center>
      <div class="qr-preview-container">
        <div v-if="qrCodeUrl" class="qr-image-container">
          <img :src="qrCodeUrl" alt="二维码" class="qr-image" />
          <div class="qr-info">
            <p><strong>产品名称:</strong>{{ currentQRProduct.productName }}</p>
            <p><strong>产品编码:</strong>{{ currentQRProduct.productCode }}</p>
            <p><strong>批次号:</strong>{{ currentQRProduct.batchNo }}</p>
            <p><strong>标识码:</strong>{{ currentQRProduct.identifierCode }}</p>
            <p><strong>标识类型:</strong>{{ currentQRProduct.identifierType }}</p>
          </div>
        </div>
        <div v-else class="qr-loading">
          <el-icon class="is-loading"><Loading /></el-icon>
          <p>正在生成二维码...</p>
        </div>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="qrCodeDialogVisible = false">关闭</el-button>
          <el-button
            v-if="qrCodeUrl"
            type="primary"
            @click="copyQRContent"
            icon="CopyDocument"
          >
            å¤åˆ¶å†…容
          </el-button>
          <el-button
            v-if="qrCodeUrl"
            type="success"
            @click="downloadQRCode"
            icon="Download"
          >
            ä¸‹è½½äºŒç»´ç 
          </el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Search, Loading, Download } from '@element-plus/icons-vue'
import Pagination from '@/components/PIMTable/Pagination.vue'
import QRCode from 'qrcode'
// å“åº”式数据
const loading = ref(false)
const searchForm = reactive({
  productName: '',
  identifierType: '',
  status: ''
})
const identifierList = ref([
  {
    id: 1,
    productName: '工业传感器A型',
    productCode: 'SENSOR001',
    batchNo: 'B202312001',
    identifierType: '二维码',
    identifierCode: 'QR_SENSOR001_B202312001_001',
    status: '已分配',
    generateTime: '2023-12-01 10:00:00',
    remark: '重要产品标识'
  },
  {
    id: 2,
    productName: '控制面板B型',
    productCode: 'PANEL002',
    batchNo: 'B202312002',
    identifierType: '防伪码',
    identifierCode: 'SEC_PANEL002_B202312002_001',
    status: '已生成',
    generateTime: '2023-12-02 14:30:00',
    remark: '常规产品标识'
  },
  {
    id: 3,
    productName: '数据采集器C型',
    productCode: 'COLLECTOR003',
    batchNo: 'B202312003',
    identifierType: '防伪码',
    identifierCode: 'SEC_COLLECTOR003_B202312003_001',
    status: '已使用',
    generateTime: '2023-12-03 09:15:00',
    remark: '测试产品标识'
  }
])
const pagination = reactive({
  total: 3,
  currentPage: 1,
  pageSize: 10
})
const dialogVisible = ref(false)
const dialogTitle = ref('新增标识')
const form = reactive({
  productName: '',
  productCode: '',
  batchNo: '',
  identifierType: '',
  quantity: 1,
  status: '已生成',
  remark: ''
})
const rules = {
  productName: [{ required: true, message: '请输入产品名称', trigger: 'blur' }],
  productCode: [{ required: true, message: '请输入产品编码', trigger: 'blur' }],
  batchNo: [{ required: true, message: '请输入批次号', trigger: 'blur' }],
  identifierType: [{ required: true, message: '请选择标识类型', trigger: 'change' }],
  quantity: [{ required: true, message: '请输入生成数量', trigger: 'blur' }],
  status: [{ required: true, message: '请选择状态', trigger: 'change' }]
}
const isEdit = ref(false)
const editId = ref(null)
const generateDialogVisible = ref(false)
const reassignDialogVisible = ref(false)
const currentProduct = ref({})
const generateQuantity = ref(1)
const codeRule = ref('')
const customPrefix = ref('')
const newBatchNo = ref('')
const reassignReason = ref('')
const formRef = ref()
// äºŒç»´ç ç›¸å…³å˜é‡
const qrCodeDialogVisible = ref(false)
const qrCodeUrl = ref('')
const currentQRProduct = ref({})
// è®¡ç®—属性
const filteredList = computed(() => {
  let list = identifierList.value
  if (searchForm.productName) {
    list = list.filter(item => item.productName.includes(searchForm.productName))
  }
  if (searchForm.identifierType) {
    list = list.filter(item => item.identifierType === searchForm.identifierType)
  }
  if (searchForm.status) {
    list = list.filter(item => item.status === searchForm.status)
  }
  return list
})
// æ–¹æ³•
const getIdentifierTypeType = (type) => {
  const typeMap = {
    '二维码': 'success',
    '防伪码': 'warning'
  }
  return typeMap[type] || 'info'
}
const getStatusType = (status) => {
  const statusMap = {
    '已生成': 'info',
    '已分配': 'primary',
    '已使用': 'success',
    '已作废': 'danger'
  }
  return statusMap[status] || 'info'
}
const handleSearch = () => {
  // æœç´¢é€»è¾‘已在computed中处理
}
const resetSearch = () => {
  searchForm.productName = ''
  searchForm.identifierType = ''
  searchForm.status = ''
}
const handleAdd = () => {
  dialogTitle.value = '新增标识'
  isEdit.value = false
  form.productName = ''
  form.productCode = ''
  form.batchNo = ''
  form.identifierType = ''
  form.quantity = 1
  form.status = '已生成'
  form.remark = ''
  dialogVisible.value = true
}
const handleView = (row) => {
  // æŸ¥çœ‹æ ‡è¯†è¯¦æƒ…
  ElMessage.info('查看标识详情功能待实现')
}
const handleEdit = (row) => {
  dialogTitle.value = '编辑标识'
  isEdit.value = true
  editId.value = row.id
  Object.assign(form, row)
  dialogVisible.value = true
}
const handleExport = (row) => {
  // å¯¼å‡ºæ ‡è¯†
  ElMessage.success(`已导出标识: ${row.identifierCode}`)
}
const handleReassign = (row) => {
  currentProduct.value = row
  newBatchNo.value = ''
  reassignReason.value = ''
  reassignDialogVisible.value = true
}
const handleDelete = (row) => {
  ElMessageBox.confirm('确认删除该标识吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    const index = identifierList.value.findIndex(item => item.id === row.id)
    if (index > -1) {
      identifierList.value.splice(index, 1)
      pagination.total--
      ElMessage.success('删除成功')
    }
  })
}
// ç”ŸæˆäºŒç»´ç 
const generateQRCode = async (row) => {
  try {
    // æ£€æŸ¥å¿…要字段
    if (!row.productName || !row.productCode || !row.batchNo) {
      ElMessage.warning('产品信息不完整,无法生成二维码')
      return
    }
    currentQRProduct.value = row
    qrCodeUrl.value = ''
    qrCodeDialogVisible.value = true
    // æž„建二维码内容
    let qrContent = ''
    if (row.identifierType === '二维码') {
      qrContent = `${row.productName}|${row.productCode}|${row.batchNo}|${row.identifierCode}`
    } else if (row.identifierType === '防伪码') {
      // é˜²ä¼ªç æ ¼å¼ï¼šSEC_产品编码_批次号_时间戳_随机数
      const timestamp = Date.now()
      const random = Math.random().toString(36).substr(2, 8)
      qrContent = `SEC_${row.productCode}_${row.batchNo}_${timestamp}_${random}`
    }
    // ç”ŸæˆäºŒç»´ç 
    qrCodeUrl.value = await QRCode.toDataURL(qrContent, {
      width: 256,
      margin: 2,
      color: {
        dark: '#000000',
        light: '#FFFFFF'
      },
      errorCorrectionLevel: row.identifierType === '防伪码' ? 'H' : 'M'
    })
    ElMessage.success('二维码生成成功!')
  } catch (error) {
    console.error('生成二维码失败:', error)
    ElMessage.error('生成二维码失败:' + error.message)
    qrCodeDialogVisible.value = false
  }
}
// ä¸‹è½½äºŒç»´ç 
const downloadQRCode = () => {
  if (!qrCodeUrl.value) {
    ElMessage.warning('请先生成二维码')
    return
  }
  const a = document.createElement('a')
  a.href = qrCodeUrl.value
  a.download = `${currentQRProduct.value.productName}_${currentQRProduct.value.identifierType}_${new Date().getTime()}.png`
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
  ElMessage.success('下载成功!')
}
// å¤åˆ¶äºŒç»´ç å†…容
const copyQRContent = async () => {
  if (!currentQRProduct.value) {
    ElMessage.warning('没有可复制的内容')
    return
  }
  try {
    let content = ''
    if (currentQRProduct.value.identifierType === '二维码') {
      content = `${currentQRProduct.value.productName}|${currentQRProduct.value.productCode}|${currentQRProduct.value.batchNo}|${currentQRProduct.value.identifierCode}`
    } else if (currentQRProduct.value.identifierType === '防伪码') {
      const timestamp = Date.now()
      const random = Math.random().toString(36).substr(2, 8)
      content = `SEC_${currentQRProduct.value.productCode}_${currentQRProduct.value.batchNo}_${timestamp}_${random}`
    }
    await navigator.clipboard.writeText(content)
    ElMessage.success('内容已复制到剪贴板')
  } catch (error) {
    // é™çº§æ–¹æ¡ˆ
    const textArea = document.createElement('textarea')
    textArea.value = content
    document.body.appendChild(textArea)
    textArea.select()
    document.execCommand('copy')
    document.body.removeChild(textArea)
    ElMessage.success('内容已复制到剪贴板')
  }
}
const generateIdentifiers = () => {
  if (!codeRule.value) {
    ElMessage.warning('请选择编码规则')
    return
  }
  // ç”Ÿæˆæ ‡è¯†çš„逻辑
  const newIdentifiers = []
  for (let i = 1; i <= generateQuantity.value; i++) {
    let identifierCode = ''
    if (codeRule.value === '产品编码+批次号+序号') {
      identifierCode = `${currentProduct.value.productCode}_${currentProduct.value.batchNo}_${String(i).padStart(3, '0')}`
    } else if (codeRule.value === '时间戳+随机数') {
      identifierCode = `TS_${Date.now()}_${Math.floor(Math.random() * 1000)}`
    } else if (codeRule.value === '自定义规则') {
      identifierCode = `${customPrefix.value || 'CUSTOM'}_${Date.now()}_${i}`
    }
    newIdentifiers.push({
      id: Math.max(...identifierList.value.map(item => item.id)) + i,
      productName: currentProduct.value.productName,
      productCode: currentProduct.value.productCode,
      batchNo: currentProduct.value.batchNo,
      identifierType: currentProduct.value.identifierType,
      identifierCode: identifierCode,
      status: '已生成',
      generateTime: new Date().toLocaleString(),
      remark: '批量生成'
    })
  }
  identifierList.value.push(...newIdentifiers)
  pagination.total += newIdentifiers.length
  ElMessage.success(`成功生成 ${newIdentifiers.length} ä¸ªæ ‡è¯†`)
  generateDialogVisible.value = false
}
const saveReassign = () => {
  if (!newBatchNo.value) {
    ElMessage.warning('请输入新批次号')
    return
  }
  const index = identifierList.value.findIndex(item => item.id === currentProduct.value.id)
  if (index > -1) {
    identifierList.value[index].batchNo = newBatchNo.value
    identifierList.value[index].status = '已分配'
    ElMessage.success('标识重新分配成功')
    reassignDialogVisible.value = false
  }
}
const handleSubmit = () => {
  formRef.value.validate((valid) => {
    if (valid) {
      if (isEdit.value) {
        // ç¼–辑
        const index = identifierList.value.findIndex(item => item.id === editId.value)
        if (index > -1) {
          identifierList.value[index] = { ...form, id: editId.value }
          ElMessage.success('编辑成功')
        }
             } else {
         // æ–°å¢ž
         const newId = Math.max(...identifierList.value.map(item => item.id)) + 1
         // æ ¹æ®æ ‡è¯†ç±»åž‹ç”Ÿæˆä¸åŒçš„æ ‡è¯†ç 
         let identifierCode = ''
         if (form.identifierType === '二维码') {
           identifierCode = `QR_${form.productCode}_${form.batchNo}_001`
         } else if (form.identifierType === '防伪码') {
           identifierCode = `SEC_${form.productCode}_${form.batchNo}_001`
         }
         identifierList.value.push({
           ...form,
           id: newId,
           identifierCode: identifierCode,
           generateTime: new Date().toLocaleString()
         })
         pagination.total++
         ElMessage.success('新增成功')
       }
      dialogVisible.value = false
    }
  })
}
const handleCurrentChange = (val) => {
  pagination.currentPage = val.page
  pagination.pageSize = val.limit
}
</script>
<style scoped>
.search-row {
  margin-bottom: 20px;
}
.quick-actions-row {
  margin-bottom: 20px;
}
.quick-actions-row .el-alert {
  margin-bottom: 0;
}
.quick-actions-row .el-alert p {
  margin: 5px 0;
  font-size: 14px;
  line-height: 1.5;
}
/* äºŒç»´ç é¢„览样式 */
.qr-preview-container {
  text-align: center;
  padding: 20px;
}
.qr-image-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
}
.qr-image {
  max-width: 100%;
  height: auto;
  border: 2px solid #e0e0e0;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.qr-info {
  text-align: left;
  background: #f8f9fa;
  padding: 15px;
  border-radius: 8px;
  min-width: 300px;
}
.qr-info p {
  margin: 8px 0;
  color: #666;
  font-size: 14px;
}
.qr-loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 15px;
  padding: 40px 0;
}
.qr-loading .el-icon {
  font-size: 32px;
  color: #409EFF;
}
.qr-loading p {
  color: #666;
  margin: 0;
}
</style>
src/views/qualityManagement/nonconformingManagement/index.vue
@@ -4,7 +4,7 @@
      <div style="display: flex;flex-direction: row;align-items: center;">
        <div>
          <span class="search_title">类型:</span>
          <el-select v-model="searchForm.inspectType" clearable style="width: 240px" @change="handleQuery">
          <el-select v-model="searchForm.inspectType" clearable style="width: 200px" @change="handleQuery">
            <el-option label="原材料检验" :value="0" />
            <el-option label="过程检验" :value="1" />
            <el-option label="出厂检验" :value="2" />
@@ -12,7 +12,7 @@
        </div>
        <div style="margin-left: 10px">
          <span class="search_title">状态:</span>
          <el-select v-model="searchForm.inspectState" clearable style="width: 240px" @change="handleQuery">
          <el-select v-model="searchForm.inspectState" clearable style="width: 200px" @change="handleQuery">
            <el-option label="待处理" :value="0" />
            <el-option label="已处理" :value="1" />
          </el-select>
@@ -21,7 +21,7 @@
          <span class="search_title">产品名称:</span>
          <el-input
              v-model="searchForm.productName"
              style="width: 240px"
              style="width: 200px"
              placeholder="请输入产品名称搜索"
              @change="handleQuery"
              clearable
@@ -30,6 +30,7 @@
        </div>
        <span  style="margin-left: 10px" class="search_title">检测日期:</span>
        <el-date-picker  v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
                                                 style="width: 300px"
                         placeholder="请选择" clearable @change="changeDaterange" />
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">搜索</el-button>
      </div>
src/views/qualityManagement/visualization/qualityDashboard.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,307 @@
<template>
  <div class="quality-dashboard">
    <el-row :gutter="16">
      <el-col :xs="24" :sm="12">
        <el-card shadow="hover" class="panel">
          <template #header>
            <div class="panel-title">
              æ£€æµ‹æ ·å“åŠ¨æ€çŠ¶æ€
              <div class="actions">
                <el-switch v-model="voiceEnabled" active-text="语音预警" inactive-text="静音" />
              </div>
            </div>
          </template>
          <div class="status-list">
            <div v-for="item in sampleStatus" :key="item.id" class="status-item" :class="item.status">
              <div class="left">
                <span class="dot" :class="item.status"></span>
                <span class="name">{{ item.name }}</span>
              </div>
              <div class="right">
                <el-tag :type="statusTagType(item.status)" size="small">{{ statusLabel(item.status) }}</el-tag>
                <span class="time">{{ item.time }}</span>
              </div>
            </div>
          </div>
        </el-card>
      </el-col>
      <el-col :xs="24" :sm="12">
        <el-card shadow="hover" class="panel">
          <template #header>
            <div class="panel-title">任务排行(Top 10)</div>
          </template>
          <EChart :xAxis="tasksXAxis" :yAxis="[{ type: 'value' }]" :series="tasksSeries" :grid="{ left: 40, right: 20, top: 20, bottom: 40 }" :tooltip="{ trigger: 'axis' }" :barColors="['#3b82f6']" :chartStyle="{ height: '320px', width: '100%' }" />
        </el-card>
      </el-col>
    </el-row>
    <el-row :gutter="16" style="margin-top: 16px;">
      <el-col :xs="24" :sm="14">
        <el-card shadow="hover" class="panel">
          <template #header>
            <div class="panel-title">历史趋势</div>
          </template>
          <EChart :xAxis="[{ type: 'category', data: trendXAxis }]" :yAxis="[{ type: 'value', name: '数量' }]" :series="trendSeries" :tooltip="{ trigger: 'axis' }" :legend="{ top: 0 }" :lineColors="['#10b981', '#f59e0b']" :chartStyle="{ height: '340px', width: '100%' }" />
        </el-card>
      </el-col>
      <el-col :xs="24" :sm="10">
        <el-card shadow="hover" class="panel">
          <template #header>
            <div class="panel-title">合格率分析</div>
          </template>
          <EChart :series="passRateSeries" :legend="{ show: false }" :chartStyle="{ height: '340px', width: '100%' }" />
          <div class="passrate-text">
            å½“前合格率:<b>{{ (passRate * 100).toFixed(1) }}%</b>
          </div>
        </el-card>
      </el-col>
    </el-row>
    <el-row :gutter="16" style="margin-top: 16px;">
      <el-col :xs="24">
        <el-card shadow="hover" class="panel">
          <template #header>
            <div class="panel-title">SPC æŽ§åˆ¶å›¾ï¼ˆXbar)</div>
          </template>
          <EChart :xAxis="[{ type: 'category', data: spcXAxis }]" :yAxis="[{ type: 'value', name: '测量值' }]" :series="spcSeries" :legend="{ top: 0 }" :tooltip="{ trigger: 'axis' }" :lineColors="['#2563eb', '#ef4444', '#f97316', '#22c55e']" :chartStyle="{ height: '380px', width: '100%' }" />
        </el-card>
      </el-col>
    </el-row>
  </div>
</template>
<script setup>
import { onMounted, onBeforeUnmount, reactive, ref } from 'vue'
import EChart from '@/components/Echarts/echarts.vue'
const voiceEnabled = ref(true)
let dataTimer = null
// 1) æ ·å“åŠ¨æ€çŠ¶æ€ï¼ˆæ»šåŠ¨æ›´æ–°ï¼‰
const sampleStatus = ref([])
const statusPool = ['processing', 'warning', 'error', 'success']
function statusLabel(s) {
  return s === 'processing' ? '检测中' : s === 'warning' ? '预警' : s === 'error' ? '不合格' : '合格'
}
function statusTagType(s) {
  return s === 'processing' ? 'info' : s === 'warning' ? 'warning' : s === 'error' ? 'danger' : 'success'
}
function randomSample() {
  const id = Math.random().toString(36).slice(2, 8)
  const status = statusPool[Math.floor(Math.random() * statusPool.length)]
  const name = `样品-${Math.floor(Math.random() * 900 + 100)}`
  const time = new Date().toLocaleTimeString('zh-CN', { hour12: false })
  return { id, name, status, time }
}
// 2) ä»»åŠ¡æŽ’è¡Œï¼ˆæŸ±çŠ¶å›¾ï¼‰
const tasksXAxis = reactive([{ type: 'category', data: [] }])
const tasksSeries = ref([
  {
    type: 'bar',
    data: [],
    label: { show: true, position: 'inside', align: 'center', verticalAlign: 'middle', color: '#fff' },
    encode: undefined,
  },
])
// 3) åŽ†å²è¶‹åŠ¿ï¼ˆæŠ˜çº¿ï¼‰
const trendXAxis = ref([])
const trendSeries = ref([
  { name: '来样数', type: 'line', smooth: true, data: [] },
  { name: '完成数', type: 'line', smooth: true, data: [] },
])
// 4) åˆæ ¼çŽ‡åˆ†æžï¼ˆä»ªè¡¨ç›˜ï¼‰
const passRate = ref(0.92)
const passRateSeries = ref([
  {
    type: 'gauge',
    progress: { show: true, width: 12 },
    axisLine: { lineStyle: { width: 12 } },
    pointer: { show: true },
    detail: { valueAnimation: true, formatter: (v) => `${(v * 100).toFixed(1)}%` },
    data: [{ value: passRate.value }],
  },
])
// 5) SPC æŽ§åˆ¶å›¾
const spcXAxis = ref([])
const spcData = ref([]) // å®žé™…测量值
const CL = ref(50)
const UCL = ref(55)
const LCL = ref(45)
const spcSeries = ref([
  {
    name: '测量均值',
    type: 'line',
    smooth: false,
    symbol: 'circle',
    data: [],
    markLine: {
      symbol: 'none',
      lineStyle: { type: 'dashed', color: '#999' },
      data: [
        { yAxis: () => UCL.value, name: 'UCL' },
        { yAxis: () => CL.value, name: 'CL' },
        { yAxis: () => LCL.value, name: 'LCL' },
      ],
      label: { formatter: ({ name }) => name },
    },
  },
  { name: 'UCL', type: 'line', data: [], symbol: 'none', lineStyle: { type: 'dashed', color: '#ef4444' } },
  { name: 'CL', type: 'line', data: [], symbol: 'none', lineStyle: { type: 'dashed', color: '#f97316' } },
  { name: 'LCL', type: 'line', data: [], symbol: 'none', lineStyle: { type: 'dashed', color: '#22c55e' } },
])
// è¯­éŸ³æ’­æŠ¥
function speak(text) {
  if (!voiceEnabled.value) return
  if (!('speechSynthesis' in window)) return
  const utter = new SpeechSynthesisUtterance(text)
  utter.lang = 'zh-CN'
  try {
    window.speechSynthesis.cancel()
    window.speechSynthesis.speak(utter)
  } catch (e) {
    // ignore
  }
}
function refreshFakeData() {
  // æ ·å“çŠ¶æ€æ»šåŠ¨
  const next = randomSample()
  sampleStatus.value = [next, ...sampleStatus.value].slice(0, 8)
  // ä»»åŠ¡æŽ’è¡Œ
  const tasks = Array.from({ length: 10 }).map((_, i) => ({ name: `任务-${i + 1}`, count: Math.floor(Math.random() * 100 + 20) }))
  tasks.sort((a, b) => a.count - b.count)
  tasksXAxis.data = tasks.map(t => t.name)
  tasksSeries.value[0].data = tasks.map(t => t.count)
  // åŽ†å²è¶‹åŠ¿ï¼ˆè¿½åŠ ç‚¹ï¼‰
  const nowLabel = new Date().toLocaleTimeString('zh-CN', { minute: '2-digit', second: '2-digit' })
  if (trendXAxis.value.length > 15) {
    trendXAxis.value.shift()
    trendSeries.value[0].data.shift()
    trendSeries.value[1].data.shift()
  }
  trendXAxis.value.push(nowLabel)
  const incoming = Math.floor(Math.random() * 30 + 20)
  const finished = Math.max(0, incoming - Math.floor(Math.random() * 10))
  trendSeries.value[0].data.push(incoming)
  trendSeries.value[1].data.push(finished)
  // åˆæ ¼çŽ‡ï¼ˆè½»å¾®æ³¢åŠ¨ï¼‰
  const delta = (Math.random() - 0.5) * 0.02
  passRate.value = Math.min(0.99, Math.max(0.6, passRate.value + delta))
  passRateSeries.value[0].data[0].value = passRate.value
  // SPC æ•°æ®ï¼ˆçª—口移动)
  const nextVal = CL.value + (Math.random() - 0.5) * 8 // æ³¢åЍ
  if (spcXAxis.value.length > 30) {
    spcXAxis.value.shift()
    spcData.value.shift()
  }
  spcXAxis.value.push(`${spcXAxis.value.length + 1}`)
  spcData.value.push(parseFloat(nextVal.toFixed(2)))
  spcSeries.value[0].data = [...spcData.value]
  spcSeries.value[1].data = new Array(spcData.value.length).fill(UCL.value)
  spcSeries.value[2].data = new Array(spcData.value.length).fill(CL.value)
  spcSeries.value[3].data = new Array(spcData.value.length).fill(LCL.value)
  // è§¦å‘播报:合格率过低或 SPC è¶…限
  if (passRate.value < 0.8) {
    speak(`预警,当前合格率为 ${(passRate.value * 100).toFixed(0)}%,低于 80% é˜ˆå€¼`)
  }
  const last = spcData.value[spcData.value.length - 1]
  if (last > UCL.value) {
    speak(`预警,最新测量值 ${last.toFixed(2)} è¶…过上限`)
  }
  if (last < LCL.value) {
    speak(`预警,最新测量值 ${last.toFixed(2)} ä½ŽäºŽä¸‹é™`)
  }
}
onMounted(() => {
  // åˆå§‹åŒ–几条假数据
  sampleStatus.value = Array.from({ length: 5 }).map(() => randomSample())
  for (let i = 0; i < 10; i++) {
    trendXAxis.value.push(`T-${i}`)
    trendSeries.value[0].data.push(Math.floor(Math.random() * 30 + 20))
    trendSeries.value[1].data.push(Math.floor(Math.random() * 25 + 15))
  }
  for (let i = 0; i < 20; i++) {
    spcXAxis.value.push(`${i + 1}`)
    const v = CL.value + (Math.random() - 0.5) * 6
    spcData.value.push(parseFloat(v.toFixed(2)))
  }
  spcSeries.value[0].data = [...spcData.value]
  spcSeries.value[1].data = new Array(spcData.value.length).fill(UCL.value)
  spcSeries.value[2].data = new Array(spcData.value.length).fill(CL.value)
  spcSeries.value[3].data = new Array(spcData.value.length).fill(LCL.value)
  dataTimer = setInterval(refreshFakeData, 10000)
})
onBeforeUnmount(() => {
  if (dataTimer) clearInterval(dataTimer)
  try { window.speechSynthesis && window.speechSynthesis.cancel() } catch (e) {}
})
</script>
<style scoped>
.quality-dashboard {
  padding: 8px;
}
.panel-title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-weight: 600;
}
.status-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: 320px;
  overflow: auto;
}
.status-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 10px;
  border-radius: 6px;
  background: var(--el-fill-color-light);
}
.status-item .left {
  display: flex;
  align-items: center;
  gap: 8px;
}
.status-item .right {
  display: flex;
  align-items: center;
  gap: 10px;
}
.status-item .name { font-weight: 500; }
.status-item .time { color: var(--el-text-color-secondary); font-size: 12px; }
.dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  display: inline-block;
}
.dot.processing { background: #60a5fa; }
.dot.warning { background: #f59e0b; }
.dot.error { background: #ef4444; }
.dot.success { background: #10b981; }
.passrate-text {
  text-align: center;
  margin-top: 8px;
}
</style>
src/views/reportAnalysis/reportManagement.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,733 @@
<template>
  <div class="report-management">
    <!-- é¡µé¢æ ‡é¢˜ -->
    <div class="page-header">
      <h2>报表管理</h2>
      <p>提供样品进度、设备使用、检测项目、领用记录等各类自动统计报表</p>
    </div>
    <!-- ç­›é€‰æ¡ä»¶ -->
    <el-card class="filter-card" shadow="never">
      <el-form :model="filterForm" inline>
        <el-form-item label="时间范围">
          <el-date-picker
            v-model="filterForm.dateRange"
            type="daterange"
            range-separator="至"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
            format="YYYY-MM-DD"
            value-format="YYYY-MM-DD"
            @change="handleFilterChange"
          />
        </el-form-item>
        <el-form-item label="报表类型">
          <el-select v-model="filterForm.reportType" placeholder="请选择报表类型" @change="handleFilterChange">
            <el-option label="样品进度报表" value="sample" />
            <el-option label="设备使用报表" value="equipment" />
            <el-option label="检测项目报表" value="inspection" />
            <el-option label="领用记录报表" value="usage" />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleFilterChange">查询</el-button>
          <el-button @click="resetFilter">重置</el-button>
          <el-button type="success" @click="exportReport">导出报表</el-button>
        </el-form-item>
      </el-form>
    </el-card>
    <!-- ç»Ÿè®¡å¡ç‰‡ -->
    <div class="statistics-cards">
      <el-row :gutter="20">
        <el-col :span="6">
          <el-card class="stat-card" shadow="hover">
            <div class="stat-content">
              <div class="stat-icon">
                <el-icon><Box /></el-icon>
              </div>
              <div class="stat-info">
                <div class="stat-number">{{ statistics.totalSamples }}</div>
                <div class="stat-label">总样品数</div>
              </div>
            </div>
          </el-card>
        </el-col>
        <el-col :span="6">
          <el-card class="stat-card" shadow="hover">
            <div class="stat-content">
              <div class="stat-icon">
                <el-icon><Tools /></el-icon>
              </div>
              <div class="stat-info">
                <div class="stat-number">{{ statistics.activeEquipment }}</div>
                <div class="stat-label">在用设备</div>
              </div>
            </div>
          </el-card>
        </el-col>
        <el-col :span="6">
          <el-card class="stat-card" shadow="hover">
            <div class="stat-content">
              <div class="stat-icon">
                <el-icon><Document /></el-icon>
              </div>
              <div class="stat-info">
                <div class="stat-number">{{ statistics.completedInspections }}</div>
                <div class="stat-label">已完成检测</div>
              </div>
            </div>
          </el-card>
        </el-col>
        <el-col :span="6">
          <el-card class="stat-card" shadow="hover">
            <div class="stat-content">
              <div class="stat-icon">
                <el-icon><ShoppingCart /></el-icon>
              </div>
              <div class="stat-info">
                <div class="stat-number">{{ statistics.totalUsage }}</div>
                <div class="stat-label">总领用次数</div>
              </div>
            </div>
          </el-card>
        </el-col>
      </el-row>
    </div>
    <!-- å›¾è¡¨åŒºåŸŸ -->
    <div class="charts-container">
      <el-row :gutter="20">
        <!-- æ ·å“è¿›åº¦å›¾è¡¨ -->
        <el-col :span="12">
          <el-card class="chart-card" shadow="hover">
            <template #header>
              <div class="card-header">
                <span>样品进度统计</span>
                <el-button type="text" @click="refreshSampleChart">刷新</el-button>
              </div>
            </template>
            <div ref="sampleChartRef" class="chart-container"></div>
          </el-card>
        </el-col>
        <!-- è®¾å¤‡ä½¿ç”¨å›¾è¡¨ -->
        <el-col :span="12">
          <el-card class="chart-card" shadow="hover">
            <template #header>
              <div class="card-header">
                <span>设备使用率统计</span>
                <el-button type="text" @click="refreshEquipmentChart">刷新</el-button>
              </div>
            </template>
            <div ref="equipmentChartRef" class="chart-container"></div>
          </el-card>
        </el-col>
      </el-row>
      <el-row :gutter="20" style="margin-top: 20px;">
        <!-- æ£€æµ‹é¡¹ç›®ç»Ÿè®¡ -->
        <el-col :span="12">
          <el-card class="chart-card" shadow="hover">
            <template #header>
              <div class="card-header">
                <span>检测项目分布</span>
                <el-button type="text" @click="refreshInspectionChart">刷新</el-button>
              </div>
            </template>
            <div ref="inspectionChartRef" class="chart-container"></div>
          </el-card>
        </el-col>
        <!-- é¢†ç”¨è®°å½•趋势 -->
        <el-col :span="12">
          <el-card class="chart-card" shadow="hover">
            <template #header>
              <div class="card-header">
                <span>领用记录趋势</span>
                <el-button type="text" @click="refreshUsageChart">刷新</el-button>
              </div>
            </template>
            <div ref="usageChartRef" class="chart-container"></div>
          </el-card>
        </el-col>
      </el-row>
    </div>
    <!-- è¯¦ç»†æ•°æ®è¡¨æ ¼ -->
    <el-card class="table-card" shadow="hover">
      <template #header>
        <div class="card-header">
          <span>详细数据</span>
          <div>
            <el-button type="primary" size="small" @click="refreshTable">刷新</el-button>
            <el-button type="success" size="small" @click="exportTable">导出</el-button>
          </div>
        </div>
      </template>
      <el-table
        :data="tableData"
        style="width: 100%"
        v-loading="tableLoading"
        stripe
        border
      >
        <el-table-column prop="id" label="编号" width="80" />
        <el-table-column prop="name" label="名称" />
        <el-table-column prop="type" label="类型" width="120" />
        <el-table-column prop="status" label="状态" width="100">
          <template #default="scope">
            <el-tag :type="getStatusType(scope.row.status)">
              {{ scope.row.status }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="progress" label="进度" width="120">
          <template #default="scope">
            <el-progress :percentage="scope.row.progress" :status="getProgressStatus(scope.row.progress)" />
          </template>
        </el-table-column>
        <el-table-column prop="createTime" label="创建时间" width="180" />
        <el-table-column prop="updateTime" label="更新时间" width="180" />
        <el-table-column label="操作" width="150" fixed="right">
          <template #default="scope">
            <el-button type="text" size="small" @click="viewDetail(scope.row)">查看</el-button>
            <el-button type="text" size="small" @click="editItem(scope.row)">编辑</el-button>
          </template>
        </el-table-column>
      </el-table>
      <div class="pagination-container">
        <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>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, nextTick } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import * as echarts from 'echarts'
import { Box, Tools, Document, ShoppingCart } from '@element-plus/icons-vue'
// å“åº”式数据
const filterForm = reactive({
  dateRange: [],
  reportType: 'sample'
})
const statistics = reactive({
  totalSamples: 1250,
  activeEquipment: 45,
  completedInspections: 890,
  totalUsage: 2340
})
const tableData = ref([])
const tableLoading = ref(false)
const pagination = reactive({
  currentPage: 1,
  pageSize: 20,
  total: 0
})
// å›¾è¡¨å¼•用
const sampleChartRef = ref(null)
const equipmentChartRef = ref(null)
const inspectionChartRef = ref(null)
const usageChartRef = ref(null)
// å›¾è¡¨å®žä¾‹
let sampleChart = null
let equipmentChart = null
let inspectionChart = null
let usageChart = null
// åˆå§‹åŒ–数据
const initData = () => {
  // æ¨¡æ‹Ÿè¡¨æ ¼æ•°æ®
  tableData.value = [
    {
      id: 'SP001',
      name: '样品A-001',
      type: '金属材料',
      status: '检测中',
      progress: 75,
      createTime: '2024-01-15 09:30:00',
      updateTime: '2024-01-20 14:20:00'
    },
    {
      id: 'SP002',
      name: '样品B-002',
      type: '塑料制品',
      status: '已完成',
      progress: 100,
      createTime: '2024-01-10 10:15:00',
      updateTime: '2024-01-18 16:45:00'
    },
    {
      id: 'SP003',
      name: '样品C-003',
      type: '电子元件',
      status: '待检测',
      progress: 0,
      createTime: '2024-01-22 08:45:00',
      updateTime: '2024-01-22 08:45:00'
    },
    {
      id: 'EQ001',
      name: '检测设备A',
      type: '光谱仪',
      status: '使用中',
      progress: 60,
      createTime: '2024-01-05 14:20:00',
      updateTime: '2024-01-20 11:30:00'
    },
    {
      id: 'EQ002',
      name: '检测设备B',
      type: '显微镜',
      status: '空闲',
      progress: 0,
      createTime: '2024-01-08 16:10:00',
      updateTime: '2024-01-19 09:15:00'
    }
  ]
  pagination.total = tableData.value.length
}
// åˆå§‹åŒ–样品进度图表
const initSampleChart = () => {
  if (sampleChartRef.value) {
    sampleChart = echarts.init(sampleChartRef.value)
    const option = {
      title: {
        text: '样品进度分布',
        left: 'center'
      },
      tooltip: {
        trigger: 'item',
        formatter: '{a} <br/>{b}: {c} ({d}%)'
      },
      legend: {
        orient: 'vertical',
        left: 'left'
      },
      series: [
        {
          name: '样品状态',
          type: 'pie',
          radius: ['40%', '70%'],
          avoidLabelOverlap: false,
          label: {
            show: false,
            position: 'center'
          },
          emphasis: {
            label: {
              show: true,
              fontSize: '18',
              fontWeight: 'bold'
            }
          },
          labelLine: {
            show: false
          },
          data: [
            { value: 450, name: '已完成' },
            { value: 320, name: '检测中' },
            { value: 280, name: '待检测' },
            { value: 200, name: '已暂停' }
          ]
        }
      ]
    }
    sampleChart.setOption(option)
  }
}
// åˆå§‹åŒ–设备使用图表
const initEquipmentChart = () => {
  if (equipmentChartRef.value) {
    equipmentChart = echarts.init(equipmentChartRef.value)
    const option = {
      title: {
        text: '设备使用率',
        left: 'center'
      },
      tooltip: {
        trigger: 'axis',
        axisPointer: {
          type: 'shadow'
        }
      },
      xAxis: {
        type: 'category',
        data: ['光谱仪', '显微镜', '硬度计', '拉力机', '冲击机', '金相仪']
      },
      yAxis: {
        type: 'value',
        name: '使用率(%)'
      },
      series: [
        {
          name: '使用率',
          type: 'bar',
          data: [85, 60, 75, 90, 45, 70],
          label: {
            show: true,
            position: 'inside',
            align: 'center',
            verticalAlign: 'middle',
            formatter: '{c}%',
            color: '#fff'
          },
          itemStyle: {
            color: function(params) {
              const colors = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399', '#9C27B0']
              return colors[params.dataIndex]
            }
          }
        }
      ]
    }
    equipmentChart.setOption(option)
  }
}
// åˆå§‹åŒ–检测项目图表
const initInspectionChart = () => {
  if (inspectionChartRef.value) {
    inspectionChart = echarts.init(inspectionChartRef.value)
    const option = {
      title: {
        text: '检测项目分布',
        left: 'center'
      },
      tooltip: {
        trigger: 'item'
      },
      legend: {
        orient: 'vertical',
        left: 'left'
      },
      series: [
        {
          name: '检测项目',
          type: 'pie',
          radius: '50%',
          data: [
            { value: 335, name: '物理性能' },
            { value: 310, name: '化学分析' },
            { value: 234, name: '尺寸测量' },
            { value: 135, name: '外观检查' },
            { value: 148, name: '其他检测' }
          ],
          emphasis: {
            itemStyle: {
              shadowBlur: 10,
              shadowOffsetX: 0,
              shadowColor: 'rgba(0, 0, 0, 0.5)'
            }
          }
        }
      ]
    }
    inspectionChart.setOption(option)
  }
}
// åˆå§‹åŒ–领用记录图表
const initUsageChart = () => {
  if (usageChartRef.value) {
    usageChart = echarts.init(usageChartRef.value)
    const option = {
      title: {
        text: '领用记录趋势',
        left: 'center'
      },
      tooltip: {
        trigger: 'axis'
      },
      legend: {
        data: ['领用次数', '归还次数']
      },
      xAxis: {
        type: 'category',
        boundaryGap: false,
        data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
      },
      yAxis: {
        type: 'value'
      },
      series: [
        {
          name: '领用次数',
          type: 'line',
          stack: 'Total',
          data: [120, 132, 101, 134, 90, 230, 210, 182, 191, 234, 290, 330]
        },
        {
          name: '归还次数',
          type: 'line',
          stack: 'Total',
          data: [110, 125, 95, 128, 85, 220, 200, 175, 185, 225, 280, 320]
        }
      ]
    }
    usageChart.setOption(option)
  }
}
// äº‹ä»¶å¤„理函数
const handleFilterChange = () => {
  ElMessage.success('筛选条件已更新')
  // è¿™é‡Œå¯ä»¥æ ¹æ®ç­›é€‰æ¡ä»¶é‡æ–°åŠ è½½æ•°æ®
}
const resetFilter = () => {
  filterForm.dateRange = []
  filterForm.reportType = 'sample'
  ElMessage.info('筛选条件已重置')
}
const exportReport = () => {
  ElMessage.success('报表导出功能开发中...')
}
const refreshSampleChart = () => {
  initSampleChart()
  ElMessage.success('样品进度图表已刷新')
}
const refreshEquipmentChart = () => {
  initEquipmentChart()
  ElMessage.success('设备使用图表已刷新')
}
const refreshInspectionChart = () => {
  initInspectionChart()
  ElMessage.success('检测项目图表已刷新')
}
const refreshUsageChart = () => {
  initUsageChart()
  ElMessage.success('领用记录图表已刷新')
}
const refreshTable = () => {
  tableLoading.value = true
  setTimeout(() => {
    tableLoading.value = false
    ElMessage.success('表格数据已刷新')
  }, 1000)
}
const exportTable = () => {
  ElMessage.success('表格导出功能开发中...')
}
const handleSizeChange = (val) => {
  pagination.pageSize = val
  // é‡æ–°åŠ è½½æ•°æ®
}
const handleCurrentChange = (val) => {
  pagination.currentPage = val
  // é‡æ–°åŠ è½½æ•°æ®
}
const getStatusType = (status) => {
  const statusMap = {
    '已完成': 'success',
    '检测中': 'warning',
    '待检测': 'info',
    '已暂停': 'danger',
    '使用中': 'primary',
    '空闲': 'info'
  }
  return statusMap[status] || 'info'
}
const getProgressStatus = (progress) => {
  if (progress === 100) return 'success'
  if (progress >= 80) return 'warning'
  if (progress >= 50) return ''
  return 'exception'
}
const viewDetail = (row) => {
  ElMessage.info(`查看详情: ${row.name}`)
}
const editItem = (row) => {
  ElMessage.info(`编辑项目: ${row.name}`)
}
// ç”Ÿå‘½å‘¨æœŸ
onMounted(() => {
  initData()
  nextTick(() => {
    initSampleChart()
    initEquipmentChart()
    initInspectionChart()
    initUsageChart()
  })
  // ç›‘听窗口大小变化,重新调整图表大小
  window.addEventListener('resize', () => {
    sampleChart?.resize()
    equipmentChart?.resize()
    inspectionChart?.resize()
    usageChart?.resize()
  })
})
</script>
<style scoped>
.report-management {
  padding: 20px;
  background-color: #f5f5f5;
  min-height: 100vh;
}
.page-header {
  margin-bottom: 20px;
  text-align: center;
}
.page-header h2 {
  color: #303133;
  margin-bottom: 8px;
  font-size: 24px;
  font-weight: 600;
}
.page-header p {
  color: #909399;
  font-size: 14px;
  margin: 0;
}
.filter-card {
  margin-bottom: 20px;
}
.statistics-cards {
  margin-bottom: 20px;
}
.stat-card {
  height: 120px;
}
.stat-content {
  display: flex;
  align-items: center;
  height: 100%;
}
.stat-icon {
  width: 60px;
  height: 60px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-right: 20px;
  font-size: 24px;
  color: white;
}
.stat-card:nth-child(1) .stat-icon {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.stat-card:nth-child(2) .stat-icon {
  background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
.stat-card:nth-child(3) .stat-icon {
  background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}
.stat-card:nth-child(4) .stat-icon {
  background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
}
.stat-info {
  flex: 1;
}
.stat-number {
  font-size: 28px;
  font-weight: bold;
  color: #303133;
  margin-bottom: 8px;
}
.stat-label {
  font-size: 14px;
  color: #909399;
}
.charts-container {
  margin-bottom: 20px;
}
.chart-card {
  margin-bottom: 20px;
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.chart-container {
  height: 300px;
  width: 100%;
}
.table-card {
  margin-bottom: 20px;
}
.pagination-container {
  margin-top: 20px;
  text-align: right;
}
:deep(.el-card__header) {
  padding: 15px 20px;
  border-bottom: 1px solid #ebeef5;
  background-color: #fafafa;
}
:deep(.el-card__body) {
  padding: 20px;
}
:deep(.el-table) {
  margin-bottom: 20px;
}
:deep(.el-progress) {
  margin: 0;
}
:deep(.el-tag) {
  margin: 0;
}
</style>
src/views/reportAnalysis/reportManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,716 @@
<template>
    <div class="report-management">
        <!-- ç­›é€‰æ¡ä»¶ -->
        <el-card class="filter-card" shadow="never">
            <el-form :model="filterForm" inline>
                <el-form-item label="时间范围">
                    <el-date-picker
                        style="width: 300px"
                        v-model="filterForm.dateRange"
                        type="daterange"
                        range-separator="至"
                        start-placeholder="开始日期"
                        end-placeholder="结束日期"
                        format="YYYY-MM-DD"
                        value-format="YYYY-MM-DD"
                        @change="handleFilterChange"
                    />
                </el-form-item>
                <el-form-item label="报表类型">
                    <el-select v-model="filterForm.reportType" placeholder="请选择报表类型" @change="handleFilterChange" style="width: 300px">
                        <el-option label="样品进度报表" value="sample" />
                        <el-option label="设备使用报表" value="equipment" />
                        <el-option label="检测项目报表" value="inspection" />
                        <el-option label="领用记录报表" value="usage" />
                    </el-select>
                </el-form-item>
                <el-form-item>
                    <el-button type="primary" @click="handleFilterChange">查询</el-button>
                    <el-button @click="resetFilter">重置</el-button>
                    <el-button type="success" @click="exportReport">导出报表</el-button>
                </el-form-item>
            </el-form>
        </el-card>
        <!-- ç»Ÿè®¡å¡ç‰‡ -->
        <div class="statistics-cards">
            <el-row :gutter="20">
                <el-col :span="6">
                    <el-card class="stat-card" shadow="hover">
                        <div class="stat-content">
                            <div class="stat-icon">
                                <el-icon><Box /></el-icon>
                            </div>
                            <div class="stat-info">
                                <div class="stat-number">{{ statistics.totalSamples }}</div>
                                <div class="stat-label">总样品数</div>
                            </div>
                        </div>
                    </el-card>
                </el-col>
                <el-col :span="6">
                    <el-card class="stat-card" shadow="hover">
                        <div class="stat-content">
                            <div class="stat-icon">
                                <el-icon><Tools /></el-icon>
                            </div>
                            <div class="stat-info">
                                <div class="stat-number">{{ statistics.activeEquipment }}</div>
                                <div class="stat-label">在用设备</div>
                            </div>
                        </div>
                    </el-card>
                </el-col>
                <el-col :span="6">
                    <el-card class="stat-card" shadow="hover">
                        <div class="stat-content">
                            <div class="stat-icon">
                                <el-icon><Document /></el-icon>
                            </div>
                            <div class="stat-info">
                                <div class="stat-number">{{ statistics.completedInspections }}</div>
                                <div class="stat-label">已完成检测</div>
                            </div>
                        </div>
                    </el-card>
                </el-col>
                <el-col :span="6">
                    <el-card class="stat-card" shadow="hover">
                        <div class="stat-content">
                            <div class="stat-icon">
                                <el-icon><ShoppingCart /></el-icon>
                            </div>
                            <div class="stat-info">
                                <div class="stat-number">{{ statistics.totalUsage }}</div>
                                <div class="stat-label">总领用次数</div>
                            </div>
                        </div>
                    </el-card>
                </el-col>
            </el-row>
        </div>
        <!-- å›¾è¡¨åŒºåŸŸ -->
        <div class="charts-container">
            <el-row :gutter="20">
                <!-- æ ·å“è¿›åº¦å›¾è¡¨ -->
                <el-col :span="12">
                    <el-card class="chart-card" shadow="hover">
                        <template #header>
                            <div class="card-header">
                                <span>样品进度统计</span>
                                <el-button link @click="refreshSampleChart">刷新</el-button>
                            </div>
                        </template>
                        <div ref="sampleChartRef" class="chart-container"></div>
                    </el-card>
                </el-col>
                <!-- è®¾å¤‡ä½¿ç”¨å›¾è¡¨ -->
                <el-col :span="12">
                    <el-card class="chart-card" shadow="hover">
                        <template #header>
                            <div class="card-header">
                                <span>设备使用率统计</span>
                                <el-button link @click="refreshEquipmentChart">刷新</el-button>
                            </div>
                        </template>
                        <div ref="equipmentChartRef" class="chart-container"></div>
                    </el-card>
                </el-col>
            </el-row>
            <el-row :gutter="20" style="margin-top: 20px;">
                <!-- æ£€æµ‹é¡¹ç›®ç»Ÿè®¡ -->
                <el-col :span="12">
                    <el-card class="chart-card" shadow="hover">
                        <template #header>
                            <div class="card-header">
                                <span>检测项目分布</span>
                                <el-button link @click="refreshInspectionChart">刷新</el-button>
                            </div>
                        </template>
                        <div ref="inspectionChartRef" class="chart-container"></div>
                    </el-card>
                </el-col>
                <!-- é¢†ç”¨è®°å½•趋势 -->
                <el-col :span="12">
                    <el-card class="chart-card" shadow="hover">
                        <template #header>
                            <div class="card-header">
                                <span>领用记录趋势</span>
                                <el-button link @click="refreshUsageChart">刷新</el-button>
                            </div>
                        </template>
                        <div ref="usageChartRef" class="chart-container"></div>
                    </el-card>
                </el-col>
            </el-row>
        </div>
        <!-- è¯¦ç»†æ•°æ®è¡¨æ ¼ -->
        <el-card class="table-card" shadow="hover">
            <template #header>
                <div class="card-header">
                    <span>详细数据</span>
                    <div>
                        <el-button type="primary" size="small" @click="refreshTable">刷新</el-button>
                        <el-button type="success" size="small" @click="exportTable">导出</el-button>
                    </div>
                </div>
            </template>
            <el-table
                :data="tableData"
                style="width: 100%"
                v-loading="tableLoading"
                stripe
                border
            >
                <el-table-column prop="id" label="编号" width="80" />
                <el-table-column prop="name" label="名称" />
                <el-table-column prop="type" label="类型" width="120" />
                <el-table-column prop="status" label="状态" width="100">
                    <template #default="scope">
                        <el-tag :type="getStatusType(scope.row.status)">
                            {{ scope.row.status }}
                        </el-tag>
                    </template>
                </el-table-column>
                <el-table-column prop="progress" label="进度" width="120">
                    <template #default="scope">
                        <el-progress :percentage="scope.row.progress" :status="getProgressStatus(scope.row.progress)" />
                    </template>
                </el-table-column>
                <el-table-column prop="createTime" label="创建时间" width="180" />
                <el-table-column prop="updateTime" label="更新时间" width="180" />
                <el-table-column label="操作" width="150" fixed="right">
                    <template #default="scope">
                        <el-button link size="small" @click="viewDetail(scope.row)">查看</el-button>
                        <el-button link size="small" @click="editItem(scope.row)">编辑</el-button>
                    </template>
                </el-table-column>
            </el-table>
            <div class="pagination-container">
                <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>
    </div>
</template>
<script setup>
import { ref, reactive, onMounted, nextTick } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import * as echarts from 'echarts'
import { Box, Tools, Document, ShoppingCart } from '@element-plus/icons-vue'
// å“åº”式数据
const filterForm = reactive({
    dateRange: [],
    reportType: 'sample'
})
const statistics = reactive({
    totalSamples: 1250,
    activeEquipment: 45,
    completedInspections: 890,
    totalUsage: 2340
})
const tableData = ref([])
const tableLoading = ref(false)
const pagination = reactive({
    currentPage: 1,
    pageSize: 20,
    total: 0
})
// å›¾è¡¨å¼•用
const sampleChartRef = ref(null)
const equipmentChartRef = ref(null)
const inspectionChartRef = ref(null)
const usageChartRef = ref(null)
// å›¾è¡¨å®žä¾‹
let sampleChart = null
let equipmentChart = null
let inspectionChart = null
let usageChart = null
// åˆå§‹åŒ–数据
const initData = () => {
    // æ¨¡æ‹Ÿè¡¨æ ¼æ•°æ®
    tableData.value = [
        {
            id: 'SP001',
            name: '样品A-001',
            type: '金属材料',
            status: '检测中',
            progress: 75,
            createTime: '2024-01-15 09:30:00',
            updateTime: '2024-01-20 14:20:00'
        },
        {
            id: 'SP002',
            name: '样品B-002',
            type: '塑料制品',
            status: '已完成',
            progress: 100,
            createTime: '2024-01-10 10:15:00',
            updateTime: '2024-01-18 16:45:00'
        },
        {
            id: 'SP003',
            name: '样品C-003',
            type: '电子元件',
            status: '待检测',
            progress: 0,
            createTime: '2024-01-22 08:45:00',
            updateTime: '2024-01-22 08:45:00'
        },
        {
            id: 'EQ001',
            name: '检测设备A',
            type: '光谱仪',
            status: '使用中',
            progress: 60,
            createTime: '2024-01-05 14:20:00',
            updateTime: '2024-01-20 11:30:00'
        },
        {
            id: 'EQ002',
            name: '检测设备B',
            type: '显微镜',
            status: '空闲',
            progress: 0,
            createTime: '2024-01-08 16:10:00',
            updateTime: '2024-01-19 09:15:00'
        }
    ]
    pagination.total = tableData.value.length
}
// åˆå§‹åŒ–样品进度图表
const initSampleChart = () => {
    if (sampleChartRef.value) {
        sampleChart = echarts.init(sampleChartRef.value)
        const option = {
            title: {
                show: false
            },
            tooltip: {
                trigger: 'item',
                formatter: '{a} <br/>{b}: {c} ({d}%)'
            },
            legend: {
                orient: 'vertical',
                left: 'left'
            },
            series: [
                {
                    name: '样品状态',
                    type: 'pie',
                    radius: ['40%', '70%'],
                    avoidLabelOverlap: false,
                    label: {
                        show: false,
                        position: 'center'
                    },
                    emphasis: {
                        label: {
                            show: true,
                            fontSize: '18',
                            fontWeight: 'bold'
                        }
                    },
                    labelLine: {
                        show: false
                    },
                    data: [
                        { value: 450, name: '已完成' },
                        { value: 320, name: '检测中' },
                        { value: 280, name: '待检测' },
                        { value: 200, name: '已暂停' }
                    ]
                }
            ]
        }
        sampleChart.setOption(option)
    }
}
// åˆå§‹åŒ–设备使用图表
const initEquipmentChart = () => {
    if (equipmentChartRef.value) {
        equipmentChart = echarts.init(equipmentChartRef.value)
        const option = {
            title: {
                show: false
            },
            tooltip: {
                trigger: 'axis',
                axisPointer: {
                    type: 'shadow'
                }
            },
            xAxis: {
                type: 'category',
                data: ['光谱仪', '显微镜', '硬度计', '拉力机', '冲击机', '金相仪']
            },
            yAxis: {
                type: 'value',
                name: '使用率(%)'
            },
            series: [
                {
                    name: '使用率',
                    type: 'bar',
                    data: [85, 60, 75, 90, 45, 70],
                    itemStyle: {
                        color: function(params) {
                            const colors = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399', '#9C27B0']
                            return colors[params.dataIndex]
                        }
                    }
                }
            ]
        }
        equipmentChart.setOption(option)
    }
}
// åˆå§‹åŒ–检测项目图表
const initInspectionChart = () => {
    if (inspectionChartRef.value) {
        inspectionChart = echarts.init(inspectionChartRef.value)
        const option = {
            title: {
                show: false
            },
            tooltip: {
                trigger: 'item'
            },
            legend: {
                orient: 'vertical',
                left: 'left'
            },
            series: [
                {
                    name: '检测项目',
                    type: 'pie',
                    radius: '50%',
                    data: [
                        { value: 335, name: '物理性能' },
                        { value: 310, name: '化学分析' },
                        { value: 234, name: '尺寸测量' },
                        { value: 135, name: '外观检查' },
                        { value: 148, name: '其他检测' }
                    ],
                    emphasis: {
                        itemStyle: {
                            shadowBlur: 10,
                            shadowOffsetX: 0,
                            shadowColor: 'rgba(0, 0, 0, 0.5)'
                        }
                    }
                }
            ]
        }
        inspectionChart.setOption(option)
    }
}
// åˆå§‹åŒ–领用记录图表
const initUsageChart = () => {
    if (usageChartRef.value) {
        usageChart = echarts.init(usageChartRef.value)
        const option = {
            title: {
                show: false
            },
            tooltip: {
                trigger: 'axis'
            },
            legend: {
                data: ['领用次数', '归还次数']
            },
            xAxis: {
                type: 'category',
                boundaryGap: false,
                data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
            },
            yAxis: {
                type: 'value'
            },
            series: [
                {
                    name: '领用次数',
                    type: 'line',
                    stack: 'Total',
                    data: [120, 132, 101, 134, 90, 230, 210, 182, 191, 234, 290, 330]
                },
                {
                    name: '归还次数',
                    type: 'line',
                    stack: 'Total',
                    data: [110, 125, 95, 128, 85, 220, 200, 175, 185, 225, 280, 320]
                }
            ]
        }
        usageChart.setOption(option)
    }
}
// äº‹ä»¶å¤„理函数
const handleFilterChange = () => {
    ElMessage.success('筛选条件已更新')
    // è¿™é‡Œå¯ä»¥æ ¹æ®ç­›é€‰æ¡ä»¶é‡æ–°åŠ è½½æ•°æ®
}
const resetFilter = () => {
    filterForm.dateRange = []
    filterForm.reportType = 'sample'
    ElMessage.info('筛选条件已重置')
}
const exportReport = () => {
    ElMessage.success('报表导出功能开发中...')
}
const refreshSampleChart = () => {
    initSampleChart()
    ElMessage.success('样品进度图表已刷新')
}
const refreshEquipmentChart = () => {
    initEquipmentChart()
    ElMessage.success('设备使用图表已刷新')
}
const refreshInspectionChart = () => {
    initInspectionChart()
    ElMessage.success('检测项目图表已刷新')
}
const refreshUsageChart = () => {
    initUsageChart()
    ElMessage.success('领用记录图表已刷新')
}
const refreshTable = () => {
    tableLoading.value = true
    setTimeout(() => {
        tableLoading.value = false
        ElMessage.success('表格数据已刷新')
    }, 1000)
}
const exportTable = () => {
    ElMessage.success('表格导出功能开发中...')
}
const handleSizeChange = (val) => {
    pagination.pageSize = val
    // é‡æ–°åŠ è½½æ•°æ®
}
const handleCurrentChange = (val) => {
    pagination.currentPage = val
    // é‡æ–°åŠ è½½æ•°æ®
}
const getStatusType = (status) => {
    const statusMap = {
        '已完成': 'success',
        '检测中': 'warning',
        '待检测': 'info',
        '已暂停': 'danger',
        '使用中': 'primary',
        '空闲': 'info'
    }
    return statusMap[status] || 'info'
}
const getProgressStatus = (progress) => {
    if (progress === 100) return 'success'
    if (progress >= 80) return 'warning'
    if (progress >= 50) return ''
    return 'exception'
}
const viewDetail = (row) => {
    ElMessage.info(`查看详情: ${row.name}`)
}
const editItem = (row) => {
    ElMessage.info(`编辑项目: ${row.name}`)
}
// ç”Ÿå‘½å‘¨æœŸ
onMounted(() => {
    initData()
    nextTick(() => {
        initSampleChart()
        initEquipmentChart()
        initInspectionChart()
        initUsageChart()
    })
    // ç›‘听窗口大小变化,重新调整图表大小
    window.addEventListener('resize', () => {
        sampleChart?.resize()
        equipmentChart?.resize()
        inspectionChart?.resize()
        usageChart?.resize()
    })
})
</script>
<style scoped>
.report-management {
    padding: 20px;
    background-color: #f5f5f5;
    min-height: 100vh;
}
.page-header {
    margin-bottom: 20px;
    text-align: center;
}
.page-header h2 {
    color: #303133;
    margin-bottom: 8px;
    font-size: 24px;
    font-weight: 600;
}
.page-header p {
    color: #909399;
    font-size: 14px;
    margin: 0;
}
.filter-card {
    margin-bottom: 20px;
}
.statistics-cards {
    margin-bottom: 20px;
}
.stat-card {
    height: 120px;
}
.stat-content {
    display: flex;
    align-items: center;
    height: 100%;
}
.stat-icon {
    width: 60px;
    height: 60px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-right: 20px;
    font-size: 24px;
    color: white;
}
.stat-card:nth-child(1) .stat-icon {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.stat-card:nth-child(2) .stat-icon {
    background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
.stat-card:nth-child(3) .stat-icon {
    background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}
.stat-card:nth-child(4) .stat-icon {
    background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
}
.stat-info {
    flex: 1;
}
.stat-number {
    font-size: 28px;
    font-weight: bold;
    color: #303133;
    margin-bottom: 8px;
}
.stat-label {
    font-size: 14px;
    color: #909399;
}
.charts-container {
    margin-bottom: 20px;
}
.chart-card {
    margin-bottom: 20px;
}
.card-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
}
.chart-container {
    height: 300px;
    width: 100%;
}
.table-card {
    margin-bottom: 20px;
}
.pagination-container {
    margin-top: 20px;
    text-align: right;
}
:deep(.el-card__header) {
    padding: 15px 20px;
    border-bottom: 1px solid #ebeef5;
    background-color: #fafafa;
}
:deep(.el-card__body) {
    padding: 20px;
}
:deep(.el-table) {
    margin-bottom: 20px;
}
:deep(.el-progress) {
    margin: 0;
}
:deep(.el-tag) {
    margin: 0;
}
</style>
src/views/salesManagement/customerManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,423 @@
<template>
  <div class="app-container">
    <el-card class="box-card">
      <!-- æœç´¢åŒºåŸŸ -->
      <el-row :gutter="20" class="search-row">
        <el-col :span="6">
          <el-input
            v-model="searchForm.name"
            placeholder="请输入客户名称"
            clearable
            @keyup.enter="handleSearch"
          >
            <template #prefix>
              <el-icon><Search /></el-icon>
            </template>
          </el-input>
        </el-col>
        <el-col :span="6">
          <el-select v-model="searchForm.region" placeholder="请选择区域" clearable>
            <el-option label="华东区" value="华东区"></el-option>
            <el-option label="华南区" value="华南区"></el-option>
            <el-option label="华北区" value="华北区"></el-option>
            <el-option label="西南区" value="西南区"></el-option>
          </el-select>
        </el-col>
        <el-col :span="6">
          <el-select v-model="searchForm.level" placeholder="请选择客户等级" clearable>
            <el-option label="VIP客户" value="VIP客户"></el-option>
            <el-option label="重要客户" value="重要客户"></el-option>
            <el-option label="普通客户" value="普通客户"></el-option>
          </el-select>
        </el-col>
        <el-col :span="6">
          <el-button type="primary" @click="handleSearch">搜索</el-button>
          <el-button @click="resetSearch">重置</el-button>
          <el-button style="float: right;" type="primary" @click="handleAdd">
            æ–°å¢žå®¢æˆ·
          </el-button>
        </el-col>
      </el-row>
      <!-- å®¢æˆ·åˆ—表 -->
      <el-table
        :data="filteredList"
        style="width: 100%"
        v-loading="loading"
        border
        stripe
        height="calc(100vh - 22em)"
      >
        <el-table-column prop="id" label="ID" width="80" align="center"/>
        <el-table-column prop="name" label="客户名称" width="150" />
        <el-table-column prop="contactPerson" label="联系人" width="100" />
        <el-table-column prop="phone" label="联系电话" width="140" />
        <el-table-column prop="email" label="邮箱" />
        <el-table-column prop="region" label="区域" width="100" />
        <el-table-column prop="level" label="客户等级" width="100">
          <template #default="scope">
            <el-tag :type="getLevelType(scope.row.level)">
              {{ scope.row.level }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="salesperson" label="负责业务员" width="120" />
        <el-table-column prop="status" label="状态" width="80">
          <template #default="scope">
            <el-tag :type="getStatusType(scope.row.status)">
              {{ scope.row.status }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="操作" width="200" fixed="right" align="center">
          <template #default="scope">
            <el-button link type="primary" @click="handleEdit(scope.row)">编辑</el-button>
            <el-button link type="primary" @click="handleAllocation(scope.row)">分配</el-button>
            <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- åˆ†é¡µ -->
      <pagination
        :total="pagination.total"
        layout="total, sizes, prev, pager, next, jumper"
        :page="pagination.currentPage"
        :limit="pagination.pageSize"
        @pagination="handleCurrentChange"
      />
    </el-card>
    <!-- æ–°å¢ž/编辑对话框 -->
    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px">
      <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="客户名称" prop="name">
              <el-input v-model="form.name" placeholder="请输入客户名称"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="联系人" prop="contactPerson">
              <el-input v-model="form.contactPerson" placeholder="请输入联系人"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="联系电话" prop="phone">
              <el-input v-model="form.phone" placeholder="请输入联系电话"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="邮箱" prop="email">
              <el-input v-model="form.email" placeholder="请输入邮箱"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="区域" prop="region">
              <el-select v-model="form.region" placeholder="请选择区域" style="width: 100%">
                <el-option label="华东区" value="华东区"></el-option>
                <el-option label="华南区" value="华南区"></el-option>
                <el-option label="华北区" value="华北区"></el-option>
                <el-option label="西南区" value="西南区"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="客户等级" prop="level">
              <el-select v-model="form.level" placeholder="请选择客户等级" style="width: 100%">
                <el-option label="VIP客户" value="VIP客户"></el-option>
                <el-option label="重要客户" value="重要客户"></el-option>
                <el-option label="普通客户" value="普通客户"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="负责业务员" prop="salesperson">
              <el-select v-model="form.salesperson" placeholder="请选择业务员" style="width: 100%">
                <el-option label="张三" value="张三"></el-option>
                <el-option label="李四" value="李四"></el-option>
                <el-option label="王五" value="王五"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="状态" prop="status">
              <el-select v-model="form.status" placeholder="请选择状态" style="width: 100%">
                <el-option label="活跃" value="活跃"></el-option>
                <el-option label="潜在" value="潜在"></el-option>
                <el-option label="流失" value="流失"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="dialogVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="handleSubmit">ç¡® å®š</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- å®¢æˆ·åˆ†é…å¯¹è¯æ¡† -->
    <el-dialog v-model="allocationDialogVisible" title="客户分配" width="500px">
      <el-form label-width="100px">
        <el-form-item label="客户名称">
          <span>{{ currentCustomer.name }}</span>
        </el-form-item>
        <el-form-item label="当前业务员">
          <span>{{ currentCustomer.salesperson }}</span>
        </el-form-item>
        <el-form-item label="重新分配">
          <el-select v-model="newSalesperson" placeholder="请选择新业务员" style="width: 100%">
            <el-option label="张三" value="张三"></el-option>
            <el-option label="李四" value="李四"></el-option>
            <el-option label="王五" value="王五"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="分配原因">
          <el-input v-model="allocationReason" type="textarea" rows="3" placeholder="请输入分配原因"></el-input>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="allocationDialogVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="saveAllocation">ç¡® å®š</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Search } from '@element-plus/icons-vue'
import Pagination from '@/components/PIMTable/Pagination.vue'
// å“åº”式数据
const loading = ref(false)
const searchForm = reactive({
  name: '',
  region: '',
  level: ''
})
const customerList = ref([
  {
    id: 1,
    name: '上海科技有限公司',
    contactPerson: '张经理',
    phone: '021-12345678',
    email: 'zhang@shanghai-tech.com',
    region: '华东区',
    level: 'VIP客户',
    salesperson: '张三',
    status: '活跃'
  },
  {
    id: 2,
    name: '深圳电子有限公司',
    contactPerson: '李总',
    phone: '0755-87654321',
    email: 'li@shenzhen-elec.com',
    region: '华南区',
    level: '重要客户',
    salesperson: '李四',
    status: '活跃'
  },
  {
    id: 3,
    name: '北京贸易公司',
    contactPerson: '王经理',
    phone: '010-11223344',
    email: 'wang@beijing-trade.com',
    region: '华北区',
    level: '普通客户',
    salesperson: '王五',
    status: '潜在'
  }
])
const pagination = reactive({
  total: 3,
  currentPage: 1,
  pageSize: 10
})
const dialogVisible = ref(false)
const dialogTitle = ref('新增客户')
const form = reactive({
  name: '',
  contactPerson: '',
  phone: '',
  email: '',
  region: '',
  level: '',
  salesperson: '',
  status: '活跃'
})
const rules = {
  name: [{ required: true, message: '请输入客户名称', trigger: 'blur' }],
  contactPerson: [{ required: true, message: '请输入联系人', trigger: 'blur' }],
  phone: [{ required: true, message: '请输入联系电话', trigger: 'blur' }],
  email: [{ required: true, message: '请输入邮箱', trigger: 'blur' }],
  region: [{ required: true, message: '请选择区域', trigger: 'change' }],
  level: [{ required: true, message: '请选择客户等级', trigger: 'change' }],
  salesperson: [{ required: true, message: '请选择业务员', trigger: 'change' }],
  status: [{ required: true, message: '请选择状态', trigger: 'change' }]
}
const isEdit = ref(false)
const editId = ref(null)
const allocationDialogVisible = ref(false)
const currentCustomer = ref({})
const newSalesperson = ref('')
const allocationReason = ref('')
const formRef = ref()
// è®¡ç®—属性
const filteredList = computed(() => {
  let list = customerList.value
  if (searchForm.name) {
    list = list.filter(item => item.name.includes(searchForm.name))
  }
  if (searchForm.region) {
    list = list.filter(item => item.region === searchForm.region)
  }
  if (searchForm.level) {
    list = list.filter(item => item.level === searchForm.level)
  }
  return list
})
// æ–¹æ³•
const getLevelType = (level) => {
  const levelMap = {
    'VIP客户': 'danger',
    '重要客户': 'warning',
    '普通客户': 'info'
  }
  return levelMap[level] || 'info'
}
const getStatusType = (status) => {
  const statusMap = {
    '活跃': 'success',
    '潜在': 'warning',
    '流失': 'danger'
  }
  return statusMap[status] || 'info'
}
const handleSearch = () => {
  // æœç´¢é€»è¾‘已在computed中处理
}
const resetSearch = () => {
  searchForm.name = ''
  searchForm.region = ''
  searchForm.level = ''
}
const handleAdd = () => {
  dialogTitle.value = '新增客户'
  isEdit.value = false
  form.name = ''
  form.contactPerson = ''
  form.phone = ''
  form.email = ''
  form.region = ''
  form.level = ''
  form.salesperson = ''
  form.status = '活跃'
  dialogVisible.value = true
}
const handleEdit = (row) => {
  dialogTitle.value = '编辑客户'
  isEdit.value = true
  editId.value = row.id
  Object.assign(form, row)
  dialogVisible.value = true
}
const handleDelete = (row) => {
  ElMessageBox.confirm('确认删除该客户吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    const index = customerList.value.findIndex(item => item.id === row.id)
    if (index > -1) {
      customerList.value.splice(index, 1)
      pagination.total--
      ElMessage.success('删除成功')
    }
  })
}
const handleAllocation = (row) => {
  currentCustomer.value = row
  newSalesperson.value = ''
  allocationReason.value = ''
  allocationDialogVisible.value = true
}
const saveAllocation = () => {
  if (!newSalesperson.value) {
    ElMessage.warning('请选择新业务员')
    return
  }
  const index = customerList.value.findIndex(item => item.id === currentCustomer.value.id)
  if (index > -1) {
    customerList.value[index].salesperson = newSalesperson.value
    ElMessage.success('客户分配成功')
    allocationDialogVisible.value = false
  }
}
const handleSubmit = () => {
  formRef.value.validate((valid) => {
    if (valid) {
      if (isEdit.value) {
        // ç¼–辑
        const index = customerList.value.findIndex(item => item.id === editId.value)
        if (index > -1) {
          customerList.value[index] = { ...form, id: editId.value }
          ElMessage.success('编辑成功')
        }
      } else {
        // æ–°å¢ž
        const newId = Math.max(...customerList.value.map(item => item.id)) + 1
        customerList.value.push({
          ...form,
          id: newId
        })
        pagination.total++
        ElMessage.success('新增成功')
      }
      dialogVisible.value = false
    }
  })
}
const handleCurrentChange = (val) => {
  pagination.currentPage = val.page
  pagination.pageSize = val.limit
}
</script>
<style scoped>
.search-row {
  margin-bottom: 20px;
}
</style>
src/views/salesManagement/orderManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,495 @@
<template>
  <div class="app-container">
    <el-card class="box-card">
      <!-- æœç´¢åŒºåŸŸ -->
      <el-row :gutter="20" class="search-row">
        <el-col :span="6">
          <el-input
            v-model="searchForm.orderNo"
            placeholder="请输入订单号"
            clearable
            @keyup.enter="handleSearch"
          >
            <template #prefix>
              <el-icon><Search /></el-icon>
            </template>
          </el-input>
        </el-col>
        <el-col :span="6">
          <el-select v-model="searchForm.customer" placeholder="请选择客户" clearable>
            <el-option label="上海科技有限公司" value="上海科技有限公司"></el-option>
            <el-option label="深圳电子有限公司" value="深圳电子有限公司"></el-option>
            <el-option label="北京贸易公司" value="北京贸易公司"></el-option>
          </el-select>
        </el-col>
        <el-col :span="6">
          <el-select v-model="searchForm.status" placeholder="请选择订单状态" clearable>
            <el-option label="待审核" value="待审核"></el-option>
            <el-option label="已审核" value="已审核"></el-option>
            <el-option label="已发货" value="已发货"></el-option>
            <el-option label="已完成" value="已完成"></el-option>
            <el-option label="已取消" value="已取消"></el-option>
          </el-select>
        </el-col>
        <el-col :span="6">
          <el-button type="primary" @click="handleSearch">搜索</el-button>
          <el-button @click="resetSearch">重置</el-button>
          <el-button style="float: right;" type="primary" @click="handleAdd">
            æ–°å¢žè®¢å•
          </el-button>
        </el-col>
      </el-row>
      <!-- è®¢å•列表 -->
      <el-table
        :data="filteredList"
        style="width: 100%"
        v-loading="loading"
        border
        stripe
        height="calc(100vh - 22em)"
      >
        <el-table-column prop="id" label="ID" width="80" align="center"/>
        <el-table-column prop="orderNo" label="订单号" width="150" />
        <el-table-column prop="customer" label="客户名称" />
        <el-table-column prop="salesperson" label="业务员" width="100" />
        <el-table-column prop="orderDate" label="下单日期" width="120" />
        <el-table-column prop="amount" label="订单金额" width="120">
          <template #default="scope">
            Â¥{{ scope.row.amount.toFixed(2) }}
          </template>
        </el-table-column>
        <el-table-column prop="paymentMethod" label="付款方式" width="120" />
        <el-table-column prop="status" label="订单状态" width="100">
          <template #default="scope">
            <el-tag :type="getStatusType(scope.row.status)">
              {{ scope.row.status }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="操作" width="250" fixed="right" align="center">
          <template #default="scope">
            <el-button link type="primary" @click="handleView(scope.row)">查看</el-button>
            <el-button link type="primary" @click="handleEdit(scope.row)" v-if="scope.row.status === '待审核'">编辑</el-button>
            <el-button link type="primary" @click="handleReview(scope.row)" v-if="scope.row.status === '待审核'">审核</el-button>
            <el-button link type="primary" @click="handleTransfer(scope.row)" v-if="scope.row.status === '已审核'">转单</el-button>
            <el-button link type="danger" @click="handleCancel(scope.row)" v-if="scope.row.status === '待审核'">取消</el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- åˆ†é¡µ -->
      <pagination
        :total="pagination.total"
        layout="total, sizes, prev, pager, next, jumper"
        :page="pagination.currentPage"
        :limit="pagination.pageSize"
        @pagination="handleCurrentChange"
      />
    </el-card>
    <!-- æ–°å¢ž/编辑对话框 -->
    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="700px">
      <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="客户名称" prop="customer">
              <el-select v-model="form.customer" placeholder="请选择客户" style="width: 100%">
                <el-option label="上海科技有限公司" value="上海科技有限公司"></el-option>
                <el-option label="深圳电子有限公司" value="深圳电子有限公司"></el-option>
                <el-option label="北京贸易公司" value="北京贸易公司"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="业务员" prop="salesperson">
              <el-select v-model="form.salesperson" placeholder="请选择业务员" style="width: 100%">
                <el-option label="张三" value="张三"></el-option>
                <el-option label="李四" value="李四"></el-option>
                <el-option label="王五" value="王五"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="订单日期" prop="orderDate">
              <el-date-picker
                v-model="form.orderDate"
                type="date"
                placeholder="选择订单日期"
                style="width: 100%"
                format="YYYY-MM-DD"
                value-format="YYYY-MM-DD"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="订单金额" prop="amount">
              <el-input-number v-model="form.amount" :precision="2" :min="0" style="width: 100%"></el-input-number>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="付款方式" prop="paymentMethod">
              <el-select v-model="form.paymentMethod" placeholder="请选择付款方式" style="width: 100%">
                <el-option label="全款到付" value="全款到付"></el-option>
                <el-option label="分期付款" value="分期付款"></el-option>
                <el-option label="月结" value="月结"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="订单状态" prop="status">
              <el-select v-model="form.status" placeholder="请选择状态" style="width: 100%">
                <el-option label="待审核" value="待审核"></el-option>
                <el-option label="已审核" value="已审核"></el-option>
                <el-option label="已发货" value="已发货"></el-option>
                <el-option label="已完成" value="已完成"></el-option>
                <el-option label="已取消" value="已取消"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="备注" prop="remark">
              <el-input type="textarea" v-model="form.remark" placeholder="请输入备注信息" rows="3"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="dialogVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="handleSubmit">ç¡® å®š</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- è®¢å•审核对话框 -->
    <el-dialog v-model="reviewDialogVisible" title="订单审核" width="500px">
      <el-form label-width="100px">
        <el-form-item label="订单号">
          <span>{{ currentOrder.orderNo }}</span>
        </el-form-item>
        <el-form-item label="客户名称">
          <span>{{ currentOrder.customer }}</span>
        </el-form-item>
        <el-form-item label="订单金额">
          <span>Â¥{{ currentOrder.amount.toFixed(2) }}</span>
        </el-form-item>
        <el-form-item label="审核结果" prop="reviewResult">
          <el-radio-group v-model="reviewResult">
            <el-radio label="通过">通过</el-radio>
            <el-radio label="拒绝">拒绝</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="审核意见" prop="reviewComment">
          <el-input type="textarea" v-model="reviewComment" rows="3" placeholder="请输入审核意见"></el-input>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="reviewDialogVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="saveReview">ç¡® å®š</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- è®¢å•转单对话框 -->
    <el-dialog v-model="transferDialogVisible" title="订单转单" width="500px">
      <el-form label-width="100px">
        <el-form-item label="订单号">
          <span>{{ currentOrder.orderNo }}</span>
        </el-form-item>
        <el-form-item label="当前业务员">
          <span>{{ currentOrder.salesperson }}</span>
        </el-form-item>
        <el-form-item label="转单给" prop="newSalesperson">
          <el-select v-model="newSalesperson" placeholder="请选择新业务员" style="width: 100%">
            <el-option label="张三" value="张三"></el-option>
            <el-option label="李四" value="李四"></el-option>
            <el-option label="王五" value="王五"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="转单原因" prop="transferReason">
          <el-input type="textarea" v-model="transferReason" rows="3" placeholder="请输入转单原因"></el-input>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="transferDialogVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="saveTransfer">ç¡® å®š</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Search } from '@element-plus/icons-vue'
import Pagination from '@/components/PIMTable/Pagination.vue'
// å“åº”式数据
const loading = ref(false)
const searchForm = reactive({
  orderNo: '',
  customer: '',
  status: ''
})
const orderList = ref([
  {
    id: 1,
    orderNo: 'ORD202312001',
    customer: '上海科技有限公司',
    salesperson: '张三',
    orderDate: '2023-12-01',
    amount: 50000.00,
    paymentMethod: '全款到付',
    status: '待审核',
    remark: '重要客户订单'
  },
  {
    id: 2,
    orderNo: 'ORD202312002',
    customer: '深圳电子有限公司',
    salesperson: '李四',
    orderDate: '2023-12-02',
    amount: 35000.00,
    paymentMethod: '分期付款',
    status: '已审核',
    remark: '常规订单'
  },
  {
    id: 3,
    orderNo: 'ORD202312003',
    customer: '北京贸易公司',
    salesperson: '王五',
    orderDate: '2023-12-03',
    amount: 28000.00,
    paymentMethod: '月结',
    status: '已发货',
    remark: '新客户订单'
  }
])
const pagination = reactive({
  total: 3,
  currentPage: 1,
  pageSize: 10
})
const dialogVisible = ref(false)
const dialogTitle = ref('新增订单')
const form = reactive({
  customer: '',
  salesperson: '',
  orderDate: '',
  amount: 0,
  paymentMethod: '',
  status: '待审核',
  remark: ''
})
const rules = {
  customer: [{ required: true, message: '请选择客户', trigger: 'change' }],
  salesperson: [{ required: true, message: '请选择业务员', trigger: 'change' }],
  orderDate: [{ required: true, message: '请选择订单日期', trigger: 'change' }],
  amount: [{ required: true, message: '请输入订单金额', trigger: 'blur' }],
  paymentMethod: [{ required: true, message: '请选择付款方式', trigger: 'change' }],
  status: [{ required: true, message: '请选择状态', trigger: 'change' }]
}
const isEdit = ref(false)
const editId = ref(null)
const reviewDialogVisible = ref(false)
const transferDialogVisible = ref(false)
const currentOrder = ref({})
const reviewResult = ref('')
const reviewComment = ref('')
const newSalesperson = ref('')
const transferReason = ref('')
const formRef = ref()
// è®¡ç®—属性
const filteredList = computed(() => {
  let list = orderList.value
  if (searchForm.orderNo) {
    list = list.filter(item => item.orderNo.includes(searchForm.orderNo))
  }
  if (searchForm.customer) {
    list = list.filter(item => item.customer === searchForm.customer)
  }
  if (searchForm.status) {
    list = list.filter(item => item.status === searchForm.status)
  }
  return list
})
// æ–¹æ³•
const getStatusType = (status) => {
  const statusMap = {
    '待审核': 'warning',
    '已审核': 'primary',
    '已发货': 'success',
    '已完成': 'success',
    '已取消': 'danger'
  }
  return statusMap[status] || 'info'
}
const handleSearch = () => {
  // æœç´¢é€»è¾‘已在computed中处理
}
const resetSearch = () => {
  searchForm.orderNo = ''
  searchForm.customer = ''
  searchForm.status = ''
}
const handleAdd = () => {
  dialogTitle.value = '新增订单'
  isEdit.value = false
  form.customer = ''
  form.salesperson = ''
  form.orderDate = ''
  form.amount = 0
  form.paymentMethod = ''
  form.status = '待审核'
  form.remark = ''
  dialogVisible.value = true
}
const handleView = (row) => {
  // æŸ¥çœ‹è®¢å•详情
  ElMessage.info('查看订单详情功能待实现')
}
const handleEdit = (row) => {
  dialogTitle.value = '编辑订单'
  isEdit.value = true
  editId.value = row.id
  Object.assign(form, row)
  dialogVisible.value = true
}
const handleReview = (row) => {
  currentOrder.value = row
  reviewResult.value = ''
  reviewComment.value = ''
  reviewDialogVisible.value = true
}
const handleTransfer = (row) => {
  currentOrder.value = row
  newSalesperson.value = ''
  transferReason.value = ''
  transferDialogVisible.value = true
}
const handleCancel = (row) => {
  ElMessageBox.confirm('确认取消该订单吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    const index = orderList.value.findIndex(item => item.id === row.id)
    if (index > -1) {
      orderList.value[index].status = '已取消'
      ElMessage.success('订单已取消')
    }
  })
}
const handleDelete = (row) => {
  ElMessageBox.confirm('确认删除该订单吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    const index = orderList.value.findIndex(item => item.id === row.id)
    if (index > -1) {
      orderList.value.splice(index, 1)
      pagination.total--
      ElMessage.success('删除成功')
    }
  })
}
const saveReview = () => {
  if (!reviewResult.value) {
    ElMessage.warning('请选择审核结果')
    return
  }
  const index = orderList.value.findIndex(item => item.id === currentOrder.value.id)
  if (index > -1) {
    if (reviewResult.value === '通过') {
      orderList.value[index].status = '已审核'
      ElMessage.success('订单审核通过')
    } else {
      orderList.value[index].status = '已取消'
      ElMessage.success('订单审核拒绝')
    }
    reviewDialogVisible.value = false
  }
}
const saveTransfer = () => {
  if (!newSalesperson.value) {
    ElMessage.warning('请选择新业务员')
    return
  }
  const index = orderList.value.findIndex(item => item.id === currentOrder.value.id)
  if (index > -1) {
    orderList.value[index].salesperson = newSalesperson.value
    ElMessage.success('订单转单成功')
    transferDialogVisible.value = false
  }
}
const handleSubmit = () => {
  formRef.value.validate((valid) => {
    if (valid) {
      if (isEdit.value) {
        // ç¼–辑
        const index = orderList.value.findIndex(item => item.id === editId.value)
        if (index > -1) {
          orderList.value[index] = { ...form, id: editId.value }
          ElMessage.success('编辑成功')
        }
      } else {
        // æ–°å¢ž
        const newId = Math.max(...orderList.value.map(item => item.id)) + 1
        const orderNo = `ORD${new Date().getFullYear()}${String(new Date().getMonth() + 1).padStart(2, '0')}${String(new Date().getDate()).padStart(2, '0')}${String(newId).padStart(3, '0')}`
        orderList.value.push({
          ...form,
          id: newId,
          orderNo: orderNo
        })
        pagination.total++
        ElMessage.success('新增成功')
      }
      dialogVisible.value = false
    }
  })
}
const handleCurrentChange = (val) => {
  pagination.currentPage = val.page
  pagination.pageSize = val.limit
}
</script>
<style scoped>
.search-row {
  margin-bottom: 20px;
}
</style>
src/views/salesManagement/paymentShipping/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,534 @@
<template>
  <div class="app-container">
    <el-card class="box-card">
      <!-- æœç´¢åŒºåŸŸ -->
      <el-row :gutter="20" class="search-row">
        <el-col :span="6">
          <el-input
            v-model="searchForm.orderNo"
            placeholder="请输入订单号"
            clearable
            @keyup.enter="handleSearch"
          >
            <template #prefix>
              <el-icon><Search /></el-icon>
            </template>
          </el-input>
        </el-col>
        <el-col :span="6">
          <el-select v-model="searchForm.paymentStatus" placeholder="请选择付款状态" clearable>
            <el-option label="未付款" value="未付款"></el-option>
            <el-option label="已付款" value="已付款"></el-option>
            <el-option label="部分付款" value="部分付款"></el-option>
          </el-select>
        </el-col>
        <el-col :span="6">
          <el-select v-model="searchForm.shippingStatus" placeholder="请选择发货状态" clearable>
            <el-option label="待发货" value="待发货"></el-option>
            <el-option label="已发货" value="已发货"></el-option>
            <el-option label="已签收" value="已签收"></el-option>
          </el-select>
        </el-col>
        <el-col :span="6">
          <el-button type="primary" @click="handleSearch">搜索</el-button>
          <el-button @click="resetSearch">重置</el-button>
          <el-button style="float: right;" type="primary" @click="handleAdd">
            æ–°å¢žè®°å½•
          </el-button>
        </el-col>
      </el-row>
      <!-- æ”¯ä»˜ä¸Žå‘货列表 -->
      <el-table
        :data="filteredList"
        style="width: 100%"
        v-loading="loading"
        border
        stripe
        height="calc(100vh - 22em)"
      >
        <el-table-column prop="id" label="ID" width="80" align="center"/>
        <el-table-column prop="orderNo" label="订单号" />
        <el-table-column prop="customer" label="客户名称" />
        <el-table-column prop="orderAmount" label="订单金额" width="120">
          <template #default="scope">
            Â¥{{ scope.row.orderAmount.toFixed(2) }}
          </template>
        </el-table-column>
        <el-table-column prop="paymentMethod" label="付款方式" width="120" />
        <el-table-column prop="paymentStatus" label="付款状态" width="100">
          <template #default="scope">
            <el-tag :type="getPaymentStatusType(scope.row.paymentStatus)">
              {{ scope.row.paymentStatus }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="shippingStatus" label="发货状态" width="100">
          <template #default="scope">
            <el-tag :type="getShippingStatusType(scope.row.shippingStatus)">
              {{ scope.row.shippingStatus }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="shippingDate" label="发货日期" width="120" />
        <el-table-column label="操作" width="250" fixed="right" align="center">
          <template #default="scope">
            <el-button link type="primary" @click="handleView(scope.row)">查看</el-button>
            <el-button link type="primary" @click="handlePayment(scope.row)" v-if="scope.row.paymentStatus !== '已付款'">付款</el-button>
            <el-button link type="primary" @click="handleShipping(scope.row)" v-if="scope.row.paymentStatus === '已付款' && scope.row.shippingStatus === '待发货'">发货</el-button>
            <el-button link type="primary" @click="handleEdit(scope.row)">编辑</el-button>
            <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- åˆ†é¡µ -->
      <pagination
        :total="pagination.total"
        layout="total, sizes, prev, pager, next, jumper"
        :page="pagination.currentPage"
        :limit="pagination.pageSize"
        @pagination="handleCurrentChange"
      />
    </el-card>
    <!-- æ–°å¢ž/编辑对话框 -->
    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="700px">
      <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="订单号" prop="orderNo">
              <el-input v-model="form.orderNo" placeholder="请输入订单号"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="客户名称" prop="customer">
              <el-select v-model="form.customer" placeholder="请选择客户" style="width: 100%">
                <el-option label="上海科技有限公司" value="上海科技有限公司"></el-option>
                <el-option label="深圳电子有限公司" value="深圳电子有限公司"></el-option>
                <el-option label="北京贸易公司" value="北京贸易公司"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="订单金额" prop="orderAmount">
              <el-input-number v-model="form.orderAmount" :precision="2" :min="0" style="width: 100%"></el-input-number>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="付款方式" prop="paymentMethod">
              <el-select v-model="form.paymentMethod" placeholder="请选择付款方式" style="width: 100%">
                <el-option label="全款到付" value="全款到付"></el-option>
                <el-option label="分期付款" value="分期付款"></el-option>
                <el-option label="月结" value="月结"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="付款状态" prop="paymentStatus">
              <el-select v-model="form.paymentStatus" placeholder="请选择付款状态" style="width: 100%">
                <el-option label="未付款" value="未付款"></el-option>
                <el-option label="已付款" value="已付款"></el-option>
                <el-option label="部分付款" value="部分付款"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="发货状态" prop="shippingStatus">
              <el-select v-model="form.shippingStatus" placeholder="请选择发货状态" style="width: 100%">
                <el-option label="待发货" value="待发货"></el-option>
                <el-option label="已发货" value="已发货"></el-option>
                <el-option label="已签收" value="已签收"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="发货日期" prop="shippingDate">
              <el-date-picker
                v-model="form.shippingDate"
                type="date"
                placeholder="选择发货日期"
                style="width: 100%"
                format="YYYY-MM-DD"
                value-format="YYYY-MM-DD"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="物流单号" prop="trackingNo">
              <el-input v-model="form.trackingNo" placeholder="请输入物流单号"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="备注" prop="remark">
              <el-input type="textarea" v-model="form.remark" placeholder="请输入备注信息" rows="3"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="dialogVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="handleSubmit">ç¡® å®š</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- ä»˜æ¬¾å¯¹è¯æ¡† -->
    <el-dialog v-model="paymentDialogVisible" title="订单付款" width="500px">
      <el-form label-width="100px">
        <el-form-item label="订单号">
          <span>{{ currentRecord.orderNo }}</span>
        </el-form-item>
        <el-form-item label="客户名称">
          <span>{{ currentRecord.customer }}</span>
        </el-form-item>
        <el-form-item label="订单金额">
          <span>Â¥{{ currentRecord.orderAmount.toFixed(2) }}</span>
        </el-form-item>
        <el-form-item label="付款金额" prop="paymentAmount">
          <el-input-number v-model="paymentAmount" :precision="2" :min="0" :max="currentRecord.orderAmount" style="width: 100%"></el-input-number>
        </el-form-item>
        <el-form-item label="付款方式" prop="paymentMethod">
          <el-select v-model="paymentMethod" placeholder="请选择付款方式" style="width: 100%">
            <el-option label="现金" value="现金"></el-option>
            <el-option label="银行转账" value="银行转账"></el-option>
            <el-option label="支付宝" value="支付宝"></el-option>
            <el-option label="微信支付" value="微信支付"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="付款备注" prop="paymentRemark">
          <el-input type="textarea" v-model="paymentRemark" rows="3" placeholder="请输入付款备注"></el-input>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="paymentDialogVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="savePayment">ç¡® å®š</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- å‘货对话框 -->
    <el-dialog v-model="shippingDialogVisible" title="订单发货" width="500px">
      <el-form label-width="100px">
        <el-form-item label="订单号">
          <span>{{ currentRecord.orderNo }}</span>
        </el-form-item>
        <el-form-item label="客户名称">
          <span>{{ currentRecord.customer }}</span>
        </el-form-item>
        <el-form-item label="发货日期" prop="shippingDate">
          <el-date-picker
            v-model="shippingDate"
            type="date"
            placeholder="选择发货日期"
            style="width: 100%"
            format="YYYY-MM-DD"
            value-format="YYYY-MM-DD"
          />
        </el-form-item>
        <el-form-item label="物流公司" prop="logisticsCompany">
          <el-select v-model="logisticsCompany" placeholder="请选择物流公司" style="width: 100%">
            <el-option label="顺丰速运" value="顺丰速运"></el-option>
            <el-option label="圆通速递" value="圆通速递"></el-option>
            <el-option label="中通快递" value="中通快递"></el-option>
            <el-option label="申通快递" value="申通快递"></el-option>
            <el-option label="韵达速递" value="韵达速递"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="物流单号" prop="trackingNo">
          <el-input v-model="trackingNo" placeholder="请输入物流单号"></el-input>
        </el-form-item>
        <el-form-item label="发货备注" prop="shippingRemark">
          <el-input type="textarea" v-model="shippingRemark" rows="3" placeholder="请输入发货备注"></el-input>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="shippingDialogVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="saveShipping">ç¡® å®š</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Search } from '@element-plus/icons-vue'
import Pagination from '@/components/PIMTable/Pagination.vue'
// å“åº”式数据
const loading = ref(false)
const searchForm = reactive({
  orderNo: '',
  paymentStatus: '',
  shippingStatus: ''
})
const recordList = ref([
  {
    id: 1,
    orderNo: 'ORD202312001',
    customer: '上海科技有限公司',
    orderAmount: 50000.00,
    paymentMethod: '全款到付',
    paymentStatus: '已付款',
    shippingStatus: '已发货',
    shippingDate: '2023-12-05',
    trackingNo: 'SF1234567890',
    remark: '重要客户订单'
  },
  {
    id: 2,
    orderNo: 'ORD202312002',
    customer: '深圳电子有限公司',
    orderAmount: 35000.00,
    paymentMethod: '分期付款',
    paymentStatus: '部分付款',
    shippingStatus: '待发货',
    shippingDate: '',
    trackingNo: '',
    remark: '常规订单'
  },
  {
    id: 3,
    orderNo: 'ORD202312003',
    customer: '北京贸易公司',
    orderAmount: 28000.00,
    paymentMethod: '月结',
    paymentStatus: '未付款',
    shippingStatus: '待发货',
    shippingDate: '',
    trackingNo: '',
    remark: '新客户订单'
  }
])
const pagination = reactive({
  total: 3,
  currentPage: 1,
  pageSize: 10
})
const dialogVisible = ref(false)
const dialogTitle = ref('新增记录')
const form = reactive({
  orderNo: '',
  customer: '',
  orderAmount: 0,
  paymentMethod: '',
  paymentStatus: '未付款',
  shippingStatus: '待发货',
  shippingDate: '',
  trackingNo: '',
  remark: ''
})
const rules = {
  orderNo: [{ required: true, message: '请输入订单号', trigger: 'blur' }],
  customer: [{ required: true, message: '请选择客户', trigger: 'change' }],
  orderAmount: [{ required: true, message: '请输入订单金额', trigger: 'blur' }],
  paymentMethod: [{ required: true, message: '请选择付款方式', trigger: 'change' }],
  paymentStatus: [{ required: true, message: '请选择付款状态', trigger: 'change' }],
  shippingStatus: [{ required: true, message: '请选择发货状态', trigger: 'change' }]
}
const isEdit = ref(false)
const editId = ref(null)
const paymentDialogVisible = ref(false)
const shippingDialogVisible = ref(false)
const currentRecord = ref({})
const paymentAmount = ref(0)
const paymentMethod = ref('')
const paymentRemark = ref('')
const shippingDate = ref('')
const logisticsCompany = ref('')
const trackingNo = ref('')
const shippingRemark = ref('')
const formRef = ref()
// è®¡ç®—属性
const filteredList = computed(() => {
  let list = recordList.value
  if (searchForm.orderNo) {
    list = list.filter(item => item.orderNo.includes(searchForm.orderNo))
  }
  if (searchForm.paymentStatus) {
    list = list.filter(item => item.paymentStatus === searchForm.paymentStatus)
  }
  if (searchForm.shippingStatus) {
    list = list.filter(item => item.shippingStatus === searchForm.shippingStatus)
  }
  return list
})
// æ–¹æ³•
const getPaymentStatusType = (status) => {
  const statusMap = {
    '未付款': 'danger',
    '已付款': 'success',
    '部分付款': 'warning'
  }
  return statusMap[status] || 'info'
}
const getShippingStatusType = (status) => {
  const statusMap = {
    '待发货': 'warning',
    '已发货': 'primary',
    '已签收': 'success'
  }
  return statusMap[status] || 'info'
}
const handleSearch = () => {
  // æœç´¢é€»è¾‘已在computed中处理
}
const resetSearch = () => {
  searchForm.orderNo = ''
  searchForm.paymentStatus = ''
  searchForm.shippingStatus = ''
}
const handleAdd = () => {
  dialogTitle.value = '新增记录'
  isEdit.value = false
  form.orderNo = ''
  form.customer = ''
  form.orderAmount = 0
  form.paymentMethod = ''
  form.paymentStatus = '未付款'
  form.shippingStatus = '待发货'
  form.shippingDate = ''
  form.trackingNo = ''
  form.remark = ''
  dialogVisible.value = true
}
const handleView = (row) => {
  // æŸ¥çœ‹è®°å½•详情
  ElMessage.info('查看记录详情功能待实现')
}
const handleEdit = (row) => {
  dialogTitle.value = '编辑记录'
  isEdit.value = true
  editId.value = row.id
  Object.assign(form, row)
  dialogVisible.value = true
}
const handlePayment = (row) => {
  currentRecord.value = row
  paymentAmount.value = row.orderAmount
  paymentMethod.value = ''
  paymentRemark.value = ''
  paymentDialogVisible.value = true
}
const handleShipping = (row) => {
  currentRecord.value = row
  shippingDate.value = ''
  logisticsCompany.value = ''
  trackingNo.value = ''
  shippingRemark.value = ''
  shippingDialogVisible.value = true
}
const handleDelete = (row) => {
  ElMessageBox.confirm('确认删除该记录吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    const index = recordList.value.findIndex(item => item.id === row.id)
    if (index > -1) {
      recordList.value.splice(index, 1)
      pagination.total--
      ElMessage.success('删除成功')
    }
  })
}
const savePayment = () => {
  if (!paymentMethod.value) {
    ElMessage.warning('请选择付款方式')
    return
  }
  const index = recordList.value.findIndex(item => item.id === currentRecord.value.id)
  if (index > -1) {
    if (paymentAmount.value >= currentRecord.value.orderAmount) {
      recordList.value[index].paymentStatus = '已付款'
    } else if (paymentAmount.value > 0) {
      recordList.value[index].paymentStatus = '部分付款'
    }
    ElMessage.success('付款记录已保存')
    paymentDialogVisible.value = false
  }
}
const saveShipping = () => {
  if (!shippingDate.value || !logisticsCompany.value || !trackingNo.value) {
    ElMessage.warning('请填写完整的发货信息')
    return
  }
  const index = recordList.value.findIndex(item => item.id === currentRecord.value.id)
  if (index > -1) {
    recordList.value[index].shippingStatus = '已发货'
    recordList.value[index].shippingDate = shippingDate.value
    recordList.value[index].trackingNo = trackingNo.value
    ElMessage.success('发货信息已保存')
    shippingDialogVisible.value = false
  }
}
const handleSubmit = () => {
  formRef.value.validate((valid) => {
    if (valid) {
      if (isEdit.value) {
        // ç¼–辑
        const index = recordList.value.findIndex(item => item.id === editId.value)
        if (index > -1) {
          recordList.value[index] = { ...form, id: editId.value }
          ElMessage.success('编辑成功')
        }
      } else {
        // æ–°å¢ž
        const newId = Math.max(...recordList.value.map(item => item.id)) + 1
        recordList.value.push({
          ...form,
          id: newId
        })
        pagination.total++
        ElMessage.success('新增成功')
      }
      dialogVisible.value = false
    }
  })
}
const handleCurrentChange = (val) => {
  pagination.currentPage = val.page
  pagination.pageSize = val.limit
}
</script>
<style scoped>
.search-row {
  margin-bottom: 20px;
}
</style>
src/views/salesManagement/salespersonManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,392 @@
<template>
  <div class="app-container">
    <el-card class="box-card">
      <!-- æœç´¢åŒºåŸŸ -->
      <el-row :gutter="20" class="search-row">
        <el-col :span="6">
          <el-input
            v-model="searchForm.name"
            placeholder="请输入业务员姓名"
            clearable
            @keyup.enter="handleSearch"
          >
            <template #prefix>
              <el-icon><Search /></el-icon>
            </template>
          </el-input>
        </el-col>
        <el-col :span="6">
          <el-select v-model="searchForm.department" placeholder="请选择部门" clearable>
            <el-option label="销售部" value="销售部"></el-option>
            <el-option label="市场部" value="市场部"></el-option>
            <el-option label="客服部" value="客服部"></el-option>
          </el-select>
        </el-col>
        <el-col :span="6">
          <el-select v-model="searchForm.status" placeholder="请选择状态" clearable>
            <el-option label="在职" value="在职"></el-option>
            <el-option label="离职" value="离职"></el-option>
            <el-option label="试用期" value="试用期"></el-option>
          </el-select>
        </el-col>
        <el-col :span="6">
          <el-button type="primary" @click="handleSearch">搜索</el-button>
          <el-button @click="resetSearch">重置</el-button>
          <el-button type="primary" style="float: right;" @click="handleAdd">新增业务员</el-button>
        </el-col>
      </el-row>
      <!-- ä¸šåŠ¡å‘˜åˆ—è¡¨ -->
      <el-table
        :data="filteredList"
        style="width: 100%"
        v-loading="loading"
        border
        stripe
        height="calc(100vh - 22em)"
      >
        <el-table-column prop="id" label="ID" width="80" align="center"/>
        <el-table-column prop="name" label="姓名" width="120" />
        <el-table-column prop="phone" label="联系电话" width="140" />
        <el-table-column prop="email" label="邮箱" width="200" />
        <el-table-column prop="department" label="部门" width="100" />
        <el-table-column prop="position" label="职位" width="100" />
        <el-table-column prop="hireDate" label="入职日期" width="120" />
        <el-table-column prop="status" label="状态" width="80">
          <template #default="scope">
            <el-tag :type="getStatusType(scope.row.status)">
              {{ scope.row.status }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="permissions" label="权限">
          <template #default="scope">
            <el-tag v-for="perm in scope.row.permissions" :key="perm" size="small" style="margin-right: 5px;">
              {{ perm }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="操作" width="200" fixed="right" align="center">
          <template #default="scope">
            <el-button link type="primary" @click="handleEdit(scope.row)">编辑</el-button>
            <el-button link type="primary" @click="handlePermissions(scope.row)">权限</el-button>
            <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- åˆ†é¡µ -->
      <pagination
        :total="pagination.total"
        layout="total, sizes, prev, pager, next, jumper"
        :page="pagination.currentPage"
        :limit="pagination.pageSize"
        @pagination="handleCurrentChange"
      />
    </el-card>
    <!-- æ–°å¢ž/编辑对话框 -->
    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px">
      <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="姓名" prop="name">
              <el-input v-model="form.name" placeholder="请输入姓名"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="联系电话" prop="phone">
              <el-input v-model="form.phone" placeholder="请输入联系电话"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="邮箱" prop="email">
              <el-input v-model="form.email" placeholder="请输入邮箱"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="部门" prop="department">
              <el-select v-model="form.department" placeholder="请选择部门" style="width: 100%">
                <el-option label="销售部" value="销售部"></el-option>
                <el-option label="市场部" value="市场部"></el-option>
                <el-option label="客服部" value="客服部"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="职位" prop="position">
              <el-input v-model="form.position" placeholder="请输入职位"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="入职日期" prop="hireDate">
              <el-date-picker
                v-model="form.hireDate"
                type="date"
                placeholder="选择入职日期"
                style="width: 100%"
                format="YYYY-MM-DD"
                value-format="YYYY-MM-DD"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="状态" prop="status">
              <el-select v-model="form.status" placeholder="请选择状态" style="width: 100%">
                <el-option label="在职" value="在职"></el-option>
                <el-option label="离职" value="离职"></el-option>
                <el-option label="试用期" value="试用期"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="dialogVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="handleSubmit">ç¡® å®š</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- æƒé™è®¾ç½®å¯¹è¯æ¡† -->
    <el-dialog v-model="permissionDialogVisible" title="权限设置" width="500px">
      <el-form label-width="100px">
        <el-form-item label="业务员姓名">
          <span>{{ currentSalesperson.name }}</span>
        </el-form-item>
        <el-form-item label="权限设置">
          <el-checkbox-group v-model="currentPermissions">
            <el-checkbox label="订单管理">订单管理</el-checkbox>
            <el-checkbox label="客户管理">客户管理</el-checkbox>
            <el-checkbox label="财务管理">财务管理</el-checkbox>
            <el-checkbox label="发货管理">发货管理</el-checkbox>
            <el-checkbox label="报表查看">报表查看</el-checkbox>
            <el-checkbox label="系统设置">系统设置</el-checkbox>
          </el-checkbox-group>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="permissionDialogVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="savePermissions">ç¡® å®š</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, computed, nextTick } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Search } from '@element-plus/icons-vue'
import Pagination from '@/components/PIMTable/Pagination.vue'
// å“åº”式数据
const loading = ref(false)
const searchForm = reactive({
  name: '',
  department: '',
  status: ''
})
const salespersonList = ref([
  {
    id: 1,
    name: '张三',
    phone: '13800138001',
    email: 'zhangsan@company.com',
    department: '销售部',
    position: '销售经理',
    hireDate: '2023-01-15',
    status: '在职',
    permissions: ['订单管理', '客户管理', '财务管理']
  },
  {
    id: 2,
    name: '李四',
    phone: '13800138002',
    email: 'lisi@company.com',
    department: '市场部',
    position: '市场专员',
    hireDate: '2023-03-20',
    status: '在职',
    permissions: ['客户管理', '报表查看']
  },
  {
    id: 3,
    name: '王五',
    phone: '13800138003',
    email: 'wangwu@company.com',
    department: '客服部',
    position: '客服主管',
    hireDate: '2022-11-10',
    status: '在职',
    permissions: ['客户管理', '发货管理']
  }
])
const pagination = ref({
  total: 3,
  currentPage: 1,
  pageSize: 10
})
const dialogVisible = ref(false)
const dialogTitle = ref('新增业务员')
const form = reactive({
  name: '',
  phone: '',
  email: '',
  department: '',
  position: '',
  hireDate: '',
  status: '在职'
})
const rules = {
  name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
  phone: [{ required: true, message: '请输入联系电话', trigger: 'blur' }],
  email: [{ required: true, message: '请输入邮箱', trigger: 'blur' }],
  department: [{ required: true, message: '请选择部门', trigger: 'change' }],
  position: [{ required: true, message: '请输入职位', trigger: 'blur' }],
  hireDate: [{ required: true, message: '请选择入职日期', trigger: 'change' }],
  status: [{ required: true, message: '请选择状态', trigger: 'change' }]
}
const isEdit = ref(false)
const editId = ref(null)
const permissionDialogVisible = ref(false)
const currentSalesperson = ref({})
const currentPermissions = ref([])
const formRef = ref()
// è®¡ç®—属性
const filteredList = computed(() => {
  let list = salespersonList.value
  if (searchForm.name) {
    list = list.filter(item => item.name.includes(searchForm.name))
  }
  if (searchForm.department) {
    list = list.filter(item => item.department === searchForm.department)
  }
  if (searchForm.status) {
    list = list.filter(item => item.status === searchForm.status)
  }
  return list
})
// æ–¹æ³•
const getStatusType = (status) => {
  const statusMap = {
    '在职': 'success',
    '离职': 'danger',
    '试用期': 'warning'
  }
  return statusMap[status] || 'info'
}
const handleSearch = () => {
  // æœç´¢é€»è¾‘已在computed中处理
}
const resetSearch = () => {
  searchForm.name = ''
  searchForm.department = ''
  searchForm.status = ''
}
const handleAdd = () => {
  dialogTitle.value = '新增业务员'
  isEdit.value = false
  form.name = ''
  form.phone = ''
  form.email = ''
  form.department = ''
  form.position = ''
  form.hireDate = ''
  form.status = '在职'
  dialogVisible.value = true
}
const handleEdit = (row) => {
  dialogTitle.value = '编辑业务员'
  isEdit.value = true
  editId.value = row.id
  Object.assign(form, row)
  dialogVisible.value = true
}
const handleDelete = (row) => {
  ElMessageBox.confirm('确认删除该业务员吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    const index = salespersonList.value.findIndex(item => item.id === row.id)
    if (index > -1) {
      salespersonList.value.splice(index, 1)
      pagination.value.total--
      ElMessage.success('删除成功')
    }
  })
}
const handlePermissions = (row) => {
  currentSalesperson.value = row
  currentPermissions.value = [...row.permissions]
  permissionDialogVisible.value = true
}
const savePermissions = () => {
  const index = salespersonList.value.findIndex(item => item.id === currentSalesperson.value.id)
  if (index > -1) {
    salespersonList.value[index].permissions = [...currentPermissions.value]
    ElMessage.success('权限设置成功')
    permissionDialogVisible.value = false
  }
}
const handleSubmit = () => {
  formRef.value.validate((valid) => {
    if (valid) {
      if (isEdit.value) {
        // ç¼–辑
        const index = salespersonList.value.findIndex(item => item.id === editId.value)
        if (index > -1) {
          salespersonList.value[index] = { ...form, id: editId.value }
          ElMessage.success('编辑成功')
        }
      } else {
        // æ–°å¢ž
        const newId = Math.max(...salespersonList.value.map(item => item.id)) + 1
        salespersonList.value.push({
          ...form,
          id: newId,
          permissions: []
        })
        pagination.value.total++
        ElMessage.success('新增成功')
      }
      dialogVisible.value = false
    }
  })
}
const handleCurrentChange = (val) => {
  pagination.value.currentPage = val.page
  pagination.value.pageSize = val.limit
}
</script>
<style scoped>
.search-row {
  margin-bottom: 20px;
}
</style>
src/views/tool/qrCodeDemo/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,405 @@
<template>
  <div class="app-container">
    <el-card class="box-card">
      <template #header>
        <div class="card-header">
          <span>二维码生成器演示</span>
          <el-tag type="success">功能完整版</el-tag>
        </div>
      </template>
      <el-alert
        title="功能说明"
        type="info"
        :closable="false"
        show-icon
      >
        <p>本演示页面展示了二维码生成器的完整功能,包括:</p>
        <ul>
          <li>基础二维码生成</li>
          <li>防伪码生成</li>
          <li>批量生成功能</li>
          <li>图片下载功能</li>
        </ul>
      </el-alert>
      <el-divider content-position="center">快速体验</el-divider>
      <!-- å¿«é€Ÿç”Ÿæˆç¤ºä¾‹ -->
      <el-row :gutter="20">
        <el-col :span="8">
          <el-card shadow="hover" class="demo-card">
            <template #header>
              <div class="demo-header">
                <span>网址二维码</span>
                <el-button type="primary" size="small" @click="generateDemo('url')">
                  ç”Ÿæˆ
                </el-button>
              </div>
            </template>
            <div class="demo-content">
              <p>生成指向特定网址的二维码</p>
              <p class="demo-text">https://www.example.com</p>
            </div>
          </el-card>
        </el-col>
        <el-col :span="8">
          <el-card shadow="hover" class="demo-card">
            <template #header>
              <div class="demo-header">
                <span>联系方式</span>
                <el-button type="primary" size="small" @click="generateDemo('contact')">
                  ç”Ÿæˆ
                </el-button>
              </div>
            </template>
            <div class="demo-content">
              <p>生成包含联系信息的二维码</p>
              <p class="demo-text">姓名:张三<br>电话:13800138000</p>
            </div>
          </el-card>
        </el-col>
        <el-col :span="8">
          <el-card shadow="hover" class="demo-card">
            <template #header>
              <div class="demo-header">
                <span>产品信息</span>
                <el-button type="primary" size="small" @click="generateDemo('product')">
                  ç”Ÿæˆ
                </el-button>
              </div>
            </template>
            <div class="demo-content">
              <p>生成产品信息的二维码</p>
              <p class="demo-text">产品:工业传感器<br>型号:SENSOR-001</p>
            </div>
          </el-card>
        </el-col>
      </el-row>
      <el-divider content-position="center">功能入口</el-divider>
      <!-- åŠŸèƒ½å…¥å£ -->
      <el-row :gutter="20">
        <el-col :span="12">
          <el-card shadow="hover" class="feature-card">
            <div class="feature-content">
              <div class="feature-icon">
                <el-icon size="40" color="#409EFF"><QrCode /></el-icon>
              </div>
              <div class="feature-text">
                <h3>基础二维码生成</h3>
                <p>快速生成各种内容的二维码,支持自定义样式和下载</p>
                <el-button type="primary" @click="goToSimple">开始使用</el-button>
              </div>
            </div>
          </el-card>
        </el-col>
        <el-col :span="12">
          <el-card shadow="hover" class="feature-card">
            <div class="feature-content">
              <div class="feature-icon">
                <el-icon size="40" color="#E6A23C"><Shield /></el-icon>
              </div>
              <div class="feature-text">
                <h3>防伪码生成</h3>
                <p>生成具有防伪功能的二维码,支持批量生成和下载</p>
                <el-button type="warning" @click="goToAdvanced">开始使用</el-button>
              </div>
            </div>
          </el-card>
        </el-col>
      </el-row>
      <el-divider content-position="center">使用说明</el-divider>
      <!-- ä½¿ç”¨è¯´æ˜Ž -->
      <el-collapse v-model="activeNames">
        <el-collapse-item title="二维码生成步骤" name="1">
          <div class="instruction-content">
            <ol>
              <li>选择要生成的内容类型(文本、网址、联系方式等)</li>
              <li>输入具体内容</li>
              <li>选择二维码尺寸和颜色</li>
              <li>点击生成按钮</li>
              <li>预览生成的二维码</li>
              <li>下载图片到本地</li>
            </ol>
          </div>
        </el-collapse-item>
        <el-collapse-item title="防伪码特点" name="2">
          <div class="instruction-content">
            <ul>
              <li><strong>唯一性</strong>:每个防伪码都包含时间戳和随机数</li>
              <li><strong>高纠错</strong>:使用最高纠错级别,扫描成功率更高</li>
              <li><strong>批量生成</strong>:支持一次生成多个防伪码</li>
              <li><strong>格式规范</strong>:SEC_产品编码_批次号_时间戳_随机数</li>
            </ul>
          </div>
        </el-collapse-item>
        <el-collapse-item title="应用场景" name="3">
          <div class="instruction-content">
            <div class="scenario-grid">
              <div class="scenario-item">
                <el-icon color="#67C23A"><Goods /></el-icon>
                <span>产品包装</span>
              </div>
              <div class="scenario-item">
                <el-icon color="#409EFF"><Document /></el-icon>
                <span>文档验证</span>
              </div>
              <div class="scenario-item">
                <el-icon color="#E6A23C"><Tickets /></el-icon>
                <span>票据防伪</span>
              </div>
              <div class="scenario-item">
                <el-icon color="#F56C6C"><Medal /></el-icon>
                <span>证书验证</span>
              </div>
            </div>
          </div>
        </el-collapse-item>
      </el-collapse>
    </el-card>
    <!-- ç”Ÿæˆçš„二维码预览 -->
    <el-dialog v-model="previewVisible" title="生成的二维码" width="400px" center>
      <div class="preview-container">
        <img v-if="previewUrl" :src="previewUrl" alt="预览二维码" />
        <div class="preview-info">
          <p><strong>内容:</strong>{{ previewContent }}</p>
          <p><strong>类型:</strong>{{ previewType }}</p>
        </div>
        <div class="preview-actions">
          <el-button type="success" @click="downloadPreview" icon="Download">
            ä¸‹è½½å›¾ç‰‡
          </el-button>
          <el-button @click="previewVisible = false">关闭</el-button>
        </div>
      </div>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { QrCode, Shield, Goods, Document, Tickets, Medal } from '@element-plus/icons-vue'
import QRCode from 'qrcode'
defineOptions({
  name: 'QRCodeDemo'
})
const router = useRouter()
const activeNames = ref(['1'])
const previewVisible = ref(false)
const previewUrl = ref('')
const previewContent = ref('')
const previewType = ref('')
// ç”Ÿæˆæ¼”示二维码
const generateDemo = async (type) => {
  try {
    let content = ''
    let typeName = ''
    switch (type) {
      case 'url':
        content = 'https://www.example.com'
        typeName = '网址二维码'
        break
      case 'contact':
        content = 'BEGIN:VCARD\nVERSION:3.0\nFN:张三\nTEL:13800138000\nEND:VCARD'
        typeName = '联系方式二维码'
        break
      case 'product':
        content = '产品名称:工业传感器\n型号:SENSOR-001\n规格:高精度型\n用途:工业自动化'
        typeName = '产品信息二维码'
        break
    }
    const qrCodeUrl = await QRCode.toDataURL(content, {
      width: 256,
      margin: 2,
      color: {
        dark: '#000000',
        light: '#FFFFFF'
      }
    })
    previewUrl.value = qrCodeUrl
    previewContent.value = content
    previewType.value = typeName
    previewVisible.value = true
  } catch (error) {
    console.error('生成演示二维码失败:', error)
    ElMessage.error('生成失败:' + error.message)
  }
}
// ä¸‹è½½é¢„览的二维码
const downloadPreview = () => {
  if (!previewUrl.value) {
    ElMessage.warning('没有可下载的二维码')
    return
  }
  const a = document.createElement('a')
  a.href = previewUrl.value
  a.download = `${previewType.value}_${new Date().getTime()}.png`
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
  ElMessage.success('下载成功!')
}
// è·³è½¬åˆ°ç®€åŒ–版页面
const goToSimple = () => {
  router.push('/tool/qrCodeSimple')
}
// è·³è½¬åˆ°é«˜çº§ç‰ˆé¡µé¢
const goToAdvanced = () => {
  router.push('/tool/qrCodeGenerator')
}
</script>
<style scoped>
.app-container {
  padding: 20px;
}
.box-card {
  margin-bottom: 20px;
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.demo-card {
  margin-bottom: 20px;
}
.demo-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.demo-content {
  text-align: center;
}
.demo-text {
  font-size: 12px;
  color: #666;
  background: #f8f9fa;
  padding: 8px;
  border-radius: 4px;
  margin-top: 10px;
}
.feature-card {
  margin-bottom: 20px;
}
.feature-content {
  display: flex;
  align-items: center;
  gap: 20px;
}
.feature-icon {
  flex-shrink: 0;
}
.feature-text h3 {
  margin: 0 0 10px 0;
  color: #303133;
}
.feature-text p {
  margin: 0 0 15px 0;
  color: #606266;
  line-height: 1.5;
}
.instruction-content {
  padding: 10px 0;
}
.instruction-content ol,
.instruction-content ul {
  margin: 0;
  padding-left: 20px;
}
.instruction-content li {
  margin: 8px 0;
  line-height: 1.6;
}
.scenario-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 20px;
  margin-top: 15px;
}
.scenario-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 15px;
  background: #f8f9fa;
  border-radius: 8px;
  text-align: center;
}
.scenario-item span {
  font-weight: 500;
  color: #303133;
}
.preview-container {
  text-align: center;
}
.preview-container img {
  max-width: 100%;
  height: auto;
  border: 2px solid #e0e0e0;
  border-radius: 8px;
  margin-bottom: 20px;
}
.preview-info {
  text-align: left;
  background: #f8f9fa;
  padding: 15px;
  border-radius: 8px;
  margin-bottom: 20px;
}
.preview-info p {
  margin: 8px 0;
  color: #666;
}
.preview-actions {
  display: flex;
  justify-content: center;
  gap: 15px;
}
</style>
src/views/tool/qrCodeGenerator/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,433 @@
<template>
  <div class="app-container">
    <el-card class="box-card">
      <template #header>
        <div class="card-header">
          <span>二维码与防伪码生成器</span>
          <el-button type="primary" @click="showBatchDialog" icon="Plus">
            æ‰¹é‡ç”Ÿæˆ
          </el-button>
        </div>
      </template>
      <!-- é›†æˆäºŒç»´ç ç”Ÿæˆç»„ä»¶ -->
      <QRCodeGenerator ref="qrGeneratorRef" />
    </el-card>
    <!-- æ‰¹é‡ç”Ÿæˆå¯¹è¯æ¡† -->
    <el-dialog v-model="batchDialogVisible" title="批量生成设置" width="800px">
      <el-form :model="batchForm" :rules="batchRules" ref="batchFormRef" label-width="120px">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="标识类型" prop="type">
              <el-select v-model="batchForm.type" placeholder="请选择标识类型" style="width: 100%">
                <el-option label="二维码" value="qrcode"></el-option>
                <el-option label="防伪码" value="security"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="生成数量" prop="quantity">
              <el-input-number
                v-model="batchForm.quantity"
                :min="1"
                :max="1000"
                :step="10"
                style="width: 100%"
              ></el-input-number>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="前缀" prop="prefix">
              <el-input v-model="batchForm.prefix" placeholder="请输入前缀,如:PROD_"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="起始编号" prop="startNumber">
              <el-input-number v-model="batchForm.startNumber" :min="1" style="width: 100%"></el-input-number>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="尺寸" prop="size">
              <el-input-number
                v-model="batchForm.size"
                :min="100"
                :max="500"
                :step="50"
                style="width: 100%"
              ></el-input-number>
            </el-col>
          <el-col :span="12">
            <el-form-item label="边距" prop="margin">
              <el-input-number
                v-model="batchForm.margin"
                :min="0"
                :max="10"
                :step="1"
                style="width: 100%"
              ></el-input-number>
            </el-col>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="前景色" prop="foregroundColor">
              <el-color-picker v-model="batchForm.foregroundColor" style="width: 100%"></el-color-picker>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="背景色" prop="backgroundColor">
              <el-color-picker v-model="batchForm.backgroundColor" style="width: 100%"></el-color-picker>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="备注">
              <el-input
                v-model="batchForm.remark"
                type="textarea"
                :rows="3"
                placeholder="请输入备注信息"
              ></el-input>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="batchDialogVisible = false">取消</el-button>
          <el-button type="primary" @click="startBatchGeneration" :loading="generating">
            å¼€å§‹ç”Ÿæˆ
          </el-button>
        </div>
      </template>
    </el-dialog>
    <!-- æ‰¹é‡ç”Ÿæˆè¿›åº¦ -->
    <el-dialog v-model="progressDialogVisible" title="批量生成进度" width="500px" :close-on-click-modal="false">
      <div class="progress-container">
        <el-progress
          :percentage="progressPercentage"
          :status="progressStatus"
          :stroke-width="20"
        ></el-progress>
        <p class="progress-text">{{ progressText }}</p>
        <div class="progress-details">
          <p>已生成: {{ generatedCount }} / {{ totalCount }}</p>
          <p>当前内容: {{ currentContent }}</p>
        </div>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="cancelGeneration" :disabled="!canCancel">取消</el-button>
          <el-button type="primary" @click="downloadBatchResults" v-if="generationCompleted">
            ä¸‹è½½ç»“æžœ
          </el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import QRCodeGenerator from '@/components/QRCodeGenerator/index.vue'
import QRCode from 'qrcode'
import JSZip from 'jszip'
defineOptions({
  name: 'QRCodeGeneratorPage'
})
// ç»„件引用
const qrGeneratorRef = ref()
// æ‰¹é‡ç”Ÿæˆç›¸å…³
const batchDialogVisible = ref(false)
const progressDialogVisible = ref(false)
const generating = ref(false)
const generationCompleted = ref(false)
const canCancel = ref(true)
const batchForm = reactive({
  type: 'qrcode',
  quantity: 100,
  prefix: 'PROD_',
  startNumber: 1,
  size: 200,
  margin: 2,
  foregroundColor: '#000000',
  backgroundColor: '#FFFFFF',
  remark: ''
})
const batchRules = {
  type: [{ required: true, message: '请选择标识类型', trigger: 'change' }],
  quantity: [{ required: true, message: '请输入生成数量', trigger: 'blur' }],
  prefix: [{ required: true, message: '请输入前缀', trigger: 'blur' }],
  startNumber: [{ required: true, message: '请输入起始编号', trigger: 'blur' }]
}
// è¿›åº¦ç›¸å…³
const progressPercentage = ref(0)
const progressStatus = ref('')
const progressText = ref('准备中...')
const generatedCount = ref(0)
const totalCount = ref(0)
const currentContent = ref('')
// ç”Ÿæˆç»“æžœ
const batchResults = ref([])
// æ˜¾ç¤ºæ‰¹é‡ç”Ÿæˆå¯¹è¯æ¡†
const showBatchDialog = () => {
  batchDialogVisible.value = true
  // é‡ç½®è¡¨å•
  Object.assign(batchForm, {
    type: 'qrcode',
    quantity: 100,
    prefix: 'PROD_',
    startNumber: 1,
    size: 200,
    margin: 2,
    foregroundColor: '#000000',
    backgroundColor: '#FFFFFF',
    remark: ''
  })
}
// å¼€å§‹æ‰¹é‡ç”Ÿæˆ
const startBatchGeneration = async () => {
  try {
    await batchFormRef.value.validate()
    if (!batchForm.prefix.trim()) {
      ElMessage.warning('请输入前缀')
      return
    }
    batchDialogVisible.value = false
    progressDialogVisible.value = true
    generating.value = true
    generationCompleted.value = false
    canCancel.value = true
    // é‡ç½®è¿›åº¦
    progressPercentage.value = 0
    progressStatus.value = ''
    progressText.value = '开始生成...'
    generatedCount.value = 0
    totalCount.value = batchForm.quantity
    batchResults.value = []
    await generateBatchCodes()
  } catch (error) {
    console.error('批量生成失败:', error)
    ElMessage.error('批量生成失败:' + error.message)
  }
}
// ç”Ÿæˆé˜²ä¼ªç å†…容
const generateSecurityCode = (content) => {
  const timestamp = Date.now()
  const random = Math.random().toString(36).substr(2, 8)
  return `SEC_${content}_${timestamp}_${random}`
}
// æ‰¹é‡ç”Ÿæˆç 
const generateBatchCodes = async () => {
  try {
    for (let i = 0; i < batchForm.quantity; i++) {
      if (!canCancel.value) {
        progressText.value = '生成已取消'
        progressStatus.value = 'exception'
        break
      }
      const number = batchForm.startNumber + i
      const content = `${batchForm.prefix}${number.toString().padStart(6, '0')}`
      currentContent.value = content
      let codeUrl
      if (batchForm.type === 'qrcode') {
        codeUrl = await QRCode.toDataURL(content, {
          width: batchForm.size,
          margin: batchForm.margin,
          color: {
            dark: batchForm.foregroundColor,
            light: batchForm.backgroundColor
          },
          errorCorrectionLevel: 'M'
        })
      } else {
        const securityContent = generateSecurityCode(content)
        codeUrl = await QRCode.toDataURL(securityContent, {
          width: batchForm.size,
          margin: batchForm.margin,
          color: {
            dark: batchForm.foregroundColor,
            light: batchForm.backgroundColor
          },
          errorCorrectionLevel: 'H'
        })
      }
      batchResults.value.push({
        content,
        url: codeUrl,
        type: batchForm.type,
        generateTime: new Date().toLocaleString()
      })
      generatedCount.value = i + 1
      progressPercentage.value = Math.round(((i + 1) / batchForm.quantity) * 100)
      progressText.value = `正在生成第 ${i + 1} ä¸ªç ...`
      // æ·»åŠ å°å»¶è¿Ÿï¼Œè®©ç”¨æˆ·çœ‹åˆ°è¿›åº¦
      await new Promise(resolve => setTimeout(resolve, 50))
    }
    if (canCancel.value) {
      progressText.value = '生成完成!'
      progressStatus.value = 'success'
      generationCompleted.value = true
      ElMessage.success(`批量生成完成,共生成 ${batchForm.quantity} ä¸ªç `)
    }
  } catch (error) {
    console.error('批量生成失败:', error)
    progressText.value = '生成失败:' + error.message
    progressStatus.value = 'exception'
    ElMessage.error('批量生成失败:' + error.message)
  } finally {
    generating.value = false
  }
}
// å–消生成
const cancelGeneration = () => {
  canCancel.value = false
  progressText.value = '正在取消...'
  setTimeout(() => {
    progressDialogVisible.value = false
    ElMessage.info('生成已取消')
  }, 1000)
}
// ä¸‹è½½æ‰¹é‡ç”Ÿæˆç»“æžœ
const downloadBatchResults = async () => {
  if (batchResults.value.length === 0) {
    ElMessage.warning('没有可下载的结果')
    return
  }
  try {
    const zip = new JSZip()
    // åˆ›å»ºè¯´æ˜Žæ–‡ä»¶
    const readme = `批量生成说明
类型: ${batchForm.type === 'qrcode' ? '二维码' : '防伪码'}
数量: ${batchResults.value.length}
前缀: ${batchForm.prefix}
起始编号: ${batchForm.startNumber}
尺寸: ${batchForm.size}x${batchForm.size}px
生成时间: ${new Date().toLocaleString()}
备注: ${batchForm.remark || '无'}
文件列表:
${batchResults.value.map((item, index) => `${index + 1}. ${item.content}.png`).join('\n')}
`
    zip.file('README.txt', readme)
    // æ·»åŠ å›¾ç‰‡æ–‡ä»¶
    batchResults.value.forEach((result, index) => {
      const base64Data = result.url.split(',')[1]
      const byteCharacters = atob(base64Data)
      const byteNumbers = new Array(byteCharacters.length)
      for (let i = 0; i < byteCharacters.length; i++) {
        byteNumbers[i] = byteCharacters.charCodeAt(i)
      }
      const byteArray = new Uint8Array(byteNumbers)
      zip.file(`${result.content}.png`, byteArray)
    })
    const content = await zip.generateAsync({ type: 'blob' })
    const a = document.createElement('a')
    a.href = URL.createObjectURL(content)
    a.download = `批量${batchForm.type === 'qrcode' ? '二维码' : '防伪码'}_${batchForm.prefix}_${new Date().getTime()}.zip`
    document.body.appendChild(a)
    a.click()
    document.body.removeChild(a)
    URL.revokeObjectURL(a.href)
    ElMessage.success('批量下载完成!')
    progressDialogVisible.value = false
  } catch (error) {
    console.error('批量下载失败:', error)
    ElMessage.error('批量下载失败:' + error.message)
  }
}
// è¡¨å•引用
const batchFormRef = ref()
</script>
<style scoped>
.app-container {
  padding: 20px;
}
.box-card {
  margin-bottom: 20px;
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.progress-container {
  text-align: center;
  padding: 20px;
}
.progress-text {
  margin: 20px 0;
  font-size: 16px;
  font-weight: bold;
}
.progress-details {
  margin-top: 20px;
  text-align: left;
  background: #f8f9fa;
  padding: 15px;
  border-radius: 8px;
}
.progress-details p {
  margin: 8px 0;
  color: #666;
}
.dialog-footer {
  text-align: right;
}
</style>
src/views/tool/qrCodeSimple/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,526 @@
<template>
  <div class="app-container">
    <el-row :gutter="20">
      <!-- å·¦ä¾§ï¼šç”Ÿæˆè¡¨å• -->
      <el-col :span="12">
        <el-card class="box-card">
          <template #header>
            <div class="card-header">
              <span>二维码生成</span>
            </div>
          </template>
          <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
            <el-form-item label="内容" prop="content">
              <el-input
                v-model="form.content"
                type="textarea"
                :rows="4"
                placeholder="请输入要生成二维码的内容,如:网址、文本、联系方式等"
              ></el-input>
            </el-form-item>
            <el-form-item label="尺寸">
              <el-select v-model="form.size" style="width: 100%">
                <el-option label="小尺寸 (128x128)" value="128"></el-option>
                <el-option label="标准尺寸 (256x256)" value="256"></el-option>
                <el-option label="大尺寸 (512x512)" value="512"></el-option>
                <el-option label="超大尺寸 (1024x1024)" value="1024"></el-option>
              </el-select>
            </el-form-item>
            <el-form-item label="前景色">
              <el-color-picker v-model="form.foregroundColor"></el-color-picker>
            </el-form-item>
            <el-form-item label="背景色">
              <el-color-picker v-model="form.backgroundColor"></el-color-picker>
            </el-form-item>
            <el-form-item>
              <el-button type="primary" @click="generateQRCode" :loading="generating" size="large">
                ç”ŸæˆäºŒç»´ç 
              </el-button>
              <el-button @click="resetForm">重置</el-button>
            </el-form-item>
          </el-form>
        </el-card>
      </el-col>
      <!-- å³ä¾§ï¼šé¢„览和下载 -->
      <el-col :span="12">
        <el-card class="box-card">
          <template #header>
            <div class="card-header">
              <span>预览与下载</span>
              <el-button
                v-if="qrCodeUrl"
                type="success"
                @click="downloadQRCode"
                icon="Download"
                size="small"
              >
                ä¸‹è½½å›¾ç‰‡
              </el-button>
            </div>
          </template>
          <div v-if="qrCodeUrl" class="preview-container">
            <div class="qr-preview">
              <img :src="qrCodeUrl" alt="生成的二维码" />
            </div>
            <div class="qr-info">
              <p><strong>内容:</strong>{{ form.content }}</p>
              <p><strong>尺寸:</strong>{{ form.size }}x{{ form.size }}px</p>
              <p><strong>生成时间:</strong>{{ generateTime }}</p>
            </div>
          </div>
          <div v-else class="empty-preview">
            <el-empty description="请先生成二维码" :image-size="100"></el-empty>
          </div>
        </el-card>
      </el-col>
    </el-row>
    <!-- é˜²ä¼ªç ç”ŸæˆåŒºåŸŸ -->
    <el-row :gutter="20" style="margin-top: 20px;">
      <el-col :span="24">
        <el-card class="box-card">
          <template #header>
            <div class="card-header">
              <span>防伪码生成</span>
              <el-button type="warning" @click="showSecurityDialog" icon="Shield">
                ç”Ÿæˆé˜²ä¼ªç 
              </el-button>
            </div>
          </template>
          <div class="security-info">
            <p>防伪码特点:</p>
            <ul>
              <li>包含时间戳和随机数,确保唯一性</li>
              <li>使用最高纠错级别,提高扫描成功率</li>
              <li>支持批量生成和下载</li>
              <li>适用于产品防伪、文档验证等场景</li>
            </ul>
          </div>
        </el-card>
      </el-col>
    </el-row>
    <!-- é˜²ä¼ªç ç”Ÿæˆå¯¹è¯æ¡† -->
    <el-dialog v-model="securityDialogVisible" title="防伪码生成" width="600px">
      <el-form :model="securityForm" :rules="securityRules" ref="securityFormRef" label-width="100px">
        <el-form-item label="产品名称" prop="productName">
          <el-input v-model="securityForm.productName" placeholder="请输入产品名称"></el-input>
        </el-form-item>
        <el-form-item label="产品编码" prop="productCode">
          <el-input v-model="securityForm.productCode" placeholder="请输入产品编码"></el-input>
        </el-form-item>
        <el-form-item label="批次号" prop="batchNo">
          <el-input v-model="securityForm.batchNo" placeholder="请输入批次号"></el-input>
        </el-form-item>
        <el-form-item label="生成数量" prop="quantity">
          <el-input-number
            v-model="securityForm.quantity"
            :min="1"
            :max="100"
            style="width: 100%"
          ></el-input-number>
        </el-form-item>
        <el-form-item label="尺寸">
          <el-select v-model="securityForm.size" style="width: 100%">
            <el-option label="标准尺寸 (256x256)" value="256"></el-option>
            <el-option label="大尺寸 (512x512)" value="512"></el-option>
          </el-select>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="securityDialogVisible = false">取消</el-button>
          <el-button type="primary" @click="generateSecurityCodes" :loading="generatingSecurity">
            å¼€å§‹ç”Ÿæˆ
          </el-button>
        </div>
      </template>
    </el-dialog>
    <!-- é˜²ä¼ªç ç»“果展示 -->
    <el-dialog v-model="securityResultVisible" title="防伪码生成结果" width="80%" top="5vh">
      <div v-if="securityResults.length > 0" class="security-results">
        <div class="results-header">
          <p>共生成 {{ securityResults.length }} ä¸ªé˜²ä¼ªç </p>
          <el-button type="success" @click="downloadAllSecurityCodes" icon="Download">
            ä¸‹è½½å…¨éƒ¨
          </el-button>
        </div>
        <div class="results-grid">
          <div
            v-for="(result, index) in securityResults"
            :key="index"
            class="result-item"
          >
            <img :src="result.url" :alt="result.content" />
            <p class="result-content">{{ result.content }}</p>
            <el-button size="small" @click="downloadSingleSecurityCode(result)">
              ä¸‹è½½
            </el-button>
          </div>
        </div>
      </div>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { ElMessage } from 'element-plus'
import QRCode from 'qrcode'
import JSZip from 'jszip'
defineOptions({
  name: 'QRCodeSimple'
})
// äºŒç»´ç ç”Ÿæˆè¡¨å•
const form = reactive({
  content: '',
  size: '256',
  foregroundColor: '#000000',
  backgroundColor: '#FFFFFF'
})
const rules = {
  content: [{ required: true, message: '请输入内容', trigger: 'blur' }]
}
// é˜²ä¼ªç ç”Ÿæˆè¡¨å•
const securityForm = reactive({
  productName: '',
  productCode: '',
  batchNo: '',
  quantity: 10,
  size: '256'
})
const securityRules = {
  productName: [{ required: true, message: '请输入产品名称', trigger: 'blur' }],
  productCode: [{ required: true, message: '请输入产品编码', trigger: 'blur' }],
  batchNo: [{ required: true, message: '请输入批次号', trigger: 'blur' }],
  quantity: [{ required: true, message: '请输入生成数量', trigger: 'blur' }]
}
// å“åº”式数据
const formRef = ref()
const securityFormRef = ref()
const generating = ref(false)
const generatingSecurity = ref(false)
const qrCodeUrl = ref('')
const generateTime = ref('')
const securityDialogVisible = ref(false)
const securityResultVisible = ref(false)
const securityResults = ref([])
// ç”ŸæˆäºŒç»´ç 
const generateQRCode = async () => {
  try {
    await formRef.value.validate()
    if (!form.content.trim()) {
      ElMessage.warning('请输入要生成二维码的内容')
      return
    }
    generating.value = true
    qrCodeUrl.value = await QRCode.toDataURL(form.content, {
      width: parseInt(form.size),
      margin: 2,
      color: {
        dark: form.foregroundColor,
        light: form.backgroundColor
      },
      errorCorrectionLevel: 'M'
    })
    generateTime.value = new Date().toLocaleString()
    ElMessage.success('二维码生成成功!')
  } catch (error) {
    console.error('生成二维码失败:', error)
    ElMessage.error('生成二维码失败:' + error.message)
  } finally {
    generating.value = false
  }
}
// ä¸‹è½½äºŒç»´ç 
const downloadQRCode = () => {
  if (!qrCodeUrl.value) {
    ElMessage.warning('请先生成二维码')
    return
  }
  const a = document.createElement('a')
  a.href = qrCodeUrl.value
  a.download = `二维码_${new Date().getTime()}.png`
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
  ElMessage.success('下载成功!')
}
// é‡ç½®è¡¨å•
const resetForm = () => {
  formRef.value.resetFields()
  qrCodeUrl.value = ''
  generateTime.value = ''
}
// æ˜¾ç¤ºé˜²ä¼ªç å¯¹è¯æ¡†
const showSecurityDialog = () => {
  securityDialogVisible.value = true
  // é‡ç½®è¡¨å•
  Object.assign(securityForm, {
    productName: '',
    productCode: '',
    batchNo: '',
    quantity: 10,
    size: '256'
  })
}
// ç”Ÿæˆé˜²ä¼ªç 
const generateSecurityCodes = async () => {
  try {
    await securityFormRef.value.validate()
    generatingSecurity.value = true
    securityResults.value = []
    for (let i = 1; i <= securityForm.quantity; i++) {
      const timestamp = Date.now() + i // ç¡®ä¿æ¯ä¸ªç éƒ½æœ‰ä¸åŒçš„æ—¶é—´æˆ³
      const random = Math.random().toString(36).substr(2, 8)
      const content = `SEC_${securityForm.productCode}_${securityForm.batchNo}_${timestamp}_${random}`
      const codeUrl = await QRCode.toDataURL(content, {
        width: parseInt(securityForm.size),
        margin: 2,
        color: {
          dark: '#000000',
          light: '#FFFFFF'
        },
        errorCorrectionLevel: 'H' // æœ€é«˜çº é”™çº§åˆ«
      })
      securityResults.value.push({
        content,
        url: codeUrl,
        productName: securityForm.productName,
        productCode: securityForm.productCode,
        batchNo: securityForm.batchNo,
        generateTime: new Date().toLocaleString()
      })
    }
    securityDialogVisible.value = false
    securityResultVisible.value = true
    ElMessage.success(`防伪码生成完成,共生成 ${securityForm.quantity} ä¸ª`)
  } catch (error) {
    console.error('生成防伪码失败:', error)
    ElMessage.error('生成防伪码失败:' + error.message)
  } finally {
    generatingSecurity.value = false
  }
}
// ä¸‹è½½å•个防伪码
const downloadSingleSecurityCode = (result) => {
  const a = document.createElement('a')
  a.href = result.url
  a.download = `${result.content}.png`
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
  ElMessage.success('下载成功!')
}
// ä¸‹è½½æ‰€æœ‰é˜²ä¼ªç 
const downloadAllSecurityCodes = async () => {
  if (securityResults.value.length === 0) {
    ElMessage.warning('没有可下载的防伪码')
    return
  }
  try {
    const zip = new JSZip()
    // åˆ›å»ºè¯´æ˜Žæ–‡ä»¶
    const readme = `防伪码生成说明
产品名称: ${securityForm.productName}
产品编码: ${securityForm.productCode}
批次号: ${securityForm.batchNo}
数量: ${securityResults.value.length}
尺寸: ${securityForm.size}x${securityForm.size}px
生成时间: ${new Date().toLocaleString()}
文件列表:
${securityResults.value.map((item, index) => `${index + 1}. ${item.content}.png`).join('\n')}
`
    zip.file('README.txt', readme)
    // æ·»åŠ å›¾ç‰‡æ–‡ä»¶
    securityResults.value.forEach((result) => {
      const base64Data = result.url.split(',')[1]
      const byteCharacters = atob(base64Data)
      const byteNumbers = new Array(byteCharacters.length)
      for (let i = 0; i < byteCharacters.length; i++) {
        byteNumbers[i] = byteCharacters.charCodeAt(i)
      }
      const byteArray = new Uint8Array(byteNumbers)
      zip.file(`${result.content}.png`, byteArray)
    })
    const content = await zip.generateAsync({ type: 'blob' })
    const a = document.createElement('a')
    a.href = URL.createObjectURL(content)
    a.download = `防伪码_${securityForm.productCode}_${securityForm.batchNo}_${new Date().getTime()}.zip`
    document.body.appendChild(a)
    a.click()
    document.body.removeChild(a)
    URL.revokeObjectURL(a.href)
    ElMessage.success('批量下载完成!')
  } catch (error) {
    console.error('批量下载失败:', error)
    ElMessage.error('批量下载失败:' + error.message)
  }
}
</script>
<style scoped>
.app-container {
  padding: 20px;
}
.box-card {
  margin-bottom: 20px;
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.preview-container {
  text-align: center;
}
.qr-preview img {
  max-width: 100%;
  height: auto;
  border: 2px solid #e0e0e0;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.qr-info {
  margin-top: 20px;
  text-align: left;
  background: #f8f9fa;
  padding: 15px;
  border-radius: 8px;
}
.qr-info p {
  margin: 8px 0;
  color: #666;
}
.empty-preview {
  padding: 40px 0;
}
.security-info {
  padding: 20px;
  background: #f8f9fa;
  border-radius: 8px;
}
.security-info p {
  font-weight: bold;
  margin-bottom: 10px;
}
.security-info ul {
  margin: 0;
  padding-left: 20px;
}
.security-info li {
  margin: 8px 0;
  color: #666;
}
.security-results {
  padding: 20px;
}
.results-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
  padding-bottom: 15px;
  border-bottom: 1px solid #e0e0e0;
}
.results-header p {
  font-size: 16px;
  font-weight: bold;
  margin: 0;
}
.results-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 20px;
}
.result-item {
  text-align: center;
  padding: 15px;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  background: #fff;
}
.result-item img {
  width: 100px;
  height: 100px;
  margin-bottom: 10px;
}
.result-content {
  font-size: 12px;
  color: #666;
  margin: 10px 0;
  word-break: break-all;
}
.dialog-footer {
  text-align: right;
}
</style>