spring
3 天以前 653120dde7588e5be464166d3c316dc80227dbb0
Merge remote-tracking branch 'refs/remotes/origin/dev_ZQHX' into dev

# Conflicts:
# .env.development
# .env.production
# .env.staging
# index.html
# package.json
# src/layout/components/Sidebar/Logo.vue
# src/main.js
# vite.config.js
已删除4个文件
已修改18个文件
已添加20个文件
8921 ■■■■■ 文件已修改
.env.development 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.production 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.staging 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
index.html 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/equipmentManagement/brand.js 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/PIMTable/PIMTable.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/QRCodeGenerator/index.vue 541 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/Logo.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/index.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/approvalProcess/index1.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/approvalProcess/index2.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/approvalProcess/index3.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/approvalProcess/index4.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/attendanceManagement/index.vue 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/knowledgeBase/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/meetingBoard/index.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/officeSupplies/index.vue 670 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/planTemplate/index.vue 750 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/sealManagement/index.vue 588 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/warningSystem/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/brand/index.vue 217 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/ledger/Form.vue 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/ledger/index.vue 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/arrivalManagement/index.vue 189 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/index.vue 378 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/priceManagement/index.vue 276 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/purchaseOrder/index.vue 188 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/qualityInspection/index.vue 285 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/returnManagement/index.vue 234 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productManagement/productIdentifier/index.vue 708 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/nonconformingManagement/index.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/visualization/qualityDashboard.vue 307 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/reportManagement.vue 733 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/reportManagement/index.vue 716 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/customerManagement/index.vue 423 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/orderManagement/index.vue 495 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/paymentShipping/index.vue 534 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salespersonManagement/index.vue 392 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.development
@@ -1,8 +1,8 @@
# é¡µé¢æ ‡é¢˜
VITE_APP_TITLE = åŸºæ™ºæ²¹äº•管理系统
VITE_APP_TITLE = ä¸­å¼ºæ’兴管理系统
# å¼€å‘环境配置
VITE_APP_ENV = 'development'
# åŸºæ™ºæ²¹äº•管理系统/开发环境
# ä¸­å¼ºæ’兴管理系统/开发环境
VITE_APP_BASE_API = '/dev-api'
.env.production
@@ -1,11 +1,11 @@
# é¡µé¢æ ‡é¢˜
VITE_APP_TITLE = MOM(制造运营管理系统)
VITE_APP_TITLE = ä¸­å¼ºæ’兴管理系统
# ç”Ÿäº§çŽ¯å¢ƒé…ç½®
VITE_APP_ENV = 'production'
# MIS系统(管理信息系统)/生产环境
# ä¸­å¼ºæ’兴管理系统/生产环境
VITE_APP_BASE_API = '/prod-api'
# æ˜¯å¦åœ¨æ‰“包时开启压缩,支持 gzip å’Œ brotli
VITE_BUILD_COMPRESS = gzip
VITE_BUILD_COMPRESS = gzip
.env.staging
@@ -1,10 +1,10 @@
# é¡µé¢æ ‡é¢˜
VITE_APP_TITLE = åŸºæ™ºæ²¹äº•管理系统
VITE_APP_TITLE = ä¸­å¼ºæ’兴管理系统
# ç”Ÿäº§çŽ¯å¢ƒé…ç½®
VITE_APP_ENV = 'staging'
# åŸºæ™ºæ²¹äº•管理系统/生产环境
# ä¸­å¼ºæ’兴管理系统/生产环境
VITE_APP_BASE_API = '/stage-api'
# æ˜¯å¦åœ¨æ‰“包时开启压缩,支持 gzip å’Œ brotli
index.html
@@ -6,8 +6,8 @@
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="renderer" content="webkit">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <link rel="icon" href="/favicon.ico">
  <title>MOM(制造运营管理系统)</title>
  <link rel="icon" href="/ZQHXico.ico">
  <title>中强恒兴管理系统</title>
  <!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
  <style>
    html,
@@ -212,4 +212,4 @@
  <script type="module" src="/src/main.js"></script>
</body>
</html>
</html>
package.json
@@ -1,7 +1,7 @@
{
  "name": "ruoyi",
  "version": "3.8.9",
  "description": "MES",
  "description": "中强恒兴管理系统",
  "author": "若依",
  "license": "MIT",
  "type": "module",
src/api/equipmentManagement/brand.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,93 @@
// è®¾å¤‡å“ç‰Œç®¡ç† - æœ¬åœ°å‡æ•°æ® API(使用 localStorage æŒä¹…化)
const STORAGE_KEY = 'EQUIPMENT_BRANDS';
function readStore() {
  try {
    const raw = localStorage.getItem(STORAGE_KEY);
    if (raw) {
      const parsed = JSON.parse(raw);
      if (Array.isArray(parsed)) return parsed;
    }
  } catch (e) {
    // ignore
  }
  // åˆå§‹åŒ–一些示例数据
  const initial = [
    { id: 1, name: '西门子', country: '德国', description: '工业自动化与电气工程品牌', createdAt: Date.now() - 86400000 * 10 },
    { id: 2, name: '施耐德', country: '法国', description: '能源管理与自动化', createdAt: Date.now() - 86400000 * 7 },
    { id: 3, name: '三菱电机', country: '日本', description: '电气与自动化设备', createdAt: Date.now() - 86400000 * 3 },
  ];
  localStorage.setItem(STORAGE_KEY, JSON.stringify(initial));
  return initial;
}
function writeStore(list) {
  localStorage.setItem(STORAGE_KEY, JSON.stringify(list));
}
function nextId(list) {
  const maxId = list.reduce((max, item) => Math.max(max, Number(item.id) || 0), 0);
  return maxId + 1;
}
export function getBrandPage(params = {}) {
  const { current = 1, size = 10, name } = params;
  const list = readStore();
  let filtered = list;
  if (name) {
    const kw = String(name).trim();
    filtered = filtered.filter((b) =>
      (b.name && b.name.includes(kw)) || (b.country && b.country.includes(kw))
    );
  }
  const start = (current - 1) * size;
  const end = start + Number(size);
  const records = filtered.slice(start, end);
  return Promise.resolve({
    code: 200,
    data: {
      total: filtered.length,
      records,
    },
    msg: 'ok',
  });
}
export function getBrandById(id) {
  const list = readStore();
  const item = list.find((i) => String(i.id) === String(id));
  return Promise.resolve({ code: 200, data: item || null, msg: 'ok' });
}
export function addBrand(data) {
  const list = readStore();
  const item = { ...data };
  item.id = nextId(list);
  item.createdAt = Date.now();
  list.unshift(item);
  writeStore(list);
  return Promise.resolve({ code: 200, data: item, msg: '新增成功' });
}
export function editBrand(data) {
  const list = readStore();
  const index = list.findIndex((i) => String(i.id) === String(data.id));
  if (index !== -1) {
    list[index] = { ...list[index], ...data };
    writeStore(list);
    return Promise.resolve({ code: 200, data: list[index], msg: '修改成功' });
  }
  return Promise.resolve({ code: 404, data: null, msg: '未找到该品牌' });
}
export function delBrand(idOrIds) {
  const list = readStore();
  const ids = Array.isArray(idOrIds) ? idOrIds.map(String) : [String(idOrIds)];
  const newList = list.filter((i) => !ids.includes(String(i.id)));
  writeStore(newList);
  return Promise.resolve({ code: 200, data: null, msg: '删除成功' });
}
src/components/PIMTable/PIMTable.vue
@@ -124,7 +124,6 @@
          <template v-for="(o, key) in item.operation" :key="key">
            <el-button
              v-show="o.type != 'upload'"
              size="small"
              v-if="o.showHide ? o.showHide(scope.row) : true"
              :disabled="o.disabled ? o.disabled(scope.row) : false"
              :plain="o.plain"
@@ -149,7 +148,6 @@
                (o.uploadIdFun ? o.uploadIdFun(scope.row) : scope.row.id)
              "
              ref="uploadRef"
              size="small"
              :multiple="o.multiple ? o.multiple : false"
              :limit="1"
              :disabled="o.disabled ? o.disabled(scope.row) : false"
@@ -178,7 +176,6 @@
              :show-file-list="false"
            >
              <el-button
                :size="o.size ? o.size : 'small'"
                link
                type="primary"
                :disabled="o.disabled ? o.disabled(scope.row) : false"
src/components/QRCodeGenerator/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,541 @@
<template>
  <div class="qr-code-generator">
    <!-- äºŒç»´ç ç”Ÿæˆè¡¨å• -->
    <el-form :model="form" :rules="rules" ref="formRef" label-width="120px" class="qr-form">
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item label="标识类型" prop="type">
            <el-select v-model="form.type" placeholder="请选择标识类型" style="width: 100%">
              <el-option label="二维码" value="qrcode"></el-option>
              <el-option label="防伪码" value="security"></el-option>
            </el-select>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="内容" prop="content">
            <el-input
              v-model="form.content"
              placeholder="请输入要编码的内容"
              :type="form.type === 'security' ? 'textarea' : 'text'"
              :rows="form.type === 'security' ? 3 : 1"
            ></el-input>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item label="尺寸" prop="size">
            <el-input-number
              v-model="form.size"
              :min="100"
              :max="500"
              :step="50"
              style="width: 100%"
            ></el-input-number>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="边距" prop="margin">
            <el-input-number
              v-model="form.margin"
              :min="0"
              :max="10"
              :step="1"
              style="width: 100%"
            ></el-input-number>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item label="前景色" prop="foregroundColor">
            <el-color-picker v-model="form.foregroundColor" style="width: 100%"></el-color-picker>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="背景色" prop="backgroundColor">
            <el-color-picker v-model="form.backgroundColor" style="width: 100%"></el-color-picker>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="20">
        <el-col :span="24">
          <el-form-item>
            <el-button type="primary" @click="generateCode" :loading="generating">
              ç”Ÿæˆ{{ form.type === 'qrcode' ? '二维码' : '防伪码' }}
            </el-button>
            <el-button @click="resetForm">重置</el-button>
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
    <!-- ç”Ÿæˆçš„码显示区域 -->
    <div v-if="generatedCodeUrl" class="code-display">
      <el-divider content-position="center">
        {{ form.type === 'qrcode' ? '生成的二维码' : '生成的防伪码' }}
      </el-divider>
      <div class="code-container">
        <div class="code-image">
          <img :src="generatedCodeUrl" :alt="form.type === 'qrcode' ? '二维码' : '防伪码'" />
        </div>
        <div class="code-info">
          <p><strong>内容:</strong>{{ form.content }}</p>
          <p><strong>类型:</strong>{{ form.type === 'qrcode' ? '二维码' : '防伪码' }}</p>
          <p><strong>尺寸:</strong>{{ form.size }}x{{ form.size }}px</p>
          <p><strong>生成时间:</strong>{{ generateTime }}</p>
        </div>
      </div>
      <div class="code-actions">
        <el-button type="success" @click="downloadCode" icon="Download">
          ä¸‹è½½å›¾ç‰‡
        </el-button>
        <el-button type="primary" @click="copyToClipboard" icon="CopyDocument">
          å¤åˆ¶å†…容
        </el-button>
        <el-button @click="printCode" icon="Printer">
          æ‰“印
        </el-button>
      </div>
    </div>
    <!-- æ‰¹é‡ç”Ÿæˆå¯¹è¯æ¡† -->
    <el-dialog v-model="batchDialogVisible" title="批量生成" width="600px">
      <el-form :model="batchForm" label-width="120px">
        <el-form-item label="生成数量">
          <el-input-number v-model="batchForm.quantity" :min="1" :max="100" style="width: 100%"></el-input-number>
        </el-form-item>
        <el-form-item label="前缀">
          <el-input v-model="batchForm.prefix" placeholder="请输入前缀,如:PROD_"></el-input>
        </el-form-item>
        <el-form-item label="起始编号">
          <el-input-number v-model="batchForm.startNumber" :min="1" style="width: 100%"></el-input-number>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="batchDialogVisible = false">取消</el-button>
          <el-button type="primary" @click="generateBatchCodes">开始生成</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- æ‰¹é‡ç”Ÿæˆç»“æžœ -->
    <div v-if="batchCodes.length > 0" class="batch-results">
      <el-divider content-position="center">批量生成结果</el-divider>
      <div class="batch-grid">
        <div
          v-for="(code, index) in batchCodes"
          :key="index"
          class="batch-item"
        >
          <img :src="code.url" :alt="code.content" />
          <p class="batch-content">{{ code.content }}</p>
          <el-button size="small" @click="downloadSingleCode(code)">下载</el-button>
        </div>
      </div>
      <div class="batch-actions">
        <el-button type="success" @click="downloadAllCodes">下载全部</el-button>
        <el-button @click="clearBatchCodes">清空结果</el-button>
      </div>
    </div>
  </div>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
import QRCode from 'qrcode'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Download, CopyDocument, Printer } from '@element-plus/icons-vue'
// å®šä¹‰ç»„件名称
defineOptions({
  name: 'QRCodeGenerator'
})
// è¡¨å•数据
const form = reactive({
  type: 'qrcode',
  content: '',
  size: 200,
  margin: 2,
  foregroundColor: '#000000',
  backgroundColor: '#FFFFFF'
})
// è¡¨å•验证规则
const rules = {
  type: [{ required: true, message: '请选择标识类型', trigger: 'change' }],
  content: [{ required: true, message: '请输入内容', trigger: 'blur' }]
}
// å“åº”式数据
const formRef = ref()
const generating = ref(false)
const generatedCodeUrl = ref('')
const generateTime = ref('')
const batchDialogVisible = ref(false)
const batchForm = reactive({
  quantity: 10,
  prefix: '',
  startNumber: 1
})
const batchCodes = ref([])
// ç”ŸæˆäºŒç»´ç æˆ–防伪码
const generateCode = async () => {
  try {
    await formRef.value.validate()
    if (!form.content.trim()) {
      ElMessage.warning('请输入要编码的内容')
      return
    }
    generating.value = true
    if (form.type === 'qrcode') {
      // ç”ŸæˆäºŒç»´ç 
      generatedCodeUrl.value = await QRCode.toDataURL(form.content, {
        width: form.size,
        margin: form.margin,
        color: {
          dark: form.foregroundColor,
          light: form.backgroundColor
        },
        errorCorrectionLevel: 'M'
      })
    } else {
      // ç”Ÿæˆé˜²ä¼ªç ï¼ˆä½¿ç”¨äºŒç»´ç æŠ€æœ¯ï¼Œä½†å†…容格式不同)
      const securityContent = generateSecurityCode(form.content)
      generatedCodeUrl.value = await QRCode.toDataURL(securityContent, {
        width: form.size,
        margin: form.margin,
        color: {
          dark: form.foregroundColor,
          light: form.backgroundColor
        },
        errorCorrectionLevel: 'H' // é˜²ä¼ªç ä½¿ç”¨æœ€é«˜çº é”™çº§åˆ«
      })
    }
    generateTime.value = new Date().toLocaleString()
    ElMessage.success('生成成功!')
  } catch (error) {
    console.error('生成失败:', error)
    ElMessage.error('生成失败:' + error.message)
  } finally {
    generating.value = false
  }
}
// ç”Ÿæˆé˜²ä¼ªç å†…容
const generateSecurityCode = (content) => {
  const timestamp = Date.now()
  const random = Math.random().toString(36).substr(2, 8)
  return `SEC_${content}_${timestamp}_${random}`
}
// ä¸‹è½½ç”Ÿæˆçš„码
const downloadCode = () => {
  if (!generatedCodeUrl.value) {
    ElMessage.warning('请先生成码')
    return
  }
  const a = document.createElement('a')
  a.href = generatedCodeUrl.value
  a.download = `${form.type === 'qrcode' ? '二维码' : '防伪码'}_${new Date().getTime()}.png`
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
  ElMessage.success('下载成功!')
}
// å¤åˆ¶å†…容到剪贴板
const copyToClipboard = async () => {
  try {
    await navigator.clipboard.writeText(form.content)
    ElMessage.success('内容已复制到剪贴板')
  } catch (error) {
    // é™çº§æ–¹æ¡ˆ
    const textArea = document.createElement('textarea')
    textArea.value = form.content
    document.body.appendChild(textArea)
    textArea.select()
    document.execCommand('copy')
    document.body.removeChild(textArea)
    ElMessage.success('内容已复制到剪贴板')
  }
}
// æ‰“印码
const printCode = () => {
  if (!generatedCodeUrl.value) {
    ElMessage.warning('请先生成码')
    return
  }
  const printWindow = window.open('', '_blank')
  printWindow.document.write(`
    <html>
      <head>
        <title>打印${form.type === 'qrcode' ? '二维码' : '防伪码'}</title>
        <style>
          body { text-align: center; padding: 20px; }
          img { max-width: 100%; height: auto; }
          .info { margin: 20px 0; }
        </style>
      </head>
      <body>
        <h2>${form.type === 'qrcode' ? '二维码' : '防伪码'}</h2>
        <img src="${generatedCodeUrl.value}" alt="${form.type === 'qrcode' ? '二维码' : '防伪码'}" />
        <div class="info">
          <p><strong>内容:</strong>${form.content}</p>
          <p><strong>生成时间:</strong>${generateTime.value}</p>
        </div>
      </body>
    </html>
  `)
  printWindow.document.close()
  printWindow.print()
}
// é‡ç½®è¡¨å•
const resetForm = () => {
  formRef.value.resetFields()
  generatedCodeUrl.value = ''
  generateTime.value = ''
  batchCodes.value = []
}
// æ‰¹é‡ç”Ÿæˆ
const generateBatchCodes = async () => {
  if (!batchForm.prefix.trim()) {
    ElMessage.warning('请输入前缀')
    return
  }
  batchCodes.value = []
  generating.value = true
  try {
    for (let i = 0; i < batchForm.quantity; i++) {
      const number = batchForm.startNumber + i
      const content = `${batchForm.prefix}${number.toString().padStart(6, '0')}`
      let codeUrl
      if (form.type === 'qrcode') {
        codeUrl = await QRCode.toDataURL(content, {
          width: form.size,
          margin: form.margin,
          color: {
            dark: form.foregroundColor,
            light: form.backgroundColor
          }
        })
      } else {
        const securityContent = generateSecurityCode(content)
        codeUrl = await QRCode.toDataURL(securityContent, {
          width: form.size,
          margin: form.margin,
          color: {
            dark: form.foregroundColor,
            light: form.backgroundColor
          }
        })
      }
      batchCodes.value.push({
        content,
        url: codeUrl
      })
    }
    ElMessage.success(`批量生成完成,共生成 ${batchForm.quantity} ä¸ªç `)
    batchDialogVisible.value = false
  } catch (error) {
    console.error('批量生成失败:', error)
    ElMessage.error('批量生成失败:' + error.message)
  } finally {
    generating.value = false
  }
}
// ä¸‹è½½å•个批量生成的码
const downloadSingleCode = (code) => {
  const a = document.createElement('a')
  a.href = code.url
  a.download = `${code.content}.png`
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
}
// ä¸‹è½½æ‰€æœ‰æ‰¹é‡ç”Ÿæˆçš„码
const downloadAllCodes = async () => {
  if (batchCodes.value.length === 0) {
    ElMessage.warning('没有可下载的码')
    return
  }
  try {
    // ä½¿ç”¨JSZip打包下载
    const JSZip = await import('jszip')
    const zip = new JSZip.default()
    batchCodes.value.forEach((code, index) => {
      // å°†base64转换为blob
      const base64Data = code.url.split(',')[1]
      const byteCharacters = atob(base64Data)
      const byteNumbers = new Array(byteCharacters.length)
      for (let i = 0; i < byteCharacters.length; i++) {
        byteNumbers[i] = byteCharacters.charCodeAt(i)
      }
      const byteArray = new Uint8Array(byteNumbers)
      zip.file(`${code.content}.png`, byteArray)
    })
    const content = await zip.generateAsync({ type: 'blob' })
    const a = document.createElement('a')
    a.href = URL.createObjectURL(content)
    a.download = `批量${form.type === 'qrcode' ? '二维码' : '防伪码'}_${new Date().getTime()}.zip`
    document.body.appendChild(a)
    a.click()
    document.body.removeChild(a)
    URL.revokeObjectURL(a.href)
    ElMessage.success('批量下载完成!')
  } catch (error) {
    console.error('批量下载失败:', error)
    ElMessage.error('批量下载失败,请逐个下载')
  }
}
// æ¸…空批量生成结果
const clearBatchCodes = () => {
  batchCodes.value = []
}
// æš´éœ²æ–¹æ³•给父组件
defineExpose({
  generateCode,
  downloadCode,
  resetForm,
  form
})
</script>
<style scoped>
.qr-code-generator {
  padding: 20px;
}
.qr-form {
  background: #f8f9fa;
  padding: 20px;
  border-radius: 8px;
  margin-bottom: 20px;
}
.code-display {
  margin-top: 30px;
}
.code-container {
  display: flex;
  justify-content: center;
  align-items: flex-start;
  gap: 40px;
  margin: 20px 0;
}
.code-image img {
  border: 2px solid #e0e0e0;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.code-info {
  text-align: left;
  min-width: 200px;
}
.code-info p {
  margin: 8px 0;
  color: #666;
}
.code-actions {
  text-align: center;
  margin: 20px 0;
}
.code-actions .el-button {
  margin: 0 10px;
}
.batch-results {
  margin-top: 30px;
}
.batch-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 20px;
  margin: 20px 0;
}
.batch-item {
  text-align: center;
  padding: 15px;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  background: #fff;
}
.batch-item img {
  width: 100px;
  height: 100px;
  margin-bottom: 10px;
}
.batch-content {
  font-size: 12px;
  color: #666;
  margin: 10px 0;
  word-break: break-all;
}
.batch-actions {
  text-align: center;
  margin: 20px 0;
}
.batch-actions .el-button {
  margin: 0 10px;
}
@media (max-width: 768px) {
  .code-container {
    flex-direction: column;
    align-items: center;
  }
  .batch-grid {
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  }
}
</style>
src/layout/components/Sidebar/Logo.vue
@@ -16,7 +16,7 @@
<script setup>
import { ref, computed, onMounted, watch } from 'vue'
import useUserStore from '@/store/modules/user'
import defaultLogo from '@/assets/indexViews/JZYJLogo.png' // å¯¼å…¥é»˜è®¤logo
import defaultLogo from '@/assets/indexViews/ZQHXLogo.png' // å¯¼å…¥é»˜è®¤logo
defineProps({
  collapse: {
src/layout/components/Sidebar/index.vue
@@ -30,7 +30,6 @@
const sideTheme = computed(() => settingsStore.sideTheme)
const theme = computed(() => settingsStore.theme)
const isCollapse = computed(() => !appStore.sidebar.opened)
console.log(44444, settingsStore.isDark, sideTheme.value)
// èŽ·å–èœå•èƒŒæ™¯è‰²
const getMenuBackground = computed(() => {
src/main.js
@@ -76,7 +76,7 @@
app.config.globalProperties.addDateRange = addDateRange;
app.config.globalProperties.selectDictLabel = selectDictLabel;
app.config.globalProperties.selectDictLabels = selectDictLabels;
app.config.globalProperties.javaApi = "http://10.136.12.71:8014";
app.config.globalProperties.javaApi = "http://114.132.189.42:8080";
app.config.globalProperties.HaveJson = (val) => {
  return JSON.parse(JSON.stringify(val));
};
src/views/collaborativeApproval/approvalProcess/index1.vue
ÎļþÒÑɾ³ý
src/views/collaborativeApproval/approvalProcess/index2.vue
ÎļþÒÑɾ³ý
src/views/collaborativeApproval/approvalProcess/index3.vue
ÎļþÒÑɾ³ý
src/views/collaborativeApproval/approvalProcess/index4.vue
ÎļþÒÑɾ³ý
src/views/collaborativeApproval/attendanceManagement/index.vue
@@ -25,8 +25,8 @@
            </el-table-column>
            <el-table-column label="操作" fixed="right">
              <template #default="scope">
                <el-button type="primary" size="small" @click="openDialog('holiday', 'edit', scope.row)">编辑</el-button>
                <el-button type="danger" size="small" @click="deleteItem('holiday', scope.row)">删除</el-button>
                <el-button type="primary" link @click="openDialog('holiday', 'edit', scope.row)">编辑</el-button>
                <el-button type="danger" link @click="deleteItem('holiday', scope.row)">删除</el-button>
              </template>
            </el-table-column>
          </el-table>
@@ -52,8 +52,8 @@
            </el-table-column>
            <el-table-column label="操作" fixed="right">
              <template #default="scope">
                <el-button type="primary" size="small" @click="openDialog('annual', 'edit', scope.row)">编辑</el-button>
                <el-button type="danger" size="small" @click="deleteItem('annual', scope.row)">删除</el-button>
                <el-button type="primary" link @click="openDialog('annual', 'edit', scope.row)">编辑</el-button>
                <el-button type="danger" link @click="deleteItem('annual', scope.row)">删除</el-button>
              </template>
            </el-table-column>
          </el-table>
@@ -84,8 +84,8 @@
            </el-table-column>
            <el-table-column label="操作" fixed="right">
              <template #default="scope">
                <el-button type="primary" size="small" @click="openDialog('overtime', 'edit', scope.row)">编辑</el-button>
                <el-button type="danger" size="small" @click="deleteItem('overtime', scope.row)">删除</el-button>
                <el-button type="primary" link @click="openDialog('overtime', 'edit', scope.row)">编辑</el-button>
                <el-button type="danger" link @click="deleteItem('overtime', scope.row)">删除</el-button>
              </template>
            </el-table-column>
          </el-table>
@@ -118,8 +118,8 @@
            </el-table-column>
            <el-table-column label="操作" fixed="right">
              <template #default="scope">
                <el-button type="primary" size="small" @click="openDialog('worktime', 'edit', scope.row)">编辑</el-button>
                <el-button type="danger" size="small" @click="deleteItem('worktime', scope.row)">删除</el-button>
                <el-button type="primary" link @click="openDialog('worktime', 'edit', scope.row)">编辑</el-button>
                <el-button type="danger" link @click="deleteItem('worktime', scope.row)">删除</el-button>
              </template>
            </el-table-column>
          </el-table>
src/views/collaborativeApproval/knowledgeBase/index.vue
@@ -410,7 +410,7 @@
    problem: "大额合同审批流程复杂,审批时间长,影响业务进展",
    solution: "建立绿色通道,对符合条件的合同采用简化审批流程,由部门负责人直接审批,平均审批时间从3天缩短至1天",
    keyPoints: "绿色通道条件,简化流程,审批权限,时间控制",
    creator: "张经理",
    creator: "陈志强",
    usageCount: 15,
    createTime: "2025-01-15 10:30:00"
  },
@@ -488,7 +488,7 @@
    problem: `在${randomScenario}过程中遇到的问题描述...`,
    solution: `针对${randomScenario}的解决方案和操作步骤...`,
    keyPoints: "关键要点1,关键要点2,关键要点3,关键要点4",
    creator: ["张经理", "李主管", "王专员", "刘总监"][Math.floor(Math.random() * 4)],
          creator: ["陈志强", "刘雅婷", "王建国", "赵丽华"][Math.floor(Math.random() * 4)],
    usageCount: Math.floor(Math.random() * 20) + 1,
    createTime: now.toLocaleString()
  };
src/views/collaborativeApproval/meetingBoard/index.vue
@@ -164,8 +164,8 @@
    startTime: '2025-01-15 09:00:00',
    endTime: '2025-01-15 10:30:00',
    location: '会议室A',
    host: '张经理',
    participants: ['张经理', '李工程师', '王设计师', '赵测试员'],
    host: '陈志强',
    participants: ['陈志强', '刘雅婷', '王建国', '赵丽华'],
    agenda: [
      { time: '09:00-09:15', content: '上周工作总结', status: 'completed' },
      { time: '09:15-09:45', content: '本周开发计划', status: 'active' },
@@ -180,8 +180,8 @@
    startTime: '2025-01-15 14:00:00',
    endTime: '2025-01-15 15:00:00',
    location: '线上会议',
    host: '陈总监',
    participants: ['陈总监', '刘产品经理', '孙客户经理', '客户代表'],
    host: '陈志强',
    participants: ['陈志强', '刘雅婷', '孙明华', '客户代表'],
    agenda: [
      { time: '14:00-14:20', content: '需求背景介绍', status: 'pending' },
      { time: '14:20-14:40', content: '功能需求分析', status: 'pending' },
src/views/collaborativeApproval/notificationManagement/index.vue
@@ -664,9 +664,9 @@
    console.error('获取员工列表失败:', error);
    // å¦‚果接口都失败,使用默认数据
    employees.value = [
      { label: "张三", value: "001", dept: "技术部", phone: "13800138001", email: "zhangsan@company.com", status: "0" },
      { label: "李四", value: "002", dept: "销售部", phone: "13800138002", email: "lisi@company.com", status: "0" },
      { label: "王五", value: "003", dept: "人事部", phone: "13800138003", email: "wangwu@company.com", status: "0" }
      { label: "陈志强", value: "001", dept: "技术部", phone: "13800138001", email: "chenzhiqiang@company.com", status: "0" },
      { label: "刘雅婷", value: "002", dept: "销售部", phone: "13800138002", email: "liuyating@company.com", status: "0" },
      { label: "王建国", value: "003", dept: "人事部", phone: "13800138003", email: "wangjianguo@company.com", status: "0" }
    ];
  } finally {
    employeesLoading.value = false;
src/views/collaborativeApproval/officeSupplies/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,670 @@
<template>
  <div class="app-container">
    <el-card class="box-card">
      <template #header>
        <div class="card-header">
          <span>办公物资申请管理</span>
          <el-button type="primary" @click="showApplyDialog = true">
            <el-icon><Plus /></el-icon>
            æ–°å»ºç”³è¯·
          </el-button>
        </div>
      </template>
             <!-- æœç´¢åŒºåŸŸ -->
       <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
         <el-form-item label="申请编号" prop="applyNo">
           <el-input
             v-model="queryParams.applyNo"
             placeholder="请输入申请编号"
             clearable
             style="width: 200px"
             @keyup.enter="handleQuery"
           />
         </el-form-item>
         <el-form-item label="申请人" prop="applicant">
           <el-input
             v-model="queryParams.applicant"
             placeholder="请输入申请人"
             clearable
             style="width: 200px"
             @keyup.enter="handleQuery"
           />
         </el-form-item>
         <el-form-item label="申请状态" prop="status">
           <el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 200px">
             <el-option label="待审批" value="pending" />
             <el-option label="已通过" value="approved" />
             <el-option label="已拒绝" value="rejected" />
             <el-option label="已发放" value="issued" />
           </el-select>
         </el-form-item>
         <el-form-item>
           <el-button type="primary" @click="handleQuery">
             <el-icon><Search /></el-icon>
             æœç´¢
           </el-button>
           <el-button @click="resetQuery">
             <el-icon><Refresh /></el-icon>
             é‡ç½®
           </el-button>
         </el-form-item>
         <el-form-item>
            <el-button type="primary" @click="handleExport">
            <el-icon><Download /></el-icon>
            å¯¼å‡º
          </el-button>
          <el-button type="success" @click="handleBatchApprove" :disabled="multipleSelection.length === 0">
            <el-icon><Check /></el-icon>
            æ‰¹é‡å®¡æ‰¹
          </el-button>
         </el-form-item>
       </el-form>
      <!-- è¡¨æ ¼åŒºåŸŸ -->
      <el-table
        v-loading="loading"
        :data="suppliesList"
        @selection-change="handleSelectionChange"
        style="width: 100%"
      >
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="申请编号" align="center" prop="applyNo" width="180" />
        <el-table-column label="申请人" align="center" prop="applicant" width="120" />
        <el-table-column label="部门" align="center" prop="department" width="120" />
        <el-table-column label="物资类型" align="center" prop="supplyType" width="120" />
        <el-table-column label="申请数量" align="center" prop="quantity" width="100" />
        <el-table-column label="申请原因" align="center" prop="reason" min-width="200" show-overflow-tooltip />
        <el-table-column label="申请状态" align="center" prop="status" width="100">
          <template #default="scope">
            <el-tag :type="getStatusType(scope.row.status)">
              {{ getStatusText(scope.row.status) }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="申请时间" align="center" prop="applyTime" width="180" />
        <el-table-column label="审批人" align="center" prop="approver" width="120" />
        <el-table-column label="审批时间" align="center" prop="approveTime" width="180" />
        <el-table-column label="发放时间" align="center" prop="issueTime" width="180" />
        <el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width" width="200">
          <template #default="scope">
            <el-button
              v-if="scope.row.status === 'pending'"
              type="primary"
              link
              @click="handleApprove(scope.row)"
            >
              å®¡æ‰¹
            </el-button>
            <el-button
              v-if="scope.row.status === 'approved'"
              type="success"
                            link
              @click="handleIssue(scope.row)"
            >
              å‘放
            </el-button>
            <el-button
              type="info"
                            link
              @click="handleDetail(scope.row)"
            >
              è¯¦æƒ…
            </el-button>
            <el-button
              v-if="scope.row.status === 'pending'"
              type="danger"
                            link
              @click="handleDelete(scope.row)"
            >
              åˆ é™¤
            </el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- åˆ†é¡µ -->
      <pagination
        v-show="total > 0"
        :total="total"
        v-model:page="queryParams.pageNum"
        v-model:limit="queryParams.pageSize"
        @pagination="getList"
      />
    </el-card>
    <!-- ç”³è¯·å¯¹è¯æ¡† -->
    <el-dialog
      v-model="showApplyDialog"
      title="办公物资申请"
      width="600px"
      append-to-body
    >
      <el-form ref="applyFormRef" :model="applyForm" :rules="applyRules" label-width="100px">
        <el-form-item label="物资类型" prop="supplyType">
          <el-select v-model="applyForm.supplyType" placeholder="请选择物资类型" style="width: 100%">
            <el-option label="办公用品" value="office" />
            <el-option label="电子设备" value="electronic" />
            <el-option label="清洁用品" value="cleaning" />
            <el-option label="其他" value="other" />
          </el-select>
        </el-form-item>
        <el-form-item label="具体物品" prop="itemName">
          <el-input v-model="applyForm.itemName" placeholder="请输入具体物品名称" />
        </el-form-item>
        <el-form-item label="申请数量" prop="quantity">
          <el-input-number v-model="applyForm.quantity" :min="1" :max="999" style="width: 100%" />
        </el-form-item>
        <el-form-item label="申请原因" prop="reason">
          <el-input
            v-model="applyForm.reason"
            type="textarea"
            :rows="3"
            placeholder="请输入申请原因"
          />
        </el-form-item>
        <el-form-item label="紧急程度" prop="urgency">
          <el-radio-group v-model="applyForm.urgency">
            <el-radio label="normal">普通</el-radio>
            <el-radio label="urgent">紧急</el-radio>
            <el-radio label="very_urgent">非常紧急</el-radio>
          </el-radio-group>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="showApplyDialog = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="submitApply">ç¡® å®š</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- å®¡æ‰¹å¯¹è¯æ¡† -->
    <el-dialog
      v-model="showApproveDialog"
      title="审批申请"
      width="500px"
      append-to-body
    >
      <el-form ref="approveFormRef" :model="approveForm" :rules="approveRules" label-width="100px">
        <el-form-item label="审批结果" prop="approveResult">
          <el-radio-group v-model="approveForm.approveResult">
            <el-radio label="approved">通过</el-radio>
            <el-radio label="rejected">拒绝</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="审批意见" prop="approveComment">
          <el-input
            v-model="approveForm.approveComment"
            type="textarea"
            :rows="3"
            placeholder="请输入审批意见"
          />
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="showApproveDialog = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="submitApprove">ç¡® å®š</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- è¯¦æƒ…对话框 -->
    <el-dialog
      v-model="showDetailDialog"
      title="申请详情"
      width="700px"
      append-to-body
    >
      <el-descriptions :column="2" border>
        <el-descriptions-item label="申请编号">{{ currentDetail.applyNo }}</el-descriptions-item>
        <el-descriptions-item label="申请人">{{ currentDetail.applicant }}</el-descriptions-item>
        <el-descriptions-item label="部门">{{ currentDetail.department }}</el-descriptions-item>
        <el-descriptions-item label="物资类型">{{ currentDetail.supplyType }}</el-descriptions-item>
        <el-descriptions-item label="具体物品">{{ currentDetail.itemName }}</el-descriptions-item>
        <el-descriptions-item label="申请数量">{{ currentDetail.quantity }}</el-descriptions-item>
        <el-descriptions-item label="申请原因" :span="2">{{ currentDetail.reason }}</el-descriptions-item>
        <el-descriptions-item label="申请状态">
          <el-tag :type="getStatusType(currentDetail.status)">
            {{ getStatusText(currentDetail.status) }}
          </el-tag>
        </el-descriptions-item>
        <el-descriptions-item label="申请时间">{{ currentDetail.applyTime }}</el-descriptions-item>
        <el-descriptions-item label="审批人">{{ currentDetail.approver || '-' }}</el-descriptions-item>
        <el-descriptions-item label="审批时间">{{ currentDetail.approveTime || '-' }}</el-descriptions-item>
        <el-descriptions-item label="审批意见" :span="2">{{ currentDetail.approveComment || '-' }}</el-descriptions-item>
        <el-descriptions-item label="发放时间">{{ currentDetail.issueTime || '-' }}</el-descriptions-item>
        <el-descriptions-item label="发放人">{{ currentDetail.issuer || '-' }}</el-descriptions-item>
      </el-descriptions>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Search, Refresh, Download, Check } from '@element-plus/icons-vue'
// å“åº”式数据
const loading = ref(false)
const showSearch = ref(true)
const showApplyDialog = ref(false)
const showApproveDialog = ref(false)
const showDetailDialog = ref(false)
const multipleSelection = ref([])
const total = ref(0)
const suppliesList = ref([])
const currentDetail = ref({})
// æŸ¥è¯¢å‚æ•°
const queryParams = reactive({
  pageNum: 1,
  pageSize: 10,
  applyNo: '',
  applicant: '',
  status: ''
})
// ç”³è¯·è¡¨å•
const applyForm = reactive({
  supplyType: '',
  itemName: '',
  quantity: 1,
  reason: '',
  urgency: 'normal'
})
// å®¡æ‰¹è¡¨å•
const approveForm = reactive({
  approveResult: 'approved',
  approveComment: ''
})
// è¡¨å•校验规则
const applyRules = {
  supplyType: [{ required: true, message: '请选择物资类型', trigger: 'change' }],
  itemName: [{ required: true, message: '请输入具体物品名称', trigger: 'blur' }],
  quantity: [{ required: true, message: '请输入申请数量', trigger: 'blur' }],
  reason: [{ required: true, message: '请输入申请原因', trigger: 'blur' }]
}
const approveRules = {
  approveResult: [{ required: true, message: '请选择审批结果', trigger: 'change' }],
  approveComment: [{ required: true, message: '请输入审批意见', trigger: 'blur' }]
}
// å‡æ•°æ®
const mockData = [
  {
    id: 1,
    applyNo: 'WS20241201001',
    applicant: '陈志强',
    department: '技术部',
    supplyType: '办公用品',
    itemName: 'A4打印纸',
    quantity: 10,
    reason: '日常办公打印需要',
    status: 'pending',
    applyTime: '2025-12-01 09:30:00',
    approver: '',
    approveTime: '',
    approveComment: '',
    issueTime: '',
    issuer: ''
  },
  {
    id: 2,
    applyNo: 'WS20241201002',
    applicant: '刘雅婷',
    department: '人事部',
    supplyType: '电子设备',
    itemName: '无线鼠标',
    quantity: 2,
    reason: '新员工入职配备',
    status: 'approved',
    applyTime: '2025-12-01 10:15:00',
    approver: '王建国',
    approveTime: '2025-12-01 14:20:00',
    approveComment: '同意申请,请及时发放',
    issueTime: '',
    issuer: ''
  },
  {
    id: 3,
    applyNo: 'WS20241201003',
    applicant: '王建国',
    department: '财务部',
    supplyType: '清洁用品',
    itemName: '洗手液',
    quantity: 5,
    reason: '办公室清洁用品补充',
    status: 'issued',
    applyTime: '2025-12-01 11:00:00',
    approver: '刘雅婷',
    approveTime: '2025-12-01 15:30:00',
    approveComment: '同意申请',
    issueTime: '2025-12-01 16:00:00',
    issuer: '钱伟明'
  },
  {
    id: 4,
    applyNo: 'WS20241201004',
    applicant: '赵丽华',
    department: '市场部',
    supplyType: '其他',
    itemName: '文件夹',
    quantity: 20,
    reason: '项目资料整理需要',
    status: 'rejected',
    applyTime: '2025-12-01 13:45:00',
    approver: '陈志强',
    approveTime: '2025-12-01 17:00:00',
    approveComment: '数量过多,建议减少到10个',
    issueTime: '',
    issuer: ''
  },
  {
    id: 5,
    applyNo: 'WS20241202001',
    applicant: '钱伟明',
    department: '运营部',
    supplyType: '办公用品',
    itemName: '签字笔',
    quantity: 50,
    reason: '部门日常办公用品补充',
    status: 'pending',
    applyTime: '2025-12-02 08:30:00',
    approver: '',
    approveTime: '',
    approveComment: '',
    issueTime: '',
    issuer: ''
  },
  {
    id: 6,
    applyNo: 'WS20241202002',
    applicant: '孙明华',
    department: '技术部',
    supplyType: '电子设备',
    itemName: '键盘',
    quantity: 3,
    reason: '新员工设备配备',
    status: 'approved',
    applyTime: '2025-12-02 14:20:00',
    approver: '陈志强',
    approveTime: '2025-12-02 16:00:00',
    approveComment: '同意申请',
    issueTime: '',
    issuer: ''
  },
  {
    id: 7,
    applyNo: 'WS20241203001',
    applicant: '周美玲',
    department: '人事部',
    supplyType: '清洁用品',
    itemName: '纸巾',
    quantity: 30,
    reason: '办公区域清洁用品补充',
    status: 'issued',
    applyTime: '2025-12-03 09:15:00',
    approver: '赵丽华',
    approveTime: '2025-12-03 10:30:00',
    approveComment: '同意申请',
    issueTime: '2025-12-03 11:00:00',
    issuer: '孙明华'
  },
  {
    id: 8,
    applyNo: 'WS20241203002',
    applicant: '吴志强',
    department: '财务部',
    supplyType: '其他',
    itemName: '计算器',
    quantity: 2,
    reason: '财务核算工作需要',
    status: 'rejected',
    applyTime: '2025-12-03 15:45:00',
    approver: '王建国',
    approveTime: '2025-12-03 17:20:00',
    approveComment: '已有计算器,暂不需要',
    issueTime: '',
    issuer: ''
  }
]
// èŽ·å–åˆ—è¡¨æ•°æ®
const getList = () => {
  loading.value = true
  // æ¨¡æ‹Ÿå¼‚步请求
  setTimeout(() => {
    let filteredData = [...mockData]
    // æ ¹æ®æŸ¥è¯¢æ¡ä»¶è¿‡æ»¤
    if (queryParams.applyNo) {
      filteredData = filteredData.filter(item =>
        item.applyNo.toLowerCase().includes(queryParams.applyNo.toLowerCase())
      )
    }
    if (queryParams.applicant) {
      filteredData = filteredData.filter(item =>
        item.applicant.toLowerCase().includes(queryParams.applicant.toLowerCase())
      )
    }
    if (queryParams.status) {
      filteredData = filteredData.filter(item =>
        item.status === queryParams.status
      )
    }
    // æŒ‰ç”³è¯·æ—¶é—´å€’序排列
    filteredData.sort((a, b) => new Date(b.applyTime) - new Date(a.applyTime))
    total.value = filteredData.length
    suppliesList.value = filteredData.slice(
      (queryParams.pageNum - 1) * queryParams.pageSize,
      queryParams.pageNum * queryParams.pageSize
    )
    loading.value = false
  }, 500)
}
// æŸ¥è¯¢
const handleQuery = () => {
  queryParams.pageNum = 1
  getList()
}
// é‡ç½®æŸ¥è¯¢
const resetQuery = () => {
  queryParams.applyNo = ''
  queryParams.applicant = ''
  queryParams.status = ''
  handleQuery()
}
// å¤šé€‰
const handleSelectionChange = (selection) => {
  multipleSelection.value = selection
}
// èŽ·å–çŠ¶æ€ç±»åž‹
const getStatusType = (status) => {
  const statusMap = {
    pending: 'warning',
    approved: 'success',
    rejected: 'danger',
    issued: 'info'
  }
  return statusMap[status] || 'info'
}
// èŽ·å–çŠ¶æ€æ–‡æœ¬
const getStatusText = (status) => {
  const statusMap = {
    pending: '待审批',
    approved: '已通过',
    rejected: '已拒绝',
    issued: '已发放'
  }
  return statusMap[status] || status
}
// æäº¤ç”³è¯·
const submitApply = () => {
  const newApply = {
    id: mockData.length + 1,
    applyNo: `WS${new Date().getTime()}`,
    applicant: '当前用户',
    department: '技术部',
    supplyType: applyForm.supplyType,
    itemName: applyForm.itemName,
    quantity: applyForm.quantity,
    reason: applyForm.reason,
    status: 'pending',
    applyTime: new Date().toLocaleString(),
    approver: '',
    approveTime: '',
    approveComment: '',
    issueTime: '',
    issuer: ''
  }
  mockData.unshift(newApply)
  showApplyDialog.value = false
  ElMessage.success('申请提交成功')
  getList()
  // é‡ç½®è¡¨å•
  Object.assign(applyForm, {
    supplyType: '',
    itemName: '',
    quantity: 1,
    reason: '',
    urgency: 'normal'
  })
}
// å®¡æ‰¹
const handleApprove = (row) => {
  currentDetail.value = row
  showApproveDialog.value = true
}
// æäº¤å®¡æ‰¹
const submitApprove = () => {
  const index = mockData.findIndex(item => item.id === currentDetail.value.id)
  if (index !== -1) {
    mockData[index].status = approveForm.approveResult
    mockData[index].approver = '当前审批人'
    mockData[index].approveTime = new Date().toLocaleString()
    mockData[index].approveComment = approveForm.approveComment
  }
  showApproveDialog.value = false
  ElMessage.success('审批完成')
  getList()
  // é‡ç½®è¡¨å•
  Object.assign(approveForm, {
    approveResult: 'approved',
    approveComment: ''
  })
}
// å‘放
const handleIssue = (row) => {
  const index = mockData.findIndex(item => item.id === row.id)
  if (index !== -1) {
    mockData[index].status = 'issued'
    mockData[index].issueTime = new Date().toLocaleString()
    mockData[index].issuer = '当前发放人'
  }
  ElMessage.success('发放完成')
  getList()
}
// æŸ¥çœ‹è¯¦æƒ…
const handleDetail = (row) => {
  currentDetail.value = row
  showDetailDialog.value = true
}
// åˆ é™¤
const handleDelete = (row) => {
  ElMessageBox.confirm('确认删除该申请吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    const index = mockData.findIndex(item => item.id === row.id)
    if (index !== -1) {
      mockData.splice(index, 1)
    }
    ElMessage.success('删除成功')
    getList()
  })
}
// æ‰¹é‡å®¡æ‰¹
const handleBatchApprove = () => {
  if (multipleSelection.value.length === 0) {
    ElMessage.warning('请选择要审批的记录')
    return
  }
  ElMessageBox.confirm(`确认批量审批选中的 ${multipleSelection.value.length} æ¡è®°å½•吗?`, '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    multipleSelection.value.forEach(row => {
      const index = mockData.findIndex(item => item.id === row.id)
      if (index !== -1) {
        mockData[index].status = 'approved'
        mockData[index].approver = '当前审批人'
        mockData[index].approveTime = new Date().toLocaleString()
        mockData[index].approveComment = '批量审批通过'
      }
    })
    ElMessage.success('批量审批完成')
    getList()
  })
}
// å¯¼å‡º
const handleExport = () => {
  ElMessage.success('导出功能开发中...')
}
// é¡µé¢åŠ è½½æ—¶èŽ·å–æ•°æ®
onMounted(() => {
  getList()
})
</script>
<style scoped>
.app-container {
  padding: 20px;
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.mb8 {
  margin-bottom: 8px;
}
.dialog-footer {
  text-align: right;
}
:deep(.el-descriptions__label) {
  width: 120px;
}
</style>
src/views/collaborativeApproval/planTemplate/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,750 @@
<template>
  <div class="app-container">
    <!-- é¡¶éƒ¨æ“ä½œæ  -->
    <div class="header-actions">
      <div class="left-actions">
        <el-select v-model="currentLevel" placeholder="选择计划级别" style="width: 150px" @change="handleLevelChange">
          <el-option label="个人计划" value="personal" />
          <el-option label="小组计划" value="group" />
          <el-option label="部门计划" value="department" />
          <el-option label="公司计划" value="company" />
        </el-select>
        <el-select v-model="currentPeriod" placeholder="选择时间周期" style="width: 120px; margin-left: 10px" @change="handlePeriodChange">
          <el-option label="周计划" value="week" />
          <el-option label="月计划" value="month" />
          <el-option label="年计划" value="year" />
        </el-select>
        <el-date-picker
          v-model="currentDate"
          :type="datePickerType"
          placeholder="选择日期"
          style="width: 180px; margin-left: 10px"
          @change="handleDateChange"
        />
      </div>
      <div class="right-actions">
        <el-button type="primary" @click="handleAddPlan">新增计划</el-button>
        <el-button @click="handleExport">导出计划</el-button>
        <el-button @click="handleShare">共享计划</el-button>
      </div>
    </div>
    <!-- è®¡åˆ’概览卡片 -->
    <div class="overview-cards">
      <el-row :gutter="20">
        <el-col :span="6">
          <el-card class="overview-card">
            <div class="card-content">
              <div class="card-icon personal">
                <el-icon><User /></el-icon>
              </div>
              <div class="card-info">
                <div class="card-title">个人计划</div>
                <div class="card-number">{{ overviewData.personal.total }}</div>
                <div class="card-progress">
                  <el-progress :percentage="overviewData.personal.completion" :stroke-width="6" />
                </div>
              </div>
            </div>
          </el-card>
        </el-col>
        <el-col :span="6">
          <el-card class="overview-card">
            <div class="card-content">
              <div class="card-icon group">
                <el-icon><UserFilled /></el-icon>
              </div>
              <div class="card-info">
                <div class="card-title">小组计划</div>
                <div class="card-number">{{ overviewData.group.total }}</div>
                <div class="card-progress">
                  <el-progress :percentage="overviewData.group.completion" :stroke-width="6" />
                </div>
              </div>
            </div>
          </el-card>
        </el-col>
        <el-col :span="6">
          <el-card class="overview-card">
            <div class="card-content">
              <div class="card-icon department">
                <el-icon><OfficeBuilding /></el-icon>
              </div>
              <div class="card-info">
                <div class="card-title">部门计划</div>
                <div class="card-number">{{ overviewData.department.total }}</div>
                <div class="card-progress">
                  <el-progress :percentage="overviewData.department.completion" :stroke-width="6" />
                </div>
              </div>
            </div>
          </el-card>
        </el-col>
        <el-col :span="6">
          <el-card class="overview-card">
            <div class="card-content">
              <div class="card-icon company">
                <el-icon><House /></el-icon>
              </div>
              <div class="card-info">
                <div class="card-title">公司计划</div>
                <div class="card-number">{{ overviewData.company.total }}</div>
                <div class="card-progress">
                  <el-progress :percentage="overviewData.company.completion" :stroke-width="6" />
                </div>
              </div>
            </div>
          </el-card>
        </el-col>
      </el-row>
    </div>
    <!-- è®¡åˆ’列表 -->
    <div class="plan-content">
      <el-card>
        <template #header>
          <div class="card-header">
            <span>{{ getCurrentLevelText() }} - {{ getCurrentPeriodText() }}</span>
            <div>
              <el-button size="small" @click="handleRefresh">刷新</el-button>
              <el-button size="small" @click="handleFilter">筛选</el-button>
            </div>
          </div>
        </template>
        <div class="plan-list">
          <div v-for="plan in planList" :key="plan.id" class="plan-item">
            <div class="plan-header">
              <div class="plan-title">
                <el-tag :type="getPriorityType(plan.priority)" size="small">{{ getPriorityText(plan.priority) }}</el-tag>
                <span class="title-text">{{ plan.title }}</span>
              </div>
              <div class="plan-actions">
                <el-button size="small" @click="handleEditPlan(plan)">编辑</el-button>
                <el-button size="small" @click="handleViewDetail(plan)">详情</el-button>
                <el-dropdown @command="handleMoreAction">
                  <el-button size="small">
                    æ›´å¤š<el-icon class="el-icon--right"><ArrowDown /></el-icon>
                  </el-button>
                  <template #dropdown>
                    <el-dropdown-menu>
                      <el-dropdown-item command="share">共享</el-dropdown-item>
                      <el-dropdown-item command="copy">复制</el-dropdown-item>
                      <el-dropdown-item command="delete" divided>删除</el-dropdown-item>
                    </el-dropdown-menu>
                  </template>
                </el-dropdown>
              </div>
            </div>
            <div class="plan-content">
              <div class="plan-description">{{ plan.description }}</div>
              <div class="plan-meta">
                <div class="meta-item">
                  <el-icon><Calendar /></el-icon>
                  <span>{{ plan.startDate }} - {{ plan.endDate }}</span>
                </div>
                <div class="meta-item">
                  <el-icon><User /></el-icon>
                  <span>{{ plan.assignee }}</span>
                </div>
                <div class="meta-item">
                  <el-icon><Clock /></el-icon>
                  <span>进度: {{ plan.progress }}%</span>
                </div>
                <div class="meta-item">
                  <el-icon><Flag /></el-icon>
                  <span>{{ getStatusText(plan.status) }}</span>
                </div>
              </div>
              <div class="plan-progress">
                <el-progress
                  :percentage="plan.progress"
                  :color="getProgressColor(plan.progress)"
                  :stroke-width="8"
                />
              </div>
              <div class="plan-tags">
                <el-tag v-for="tag in plan.tags" :key="tag" size="small" style="margin-right: 5px">
                  {{ tag }}
                </el-tag>
              </div>
            </div>
          </div>
        </div>
      </el-card>
    </div>
    <!-- æ–°å¢ž/编辑计划对话框 -->
    <el-dialog
      v-model="planDialogVisible"
      :title="dialogTitle"
      width="600px"
      @close="handleDialogClose"
    >
      <el-form :model="planForm" :rules="planRules" ref="planFormRef" label-width="100px">
        <el-form-item label="计划标题" prop="title">
          <el-input v-model="planForm.title" placeholder="请输入计划标题" />
        </el-form-item>
        <el-form-item label="计划描述" prop="description">
          <el-input
            v-model="planForm.description"
            type="textarea"
            :rows="3"
            placeholder="请输入计划描述"
          />
        </el-form-item>
        <el-form-item label="计划级别" prop="level">
          <el-select v-model="planForm.level" placeholder="选择计划级别" style="width: 100%">
            <el-option label="个人计划" value="personal" />
            <el-option label="小组计划" value="group" />
            <el-option label="部门计划" value="department" />
            <el-option label="公司计划" value="company" />
          </el-select>
        </el-form-item>
        <el-form-item label="时间周期" prop="period">
          <el-select v-model="planForm.period" placeholder="选择时间周期" style="width: 100%">
            <el-option label="周计划" value="week" />
            <el-option label="月计划" value="month" />
            <el-option label="年计划" value="year" />
          </el-select>
        </el-form-item>
        <el-form-item label="开始时间" prop="startDate">
          <el-date-picker
            v-model="planForm.startDate"
            type="date"
            placeholder="选择开始时间"
            style="width: 100%"
          />
        </el-form-item>
        <el-form-item label="结束时间" prop="endDate">
          <el-date-picker
            v-model="planForm.endDate"
            type="date"
            placeholder="选择结束时间"
            style="width: 100%"
          />
        </el-form-item>
        <el-form-item label="负责人" prop="assignee">
          <el-input v-model="planForm.assignee" placeholder="请输入负责人" />
        </el-form-item>
        <el-form-item label="优先级" prop="priority">
          <el-select v-model="planForm.priority" placeholder="选择优先级" style="width: 100%">
            <el-option label="高" value="high" />
            <el-option label="中" value="medium" />
            <el-option label="低" value="low" />
          </el-select>
        </el-form-item>
        <el-form-item label="标签">
          <el-input v-model="planForm.tags" placeholder="请输入标签,用逗号分隔" />
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="planDialogVisible = false">取消</el-button>
          <el-button type="primary" @click="handleSavePlan">保存</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
  User,
  UserFilled,
  OfficeBuilding,
  House,
  Calendar,
  Clock,
  Flag,
  ArrowDown
} from '@element-plus/icons-vue'
// å“åº”式数据
const currentLevel = ref('personal')
const currentPeriod = ref('week')
const currentDate = ref(new Date())
const planDialogVisible = ref(false)
const dialogTitle = ref('新增计划')
const planFormRef = ref()
// è¡¨å•数据
const planForm = reactive({
  title: '',
  description: '',
  level: 'personal',
  period: 'week',
  startDate: '',
  endDate: '',
  assignee: '',
  priority: 'medium',
  tags: ''
})
// è¡¨å•验证规则
const planRules = {
  title: [{ required: true, message: '请输入计划标题', trigger: 'blur' }],
  description: [{ required: true, message: '请输入计划描述', trigger: 'blur' }],
  level: [{ required: true, message: '请选择计划级别', trigger: 'change' }],
  period: [{ required: true, message: '请选择时间周期', trigger: 'change' }],
  startDate: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
  endDate: [{ required: true, message: '请选择结束时间', trigger: 'change' }],
  assignee: [{ required: true, message: '请输入负责人', trigger: 'blur' }],
  priority: [{ required: true, message: '请选择优先级', trigger: 'change' }]
}
// æ¦‚览数据
const overviewData = reactive({
  personal: { total: 12, completion: 75 },
  group: { total: 8, completion: 60 },
  department: { total: 15, completion: 45 },
  company: { total: 6, completion: 30 }
})
// è®¡åˆ’列表数据
const planList = ref([
  {
    id: 1,
    title: '产品需求分析',
    description: '对新产品进行详细的需求分析和市场调研,制定产品规划方案',
    level: 'personal',
    period: 'week',
    startDate: '2025-01-15',
    endDate: '2025-01-21',
    assignee: '陈志强',
    priority: 'high',
    status: 'in_progress',
    progress: 80,
    tags: ['产品', '分析', '调研']
  },
  {
    id: 2,
    title: '技术架构设计',
    description: '设计系统技术架构,包括数据库设计、接口设计等',
    level: 'group',
    period: 'month',
    startDate: '2025-01-01',
    endDate: '2025-01-31',
    assignee: '刘雅婷',
    priority: 'high',
    status: 'completed',
    progress: 100,
    tags: ['技术', '架构', '设计']
  },
  {
    id: 3,
    title: '市场推广计划',
    description: '制定年度市场推广策略和营销计划',
    level: 'department',
    period: 'year',
    startDate: '2025-01-01',
    endDate: '2025-12-31',
    assignee: '王建国',
    priority: 'medium',
    status: 'not_started',
    progress: 0,
    tags: ['市场', '推广', '营销']
  },
  {
    id: 4,
    title: '团队建设活动',
    description: '组织团队建设活动,提升团队凝聚力和协作效率',
    level: 'company',
    period: 'month',
    startDate: '2025-01-15',
    endDate: '2025-02-15',
    assignee: '赵丽华',
    priority: 'low',
    status: 'in_progress',
    progress: 30,
    tags: ['团队', '建设', '活动']
  }
])
// è®¡ç®—属性
const datePickerType = computed(() => {
  switch (currentPeriod.value) {
    case 'week':
      return 'week'
    case 'month':
      return 'month'
    case 'year':
      return 'year'
    default:
      return 'date'
  }
})
// æ–¹æ³•
const handleLevelChange = (value) => {
  console.log('计划级别变更:', value)
  // è¿™é‡Œå¯ä»¥æ ¹æ®çº§åˆ«ç­›é€‰æ•°æ®
}
const handlePeriodChange = (value) => {
  console.log('时间周期变更:', value)
  // è¿™é‡Œå¯ä»¥æ ¹æ®å‘¨æœŸç­›é€‰æ•°æ®
}
const handleDateChange = (value) => {
  console.log('日期变更:', value)
  // è¿™é‡Œå¯ä»¥æ ¹æ®æ—¥æœŸç­›é€‰æ•°æ®
}
const handleAddPlan = () => {
  dialogTitle.value = '新增计划'
  planDialogVisible.value = true
  // é‡ç½®è¡¨å•
  Object.keys(planForm).forEach(key => {
    planForm[key] = ''
  })
  planForm.level = 'personal'
  planForm.period = 'week'
  planForm.priority = 'medium'
}
const handleEditPlan = (plan) => {
  dialogTitle.value = '编辑计划'
  planDialogVisible.value = true
  // å¡«å……表单数据
  Object.keys(planForm).forEach(key => {
    if (key === 'tags') {
      planForm[key] = plan[key].join(', ')
    } else {
      planForm[key] = plan[key]
    }
  })
}
const handleViewDetail = (plan) => {
  ElMessage.info(`查看计划详情: ${plan.title}`)
}
const handleMoreAction = (command) => {
  switch (command) {
    case 'share':
      ElMessage.success('计划已共享')
      break
    case 'copy':
      ElMessage.success('计划已复制')
      break
    case 'delete':
      ElMessageBox.confirm('确定要删除这个计划吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        ElMessage.success('计划已删除')
      })
      break
  }
}
const handleSavePlan = async () => {
  try {
    await planFormRef.value.validate()
    ElMessage.success('计划保存成功')
    planDialogVisible.value = false
  } catch (error) {
    console.log('表单验证失败:', error)
  }
}
const handleDialogClose = () => {
  planFormRef.value?.resetFields()
}
const handleRefresh = () => {
  ElMessage.success('数据已刷新')
}
const handleFilter = () => {
  ElMessage.info('打开筛选面板')
}
const handleExport = () => {
  ElMessage.success('计划已导出')
}
const handleShare = () => {
  ElMessage.success('计划已共享')
}
const getCurrentLevelText = () => {
  const levelMap = {
    personal: '个人计划',
    group: '小组计划',
    department: '部门计划',
    company: '公司计划'
  }
  return levelMap[currentLevel.value] || '个人计划'
}
const getCurrentPeriodText = () => {
  const periodMap = {
    week: '周计划',
    month: '月计划',
    year: '年计划'
  }
  return periodMap[currentPeriod.value] || '周计划'
}
const getPriorityType = (priority) => {
  const typeMap = {
    high: 'danger',
    medium: 'warning',
    low: 'info'
  }
  return typeMap[priority] || 'info'
}
const getPriorityText = (priority) => {
  const textMap = {
    high: '高',
    medium: '中',
    low: '低'
  }
  return textMap[priority] || '中'
}
const getStatusText = (status) => {
  const statusMap = {
    not_started: '未开始',
    in_progress: '进行中',
    completed: '已完成',
    paused: '已暂停'
  }
  return statusMap[status] || '未知'
}
const getProgressColor = (progress) => {
  if (progress >= 80) return '#67C23A'
  if (progress >= 50) return '#E6A23C'
  return '#F56C6C'
}
onMounted(() => {
  console.log('多级计划模板页面已加载')
})
</script>
<style scoped>
.app-container {
  padding: 20px;
  background-color: #f5f5f5;
  min-height: 100vh;
}
.header-actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
  background: white;
  padding: 20px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.left-actions {
  display: flex;
  align-items: center;
}
.right-actions {
  display: flex;
  gap: 10px;
}
.overview-cards {
  margin-bottom: 20px;
}
.overview-card {
  height: 120px;
}
.card-content {
  display: flex;
  align-items: center;
  height: 100%;
}
.card-icon {
  width: 60px;
  height: 60px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-right: 15px;
  font-size: 24px;
  color: white;
}
.card-icon.personal {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.card-icon.group {
  background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
.card-icon.department {
  background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}
.card-icon.company {
  background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
}
.card-info {
  flex: 1;
}
.card-title {
  font-size: 14px;
  color: #666;
  margin-bottom: 5px;
}
.card-number {
  font-size: 24px;
  font-weight: bold;
  color: #333;
  margin-bottom: 10px;
}
.card-progress {
  width: 100%;
}
.plan-content {
  background: white;
  border-radius: 8px;
  overflow: hidden;
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-weight: bold;
  color: #333;
}
.header-actions {
  display: flex;
  gap: 10px;
}
.plan-list {
  padding: 20px 0;
}
.plan-item {
  border: 1px solid #e4e7ed;
  border-radius: 8px;
  margin-bottom: 15px;
  padding: 20px;
  transition: all 0.3s ease;
}
.plan-item:hover {
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  transform: translateY(-2px);
}
.plan-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 15px;
}
.plan-title {
  display: flex;
  align-items: center;
  gap: 10px;
}
.title-text {
  font-size: 16px;
  font-weight: bold;
  color: #333;
}
.plan-actions {
  display: flex;
  gap: 10px;
}
.plan-content {
  margin-bottom: 15px;
}
.plan-description {
  color: #666;
  margin-bottom: 15px;
  line-height: 1.6;
}
.plan-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
  margin-bottom: 15px;
}
.meta-item {
  display: flex;
  align-items: center;
  gap: 5px;
  color: #666;
  font-size: 14px;
}
.plan-progress {
  margin-bottom: 15px;
}
.plan-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
}
.dialog-footer {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}
/* å“åº”式设计 */
@media (max-width: 768px) {
  .header-actions {
    flex-direction: column;
    gap: 15px;
  }
  .left-actions {
    flex-wrap: wrap;
    gap: 10px;
  }
  .plan-meta {
    flex-direction: column;
    gap: 10px;
  }
  .plan-header {
    flex-direction: column;
    align-items: flex-start;
    gap: 10px;
  }
}
</style>
src/views/collaborativeApproval/sealManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,588 @@
<template>
  <div class="app-container">
    <el-card class="box-card">
      <template #header>
        <div class="card-header">
          <span>用印管理与规章制度发布</span>
          <el-button type="primary" @click="showSealApplyDialog = true">
            <el-icon><Plus /></el-icon>
            ç”³è¯·ç”¨å°
          </el-button>
        </div>
      </template>
      <el-tabs v-model="activeTab" type="border-card">
        <!-- ç”¨å°ç”³è¯·ç®¡ç† -->
        <el-tab-pane label="用印申请管理" name="seal">
          <div class="tab-content">
            <el-row :gutter="20" class="mb-20">
              <el-col :span="6">
                <el-input v-model="sealSearchForm.keyword" placeholder="请输入申请标题或申请人" clearable />
              </el-col>
              <el-col :span="4">
                <el-select v-model="sealSearchForm.status" placeholder="审批状态" clearable>
                  <el-option label="待审批" value="pending" />
                  <el-option label="已通过" value="approved" />
                  <el-option label="已拒绝" value="rejected" />
                </el-select>
              </el-col>
              <el-col :span="4">
                <el-button type="primary" @click="searchSealApplications">搜索</el-button>
                <el-button @click="resetSealSearch">重置</el-button>
              </el-col>
            </el-row>
            <el-table :data="sealApplications" style="width: 100%">
              <el-table-column prop="id" label="申请编号" width="120" />
              <el-table-column prop="title" label="申请标题" min-width="200" />
              <el-table-column prop="applicant" label="申请人" width="120" />
              <el-table-column prop="department" label="所属部门" width="150" />
              <el-table-column prop="sealType" label="用印类型" width="120" />
              <el-table-column prop="applyTime" label="申请时间" width="180" />
              <el-table-column prop="status" label="状态" width="100">
                <template #default="scope">
                  <el-tag :type="getStatusType(scope.row.status)">
                    {{ getStatusText(scope.row.status) }}
                  </el-tag>
                </template>
              </el-table-column>
              <el-table-column label="操作" width="200" fixed="right">
                <template #default="scope">
                  <el-button link @click="viewSealDetail(scope.row)">查看</el-button>
                  <el-button
                    v-if="scope.row.status === 'pending'"
                                        link
                    type="primary"
                    @click="approveSeal(scope.row)"
                  >
                    å®¡æ‰¹
                  </el-button>
                  <el-button
                    v-if="scope.row.status === 'pending'"
                                        link
                    type="danger"
                    @click="rejectSeal(scope.row)"
                  >
                    æ‹’绝
                  </el-button>
                </template>
              </el-table-column>
            </el-table>
          </div>
        </el-tab-pane>
        <!-- è§„章制度管理 -->
        <el-tab-pane label="规章制度管理" name="regulations">
          <div class="tab-content">
            <el-row :gutter="20" class="mb-20">
              <el-col :span="6">
                <el-input v-model="regulationSearchForm.keyword" placeholder="请输入制度标题或发布人" clearable />
              </el-col>
              <el-col :span="4">
                <el-select v-model="regulationSearchForm.category" placeholder="制度分类" clearable>
                  <el-option label="人事制度" value="hr" />
                  <el-option label="财务制度" value="finance" />
                  <el-option label="安全制度" value="safety" />
                  <el-option label="技术制度" value="tech" />
                </el-select>
              </el-col>
              <el-col :span="8">
                <el-button type="primary" @click="searchRegulations">搜索</el-button>
                <el-button @click="resetRegulationSearch">重置</el-button>
                <el-button type="success" @click="showRegulationDialog = true">
                  å‘布制度
                </el-button>
              </el-col>
            </el-row>
            <el-table :data="regulations" style="width: 100%">
              <el-table-column prop="id" label="制度编号" width="120" />
              <el-table-column prop="title" label="制度标题" min-width="200" />
              <el-table-column prop="category" label="分类" width="120">
                <template #default="scope">
                  <el-tag>{{ getCategoryText(scope.row.category) }}</el-tag>
                </template>
              </el-table-column>
              <el-table-column prop="version" label="版本" width="80" />
              <el-table-column prop="publisher" label="发布人" width="120" />
              <el-table-column prop="publishTime" label="发布时间" width="180" />
              <el-table-column prop="status" label="状态" width="100">
                <template #default="scope">
                  <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'">
                    {{ scope.row.status === 'active' ? '生效中' : '已废止' }}
                  </el-tag>
                </template>
              </el-table-column>
              <el-table-column prop="readCount" label="已读人数" width="100" />
              <el-table-column label="操作" width="250" fixed="right">
                <template #default="scope">
                  <el-button link @click="viewRegulation(scope.row)">查看</el-button>
                  <el-button link type="primary" @click="editRegulation(scope.row)">编辑</el-button>
                  <el-button link type="success" @click="viewVersionHistory(scope.row)">版本历史</el-button>
                  <el-button link type="warning" @click="viewReadStatus(scope.row)">阅读状态</el-button>
                </template>
              </el-table-column>
            </el-table>
          </div>
        </el-tab-pane>
      </el-tabs>
    </el-card>
    <!-- ç”¨å°ç”³è¯·å¯¹è¯æ¡† -->
    <el-dialog v-model="showSealApplyDialog" title="申请用印" width="600px">
      <el-form :model="sealForm" :rules="sealRules" ref="sealFormRef" label-width="100px">
        <el-form-item label="申请标题" prop="title">
          <el-input v-model="sealForm.title" placeholder="请输入申请标题" />
        </el-form-item>
        <el-form-item label="用印类型" prop="sealType">
          <el-select v-model="sealForm.sealType" placeholder="请选择用印类型" style="width: 100%">
            <el-option label="公章" value="official" />
            <el-option label="合同专用章" value="contract" />
            <el-option label="财务专用章" value="finance" />
            <el-option label="法人章" value="legal" />
          </el-select>
        </el-form-item>
        <el-form-item label="申请原因" prop="reason">
          <el-input v-model="sealForm.reason" type="textarea" :rows="4" placeholder="请详细说明用印原因" />
        </el-form-item>
        <el-form-item label="紧急程度" prop="urgency">
          <el-radio-group v-model="sealForm.urgency">
            <el-radio label="normal">普通</el-radio>
            <el-radio label="urgent">紧急</el-radio>
            <el-radio label="very-urgent">特急</el-radio>
          </el-radio-group>
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="showSealApplyDialog = false">取消</el-button>
          <el-button type="primary" @click="submitSealApplication">提交申请</el-button>
        </span>
      </template>
    </el-dialog>
    <!-- è§„章制度发布对话框 -->
    <el-dialog v-model="showRegulationDialog" title="发布规章制度" width="800px">
      <el-form :model="regulationForm" :rules="regulationRules" ref="regulationFormRef" label-width="100px">
        <el-form-item label="制度标题" prop="title">
          <el-input v-model="regulationForm.title" placeholder="请输入制度标题" />
        </el-form-item>
        <el-form-item label="制度分类" prop="category">
          <el-select v-model="regulationForm.category" placeholder="请选择制度分类" style="width: 100%">
            <el-option label="人事制度" value="hr" />
            <el-option label="财务制度" value="finance" />
            <el-option label="安全制度" value="safety" />
            <el-option label="技术制度" value="tech" />
          </el-select>
        </el-form-item>
        <el-form-item label="制度内容" prop="content">
          <el-input v-model="regulationForm.content" type="textarea" :rows="10" placeholder="请输入制度详细内容" />
        </el-form-item>
        <el-form-item label="生效时间" prop="effectiveTime">
          <el-date-picker v-model="regulationForm.effectiveTime" type="datetime" placeholder="选择生效时间" style="width: 100%" />
        </el-form-item>
        <el-form-item label="适用范围" prop="scope">
          <el-checkbox-group v-model="regulationForm.scope">
            <el-checkbox label="all">全体员工</el-checkbox>
            <el-checkbox label="manager">管理层</el-checkbox>
            <el-checkbox label="hr">人事部门</el-checkbox>
            <el-checkbox label="finance">财务部门</el-checkbox>
            <el-checkbox label="tech">技术部门</el-checkbox>
          </el-checkbox-group>
        </el-form-item>
        <el-form-item label="是否需要确认" prop="requireConfirm">
          <el-switch v-model="regulationForm.requireConfirm" />
          <span class="ml-10">开启后员工需要阅读确认</span>
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="showRegulationDialog = false">取消</el-button>
          <el-button type="primary" @click="submitRegulation">发布制度</el-button>
        </span>
      </template>
    </el-dialog>
    <!-- ç”¨å°è¯¦æƒ…对话框 -->
    <el-dialog v-model="showSealDetailDialog" title="用印申请详情" width="700px">
      <div v-if="currentSealDetail" class="mb10">
        <el-descriptions :column="2" border>
          <el-descriptions-item label="申请编号">{{ currentSealDetail.id }}</el-descriptions-item>
          <el-descriptions-item label="申请标题">{{ currentSealDetail.title }}</el-descriptions-item>
          <el-descriptions-item label="申请人">{{ currentSealDetail.applicant }}</el-descriptions-item>
          <el-descriptions-item label="所属部门">{{ currentSealDetail.department }}</el-descriptions-item>
          <el-descriptions-item label="用印类型">{{ currentSealDetail.sealType }}</el-descriptions-item>
          <el-descriptions-item label="申请时间">{{ currentSealDetail.applyTime }}</el-descriptions-item>
          <el-descriptions-item label="状态">
            <el-tag :type="getStatusType(currentSealDetail.status)">
              {{ getStatusText(currentSealDetail.status) }}
            </el-tag>
          </el-descriptions-item>
          <el-descriptions-item label="申请原因" :span="2">{{ currentSealDetail.reason }}</el-descriptions-item>
        </el-descriptions>
      </div>
    </el-dialog>
    <!-- è§„章制度详情对话框 -->
    <el-dialog v-model="showRegulationDetailDialog" title="规章制度详情" width="800px">
      <div v-if="currentRegulationDetail">
        <el-descriptions :column="2" border>
          <el-descriptions-item label="制度编号">{{ currentRegulationDetail.id }}</el-descriptions-item>
          <el-descriptions-item label="制度标题">{{ currentRegulationDetail.title }}</el-descriptions-item>
          <el-descriptions-item label="分类">{{ getCategoryText(currentRegulationDetail.category) }}</el-descriptions-item>
          <el-descriptions-item label="版本">{{ currentRegulationDetail.version }}</el-descriptions-item>
          <el-descriptions-item label="发布人">{{ currentRegulationDetail.publisher }}</el-descriptions-item>
          <el-descriptions-item label="发布时间">{{ currentRegulationDetail.publishTime }}</el-descriptions-item>
        </el-descriptions>
        <div class="mt-20">
          <h4>制度内容</h4>
          <div class="regulation-content">{{ currentRegulationDetail.content }}</div>
        </div>
      </div>
    </el-dialog>
    <!-- ç‰ˆæœ¬åŽ†å²å¯¹è¯æ¡† -->
    <el-dialog v-model="showVersionHistoryDialog" title="版本历史" width="800px">
      <el-table :data="versionHistory" style="width: 100%;margin-bottom: 10px">
        <el-table-column prop="version" label="版本号" width="100" />
        <el-table-column prop="updateTime" label="更新时间" width="180" />
        <el-table-column prop="updater" label="更新人" width="120" />
        <el-table-column prop="changeLog" label="变更说明" />
      </el-table>
    </el-dialog>
    <!-- é˜…读状态对话框 -->
    <el-dialog v-model="showReadStatusDialog" title="阅读状态" width="800px">
      <el-table :data="readStatusList" style="width: 100%;margin-bottom: 10px">
        <el-table-column prop="employee" label="员工姓名" width="120" />
        <el-table-column prop="department" label="所属部门" width="150" />
        <el-table-column prop="readTime" label="阅读时间" width="180" />
        <el-table-column prop="confirmTime" label="确认时间" width="180" />
        <el-table-column prop="status" label="状态" width="100">
          <template #default="scope">
            <el-tag :type="scope.row.status === 'confirmed' ? 'success' : 'warning'">
              {{ scope.row.status === 'confirmed' ? '已确认' : '未确认' }}
            </el-tag>
          </template>
        </el-table-column>
      </el-table>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
// å“åº”式数据
const activeTab = ref('seal')
// ç”¨å°ç”³è¯·ç›¸å…³
const showSealApplyDialog = ref(false)
const showSealDetailDialog = ref(false)
const currentSealDetail = ref(null)
const sealFormRef = ref()
const sealForm = reactive({
  title: '',
  sealType: '',
  reason: '',
  urgency: 'normal'
})
const sealRules = {
  title: [{ required: true, message: '请输入申请标题', trigger: 'blur' }],
  sealType: [{ required: true, message: '请选择用印类型', trigger: 'change' }],
  reason: [{ required: true, message: '请输入申请原因', trigger: 'blur' }]
}
const sealSearchForm = reactive({
  keyword: '',
  status: ''
})
// è§„章制度相关
const showRegulationDialog = ref(false)
const showRegulationDetailDialog = ref(false)
const showVersionHistoryDialog = ref(false)
const showReadStatusDialog = ref(false)
const currentRegulationDetail = ref(null)
const regulationFormRef = ref()
const regulationForm = reactive({
  title: '',
  category: '',
  content: '',
  effectiveTime: '',
  scope: [],
  requireConfirm: true
})
const regulationRules = {
  title: [{ required: true, message: '请输入制度标题', trigger: 'blur' }],
  category: [{ required: true, message: '请选择制度分类', trigger: 'change' }],
  content: [{ required: true, message: '请输入制度内容', trigger: 'blur' }],
  effectiveTime: [{ required: true, message: '请选择生效时间', trigger: 'change' }],
  scope: [{ required: true, message: '请选择适用范围', trigger: 'change' }]
}
const regulationSearchForm = reactive({
  keyword: '',
  category: ''
})
// å‡æ•°æ®
const sealApplications = ref([
  {
    id: 'SEAL001',
    title: '合同用印申请',
    applicant: '陈志强',
    department: '销售部',
    sealType: '合同专用章',
    applyTime: '2025-01-15 10:30:00',
    status: 'pending',
    reason: '客户合同需要盖章'
  },
  {
    id: 'SEAL002',
    title: '财务报告用印',
    applicant: '王建国',
    department: '财务部',
    sealType: '财务专用章',
    applyTime: '2025-01-14 14:20:00',
    status: 'approved',
    reason: '季度财务报告需要盖章'
  },
  {
    id: 'SEAL003',
    title: '公司章程用印',
    applicant: '孙明华',
    department: '法务部',
    sealType: '公章',
    applyTime: '2025-01-13 09:15:00',
    status: 'rejected',
    reason: '公司章程修改需要盖章'
  }
])
const regulations = ref([
  {
    id: 'REG001',
    title: '员工考勤管理制度',
    category: 'hr',
    version: 'v2.1',
    publisher: '人事部',
    publishTime: '2025-01-10 09:00:00',
    status: 'active',
    readCount: 45,
    content: '为规范员工考勤管理,提高工作效率,特制定本制度...'
  },
  {
    id: 'REG002',
    title: '财务报销制度',
    category: 'finance',
    version: 'v1.5',
    publisher: '财务部',
    publishTime: '2025-01-08 14:30:00',
    status: 'active',
    readCount: 38,
    content: '为规范财务报销流程,加强财务管理,特制定本制度...'
  },
  {
    id: 'REG003',
    title: '安全生产管理制度',
    category: 'safety',
    version: 'v3.0',
    publisher: '安全部',
    publishTime: '2025-01-05 16:00:00',
    status: 'active',
    readCount: 52,
    content: '为确保员工人身安全,预防安全事故发生,特制定本制度...'
  }
])
const versionHistory = ref([
  { version: 'v2.1', updateTime: '2025-01-10 09:00:00', updater: '人事部', changeLog: '更新考勤时间规定' },
  { version: 'v2.0', updateTime: '2023-12-15 10:30:00', updater: '人事部', changeLog: '新增加班管理规定' },
  { version: 'v1.0', updateTime: '2023-11-01 14:00:00', updater: '人事部', changeLog: '首次发布' }
])
const readStatusList = ref([
  { employee: '陈志强', department: '销售部', readTime: '2025-01-11 10:30:00', confirmTime: '2025-01-11 10:35:00', status: 'confirmed' },
  { employee: '刘雅婷', department: '技术部', readTime: '2025-01-11 14:20:00', confirmTime: '', status: 'unconfirmed' },
  { employee: '王建国', department: '财务部', readTime: '2025-01-12 09:15:00', confirmTime: '2025-01-12 09:20:00', status: 'confirmed' }
])
// æ–¹æ³•
const getStatusType = (status) => {
  const statusMap = {
    pending: 'warning',
    approved: 'success',
    rejected: 'danger'
  }
  return statusMap[status] || 'info'
}
const getStatusText = (status) => {
  const statusMap = {
    pending: '待审批',
    approved: '已通过',
    rejected: '已拒绝'
  }
  return statusMap[status] || '未知'
}
const getCategoryText = (category) => {
  const categoryMap = {
    hr: '人事制度',
    finance: '财务制度',
    safety: '安全制度',
    tech: '技术制度'
  }
  return categoryMap[category] || '未知'
}
const searchSealApplications = () => {
  ElMessage.success('搜索完成')
}
const resetSealSearch = () => {
  sealSearchForm.keyword = ''
  sealSearchForm.status = ''
  searchSealApplications()
}
const searchRegulations = () => {
  ElMessage.success('搜索完成')
}
const resetRegulationSearch = () => {
  regulationSearchForm.keyword = ''
  regulationSearchForm.category = ''
  searchRegulations()
}
const submitSealApplication = async () => {
  try {
    await sealFormRef.value.validate()
    ElMessage.success('申请提交成功')
    showSealApplyDialog.value = false
    Object.assign(sealForm, {
      title: '',
      sealType: '',
      reason: '',
      urgency: 'normal'
    })
  } catch (error) {
    ElMessage.error('请完善申请信息')
  }
}
const submitRegulation = async () => {
  try {
    await regulationFormRef.value.validate()
    ElMessage.success('制度发布成功')
    showRegulationDialog.value = false
    Object.assign(regulationForm, {
      title: '',
      category: '',
      content: '',
      effectiveTime: '',
      scope: [],
      requireConfirm: true
    })
  } catch (error) {
    ElMessage.error('请完善制度信息')
  }
}
const viewSealDetail = (row) => {
  currentSealDetail.value = row
  showSealDetailDialog.value = true
}
const approveSeal = (row) => {
  ElMessageBox.confirm('确认通过该用印申请?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    row.status = 'approved'
    ElMessage.success('审批通过')
  })
}
const rejectSeal = (row) => {
  ElMessageBox.prompt('请输入拒绝原因', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    inputPattern: /\S+/,
    inputErrorMessage: '拒绝原因不能为空'
  }).then(({ value }) => {
    row.status = 'rejected'
    ElMessage.success('已拒绝申请')
  })
}
const viewRegulation = (row) => {
  currentRegulationDetail.value = row
  showRegulationDetailDialog.value = true
}
const editRegulation = (row) => {
  ElMessage.info('编辑功能开发中...')
}
const viewVersionHistory = (row) => {
  showVersionHistoryDialog.value = true
}
const viewReadStatus = (row) => {
  showReadStatusDialog.value = true
}
onMounted(() => {
  // åˆå§‹åŒ–
})
</script>
<style scoped>
.app-container {
  padding: 20px;
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.tab-content {
  padding: 20px 0;
}
.mb-20 {
  margin-bottom: 20px;
}
.mt-20 {
  margin-top: 20px;
}
.ml-10 {
  margin-left: 10px;
}
.regulation-content {
  background-color: #f5f5f5;
  padding: 15px;
  border-radius: 4px;
  line-height: 1.6;
  white-space: pre-wrap;
}
.dialog-footer {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}
</style>
src/views/collaborativeApproval/warningSystem/index.vue
@@ -96,7 +96,7 @@
          levelText: '红色预警',
          status: 'pending',
          statusText: '待处理',
          responsible: '张经理',
          responsible: '陈志强',
          description: 'A项目预算执行率已达95%,预计将超出预算范围。',
          impact: '影响项目整体财务指标,可能导致项目亏损',
          suggestions: '暂停非必要支出,优化资源配置,申请预算调整'
@@ -148,7 +148,7 @@
          levelText: '红色预警',
          status: 'pending',
          statusText: '待处理',
          responsible: '陈总监',
          responsible: '陈志强',
          description: '产品D在客户现场出现质量问题。',
          impact: '影响客户满意度,可能造成经济损失',
          suggestions: '立即召回问题产品,分析原因,制定改进措施'
src/views/equipmentManagement/brand/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,217 @@
<template>
  <div class="app-container">
    <el-form :model="filters" :inline="true">
      <el-form-item label="品牌名称/国家">
        <el-input
          v-model="filters.name"
          style="width: 240px"
          placeholder="请输入关键词"
          clearable
          prefix-icon="Search"
          @change="getTableData"
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="getTableData">搜索</el-button>
        <el-button @click="resetFilters">重置</el-button>
      </el-form-item>
    </el-form>
    <div class="table_list">
      <div class="actions">
        <div></div>
        <div>
          <el-button type="primary" @click="openAdd" icon="Plus"> æ–°å¢ž </el-button>
          <el-button
            type="danger"
            icon="Delete"
            :disabled="multipleSelection.length <= 0"
            @click="handleBatchDelete"
          >批量删除</el-button>
        </div>
      </div>
      <PIMTable
        rowKey="id"
        isSelection
        :column="columns"
        :tableData="dataList"
        :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
          total: pagination.total,
        }"
        @selection-change="handleSelectionChange"
        @pagination="changePage"
      >
      </PIMTable>
    </div>
    <el-dialog v-model="visible" :title="dialogTitle" width="520px" destroy-on-close>
      <el-form :model="form" ref="formRef" :rules="rules" label-width="90px">
        <el-form-item label="品牌名称" prop="name">
          <el-input v-model="form.name" placeholder="请输入品牌名称" />
        </el-form-item>
        <el-form-item label="所属国家" prop="country">
          <el-input v-model="form.country" placeholder="请输入国家/地区" />
        </el-form-item>
        <el-form-item label="描述" prop="description">
          <el-input v-model="form.description" type="textarea" :rows="3" placeholder="可填写品牌简介" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="visible = false">取消</el-button>
        <el-button type="primary" @click="handleSubmit">确定</el-button>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, getCurrentInstance, onMounted } from 'vue'
import { ElMessageBox, ElMessage } from 'element-plus'
import { usePaginationApi } from '@/hooks/usePaginationApi'
import { getBrandPage, addBrand, editBrand, delBrand } from '@/api/equipmentManagement/brand'
defineOptions({ name: '设备品牌管理' })
const { proxy } = getCurrentInstance()
const multipleSelection = ref([])
const formRef = ref()
const visible = ref(false)
const dialogTitle = ref('新增品牌')
const form = ref({ id: undefined, name: '', country: '', description: '' })
const rules = {
  name: [{ required: true, message: '请输入品牌名称', trigger: 'blur' }],
  country: [{ required: true, message: '请输入所属国家', trigger: 'blur' }]
}
const {
  filters,
  columns,
  dataList,
  pagination,
  getTableData,
  resetFilters,
  onCurrentChange,
} = usePaginationApi(
  getBrandPage,
  { name: undefined },
  [
    { label: '品牌名称', align: 'center', prop: 'name' },
    { label: '所属国家', align: 'center', prop: 'country' },
    { label: '描述', align: 'center', prop: 'description' },
    { label: '创建时间', align: 'center', prop: 'createdAt' },
    {
      dataType: 'action',
      label: '操作',
      align: 'center',
      fixed: 'right',
      width: 140,
      operation: [
        {
          name: '编辑',
          type: 'text',
          clickFun: (row) => openEdit(row),
        },
        {
          name: '删除',
          type: 'text',
          clickFun: (row) => handleDelete(row.id),
        }
      ]
    }
  ]
)
const handleSelectionChange = (list) => {
  multipleSelection.value = list
}
const changePage = ({ page, limit }) => {
  pagination.currentPage = page
  pagination.pageSize = limit
  onCurrentChange(page)
}
function resetForm() {
  form.value = { id: undefined, name: '', country: '', description: '' }
}
function openAdd() {
  resetForm()
  dialogTitle.value = '新增品牌'
  visible.value = true
}
function openEdit(row) {
  form.value = { id: row.id, name: row.name, country: row.country, description: row.description }
  dialogTitle.value = '编辑品牌'
  visible.value = true
}
function handleSubmit() {
  formRef.value.validate(async (valid) => {
    if (!valid) return
    const isEdit = Boolean(form.value.id)
    const api = isEdit ? editBrand : addBrand
    const { code, msg } = await api({ ...form.value })
    if (code === 200) {
      ElMessage.success(isEdit ? '修改成功' : '新增成功')
      visible.value = false
      getTableData()
    } else {
      ElMessage.error(msg || '操作失败')
    }
  })
}
function handleDelete(id) {
  ElMessageBox.confirm('此操作将永久删除该品牌, æ˜¯å¦ç»§ç»­?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  }).then(async () => {
    const { code } = await delBrand(id)
    if (code === 200) {
      ElMessage.success('删除成功')
      getTableData()
    }
  })
}
function handleBatchDelete() {
  if (multipleSelection.value.length === 0) return
  ElMessageBox.confirm('将删除选中的品牌,是否继续?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  }).then(async () => {
    const ids = multipleSelection.value.map((i) => i.id)
    const { code } = await delBrand(ids)
    if (code === 200) {
      ElMessage.success('删除成功')
      getTableData()
    }
  })
}
onMounted(() => {
  getTableData()
})
</script>
<style scoped lang="scss">
.table_list { margin-top: unset; }
.actions {
  display: flex;
  justify-content: space-between;
  margin-bottom: 10px;
}
</style>
src/views/equipmentManagement/ledger/Form.vue
@@ -12,13 +12,28 @@
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="设备品牌" prop="deviceBrand">
          <el-input v-model="form.deviceBrand" placeholder="请输入设备品牌" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="供应商" prop="supplierName">
          <el-input v-model="form.supplierName" placeholder="请输入供应商" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="存放位置" prop="storageLocation">
          <el-input v-model="form.storageLocation" placeholder="请输入存放位置" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="单位" prop="unit">
          <el-input v-model="form.unit" placeholder="请输入单位" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="启用折旧" prop="enableDepreciation">
          <el-switch v-model="form.enableDepreciation" :active-value="true" :inactive-value="false" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
@@ -133,7 +148,10 @@
const { form, resetForm } = useFormData({
  deviceName: undefined, // è®¾å¤‡åç§°
  deviceModel: undefined, // è§„格型号
  deviceBrand: undefined, // è®¾å¤‡å“ç‰Œ
  supplierName: undefined, // ä¾›åº”商
  storageLocation: undefined, // å­˜æ”¾ä½ç½®
  enableDepreciation: false, // æ˜¯å¦å¯ç”¨æŠ˜æ—§
  unit: undefined, // å•位
  number: undefined, // æ•°é‡
  taxIncludingPriceUnit: undefined, // å«ç¨Žå•ä»·
@@ -152,7 +170,10 @@
  if (code == 200) {
    form.deviceName = data.deviceName;
    form.deviceModel = data.deviceModel;
    form.deviceBrand = data.deviceBrand;
    form.supplierName = data.supplierName;
    form.storageLocation = data.storageLocation;
    form.enableDepreciation = data.enableDepreciation;
    form.unit = data.unit;
    form.number = data.number;
    form.taxIncludingPriceUnit = data.taxIncludingPriceUnit;
src/views/equipmentManagement/ledger/index.vue
@@ -146,6 +146,11 @@
      prop: "deviceModel",
    },
    {
      label: "设备品牌",
      align: "center",
      prop: "deviceBrand",
    },
    {
      label: "供应商",
      align: "center",
      prop: "supplierName",
@@ -154,6 +159,11 @@
      label: "单位",
      align: "center",
      prop: "unit",
    },
    {
      label: "存放位置",
      align: "center",
      prop: "storageLocation",
    },
    {
      label: "数量",
@@ -181,6 +191,12 @@
      prop: "unTaxIncludingPriceTotal",
    },
    {
      label: "启用折旧",
      align: "center",
      prop: "enableDepreciation",
      formatData: (v) => (v ? "是" : "否"),
    },
    {
      label: "录入人",
      align: "center",
      prop: "createUser",
src/views/procurementManagement/arrivalManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,189 @@
<template>
  <div class="app-container">
    <el-card class="search-card" shadow="never">
      <el-form :model="searchForm" :inline="true">
        <el-form-item label="采购订单号:">
          <el-input v-model="searchForm.orderNo" placeholder="请输入订单号" clearable />
        </el-form-item>
        <el-form-item label="供应商名称:">
          <el-input v-model="searchForm.supplierName" placeholder="请输入供应商名称" clearable />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleSearch">搜索</el-button>
          <el-button @click="resetSearch">重置</el-button>
        </el-form-item>
      </el-form>
    </el-card>
    <el-card class="table-card" shadow="never">
      <div class="table-header">
        <el-button type="primary" @click="openDialog('add')">新增到货</el-button>
        <el-button type="success" @click="handleBatchReceive">批量收货</el-button>
        <el-button type="danger" @click="handleBatchDelete">批量删除</el-button>
      </div>
      <el-table :data="tableData" border v-loading="loading" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="到货单号" prop="arrivalNo" width="180" />
        <el-table-column label="采购订单号" prop="orderNo" width="180" />
        <el-table-column label="供应商名称" prop="supplierName" />
        <el-table-column label="到货状态" prop="status" width="100">
          <template #default="{ row }">
            <el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="到货数量" prop="arrivalQuantity" width="100" />
        <el-table-column label="到货时间" prop="arrivalTime" width="180" />
        <el-table-column label="操作" width="200" align="center">
          <template #default="{ row }">
            <el-button type="primary" link @click="openDialog('edit', row)">编辑</el-button>
            <el-button type="success" link @click="handleReceive(row)">收货</el-button>
            <el-button type="danger" link @click="handleDelete(row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
    </el-card>
    <el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? '新增到货' : '编辑到货'" width="600px">
      <el-form :model="formData" label-width="120px">
        <el-form-item label="采购订单号">
          <el-select v-model="formData.orderNo" placeholder="请选择采购订单" style="width: 100%">
            <el-option label="PO20241201001" value="PO20241201001" />
            <el-option label="PO20241201002" value="PO20241201002" />
          </el-select>
        </el-form-item>
        <el-form-item label="供应商名称">
          <el-input v-model="formData.supplierName" placeholder="供应商名称" readonly />
        </el-form-item>
        <el-form-item label="备注">
          <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请输入备注信息" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="dialogVisible = false">取消</el-button>
        <el-button type="primary" @click="handleSubmit">确定</el-button>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
const loading = ref(false)
const dialogVisible = ref(false)
const dialogType = ref('add')
const selectedRows = ref([])
const searchForm = reactive({
  orderNo: '',
  supplierName: ''
})
const formData = reactive({
  orderNo: '',
  supplierName: '',
  remark: ''
})
const mockData = [
  {
    id: 1,
    arrivalNo: 'AR20241201001',
    orderNo: 'PO20241201001',
    supplierName: '供应商A',
    status: 'received',
    arrivalQuantity: 250,
    arrivalTime: '2025-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: '2025-12-01 18:30',
    content: '新增采购订单 PO20241201004',
    type: 'primary'
  },
  {
    time: '2025-12-01 17:45',
    content: '完成质检单 QI20241201002',
    type: 'success'
  },
  {
    time: '2025-12-01 16:20',
    content: '到货单 AR20241201003 å·²æ”¶è´§',
    type: 'success'
  },
  {
    time: '2025-12-01 15:15',
    content: '价格调整:商品B ä»Ž Â¥80 è°ƒæ•´ä¸º Â¥75',
    type: 'warning'
  },
  {
    time: '2025-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: '2025-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: '2025-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: '2025-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: '2025-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: '2025-12-01 17:30:00',
    returnReason: '质量问题',
    remark: '商品存在质量问题'
  }
]
const tableData = ref([...mockData])
const getReturnTypeText = (type) => {
  const typeMap = { purchase: '采购退货', quality: '质检退货' }
  return typeMap[type] || '未知'
}
const getStatusType = (status) => {
  const statusMap = { pending: 'warning', approved: 'success', returned: 'info' }
  return statusMap[status] || 'info'
}
const getStatusText = (status) => {
  const statusMap = { pending: '待审核', approved: '已审核', returned: '已退货' }
  return statusMap[status] || '未知'
}
const handleSearch = () => {
  loading.value = true
  setTimeout(() => { loading.value = false }, 500)
}
const resetSearch = () => {
  Object.assign(searchForm, { returnNo: '', returnType: '' })
}
const openDialog = (type, row = {}) => {
  dialogType.value = type
  if (type === 'edit' && row.id) {
    Object.assign(formData, {
      returnType: row.returnType,
      relatedNo: row.relatedNo,
      supplierName: row.supplierName,
      returnReason: row.returnReason,
      remark: row.remark
    })
  } else {
    Object.assign(formData, {
      returnType: '',
      relatedNo: '',
      supplierName: '',
      returnReason: '',
      remark: ''
    })
  }
  dialogVisible.value = true
}
const handleSubmit = () => {
  if (dialogType.value === 'add') {
    const newReturn = {
      id: Date.now(),
      returnNo: `RT${Date.now()}`,
      relatedNo: formData.relatedNo,
      returnType: formData.returnType,
      supplierName: formData.supplierName,
      status: 'pending',
      returnAmount: 0,
      createTime: new Date().toLocaleString(),
      returnReason: formData.returnReason,
      remark: formData.remark
    }
    tableData.value.unshift(newReturn)
    ElMessage.success('新增成功')
  }
  dialogVisible.value = false
}
const handleApprove = (row) => {
  row.status = 'approved'
  ElMessage.success('审核通过')
}
const handleDelete = (row) => {
  ElMessageBox.confirm('确定要删除这条记录吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    const index = tableData.value.findIndex(item => item.id === row.id)
    if (index !== -1) {
      tableData.value.splice(index, 1)
      ElMessage.success('删除成功')
    }
  })
}
const handleBatchApprove = () => {
  ElMessage.success('批量审核成功')
}
const handleBatchDelete = () => {
  ElMessage.success('批量删除成功')
}
const handleSelectionChange = (rows) => {
  selectedRows.value = rows
}
</script>
<style scoped>
.app-container { padding: 20px; }
.search-card { margin-bottom: 20px; }
.table-card { margin-bottom: 20px; }
.table-header { margin-bottom: 20px; }
</style>
src/views/productManagement/productIdentifier/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,708 @@
<template>
  <div class="app-container">
    <el-card class="box-card">
             <!-- æœç´¢åŒºåŸŸ -->
       <el-row :gutter="20" class="search-row">
         <el-col :span="6">
           <el-input
             v-model="searchForm.productName"
             placeholder="请输入产品名称"
             clearable
             @keyup.enter="handleSearch"
           >
             <template #prefix>
               <el-icon><Search /></el-icon>
             </template>
           </el-input>
         </el-col>
         <el-col :span="6">
           <el-select v-model="searchForm.identifierType" placeholder="请选择标识类型" clearable>
             <el-option label="二维码" value="二维码"></el-option>
             <el-option label="防伪码" value="防伪码"></el-option>
           </el-select>
         </el-col>
         <el-col :span="6">
           <el-select v-model="searchForm.status" placeholder="请选择状态" clearable>
             <el-option label="已生成" value="已生成"></el-option>
             <el-option label="已分配" value="已分配"></el-option>
             <el-option label="已使用" value="已使用"></el-option>
             <el-option label="已作废" value="已作废"></el-option>
           </el-select>
         </el-col>
         <el-col :span="6">
           <el-button type="primary" @click="handleSearch">搜索</el-button>
           <el-button @click="resetSearch">重置</el-button>
           <el-button style="float: right;" type="primary" @click="handleAdd">
             æ–°å¢žæ ‡è¯†
           </el-button>
         </el-col>
       </el-row>
      <!-- äº§å“æ ‡è¯†åˆ—表 -->
      <el-table
        :data="filteredList"
        style="width: 100%"
        v-loading="loading"
        border
        stripe
        height="calc(100vh - 22em)"
      >
        <el-table-column prop="id" label="ID" width="80" align="center"/>
        <el-table-column prop="productName" label="产品名称" width="150" />
        <el-table-column prop="productCode" label="产品编码" width="120" />
        <el-table-column prop="batchNo" label="批次号" width="120" />
        <el-table-column prop="identifierType" label="标识类型" width="100">
          <template #default="scope">
            <el-tag :type="getIdentifierTypeType(scope.row.identifierType)">
              {{ scope.row.identifierType }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="identifierCode" label="标识码" />
        <el-table-column prop="status" label="状态" width="100">
          <template #default="scope">
            <el-tag :type="getStatusType(scope.row.status)">
              {{ scope.row.status }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="generateTime" label="生成时间" width="160" />
        <el-table-column label="操作" fixed="right" align="center" width="280">
          <template #default="scope">
            <el-button link type="primary" @click="handleView(scope.row)">查看</el-button>
            <el-button link type="primary" @click="handleEdit(scope.row)">编辑</el-button>
            <el-button link type="success" @click="generateQRCode(scope.row)">生成二维码</el-button>
            <el-button link type="primary" @click="handleExport(scope.row)">导出</el-button>
            <el-button link type="primary" @click="handleReassign(scope.row)" v-if="scope.row.status === '已分配'">重新分配</el-button>
            <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- åˆ†é¡µ -->
      <pagination
        :total="pagination.total"
        layout="total, sizes, prev, pager, next, jumper"
        :page="pagination.currentPage"
        :limit="pagination.pageSize"
        @pagination="handleCurrentChange"
      />
    </el-card>
    <!-- æ–°å¢ž/编辑对话框 -->
    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="700px">
      <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="产品名称" prop="productName">
              <el-input v-model="form.productName" placeholder="请输入产品名称"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="产品编码" prop="productCode">
              <el-input v-model="form.productCode" placeholder="请输入产品编码"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="批次号" prop="batchNo">
              <el-input v-model="form.batchNo" placeholder="请输入批次号"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="标识类型" prop="identifierType">
              <el-select v-model="form.identifierType" placeholder="请选择标识类型" style="width: 100%">
                <el-option label="二维码" value="二维码"></el-option>
                <el-option label="防伪码" value="防伪码"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="生成数量" prop="quantity">
              <el-input-number v-model="form.quantity" :min="1" :max="10000" style="width: 100%"></el-input-number>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="状态" prop="status">
              <el-select v-model="form.status" placeholder="请选择状态" style="width: 100%">
                <el-option label="已生成" value="已生成"></el-option>
                <el-option label="已分配" value="已分配"></el-option>
                <el-option label="已使用" value="已使用"></el-option>
                <el-option label="已作废" value="已作废"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="备注" prop="remark">
              <el-input type="textarea" v-model="form.remark" placeholder="请输入备注信息" rows="3"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="dialogVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="handleSubmit">ç¡® å®š</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- æ ‡è¯†ç”Ÿæˆå¯¹è¯æ¡† -->
    <el-dialog v-model="generateDialogVisible" title="标识生成" width="500px">
      <el-form label-width="100px">
        <el-form-item label="产品名称">
          <span>{{ currentProduct.productName }}</span>
        </el-form-item>
        <el-form-item label="产品编码">
          <span>{{ currentProduct.productCode }}</span>
        </el-form-item>
        <el-form-item label="批次号">
          <span>{{ currentProduct.batchNo }}</span>
        </el-form-item>
        <el-form-item label="标识类型">
          <span>{{ currentProduct.identifierType }}</span>
        </el-form-item>
        <el-form-item label="生成数量" prop="generateQuantity">
          <el-input-number v-model="generateQuantity" :min="1" :max="10000" style="width: 100%"></el-input-number>
        </el-form-item>
        <el-form-item label="编码规则" prop="codeRule">
          <el-select v-model="codeRule" placeholder="请选择编码规则" style="width: 100%">
            <el-option label="产品编码+批次号+序号" value="产品编码+批次号+序号"></el-option>
            <el-option label="时间戳+随机数" value="时间戳+随机数"></el-option>
            <el-option label="自定义规则" value="自定义规则"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="自定义前缀" prop="customPrefix" v-if="codeRule === '自定义规则'">
          <el-input v-model="customPrefix" placeholder="请输入自定义前缀"></el-input>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="generateDialogVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="generateIdentifiers">生 æˆ</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- é‡æ–°åˆ†é…å¯¹è¯æ¡† -->
    <el-dialog v-model="reassignDialogVisible" title="重新分配标识" width="500px">
      <el-form label-width="100px">
        <el-form-item label="产品名称">
          <span>{{ currentProduct.productName }}</span>
        </el-form-item>
        <el-form-item label="标识码">
          <span>{{ currentProduct.identifierCode }}</span>
        </el-form-item>
        <el-form-item label="新批次号" prop="newBatchNo">
          <el-input v-model="newBatchNo" placeholder="请输入新批次号"></el-input>
        </el-form-item>
        <el-form-item label="分配原因" prop="reassignReason">
          <el-input type="textarea" v-model="reassignReason" rows="3" placeholder="请输入重新分配原因"></el-input>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="reassignDialogVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="saveReassign">ç¡® å®š</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- äºŒç»´ç é¢„览对话框 -->
    <el-dialog v-model="qrCodeDialogVisible" title="二维码预览" width="500px" center>
      <div class="qr-preview-container">
        <div v-if="qrCodeUrl" class="qr-image-container">
          <img :src="qrCodeUrl" alt="二维码" class="qr-image" />
          <div class="qr-info">
            <p><strong>产品名称:</strong>{{ currentQRProduct.productName }}</p>
            <p><strong>产品编码:</strong>{{ currentQRProduct.productCode }}</p>
            <p><strong>批次号:</strong>{{ currentQRProduct.batchNo }}</p>
            <p><strong>标识码:</strong>{{ currentQRProduct.identifierCode }}</p>
            <p><strong>标识类型:</strong>{{ currentQRProduct.identifierType }}</p>
          </div>
        </div>
        <div v-else class="qr-loading">
          <el-icon class="is-loading"><Loading /></el-icon>
          <p>正在生成二维码...</p>
        </div>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="qrCodeDialogVisible = false">关闭</el-button>
          <el-button
            v-if="qrCodeUrl"
            type="primary"
            @click="copyQRContent"
            icon="CopyDocument"
          >
            å¤åˆ¶å†…容
          </el-button>
          <el-button
            v-if="qrCodeUrl"
            type="success"
            @click="downloadQRCode"
            icon="Download"
          >
            ä¸‹è½½äºŒç»´ç 
          </el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Search, Loading, Download } from '@element-plus/icons-vue'
import Pagination from '@/components/PIMTable/Pagination.vue'
import QRCode from 'qrcode'
// å“åº”式数据
const loading = ref(false)
const searchForm = reactive({
  productName: '',
  identifierType: '',
  status: ''
})
const identifierList = ref([
  {
    id: 1,
    productName: '工业传感器A型',
    productCode: 'SENSOR001',
    batchNo: 'B202312001',
    identifierType: '二维码',
    identifierCode: 'QR_SENSOR001_B202312001_001',
    status: '已分配',
    generateTime: '2023-12-01 10:00:00',
    remark: '重要产品标识'
  },
  {
    id: 2,
    productName: '控制面板B型',
    productCode: 'PANEL002',
    batchNo: 'B202312002',
    identifierType: '防伪码',
    identifierCode: 'SEC_PANEL002_B202312002_001',
    status: '已生成',
    generateTime: '2023-12-02 14:30:00',
    remark: '常规产品标识'
  },
  {
    id: 3,
    productName: '数据采集器C型',
    productCode: 'COLLECTOR003',
    batchNo: 'B202312003',
    identifierType: '防伪码',
    identifierCode: 'SEC_COLLECTOR003_B202312003_001',
    status: '已使用',
    generateTime: '2023-12-03 09:15:00',
    remark: '测试产品标识'
  }
])
const pagination = reactive({
  total: 3,
  currentPage: 1,
  pageSize: 10
})
const dialogVisible = ref(false)
const dialogTitle = ref('新增标识')
const form = reactive({
  productName: '',
  productCode: '',
  batchNo: '',
  identifierType: '',
  quantity: 1,
  status: '已生成',
  remark: ''
})
const rules = {
  productName: [{ required: true, message: '请输入产品名称', trigger: 'blur' }],
  productCode: [{ required: true, message: '请输入产品编码', trigger: 'blur' }],
  batchNo: [{ required: true, message: '请输入批次号', trigger: 'blur' }],
  identifierType: [{ required: true, message: '请选择标识类型', trigger: 'change' }],
  quantity: [{ required: true, message: '请输入生成数量', trigger: 'blur' }],
  status: [{ required: true, message: '请选择状态', trigger: 'change' }]
}
const isEdit = ref(false)
const editId = ref(null)
const generateDialogVisible = ref(false)
const reassignDialogVisible = ref(false)
const currentProduct = ref({})
const generateQuantity = ref(1)
const codeRule = ref('')
const customPrefix = ref('')
const newBatchNo = ref('')
const reassignReason = ref('')
const formRef = ref()
// äºŒç»´ç ç›¸å…³å˜é‡
const qrCodeDialogVisible = ref(false)
const qrCodeUrl = ref('')
const currentQRProduct = ref({})
// è®¡ç®—属性
const filteredList = computed(() => {
  let list = identifierList.value
  if (searchForm.productName) {
    list = list.filter(item => item.productName.includes(searchForm.productName))
  }
  if (searchForm.identifierType) {
    list = list.filter(item => item.identifierType === searchForm.identifierType)
  }
  if (searchForm.status) {
    list = list.filter(item => item.status === searchForm.status)
  }
  return list
})
// æ–¹æ³•
const getIdentifierTypeType = (type) => {
  const typeMap = {
    '二维码': 'success',
    '防伪码': 'warning'
  }
  return typeMap[type] || 'info'
}
const getStatusType = (status) => {
  const statusMap = {
    '已生成': 'info',
    '已分配': 'primary',
    '已使用': 'success',
    '已作废': 'danger'
  }
  return statusMap[status] || 'info'
}
const handleSearch = () => {
  // æœç´¢é€»è¾‘已在computed中处理
}
const resetSearch = () => {
  searchForm.productName = ''
  searchForm.identifierType = ''
  searchForm.status = ''
}
const handleAdd = () => {
  dialogTitle.value = '新增标识'
  isEdit.value = false
  form.productName = ''
  form.productCode = ''
  form.batchNo = ''
  form.identifierType = ''
  form.quantity = 1
  form.status = '已生成'
  form.remark = ''
  dialogVisible.value = true
}
const handleView = (row) => {
  // æŸ¥çœ‹æ ‡è¯†è¯¦æƒ…
  ElMessage.info('查看标识详情功能待实现')
}
const handleEdit = (row) => {
  dialogTitle.value = '编辑标识'
  isEdit.value = true
  editId.value = row.id
  Object.assign(form, row)
  dialogVisible.value = true
}
const handleExport = (row) => {
  // å¯¼å‡ºæ ‡è¯†
  ElMessage.success(`已导出标识: ${row.identifierCode}`)
}
const handleReassign = (row) => {
  currentProduct.value = row
  newBatchNo.value = ''
  reassignReason.value = ''
  reassignDialogVisible.value = true
}
const handleDelete = (row) => {
  ElMessageBox.confirm('确认删除该标识吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    const index = identifierList.value.findIndex(item => item.id === row.id)
    if (index > -1) {
      identifierList.value.splice(index, 1)
      pagination.total--
      ElMessage.success('删除成功')
    }
  })
}
// ç”ŸæˆäºŒç»´ç 
const generateQRCode = async (row) => {
  try {
    // æ£€æŸ¥å¿…要字段
    if (!row.productName || !row.productCode || !row.batchNo) {
      ElMessage.warning('产品信息不完整,无法生成二维码')
      return
    }
    currentQRProduct.value = row
    qrCodeUrl.value = ''
    qrCodeDialogVisible.value = true
    // æž„建二维码内容
    let qrContent = ''
    if (row.identifierType === '二维码') {
      qrContent = `${row.productName}|${row.productCode}|${row.batchNo}|${row.identifierCode}`
    } else if (row.identifierType === '防伪码') {
      // é˜²ä¼ªç æ ¼å¼ï¼šSEC_产品编码_批次号_时间戳_随机数
      const timestamp = Date.now()
      const random = Math.random().toString(36).substr(2, 8)
      qrContent = `SEC_${row.productCode}_${row.batchNo}_${timestamp}_${random}`
    }
    // ç”ŸæˆäºŒç»´ç 
    qrCodeUrl.value = await QRCode.toDataURL(qrContent, {
      width: 256,
      margin: 2,
      color: {
        dark: '#000000',
        light: '#FFFFFF'
      },
      errorCorrectionLevel: row.identifierType === '防伪码' ? 'H' : 'M'
    })
    ElMessage.success('二维码生成成功!')
  } catch (error) {
    console.error('生成二维码失败:', error)
    ElMessage.error('生成二维码失败:' + error.message)
    qrCodeDialogVisible.value = false
  }
}
// ä¸‹è½½äºŒç»´ç 
const downloadQRCode = () => {
  if (!qrCodeUrl.value) {
    ElMessage.warning('请先生成二维码')
    return
  }
  const a = document.createElement('a')
  a.href = qrCodeUrl.value
  a.download = `${currentQRProduct.value.productName}_${currentQRProduct.value.identifierType}_${new Date().getTime()}.png`
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
  ElMessage.success('下载成功!')
}
// å¤åˆ¶äºŒç»´ç å†…容
const copyQRContent = async () => {
  if (!currentQRProduct.value) {
    ElMessage.warning('没有可复制的内容')
    return
  }
  try {
    let content = ''
    if (currentQRProduct.value.identifierType === '二维码') {
      content = `${currentQRProduct.value.productName}|${currentQRProduct.value.productCode}|${currentQRProduct.value.batchNo}|${currentQRProduct.value.identifierCode}`
    } else if (currentQRProduct.value.identifierType === '防伪码') {
      const timestamp = Date.now()
      const random = Math.random().toString(36).substr(2, 8)
      content = `SEC_${currentQRProduct.value.productCode}_${currentQRProduct.value.batchNo}_${timestamp}_${random}`
    }
    await navigator.clipboard.writeText(content)
    ElMessage.success('内容已复制到剪贴板')
  } catch (error) {
    // é™çº§æ–¹æ¡ˆ
    const textArea = document.createElement('textarea')
    textArea.value = content
    document.body.appendChild(textArea)
    textArea.select()
    document.execCommand('copy')
    document.body.removeChild(textArea)
    ElMessage.success('内容已复制到剪贴板')
  }
}
const generateIdentifiers = () => {
  if (!codeRule.value) {
    ElMessage.warning('请选择编码规则')
    return
  }
  // ç”Ÿæˆæ ‡è¯†çš„逻辑
  const newIdentifiers = []
  for (let i = 1; i <= generateQuantity.value; i++) {
    let identifierCode = ''
    if (codeRule.value === '产品编码+批次号+序号') {
      identifierCode = `${currentProduct.value.productCode}_${currentProduct.value.batchNo}_${String(i).padStart(3, '0')}`
    } else if (codeRule.value === '时间戳+随机数') {
      identifierCode = `TS_${Date.now()}_${Math.floor(Math.random() * 1000)}`
    } else if (codeRule.value === '自定义规则') {
      identifierCode = `${customPrefix.value || 'CUSTOM'}_${Date.now()}_${i}`
    }
    newIdentifiers.push({
      id: Math.max(...identifierList.value.map(item => item.id)) + i,
      productName: currentProduct.value.productName,
      productCode: currentProduct.value.productCode,
      batchNo: currentProduct.value.batchNo,
      identifierType: currentProduct.value.identifierType,
      identifierCode: identifierCode,
      status: '已生成',
      generateTime: new Date().toLocaleString(),
      remark: '批量生成'
    })
  }
  identifierList.value.push(...newIdentifiers)
  pagination.total += newIdentifiers.length
  ElMessage.success(`成功生成 ${newIdentifiers.length} ä¸ªæ ‡è¯†`)
  generateDialogVisible.value = false
}
const saveReassign = () => {
  if (!newBatchNo.value) {
    ElMessage.warning('请输入新批次号')
    return
  }
  const index = identifierList.value.findIndex(item => item.id === currentProduct.value.id)
  if (index > -1) {
    identifierList.value[index].batchNo = newBatchNo.value
    identifierList.value[index].status = '已分配'
    ElMessage.success('标识重新分配成功')
    reassignDialogVisible.value = false
  }
}
const handleSubmit = () => {
  formRef.value.validate((valid) => {
    if (valid) {
      if (isEdit.value) {
        // ç¼–辑
        const index = identifierList.value.findIndex(item => item.id === editId.value)
        if (index > -1) {
          identifierList.value[index] = { ...form, id: editId.value }
          ElMessage.success('编辑成功')
        }
             } else {
         // æ–°å¢ž
         const newId = Math.max(...identifierList.value.map(item => item.id)) + 1
         // æ ¹æ®æ ‡è¯†ç±»åž‹ç”Ÿæˆä¸åŒçš„æ ‡è¯†ç 
         let identifierCode = ''
         if (form.identifierType === '二维码') {
           identifierCode = `QR_${form.productCode}_${form.batchNo}_001`
         } else if (form.identifierType === '防伪码') {
           identifierCode = `SEC_${form.productCode}_${form.batchNo}_001`
         }
         identifierList.value.push({
           ...form,
           id: newId,
           identifierCode: identifierCode,
           generateTime: new Date().toLocaleString()
         })
         pagination.total++
         ElMessage.success('新增成功')
       }
      dialogVisible.value = false
    }
  })
}
const handleCurrentChange = (val) => {
  pagination.currentPage = val.page
  pagination.pageSize = val.limit
}
</script>
<style scoped>
.search-row {
  margin-bottom: 20px;
}
.quick-actions-row {
  margin-bottom: 20px;
}
.quick-actions-row .el-alert {
  margin-bottom: 0;
}
.quick-actions-row .el-alert p {
  margin: 5px 0;
  font-size: 14px;
  line-height: 1.5;
}
/* äºŒç»´ç é¢„览样式 */
.qr-preview-container {
  text-align: center;
  padding: 20px;
}
.qr-image-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
}
.qr-image {
  max-width: 100%;
  height: auto;
  border: 2px solid #e0e0e0;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.qr-info {
  text-align: left;
  background: #f8f9fa;
  padding: 15px;
  border-radius: 8px;
  min-width: 300px;
}
.qr-info p {
  margin: 8px 0;
  color: #666;
  font-size: 14px;
}
.qr-loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 15px;
  padding: 40px 0;
}
.qr-loading .el-icon {
  font-size: 32px;
  color: #409EFF;
}
.qr-loading p {
  color: #666;
  margin: 0;
}
</style>
src/views/qualityManagement/nonconformingManagement/index.vue
@@ -4,7 +4,7 @@
      <div style="display: flex;flex-direction: row;align-items: center;">
        <div>
          <span class="search_title">类型:</span>
          <el-select v-model="searchForm.inspectType" clearable style="width: 240px" @change="handleQuery">
          <el-select v-model="searchForm.inspectType" clearable style="width: 200px" @change="handleQuery">
            <el-option label="原材料检验" :value="0" />
            <el-option label="过程检验" :value="1" />
            <el-option label="出厂检验" :value="2" />
@@ -12,7 +12,7 @@
        </div>
        <div style="margin-left: 10px">
          <span class="search_title">状态:</span>
          <el-select v-model="searchForm.inspectState" clearable style="width: 240px" @change="handleQuery">
          <el-select v-model="searchForm.inspectState" clearable style="width: 200px" @change="handleQuery">
            <el-option label="待处理" :value="0" />
            <el-option label="已处理" :value="1" />
          </el-select>
@@ -21,7 +21,7 @@
          <span class="search_title">产品名称:</span>
          <el-input
              v-model="searchForm.productName"
              style="width: 240px"
              style="width: 200px"
              placeholder="请输入产品名称搜索"
              @change="handleQuery"
              clearable
@@ -30,6 +30,7 @@
        </div>
        <span  style="margin-left: 10px" class="search_title">检测日期:</span>
        <el-date-picker  v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
                                                 style="width: 300px"
                         placeholder="请选择" clearable @change="changeDaterange" />
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">搜索</el-button>
      </div>
src/views/qualityManagement/visualization/qualityDashboard.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,307 @@
<template>
  <div class="quality-dashboard">
    <el-row :gutter="16">
      <el-col :xs="24" :sm="12">
        <el-card shadow="hover" class="panel">
          <template #header>
            <div class="panel-title">
              æ£€æµ‹æ ·å“åŠ¨æ€çŠ¶æ€
              <div class="actions">
                <el-switch v-model="voiceEnabled" active-text="语音预警" inactive-text="静音" />
              </div>
            </div>
          </template>
          <div class="status-list">
            <div v-for="item in sampleStatus" :key="item.id" class="status-item" :class="item.status">
              <div class="left">
                <span class="dot" :class="item.status"></span>
                <span class="name">{{ item.name }}</span>
              </div>
              <div class="right">
                <el-tag :type="statusTagType(item.status)" size="small">{{ statusLabel(item.status) }}</el-tag>
                <span class="time">{{ item.time }}</span>
              </div>
            </div>
          </div>
        </el-card>
      </el-col>
      <el-col :xs="24" :sm="12">
        <el-card shadow="hover" class="panel">
          <template #header>
            <div class="panel-title">任务排行(Top 10)</div>
          </template>
          <EChart :xAxis="tasksXAxis" :yAxis="[{ type: 'value' }]" :series="tasksSeries" :grid="{ left: 40, right: 20, top: 20, bottom: 40 }" :tooltip="{ trigger: 'axis' }" :barColors="['#3b82f6']" :chartStyle="{ height: '320px', width: '100%' }" />
        </el-card>
      </el-col>
    </el-row>
    <el-row :gutter="16" style="margin-top: 16px;">
      <el-col :xs="24" :sm="14">
        <el-card shadow="hover" class="panel">
          <template #header>
            <div class="panel-title">历史趋势</div>
          </template>
          <EChart :xAxis="[{ type: 'category', data: trendXAxis }]" :yAxis="[{ type: 'value', name: '数量' }]" :series="trendSeries" :tooltip="{ trigger: 'axis' }" :legend="{ top: 0 }" :lineColors="['#10b981', '#f59e0b']" :chartStyle="{ height: '340px', width: '100%' }" />
        </el-card>
      </el-col>
      <el-col :xs="24" :sm="10">
        <el-card shadow="hover" class="panel">
          <template #header>
            <div class="panel-title">合格率分析</div>
          </template>
          <EChart :series="passRateSeries" :legend="{ show: false }" :chartStyle="{ height: '340px', width: '100%' }" />
          <div class="passrate-text">
            å½“前合格率:<b>{{ (passRate * 100).toFixed(1) }}%</b>
          </div>
        </el-card>
      </el-col>
    </el-row>
    <el-row :gutter="16" style="margin-top: 16px;">
      <el-col :xs="24">
        <el-card shadow="hover" class="panel">
          <template #header>
            <div class="panel-title">SPC æŽ§åˆ¶å›¾ï¼ˆXbar)</div>
          </template>
          <EChart :xAxis="[{ type: 'category', data: spcXAxis }]" :yAxis="[{ type: 'value', name: '测量值' }]" :series="spcSeries" :legend="{ top: 0 }" :tooltip="{ trigger: 'axis' }" :lineColors="['#2563eb', '#ef4444', '#f97316', '#22c55e']" :chartStyle="{ height: '380px', width: '100%' }" />
        </el-card>
      </el-col>
    </el-row>
  </div>
</template>
<script setup>
import { onMounted, onBeforeUnmount, reactive, ref } from 'vue'
import EChart from '@/components/Echarts/echarts.vue'
const voiceEnabled = ref(true)
let dataTimer = null
// 1) æ ·å“åŠ¨æ€çŠ¶æ€ï¼ˆæ»šåŠ¨æ›´æ–°ï¼‰
const sampleStatus = ref([])
const statusPool = ['processing', 'warning', 'error', 'success']
function statusLabel(s) {
  return s === 'processing' ? '检测中' : s === 'warning' ? '预警' : s === 'error' ? '不合格' : '合格'
}
function statusTagType(s) {
  return s === 'processing' ? 'info' : s === 'warning' ? 'warning' : s === 'error' ? 'danger' : 'success'
}
function randomSample() {
  const id = Math.random().toString(36).slice(2, 8)
  const status = statusPool[Math.floor(Math.random() * statusPool.length)]
  const name = `样品-${Math.floor(Math.random() * 900 + 100)}`
  const time = new Date().toLocaleTimeString('zh-CN', { hour12: false })
  return { id, name, status, time }
}
// 2) ä»»åŠ¡æŽ’è¡Œï¼ˆæŸ±çŠ¶å›¾ï¼‰
const tasksXAxis = reactive([{ type: 'category', data: [] }])
const tasksSeries = ref([
  {
    type: 'bar',
    data: [],
    label: { show: true, position: 'inside', align: 'center', verticalAlign: 'middle', color: '#fff' },
    encode: undefined,
  },
])
// 3) åŽ†å²è¶‹åŠ¿ï¼ˆæŠ˜çº¿ï¼‰
const trendXAxis = ref([])
const trendSeries = ref([
  { name: '来样数', type: 'line', smooth: true, data: [] },
  { name: '完成数', type: 'line', smooth: true, data: [] },
])
// 4) åˆæ ¼çŽ‡åˆ†æžï¼ˆä»ªè¡¨ç›˜ï¼‰
const passRate = ref(0.92)
const passRateSeries = ref([
  {
    type: 'gauge',
    progress: { show: true, width: 12 },
    axisLine: { lineStyle: { width: 12 } },
    pointer: { show: true },
    detail: { valueAnimation: true, formatter: (v) => `${(v * 100).toFixed(1)}%` },
    data: [{ value: passRate.value }],
  },
])
// 5) SPC æŽ§åˆ¶å›¾
const spcXAxis = ref([])
const spcData = ref([]) // å®žé™…测量值
const CL = ref(50)
const UCL = ref(55)
const LCL = ref(45)
const spcSeries = ref([
  {
    name: '测量均值',
    type: 'line',
    smooth: false,
    symbol: 'circle',
    data: [],
    markLine: {
      symbol: 'none',
      lineStyle: { type: 'dashed', color: '#999' },
      data: [
        { yAxis: () => UCL.value, name: 'UCL' },
        { yAxis: () => CL.value, name: 'CL' },
        { yAxis: () => LCL.value, name: 'LCL' },
      ],
      label: { formatter: ({ name }) => name },
    },
  },
  { name: 'UCL', type: 'line', data: [], symbol: 'none', lineStyle: { type: 'dashed', color: '#ef4444' } },
  { name: 'CL', type: 'line', data: [], symbol: 'none', lineStyle: { type: 'dashed', color: '#f97316' } },
  { name: 'LCL', type: 'line', data: [], symbol: 'none', lineStyle: { type: 'dashed', color: '#22c55e' } },
])
// è¯­éŸ³æ’­æŠ¥
function speak(text) {
  if (!voiceEnabled.value) return
  if (!('speechSynthesis' in window)) return
  const utter = new SpeechSynthesisUtterance(text)
  utter.lang = 'zh-CN'
  try {
    window.speechSynthesis.cancel()
    window.speechSynthesis.speak(utter)
  } catch (e) {
    // ignore
  }
}
function refreshFakeData() {
  // æ ·å“çŠ¶æ€æ»šåŠ¨
  const next = randomSample()
  sampleStatus.value = [next, ...sampleStatus.value].slice(0, 8)
  // ä»»åŠ¡æŽ’è¡Œ
  const tasks = Array.from({ length: 10 }).map((_, i) => ({ name: `任务-${i + 1}`, count: Math.floor(Math.random() * 100 + 20) }))
  tasks.sort((a, b) => a.count - b.count)
  tasksXAxis.data = tasks.map(t => t.name)
  tasksSeries.value[0].data = tasks.map(t => t.count)
  // åŽ†å²è¶‹åŠ¿ï¼ˆè¿½åŠ ç‚¹ï¼‰
  const nowLabel = new Date().toLocaleTimeString('zh-CN', { minute: '2-digit', second: '2-digit' })
  if (trendXAxis.value.length > 15) {
    trendXAxis.value.shift()
    trendSeries.value[0].data.shift()
    trendSeries.value[1].data.shift()
  }
  trendXAxis.value.push(nowLabel)
  const incoming = Math.floor(Math.random() * 30 + 20)
  const finished = Math.max(0, incoming - Math.floor(Math.random() * 10))
  trendSeries.value[0].data.push(incoming)
  trendSeries.value[1].data.push(finished)
  // åˆæ ¼çŽ‡ï¼ˆè½»å¾®æ³¢åŠ¨ï¼‰
  const delta = (Math.random() - 0.5) * 0.02
  passRate.value = Math.min(0.99, Math.max(0.6, passRate.value + delta))
  passRateSeries.value[0].data[0].value = passRate.value
  // SPC æ•°æ®ï¼ˆçª—口移动)
  const nextVal = CL.value + (Math.random() - 0.5) * 8 // æ³¢åЍ
  if (spcXAxis.value.length > 30) {
    spcXAxis.value.shift()
    spcData.value.shift()
  }
  spcXAxis.value.push(`${spcXAxis.value.length + 1}`)
  spcData.value.push(parseFloat(nextVal.toFixed(2)))
  spcSeries.value[0].data = [...spcData.value]
  spcSeries.value[1].data = new Array(spcData.value.length).fill(UCL.value)
  spcSeries.value[2].data = new Array(spcData.value.length).fill(CL.value)
  spcSeries.value[3].data = new Array(spcData.value.length).fill(LCL.value)
  // è§¦å‘播报:合格率过低或 SPC è¶…限
  if (passRate.value < 0.8) {
    speak(`预警,当前合格率为 ${(passRate.value * 100).toFixed(0)}%,低于 80% é˜ˆå€¼`)
  }
  const last = spcData.value[spcData.value.length - 1]
  if (last > UCL.value) {
    speak(`预警,最新测量值 ${last.toFixed(2)} è¶…过上限`)
  }
  if (last < LCL.value) {
    speak(`预警,最新测量值 ${last.toFixed(2)} ä½ŽäºŽä¸‹é™`)
  }
}
onMounted(() => {
  // åˆå§‹åŒ–几条假数据
  sampleStatus.value = Array.from({ length: 5 }).map(() => randomSample())
  for (let i = 0; i < 10; i++) {
    trendXAxis.value.push(`T-${i}`)
    trendSeries.value[0].data.push(Math.floor(Math.random() * 30 + 20))
    trendSeries.value[1].data.push(Math.floor(Math.random() * 25 + 15))
  }
  for (let i = 0; i < 20; i++) {
    spcXAxis.value.push(`${i + 1}`)
    const v = CL.value + (Math.random() - 0.5) * 6
    spcData.value.push(parseFloat(v.toFixed(2)))
  }
  spcSeries.value[0].data = [...spcData.value]
  spcSeries.value[1].data = new Array(spcData.value.length).fill(UCL.value)
  spcSeries.value[2].data = new Array(spcData.value.length).fill(CL.value)
  spcSeries.value[3].data = new Array(spcData.value.length).fill(LCL.value)
  dataTimer = setInterval(refreshFakeData, 10000)
})
onBeforeUnmount(() => {
  if (dataTimer) clearInterval(dataTimer)
  try { window.speechSynthesis && window.speechSynthesis.cancel() } catch (e) {}
})
</script>
<style scoped>
.quality-dashboard {
  padding: 8px;
}
.panel-title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-weight: 600;
}
.status-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: 320px;
  overflow: auto;
}
.status-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 10px;
  border-radius: 6px;
  background: var(--el-fill-color-light);
}
.status-item .left {
  display: flex;
  align-items: center;
  gap: 8px;
}
.status-item .right {
  display: flex;
  align-items: center;
  gap: 10px;
}
.status-item .name { font-weight: 500; }
.status-item .time { color: var(--el-text-color-secondary); font-size: 12px; }
.dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  display: inline-block;
}
.dot.processing { background: #60a5fa; }
.dot.warning { background: #f59e0b; }
.dot.error { background: #ef4444; }
.dot.success { background: #10b981; }
.passrate-text {
  text-align: center;
  margin-top: 8px;
}
</style>
src/views/reportAnalysis/reportManagement.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,733 @@
<template>
  <div class="report-management">
    <!-- é¡µé¢æ ‡é¢˜ -->
    <div class="page-header">
      <h2>报表管理</h2>
      <p>提供样品进度、设备使用、检测项目、领用记录等各类自动统计报表</p>
    </div>
    <!-- ç­›é€‰æ¡ä»¶ -->
    <el-card class="filter-card" shadow="never">
      <el-form :model="filterForm" inline>
        <el-form-item label="时间范围">
          <el-date-picker
            v-model="filterForm.dateRange"
            type="daterange"
            range-separator="至"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
            format="YYYY-MM-DD"
            value-format="YYYY-MM-DD"
            @change="handleFilterChange"
          />
        </el-form-item>
        <el-form-item label="报表类型">
          <el-select v-model="filterForm.reportType" placeholder="请选择报表类型" @change="handleFilterChange">
            <el-option label="样品进度报表" value="sample" />
            <el-option label="设备使用报表" value="equipment" />
            <el-option label="检测项目报表" value="inspection" />
            <el-option label="领用记录报表" value="usage" />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleFilterChange">查询</el-button>
          <el-button @click="resetFilter">重置</el-button>
          <el-button type="success" @click="exportReport">导出报表</el-button>
        </el-form-item>
      </el-form>
    </el-card>
    <!-- ç»Ÿè®¡å¡ç‰‡ -->
    <div class="statistics-cards">
      <el-row :gutter="20">
        <el-col :span="6">
          <el-card class="stat-card" shadow="hover">
            <div class="stat-content">
              <div class="stat-icon">
                <el-icon><Box /></el-icon>
              </div>
              <div class="stat-info">
                <div class="stat-number">{{ statistics.totalSamples }}</div>
                <div class="stat-label">总样品数</div>
              </div>
            </div>
          </el-card>
        </el-col>
        <el-col :span="6">
          <el-card class="stat-card" shadow="hover">
            <div class="stat-content">
              <div class="stat-icon">
                <el-icon><Tools /></el-icon>
              </div>
              <div class="stat-info">
                <div class="stat-number">{{ statistics.activeEquipment }}</div>
                <div class="stat-label">在用设备</div>
              </div>
            </div>
          </el-card>
        </el-col>
        <el-col :span="6">
          <el-card class="stat-card" shadow="hover">
            <div class="stat-content">
              <div class="stat-icon">
                <el-icon><Document /></el-icon>
              </div>
              <div class="stat-info">
                <div class="stat-number">{{ statistics.completedInspections }}</div>
                <div class="stat-label">已完成检测</div>
              </div>
            </div>
          </el-card>
        </el-col>
        <el-col :span="6">
          <el-card class="stat-card" shadow="hover">
            <div class="stat-content">
              <div class="stat-icon">
                <el-icon><ShoppingCart /></el-icon>
              </div>
              <div class="stat-info">
                <div class="stat-number">{{ statistics.totalUsage }}</div>
                <div class="stat-label">总领用次数</div>
              </div>
            </div>
          </el-card>
        </el-col>
      </el-row>
    </div>
    <!-- å›¾è¡¨åŒºåŸŸ -->
    <div class="charts-container">
      <el-row :gutter="20">
        <!-- æ ·å“è¿›åº¦å›¾è¡¨ -->
        <el-col :span="12">
          <el-card class="chart-card" shadow="hover">
            <template #header>
              <div class="card-header">
                <span>样品进度统计</span>
                <el-button type="text" @click="refreshSampleChart">刷新</el-button>
              </div>
            </template>
            <div ref="sampleChartRef" class="chart-container"></div>
          </el-card>
        </el-col>
        <!-- è®¾å¤‡ä½¿ç”¨å›¾è¡¨ -->
        <el-col :span="12">
          <el-card class="chart-card" shadow="hover">
            <template #header>
              <div class="card-header">
                <span>设备使用率统计</span>
                <el-button type="text" @click="refreshEquipmentChart">刷新</el-button>
              </div>
            </template>
            <div ref="equipmentChartRef" class="chart-container"></div>
          </el-card>
        </el-col>
      </el-row>
      <el-row :gutter="20" style="margin-top: 20px;">
        <!-- æ£€æµ‹é¡¹ç›®ç»Ÿè®¡ -->
        <el-col :span="12">
          <el-card class="chart-card" shadow="hover">
            <template #header>
              <div class="card-header">
                <span>检测项目分布</span>
                <el-button type="text" @click="refreshInspectionChart">刷新</el-button>
              </div>
            </template>
            <div ref="inspectionChartRef" class="chart-container"></div>
          </el-card>
        </el-col>
        <!-- é¢†ç”¨è®°å½•趋势 -->
        <el-col :span="12">
          <el-card class="chart-card" shadow="hover">
            <template #header>
              <div class="card-header">
                <span>领用记录趋势</span>
                <el-button type="text" @click="refreshUsageChart">刷新</el-button>
              </div>
            </template>
            <div ref="usageChartRef" class="chart-container"></div>
          </el-card>
        </el-col>
      </el-row>
    </div>
    <!-- è¯¦ç»†æ•°æ®è¡¨æ ¼ -->
    <el-card class="table-card" shadow="hover">
      <template #header>
        <div class="card-header">
          <span>详细数据</span>
          <div>
            <el-button type="primary" size="small" @click="refreshTable">刷新</el-button>
            <el-button type="success" size="small" @click="exportTable">导出</el-button>
          </div>
        </div>
      </template>
      <el-table
        :data="tableData"
        style="width: 100%"
        v-loading="tableLoading"
        stripe
        border
      >
        <el-table-column prop="id" label="编号" width="80" />
        <el-table-column prop="name" label="名称" />
        <el-table-column prop="type" label="类型" width="120" />
        <el-table-column prop="status" label="状态" width="100">
          <template #default="scope">
            <el-tag :type="getStatusType(scope.row.status)">
              {{ scope.row.status }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="progress" label="进度" width="120">
          <template #default="scope">
            <el-progress :percentage="scope.row.progress" :status="getProgressStatus(scope.row.progress)" />
          </template>
        </el-table-column>
        <el-table-column prop="createTime" label="创建时间" width="180" />
        <el-table-column prop="updateTime" label="更新时间" width="180" />
        <el-table-column label="操作" width="150" fixed="right">
          <template #default="scope">
            <el-button type="text" size="small" @click="viewDetail(scope.row)">查看</el-button>
            <el-button type="text" size="small" @click="editItem(scope.row)">编辑</el-button>
          </template>
        </el-table-column>
      </el-table>
      <div class="pagination-container">
        <el-pagination
          v-model:current-page="pagination.currentPage"
          v-model:page-size="pagination.pageSize"
          :page-sizes="[10, 20, 50, 100]"
          :total="pagination.total"
          layout="total, sizes, prev, pager, next, jumper"
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
        />
      </div>
    </el-card>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, nextTick } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import * as echarts from 'echarts'
import { Box, Tools, Document, ShoppingCart } from '@element-plus/icons-vue'
// å“åº”式数据
const filterForm = reactive({
  dateRange: [],
  reportType: 'sample'
})
const statistics = reactive({
  totalSamples: 1250,
  activeEquipment: 45,
  completedInspections: 890,
  totalUsage: 2340
})
const tableData = ref([])
const tableLoading = ref(false)
const pagination = reactive({
  currentPage: 1,
  pageSize: 20,
  total: 0
})
// å›¾è¡¨å¼•用
const sampleChartRef = ref(null)
const equipmentChartRef = ref(null)
const inspectionChartRef = ref(null)
const usageChartRef = ref(null)
// å›¾è¡¨å®žä¾‹
let sampleChart = null
let equipmentChart = null
let inspectionChart = null
let usageChart = null
// åˆå§‹åŒ–数据
const initData = () => {
  // æ¨¡æ‹Ÿè¡¨æ ¼æ•°æ®
  tableData.value = [
    {
      id: 'SP001',
      name: '样品A-001',
      type: '金属材料',
      status: '检测中',
      progress: 75,
      createTime: '2025-01-15 09:30:00',
      updateTime: '2025-01-20 14:20:00'
    },
    {
      id: 'SP002',
      name: '样品B-002',
      type: '塑料制品',
      status: '已完成',
      progress: 100,
      createTime: '2025-01-10 10:15:00',
      updateTime: '2025-01-18 16:45:00'
    },
    {
      id: 'SP003',
      name: '样品C-003',
      type: '电子元件',
      status: '待检测',
      progress: 0,
      createTime: '2025-01-22 08:45:00',
      updateTime: '2025-01-22 08:45:00'
    },
    {
      id: 'EQ001',
      name: '检测设备A',
      type: '光谱仪',
      status: '使用中',
      progress: 60,
      createTime: '2025-01-05 14:20:00',
      updateTime: '2025-01-20 11:30:00'
    },
    {
      id: 'EQ002',
      name: '检测设备B',
      type: '显微镜',
      status: '空闲',
      progress: 0,
      createTime: '2025-01-08 16:10:00',
      updateTime: '2025-01-19 09:15:00'
    }
  ]
  pagination.total = tableData.value.length
}
// åˆå§‹åŒ–样品进度图表
const initSampleChart = () => {
  if (sampleChartRef.value) {
    sampleChart = echarts.init(sampleChartRef.value)
    const option = {
      title: {
        text: '样品进度分布',
        left: 'center'
      },
      tooltip: {
        trigger: 'item',
        formatter: '{a} <br/>{b}: {c} ({d}%)'
      },
      legend: {
        orient: 'vertical',
        left: 'left'
      },
      series: [
        {
          name: '样品状态',
          type: 'pie',
          radius: ['40%', '70%'],
          avoidLabelOverlap: false,
          label: {
            show: false,
            position: 'center'
          },
          emphasis: {
            label: {
              show: true,
              fontSize: '18',
              fontWeight: 'bold'
            }
          },
          labelLine: {
            show: false
          },
          data: [
            { value: 450, name: '已完成' },
            { value: 320, name: '检测中' },
            { value: 280, name: '待检测' },
            { value: 200, name: '已暂停' }
          ]
        }
      ]
    }
    sampleChart.setOption(option)
  }
}
// åˆå§‹åŒ–设备使用图表
const initEquipmentChart = () => {
  if (equipmentChartRef.value) {
    equipmentChart = echarts.init(equipmentChartRef.value)
    const option = {
      title: {
        text: '设备使用率',
        left: 'center'
      },
      tooltip: {
        trigger: 'axis',
        axisPointer: {
          type: 'shadow'
        }
      },
      xAxis: {
        type: 'category',
        data: ['光谱仪', '显微镜', '硬度计', '拉力机', '冲击机', '金相仪']
      },
      yAxis: {
        type: 'value',
        name: '使用率(%)'
      },
      series: [
        {
          name: '使用率',
          type: 'bar',
          data: [85, 60, 75, 90, 45, 70],
          label: {
            show: true,
            position: 'inside',
            align: 'center',
            verticalAlign: 'middle',
            formatter: '{c}%',
            color: '#fff'
          },
          itemStyle: {
            color: function(params) {
              const colors = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399', '#9C27B0']
              return colors[params.dataIndex]
            }
          }
        }
      ]
    }
    equipmentChart.setOption(option)
  }
}
// åˆå§‹åŒ–检测项目图表
const initInspectionChart = () => {
  if (inspectionChartRef.value) {
    inspectionChart = echarts.init(inspectionChartRef.value)
    const option = {
      title: {
        text: '检测项目分布',
        left: 'center'
      },
      tooltip: {
        trigger: 'item'
      },
      legend: {
        orient: 'vertical',
        left: 'left'
      },
      series: [
        {
          name: '检测项目',
          type: 'pie',
          radius: '50%',
          data: [
            { value: 335, name: '物理性能' },
            { value: 310, name: '化学分析' },
            { value: 234, name: '尺寸测量' },
            { value: 135, name: '外观检查' },
            { value: 148, name: '其他检测' }
          ],
          emphasis: {
            itemStyle: {
              shadowBlur: 10,
              shadowOffsetX: 0,
              shadowColor: 'rgba(0, 0, 0, 0.5)'
            }
          }
        }
      ]
    }
    inspectionChart.setOption(option)
  }
}
// åˆå§‹åŒ–领用记录图表
const initUsageChart = () => {
  if (usageChartRef.value) {
    usageChart = echarts.init(usageChartRef.value)
    const option = {
      title: {
        text: '领用记录趋势',
        left: 'center'
      },
      tooltip: {
        trigger: 'axis'
      },
      legend: {
        data: ['领用次数', '归还次数']
      },
      xAxis: {
        type: 'category',
        boundaryGap: false,
        data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
      },
      yAxis: {
        type: 'value'
      },
      series: [
        {
          name: '领用次数',
          type: 'line',
          stack: 'Total',
          data: [120, 132, 101, 134, 90, 230, 210, 182, 191, 234, 290, 330]
        },
        {
          name: '归还次数',
          type: 'line',
          stack: 'Total',
          data: [110, 125, 95, 128, 85, 220, 200, 175, 185, 225, 280, 320]
        }
      ]
    }
    usageChart.setOption(option)
  }
}
// äº‹ä»¶å¤„理函数
const handleFilterChange = () => {
  ElMessage.success('筛选条件已更新')
  // è¿™é‡Œå¯ä»¥æ ¹æ®ç­›é€‰æ¡ä»¶é‡æ–°åŠ è½½æ•°æ®
}
const resetFilter = () => {
  filterForm.dateRange = []
  filterForm.reportType = 'sample'
  ElMessage.info('筛选条件已重置')
}
const exportReport = () => {
  ElMessage.success('报表导出功能开发中...')
}
const refreshSampleChart = () => {
  initSampleChart()
  ElMessage.success('样品进度图表已刷新')
}
const refreshEquipmentChart = () => {
  initEquipmentChart()
  ElMessage.success('设备使用图表已刷新')
}
const refreshInspectionChart = () => {
  initInspectionChart()
  ElMessage.success('检测项目图表已刷新')
}
const refreshUsageChart = () => {
  initUsageChart()
  ElMessage.success('领用记录图表已刷新')
}
const refreshTable = () => {
  tableLoading.value = true
  setTimeout(() => {
    tableLoading.value = false
    ElMessage.success('表格数据已刷新')
  }, 1000)
}
const exportTable = () => {
  ElMessage.success('表格导出功能开发中...')
}
const handleSizeChange = (val) => {
  pagination.pageSize = val
  // é‡æ–°åŠ è½½æ•°æ®
}
const handleCurrentChange = (val) => {
  pagination.currentPage = val
  // é‡æ–°åŠ è½½æ•°æ®
}
const getStatusType = (status) => {
  const statusMap = {
    '已完成': 'success',
    '检测中': 'warning',
    '待检测': 'info',
    '已暂停': 'danger',
    '使用中': 'primary',
    '空闲': 'info'
  }
  return statusMap[status] || 'info'
}
const getProgressStatus = (progress) => {
  if (progress === 100) return 'success'
  if (progress >= 80) return 'warning'
  if (progress >= 50) return ''
  return 'exception'
}
const viewDetail = (row) => {
  ElMessage.info(`查看详情: ${row.name}`)
}
const editItem = (row) => {
  ElMessage.info(`编辑项目: ${row.name}`)
}
// ç”Ÿå‘½å‘¨æœŸ
onMounted(() => {
  initData()
  nextTick(() => {
    initSampleChart()
    initEquipmentChart()
    initInspectionChart()
    initUsageChart()
  })
  // ç›‘听窗口大小变化,重新调整图表大小
  window.addEventListener('resize', () => {
    sampleChart?.resize()
    equipmentChart?.resize()
    inspectionChart?.resize()
    usageChart?.resize()
  })
})
</script>
<style scoped>
.report-management {
  padding: 20px;
  background-color: #f5f5f5;
  min-height: 100vh;
}
.page-header {
  margin-bottom: 20px;
  text-align: center;
}
.page-header h2 {
  color: #303133;
  margin-bottom: 8px;
  font-size: 24px;
  font-weight: 600;
}
.page-header p {
  color: #909399;
  font-size: 14px;
  margin: 0;
}
.filter-card {
  margin-bottom: 20px;
}
.statistics-cards {
  margin-bottom: 20px;
}
.stat-card {
  height: 120px;
}
.stat-content {
  display: flex;
  align-items: center;
  height: 100%;
}
.stat-icon {
  width: 60px;
  height: 60px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-right: 20px;
  font-size: 24px;
  color: white;
}
.stat-card:nth-child(1) .stat-icon {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.stat-card:nth-child(2) .stat-icon {
  background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
.stat-card:nth-child(3) .stat-icon {
  background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}
.stat-card:nth-child(4) .stat-icon {
  background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
}
.stat-info {
  flex: 1;
}
.stat-number {
  font-size: 28px;
  font-weight: bold;
  color: #303133;
  margin-bottom: 8px;
}
.stat-label {
  font-size: 14px;
  color: #909399;
}
.charts-container {
  margin-bottom: 20px;
}
.chart-card {
  margin-bottom: 20px;
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.chart-container {
  height: 300px;
  width: 100%;
}
.table-card {
  margin-bottom: 20px;
}
.pagination-container {
  margin-top: 20px;
  text-align: right;
}
:deep(.el-card__header) {
  padding: 15px 20px;
  border-bottom: 1px solid #ebeef5;
  background-color: #fafafa;
}
:deep(.el-card__body) {
  padding: 20px;
}
:deep(.el-table) {
  margin-bottom: 20px;
}
:deep(.el-progress) {
  margin: 0;
}
:deep(.el-tag) {
  margin: 0;
}
</style>
src/views/reportAnalysis/reportManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,716 @@
<template>
    <div class="report-management">
        <!-- ç­›é€‰æ¡ä»¶ -->
        <el-card class="filter-card" shadow="never">
            <el-form :model="filterForm" inline>
                <el-form-item label="时间范围">
                    <el-date-picker
                        style="width: 300px"
                        v-model="filterForm.dateRange"
                        type="daterange"
                        range-separator="至"
                        start-placeholder="开始日期"
                        end-placeholder="结束日期"
                        format="YYYY-MM-DD"
                        value-format="YYYY-MM-DD"
                        @change="handleFilterChange"
                    />
                </el-form-item>
                <el-form-item label="报表类型">
                    <el-select v-model="filterForm.reportType" placeholder="请选择报表类型" @change="handleFilterChange" style="width: 300px">
                        <el-option label="样品进度报表" value="sample" />
                        <el-option label="设备使用报表" value="equipment" />
                        <el-option label="检测项目报表" value="inspection" />
                        <el-option label="领用记录报表" value="usage" />
                    </el-select>
                </el-form-item>
                <el-form-item>
                    <el-button type="primary" @click="handleFilterChange">查询</el-button>
                    <el-button @click="resetFilter">重置</el-button>
                    <el-button type="success" @click="exportReport">导出报表</el-button>
                </el-form-item>
            </el-form>
        </el-card>
        <!-- ç»Ÿè®¡å¡ç‰‡ -->
        <div class="statistics-cards">
            <el-row :gutter="20">
                <el-col :span="6">
                    <el-card class="stat-card" shadow="hover">
                        <div class="stat-content">
                            <div class="stat-icon">
                                <el-icon><Box /></el-icon>
                            </div>
                            <div class="stat-info">
                                <div class="stat-number">{{ statistics.totalSamples }}</div>
                                <div class="stat-label">总样品数</div>
                            </div>
                        </div>
                    </el-card>
                </el-col>
                <el-col :span="6">
                    <el-card class="stat-card" shadow="hover">
                        <div class="stat-content">
                            <div class="stat-icon">
                                <el-icon><Tools /></el-icon>
                            </div>
                            <div class="stat-info">
                                <div class="stat-number">{{ statistics.activeEquipment }}</div>
                                <div class="stat-label">在用设备</div>
                            </div>
                        </div>
                    </el-card>
                </el-col>
                <el-col :span="6">
                    <el-card class="stat-card" shadow="hover">
                        <div class="stat-content">
                            <div class="stat-icon">
                                <el-icon><Document /></el-icon>
                            </div>
                            <div class="stat-info">
                                <div class="stat-number">{{ statistics.completedInspections }}</div>
                                <div class="stat-label">已完成检测</div>
                            </div>
                        </div>
                    </el-card>
                </el-col>
                <el-col :span="6">
                    <el-card class="stat-card" shadow="hover">
                        <div class="stat-content">
                            <div class="stat-icon">
                                <el-icon><ShoppingCart /></el-icon>
                            </div>
                            <div class="stat-info">
                                <div class="stat-number">{{ statistics.totalUsage }}</div>
                                <div class="stat-label">总领用次数</div>
                            </div>
                        </div>
                    </el-card>
                </el-col>
            </el-row>
        </div>
        <!-- å›¾è¡¨åŒºåŸŸ -->
        <div class="charts-container">
            <el-row :gutter="20">
                <!-- æ ·å“è¿›åº¦å›¾è¡¨ -->
                <el-col :span="12">
                    <el-card class="chart-card" shadow="hover">
                        <template #header>
                            <div class="card-header">
                                <span>样品进度统计</span>
                                <el-button link @click="refreshSampleChart">刷新</el-button>
                            </div>
                        </template>
                        <div ref="sampleChartRef" class="chart-container"></div>
                    </el-card>
                </el-col>
                <!-- è®¾å¤‡ä½¿ç”¨å›¾è¡¨ -->
                <el-col :span="12">
                    <el-card class="chart-card" shadow="hover">
                        <template #header>
                            <div class="card-header">
                                <span>设备使用率统计</span>
                                <el-button link @click="refreshEquipmentChart">刷新</el-button>
                            </div>
                        </template>
                        <div ref="equipmentChartRef" class="chart-container"></div>
                    </el-card>
                </el-col>
            </el-row>
            <el-row :gutter="20" style="margin-top: 20px;">
                <!-- æ£€æµ‹é¡¹ç›®ç»Ÿè®¡ -->
                <el-col :span="12">
                    <el-card class="chart-card" shadow="hover">
                        <template #header>
                            <div class="card-header">
                                <span>检测项目分布</span>
                                <el-button link @click="refreshInspectionChart">刷新</el-button>
                            </div>
                        </template>
                        <div ref="inspectionChartRef" class="chart-container"></div>
                    </el-card>
                </el-col>
                <!-- é¢†ç”¨è®°å½•趋势 -->
                <el-col :span="12">
                    <el-card class="chart-card" shadow="hover">
                        <template #header>
                            <div class="card-header">
                                <span>领用记录趋势</span>
                                <el-button link @click="refreshUsageChart">刷新</el-button>
                            </div>
                        </template>
                        <div ref="usageChartRef" class="chart-container"></div>
                    </el-card>
                </el-col>
            </el-row>
        </div>
        <!-- è¯¦ç»†æ•°æ®è¡¨æ ¼ -->
        <el-card class="table-card" shadow="hover">
            <template #header>
                <div class="card-header">
                    <span>详细数据</span>
                    <div>
                        <el-button type="primary" size="small" @click="refreshTable">刷新</el-button>
                        <el-button type="success" size="small" @click="exportTable">导出</el-button>
                    </div>
                </div>
            </template>
            <el-table
                :data="tableData"
                style="width: 100%"
                v-loading="tableLoading"
                stripe
                border
            >
                <el-table-column prop="id" label="编号" width="80" />
                <el-table-column prop="name" label="名称" />
                <el-table-column prop="type" label="类型" width="120" />
                <el-table-column prop="status" label="状态" width="100">
                    <template #default="scope">
                        <el-tag :type="getStatusType(scope.row.status)">
                            {{ scope.row.status }}
                        </el-tag>
                    </template>
                </el-table-column>
                <el-table-column prop="progress" label="进度" width="120">
                    <template #default="scope">
                        <el-progress :percentage="scope.row.progress" :status="getProgressStatus(scope.row.progress)" />
                    </template>
                </el-table-column>
                <el-table-column prop="createTime" label="创建时间" width="180" />
                <el-table-column prop="updateTime" label="更新时间" width="180" />
                <el-table-column label="操作" width="150" fixed="right">
                    <template #default="scope">
                        <el-button link size="small" @click="viewDetail(scope.row)">查看</el-button>
                        <el-button link size="small" @click="editItem(scope.row)">编辑</el-button>
                    </template>
                </el-table-column>
            </el-table>
            <div class="pagination-container">
                <el-pagination
                    v-model:current-page="pagination.currentPage"
                    v-model:page-size="pagination.pageSize"
                    :page-sizes="[10, 20, 50, 100]"
                    :total="pagination.total"
                    layout="total, sizes, prev, pager, next, jumper"
                    @size-change="handleSizeChange"
                    @current-change="handleCurrentChange"
                />
            </div>
        </el-card>
    </div>
</template>
<script setup>
import { ref, reactive, onMounted, nextTick } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import * as echarts from 'echarts'
import { Box, Tools, Document, ShoppingCart } from '@element-plus/icons-vue'
// å“åº”式数据
const filterForm = reactive({
    dateRange: [],
    reportType: 'sample'
})
const statistics = reactive({
    totalSamples: 1250,
    activeEquipment: 45,
    completedInspections: 890,
    totalUsage: 2340
})
const tableData = ref([])
const tableLoading = ref(false)
const pagination = reactive({
    currentPage: 1,
    pageSize: 20,
    total: 0
})
// å›¾è¡¨å¼•用
const sampleChartRef = ref(null)
const equipmentChartRef = ref(null)
const inspectionChartRef = ref(null)
const usageChartRef = ref(null)
// å›¾è¡¨å®žä¾‹
let sampleChart = null
let equipmentChart = null
let inspectionChart = null
let usageChart = null
// åˆå§‹åŒ–数据
const initData = () => {
    // æ¨¡æ‹Ÿè¡¨æ ¼æ•°æ®
    tableData.value = [
        {
            id: 'SP001',
            name: '样品A-001',
            type: '金属材料',
            status: '检测中',
            progress: 75,
            createTime: '2025-01-15 09:30:00',
            updateTime: '2025-01-20 14:20:00'
        },
        {
            id: 'SP002',
            name: '样品B-002',
            type: '塑料制品',
            status: '已完成',
            progress: 100,
            createTime: '2025-01-10 10:15:00',
            updateTime: '2025-01-18 16:45:00'
        },
        {
            id: 'SP003',
            name: '样品C-003',
            type: '电子元件',
            status: '待检测',
            progress: 0,
            createTime: '2025-01-22 08:45:00',
            updateTime: '2025-01-22 08:45:00'
        },
        {
            id: 'EQ001',
            name: '检测设备A',
            type: '光谱仪',
            status: '使用中',
            progress: 60,
            createTime: '2025-01-05 14:20:00',
            updateTime: '2025-01-20 11:30:00'
        },
        {
            id: 'EQ002',
            name: '检测设备B',
            type: '显微镜',
            status: '空闲',
            progress: 0,
            createTime: '2025-01-08 16:10:00',
            updateTime: '2025-01-19 09:15:00'
        }
    ]
    pagination.total = tableData.value.length
}
// åˆå§‹åŒ–样品进度图表
const initSampleChart = () => {
    if (sampleChartRef.value) {
        sampleChart = echarts.init(sampleChartRef.value)
        const option = {
            title: {
                show: false
            },
            tooltip: {
                trigger: 'item',
                formatter: '{a} <br/>{b}: {c} ({d}%)'
            },
            legend: {
                orient: 'vertical',
                left: 'left'
            },
            series: [
                {
                    name: '样品状态',
                    type: 'pie',
                    radius: ['40%', '70%'],
                    avoidLabelOverlap: false,
                    label: {
                        show: false,
                        position: 'center'
                    },
                    emphasis: {
                        label: {
                            show: true,
                            fontSize: '18',
                            fontWeight: 'bold'
                        }
                    },
                    labelLine: {
                        show: false
                    },
                    data: [
                        { value: 450, name: '已完成' },
                        { value: 320, name: '检测中' },
                        { value: 280, name: '待检测' },
                        { value: 200, name: '已暂停' }
                    ]
                }
            ]
        }
        sampleChart.setOption(option)
    }
}
// åˆå§‹åŒ–设备使用图表
const initEquipmentChart = () => {
    if (equipmentChartRef.value) {
        equipmentChart = echarts.init(equipmentChartRef.value)
        const option = {
            title: {
                show: false
            },
            tooltip: {
                trigger: 'axis',
                axisPointer: {
                    type: 'shadow'
                }
            },
            xAxis: {
                type: 'category',
                data: ['光谱仪', '显微镜', '硬度计', '拉力机', '冲击机', '金相仪']
            },
            yAxis: {
                type: 'value',
                name: '使用率(%)'
            },
            series: [
                {
                    name: '使用率',
                    type: 'bar',
                    data: [85, 60, 75, 90, 45, 70],
                    itemStyle: {
                        color: function(params) {
                            const colors = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399', '#9C27B0']
                            return colors[params.dataIndex]
                        }
                    }
                }
            ]
        }
        equipmentChart.setOption(option)
    }
}
// åˆå§‹åŒ–检测项目图表
const initInspectionChart = () => {
    if (inspectionChartRef.value) {
        inspectionChart = echarts.init(inspectionChartRef.value)
        const option = {
            title: {
                show: false
            },
            tooltip: {
                trigger: 'item'
            },
            legend: {
                orient: 'vertical',
                left: 'left'
            },
            series: [
                {
                    name: '检测项目',
                    type: 'pie',
                    radius: '50%',
                    data: [
                        { value: 335, name: '物理性能' },
                        { value: 310, name: '化学分析' },
                        { value: 234, name: '尺寸测量' },
                        { value: 135, name: '外观检查' },
                        { value: 148, name: '其他检测' }
                    ],
                    emphasis: {
                        itemStyle: {
                            shadowBlur: 10,
                            shadowOffsetX: 0,
                            shadowColor: 'rgba(0, 0, 0, 0.5)'
                        }
                    }
                }
            ]
        }
        inspectionChart.setOption(option)
    }
}
// åˆå§‹åŒ–领用记录图表
const initUsageChart = () => {
    if (usageChartRef.value) {
        usageChart = echarts.init(usageChartRef.value)
        const option = {
            title: {
                show: false
            },
            tooltip: {
                trigger: 'axis'
            },
            legend: {
                data: ['领用次数', '归还次数']
            },
            xAxis: {
                type: 'category',
                boundaryGap: false,
                data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
            },
            yAxis: {
                type: 'value'
            },
            series: [
                {
                    name: '领用次数',
                    type: 'line',
                    stack: 'Total',
                    data: [120, 132, 101, 134, 90, 230, 210, 182, 191, 234, 290, 330]
                },
                {
                    name: '归还次数',
                    type: 'line',
                    stack: 'Total',
                    data: [110, 125, 95, 128, 85, 220, 200, 175, 185, 225, 280, 320]
                }
            ]
        }
        usageChart.setOption(option)
    }
}
// äº‹ä»¶å¤„理函数
const handleFilterChange = () => {
    ElMessage.success('筛选条件已更新')
    // è¿™é‡Œå¯ä»¥æ ¹æ®ç­›é€‰æ¡ä»¶é‡æ–°åŠ è½½æ•°æ®
}
const resetFilter = () => {
    filterForm.dateRange = []
    filterForm.reportType = 'sample'
    ElMessage.info('筛选条件已重置')
}
const exportReport = () => {
    ElMessage.success('报表导出功能开发中...')
}
const refreshSampleChart = () => {
    initSampleChart()
    ElMessage.success('样品进度图表已刷新')
}
const refreshEquipmentChart = () => {
    initEquipmentChart()
    ElMessage.success('设备使用图表已刷新')
}
const refreshInspectionChart = () => {
    initInspectionChart()
    ElMessage.success('检测项目图表已刷新')
}
const refreshUsageChart = () => {
    initUsageChart()
    ElMessage.success('领用记录图表已刷新')
}
const refreshTable = () => {
    tableLoading.value = true
    setTimeout(() => {
        tableLoading.value = false
        ElMessage.success('表格数据已刷新')
    }, 1000)
}
const exportTable = () => {
    ElMessage.success('表格导出功能开发中...')
}
const handleSizeChange = (val) => {
    pagination.pageSize = val
    // é‡æ–°åŠ è½½æ•°æ®
}
const handleCurrentChange = (val) => {
    pagination.currentPage = val
    // é‡æ–°åŠ è½½æ•°æ®
}
const getStatusType = (status) => {
    const statusMap = {
        '已完成': 'success',
        '检测中': 'warning',
        '待检测': 'info',
        '已暂停': 'danger',
        '使用中': 'primary',
        '空闲': 'info'
    }
    return statusMap[status] || 'info'
}
const getProgressStatus = (progress) => {
    if (progress === 100) return 'success'
    if (progress >= 80) return 'warning'
    if (progress >= 50) return ''
    return 'exception'
}
const viewDetail = (row) => {
    ElMessage.info(`查看详情: ${row.name}`)
}
const editItem = (row) => {
    ElMessage.info(`编辑项目: ${row.name}`)
}
// ç”Ÿå‘½å‘¨æœŸ
onMounted(() => {
    initData()
    nextTick(() => {
        initSampleChart()
        initEquipmentChart()
        initInspectionChart()
        initUsageChart()
    })
    // ç›‘听窗口大小变化,重新调整图表大小
    window.addEventListener('resize', () => {
        sampleChart?.resize()
        equipmentChart?.resize()
        inspectionChart?.resize()
        usageChart?.resize()
    })
})
</script>
<style scoped>
.report-management {
    padding: 20px;
    background-color: #f5f5f5;
    min-height: 100vh;
}
.page-header {
    margin-bottom: 20px;
    text-align: center;
}
.page-header h2 {
    color: #303133;
    margin-bottom: 8px;
    font-size: 24px;
    font-weight: 600;
}
.page-header p {
    color: #909399;
    font-size: 14px;
    margin: 0;
}
.filter-card {
    margin-bottom: 20px;
}
.statistics-cards {
    margin-bottom: 20px;
}
.stat-card {
    height: 120px;
}
.stat-content {
    display: flex;
    align-items: center;
    height: 100%;
}
.stat-icon {
    width: 60px;
    height: 60px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-right: 20px;
    font-size: 24px;
    color: white;
}
.stat-card:nth-child(1) .stat-icon {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.stat-card:nth-child(2) .stat-icon {
    background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
.stat-card:nth-child(3) .stat-icon {
    background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}
.stat-card:nth-child(4) .stat-icon {
    background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
}
.stat-info {
    flex: 1;
}
.stat-number {
    font-size: 28px;
    font-weight: bold;
    color: #303133;
    margin-bottom: 8px;
}
.stat-label {
    font-size: 14px;
    color: #909399;
}
.charts-container {
    margin-bottom: 20px;
}
.chart-card {
    margin-bottom: 20px;
}
.card-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
}
.chart-container {
    height: 300px;
    width: 100%;
}
.table-card {
    margin-bottom: 20px;
}
.pagination-container {
    margin-top: 20px;
    text-align: right;
}
:deep(.el-card__header) {
    padding: 15px 20px;
    border-bottom: 1px solid #ebeef5;
    background-color: #fafafa;
}
:deep(.el-card__body) {
    padding: 20px;
}
:deep(.el-table) {
    margin-bottom: 20px;
}
:deep(.el-progress) {
    margin: 0;
}
:deep(.el-tag) {
    margin: 0;
}
</style>
src/views/salesManagement/customerManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,423 @@
<template>
  <div class="app-container">
    <el-card class="box-card">
      <!-- æœç´¢åŒºåŸŸ -->
      <el-row :gutter="20" class="search-row">
        <el-col :span="6">
          <el-input
            v-model="searchForm.name"
            placeholder="请输入客户名称"
            clearable
            @keyup.enter="handleSearch"
          >
            <template #prefix>
              <el-icon><Search /></el-icon>
            </template>
          </el-input>
        </el-col>
        <el-col :span="6">
          <el-select v-model="searchForm.region" placeholder="请选择区域" clearable>
            <el-option label="华东区" value="华东区"></el-option>
            <el-option label="华南区" value="华南区"></el-option>
            <el-option label="华北区" value="华北区"></el-option>
            <el-option label="西南区" value="西南区"></el-option>
          </el-select>
        </el-col>
        <el-col :span="6">
          <el-select v-model="searchForm.level" placeholder="请选择客户等级" clearable>
            <el-option label="VIP客户" value="VIP客户"></el-option>
            <el-option label="重要客户" value="重要客户"></el-option>
            <el-option label="普通客户" value="普通客户"></el-option>
          </el-select>
        </el-col>
        <el-col :span="6">
          <el-button type="primary" @click="handleSearch">搜索</el-button>
          <el-button @click="resetSearch">重置</el-button>
          <el-button style="float: right;" type="primary" @click="handleAdd">
            æ–°å¢žå®¢æˆ·
          </el-button>
        </el-col>
      </el-row>
      <!-- å®¢æˆ·åˆ—表 -->
      <el-table
        :data="filteredList"
        style="width: 100%"
        v-loading="loading"
        border
        stripe
        height="calc(100vh - 22em)"
      >
        <el-table-column prop="id" label="ID" width="80" align="center"/>
        <el-table-column prop="name" label="客户名称" width="150" />
        <el-table-column prop="contactPerson" label="联系人" width="100" />
        <el-table-column prop="phone" label="联系电话" width="140" />
        <el-table-column prop="email" label="邮箱" />
        <el-table-column prop="region" label="区域" width="100" />
        <el-table-column prop="level" label="客户等级" width="100">
          <template #default="scope">
            <el-tag :type="getLevelType(scope.row.level)">
              {{ scope.row.level }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="salesperson" label="负责业务员" width="120" />
        <el-table-column prop="status" label="状态" width="80">
          <template #default="scope">
            <el-tag :type="getStatusType(scope.row.status)">
              {{ scope.row.status }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="操作" width="200" fixed="right" align="center">
          <template #default="scope">
            <el-button link type="primary" @click="handleEdit(scope.row)">编辑</el-button>
            <el-button link type="primary" @click="handleAllocation(scope.row)">分配</el-button>
            <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- åˆ†é¡µ -->
      <pagination
        :total="pagination.total"
        layout="total, sizes, prev, pager, next, jumper"
        :page="pagination.currentPage"
        :limit="pagination.pageSize"
        @pagination="handleCurrentChange"
      />
    </el-card>
    <!-- æ–°å¢ž/编辑对话框 -->
    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px">
      <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="客户名称" prop="name">
              <el-input v-model="form.name" placeholder="请输入客户名称"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="联系人" prop="contactPerson">
              <el-input v-model="form.contactPerson" placeholder="请输入联系人"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="联系电话" prop="phone">
              <el-input v-model="form.phone" placeholder="请输入联系电话"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="邮箱" prop="email">
              <el-input v-model="form.email" placeholder="请输入邮箱"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="区域" prop="region">
              <el-select v-model="form.region" placeholder="请选择区域" style="width: 100%">
                <el-option label="华东区" value="华东区"></el-option>
                <el-option label="华南区" value="华南区"></el-option>
                <el-option label="华北区" value="华北区"></el-option>
                <el-option label="西南区" value="西南区"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="客户等级" prop="level">
              <el-select v-model="form.level" placeholder="请选择客户等级" style="width: 100%">
                <el-option label="VIP客户" value="VIP客户"></el-option>
                <el-option label="重要客户" value="重要客户"></el-option>
                <el-option label="普通客户" value="普通客户"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="负责业务员" prop="salesperson">
              <el-select v-model="form.salesperson" placeholder="请选择业务员" style="width: 100%">
                <el-option label="陈志强" value="陈志强"></el-option>
                <el-option label="刘雅婷" value="刘雅婷"></el-option>
                <el-option label="王建国" value="王建国"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="状态" prop="status">
              <el-select v-model="form.status" placeholder="请选择状态" style="width: 100%">
                <el-option label="活跃" value="活跃"></el-option>
                <el-option label="潜在" value="潜在"></el-option>
                <el-option label="流失" value="流失"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="dialogVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="handleSubmit">ç¡® å®š</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- å®¢æˆ·åˆ†é…å¯¹è¯æ¡† -->
    <el-dialog v-model="allocationDialogVisible" title="客户分配" width="500px">
      <el-form label-width="100px">
        <el-form-item label="客户名称">
          <span>{{ currentCustomer.name }}</span>
        </el-form-item>
        <el-form-item label="当前业务员">
          <span>{{ currentCustomer.salesperson }}</span>
        </el-form-item>
        <el-form-item label="重新分配">
          <el-select v-model="newSalesperson" placeholder="请选择新业务员" style="width: 100%">
            <el-option label="陈志强" value="陈志强"></el-option>
            <el-option label="刘雅婷" value="刘雅婷"></el-option>
            <el-option label="王建国" value="王建国"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="分配原因">
          <el-input v-model="allocationReason" type="textarea" rows="3" placeholder="请输入分配原因"></el-input>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="allocationDialogVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="saveAllocation">ç¡® å®š</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Search } from '@element-plus/icons-vue'
import Pagination from '@/components/PIMTable/Pagination.vue'
// å“åº”式数据
const loading = ref(false)
const searchForm = reactive({
  name: '',
  region: '',
  level: ''
})
const customerList = ref([
  {
    id: 1,
    name: '上海科技有限公司',
    contactPerson: '陈志强',
    phone: '021-12345678',
    email: 'zhang@shanghai-tech.com',
    region: '华东区',
    level: 'VIP客户',
    salesperson: '陈志强',
    status: '活跃'
  },
  {
    id: 2,
    name: '深圳电子有限公司',
    contactPerson: '刘雅婷',
    phone: '0755-87654321',
    email: 'li@shenzhen-elec.com',
    region: '华南区',
    level: '重要客户',
    salesperson: '刘雅婷',
    status: '活跃'
  },
  {
    id: 3,
    name: '北京贸易公司',
    contactPerson: '王建国',
    phone: '010-11223344',
    email: 'wang@beijing-trade.com',
    region: '华北区',
    level: '普通客户',
    salesperson: '王建国',
    status: '潜在'
  }
])
const pagination = reactive({
  total: 3,
  currentPage: 1,
  pageSize: 10
})
const dialogVisible = ref(false)
const dialogTitle = ref('新增客户')
const form = reactive({
  name: '',
  contactPerson: '',
  phone: '',
  email: '',
  region: '',
  level: '',
  salesperson: '',
  status: '活跃'
})
const rules = {
  name: [{ required: true, message: '请输入客户名称', trigger: 'blur' }],
  contactPerson: [{ required: true, message: '请输入联系人', trigger: 'blur' }],
  phone: [{ required: true, message: '请输入联系电话', trigger: 'blur' }],
  email: [{ required: true, message: '请输入邮箱', trigger: 'blur' }],
  region: [{ required: true, message: '请选择区域', trigger: 'change' }],
  level: [{ required: true, message: '请选择客户等级', trigger: 'change' }],
  salesperson: [{ required: true, message: '请选择业务员', trigger: 'change' }],
  status: [{ required: true, message: '请选择状态', trigger: 'change' }]
}
const isEdit = ref(false)
const editId = ref(null)
const allocationDialogVisible = ref(false)
const currentCustomer = ref({})
const newSalesperson = ref('')
const allocationReason = ref('')
const formRef = ref()
// è®¡ç®—属性
const filteredList = computed(() => {
  let list = customerList.value
  if (searchForm.name) {
    list = list.filter(item => item.name.includes(searchForm.name))
  }
  if (searchForm.region) {
    list = list.filter(item => item.region === searchForm.region)
  }
  if (searchForm.level) {
    list = list.filter(item => item.level === searchForm.level)
  }
  return list
})
// æ–¹æ³•
const getLevelType = (level) => {
  const levelMap = {
    'VIP客户': 'danger',
    '重要客户': 'warning',
    '普通客户': 'info'
  }
  return levelMap[level] || 'info'
}
const getStatusType = (status) => {
  const statusMap = {
    '活跃': 'success',
    '潜在': 'warning',
    '流失': 'danger'
  }
  return statusMap[status] || 'info'
}
const handleSearch = () => {
  // æœç´¢é€»è¾‘已在computed中处理
}
const resetSearch = () => {
  searchForm.name = ''
  searchForm.region = ''
  searchForm.level = ''
}
const handleAdd = () => {
  dialogTitle.value = '新增客户'
  isEdit.value = false
  form.name = ''
  form.contactPerson = ''
  form.phone = ''
  form.email = ''
  form.region = ''
  form.level = ''
  form.salesperson = ''
  form.status = '活跃'
  dialogVisible.value = true
}
const handleEdit = (row) => {
  dialogTitle.value = '编辑客户'
  isEdit.value = true
  editId.value = row.id
  Object.assign(form, row)
  dialogVisible.value = true
}
const handleDelete = (row) => {
  ElMessageBox.confirm('确认删除该客户吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    const index = customerList.value.findIndex(item => item.id === row.id)
    if (index > -1) {
      customerList.value.splice(index, 1)
      pagination.total--
      ElMessage.success('删除成功')
    }
  })
}
const handleAllocation = (row) => {
  currentCustomer.value = row
  newSalesperson.value = ''
  allocationReason.value = ''
  allocationDialogVisible.value = true
}
const saveAllocation = () => {
  if (!newSalesperson.value) {
    ElMessage.warning('请选择新业务员')
    return
  }
  const index = customerList.value.findIndex(item => item.id === currentCustomer.value.id)
  if (index > -1) {
    customerList.value[index].salesperson = newSalesperson.value
    ElMessage.success('客户分配成功')
    allocationDialogVisible.value = false
  }
}
const handleSubmit = () => {
  formRef.value.validate((valid) => {
    if (valid) {
      if (isEdit.value) {
        // ç¼–辑
        const index = customerList.value.findIndex(item => item.id === editId.value)
        if (index > -1) {
          customerList.value[index] = { ...form, id: editId.value }
          ElMessage.success('编辑成功')
        }
      } else {
        // æ–°å¢ž
        const newId = Math.max(...customerList.value.map(item => item.id)) + 1
        customerList.value.push({
          ...form,
          id: newId
        })
        pagination.total++
        ElMessage.success('新增成功')
      }
      dialogVisible.value = false
    }
  })
}
const handleCurrentChange = (val) => {
  pagination.currentPage = val.page
  pagination.pageSize = val.limit
}
</script>
<style scoped>
.search-row {
  margin-bottom: 20px;
}
</style>
src/views/salesManagement/orderManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,495 @@
<template>
  <div class="app-container">
    <el-card class="box-card">
      <!-- æœç´¢åŒºåŸŸ -->
      <el-row :gutter="20" class="search-row">
        <el-col :span="6">
          <el-input
            v-model="searchForm.orderNo"
            placeholder="请输入订单号"
            clearable
            @keyup.enter="handleSearch"
          >
            <template #prefix>
              <el-icon><Search /></el-icon>
            </template>
          </el-input>
        </el-col>
        <el-col :span="6">
          <el-select v-model="searchForm.customer" placeholder="请选择客户" clearable>
            <el-option label="上海科技有限公司" value="上海科技有限公司"></el-option>
            <el-option label="深圳电子有限公司" value="深圳电子有限公司"></el-option>
            <el-option label="北京贸易公司" value="北京贸易公司"></el-option>
          </el-select>
        </el-col>
        <el-col :span="6">
          <el-select v-model="searchForm.status" placeholder="请选择订单状态" clearable>
            <el-option label="待审核" value="待审核"></el-option>
            <el-option label="已审核" value="已审核"></el-option>
            <el-option label="已发货" value="已发货"></el-option>
            <el-option label="已完成" value="已完成"></el-option>
            <el-option label="已取消" value="已取消"></el-option>
          </el-select>
        </el-col>
        <el-col :span="6">
          <el-button type="primary" @click="handleSearch">搜索</el-button>
          <el-button @click="resetSearch">重置</el-button>
          <el-button style="float: right;" type="primary" @click="handleAdd">
            æ–°å¢žè®¢å•
          </el-button>
        </el-col>
      </el-row>
      <!-- è®¢å•列表 -->
      <el-table
        :data="filteredList"
        style="width: 100%"
        v-loading="loading"
        border
        stripe
        height="calc(100vh - 22em)"
      >
        <el-table-column prop="id" label="ID" width="80" align="center"/>
        <el-table-column prop="orderNo" label="订单号" width="150" />
        <el-table-column prop="customer" label="客户名称" />
        <el-table-column prop="salesperson" label="业务员" width="100" />
        <el-table-column prop="orderDate" label="下单日期" width="120" />
        <el-table-column prop="amount" label="订单金额" width="120">
          <template #default="scope">
            Â¥{{ scope.row.amount.toFixed(2) }}
          </template>
        </el-table-column>
        <el-table-column prop="paymentMethod" label="付款方式" width="120" />
        <el-table-column prop="status" label="订单状态" width="100">
          <template #default="scope">
            <el-tag :type="getStatusType(scope.row.status)">
              {{ scope.row.status }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="操作" width="250" fixed="right" align="center">
          <template #default="scope">
            <el-button link type="primary" @click="handleView(scope.row)">查看</el-button>
            <el-button link type="primary" @click="handleEdit(scope.row)" v-if="scope.row.status === '待审核'">编辑</el-button>
            <el-button link type="primary" @click="handleReview(scope.row)" v-if="scope.row.status === '待审核'">审核</el-button>
            <el-button link type="primary" @click="handleTransfer(scope.row)" v-if="scope.row.status === '已审核'">转单</el-button>
            <el-button link type="danger" @click="handleCancel(scope.row)" v-if="scope.row.status === '待审核'">取消</el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- åˆ†é¡µ -->
      <pagination
        :total="pagination.total"
        layout="total, sizes, prev, pager, next, jumper"
        :page="pagination.currentPage"
        :limit="pagination.pageSize"
        @pagination="handleCurrentChange"
      />
    </el-card>
    <!-- æ–°å¢ž/编辑对话框 -->
    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="700px">
      <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="客户名称" prop="customer">
              <el-select v-model="form.customer" placeholder="请选择客户" style="width: 100%">
                <el-option label="上海科技有限公司" value="上海科技有限公司"></el-option>
                <el-option label="深圳电子有限公司" value="深圳电子有限公司"></el-option>
                <el-option label="北京贸易公司" value="北京贸易公司"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="业务员" prop="salesperson">
              <el-select v-model="form.salesperson" placeholder="请选择业务员" style="width: 100%">
                <el-option label="陈志强" value="陈志强"></el-option>
                <el-option label="刘雅婷" value="刘雅婷"></el-option>
                <el-option label="王建国" value="王建国"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="订单日期" prop="orderDate">
              <el-date-picker
                v-model="form.orderDate"
                type="date"
                placeholder="选择订单日期"
                style="width: 100%"
                format="YYYY-MM-DD"
                value-format="YYYY-MM-DD"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="订单金额" prop="amount">
              <el-input-number v-model="form.amount" :precision="2" :min="0" style="width: 100%"></el-input-number>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="付款方式" prop="paymentMethod">
              <el-select v-model="form.paymentMethod" placeholder="请选择付款方式" style="width: 100%">
                <el-option label="全款到付" value="全款到付"></el-option>
                <el-option label="分期付款" value="分期付款"></el-option>
                <el-option label="月结" value="月结"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="订单状态" prop="status">
              <el-select v-model="form.status" placeholder="请选择状态" style="width: 100%">
                <el-option label="待审核" value="待审核"></el-option>
                <el-option label="已审核" value="已审核"></el-option>
                <el-option label="已发货" value="已发货"></el-option>
                <el-option label="已完成" value="已完成"></el-option>
                <el-option label="已取消" value="已取消"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="备注" prop="remark">
              <el-input type="textarea" v-model="form.remark" placeholder="请输入备注信息" rows="3"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="dialogVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="handleSubmit">ç¡® å®š</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- è®¢å•审核对话框 -->
    <el-dialog v-model="reviewDialogVisible" title="订单审核" width="500px">
      <el-form label-width="100px">
        <el-form-item label="订单号">
          <span>{{ currentOrder.orderNo }}</span>
        </el-form-item>
        <el-form-item label="客户名称">
          <span>{{ currentOrder.customer }}</span>
        </el-form-item>
        <el-form-item label="订单金额">
          <span>Â¥{{ currentOrder.amount.toFixed(2) }}</span>
        </el-form-item>
        <el-form-item label="审核结果" prop="reviewResult">
          <el-radio-group v-model="reviewResult">
            <el-radio label="通过">通过</el-radio>
            <el-radio label="拒绝">拒绝</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="审核意见" prop="reviewComment">
          <el-input type="textarea" v-model="reviewComment" rows="3" placeholder="请输入审核意见"></el-input>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="reviewDialogVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="saveReview">ç¡® å®š</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- è®¢å•转单对话框 -->
    <el-dialog v-model="transferDialogVisible" title="订单转单" width="500px">
      <el-form label-width="100px">
        <el-form-item label="订单号">
          <span>{{ currentOrder.orderNo }}</span>
        </el-form-item>
        <el-form-item label="当前业务员">
          <span>{{ currentOrder.salesperson }}</span>
        </el-form-item>
        <el-form-item label="转单给" prop="newSalesperson">
          <el-select v-model="newSalesperson" placeholder="请选择新业务员" style="width: 100%">
            <el-option label="陈志强" value="陈志强"></el-option>
            <el-option label="刘雅婷" value="刘雅婷"></el-option>
            <el-option label="王建国" value="王建国"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="转单原因" prop="transferReason">
          <el-input type="textarea" v-model="transferReason" rows="3" placeholder="请输入转单原因"></el-input>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="transferDialogVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="saveTransfer">ç¡® å®š</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Search } from '@element-plus/icons-vue'
import Pagination from '@/components/PIMTable/Pagination.vue'
// å“åº”式数据
const loading = ref(false)
const searchForm = reactive({
  orderNo: '',
  customer: '',
  status: ''
})
const orderList = ref([
  {
    id: 1,
    orderNo: 'ORD202312001',
    customer: '上海科技有限公司',
    salesperson: '陈志强',
    orderDate: '2023-12-01',
    amount: 50000.00,
    paymentMethod: '全款到付',
    status: '待审核',
    remark: '重要客户订单'
  },
  {
    id: 2,
    orderNo: 'ORD202312002',
    customer: '深圳电子有限公司',
    salesperson: '刘雅婷',
    orderDate: '2023-12-02',
    amount: 35000.00,
    paymentMethod: '分期付款',
    status: '已审核',
    remark: '常规订单'
  },
  {
    id: 3,
    orderNo: 'ORD202312003',
    customer: '北京贸易公司',
    salesperson: '王建国',
    orderDate: '2023-12-03',
    amount: 28000.00,
    paymentMethod: '月结',
    status: '已发货',
    remark: '新客户订单'
  }
])
const pagination = reactive({
  total: 3,
  currentPage: 1,
  pageSize: 10
})
const dialogVisible = ref(false)
const dialogTitle = ref('新增订单')
const form = reactive({
  customer: '',
  salesperson: '',
  orderDate: '',
  amount: 0,
  paymentMethod: '',
  status: '待审核',
  remark: ''
})
const rules = {
  customer: [{ required: true, message: '请选择客户', trigger: 'change' }],
  salesperson: [{ required: true, message: '请选择业务员', trigger: 'change' }],
  orderDate: [{ required: true, message: '请选择订单日期', trigger: 'change' }],
  amount: [{ required: true, message: '请输入订单金额', trigger: 'blur' }],
  paymentMethod: [{ required: true, message: '请选择付款方式', trigger: 'change' }],
  status: [{ required: true, message: '请选择状态', trigger: 'change' }]
}
const isEdit = ref(false)
const editId = ref(null)
const reviewDialogVisible = ref(false)
const transferDialogVisible = ref(false)
const currentOrder = ref({})
const reviewResult = ref('')
const reviewComment = ref('')
const newSalesperson = ref('')
const transferReason = ref('')
const formRef = ref()
// è®¡ç®—属性
const filteredList = computed(() => {
  let list = orderList.value
  if (searchForm.orderNo) {
    list = list.filter(item => item.orderNo.includes(searchForm.orderNo))
  }
  if (searchForm.customer) {
    list = list.filter(item => item.customer === searchForm.customer)
  }
  if (searchForm.status) {
    list = list.filter(item => item.status === searchForm.status)
  }
  return list
})
// æ–¹æ³•
const getStatusType = (status) => {
  const statusMap = {
    '待审核': 'warning',
    '已审核': 'primary',
    '已发货': 'success',
    '已完成': 'success',
    '已取消': 'danger'
  }
  return statusMap[status] || 'info'
}
const handleSearch = () => {
  // æœç´¢é€»è¾‘已在computed中处理
}
const resetSearch = () => {
  searchForm.orderNo = ''
  searchForm.customer = ''
  searchForm.status = ''
}
const handleAdd = () => {
  dialogTitle.value = '新增订单'
  isEdit.value = false
  form.customer = ''
  form.salesperson = ''
  form.orderDate = ''
  form.amount = 0
  form.paymentMethod = ''
  form.status = '待审核'
  form.remark = ''
  dialogVisible.value = true
}
const handleView = (row) => {
  // æŸ¥çœ‹è®¢å•详情
  ElMessage.info('查看订单详情功能待实现')
}
const handleEdit = (row) => {
  dialogTitle.value = '编辑订单'
  isEdit.value = true
  editId.value = row.id
  Object.assign(form, row)
  dialogVisible.value = true
}
const handleReview = (row) => {
  currentOrder.value = row
  reviewResult.value = ''
  reviewComment.value = ''
  reviewDialogVisible.value = true
}
const handleTransfer = (row) => {
  currentOrder.value = row
  newSalesperson.value = ''
  transferReason.value = ''
  transferDialogVisible.value = true
}
const handleCancel = (row) => {
  ElMessageBox.confirm('确认取消该订单吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    const index = orderList.value.findIndex(item => item.id === row.id)
    if (index > -1) {
      orderList.value[index].status = '已取消'
      ElMessage.success('订单已取消')
    }
  })
}
const handleDelete = (row) => {
  ElMessageBox.confirm('确认删除该订单吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    const index = orderList.value.findIndex(item => item.id === row.id)
    if (index > -1) {
      orderList.value.splice(index, 1)
      pagination.total--
      ElMessage.success('删除成功')
    }
  })
}
const saveReview = () => {
  if (!reviewResult.value) {
    ElMessage.warning('请选择审核结果')
    return
  }
  const index = orderList.value.findIndex(item => item.id === currentOrder.value.id)
  if (index > -1) {
    if (reviewResult.value === '通过') {
      orderList.value[index].status = '已审核'
      ElMessage.success('订单审核通过')
    } else {
      orderList.value[index].status = '已取消'
      ElMessage.success('订单审核拒绝')
    }
    reviewDialogVisible.value = false
  }
}
const saveTransfer = () => {
  if (!newSalesperson.value) {
    ElMessage.warning('请选择新业务员')
    return
  }
  const index = orderList.value.findIndex(item => item.id === currentOrder.value.id)
  if (index > -1) {
    orderList.value[index].salesperson = newSalesperson.value
    ElMessage.success('订单转单成功')
    transferDialogVisible.value = false
  }
}
const handleSubmit = () => {
  formRef.value.validate((valid) => {
    if (valid) {
      if (isEdit.value) {
        // ç¼–辑
        const index = orderList.value.findIndex(item => item.id === editId.value)
        if (index > -1) {
          orderList.value[index] = { ...form, id: editId.value }
          ElMessage.success('编辑成功')
        }
      } else {
        // æ–°å¢ž
        const newId = Math.max(...orderList.value.map(item => item.id)) + 1
        const orderNo = `ORD${new Date().getFullYear()}${String(new Date().getMonth() + 1).padStart(2, '0')}${String(new Date().getDate()).padStart(2, '0')}${String(newId).padStart(3, '0')}`
        orderList.value.push({
          ...form,
          id: newId,
          orderNo: orderNo
        })
        pagination.total++
        ElMessage.success('新增成功')
      }
      dialogVisible.value = false
    }
  })
}
const handleCurrentChange = (val) => {
  pagination.currentPage = val.page
  pagination.pageSize = val.limit
}
</script>
<style scoped>
.search-row {
  margin-bottom: 20px;
}
</style>
src/views/salesManagement/paymentShipping/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,534 @@
<template>
  <div class="app-container">
    <el-card class="box-card">
      <!-- æœç´¢åŒºåŸŸ -->
      <el-row :gutter="20" class="search-row">
        <el-col :span="6">
          <el-input
            v-model="searchForm.orderNo"
            placeholder="请输入订单号"
            clearable
            @keyup.enter="handleSearch"
          >
            <template #prefix>
              <el-icon><Search /></el-icon>
            </template>
          </el-input>
        </el-col>
        <el-col :span="6">
          <el-select v-model="searchForm.paymentStatus" placeholder="请选择付款状态" clearable>
            <el-option label="未付款" value="未付款"></el-option>
            <el-option label="已付款" value="已付款"></el-option>
            <el-option label="部分付款" value="部分付款"></el-option>
          </el-select>
        </el-col>
        <el-col :span="6">
          <el-select v-model="searchForm.shippingStatus" placeholder="请选择发货状态" clearable>
            <el-option label="待发货" value="待发货"></el-option>
            <el-option label="已发货" value="已发货"></el-option>
            <el-option label="已签收" value="已签收"></el-option>
          </el-select>
        </el-col>
        <el-col :span="6">
          <el-button type="primary" @click="handleSearch">搜索</el-button>
          <el-button @click="resetSearch">重置</el-button>
          <el-button style="float: right;" type="primary" @click="handleAdd">
            æ–°å¢žè®°å½•
          </el-button>
        </el-col>
      </el-row>
      <!-- æ”¯ä»˜ä¸Žå‘货列表 -->
      <el-table
        :data="filteredList"
        style="width: 100%"
        v-loading="loading"
        border
        stripe
        height="calc(100vh - 22em)"
      >
        <el-table-column prop="id" label="ID" width="80" align="center"/>
        <el-table-column prop="orderNo" label="订单号" />
        <el-table-column prop="customer" label="客户名称" />
        <el-table-column prop="orderAmount" label="订单金额" width="120">
          <template #default="scope">
            Â¥{{ scope.row.orderAmount.toFixed(2) }}
          </template>
        </el-table-column>
        <el-table-column prop="paymentMethod" label="付款方式" width="120" />
        <el-table-column prop="paymentStatus" label="付款状态" width="100">
          <template #default="scope">
            <el-tag :type="getPaymentStatusType(scope.row.paymentStatus)">
              {{ scope.row.paymentStatus }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="shippingStatus" label="发货状态" width="100">
          <template #default="scope">
            <el-tag :type="getShippingStatusType(scope.row.shippingStatus)">
              {{ scope.row.shippingStatus }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="shippingDate" label="发货日期" width="120" />
        <el-table-column label="操作" width="250" fixed="right" align="center">
          <template #default="scope">
            <el-button link type="primary" @click="handleView(scope.row)">查看</el-button>
            <el-button link type="primary" @click="handlePayment(scope.row)" v-if="scope.row.paymentStatus !== '已付款'">付款</el-button>
            <el-button link type="primary" @click="handleShipping(scope.row)" v-if="scope.row.paymentStatus === '已付款' && scope.row.shippingStatus === '待发货'">发货</el-button>
            <el-button link type="primary" @click="handleEdit(scope.row)">编辑</el-button>
            <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- åˆ†é¡µ -->
      <pagination
        :total="pagination.total"
        layout="total, sizes, prev, pager, next, jumper"
        :page="pagination.currentPage"
        :limit="pagination.pageSize"
        @pagination="handleCurrentChange"
      />
    </el-card>
    <!-- æ–°å¢ž/编辑对话框 -->
    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="700px">
      <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="订单号" prop="orderNo">
              <el-input v-model="form.orderNo" placeholder="请输入订单号"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="客户名称" prop="customer">
              <el-select v-model="form.customer" placeholder="请选择客户" style="width: 100%">
                <el-option label="上海科技有限公司" value="上海科技有限公司"></el-option>
                <el-option label="深圳电子有限公司" value="深圳电子有限公司"></el-option>
                <el-option label="北京贸易公司" value="北京贸易公司"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="订单金额" prop="orderAmount">
              <el-input-number v-model="form.orderAmount" :precision="2" :min="0" style="width: 100%"></el-input-number>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="付款方式" prop="paymentMethod">
              <el-select v-model="form.paymentMethod" placeholder="请选择付款方式" style="width: 100%">
                <el-option label="全款到付" value="全款到付"></el-option>
                <el-option label="分期付款" value="分期付款"></el-option>
                <el-option label="月结" value="月结"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="付款状态" prop="paymentStatus">
              <el-select v-model="form.paymentStatus" placeholder="请选择付款状态" style="width: 100%">
                <el-option label="未付款" value="未付款"></el-option>
                <el-option label="已付款" value="已付款"></el-option>
                <el-option label="部分付款" value="部分付款"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="发货状态" prop="shippingStatus">
              <el-select v-model="form.shippingStatus" placeholder="请选择发货状态" style="width: 100%">
                <el-option label="待发货" value="待发货"></el-option>
                <el-option label="已发货" value="已发货"></el-option>
                <el-option label="已签收" value="已签收"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="发货日期" prop="shippingDate">
              <el-date-picker
                v-model="form.shippingDate"
                type="date"
                placeholder="选择发货日期"
                style="width: 100%"
                format="YYYY-MM-DD"
                value-format="YYYY-MM-DD"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="物流单号" prop="trackingNo">
              <el-input v-model="form.trackingNo" placeholder="请输入物流单号"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="备注" prop="remark">
              <el-input type="textarea" v-model="form.remark" placeholder="请输入备注信息" rows="3"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="dialogVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="handleSubmit">ç¡® å®š</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- ä»˜æ¬¾å¯¹è¯æ¡† -->
    <el-dialog v-model="paymentDialogVisible" title="订单付款" width="500px">
      <el-form label-width="100px">
        <el-form-item label="订单号">
          <span>{{ currentRecord.orderNo }}</span>
        </el-form-item>
        <el-form-item label="客户名称">
          <span>{{ currentRecord.customer }}</span>
        </el-form-item>
        <el-form-item label="订单金额">
          <span>Â¥{{ currentRecord.orderAmount.toFixed(2) }}</span>
        </el-form-item>
        <el-form-item label="付款金额" prop="paymentAmount">
          <el-input-number v-model="paymentAmount" :precision="2" :min="0" :max="currentRecord.orderAmount" style="width: 100%"></el-input-number>
        </el-form-item>
        <el-form-item label="付款方式" prop="paymentMethod">
          <el-select v-model="paymentMethod" placeholder="请选择付款方式" style="width: 100%">
            <el-option label="现金" value="现金"></el-option>
            <el-option label="银行转账" value="银行转账"></el-option>
            <el-option label="支付宝" value="支付宝"></el-option>
            <el-option label="微信支付" value="微信支付"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="付款备注" prop="paymentRemark">
          <el-input type="textarea" v-model="paymentRemark" rows="3" placeholder="请输入付款备注"></el-input>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="paymentDialogVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="savePayment">ç¡® å®š</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- å‘货对话框 -->
    <el-dialog v-model="shippingDialogVisible" title="订单发货" width="500px">
      <el-form label-width="100px">
        <el-form-item label="订单号">
          <span>{{ currentRecord.orderNo }}</span>
        </el-form-item>
        <el-form-item label="客户名称">
          <span>{{ currentRecord.customer }}</span>
        </el-form-item>
        <el-form-item label="发货日期" prop="shippingDate">
          <el-date-picker
            v-model="shippingDate"
            type="date"
            placeholder="选择发货日期"
            style="width: 100%"
            format="YYYY-MM-DD"
            value-format="YYYY-MM-DD"
          />
        </el-form-item>
        <el-form-item label="物流公司" prop="logisticsCompany">
          <el-select v-model="logisticsCompany" placeholder="请选择物流公司" style="width: 100%">
            <el-option label="顺丰速运" value="顺丰速运"></el-option>
            <el-option label="圆通速递" value="圆通速递"></el-option>
            <el-option label="中通快递" value="中通快递"></el-option>
            <el-option label="申通快递" value="申通快递"></el-option>
            <el-option label="韵达速递" value="韵达速递"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="物流单号" prop="trackingNo">
          <el-input v-model="trackingNo" placeholder="请输入物流单号"></el-input>
        </el-form-item>
        <el-form-item label="发货备注" prop="shippingRemark">
          <el-input type="textarea" v-model="shippingRemark" rows="3" placeholder="请输入发货备注"></el-input>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="shippingDialogVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="saveShipping">ç¡® å®š</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Search } from '@element-plus/icons-vue'
import Pagination from '@/components/PIMTable/Pagination.vue'
// å“åº”式数据
const loading = ref(false)
const searchForm = reactive({
  orderNo: '',
  paymentStatus: '',
  shippingStatus: ''
})
const recordList = ref([
  {
    id: 1,
    orderNo: 'ORD202312001',
    customer: '上海科技有限公司',
    orderAmount: 50000.00,
    paymentMethod: '全款到付',
    paymentStatus: '已付款',
    shippingStatus: '已发货',
    shippingDate: '2023-12-05',
    trackingNo: 'SF1234567890',
    remark: '重要客户订单'
  },
  {
    id: 2,
    orderNo: 'ORD202312002',
    customer: '深圳电子有限公司',
    orderAmount: 35000.00,
    paymentMethod: '分期付款',
    paymentStatus: '部分付款',
    shippingStatus: '待发货',
    shippingDate: '',
    trackingNo: '',
    remark: '常规订单'
  },
  {
    id: 3,
    orderNo: 'ORD202312003',
    customer: '北京贸易公司',
    orderAmount: 28000.00,
    paymentMethod: '月结',
    paymentStatus: '未付款',
    shippingStatus: '待发货',
    shippingDate: '',
    trackingNo: '',
    remark: '新客户订单'
  }
])
const pagination = reactive({
  total: 3,
  currentPage: 1,
  pageSize: 10
})
const dialogVisible = ref(false)
const dialogTitle = ref('新增记录')
const form = reactive({
  orderNo: '',
  customer: '',
  orderAmount: 0,
  paymentMethod: '',
  paymentStatus: '未付款',
  shippingStatus: '待发货',
  shippingDate: '',
  trackingNo: '',
  remark: ''
})
const rules = {
  orderNo: [{ required: true, message: '请输入订单号', trigger: 'blur' }],
  customer: [{ required: true, message: '请选择客户', trigger: 'change' }],
  orderAmount: [{ required: true, message: '请输入订单金额', trigger: 'blur' }],
  paymentMethod: [{ required: true, message: '请选择付款方式', trigger: 'change' }],
  paymentStatus: [{ required: true, message: '请选择付款状态', trigger: 'change' }],
  shippingStatus: [{ required: true, message: '请选择发货状态', trigger: 'change' }]
}
const isEdit = ref(false)
const editId = ref(null)
const paymentDialogVisible = ref(false)
const shippingDialogVisible = ref(false)
const currentRecord = ref({})
const paymentAmount = ref(0)
const paymentMethod = ref('')
const paymentRemark = ref('')
const shippingDate = ref('')
const logisticsCompany = ref('')
const trackingNo = ref('')
const shippingRemark = ref('')
const formRef = ref()
// è®¡ç®—属性
const filteredList = computed(() => {
  let list = recordList.value
  if (searchForm.orderNo) {
    list = list.filter(item => item.orderNo.includes(searchForm.orderNo))
  }
  if (searchForm.paymentStatus) {
    list = list.filter(item => item.paymentStatus === searchForm.paymentStatus)
  }
  if (searchForm.shippingStatus) {
    list = list.filter(item => item.shippingStatus === searchForm.shippingStatus)
  }
  return list
})
// æ–¹æ³•
const getPaymentStatusType = (status) => {
  const statusMap = {
    '未付款': 'danger',
    '已付款': 'success',
    '部分付款': 'warning'
  }
  return statusMap[status] || 'info'
}
const getShippingStatusType = (status) => {
  const statusMap = {
    '待发货': 'warning',
    '已发货': 'primary',
    '已签收': 'success'
  }
  return statusMap[status] || 'info'
}
const handleSearch = () => {
  // æœç´¢é€»è¾‘已在computed中处理
}
const resetSearch = () => {
  searchForm.orderNo = ''
  searchForm.paymentStatus = ''
  searchForm.shippingStatus = ''
}
const handleAdd = () => {
  dialogTitle.value = '新增记录'
  isEdit.value = false
  form.orderNo = ''
  form.customer = ''
  form.orderAmount = 0
  form.paymentMethod = ''
  form.paymentStatus = '未付款'
  form.shippingStatus = '待发货'
  form.shippingDate = ''
  form.trackingNo = ''
  form.remark = ''
  dialogVisible.value = true
}
const handleView = (row) => {
  // æŸ¥çœ‹è®°å½•详情
  ElMessage.info('查看记录详情功能待实现')
}
const handleEdit = (row) => {
  dialogTitle.value = '编辑记录'
  isEdit.value = true
  editId.value = row.id
  Object.assign(form, row)
  dialogVisible.value = true
}
const handlePayment = (row) => {
  currentRecord.value = row
  paymentAmount.value = row.orderAmount
  paymentMethod.value = ''
  paymentRemark.value = ''
  paymentDialogVisible.value = true
}
const handleShipping = (row) => {
  currentRecord.value = row
  shippingDate.value = ''
  logisticsCompany.value = ''
  trackingNo.value = ''
  shippingRemark.value = ''
  shippingDialogVisible.value = true
}
const handleDelete = (row) => {
  ElMessageBox.confirm('确认删除该记录吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    const index = recordList.value.findIndex(item => item.id === row.id)
    if (index > -1) {
      recordList.value.splice(index, 1)
      pagination.total--
      ElMessage.success('删除成功')
    }
  })
}
const savePayment = () => {
  if (!paymentMethod.value) {
    ElMessage.warning('请选择付款方式')
    return
  }
  const index = recordList.value.findIndex(item => item.id === currentRecord.value.id)
  if (index > -1) {
    if (paymentAmount.value >= currentRecord.value.orderAmount) {
      recordList.value[index].paymentStatus = '已付款'
    } else if (paymentAmount.value > 0) {
      recordList.value[index].paymentStatus = '部分付款'
    }
    ElMessage.success('付款记录已保存')
    paymentDialogVisible.value = false
  }
}
const saveShipping = () => {
  if (!shippingDate.value || !logisticsCompany.value || !trackingNo.value) {
    ElMessage.warning('请填写完整的发货信息')
    return
  }
  const index = recordList.value.findIndex(item => item.id === currentRecord.value.id)
  if (index > -1) {
    recordList.value[index].shippingStatus = '已发货'
    recordList.value[index].shippingDate = shippingDate.value
    recordList.value[index].trackingNo = trackingNo.value
    ElMessage.success('发货信息已保存')
    shippingDialogVisible.value = false
  }
}
const handleSubmit = () => {
  formRef.value.validate((valid) => {
    if (valid) {
      if (isEdit.value) {
        // ç¼–辑
        const index = recordList.value.findIndex(item => item.id === editId.value)
        if (index > -1) {
          recordList.value[index] = { ...form, id: editId.value }
          ElMessage.success('编辑成功')
        }
      } else {
        // æ–°å¢ž
        const newId = Math.max(...recordList.value.map(item => item.id)) + 1
        recordList.value.push({
          ...form,
          id: newId
        })
        pagination.total++
        ElMessage.success('新增成功')
      }
      dialogVisible.value = false
    }
  })
}
const handleCurrentChange = (val) => {
  pagination.currentPage = val.page
  pagination.pageSize = val.limit
}
</script>
<style scoped>
.search-row {
  margin-bottom: 20px;
}
</style>
src/views/salesManagement/salespersonManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,392 @@
<template>
  <div class="app-container">
    <el-card class="box-card">
      <!-- æœç´¢åŒºåŸŸ -->
      <el-row :gutter="20" class="search-row">
        <el-col :span="6">
          <el-input
            v-model="searchForm.name"
            placeholder="请输入业务员姓名"
            clearable
            @keyup.enter="handleSearch"
          >
            <template #prefix>
              <el-icon><Search /></el-icon>
            </template>
          </el-input>
        </el-col>
        <el-col :span="6">
          <el-select v-model="searchForm.department" placeholder="请选择部门" clearable>
            <el-option label="销售部" value="销售部"></el-option>
            <el-option label="市场部" value="市场部"></el-option>
            <el-option label="客服部" value="客服部"></el-option>
          </el-select>
        </el-col>
        <el-col :span="6">
          <el-select v-model="searchForm.status" placeholder="请选择状态" clearable>
            <el-option label="在职" value="在职"></el-option>
            <el-option label="离职" value="离职"></el-option>
            <el-option label="试用期" value="试用期"></el-option>
          </el-select>
        </el-col>
        <el-col :span="6">
          <el-button type="primary" @click="handleSearch">搜索</el-button>
          <el-button @click="resetSearch">重置</el-button>
          <el-button type="primary" style="float: right;" @click="handleAdd">新增业务员</el-button>
        </el-col>
      </el-row>
      <!-- ä¸šåŠ¡å‘˜åˆ—è¡¨ -->
      <el-table
        :data="filteredList"
        style="width: 100%"
        v-loading="loading"
        border
        stripe
        height="calc(100vh - 22em)"
      >
        <el-table-column prop="id" label="ID" width="80" align="center"/>
        <el-table-column prop="name" label="姓名" width="120" />
        <el-table-column prop="phone" label="联系电话" width="140" />
        <el-table-column prop="email" label="邮箱" width="200" />
        <el-table-column prop="department" label="部门" width="100" />
        <el-table-column prop="position" label="职位" width="100" />
        <el-table-column prop="hireDate" label="入职日期" width="120" />
        <el-table-column prop="status" label="状态" width="80">
          <template #default="scope">
            <el-tag :type="getStatusType(scope.row.status)">
              {{ scope.row.status }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="permissions" label="权限">
          <template #default="scope">
            <el-tag v-for="perm in scope.row.permissions" :key="perm" size="small" style="margin-right: 5px;">
              {{ perm }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="操作" width="200" fixed="right" align="center">
          <template #default="scope">
            <el-button link type="primary" @click="handleEdit(scope.row)">编辑</el-button>
            <el-button link type="primary" @click="handlePermissions(scope.row)">权限</el-button>
            <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- åˆ†é¡µ -->
      <pagination
        :total="pagination.total"
        layout="total, sizes, prev, pager, next, jumper"
        :page="pagination.currentPage"
        :limit="pagination.pageSize"
        @pagination="handleCurrentChange"
      />
    </el-card>
    <!-- æ–°å¢ž/编辑对话框 -->
    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px">
      <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="姓名" prop="name">
              <el-input v-model="form.name" placeholder="请输入姓名"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="联系电话" prop="phone">
              <el-input v-model="form.phone" placeholder="请输入联系电话"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="邮箱" prop="email">
              <el-input v-model="form.email" placeholder="请输入邮箱"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="部门" prop="department">
              <el-select v-model="form.department" placeholder="请选择部门" style="width: 100%">
                <el-option label="销售部" value="销售部"></el-option>
                <el-option label="市场部" value="市场部"></el-option>
                <el-option label="客服部" value="客服部"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="职位" prop="position">
              <el-input v-model="form.position" placeholder="请输入职位"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="入职日期" prop="hireDate">
              <el-date-picker
                v-model="form.hireDate"
                type="date"
                placeholder="选择入职日期"
                style="width: 100%"
                format="YYYY-MM-DD"
                value-format="YYYY-MM-DD"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="状态" prop="status">
              <el-select v-model="form.status" placeholder="请选择状态" style="width: 100%">
                <el-option label="在职" value="在职"></el-option>
                <el-option label="离职" value="离职"></el-option>
                <el-option label="试用期" value="试用期"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="dialogVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="handleSubmit">ç¡® å®š</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- æƒé™è®¾ç½®å¯¹è¯æ¡† -->
    <el-dialog v-model="permissionDialogVisible" title="权限设置" width="500px">
      <el-form label-width="100px">
        <el-form-item label="业务员姓名">
          <span>{{ currentSalesperson.name }}</span>
        </el-form-item>
        <el-form-item label="权限设置">
          <el-checkbox-group v-model="currentPermissions">
            <el-checkbox label="订单管理">订单管理</el-checkbox>
            <el-checkbox label="客户管理">客户管理</el-checkbox>
            <el-checkbox label="财务管理">财务管理</el-checkbox>
            <el-checkbox label="发货管理">发货管理</el-checkbox>
            <el-checkbox label="报表查看">报表查看</el-checkbox>
            <el-checkbox label="系统设置">系统设置</el-checkbox>
          </el-checkbox-group>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="permissionDialogVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="savePermissions">ç¡® å®š</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, computed, nextTick } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Search } from '@element-plus/icons-vue'
import Pagination from '@/components/PIMTable/Pagination.vue'
// å“åº”式数据
const loading = ref(false)
const searchForm = reactive({
  name: '',
  department: '',
  status: ''
})
const salespersonList = ref([
  {
    id: 1,
    name: '陈志强',
    phone: '13800138001',
    email: 'chenzhiqiang@company.com',
    department: '销售部',
    position: '销售经理',
    hireDate: '2023-01-15',
    status: '在职',
    permissions: ['订单管理', '客户管理', '财务管理']
  },
  {
    id: 2,
    name: '刘雅婷',
    phone: '13800138002',
    email: 'liuyating@company.com',
    department: '市场部',
    position: '市场专员',
    hireDate: '2023-03-20',
    status: '在职',
    permissions: ['客户管理', '报表查看']
  },
  {
    id: 3,
    name: '王建国',
    phone: '13800138003',
    email: 'wangjianguo@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>
vite.config.js
@@ -8,8 +8,8 @@
  const { VITE_APP_ENV } = env;
  const baseUrl =
    VITE_APP_ENV == "development"
      ? "http://192.168.1.147:7003" // å¼€å‘环境后端接口
      : "http://10.136.12.71:8014"; // ç”Ÿäº§çŽ¯å¢ƒåŽç«¯æŽ¥å£
      ? "http://114.132.189.42:8089" // å¼€å‘环境后端接口
      : "http://114.132.189.42:1234"; // ç”Ÿäº§çŽ¯å¢ƒåŽç«¯æŽ¥å£
  return {
    // éƒ¨ç½²ç”Ÿäº§çŽ¯å¢ƒå’Œå¼€å‘çŽ¯å¢ƒä¸‹çš„URL。