Sfoglia il codice sorgente

feat: 实现P2优先级页面(发票、优惠券、价格预警、满意度评价、供应商绩效、员工管理、部门管理)

docker 2 mesi fa
parent
commit
fec2ebc617

+ 34 - 5
src/router/menu.ts

@@ -45,7 +45,8 @@ export const menuGroups: MenuGroup[] = [
     items: [
       { key: 'supplier-list', title: '供应商管理', path: '/supplier/list', roles: ['admin', 'manager', 'procurement'] },
       { key: 'purchase-orders', title: '采购单管理', path: '/supplier/purchase-orders', roles: ['admin', 'manager', 'procurement'] },
-      { key: 'supply-capability', title: '供货能力配置', path: '/supplier/capability', roles: ['admin', 'manager', 'procurement'] }
+      { key: 'supply-capability', title: '供货能力配置', path: '/supplier/capability', roles: ['admin', 'manager', 'procurement'] },
+      { key: 'supplier-performance', title: '供应商绩效', path: '/supplier/performance', roles: ['admin', 'manager', 'procurement'] }
     ]
   },
   {
@@ -75,21 +76,36 @@ export const menuGroups: MenuGroup[] = [
     key: 'finance',
     title: '财务中心',
     items: [
-      { key: 'finance-payments', title: '收款管理', path: '/finance/payments', roles: ['admin', 'manager', 'finance'] }
+      { key: 'finance-payments', title: '收款管理', path: '/finance/payments', roles: ['admin', 'manager', 'finance'] },
+      { key: 'finance-refund', title: '退款管理', path: '/finance/refund', roles: ['admin', 'manager', 'finance'] },
+      { key: 'supplier-settlement', title: '供应商结算', path: '/finance/settlement', roles: ['admin', 'manager', 'finance'] },
+      { key: 'finance-invoice', title: '发票管理', path: '/finance/invoice', roles: ['admin', 'manager', 'finance'] }
     ]
   },
   {
     key: 'procurement',
     title: '采购中心',
     items: [
-      { key: 'replenishment-plan', title: '备货计划', path: '/procurement/replenishment', roles: ['admin', 'manager', 'procurement'] }
+      { key: 'replenishment-plan', title: '备货计划', path: '/procurement/replenishment', roles: ['admin', 'manager', 'procurement'] },
+      { key: 'purchase-request', title: '采购需求申请', path: '/procurement/purchase-request', roles: ['admin', 'manager', 'procurement'] },
+      { key: 'procurement-iqc', title: '来料质检', path: '/procurement/iqc', roles: ['admin', 'manager', 'procurement', 'warehouse'] }
     ]
   },
   {
     key: 'crm',
     title: '客服中心',
     items: [
-      { key: 'crm-tickets', title: '工单管理', path: '/crm/tickets', roles: ['admin', 'manager', 'customer_service'] }
+      { key: 'crm-tickets', title: '工单管理', path: '/crm/tickets', roles: ['admin', 'manager', 'customer_service'] },
+      { key: 'crm-satisfaction', title: '满意度评价', path: '/crm/satisfaction', roles: ['admin', 'manager', 'customer_service'] }
+    ]
+  },
+  {
+    key: 'marketing',
+    title: '营销中心',
+    items: [
+      { key: 'marketing-promotion', title: '促销活动', path: '/marketing/promotion', roles: ['admin', 'manager', 'operator'] },
+      { key: 'marketing-coupon', title: '优惠券管理', path: '/marketing/coupon', roles: ['admin', 'manager', 'operator'] },
+      { key: 'marketing-price-watch', title: '价格预警', path: '/marketing/price-watch', roles: ['admin', 'manager', 'operator'] }
     ]
   },
   {
@@ -98,7 +114,20 @@ export const menuGroups: MenuGroup[] = [
     items: [
       { key: 'role-permission', title: '角色权限', path: '/system/roles', roles: ['admin'] },
       { key: 'operation-log', title: '操作日志', path: '/system/logs', roles: ['admin', 'manager'] },
-      { key: 'api-key', title: 'API Key 管理', path: '/system/api-keys', roles: ['admin'] }
+      { key: 'api-key', title: 'API Key 管理', path: '/system/api-keys', roles: ['admin'] },
+      { key: 'system-notification', title: '消息中心', path: '/system/notification', roles: ['admin', 'manager'] },
+      { key: 'message-template', title: '消息模板', path: '/system/message-template', roles: ['admin', 'manager'] },
+      { key: 'approval-flow', title: '审批流程', path: '/system/approval-flow', roles: ['admin'] },
+      { key: 'system-employee', title: '员工管理', path: '/system/employee', roles: ['admin'] },
+      { key: 'system-department', title: '部门管理', path: '/system/department', roles: ['admin'] }
+    ]
+  },
+  {
+    key: 'report',
+    title: '数据中心',
+    items: [
+      { key: 'inventory-turnover', title: '库存周转分析', path: '/report/inventory-turnover', roles: ['admin', 'manager', 'procurement'] },
+      { key: 'return-package', title: '退件管理', path: '/warehouse/return-package', roles: ['admin', 'manager', 'warehouse'] }
     ]
   }
 ];

+ 102 - 0
src/router/routes.ts

@@ -166,6 +166,108 @@ export const routes: RouteRecordRaw[] = [
         name: 'api-key',
         component: () => import('@/views/system/ApiKeyView.vue'),
         meta: { title: 'API Key 管理', pageKey: 'api-key', roles: ['admin'] }
+      },
+      {
+        path: '/warehouse/return-package',
+        name: 'return-package',
+        component: () => import('@/views/warehouse/ReturnPackageView.vue'),
+        meta: { title: '退件管理', pageKey: 'return-package', roles: ['admin', 'manager', 'warehouse'] }
+      },
+      {
+        path: '/finance/refund',
+        name: 'finance-refund',
+        component: () => import('@/views/finance/RefundView.vue'),
+        meta: { title: '退款管理', pageKey: 'finance-refund', roles: ['admin', 'manager', 'finance'] }
+      },
+      {
+        path: '/finance/settlement',
+        name: 'supplier-settlement',
+        component: () => import('@/views/finance/SupplierSettlementView.vue'),
+        meta: { title: '供应商结算', pageKey: 'supplier-settlement', roles: ['admin', 'manager', 'finance'] }
+      },
+      {
+        path: '/marketing/promotion',
+        name: 'marketing-promotion',
+        component: () => import('@/views/marketing/PromotionView.vue'),
+        meta: { title: '促销活动', pageKey: 'marketing-promotion', roles: ['admin', 'manager', 'operator'] }
+      },
+      {
+        path: '/report/inventory-turnover',
+        name: 'inventory-turnover',
+        component: () => import('@/views/report/InventoryTurnoverView.vue'),
+        meta: { title: '库存周转分析', pageKey: 'inventory-turnover', roles: ['admin', 'manager', 'procurement'] }
+      },
+      {
+        path: '/procurement/purchase-request',
+        name: 'purchase-request',
+        component: () => import('@/views/procurement/PurchaseRequestView.vue'),
+        meta: { title: '采购需求申请', pageKey: 'purchase-request', roles: ['admin', 'manager', 'procurement'] }
+      },
+      {
+        path: '/procurement/iqc',
+        name: 'procurement-iqc',
+        component: () => import('@/views/procurement/IQCView.vue'),
+        meta: { title: '来料质检', pageKey: 'procurement-iqc', roles: ['admin', 'manager', 'procurement', 'warehouse'] }
+      },
+      {
+        path: '/system/notification',
+        name: 'system-notification',
+        component: () => import('@/views/system/NotificationView.vue'),
+        meta: { title: '消息中心', pageKey: 'system-notification', roles: ['admin', 'manager'] }
+      },
+      {
+        path: '/system/message-template',
+        name: 'message-template',
+        component: () => import('@/views/system/MessageTemplateView.vue'),
+        meta: { title: '消息模板', pageKey: 'message-template', roles: ['admin', 'manager'] }
+      },
+      {
+        path: '/system/approval-flow',
+        name: 'approval-flow',
+        component: () => import('@/views/system/ApprovalFlowView.vue'),
+        meta: { title: '审批流程', pageKey: 'approval-flow', roles: ['admin'] }
+      },
+      {
+        path: '/finance/invoice',
+        name: 'finance-invoice',
+        component: () => import('@/views/finance/InvoiceView.vue'),
+        meta: { title: '发票管理', pageKey: 'finance-invoice', roles: ['admin', 'manager', 'finance'] }
+      },
+      {
+        path: '/marketing/coupon',
+        name: 'marketing-coupon',
+        component: () => import('@/views/marketing/CouponView.vue'),
+        meta: { title: '优惠券管理', pageKey: 'marketing-coupon', roles: ['admin', 'manager', 'operator'] }
+      },
+      {
+        path: '/marketing/price-watch',
+        name: 'marketing-price-watch',
+        component: () => import('@/views/marketing/PriceWatchView.vue'),
+        meta: { title: '价格预警', pageKey: 'marketing-price-watch', roles: ['admin', 'manager', 'operator'] }
+      },
+      {
+        path: '/crm/satisfaction',
+        name: 'crm-satisfaction',
+        component: () => import('@/views/crm/SatisfactionView.vue'),
+        meta: { title: '满意度评价', pageKey: 'crm-satisfaction', roles: ['admin', 'manager', 'customer_service'] }
+      },
+      {
+        path: '/supplier/performance',
+        name: 'supplier-performance',
+        component: () => import('@/views/supplier/SupplierPerformanceView.vue'),
+        meta: { title: '供应商绩效', pageKey: 'supplier-performance', roles: ['admin', 'manager', 'procurement'] }
+      },
+      {
+        path: '/system/employee',
+        name: 'system-employee',
+        component: () => import('@/views/system/EmployeeView.vue'),
+        meta: { title: '员工管理', pageKey: 'system-employee', roles: ['admin'] }
+      },
+      {
+        path: '/system/department',
+        name: 'system-department',
+        component: () => import('@/views/system/DepartmentView.vue'),
+        meta: { title: '部门管理', pageKey: 'system-department', roles: ['admin'] }
       }
     ]
   },

+ 299 - 0
src/views/crm/SatisfactionView.vue

@@ -0,0 +1,299 @@
+<template>
+  <div class="app-page">
+    <section class="glass-card section-card">
+      <el-form :model="filters" inline class="filter-form">
+        <el-form-item label="订单号">
+          <el-input v-model="filters.orderNo" placeholder="订单号" clearable style="width:160px" @keyup.enter="loadData" />
+        </el-form-item>
+        <el-form-item label="评价来源">
+          <el-select v-model="filters.source" placeholder="全部" clearable style="width:130px">
+            <el-option label="Shopify" value="Shopify" />
+            <el-option label="TikTok Shop" value="TikTok Shop" />
+            <el-option label="Amazon" value="Amazon" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="评分">
+          <el-select v-model="filters.rating" placeholder="全部" clearable style="width:100px">
+            <el-option label="5星" value="5" />
+            <el-option label="4星" value="4" />
+            <el-option label="3星" value="3" />
+            <el-option label="2星" value="2" />
+            <el-option label="1星" value="1" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="处理状态">
+          <el-select v-model="filters.handleStatus" placeholder="全部" clearable style="width:120px">
+            <el-option label="待处理" value="待处理" />
+            <el-option label="处理中" value="处理中" />
+            <el-option label="已处理" value="已处理" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="评价时间">
+          <el-date-picker v-model="filters.dateRange" type="daterange" range-separator="至" start-placeholder="开始" end-placeholder="结束" style="width:240px" />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="loadData">查询</el-button>
+          <el-button @click="resetFilters">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </section>
+
+    <section class="glass-card section-card" style="padding:12px 24px">
+      <div class="table-toolbar" style="margin-bottom:0">
+        <div class="chip-list">
+          <el-button type="primary" @click="exportData">导出评价</el-button>
+        </div>
+        <el-radio-group v-model="viewMode">
+          <el-radio-button label="all">全部</el-radio-button>
+          <el-radio-button label="negative">差评</el-radio-button>
+          <el-radio-button label="pending">待处理</el-radio-button>
+        </el-radio-group>
+      </div>
+    </section>
+
+    <section class="glass-card section-card">
+      <el-table :data="filteredItems" stripe style="width:100%" v-loading="loading" @selection-change="onSelection">
+        <el-table-column type="selection" width="45" />
+        <el-table-column prop="orderNo" label="订单号" width="160" />
+        <el-table-column prop="source" label="来源" width="110" />
+        <el-table-column prop="buyer" label="买家" width="100" />
+        <el-table-column prop="rating" label="评分" width="80">
+          <template #default="{ row }">
+            <el-rate v-model="row.rating" disabled text-color="#ff9900" />
+          </template>
+        </el-table-column>
+        <el-table-column prop="content" label="评价内容" min-width="250" show-overflow-tooltip />
+        <el-table-column prop="reply" label="客服回复" min-width="150" show-overflow-tooltip />
+        <el-table-column prop="handleStatus" label="处理状态" width="90">
+          <template #default="{ row }">
+            <el-tag :type="statusTag(row.handleStatus)" size="small">{{ row.handleStatus }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="csName" label="处理客服" width="100" />
+        <el-table-column prop="createTime" label="评价时间" width="160" />
+        <el-table-column label="操作" width="140" fixed="right">
+          <template #default="{ row }">
+            <el-button link type="primary" @click="openDetail(row)">详情</el-button>
+            <el-button link type="primary" @click="handleReview(row)" v-if="row.handleStatus !== '已处理'">处理</el-button>
+          </template>
+        </el-table-column>
+        <template #empty>
+          <el-empty description="暂无数据" />
+        </template>
+      </el-table>
+    </section>
+
+    <section class="glass-card section-card" style="padding:16px">
+      <h4 style="margin:0 0 12px">评价分析</h4>
+      <div class="stat-grid" style="grid-template-columns:repeat(5, 1fr)">
+        <article class="stat-card">
+          <div class="stat-card__label">本月评价数</div>
+          <div class="stat-card__value" style="font-size:24px;color:var(--cb-primary)">1,258</div>
+        </article>
+        <article class="stat-card">
+          <div class="stat-card__label">好评率</div>
+          <div class="stat-card__value" style="font-size:24px;color:var(--cb-success)">92.5%</div>
+        </article>
+        <article class="stat-card">
+          <div class="stat-card__label">差评数</div>
+          <div class="stat-card__value" style="font-size:24px;color:var(--cb-danger)">42</div>
+        </article>
+        <article class="stat-card">
+          <div class="stat-card__label">待处理</div>
+          <div class="stat-card__value" style="font-size:24px;color:var(--cb-accent)">8</div>
+        </article>
+        <article class="stat-card">
+          <div class="stat-card__label">平均评分</div>
+          <div class="stat-card__value" style="font-size:24px">4.6</div>
+        </article>
+      </div>
+    </section>
+
+    <section class="glass-card section-card" style="padding:16px">
+      <h4 style="margin:0 0 12px">差评原因分布</h4>
+      <div style="display:flex;gap:32px;padding:8px 0">
+        <div style="flex:1">
+          <div v-for="(item, idx) in negativeReasons" :key="idx" style="margin-bottom:12px">
+            <div style="display:flex;justify-content:space-between;margin-bottom:4px">
+              <span>{{ item.reason }}</span>
+              <span style="color:var(--cb-text-secondary)">{{ item.count }}条 ({{ item.percent }}%)</span>
+            </div>
+            <el-progress :percentage="item.percent" :stroke-width="8" :color="item.color" :show-text="false" />
+          </div>
+        </div>
+      </div>
+    </section>
+
+    <el-dialog v-model="detailVisible" title="评价详情" width="600px">
+      <el-descriptions :column="1" border v-if="detailItem">
+        <el-descriptions-item label="订单号">{{ detailItem.orderNo }}</el-descriptions-item>
+        <el-descriptions-item label="来源">{{ detailItem.source }}</el-descriptions-item>
+        <el-descriptions-item label="买家">{{ detailItem.buyer }}</el-descriptions-item>
+        <el-descriptions-item label="商品">{{ detailItem.productTitle }}</el-descriptions-item>
+        <el-descriptions-item label="评分">
+          <el-rate v-model="detailItem.rating" disabled />
+        </el-descriptions-item>
+        <el-descriptions-item label="评价内容">{{ detailItem.content }}</el-descriptions-item>
+        <el-descriptions-item label="评价时间">{{ detailItem.createTime }}</el-descriptions-item>
+        <el-descriptions-item label="处理状态">
+          <el-tag :type="statusTag(detailItem.handleStatus)">{{ detailItem.handleStatus }}</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="客服回复">{{ detailItem.reply || '未回复' }}</el-descriptions-item>
+        <el-descriptions-item label="处理客服">{{ detailItem.csName || '-' }}</el-descriptions-item>
+      </el-descriptions>
+      <template #footer>
+        <el-button @click="detailVisible = false">关闭</el-button>
+      </template>
+    </el-dialog>
+
+    <el-dialog v-model="handleVisible" title="评价处理" width="520px">
+      <el-form :model="handleForm" label-width="90px">
+        <el-form-item label="订单号">
+          <el-input v-model="handleForm.orderNo" disabled />
+        </el-form-item>
+        <el-form-item label="处理方式">
+          <el-radio-group v-model="handleForm.action">
+            <el-radio label="reply">回复买家</el-radio>
+            <el-radio label="refund">退款处理</el-radio>
+            <el-radio label="resend">补发商品</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="回复内容" v-if="handleForm.action === 'reply'">
+          <el-input v-model="handleForm.replyContent" type="textarea" rows="4" placeholder="请输入回复内容" />
+        </el-form-item>
+        <el-form-item label="退款金额" v-if="handleForm.action === 'refund'">
+          <el-input-number v-model="handleForm.refundAmount" :min="0" :precision="2" style="width:100%" />
+        </el-form-item>
+        <el-form-item label="处理备注">
+          <el-input v-model="handleForm.remark" type="textarea" rows="3" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="handleVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitHandle">提交处理</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed, onMounted, ref } from 'vue';
+import { ElMessage } from 'element-plus';
+
+interface SatisfactionItem {
+  id: string;
+  orderNo: string;
+  source: string;
+  buyer: string;
+  productTitle: string;
+  rating: number;
+  content: string;
+  reply: string;
+  handleStatus: string;
+  csName: string;
+  createTime: string;
+}
+
+const items = ref<SatisfactionItem[]>([
+  { id: 'S001', orderNo: 'ORD-20260420-0123', source: 'Shopify', buyer: 'John D.', productTitle: 'Nomad 防水背包 黑色 中号', rating: 5, content: 'Excellent product! Fast shipping and great quality. Would definitely buy again.', reply: 'Thank you for your positive feedback! We appreciate your support.', handleStatus: '已处理', csName: '张三', createTime: '2026-04-20 10:30:15' },
+  { id: 'S002', orderNo: 'ORD-20260419-0456', source: 'TikTok Shop', buyer: 'Sarah M.', productTitle: 'AeroDry 速干T恤 绿色 L码', rating: 4, content: 'Good product but shipping took longer than expected. Product quality is great though.', reply: 'We apologize for the delay. We will improve our shipping speed.', handleStatus: '已处理', csName: '李四', createTime: '2026-04-19 15:20:33' },
+  { id: 'S003', orderNo: 'ORD-20260419-0789', source: 'Amazon', buyer: 'Mike R.', productTitle: 'UrbanTrail 徒步鞋 白色 XL', rating: 2, content: 'Product looks different from photos. Size runs small. Disappointed.', reply: '', handleStatus: '处理中', csName: '', createTime: '2026-04-19 09:15:22' },
+  { id: 'S004', orderNo: 'ORD-20260418-0321', source: 'Shopify', buyer: 'Emily W.', productTitle: 'Nomad 防水背包 黑色 小号', rating: 1, content: 'Received damaged product. Packaging was also torn. Very unhappy with this purchase.', reply: '', handleStatus: '待处理', csName: '', createTime: '2026-04-18 21:45:08' },
+  { id: 'S005', orderNo: 'ORD-20260418-0654', source: 'TikTok Shop', buyer: 'David L.', productTitle: 'AeroDry 运动短裤 蓝色 M码', rating: 5, content: 'Perfect fit! Great material. Very comfortable for running.', reply: 'Thank you for your purchase!', handleStatus: '已处理', csName: '王五', createTime: '2026-04-18 14:30:45' },
+  { id: 'S006', orderNo: 'ORD-20260417-0987', source: 'Amazon', buyer: 'Lisa K.', productTitle: 'UrbanTrail 登山靴 黑色 42码', rating: 3, content: 'Product is okay but price is higher than competitors. Good quality though.', reply: '', handleStatus: '待处理', csName: '', createTime: '2026-04-17 11:20:30' }
+]);
+
+const negativeReasons = ref([
+  { reason: '物流问题', count: 18, percent: 42.9, color: '#F56C6C' },
+  { reason: '产品与描述不符', count: 12, percent: 28.6, color: '#E6A23C' },
+  { reason: '质量/破损问题', count: 8, percent: 19.0, color: '#909399' },
+  { reason: '尺码问题', count: 3, percent: 7.1, color: '#67C23A' },
+  { reason: '其他', count: 1, percent: 2.4, color: '#909399' }
+]);
+
+const loading = ref(false);
+const selected = ref<SatisfactionItem[]>([]);
+const viewMode = ref('all');
+const detailVisible = ref(false);
+const handleVisible = ref(false);
+const detailItem = ref<SatisfactionItem | null>(null);
+
+const filters = ref({
+  orderNo: '',
+  source: '',
+  rating: '',
+  handleStatus: '',
+  dateRange: [] as string[]
+});
+
+const handleForm = ref({
+  orderNo: '',
+  action: 'reply',
+  replyContent: '',
+  refundAmount: 0,
+  remark: ''
+});
+
+const filteredItems = computed(() => {
+  let result = items.value;
+  if (viewMode.value === 'negative') {
+    result = result.filter(item => item.rating <= 2);
+  } else if (viewMode.value === 'pending') {
+    result = result.filter(item => item.handleStatus === '待处理');
+  }
+  return result.filter(item => {
+    if (filters.value.orderNo && !item.orderNo.includes(filters.value.orderNo)) return false;
+    if (filters.value.source && item.source !== filters.value.source) return false;
+    if (filters.value.rating && item.rating !== parseInt(filters.value.rating)) return false;
+    if (filters.value.handleStatus && item.handleStatus !== filters.value.handleStatus) return false;
+    return true;
+  });
+});
+
+const statusTag = (status: string) => {
+  const map: Record<string, string> = { '待处理': 'danger', '处理中': 'warning', '已处理': 'success' };
+  return map[status] || '';
+};
+
+const loadData = () => {
+  loading.value = true;
+  setTimeout(() => { loading.value = false; }, 300);
+};
+
+const resetFilters = () => {
+  filters.value = { orderNo: '', source: '', rating: '', handleStatus: '', dateRange: [] };
+};
+
+const onSelection = (rows: SatisfactionItem[]) => { selected.value = rows; };
+
+const openDetail = (row: SatisfactionItem) => {
+  detailItem.value = row;
+  detailVisible.value = true;
+};
+
+const handleReview = (row: SatisfactionItem) => {
+  handleForm.value = { orderNo: row.orderNo, action: 'reply', replyContent: '', refundAmount: 0, remark: '' };
+  handleVisible.value = true;
+};
+
+const submitHandle = () => {
+  const idx = items.value.findIndex(s => s.id === detailItem.value?.id || s.orderNo === handleForm.value.orderNo);
+  if (idx !== -1) {
+    items.value[idx].handleStatus = '已处理';
+    items.value[idx].csName = '当前客服';
+    items.value[idx].reply = handleForm.value.replyContent || '已退款 ¥' + handleForm.value.refundAmount;
+  }
+  ElMessage.success('处理完成');
+  handleVisible.value = false;
+};
+
+const exportData = () => {
+  ElMessage.info('导出开始,完成后将自动下载');
+};
+
+onMounted(loadData);
+</script>
+
+<style scoped>
+.filter-form :deep(.el-form-item) { margin-bottom: 0; }
+</style>

+ 292 - 0
src/views/finance/InvoiceView.vue

@@ -0,0 +1,292 @@
+<template>
+  <div class="app-page">
+    <section class="glass-card section-card">
+      <el-form :model="filters" inline class="filter-form">
+        <el-form-item label="发票号">
+          <el-input v-model="filters.invoiceNo" placeholder="发票号" clearable style="width:160px" @keyup.enter="loadData" />
+        </el-form-item>
+        <el-form-item label="发票类型">
+          <el-select v-model="filters.invoiceType" placeholder="全部" clearable style="width:130px">
+            <el-option label="增值税专用发票" value="专用" />
+            <el-option label="增值税普通发票" value="普通" />
+            <el-option label="电子发票" value="电子" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="购买方">
+          <el-input v-model="filters.buyerName" placeholder="购买方名称" clearable style="width:150px" />
+        </el-form-item>
+        <el-form-item label="状态">
+          <el-select v-model="filters.status" placeholder="全部" clearable style="width:120px">
+            <el-option label="待开票" value="待开票" />
+            <el-option label="已开票" value="已开票" />
+            <el-option label="已作废" value="已作废" />
+            <el-option label="已红冲" value="已红冲" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="开票日期">
+          <el-date-picker v-model="filters.dateRange" type="daterange" range-separator="至" start-placeholder="开始" end-placeholder="结束" style="width:240px" />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="loadData">查询</el-button>
+          <el-button @click="resetFilters">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </section>
+
+    <section class="glass-card section-card" style="padding:12px 24px">
+      <div class="table-toolbar" style="margin-bottom:0">
+        <div class="chip-list">
+          <el-button type="primary" @click="openApply">申请开票</el-button>
+          <el-button @click="doExport">导出</el-button>
+        </div>
+        <span class="tip-text">累计开票金额: <b style="color:var(--cb-primary)">¥1,258,600.00</b></span>
+      </div>
+    </section>
+
+    <section class="glass-card section-card">
+      <el-table :data="filteredItems" stripe style="width:100%" v-loading="loading" @selection-change="onSelection">
+        <el-table-column type="selection" width="45" />
+        <el-table-column prop="invoiceNo" label="发票号" width="160" />
+        <el-table-column prop="invoiceType" label="发票类型" width="120">
+          <template #default="{ row }">
+            <el-tag size="small">{{ row.invoiceType }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="buyerName" label="购买方" min-width="160" />
+        <el-table-column prop="sellerName" label="销售方" min-width="160" />
+        <el-table-column prop="amount" label="金额" width="120">
+          <template #default="{ row }">
+            <span style="font-weight:600">¥{{ row.amount }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="taxRate" label="税率" width="70" />
+        <el-table-column prop="taxAmount" label="税额" width="100" />
+        <el-table-column prop="invoiceDate" label="开票日期" width="110" />
+        <el-table-column prop="status" label="状态" width="90">
+          <template #default="{ row }">
+            <el-tag :type="statusTag(row.status)" size="small">{{ row.status }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="remark" label="备注" min-width="100" show-overflow-tooltip />
+        <el-table-column label="操作" width="140" fixed="right">
+          <template #default="{ row }">
+            <el-button link type="primary" @click="openDetail(row)">详情</el-button>
+            <el-button link type="primary" @click="openEdit(row)" v-if="row.status === '待开票'">编辑</el-button>
+            <el-button link type="danger" @click="voidInvoice(row)" v-if="row.status === '已开票'">作废</el-button>
+          </template>
+        </el-table-column>
+        <template #empty>
+          <el-empty description="暂无数据" />
+        </template>
+      </el-table>
+    </section>
+
+    <section class="glass-card section-card" style="padding:16px">
+      <h4 style="margin:0 0 12px">发票汇总</h4>
+      <div class="stat-grid" style="grid-template-columns:repeat(4, 1fr)">
+        <article class="stat-card">
+          <div class="stat-card__label">本月开票</div>
+          <div class="stat-card__value" style="font-size:24px;color:var(--cb-primary)">¥328,500.00</div>
+        </article>
+        <article class="stat-card">
+          <div class="stat-card__label">待开票</div>
+          <div class="stat-card__value" style="font-size:24px;color:var(--cb-accent)">¥45,200.00</div>
+        </article>
+        <article class="stat-card">
+          <div class="stat-card__label">已作废/红冲</div>
+          <div class="stat-card__value" style="font-size:24px;color:var(--cb-danger)">¥12,800.00</div>
+        </article>
+        <article class="stat-card">
+          <div class="stat-card__label">进项税额</div>
+          <div class="stat-card__value" style="font-size:24px">¥52,560.00</div>
+        </article>
+      </div>
+    </section>
+
+    <el-dialog v-model="applyVisible" title="申请开票" width="560px">
+      <el-form :model="applyForm" label-width="100px">
+        <el-form-item label="发票类型" required>
+          <el-select v-model="applyForm.invoiceType" style="width:100%">
+            <el-option label="增值税专用发票" value="专用" />
+            <el-option label="增值税普通发票" value="普通" />
+            <el-option label="电子发票" value="电子" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="购买方名称" required>
+          <el-input v-model="applyForm.buyerName" placeholder="请输入购买方名称" />
+        </el-form-item>
+        <el-form-item label="税号">
+          <el-input v-model="applyForm.taxNo" placeholder="请输入税号" />
+        </el-form-item>
+        <el-form-item label="开户行">
+          <el-input v-model="applyForm.bankName" placeholder="请输入开户行" />
+        </el-form-item>
+        <el-form-item label="银行账号">
+          <el-input v-model="applyForm.bankAccount" placeholder="请输入银行账号" />
+        </el-form-item>
+        <el-form-item label="地址电话">
+          <el-input v-model="applyForm.addressPhone" placeholder="请输入地址和电话" />
+        </el-form-item>
+        <el-form-item label="关联单据">
+          <el-select v-model="applyForm.relatedOrders" multiple placeholder="选择关联订单" style="width:100%">
+            <el-option label="ORD-20260420-001" value="ORD-20260420-001" />
+            <el-option label="ORD-20260419-002" value="ORD-20260419-002" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="价税合计">
+          <el-input-number v-model="applyForm.amount" :min="0" style="width:100%" />
+        </el-form-item>
+        <el-form-item label="备注">
+          <el-input v-model="applyForm.remark" type="textarea" rows="3" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="applyVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitApply">提交</el-button>
+      </template>
+    </el-dialog>
+
+    <el-dialog v-model="detailVisible" title="发票详情" width="560px">
+      <el-descriptions :column="1" border v-if="detailItem">
+        <el-descriptions-item label="发票号">{{ detailItem.invoiceNo }}</el-descriptions-item>
+        <el-descriptions-item label="发票类型">{{ detailItem.invoiceType }}</el-descriptions-item>
+        <el-descriptions-item label="购买方">{{ detailItem.buyerName }}</el-descriptions-item>
+        <el-descriptions-item label="销售方">{{ detailItem.sellerName }}</el-descriptions-item>
+        <el-descriptions-item label="金额">¥{{ detailItem.amount }}</el-descriptions-item>
+        <el-descriptions-item label="税率">{{ detailItem.taxRate }}</el-descriptions-item>
+        <el-descriptions-item label="税额">¥{{ detailItem.taxAmount }}</el-descriptions-item>
+        <el-descriptions-item label="价税合计">¥{{ (parseFloat(detailItem.amount) + parseFloat(detailItem.taxAmount)).toFixed(2) }}</el-descriptions-item>
+        <el-descriptions-item label="开票日期">{{ detailItem.invoiceDate }}</el-descriptions-item>
+        <el-descriptions-item label="状态">
+          <el-tag :type="statusTag(detailItem.status)">{{ detailItem.status }}</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="购买方税号">{{ detailItem.buyerTaxNo }}</el-descriptions-item>
+        <el-descriptions-item label="备注">{{ detailItem.remark || '-' }}</el-descriptions-item>
+      </el-descriptions>
+      <template #footer>
+        <el-button @click="detailVisible = false">关闭</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed, onMounted, ref } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+
+interface InvoiceItem {
+  id: string;
+  invoiceNo: string;
+  invoiceType: string;
+  buyerName: string;
+  sellerName: string;
+  amount: string;
+  taxRate: string;
+  taxAmount: string;
+  invoiceDate: string;
+  status: string;
+  buyerTaxNo: string;
+  remark: string;
+}
+
+const items = ref<InvoiceItem[]>([
+  { id: 'INV001', invoiceNo: 'INV-20260420-001', invoiceType: '专用', buyerName: '深圳某某科技有限公司', sellerName: '广州某某贸易公司', amount: '50000.00', taxRate: '13%', taxAmount: '6500.00', invoiceDate: '2026-04-20', status: '已开票', buyerTaxNo: '91440300MA5XXXXXX', remark: '' },
+  { id: 'INV002', invoiceNo: 'INV-20260419-002', invoiceType: '普通', buyerName: '上海某某电子商务公司', sellerName: '广州某某贸易公司', amount: '28000.00', taxRate: '6%', taxAmount: '1680.00', invoiceDate: '2026-04-19', status: '已开票', buyerTaxNo: '91310000MA1FXXXXX', remark: '' },
+  { id: 'INV003', invoiceNo: 'INV-20260418-003', invoiceType: '电子', buyerName: '北京某某跨境电商公司', sellerName: '广州某某贸易公司', amount: '156000.00', taxRate: '13%', taxAmount: '20280.00', invoiceDate: '2026-04-18', status: '待开票', buyerTaxNo: '91110000MA01YYYYY', remark: '等待采购单确认' },
+  { id: 'INV004', invoiceNo: 'INV-20260417-004', invoiceType: '专用', buyerName: '杭州某某供应链公司', sellerName: '广州某某贸易公司', amount: '89000.00', taxRate: '13%', taxAmount: '11570.00', invoiceDate: '2026-04-17', status: '已红冲', buyerTaxNo: '91330100MA2BZZZZZ', remark: '重复开票已红冲' },
+  { id: 'INV005', invoiceNo: 'INV-20260416-005', invoiceType: '普通', buyerName: '成都某某贸易公司', sellerName: '广州某某贸易公司', amount: '42000.00', taxRate: '6%', taxAmount: '2520.00', invoiceDate: '2026-04-16', status: '已开票', buyerTaxNo: '91510100MA6CAAAAA', remark: '' }
+]);
+
+const loading = ref(false);
+const selected = ref<InvoiceItem[]>([]);
+const applyVisible = ref(false);
+const detailVisible = ref(false);
+const detailItem = ref<InvoiceItem | null>(null);
+
+const filters = ref({
+  invoiceNo: '',
+  invoiceType: '',
+  buyerName: '',
+  status: '',
+  dateRange: [] as string[]
+});
+
+const applyForm = ref({
+  invoiceType: '专用',
+  buyerName: '',
+  taxNo: '',
+  bankName: '',
+  bankAccount: '',
+  addressPhone: '',
+  relatedOrders: [] as string[],
+  amount: 0,
+  remark: ''
+});
+
+const filteredItems = computed(() => {
+  return items.value.filter(item => {
+    if (filters.value.invoiceNo && !item.invoiceNo.includes(filters.value.invoiceNo)) return false;
+    if (filters.value.invoiceType && item.invoiceType !== filters.value.invoiceType) return false;
+    if (filters.value.buyerName && !item.buyerName.includes(filters.value.buyerName)) return false;
+    if (filters.value.status && item.status !== filters.value.status) return false;
+    return true;
+  });
+});
+
+const statusTag = (status: string) => {
+  const map: Record<string, string> = { '待开票': 'warning', '已开票': 'success', '已作废': 'info', '已红冲': 'danger' };
+  return map[status] || '';
+};
+
+const loadData = () => {
+  loading.value = true;
+  setTimeout(() => { loading.value = false; }, 300);
+};
+
+const resetFilters = () => {
+  filters.value = { invoiceNo: '', invoiceType: '', buyerName: '', status: '', dateRange: [] };
+};
+
+const onSelection = (rows: InvoiceItem[]) => { selected.value = rows; };
+
+const openApply = () => {
+  applyForm.value = { invoiceType: '专用', buyerName: '', taxNo: '', bankName: '', bankAccount: '', addressPhone: '', relatedOrders: [], amount: 0, remark: '' };
+  applyVisible.value = true;
+};
+
+const submitApply = () => {
+  if (!applyForm.value.buyerName) {
+    ElMessage.warning('请填写购买方名称');
+    return;
+  }
+  ElMessage.success('开票申请已提交');
+  applyVisible.value = false;
+};
+
+const openDetail = (row: InvoiceItem) => {
+  detailItem.value = row;
+  detailVisible.value = true;
+};
+
+const openEdit = (row: InvoiceItem) => {
+  ElMessage.info('编辑功能开发中');
+};
+
+const voidInvoice = async (row: InvoiceItem) => {
+  await ElMessageBox.confirm(`确认作废发票 ${row.invoiceNo}?`, '发票作废');
+  const idx = items.value.findIndex(i => i.id === row.id);
+  if (idx !== -1) items.value[idx].status = '已作废';
+  ElMessage.success('发票已作废');
+};
+
+const doExport = () => {
+  ElMessage.info('导出开始,完成后将自动下载');
+};
+
+onMounted(loadData);
+</script>
+
+<style scoped>
+.filter-form :deep(.el-form-item) { margin-bottom: 0; }
+.tip-text { font-size: 13px; color: var(--cb-text-secondary); }
+</style>

+ 313 - 0
src/views/marketing/CouponView.vue

@@ -0,0 +1,313 @@
+<template>
+  <div class="app-page">
+    <section class="glass-card section-card">
+      <el-form :model="filters" inline class="filter-form">
+        <el-form-item label="优惠券名称">
+          <el-input v-model="filters.name" placeholder="优惠券名称" clearable style="width:150px" @keyup.enter="loadData" />
+        </el-form-item>
+        <el-form-item label="优惠券类型">
+          <el-select v-model="filters.couponType" placeholder="全部" clearable style="width:130px">
+            <el-option label="满减券" value="满减" />
+            <el-option label="折扣券" value="折扣" />
+            <el-option label="兑换券" value="兑换" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="状态">
+          <el-select v-model="filters.status" placeholder="全部" clearable style="width:120px">
+            <el-option label="待发放" value="待发放" />
+            <el-option label="进行中" value="进行中" />
+            <el-option label="已暂停" value="已暂停" />
+            <el-option label="已结束" value="已结束" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="有效期">
+          <el-date-picker v-model="filters.dateRange" type="daterange" range-separator="至" start-placeholder="开始" end-placeholder="结束" style="width:240px" />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="loadData">查询</el-button>
+          <el-button @click="resetFilters">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </section>
+
+    <section class="glass-card section-card" style="padding:12px 24px">
+      <div class="table-toolbar" style="margin-bottom:0">
+        <div class="chip-list">
+          <el-button type="primary" @click="openCreate">创建优惠券</el-button>
+          <el-button @click="doExport">导出</el-button>
+        </div>
+      </div>
+    </section>
+
+    <section class="glass-card section-card">
+      <el-table :data="filteredItems" stripe style="width:100%" v-loading="loading" @selection-change="onSelection">
+        <el-table-column type="selection" width="45" />
+        <el-table-column prop="name" label="优惠券名称" min-width="160" />
+        <el-table-column prop="couponType" label="类型" width="90">
+          <template #default="{ row }">
+            <el-tag size="small">{{ row.couponType }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="value" label="面值/折扣" width="110">
+          <template #default="{ row }">
+            <span style="font-weight:600">{{ row.value }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="minAmount" label="使用门槛" width="100">
+          <template #default="{ row }">
+            {{ row.minAmount === 0 ? '无门槛' : `满${row.minAmount}` }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="totalCount" label="发行量" width="80" />
+        <el-table-column prop="usedCount" label="已核销" width="80" />
+        <el-table-column prop="usedRate" label="核销率" width="80">
+          <template #default="{ row }">
+            <span :style="{ color: row.usedRate >= 70 ? 'var(--cb-success)' : row.usedRate >= 40 ? 'var(--cb-primary)' : 'var(--cb-accent)' }">
+              {{ row.usedRate }}%
+            </span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="validStart" label="开始时间" width="110" />
+        <el-table-column prop="validEnd" label="结束时间" width="110" />
+        <el-table-column prop="status" label="状态" width="90">
+          <template #default="{ row }">
+            <el-tag :type="statusTag(row.status)" size="small">{{ row.status }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="140" fixed="right">
+          <template #default="{ row }">
+            <el-button link type="primary" @click="openDetail(row)">详情</el-button>
+            <el-button link type="primary" @click="openEdit(row)">编辑</el-button>
+            <el-button link type="danger" @click="deleteCoupon(row)" v-if="row.status === '待发放'">删除</el-button>
+          </template>
+        </el-table-column>
+        <template #empty>
+          <el-empty description="暂无数据" />
+        </template>
+      </el-table>
+    </section>
+
+    <section class="glass-card section-card" style="padding:16px">
+      <h4 style="margin:0 0 12px">优惠券汇总</h4>
+      <div class="stat-grid" style="grid-template-columns:repeat(4, 1fr)">
+        <article class="stat-card">
+          <div class="stat-card__label">进行中活动</div>
+          <div class="stat-card__value" style="font-size:24px;color:var(--cb-primary)">8</div>
+        </article>
+        <article class="stat-card">
+          <div class="stat-card__label">本月核销金额</div>
+          <div class="stat-card__value" style="font-size:24px">¥45,200.00</div>
+        </article>
+        <article class="stat-card">
+          <div class="stat-card__label">平均核销率</div>
+          <div class="stat-card__value" style="font-size:24px">62.5%</div>
+        </article>
+        <article class="stat-card">
+          <div class="stat-card__label">带动销售额</div>
+          <div class="stat-card__value" style="font-size:24px;color:var(--cb-success)">¥328,600.00</div>
+        </article>
+      </div>
+    </section>
+
+    <el-dialog v-model="formVisible" :title="isEdit ? '编辑优惠券' : '创建优惠券'" width="560px">
+      <el-form :model="couponForm" label-width="100px">
+        <el-form-item label="优惠券名称" required>
+          <el-input v-model="couponForm.name" placeholder="请输入优惠券名称" />
+        </el-form-item>
+        <el-form-item label="优惠券类型" required>
+          <el-select v-model="couponForm.couponType" style="width:100%">
+            <el-option label="满减券" value="满减" />
+            <el-option label="折扣券" value="折扣" />
+            <el-option label="兑换券" value="兑换" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="面值/折扣" required v-if="couponForm.couponType !== '折扣'">
+          <el-input-number v-model="couponForm.value" :min="1" style="width:100%" />
+        </el-form-item>
+        <el-form-item label="折扣率" required v-else>
+          <el-input-number v-model="couponForm.discount" :min="1" :max="9" :precision="1" style="width:100%" />
+          <span style="margin-left:8px;color:var(--cb-text-secondary)">折</span>
+        </el-form-item>
+        <el-form-item label="使用门槛">
+          <el-input-number v-model="couponForm.minAmount" :min="0" style="width:100%" placeholder="0表示无门槛" />
+        </el-form-item>
+        <el-form-item label="发行量">
+          <el-input-number v-model="couponForm.totalCount" :min="1" style="width:100%" />
+        </el-form-item>
+        <el-form-item label="有效期" required>
+          <el-date-picker v-model="couponForm.dateRange" type="daterange" range-separator="至" start-placeholder="开始" end-placeholder="结束" style="width:100%" />
+        </el-form-item>
+        <el-form-item label="使用渠道">
+          <el-select v-model="couponForm.channels" multiple placeholder="选择渠道" style="width:100%">
+            <el-option label="Shopify" value="Shopify" />
+            <el-option label="TikTok Shop" value="TikTok Shop" />
+            <el-option label="Amazon" value="Amazon" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="备注">
+          <el-input v-model="couponForm.remark" type="textarea" rows="3" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="formVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitForm">{{ isEdit ? '保存' : '创建' }}</el-button>
+      </template>
+    </el-dialog>
+
+    <el-dialog v-model="detailVisible" title="优惠券详情" width="560px">
+      <el-descriptions :column="1" border v-if="detailItem">
+        <el-descriptions-item label="优惠券名称">{{ detailItem.name }}</el-descriptions-item>
+        <el-descriptions-item label="类型">{{ detailItem.couponType }}</el-descriptions-item>
+        <el-descriptions-item label="面值/折扣">{{ detailItem.value }}</el-descriptions-item>
+        <el-descriptions-item label="使用门槛">{{ detailItem.minAmount === 0 ? '无门槛' : `满${detailItem.minAmount}` }}</el-descriptions-item>
+        <el-descriptions-item label="发行量">{{ detailItem.totalCount }}</el-descriptions-item>
+        <el-descriptions-item label="已核销">{{ detailItem.usedCount }}</el-descriptions-item>
+        <el-descriptions-item label="核销率">{{ detailItem.usedRate }}%</el-descriptions-item>
+        <el-descriptions-item label="有效期">{{ detailItem.validStart }} 至 {{ detailItem.validEnd }}</el-descriptions-item>
+        <el-descriptions-item label="状态">
+          <el-tag :type="statusTag(detailItem.status)">{{ detailItem.status }}</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="使用渠道">{{ detailItem.channels?.join('、') || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="备注">{{ detailItem.remark || '-' }}</el-descriptions-item>
+      </el-descriptions>
+      <template #footer>
+        <el-button @click="detailVisible = false">关闭</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed, onMounted, ref } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+
+interface CouponItem {
+  id: string;
+  name: string;
+  couponType: string;
+  value: string;
+  minAmount: number;
+  totalCount: number;
+  usedCount: number;
+  usedRate: number;
+  validStart: string;
+  validEnd: string;
+  status: string;
+  channels: string[];
+  remark: string;
+}
+
+const items = ref<CouponItem[]>([
+  { id: 'C001', name: '新人专享满99减20', couponType: '满减', value: '¥20', minAmount: 99, totalCount: 5000, usedCount: 3280, usedRate: 65.6, validStart: '2026-04-01', validEnd: '2026-04-30', status: '进行中', channels: ['Shopify', 'TikTok Shop'], remark: '4月大促活动' },
+  { id: 'C002', name: '会员专享8折券', couponType: '折扣', value: '8折', minAmount: 0, totalCount: 10000, usedCount: 7250, usedRate: 72.5, validStart: '2026-04-01', validEnd: '2026-06-30', status: '进行中', channels: ['Shopify'], remark: '会员月活动' },
+  { id: 'C003', name: '满199减50', couponType: '满减', value: '¥50', minAmount: 199, totalCount: 3000, usedCount: 1890, usedRate: 63.0, validStart: '2026-03-15', validEnd: '2026-04-15', status: '已结束', channels: ['Amazon'], remark: '' },
+  { id: 'C004', name: '限时9折券', couponType: '折扣', value: '9折', minAmount: 0, totalCount: 2000, usedCount: 1200, usedRate: 60.0, validStart: '2026-04-10', validEnd: '2026-04-20', status: '已暂停', channels: ['TikTok Shop'], remark: '因技术问题暂停' },
+  { id: 'C005', name: '新品兑换券', couponType: '兑换', value: '免费兑换', minAmount: 0, totalCount: 500, usedCount: 156, usedRate: 31.2, validStart: '2026-04-18', validEnd: '2026-05-18', status: '进行中', channels: ['Shopify'], remark: '新品首发活动' },
+  { id: 'C006', name: '老客回归满149减30', couponType: '满减', value: '¥30', minAmount: 149, totalCount: 2000, usedCount: 0, usedRate: 0, validStart: '2026-04-25', validEnd: '2026-05-25', status: '待发放', channels: ['Shopify', 'Amazon'], remark: '即将发放' }
+]);
+
+const loading = ref(false);
+const selected = ref<CouponItem[]>([]);
+const formVisible = ref(false);
+const detailVisible = ref(false);
+const isEdit = ref(false);
+const detailItem = ref<CouponItem | null>(null);
+
+const filters = ref({
+  name: '',
+  couponType: '',
+  status: '',
+  dateRange: [] as string[]
+});
+
+const couponForm = ref({
+  name: '',
+  couponType: '满减',
+  value: 20,
+  discount: 9,
+  minAmount: 0,
+  totalCount: 1000,
+  dateRange: [] as string[],
+  channels: [] as string[],
+  remark: ''
+});
+
+const filteredItems = computed(() => {
+  return items.value.filter(item => {
+    if (filters.value.name && !item.name.includes(filters.value.name)) return false;
+    if (filters.value.couponType && item.couponType !== filters.value.couponType) return false;
+    if (filters.value.status && item.status !== filters.value.status) return false;
+    return true;
+  });
+});
+
+const statusTag = (status: string) => {
+  const map: Record<string, string> = { '待发放': 'info', '进行中': 'success', '已暂停': 'warning', '已结束': '' };
+  return map[status] || '';
+};
+
+const loadData = () => {
+  loading.value = true;
+  setTimeout(() => { loading.value = false; }, 300);
+};
+
+const resetFilters = () => {
+  filters.value = { name: '', couponType: '', status: '', dateRange: [] };
+};
+
+const onSelection = (rows: CouponItem[]) => { selected.value = rows; };
+
+const openCreate = () => {
+  isEdit.value = false;
+  couponForm.value = { name: '', couponType: '满减', value: 20, discount: 9, minAmount: 0, totalCount: 1000, dateRange: [], channels: [], remark: '' };
+  formVisible.value = true;
+};
+
+const openEdit = (row: CouponItem) => {
+  isEdit.value = true;
+  detailItem.value = null;
+  couponForm.value = {
+    name: row.name,
+    couponType: row.couponType,
+    value: parseInt(row.value.replace(/[^0-9]/g, '')),
+    discount: parseInt(row.value.replace(/[^0-9]/g, '')),
+    minAmount: row.minAmount,
+    totalCount: row.totalCount,
+    dateRange: [row.validStart, row.validEnd],
+    channels: row.channels,
+    remark: row.remark
+  };
+  formVisible.value = true;
+};
+
+const submitForm = () => {
+  if (!couponForm.value.name) {
+    ElMessage.warning('请填写优惠券名称');
+    return;
+  }
+  ElMessage.success(isEdit.value ? '优惠券已保存' : '优惠券已创建');
+  formVisible.value = false;
+};
+
+const openDetail = (row: CouponItem) => {
+  detailItem.value = row;
+  detailVisible.value = true;
+};
+
+const deleteCoupon = async (row: CouponItem) => {
+  await ElMessageBox.confirm(`确认删除优惠券 "${row.name}"?`, '删除确认');
+  const idx = items.value.findIndex(c => c.id === row.id);
+  if (idx !== -1) items.value.splice(idx, 1);
+  ElMessage.success('优惠券已删除');
+};
+
+const doExport = () => {
+  ElMessage.info('导出开始,完成后将自动下载');
+};
+
+onMounted(loadData);
+</script>
+
+<style scoped>
+.filter-form :deep(.el-form-item) { margin-bottom: 0; }
+</style>

+ 332 - 0
src/views/marketing/PriceWatchView.vue

@@ -0,0 +1,332 @@
+<template>
+  <div class="app-page">
+    <section class="glass-card section-card">
+      <el-form :model="filters" inline class="filter-form">
+        <el-form-item label="SKU">
+          <el-input v-model="filters.sku" placeholder="SKU编号" clearable style="width:140px" @keyup.enter="loadData" />
+        </el-form-item>
+        <el-form-item label="商品名称">
+          <el-input v-model="filters.productTitle" placeholder="商品名称" clearable style="width:160px" />
+        </el-form-item>
+        <el-form-item label="渠道">
+          <el-select v-model="filters.channel" placeholder="全部渠道" clearable style="width:140px">
+            <el-option label="Shopify" value="Shopify" />
+            <el-option label="TikTok Shop" value="TikTok Shop" />
+            <el-option label="Amazon" value="Amazon" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="预警状态">
+          <el-select v-model="filters.alertStatus" placeholder="全部" clearable style="width:120px">
+            <el-option label="正常" value="正常" />
+            <el-option label="价格高" value="价格高" />
+            <el-option label="价格低" value="价格低" />
+            <el-option label="价差大" value="价差大" />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="loadData">查询</el-button>
+          <el-button @click="resetFilters">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </section>
+
+    <section class="glass-card section-card" style="padding:12px 24px">
+      <div class="table-toolbar" style="margin-bottom:0">
+        <div class="chip-list">
+          <el-button @click="openConfig">价格源配置</el-button>
+          <el-button @click="doExport">导出</el-button>
+        </div>
+        <el-select v-model="alertThreshold" placeholder="价差阈值" clearable style="width:160px">
+          <el-option label="价差 > 10%" value="10" />
+          <el-option label="价差 > 20%" value="20" />
+          <el-option label="价差 > 30%" value="30" />
+        </el-select>
+      </div>
+    </section>
+
+    <section class="glass-card section-card">
+      <el-table :data="filteredItems" stripe style="width:100%" v-loading="loading" @selection-change="onSelection">
+        <el-table-column type="selection" width="45" />
+        <el-table-column prop="sku" label="SKU" width="130" />
+        <el-table-column prop="productTitle" label="商品标题" min-width="200" show-overflow-tooltip />
+        <el-table-column prop="channel" label="渠道" width="110" />
+        <el-table-column prop="shopName" label="店铺" width="140" />
+        <el-table-column prop="localPrice" label="本系统价格" width="110">
+          <template #default="{ row }">
+            <span style="font-weight:600">${{ row.localPrice }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="competitorPrice" label="竞品价格" width="110">
+          <template #default="{ row }">
+            <span>${{ row.competitorPrice }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="priceDiff" label="价差" width="80">
+          <template #default="{ row }">
+            <span :style="{ color: row.priceDiff > 0 ? 'var(--cb-danger)' : 'var(--cb-success)' }">
+              {{ row.priceDiff > 0 ? '+' : '' }}{{ row.priceDiff }}%
+            </span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="competitorName" label="竞品来源" width="120" show-overflow-tooltip />
+        <el-table-column prop="lastUpdate" label="更新时间" width="160" />
+        <el-table-column prop="alertStatus" label="预警状态" width="100">
+          <template #default="{ row }">
+            <el-tag :type="alertTag(row.alertStatus)" size="small">{{ row.alertStatus }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="160" fixed="right">
+          <template #default="{ row }">
+            <el-button link type="primary" @click="openDetail(row)">详情</el-button>
+            <el-button link type="primary" @click="adjustPrice(row)">调价</el-button>
+            <el-button link type="primary" @click="muteAlert(row)">忽略</el-button>
+          </template>
+        </el-table-column>
+        <template #empty>
+          <el-empty description="暂无数据" />
+        </template>
+      </el-table>
+    </section>
+
+    <section class="glass-card section-card" style="padding:16px">
+      <h4 style="margin:0 0 12px">价格监控汇总</h4>
+      <div class="stat-grid" style="grid-template-columns:repeat(4, 1fr)">
+        <article class="stat-card">
+          <div class="stat-card__label">监控商品数</div>
+          <div class="stat-card__value" style="font-size:24px;color:var(--cb-primary)">1,258</div>
+        </article>
+        <article class="stat-card">
+          <div class="stat-card__label">价格异常数</div>
+          <div class="stat-card__value" style="font-size:24px;color:var(--cb-danger)">42</div>
+        </article>
+        <article class="stat-card">
+          <div class="stat-card__label">本周调价次数</div>
+          <div class="stat-card__value" style="font-size:24px">128</div>
+        </article>
+        <article class="stat-card">
+          <div class="stat-card__label">平均价差</div>
+          <div class="stat-card__value" style="font-size:24px">+5.2%</div>
+        </article>
+      </div>
+    </section>
+
+    <el-dialog v-model="configVisible" title="价格源配置" width="600px">
+      <el-form :model="configForm" label-width="100px">
+        <el-form-item label="竞品来源">
+          <el-input v-model="configForm.sourceName" placeholder="如:Amazon官方旗舰店" style="width:100%" />
+        </el-form-item>
+        <el-form-item label="来源URL">
+          <el-input v-model="configForm.sourceUrl" placeholder="请输入竞品页面URL" style="width:100%" />
+        </el-form-item>
+        <el-form-item label="刷新频率">
+          <el-select v-model="configForm.refreshRate" style="width:100%">
+            <el-option label="每小时" value="1h" />
+            <el-option label="每6小时" value="6h" />
+            <el-option label="每天" value="1d" />
+            <el-option label="每周" value="1w" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="爬虫规则">
+          <el-input v-model="configForm.selector" placeholder="CSS选择器或XPath" style="width:100%" />
+        </el-form-item>
+        <el-form-item label="启用状态">
+          <el-switch v-model="configForm.enabled" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="configVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitConfig">保存</el-button>
+      </template>
+    </el-dialog>
+
+    <el-dialog v-model="adjustVisible" title="价格调整" width="480px">
+      <el-form :model="adjustForm" label-width="90px">
+        <el-form-item label="SKU">
+          <el-input v-model="adjustForm.sku" disabled />
+        </el-form-item>
+        <el-form-item label="当前价格">
+          <el-input v-model="adjustForm.currentPrice" disabled />
+        </el-form-item>
+        <el-form-item label="竞品价格">
+          <el-input v-model="adjustForm.competitorPrice" disabled />
+        </el-form-item>
+        <el-form-item label="调整方式">
+          <el-radio-group v-model="adjustForm.adjustType">
+            <el-radio label="fixed">固定价格</el-radio>
+            <el-radio label="formula">按公式</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="新价格" v-if="adjustForm.adjustType === 'fixed'">
+          <el-input-number v-model="adjustForm.newPrice" :min="0" :precision="2" style="width:100%" />
+        </el-form-item>
+        <el-form-item label="公式" v-else>
+          <el-input v-model="adjustForm.formula" placeholder="如:竞品价格 × 0.95" style="width:100%" />
+          <span style="color:var(--cb-text-secondary);font-size:12px;margin-top:4px">支持变量:competitor_price, cost_price, exchange_rate</span>
+        </el-form-item>
+        <el-form-item label="生效时间">
+          <el-date-picker v-model="adjustForm.effectiveTime" type="datetime" placeholder="立即生效" style="width:100%" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="adjustVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitAdjust">确认调整</el-button>
+      </template>
+    </el-dialog>
+
+    <el-dialog v-model="detailVisible" title="价格详情" width="560px">
+      <el-descriptions :column="1" border v-if="detailItem">
+        <el-descriptions-item label="SKU">{{ detailItem.sku }}</el-descriptions-item>
+        <el-descriptions-item label="商品标题">{{ detailItem.productTitle }}</el-descriptions-item>
+        <el-descriptions-item label="渠道">{{ detailItem.channel }}</el-descriptions-item>
+        <el-descriptions-item label="店铺">{{ detailItem.shopName }}</el-descriptions-item>
+        <el-descriptions-item label="本系统价格">${{ detailItem.localPrice }}</el-descriptions-item>
+        <el-descriptions-item label="竞品价格">${{ detailItem.competitorPrice }}</el-descriptions-item>
+        <el-descriptions-item label="价差">{{ detailItem.priceDiff }}%</el-descriptions-item>
+        <el-descriptions-item label="竞品来源">{{ detailItem.competitorName }}</el-descriptions-item>
+        <el-descriptions-item label="竞品URL">{{ detailItem.competitorUrl }}</el-descriptions-item>
+        <el-descriptions-item label="最后更新">{{ detailItem.lastUpdate }}</el-descriptions-item>
+        <el-descriptions-item label="预警状态">
+          <el-tag :type="alertTag(detailItem.alertStatus)">{{ detailItem.alertStatus }}</el-tag>
+        </el-descriptions-item>
+      </el-descriptions>
+      <template #footer>
+        <el-button @click="detailVisible = false">关闭</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed, onMounted, ref } from 'vue';
+import { ElMessage } from 'element-plus';
+
+interface PriceWatchItem {
+  id: string;
+  sku: string;
+  productTitle: string;
+  channel: string;
+  shopName: string;
+  localPrice: string;
+  competitorPrice: string;
+  priceDiff: number;
+  competitorName: string;
+  competitorUrl: string;
+  lastUpdate: string;
+  alertStatus: string;
+}
+
+const items = ref<PriceWatchItem[]>([
+  { id: 'PW001', sku: 'SKU-NM-BK-M', productTitle: 'Nomad 防水背包 黑色 中号', channel: 'Shopify', shopName: 'NomadPeak US', localPrice: '89.99', competitorPrice: '79.99', priceDiff: 12.5, competitorName: 'Amazon - TrailGear Official', competitorUrl: 'https://amazon.com/dp/B08XXXX', lastUpdate: '2026-04-20 10:30:15', alertStatus: '价格高' },
+  { id: 'PW002', sku: 'SKU-AE-GR-L', productTitle: 'AeroDry 速干T恤 绿色 L码', channel: 'TikTok Shop', shopName: 'AeroDry UK', localPrice: '24.99', competitorPrice: '27.99', priceDiff: -10.7, competitorName: 'TikTok Shop - SportsWorld', competitorUrl: 'https://tiktok.com/product/123', lastUpdate: '2026-04-20 09:15:22', alertStatus: '价格低' },
+  { id: 'PW003', sku: 'SKU-UT-WH-XL', productTitle: 'UrbanTrail 徒步鞋 白色 XL', channel: 'Amazon', shopName: 'UrbanTrail NA', localPrice: '129.00', competitorPrice: '119.00', priceDiff: 8.4, competitorName: 'Amazon - HikePro Store', competitorUrl: 'https://amazon.com/dp/B09YYYY', lastUpdate: '2026-04-20 08:45:33', alertStatus: '价差大' },
+  { id: 'PW004', sku: 'SKU-NM-BK-S', productTitle: 'Nomad 防水背包 黑色 小号', channel: 'Shopify', shopName: 'NomadPeak US', localPrice: '69.99', competitorPrice: '69.99', priceDiff: 0, competitorName: 'Amazon - TrailGear Official', competitorUrl: 'https://amazon.com/dp/B08ZZZZ', lastUpdate: '2026-04-19 22:20:45', alertStatus: '正常' },
+  { id: 'PW005', sku: 'SKU-AE-BL-M', productTitle: 'AeroDry 运动短裤 蓝色 M码', channel: 'TikTok Shop', shopName: 'AeroDry UK', localPrice: '19.99', competitorPrice: '22.99', priceDiff: -13.0, competitorName: 'Shopify - SportsLife', competitorUrl: 'https://sportslife.com/p/456', lastUpdate: '2026-04-19 18:35:08', alertStatus: '价格低' },
+  { id: 'PW006', sku: 'SKU-UT-BK-42', productTitle: 'UrbanTrail 登山靴 黑色 42码', channel: 'Amazon', shopName: 'UrbanTrail NA', localPrice: '189.00', competitorPrice: '199.00', priceDiff: -5.0, competitorName: 'Amazon - MountainGear', competitorUrl: 'https://amazon.com/dp/B07AAAA', lastUpdate: '2026-04-19 14:12:30', alertStatus: '正常' }
+]);
+
+const loading = ref(false);
+const selected = ref<PriceWatchItem[]>([]);
+const alertThreshold = ref('');
+const configVisible = ref(false);
+const adjustVisible = ref(false);
+const detailVisible = ref(false);
+const detailItem = ref<PriceWatchItem | null>(null);
+
+const filters = ref({
+  sku: '',
+  productTitle: '',
+  channel: '',
+  alertStatus: ''
+});
+
+const configForm = ref({
+  sourceName: '',
+  sourceUrl: '',
+  refreshRate: '1d',
+  selector: '',
+  enabled: true
+});
+
+const adjustForm = ref({
+  sku: '',
+  currentPrice: '',
+  competitorPrice: '',
+  adjustType: 'fixed',
+  newPrice: 0,
+  formula: '',
+  effectiveTime: ''
+});
+
+const filteredItems = computed(() => {
+  return items.value.filter(item => {
+    if (filters.value.sku && !item.sku.includes(filters.value.sku)) return false;
+    if (filters.value.productTitle && !item.productTitle.includes(filters.value.productTitle)) return false;
+    if (filters.value.channel && item.channel !== filters.value.channel) return false;
+    if (filters.value.alertStatus && item.alertStatus !== filters.value.alertStatus) return false;
+    return true;
+  });
+});
+
+const alertTag = (status: string) => {
+  const map: Record<string, string> = { '正常': 'success', '价格高': 'danger', '价格低': 'success', '价差大': 'warning' };
+  return map[status] || '';
+};
+
+const loadData = () => {
+  loading.value = true;
+  setTimeout(() => { loading.value = false; }, 300);
+};
+
+const resetFilters = () => {
+  filters.value = { sku: '', productTitle: '', channel: '', alertStatus: '' };
+};
+
+const onSelection = (rows: PriceWatchItem[]) => { selected.value = rows; };
+
+const openConfig = () => {
+  configForm.value = { sourceName: '', sourceUrl: '', refreshRate: '1d', selector: '', enabled: true };
+  configVisible.value = true;
+};
+
+const submitConfig = () => {
+  ElMessage.success('价格源配置已保存');
+  configVisible.value = false;
+};
+
+const openDetail = (row: PriceWatchItem) => {
+  detailItem.value = row;
+  detailVisible.value = true;
+};
+
+const adjustPrice = (row: PriceWatchItem) => {
+  adjustForm.value = {
+    sku: row.sku,
+    currentPrice: row.localPrice,
+    competitorPrice: row.competitorPrice,
+    adjustType: 'fixed',
+    newPrice: parseFloat(row.competitorPrice) * 0.95,
+    formula: `竞品价格 × 0.95 = $${(parseFloat(row.competitorPrice) * 0.95).toFixed(2)}`,
+    effectiveTime: ''
+  };
+  adjustVisible.value = true;
+};
+
+const submitAdjust = () => {
+  ElMessage.success('价格调整已提交');
+  adjustVisible.value = false;
+};
+
+const muteAlert = (row: PriceWatchItem) => {
+  ElMessage.success('已忽略该预警');
+};
+
+const doExport = () => {
+  ElMessage.info('导出开始,完成后将自动下载');
+};
+
+onMounted(loadData);
+</script>
+
+<style scoped>
+.filter-form :deep(.el-form-item) { margin-bottom: 0; }
+</style>

+ 346 - 0
src/views/supplier/SupplierPerformanceView.vue

@@ -0,0 +1,346 @@
+<template>
+  <div class="app-page">
+    <section class="glass-card section-card">
+      <el-form :model="filters" inline class="filter-form">
+        <el-form-item label="供应商">
+          <el-input v-model="filters.supplierName" placeholder="供应商名称" clearable style="width:150px" @keyup.enter="loadData" />
+        </el-form-item>
+        <el-form-item label="评分等级">
+          <el-select v-model="filters.ratingLevel" placeholder="全部" clearable style="width:120px">
+            <el-option label="A级" value="A" />
+            <el-option label="B级" value="B" />
+            <el-option label="C级" value="C" />
+            <el-option label="D级" value="D" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="合作状态">
+          <el-select v-model="filters.status" placeholder="全部" clearable style="width:120px">
+            <el-option label="合作中" value="合作中" />
+            <el-option label="已暂停" value="已暂停" />
+            <el-option label="已终止" value="已终止" />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="loadData">查询</el-button>
+          <el-button @click="resetFilters">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </section>
+
+    <section class="glass-card section-card" style="padding:12px 24px">
+      <div class="table-toolbar" style="margin-bottom:0">
+        <div class="chip-list">
+          <el-button type="primary" @click="openEvaluate">发起评价</el-button>
+          <el-button @click="doExport">导出报表</el-button>
+        </div>
+      </div>
+    </section>
+
+    <section class="glass-card section-card">
+      <el-table :data="filteredItems" stripe style="width:100%" v-loading="loading">
+        <el-table-column prop="supplierName" label="供应商" min-width="180" />
+        <el-table-column prop="contact" label="联系人" width="100" />
+        <el-table-column prop="phone" label="联系电话" width="130" />
+        <el-table-column prop="deliveryRate" label="交期达成率" width="100">
+          <template #default="{ row }">
+            <span :style="{ color: row.deliveryRate >= 95 ? 'var(--cb-success)' : row.deliveryRate >= 85 ? 'var(--cb-primary)' : 'var(--cb-danger)' }">
+              {{ row.deliveryRate }}%
+            </span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="qualityRate" label="质量合格率" width="100">
+          <template #default="{ row }">
+            <span :style="{ color: row.qualityRate >= 98 ? 'var(--cb-success)' : row.qualityRate >= 95 ? 'var(--cb-primary)' : 'var(--cb-danger)' }">
+              {{ row.qualityRate }}%
+            </span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="responseTime" label="响应时效" width="90">
+          <template #default="{ row }">
+            <span>{{ row.responseTime }}h</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="returnRate" label="退货率" width="80">
+          <template #default="{ row }">
+            <span :style="{ color: row.returnRate <= 2 ? 'var(--cb-success)' : row.returnRate <= 5 ? 'var(--cb-primary)' : 'var(--cb-danger)' }">
+              {{ row.returnRate }}%
+            </span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="priceScore" label="价格竞争力" width="100">
+          <template #default="{ row }">
+            <el-rate v-model="row.priceScore" disabled text-color="#ff9900" score-template="{value}" />
+          </template>
+        </el-table-column>
+        <el-table-column prop="overallScore" label="综合评分" width="100">
+          <template #default="{ row }">
+            <span style="font-weight:600">{{ row.overallScore }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="ratingLevel" label="等级" width="70">
+          <template #default="{ row }">
+            <el-tag :type="levelTag(row.ratingLevel)" size="small">{{ row.ratingLevel }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="status" label="状态" width="90">
+          <template #default="{ row }">
+            <el-tag :type="statusTag(row.status)" size="small">{{ row.status }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="140" fixed="right">
+          <template #default="{ row }">
+            <el-button link type="primary" @click="openDetail(row)">详情</el-button>
+            <el-button link type="primary" @click="openTrend(row)">趋势</el-button>
+          </template>
+        </el-table-column>
+        <template #empty>
+          <el-empty description="暂无数据" />
+        </template>
+      </el-table>
+    </section>
+
+    <section class="glass-card section-card" style="padding:16px">
+      <h4 style="margin:0 0 12px">供应商绩效概览</h4>
+      <div class="stat-grid" style="grid-template-columns:repeat(5, 1fr)">
+        <article class="stat-card">
+          <div class="stat-card__label">合作供应商</div>
+          <div class="stat-card__value" style="font-size:24px;color:var(--cb-primary)">28</div>
+        </article>
+        <article class="stat-card">
+          <div class="stat-card__label">A级供应商</div>
+          <div class="stat-card__value" style="font-size:24px;color:var(--cb-success)">12</div>
+        </article>
+        <article class="stat-card">
+          <div class="stat-card__label">平均交期达成率</div>
+          <div class="stat-card__value" style="font-size:24px">94.2%</div>
+        </article>
+        <article class="stat-card">
+          <div class="stat-card__label">平均质量合格率</div>
+          <div class="stat-card__value" style="font-size:24px">97.8%</div>
+        </article>
+        <article class="stat-card">
+          <div class="stat-card__label">平均综合评分</div>
+          <div class="stat-card__value" style="font-size:24px">4.2</div>
+        </article>
+      </div>
+    </section>
+
+    <el-dialog v-model="detailVisible" title="供应商绩效详情" width="700px">
+      <el-descriptions :column="2" border v-if="detailItem">
+        <el-descriptions-item label="供应商名称" :span="2">{{ detailItem.supplierName }}</el-descriptions-item>
+        <el-descriptions-item label="联系人">{{ detailItem.contact }}</el-descriptions-item>
+        <el-descriptions-item label="联系电话">{{ detailItem.phone }}</el-descriptions-item>
+        <el-descriptions-item label="交期达成率">{{ detailItem.deliveryRate }}%</el-descriptions-item>
+        <el-descriptions-item label="质量合格率">{{ detailItem.qualityRate }}%</el-descriptions-item>
+        <el-descriptions-item label="响应时效">{{ detailItem.responseTime }}h</el-descriptions-item>
+        <el-descriptions-item label="退货率">{{ detailItem.returnRate }}%</el-descriptions-item>
+        <el-descriptions-item label="价格竞争力">
+          <el-rate v-model="detailItem.priceScore" disabled />
+        </el-descriptions-item>
+        <el-descriptions-item label="综合评分">
+          <span style="font-weight:600;font-size:18px">{{ detailItem.overallScore }}</span>
+        </el-descriptions-item>
+        <el-descriptions-item label="等级">
+          <el-tag :type="levelTag(detailItem.ratingLevel)">{{ detailItem.ratingLevel }}</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="合作状态">
+          <el-tag :type="statusTag(detailItem.status)">{{ detailItem.status }}</el-tag>
+        </el-descriptions-item>
+      </el-descriptions>
+      <div style="margin-top:16px">
+        <h4 style="margin:0 0 12px">最近评价记录</h4>
+        <el-table :data="recentEvaluations" stripe size="small">
+          <el-table-column prop="evaluateTime" label="评价时间" width="160" />
+          <el-table-column prop="evaluator" label="评价人" width="100" />
+          <el-table-column prop="dimension" label="评价维度" width="120" />
+          <el-table-column prop="score" label="评分" width="80" />
+          <el-table-column prop="remark" label="备注" min-width="150" />
+        </el-table>
+      </div>
+      <template #footer>
+        <el-button @click="detailVisible = false">关闭</el-button>
+      </template>
+    </el-dialog>
+
+    <el-dialog v-model="trendVisible" title="绩效趋势" width="700px">
+      <div style="height:300px;display:flex;align-items:center;justify-content:center;color:var(--cb-text-secondary)">
+        <div style="text-align:center">
+          <div style="font-size:48px;margin-bottom:8px">📈</div>
+          <div>绩效趋势图表区域</div>
+          <div style="font-size:12px;margin-top:4px">{{ trendSupplier }} - 最近6个月趋势</div>
+        </div>
+      </div>
+      <template #footer>
+        <el-button @click="trendVisible = false">关闭</el-button>
+      </template>
+    </el-dialog>
+
+    <el-dialog v-model="evaluateVisible" title="发起供应商评价" width="520px">
+      <el-form :model="evaluateForm" label-width="100px">
+        <el-form-item label="供应商" required>
+          <el-select v-model="evaluateForm.supplierId" placeholder="选择供应商" style="width:100%">
+            <el-option v-for="s in suppliers" :key="s.id" :label="s.name" :value="s.id" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="评价维度">
+          <div style="width:100%">
+            <div style="margin-bottom:8px">
+              <span style="width:80px;display:inline-block">交期评分</span>
+              <el-rate v-model="evaluateForm.deliveryScore" />
+            </div>
+            <div style="margin-bottom:8px">
+              <span style="width:80px;display:inline-block">质量评分</span>
+              <el-rate v-model="evaluateForm.qualityScore" />
+            </div>
+            <div style="margin-bottom:8px">
+              <span style="width:80px;display:inline-block">服务评分</span>
+              <el-rate v-model="evaluateForm.serviceScore" />
+            </div>
+            <div>
+              <span style="width:80px;display:inline-block">价格评分</span>
+              <el-rate v-model="evaluateForm.priceScore" />
+            </div>
+          </div>
+        </el-form-item>
+        <el-form-item label="评价备注">
+          <el-input v-model="evaluateForm.remark" type="textarea" rows="4" placeholder="请输入评价备注" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="evaluateVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitEvaluate">提交评价</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed, onMounted, ref } from 'vue';
+import { ElMessage } from 'element-plus';
+
+interface SupplierPerformanceItem {
+  id: string;
+  supplierName: string;
+  contact: string;
+  phone: string;
+  deliveryRate: number;
+  qualityRate: number;
+  responseTime: number;
+  returnRate: number;
+  priceScore: number;
+  overallScore: number;
+  ratingLevel: string;
+  status: string;
+}
+
+interface Supplier {
+  id: string;
+  name: string;
+}
+
+const items = ref<SupplierPerformanceItem[]>([
+  { id: 'SP001', supplierName: '深圳华通供应链有限公司', contact: '王经理', phone: '138-0000-1001', deliveryRate: 98.5, qualityRate: 99.2, responseTime: 2, returnRate: 0.8, priceScore: 4.5, overallScore: 4.6, ratingLevel: 'A', status: '合作中' },
+  { id: 'SP002', supplierName: '广州鼎盛纺织制衣厂', contact: '李总', phone: '139-0000-1002', deliveryRate: 95.0, qualityRate: 97.5, responseTime: 4, returnRate: 2.1, priceScore: 4.8, overallScore: 4.4, ratingLevel: 'A', status: '合作中' },
+  { id: 'SP003', supplierName: '东莞鑫达皮革制品厂', contact: '张主管', phone: '137-0000-1003', deliveryRate: 88.0, qualityRate: 94.0, responseTime: 8, returnRate: 4.5, priceScore: 4.2, overallScore: 3.8, ratingLevel: 'B', status: '合作中' },
+  { id: 'SP004', supplierName: '宁波海天塑业有限公司', contact: '陈经理', phone: '136-0000-1004', deliveryRate: 92.0, qualityRate: 96.0, responseTime: 6, returnRate: 3.2, priceScore: 4.0, overallScore: 3.9, ratingLevel: 'B', status: '合作中' },
+  { id: 'SP005', supplierName: '苏州金诚纺织集团', contact: '刘总', phone: '135-0000-1005', deliveryRate: 82.0, qualityRate: 91.0, responseTime: 12, returnRate: 6.8, priceScore: 3.5, overallScore: 3.2, ratingLevel: 'C', status: '已暂停' },
+  { id: 'SP006', supplierName: '温州华侨鞋材有限公司', contact: '黄经理', phone: '134-0000-1006', deliveryRate: 78.0, qualityRate: 88.0, responseTime: 24, returnRate: 8.5, priceScore: 3.0, overallScore: 2.8, ratingLevel: 'D', status: '已终止' }
+]);
+
+const suppliers = ref<Supplier[]>([
+  { id: 'SP001', name: '深圳华通供应链有限公司' },
+  { id: 'SP002', name: '广州鼎盛纺织制衣厂' },
+  { id: 'SP003', name: '东莞鑫达皮革制品厂' },
+  { id: 'SP004', name: '宁波海天塑业有限公司' }
+]);
+
+const recentEvaluations = ref([
+  { evaluateTime: '2026-04-15 10:30:00', evaluator: '采购部-张明', dimension: '交期评分', score: 5, remark: '准时交货' },
+  { evaluateTime: '2026-04-10 14:20:00', evaluator: '品质部-李华', dimension: '质量评分', score: 5, remark: '抽检全部合格' },
+  { evaluateTime: '2026-04-05 09:15:00', evaluator: '采购部-张明', dimension: '综合评价', score: 4, remark: '配合度良好' }
+]);
+
+const loading = ref(false);
+const detailVisible = ref(false);
+const trendVisible = ref(false);
+const evaluateVisible = ref(false);
+const trendSupplier = ref('');
+const detailItem = ref<SupplierPerformanceItem | null>(null);
+
+const filters = ref({
+  supplierName: '',
+  ratingLevel: '',
+  status: ''
+});
+
+const evaluateForm = ref({
+  supplierId: '',
+  deliveryScore: 5,
+  qualityScore: 5,
+  serviceScore: 5,
+  priceScore: 5,
+  remark: ''
+});
+
+const filteredItems = computed(() => {
+  return items.value.filter(item => {
+    if (filters.value.supplierName && !item.supplierName.includes(filters.value.supplierName)) return false;
+    if (filters.value.ratingLevel && item.ratingLevel !== filters.value.ratingLevel) return false;
+    if (filters.value.status && item.status !== filters.value.status) return false;
+    return true;
+  });
+});
+
+const levelTag = (level: string) => {
+  const map: Record<string, string> = { 'A': 'success', 'B': '', 'C': 'warning', 'D': 'danger' };
+  return map[level] || '';
+};
+
+const statusTag = (status: string) => {
+  const map: Record<string, string> = { '合作中': 'success', '已暂停': 'warning', '已终止': 'danger' };
+  return map[status] || '';
+};
+
+const loadData = () => {
+  loading.value = true;
+  setTimeout(() => { loading.value = false; }, 300);
+};
+
+const resetFilters = () => {
+  filters.value = { supplierName: '', ratingLevel: '', status: '' };
+};
+
+const openDetail = (row: SupplierPerformanceItem) => {
+  detailItem.value = row;
+  detailVisible.value = true;
+};
+
+const openTrend = (row: SupplierPerformanceItem) => {
+  trendSupplier.value = row.supplierName;
+  trendVisible.value = true;
+};
+
+const openEvaluate = () => {
+  evaluateForm.value = { supplierId: '', deliveryScore: 5, qualityScore: 5, serviceScore: 5, priceScore: 5, remark: '' };
+  evaluateVisible.value = true;
+};
+
+const submitEvaluate = () => {
+  if (!evaluateForm.value.supplierId) {
+    ElMessage.warning('请选择供应商');
+    return;
+  }
+  ElMessage.success('评价已提交');
+  evaluateVisible.value = false;
+};
+
+const doExport = () => {
+  ElMessage.info('导出开始,完成后将自动下载');
+};
+
+onMounted(loadData);
+</script>
+
+<style scoped>
+.filter-form :deep(.el-form-item) { margin-bottom: 0; }
+</style>

+ 296 - 0
src/views/system/DepartmentView.vue

@@ -0,0 +1,296 @@
+<template>
+  <div class="app-page">
+    <section class="glass-card section-card">
+      <el-form :model="filters" inline class="filter-form">
+        <el-form-item label="部门名称">
+          <el-input v-model="filters.name" placeholder="部门名称" clearable style="width:150px" @keyup.enter="loadData" />
+        </el-form-item>
+        <el-form-item label="状态">
+          <el-select v-model="filters.status" placeholder="全部" clearable style="width:110px">
+            <el-option label="正常" value="正常" />
+            <el-option label="已停用" value="已停用" />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="loadData">查询</el-button>
+          <el-button @click="resetFilters">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </section>
+
+    <section class="glass-card section-card" style="padding:12px 24px">
+      <div class="table-toolbar" style="margin-bottom:0">
+        <div class="chip-list">
+          <el-button type="primary" @click="openCreate">新建部门</el-button>
+          <el-button @click="expandAll">展开全部</el-button>
+          <el-button @click="collapseAll">收起全部</el-button>
+        </div>
+      </div>
+    </section>
+
+    <section class="glass-card section-card">
+      <el-tree :data="treeData" :props="treeProps" node-key="id" default-expand-all :expand-on-click-node="false" @node-click="onNodeClick">
+        <template #default="{ node, data }">
+          <span class="custom-tree-node">
+            <span>
+              <el-icon v-if="data.children?.length"><FolderOpened /></el-icon>
+              <el-icon v-else><Document /></el-icon>
+              <span style="margin-left:8px">{{ data.name }}</span>
+              <el-tag size="small" style="margin-left:8px" :type="data.status === '正常' ? 'success' : 'info'">{{ data.status }}</el-tag>
+            </span>
+            <span class="node-actions">
+              <el-button link type="primary" @click.stop="openEdit(data)">编辑</el-button>
+              <el-button link type="primary" @click.stop="addChild(data)" v-if="!data.parentId">添加子部门</el-button>
+              <el-button link type="danger" @click.stop="deleteDept(data)" v-if="!data.children?.length">删除</el-button>
+            </span>
+          </span>
+        </template>
+      </el-tree>
+    </section>
+
+    <section class="glass-card section-card" style="padding:16px">
+      <h4 style="margin:0 0 12px">部门人员分布</h4>
+      <div class="stat-grid" style="grid-template-columns:repeat(auto-fill, minmax(150px, 1fr))">
+        <article class="stat-card" v-for="dept in deptStats" :key="dept.name">
+          <div class="stat-card__label">{{ dept.name }}</div>
+          <div class="stat-card__value" style="font-size:20px">{{ dept.count }}人</div>
+        </article>
+      </div>
+    </section>
+
+    <el-dialog v-model="formVisible" :title="isEdit ? '编辑部门' : '新建部门'" width="520px">
+      <el-form :model="deptForm" label-width="100px">
+        <el-form-item label="部门名称" required>
+          <el-input v-model="deptForm.name" placeholder="请输入部门名称" />
+        </el-form-item>
+        <el-form-item label="上级部门" v-if="deptForm.parentId">
+          <el-input :value="parentName" disabled />
+        </el-form-item>
+        <el-form-item label="上级部门" v-else>
+          <el-tree-select v-model="deptForm.parentId" :data="selectTreeData" placeholder="选择上级部门(不选则为顶级)" clearable style="width:100%" :props="{ label: 'name', value: 'id' }" />
+        </el-form-item>
+        <el-form-item label="部门负责人">
+          <el-select v-model="deptForm.leaderId" placeholder="选择负责人" clearable style="width:100%">
+            <el-option v-for="e in employees" :key="e.id" :label="e.name" :value="e.id" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="部门职能">
+          <el-input v-model="deptForm.description" type="textarea" rows="3" placeholder="请输入部门职能描述" />
+        </el-form-item>
+        <el-form-item label="状态">
+          <el-radio-group v-model="deptForm.status">
+            <el-radio label="正常">正常</el-radio>
+            <el-radio label="已停用">已停用</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="formVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitForm">{{ isEdit ? '保存' : '创建' }}</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed, onMounted, ref } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { FolderOpened, Document } from '@element-plus/icons-vue';
+
+interface DeptItem {
+  id: string;
+  name: string;
+  parentId?: string;
+  leaderId?: string;
+  leaderName?: string;
+  description: string;
+  status: string;
+  children?: DeptItem[];
+  employeeCount?: number;
+}
+
+const rawDepts = ref<DeptItem[]>([
+  { id: 'D001', name: '总经理办公室', leaderId: 'E001', leaderName: '张明', description: '公司整体战略与管理', status: '正常', employeeCount: 5 },
+  { id: 'D002', name: '研发部', parentId: 'D001', leaderId: 'E001', leaderName: '张明', description: '产品研发与技术支撑', status: '正常', employeeCount: 45 },
+  { id: 'D003', name: '销售部', parentId: 'D001', leaderId: 'E002', leaderName: '李华', description: '市场拓展与销售业绩', status: '正常', employeeCount: 38 },
+  { id: 'D004', name: '采购部', parentId: 'D001', leaderId: 'E003', leaderName: '王芳', description: '供应商管理与采购执行', status: '正常', employeeCount: 18 },
+  { id: 'D005', name: '仓储部', parentId: 'D001', leaderId: 'E004', leaderName: '刘强', description: '仓库管理与物流配送', status: '正常', employeeCount: 28 },
+  { id: 'D006', name: '财务部', parentId: 'D001', leaderId: 'E005', leaderName: '陈静', description: '财务核算与资金管理', status: '正常', employeeCount: 12 },
+  { id: 'D007', name: '人力资源部', parentId: 'D001', description: '人力资源规划与招聘培训', status: '正常', employeeCount: 8 },
+  { id: 'D008', name: '前端开发组', parentId: 'D002', description: '前端技术研发', status: '正常', employeeCount: 15 },
+  { id: 'D009', name: '后端开发组', parentId: 'D002', description: '后端技术研发', status: '正常', employeeCount: 18 },
+  { id: 'D010', name: '运营组', parentId: 'D003', description: '日常运营与数据分析', status: '正常', employeeCount: 20 },
+  { id: 'D011', name: '客服组', parentId: 'D003', description: '客户服务与售后支持', status: '正常', employeeCount: 12 }
+]);
+
+const employees = ref<{ id: string; name: string }[]>([
+  { id: 'E001', name: '张明' },
+  { id: 'E002', name: '李华' },
+  { id: 'E003', name: '王芳' },
+  { id: 'E004', name: '刘强' },
+  { id: 'E005', name: '陈静' }
+]);
+
+const treeData = ref<DeptItem[]>([]);
+const selectTreeData = ref<DeptItem[]>([]);
+const loading = ref(false);
+const formVisible = ref(false);
+const isEdit = ref(false);
+const parentName = ref('');
+const selectedNode = ref<DeptItem | null>(null);
+
+const filters = ref({
+  name: '',
+  status: ''
+});
+
+const deptForm = ref({
+  id: '',
+  name: '',
+  parentId: '',
+  leaderId: '',
+  description: '',
+  status: '正常'
+});
+
+const treeProps = {
+  children: 'children',
+  label: 'name'
+};
+
+const deptStats = computed(() => {
+  return rawDepts.value.filter(d => !d.parentId).map(d => ({
+    name: d.name,
+    count: d.employeeCount || 0
+  }));
+});
+
+const buildTree = (depts: DeptItem[]): DeptItem[] => {
+  const map = new Map<string, DeptItem>();
+  const roots: DeptItem[] = [];
+
+  depts.forEach(d => map.set(d.id, { ...d, children: [] }));
+
+  depts.forEach(d => {
+    const node = map.get(d.id)!;
+    if (d.parentId && map.has(d.parentId)) {
+      map.get(d.parentId)!.children!.push(node);
+    } else {
+      roots.push(node);
+    }
+  });
+
+  return roots;
+};
+
+const loadData = () => {
+  loading.value = true;
+  treeData.value = buildTree(rawDepts.value);
+  selectTreeData.value = [{ id: '', name: '无上级(顶级部门)' }, ...rawDepts.value.filter(d => !d.parentId)];
+  setTimeout(() => { loading.value = false; }, 300);
+};
+
+const resetFilters = () => {
+  filters.value = { name: '', status: '' };
+};
+
+const onNodeClick = (data: DeptItem) => {
+  selectedNode.value = data;
+};
+
+const expandAll = () => {
+  document.querySelectorAll('.el-tree-node').forEach(node => {
+    (node as any).expanded = true;
+  });
+};
+
+const collapseAll = () => {
+  document.querySelectorAll('.el-tree-node').forEach(node => {
+    (node as any).expanded = false;
+  });
+};
+
+const openCreate = () => {
+  isEdit.value = false;
+  deptForm.value = { id: '', name: '', parentId: '', leaderId: '', description: '', status: '正常' };
+  parentName.value = '';
+  formVisible.value = true;
+};
+
+const openEdit = (row: DeptItem) => {
+  isEdit.value = true;
+  deptForm.value = {
+    id: row.id,
+    name: row.name,
+    parentId: row.parentId || '',
+    leaderId: row.leaderId || '',
+    description: row.description,
+    status: row.status
+  };
+  parentName.value = row.parentId ? (rawDepts.value.find(d => d.id === row.parentId)?.name || '') : '';
+  formVisible.value = true;
+};
+
+const addChild = (parent: DeptItem) => {
+  isEdit.value = false;
+  deptForm.value = { id: '', name: '', parentId: parent.id, leaderId: '', description: '', status: '正常' };
+  parentName.value = parent.name;
+  formVisible.value = true;
+};
+
+const submitForm = () => {
+  if (!deptForm.value.name) {
+    ElMessage.warning('请填写部门名称');
+    return;
+  }
+  if (isEdit.value) {
+    const idx = rawDepts.value.findIndex(d => d.id === deptForm.value.id);
+    if (idx !== -1) {
+      rawDepts.value[idx] = { ...rawDepts.value[idx], ...deptForm.value };
+    }
+    ElMessage.success('部门已保存');
+  } else {
+    const newDept: DeptItem = {
+      id: 'D' + Date.now(),
+      name: deptForm.value.name,
+      parentId: deptForm.value.parentId || undefined,
+      leaderId: deptForm.value.leaderId || undefined,
+      description: deptForm.value.description,
+      status: deptForm.value.status,
+      employeeCount: 0
+    };
+    rawDepts.value.push(newDept);
+    ElMessage.success('部门已创建');
+  }
+  formVisible.value = false;
+  loadData();
+};
+
+const deleteDept = async (row: DeptItem) => {
+  await ElMessageBox.confirm(`确认删除部门 "${row.name}"?`, '删除确认');
+  const idx = rawDepts.value.findIndex(d => d.id === row.id);
+  if (idx !== -1) rawDepts.value.splice(idx, 1);
+  ElMessage.success('部门已删除');
+  loadData();
+};
+
+onMounted(loadData);
+</script>
+
+<style scoped>
+.filter-form :deep(.el-form-item) { margin-bottom: 0; }
+.custom-tree-node {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  width: 100%;
+  padding-right: 8px;
+}
+.node-actions {
+  display: none;
+}
+:deep(.el-tree-node__content:hover .node-actions) {
+  display: flex;
+  gap: 4px;
+}
+</style>

+ 369 - 0
src/views/system/EmployeeView.vue

@@ -0,0 +1,369 @@
+<template>
+  <div class="app-page">
+    <section class="glass-card section-card">
+      <el-form :model="filters" inline class="filter-form">
+        <el-form-item label="工号/姓名">
+          <el-input v-model="filters.keyword" placeholder="工号或姓名" clearable style="width:140px" @keyup.enter="loadData" />
+        </el-form-item>
+        <el-form-item label="部门">
+          <el-select v-model="filters.department" placeholder="全部部门" clearable style="width:140px">
+            <el-option label="研发部" value="研发部" />
+            <el-option label="销售部" value="销售部" />
+            <el-option label="采购部" value="采购部" />
+            <el-option label="仓储部" value="仓储部" />
+            <el-option label="财务部" value="财务部" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="岗位">
+          <el-input v-model="filters.position" placeholder="岗位名称" clearable style="width:130px" />
+        </el-form-item>
+        <el-form-item label="角色">
+          <el-select v-model="filters.role" placeholder="全部角色" clearable style="width:130px">
+            <el-option label="管理员" value="admin" />
+            <el-option label="经理" value="manager" />
+            <el-option label="运营" value="operator" />
+            <el-option label="采购" value="procurement" />
+            <el-option label="仓库" value="warehouse" />
+            <el-option label="客服" value="customer_service" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="状态">
+          <el-select v-model="filters.status" placeholder="全部" clearable style="width:100px">
+            <el-option label="在职" value="在职" />
+            <el-option label="离职" value="离职" />
+            <el-option label="待入职" value="待入职" />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="loadData">查询</el-button>
+          <el-button @click="resetFilters">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </section>
+
+    <section class="glass-card section-card" style="padding:12px 24px">
+      <div class="table-toolbar" style="margin-bottom:0">
+        <div class="chip-list">
+          <el-button type="primary" @click="openCreate">新建员工</el-button>
+          <el-button @click="doExport">导出</el-button>
+        </div>
+      </div>
+    </section>
+
+    <section class="glass-card section-card">
+      <el-table :data="filteredItems" stripe style="width:100%" v-loading="loading" @selection-change="onSelection">
+        <el-table-column type="selection" width="45" />
+        <el-table-column prop="employeeNo" label="工号" width="100" />
+        <el-table-column prop="name" label="姓名" width="100" />
+        <el-table-column prop="avatar" label="头像" width="70">
+          <template #default="{ row }">
+            <el-avatar :size="36" :src="row.avatar" />
+          </template>
+        </el-table-column>
+        <el-table-column prop="department" label="部门" width="100" />
+        <el-table-column prop="position" label="岗位" width="120" />
+        <el-table-column prop="role" label="系统角色" width="100">
+          <template #default="{ row }">
+            <el-tag size="small">{{ roleLabel(row.role) }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="phone" label="手机号" width="130" />
+        <el-table-column prop="email" label="邮箱" min-width="180" show-overflow-tooltip />
+        <el-table-column prop="status" label="状态" width="80">
+          <template #default="{ row }">
+            <el-tag :type="statusTag(row.status)" size="small">{{ row.status }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="hireDate" label="入职日期" width="110" />
+        <el-table-column prop="supervisor" label="汇报给" width="100" />
+        <el-table-column label="操作" width="160" fixed="right">
+          <template #default="{ row }">
+            <el-button link type="primary" @click="openDetail(row)">详情</el-button>
+            <el-button link type="primary" @click="openEdit(row)">编辑</el-button>
+            <el-button link type="danger" @click="terminateEmployee(row)" v-if="row.status === '在职'">离职</el-button>
+          </template>
+        </el-table-column>
+        <template #empty>
+          <el-empty description="暂无数据" />
+        </template>
+      </el-table>
+    </section>
+
+    <section class="glass-card section-card" style="padding:16px">
+      <h4 style="margin:0 0 12px">员工统计</h4>
+      <div class="stat-grid" style="grid-template-columns:repeat(4, 1fr)">
+        <article class="stat-card">
+          <div class="stat-card__label">总人数</div>
+          <div class="stat-card__value" style="font-size:24px;color:var(--cb-primary)">156</div>
+        </article>
+        <article class="stat-card">
+          <div class="stat-card__label">在职</div>
+          <div class="stat-card__value" style="font-size:24px;color:var(--cb-success)">142</div>
+        </article>
+        <article class="stat-card">
+          <div class="stat-card__label">本月新增</div>
+          <div class="stat-card__value" style="font-size:24px">8</div>
+        </article>
+        <article class="stat-card">
+          <div class="stat-card__label">本月离职</div>
+          <div class="stat-card__value" style="font-size:24px;color:var(--cb-danger)">3</div>
+        </article>
+      </div>
+    </section>
+
+    <el-dialog v-model="formVisible" :title="isEdit ? '编辑员工' : '新建员工'" width="600px">
+      <el-form :model="employeeForm" label-width="100px">
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="工号" required>
+              <el-input v-model="employeeForm.employeeNo" placeholder="自动生成或手动输入" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="姓名" required>
+              <el-input v-model="employeeForm.name" placeholder="请输入姓名" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="部门" required>
+              <el-select v-model="employeeForm.department" placeholder="选择部门" style="width:100%">
+                <el-option label="研发部" value="研发部" />
+                <el-option label="销售部" value="销售部" />
+                <el-option label="采购部" value="采购部" />
+                <el-option label="仓储部" value="仓储部" />
+                <el-option label="财务部" value="财务部" />
+                <el-option label="人力资源部" value="人力资源部" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="岗位" required>
+              <el-input v-model="employeeForm.position" placeholder="请输入岗位" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="系统角色" required>
+              <el-select v-model="employeeForm.role" placeholder="选择角色" style="width:100%">
+                <el-option label="管理员" value="admin" />
+                <el-option label="经理" value="manager" />
+                <el-option label="运营" value="operator" />
+                <el-option label="采购" value="procurement" />
+                <el-option label="仓库" value="warehouse" />
+                <el-option label="客服" value="customer_service" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="汇报给">
+              <el-select v-model="employeeForm.supervisorId" placeholder="选择上级" clearable style="width:100%">
+                <el-option v-for="e in employees" :key="e.id" :label="e.name" :value="e.id" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="手机号" required>
+              <el-input v-model="employeeForm.phone" placeholder="请输入手机号" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="邮箱">
+              <el-input v-model="employeeForm.email" placeholder="请输入邮箱" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-form-item label="入职日期">
+          <el-date-picker v-model="employeeForm.hireDate" type="date" placeholder="选择日期" style="width:100%" />
+        </el-form-item>
+        <el-form-item label="备注">
+          <el-input v-model="employeeForm.remark" type="textarea" rows="3" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="formVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitForm">{{ isEdit ? '保存' : '创建' }}</el-button>
+      </template>
+    </el-dialog>
+
+    <el-dialog v-model="detailVisible" title="员工详情" width="600px">
+      <el-descriptions :column="2" border v-if="detailItem">
+        <el-descriptions-item label="工号">{{ detailItem.employeeNo }}</el-descriptions-item>
+        <el-descriptions-item label="姓名">{{ detailItem.name }}</el-descriptions-item>
+        <el-descriptions-item label="部门">{{ detailItem.department }}</el-descriptions-item>
+        <el-descriptions-item label="岗位">{{ detailItem.position }}</el-descriptions-item>
+        <el-descriptions-item label="系统角色">
+          <el-tag size="small">{{ roleLabel(detailItem.role) }}</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="状态">
+          <el-tag :type="statusTag(detailItem.status)" size="small">{{ detailItem.status }}</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="手机号">{{ detailItem.phone }}</el-descriptions-item>
+        <el-descriptions-item label="邮箱">{{ detailItem.email }}</el-descriptions-item>
+        <el-descriptions-item label="入职日期">{{ detailItem.hireDate }}</el-descriptions-item>
+        <el-descriptions-item label="汇报给">{{ detailItem.supervisor }}</el-descriptions-item>
+        <el-descriptions-item label="备注" :span="2">{{ detailItem.remark || '-' }}</el-descriptions-item>
+      </el-descriptions>
+      <template #footer>
+        <el-button @click="detailVisible = false">关闭</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed, onMounted, ref } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+
+interface EmployeeItem {
+  id: string;
+  employeeNo: string;
+  name: string;
+  avatar: string;
+  department: string;
+  position: string;
+  role: string;
+  phone: string;
+  email: string;
+  status: string;
+  hireDate: string;
+  supervisor: string;
+  supervisorId: string;
+  remark: string;
+}
+
+const items = ref<EmployeeItem[]>([
+  { id: 'E001', employeeNo: 'EMP001', name: '张明', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Zhang', department: '研发部', position: '技术总监', role: 'admin', phone: '138-0000-1001', email: 'zhangming@company.com', status: '在职', hireDate: '2020-03-15', supervisor: '李总', supervisorId: '', remark: '' },
+  { id: 'E002', employeeNo: 'EMP002', name: '李华', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Li', department: '销售部', position: '销售经理', role: 'manager', phone: '139-0000-1002', email: 'lihua@company.com', status: '在职', hireDate: '2021-06-20', supervisor: '王总', supervisorId: '', remark: '' },
+  { id: 'E003', employeeNo: 'EMP003', name: '王芳', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Wang', department: '采购部', position: '采购主管', role: 'procurement', phone: '137-0000-1003', email: 'wangfang@company.com', status: '在职', hireDate: '2022-01-10', supervisor: '张明', supervisorId: 'E001', remark: '' },
+  { id: 'E004', employeeNo: 'EMP004', name: '刘强', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Liu', department: '仓储部', position: '仓库主管', role: 'warehouse', phone: '136-0000-1004', email: 'liuqiang@company.com', status: '在职', hireDate: '2021-09-05', supervisor: '张明', supervisorId: 'E001', remark: '' },
+  { id: 'E005', employeeNo: 'EMP005', name: '陈静', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Chen', department: '财务部', position: '财务经理', role: 'finance', phone: '135-0000-1005', email: 'chenjing@company.com', status: '在职', hireDate: '2020-08-25', supervisor: '李总', supervisorId: '', remark: '' },
+  { id: 'E006', employeeNo: 'EMP006', name: '周伟', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Zhou', department: '销售部', position: '运营专员', role: 'operator', phone: '134-0000-1006', email: 'zhouwei@company.com', status: '在职', hireDate: '2023-03-01', supervisor: '李华', supervisorId: 'E002', remark: '' },
+  { id: 'E007', employeeNo: 'EMP007', name: '吴婷', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Wu', department: '客服部', position: '客服主管', role: 'customer_service', phone: '133-0000-1007', email: 'wuting@company.com', status: '在职', hireDate: '2022-07-15', supervisor: '李华', supervisorId: 'E002', remark: '' },
+  { id: 'E008', employeeNo: 'EMP008', name: '赵磊', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Zhao', department: '研发部', position: '高级工程师', role: 'operator', phone: '132-0000-1008', email: 'zhaolei@company.com', status: '离职', hireDate: '2021-04-20', supervisor: '张明', supervisorId: 'E001', remark: '2024-12-31离职' }
+]);
+
+const employees = ref<{ id: string; name: string }[]>([
+  { id: 'E001', name: '张明' },
+  { id: 'E002', name: '李华' }
+]);
+
+const loading = ref(false);
+const selected = ref<EmployeeItem[]>([]);
+const formVisible = ref(false);
+const detailVisible = ref(false);
+const isEdit = ref(false);
+const detailItem = ref<EmployeeItem | null>(null);
+
+const filters = ref({
+  keyword: '',
+  department: '',
+  position: '',
+  role: '',
+  status: ''
+});
+
+const employeeForm = ref({
+  employeeNo: '',
+  name: '',
+  department: '',
+  position: '',
+  role: '',
+  supervisorId: '',
+  phone: '',
+  email: '',
+  hireDate: '',
+  remark: ''
+});
+
+const filteredItems = computed(() => {
+  return items.value.filter(item => {
+    if (filters.value.keyword && !item.employeeNo.includes(filters.value.keyword) && !item.name.includes(filters.value.keyword)) return false;
+    if (filters.value.department && item.department !== filters.value.department) return false;
+    if (filters.value.position && !item.position.includes(filters.value.position)) return false;
+    if (filters.value.role && item.role !== filters.value.role) return false;
+    if (filters.value.status && item.status !== filters.value.status) return false;
+    return true;
+  });
+});
+
+const roleLabel = (role: string) => {
+  const map: Record<string, string> = { 'admin': '管理员', 'manager': '经理', 'operator': '运营', 'procurement': '采购', 'warehouse': '仓库', 'customer_service': '客服', 'finance': '财务' };
+  return map[role] || role;
+};
+
+const statusTag = (status: string) => {
+  const map: Record<string, string> = { '在职': 'success', '离职': 'info', '待入职': 'warning' };
+  return map[status] || '';
+};
+
+const loadData = () => {
+  loading.value = true;
+  setTimeout(() => { loading.value = false; }, 300);
+};
+
+const resetFilters = () => {
+  filters.value = { keyword: '', department: '', position: '', role: '', status: '' };
+};
+
+const onSelection = (rows: EmployeeItem[]) => { selected.value = rows; };
+
+const openCreate = () => {
+  isEdit.value = false;
+  employeeForm.value = { employeeNo: '', name: '', department: '', position: '', role: '', supervisorId: '', phone: '', email: '', hireDate: '', remark: '' };
+  formVisible.value = true;
+};
+
+const openEdit = (row: EmployeeItem) => {
+  isEdit.value = true;
+  detailItem.value = null;
+  employeeForm.value = {
+    employeeNo: row.employeeNo,
+    name: row.name,
+    department: row.department,
+    position: row.position,
+    role: row.role,
+    supervisorId: row.supervisorId,
+    phone: row.phone,
+    email: row.email,
+    hireDate: row.hireDate,
+    remark: row.remark
+  };
+  formVisible.value = true;
+};
+
+const submitForm = () => {
+  if (!employeeForm.value.name || !employeeForm.value.department) {
+    ElMessage.warning('请填写必填项');
+    return;
+  }
+  ElMessage.success(isEdit.value ? '员工信息已保存' : '员工已创建');
+  formVisible.value = false;
+};
+
+const openDetail = (row: EmployeeItem) => {
+  detailItem.value = row;
+  detailVisible.value = true;
+};
+
+const terminateEmployee = async (row: EmployeeItem) => {
+  await ElMessageBox.confirm(`确认将 ${row.name} 设置为离职状态?`, '员工离职');
+  const idx = items.value.findIndex(e => e.id === row.id);
+  if (idx !== -1) items.value[idx].status = '离职';
+  ElMessage.success('员工已离职');
+};
+
+const doExport = () => {
+  ElMessage.info('导出开始,完成后将自动下载');
+};
+
+onMounted(loadData);
+</script>
+
+<style scoped>
+.filter-form :deep(.el-form-item) { margin-bottom: 0; }
+</style>