gaoluyang
5 天以前 465c270f98a50d31b8146a5a91a2539211f67a2e
中强恒兴销售管理,采购管理页面添加
已修改1个文件
已添加15个文件
6010 ■■■■■ 文件已修改
src/components/PIMTable/PIMTable.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/QRCodeGenerator/index.vue 541 ●●●●● 补丁 | 查看 | 原始文档 | 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/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/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/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/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>