|
@@ -0,0 +1,2565 @@
|
|
|
|
+<template>
|
|
|
|
+ <div class="min-h-screen bg-gray-50 dark:bg-gray-900">
|
|
|
|
+ <!-- 顶部导航栏 -->
|
|
|
|
+ <nav class="bg-white dark:bg-gray-800 shadow">
|
|
|
|
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
|
|
+ <div class="flex justify-between h-16">
|
|
|
|
+ <div class="flex">
|
|
|
|
+ <div class="flex-shrink-0 flex items-center">
|
|
|
|
+ <h1 class="text-xl font-bold text-gray-900 dark:text-white">供应商管理</h1>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="flex items-center space-x-4">
|
|
|
|
+ <button
|
|
|
|
+ @click="handleExport"
|
|
|
|
+ class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 flex items-center space-x-2"
|
|
|
|
+ >
|
|
|
|
+ <DownloadIcon class="h-5 w-5" />
|
|
|
|
+ <span>导出供应商</span>
|
|
|
|
+ </button>
|
|
|
|
+ <button
|
|
|
|
+ @click="handleImport"
|
|
|
|
+ class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 flex items-center space-x-2"
|
|
|
|
+ >
|
|
|
|
+ <UploadIcon class="h-5 w-5" />
|
|
|
|
+ <span>导入供应商</span>
|
|
|
|
+ </button>
|
|
|
|
+ <button
|
|
|
|
+ @click="handleAddVendor"
|
|
|
|
+ class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 flex items-center space-x-2"
|
|
|
|
+ >
|
|
|
|
+ <PlusIcon class="h-5 w-5" />
|
|
|
|
+ <span>添加供应商</span>
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </nav>
|
|
|
|
+
|
|
|
|
+ <!-- 主要内容区域 -->
|
|
|
|
+ <main class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
|
|
|
|
+ <!-- 搜索和筛选区域 -->
|
|
|
|
+ <div class="mb-6 bg-white dark:bg-gray-800 shadow rounded-lg p-4">
|
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
|
|
+ <div class="relative">
|
|
|
|
+ <input
|
|
|
|
+ type="text"
|
|
|
|
+ v-model="searchQuery"
|
|
|
|
+ @input="handleSearch"
|
|
|
|
+ class="w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white"
|
|
|
|
+ placeholder="搜索供应商名称/联系人"
|
|
|
|
+ />
|
|
|
|
+ <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
|
|
+ <SearchIcon class="h-5 w-5 text-gray-400" />
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <select
|
|
|
|
+ v-model="searchStatus"
|
|
|
|
+ @change="handleSearch"
|
|
|
|
+ class="border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white"
|
|
|
|
+ >
|
|
|
|
+ <option value="">所有状态</option>
|
|
|
|
+ <option value="active">正常合作</option>
|
|
|
|
+ <option value="inactive">暂停合作</option>
|
|
|
|
+ <option value="blacklisted">黑名单</option>
|
|
|
|
+ </select>
|
|
|
|
+ <select
|
|
|
|
+ v-model="searchLevel"
|
|
|
|
+ @change="handleSearch"
|
|
|
|
+ class="border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white"
|
|
|
|
+ >
|
|
|
|
+ <option value="">所有等级</option>
|
|
|
|
+ <option value="A">A级</option>
|
|
|
|
+ <option value="B">B级</option>
|
|
|
|
+ <option value="C">C级</option>
|
|
|
|
+ </select>
|
|
|
|
+ <button
|
|
|
|
+ @click="showAdvancedFilter = true"
|
|
|
|
+ class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
|
|
|
+ >
|
|
|
|
+ <FilterIcon class="h-4 w-4 mr-2" />
|
|
|
|
+ 高级筛选
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 统计面板 -->
|
|
|
|
+ <div class="mb-6 bg-white dark:bg-gray-800 shadow rounded-lg p-4">
|
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
|
|
+ <!-- 供应商总数 -->
|
|
|
|
+ <div class="bg-blue-50 dark:bg-blue-900 p-4 rounded-lg">
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
+ <div class="p-3 rounded-full bg-blue-100 dark:bg-blue-800">
|
|
|
|
+ <UsersIcon class="h-6 w-6 text-blue-600 dark:text-blue-300" />
|
|
|
|
+ </div>
|
|
|
|
+ <div class="ml-4">
|
|
|
|
+ <p class="text-sm font-medium text-blue-600 dark:text-blue-300">供应商总数</p>
|
|
|
|
+ <p class="text-2xl font-semibold text-blue-900 dark:text-blue-100">{{ vendors.length }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 活跃供应商 -->
|
|
|
|
+ <div class="bg-green-50 dark:bg-green-900 p-4 rounded-lg">
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
+ <div class="p-3 rounded-full bg-green-100 dark:bg-green-800">
|
|
|
|
+ <CheckCircleIcon class="h-6 w-6 text-green-600 dark:text-green-300" />
|
|
|
|
+ </div>
|
|
|
|
+ <div class="ml-4">
|
|
|
|
+ <p class="text-sm font-medium text-green-600 dark:text-green-300">活跃供应商</p>
|
|
|
|
+ <p class="text-2xl font-semibold text-green-900 dark:text-green-100">{{ activeVendorsCount }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 总信用额度 -->
|
|
|
|
+ <div class="bg-purple-50 dark:bg-purple-900 p-4 rounded-lg">
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
+ <div class="p-3 rounded-full bg-purple-100 dark:bg-purple-800">
|
|
|
|
+ <CreditCardIcon class="h-6 w-6 text-purple-600 dark:text-purple-300" />
|
|
|
|
+ </div>
|
|
|
|
+ <div class="ml-4">
|
|
|
|
+ <p class="text-sm font-medium text-purple-600 dark:text-purple-300">总信用额度</p>
|
|
|
|
+ <p class="text-2xl font-semibold text-purple-900 dark:text-purple-100">¥{{ totalCreditLimit }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 已用额度 -->
|
|
|
|
+ <div class="bg-yellow-50 dark:bg-yellow-900 p-4 rounded-lg">
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
+ <div class="p-3 rounded-full bg-yellow-100 dark:bg-yellow-800">
|
|
|
|
+ <AlertCircleIcon class="h-6 w-6 text-yellow-600 dark:text-yellow-300" />
|
|
|
|
+ </div>
|
|
|
|
+ <div class="ml-4">
|
|
|
|
+ <p class="text-sm font-medium text-yellow-600 dark:text-yellow-300">已用额度</p>
|
|
|
|
+ <p class="text-2xl font-semibold text-yellow-900 dark:text-yellow-100">¥{{ totalUsedCredit }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 等级分布图表 -->
|
|
|
|
+ <div class="mt-6">
|
|
|
|
+ <h3 class="text-lg font-medium text-gray-900 dark:text-white mb-4">供应商等级分布</h3>
|
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
|
|
+ <div v-for="(count, level) in levelDistribution" :key="level" class="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
|
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
|
+ <span class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ level }}级供应商</span>
|
|
|
|
+ <span class="text-lg font-semibold text-gray-900 dark:text-white">{{ count }}</span>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="mt-2">
|
|
|
|
+ <div class="w-full bg-gray-200 dark:bg-gray-600 rounded-full h-2">
|
|
|
|
+ <div
|
|
|
|
+ class="h-2 rounded-full"
|
|
|
|
+ :class="{
|
|
|
|
+ 'bg-blue-500': level === 'A',
|
|
|
|
+ 'bg-green-500': level === 'B',
|
|
|
|
+ 'bg-yellow-500': level === 'C'
|
|
|
|
+ }"
|
|
|
|
+ :style="{ width: `${(count / vendors.length) * 100}%` }"
|
|
|
|
+ ></div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 状态分布图表 -->
|
|
|
|
+ <div class="mt-6">
|
|
|
|
+ <h3 class="text-lg font-medium text-gray-900 dark:text-white mb-4">供应商状态分布</h3>
|
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
|
|
+ <div v-for="(count, status) in statusDistribution" :key="status" class="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
|
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
|
+ <span class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ getStatusText(status) }}</span>
|
|
|
|
+ <span class="text-lg font-semibold text-gray-900 dark:text-white">{{ count }}</span>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="mt-2">
|
|
|
|
+ <div class="w-full bg-gray-200 dark:bg-gray-600 rounded-full h-2">
|
|
|
|
+ <div
|
|
|
|
+ class="h-2 rounded-full"
|
|
|
|
+ :class="{
|
|
|
|
+ 'bg-green-500': status === 'active',
|
|
|
|
+ 'bg-yellow-500': status === 'inactive',
|
|
|
|
+ 'bg-red-500': status === 'blacklisted'
|
|
|
|
+ }"
|
|
|
|
+ :style="{ width: `${(count / vendors.length) * 100}%` }"
|
|
|
|
+ ></div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 供应商列表 -->
|
|
|
|
+ <div class="bg-white dark:bg-gray-800 shadow rounded-lg overflow-hidden">
|
|
|
|
+ <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
|
|
+ <thead class="bg-gray-50 dark:bg-gray-700">
|
|
|
|
+ <tr>
|
|
|
|
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
|
|
|
+ 供应商信息
|
|
|
|
+ </th>
|
|
|
|
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
|
|
|
+ 联系方式
|
|
|
|
+ </th>
|
|
|
|
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
|
|
|
+ 合作信息
|
|
|
|
+ </th>
|
|
|
|
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
|
|
|
+ 状态
|
|
|
|
+ </th>
|
|
|
|
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
|
|
|
+ 操作
|
|
|
|
+ </th>
|
|
|
|
+ </tr>
|
|
|
|
+ </thead>
|
|
|
|
+ <tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
|
|
|
+ <tr v-for="vendor in paginatedVendors" :key="vendor.id">
|
|
|
|
+ <td class="px-6 py-4 whitespace-nowrap">
|
|
|
|
+ <div class="text-sm font-medium text-gray-900 dark:text-white">
|
|
|
|
+ {{ vendor.name }}
|
|
|
|
+ </div>
|
|
|
|
+ <div class="text-sm text-gray-500 dark:text-gray-400">
|
|
|
|
+ ID: {{ vendor.id }}
|
|
|
|
+ </div>
|
|
|
|
+ </td>
|
|
|
|
+ <td class="px-6 py-4">
|
|
|
|
+ <div class="text-sm text-gray-900 dark:text-white">
|
|
|
|
+ {{ vendor.contactPerson }}
|
|
|
|
+ </div>
|
|
|
|
+ <div class="text-sm text-gray-500 dark:text-gray-400">
|
|
|
|
+ {{ vendor.phone }}
|
|
|
|
+ </div>
|
|
|
|
+ <div class="text-sm text-gray-500 dark:text-gray-400">
|
|
|
|
+ {{ vendor.email }}
|
|
|
|
+ </div>
|
|
|
|
+ </td>
|
|
|
|
+ <td class="px-6 py-4 whitespace-nowrap">
|
|
|
|
+ <div class="text-sm text-gray-900 dark:text-white">
|
|
|
|
+ 等级: {{ vendor.level }}
|
|
|
|
+ </div>
|
|
|
|
+ <div class="text-sm text-gray-500 dark:text-gray-400">
|
|
|
|
+ 合作时间: {{ formatDate(vendor.cooperationDate) }}
|
|
|
|
+ </div>
|
|
|
|
+ </td>
|
|
|
|
+ <td class="px-6 py-4 whitespace-nowrap">
|
|
|
|
+ <span :class="getStatusClass(vendor.status)" class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full">
|
|
|
|
+ {{ getStatusText(vendor.status) }}
|
|
|
|
+ </span>
|
|
|
|
+ </td>
|
|
|
|
+ <td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
|
|
|
+ <button
|
|
|
|
+ @click="handleViewDetails(vendor)"
|
|
|
|
+ class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300 mr-4"
|
|
|
|
+ >
|
|
|
|
+ 详情
|
|
|
|
+ </button>
|
|
|
|
+ <button
|
|
|
|
+ @click="handleEditVendor(vendor)"
|
|
|
|
+ class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300 mr-4"
|
|
|
|
+ >
|
|
|
|
+ 编辑
|
|
|
|
+ </button>
|
|
|
|
+ <button
|
|
|
|
+ @click="handleDeleteVendor(vendor)"
|
|
|
|
+ class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300"
|
|
|
|
+ >
|
|
|
|
+ 删除
|
|
|
|
+ </button>
|
|
|
|
+ </td>
|
|
|
|
+ </tr>
|
|
|
|
+ </tbody>
|
|
|
|
+ </table>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 分页 -->
|
|
|
|
+ <div class="mt-4 flex justify-between items-center">
|
|
|
|
+ <div class="text-sm text-gray-700 dark:text-gray-300">
|
|
|
|
+ 显示 {{ (pagination.currentPage - 1) * pagination.pageSize + 1 }} 到 {{ Math.min(pagination.currentPage * pagination.pageSize, vendors.length) }} 条,共 {{ vendors.length }} 条
|
|
|
|
+ </div>
|
|
|
|
+ <div class="flex space-x-2">
|
|
|
|
+ <button
|
|
|
|
+ @click="handlePrevPage"
|
|
|
|
+ :disabled="pagination.currentPage === 1"
|
|
|
|
+ class="px-3 py-1 border border-gray-300 dark:border-gray-600 rounded-md text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
|
|
+ >
|
|
|
|
+ 上一页
|
|
|
|
+ </button>
|
|
|
|
+ <button
|
|
|
|
+ @click="handleNextPage"
|
|
|
|
+ :disabled="pagination.currentPage === totalPages"
|
|
|
|
+ class="px-3 py-1 border border-gray-300 dark:border-gray-600 rounded-md text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
|
|
+ >
|
|
|
|
+ 下一页
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </main>
|
|
|
|
+
|
|
|
|
+ <!-- 供应商详情弹窗 -->
|
|
|
|
+ <div v-if="showVendorDetails" class="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center z-50">
|
|
|
|
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-y-auto">
|
|
|
|
+ <div class="p-6">
|
|
|
|
+ <div class="flex justify-between items-center mb-6">
|
|
|
|
+ <h3 class="text-lg font-medium text-gray-900 dark:text-white">供应商详情</h3>
|
|
|
|
+ <button
|
|
|
|
+ @click="showVendorDetails = false"
|
|
|
|
+ class="text-gray-400 hover:text-gray-500 dark:hover:text-gray-300"
|
|
|
|
+ >
|
|
|
|
+ <XIcon class="h-6 w-6" />
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 基本信息 -->
|
|
|
|
+ <div class="mb-6">
|
|
|
|
+ <h4 class="text-sm font-medium text-gray-500 dark:text-gray-400 mb-4">基本信息</h4>
|
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">供应商编号</p>
|
|
|
|
+ <p class="text-sm font-medium text-gray-900 dark:text-white">{{ selectedVendor?.id }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">供应商名称</p>
|
|
|
|
+ <p class="text-sm font-medium text-gray-900 dark:text-white">{{ selectedVendor?.name }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">供应商等级</p>
|
|
|
|
+ <p class="text-sm font-medium text-gray-900 dark:text-white">{{ selectedVendor?.level }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">合作时间</p>
|
|
|
|
+ <p class="text-sm font-medium text-gray-900 dark:text-white">{{ formatDate(selectedVendor?.cooperationDate) }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 联系信息 -->
|
|
|
|
+ <div class="mb-6">
|
|
|
|
+ <h4 class="text-sm font-medium text-gray-500 dark:text-gray-400 mb-4">联系信息</h4>
|
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">联系人</p>
|
|
|
|
+ <p class="text-sm font-medium text-gray-900 dark:text-white">{{ selectedVendor?.contactPerson }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">联系电话</p>
|
|
|
|
+ <p class="text-sm font-medium text-gray-900 dark:text-white">{{ selectedVendor?.phone }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">电子邮箱</p>
|
|
|
|
+ <p class="text-sm font-medium text-gray-900 dark:text-white">{{ selectedVendor?.email }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">传真</p>
|
|
|
|
+ <p class="text-sm font-medium text-gray-900 dark:text-white">{{ selectedVendor?.fax }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="md:col-span-2">
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">地址</p>
|
|
|
|
+ <p class="text-sm font-medium text-gray-900 dark:text-white">{{ selectedVendor?.address }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 合作信息 -->
|
|
|
|
+ <div class="mb-6">
|
|
|
|
+ <h4 class="text-sm font-medium text-gray-500 dark:text-gray-400 mb-4">合作信息</h4>
|
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">结算方式</p>
|
|
|
|
+ <p class="text-sm font-medium text-gray-900 dark:text-white">{{ selectedVendor?.paymentMethod }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">结算周期</p>
|
|
|
|
+ <p class="text-sm font-medium text-gray-900 dark:text-white">{{ selectedVendor?.paymentCycle }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">信用额度</p>
|
|
|
|
+ <p class="text-sm font-medium text-gray-900 dark:text-white">¥{{ selectedVendor?.creditLimit }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">已用额度</p>
|
|
|
|
+ <p class="text-sm font-medium text-gray-900 dark:text-white">¥{{ selectedVendor?.usedCredit }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 备注信息 -->
|
|
|
|
+ <div>
|
|
|
|
+ <h4 class="text-sm font-medium text-gray-500 dark:text-gray-400 mb-4">备注信息</h4>
|
|
|
|
+ <p class="text-sm text-gray-900 dark:text-white">{{ selectedVendor?.remarks || '无' }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 编辑供应商弹窗 -->
|
|
|
|
+ <div v-if="showEditVendor" class="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center z-50">
|
|
|
|
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full">
|
|
|
|
+ <div class="p-6">
|
|
|
|
+ <div class="flex justify-between items-center mb-6">
|
|
|
|
+ <h3 class="text-lg font-medium text-gray-900 dark:text-white">编辑供应商</h3>
|
|
|
|
+ <button
|
|
|
|
+ @click="showEditVendor = false"
|
|
|
|
+ class="text-gray-400 hover:text-gray-500 dark:hover:text-gray-300"
|
|
|
|
+ >
|
|
|
|
+ <XIcon class="h-6 w-6" />
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <form @submit.prevent="handleSubmitEdit" class="space-y-6">
|
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">供应商名称</label>
|
|
|
|
+ <input
|
|
|
|
+ type="text"
|
|
|
|
+ v-model="editVendor.name"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">供应商等级</label>
|
|
|
|
+ <select
|
|
|
|
+ v-model="editVendor.level"
|
|
|
|
+ class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:bg-gray-700 dark:text-white"
|
|
|
|
+ >
|
|
|
|
+ <option value="A">A级</option>
|
|
|
|
+ <option value="B">B级</option>
|
|
|
|
+ <option value="C">C级</option>
|
|
|
|
+ </select>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">联系人</label>
|
|
|
|
+ <input
|
|
|
|
+ type="text"
|
|
|
|
+ v-model="editVendor.contactPerson"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">联系电话</label>
|
|
|
|
+ <input
|
|
|
|
+ type="text"
|
|
|
|
+ v-model="editVendor.phone"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">电子邮箱</label>
|
|
|
|
+ <input
|
|
|
|
+ type="email"
|
|
|
|
+ v-model="editVendor.email"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">传真</label>
|
|
|
|
+ <input
|
|
|
|
+ type="text"
|
|
|
|
+ v-model="editVendor.fax"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div class="md:col-span-2">
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">地址</label>
|
|
|
|
+ <input
|
|
|
|
+ type="text"
|
|
|
|
+ v-model="editVendor.address"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">结算方式</label>
|
|
|
|
+ <select
|
|
|
|
+ v-model="editVendor.paymentMethod"
|
|
|
|
+ class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:bg-gray-700 dark:text-white"
|
|
|
|
+ >
|
|
|
|
+ <option value="cash">现金</option>
|
|
|
|
+ <option value="bank">银行转账</option>
|
|
|
|
+ <option value="check">支票</option>
|
|
|
|
+ </select>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">结算周期</label>
|
|
|
|
+ <select
|
|
|
|
+ v-model="editVendor.paymentCycle"
|
|
|
|
+ class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:bg-gray-700 dark:text-white"
|
|
|
|
+ >
|
|
|
|
+ <option value="weekly">每周</option>
|
|
|
|
+ <option value="monthly">每月</option>
|
|
|
|
+ <option value="quarterly">每季度</option>
|
|
|
|
+ </select>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">信用额度</label>
|
|
|
|
+ <input
|
|
|
|
+ type="number"
|
|
|
|
+ v-model="editVendor.creditLimit"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">状态</label>
|
|
|
|
+ <select
|
|
|
|
+ v-model="editVendor.status"
|
|
|
|
+ class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:bg-gray-700 dark:text-white"
|
|
|
|
+ >
|
|
|
|
+ <option value="active">正常合作</option>
|
|
|
|
+ <option value="inactive">暂停合作</option>
|
|
|
|
+ <option value="blacklisted">黑名单</option>
|
|
|
|
+ </select>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="md:col-span-2">
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">备注</label>
|
|
|
|
+ <textarea
|
|
|
|
+ v-model="editVendor.remarks"
|
|
|
|
+ rows="3"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ ></textarea>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <div class="flex justify-end space-x-3">
|
|
|
|
+ <button
|
|
|
|
+ type="button"
|
|
|
|
+ @click="showEditVendor = false"
|
|
|
|
+ class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
|
|
|
+ >
|
|
|
|
+ 取消
|
|
|
|
+ </button>
|
|
|
|
+ <button
|
|
|
|
+ type="submit"
|
|
|
|
+ class="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
|
|
|
+ >
|
|
|
|
+ 保存
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </form>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 添加供应商弹窗 -->
|
|
|
|
+ <div v-if="showAddVendor" class="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center z-50">
|
|
|
|
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full">
|
|
|
|
+ <div class="p-6">
|
|
|
|
+ <div class="flex justify-between items-center mb-6">
|
|
|
|
+ <h3 class="text-lg font-medium text-gray-900 dark:text-white">添加供应商</h3>
|
|
|
|
+ <button
|
|
|
|
+ @click="showAddVendor = false"
|
|
|
|
+ class="text-gray-400 hover:text-gray-500 dark:hover:text-gray-300"
|
|
|
|
+ >
|
|
|
|
+ <XIcon class="h-6 w-6" />
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <form @submit.prevent="handleSubmitAdd" class="space-y-6">
|
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">供应商名称</label>
|
|
|
|
+ <input
|
|
|
|
+ type="text"
|
|
|
|
+ v-model="newVendor.name"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">供应商等级</label>
|
|
|
|
+ <select
|
|
|
|
+ v-model="newVendor.level"
|
|
|
|
+ class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:bg-gray-700 dark:text-white"
|
|
|
|
+ >
|
|
|
|
+ <option value="A">A级</option>
|
|
|
|
+ <option value="B">B级</option>
|
|
|
|
+ <option value="C">C级</option>
|
|
|
|
+ </select>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">联系人</label>
|
|
|
|
+ <input
|
|
|
|
+ type="text"
|
|
|
|
+ v-model="newVendor.contactPerson"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">联系电话</label>
|
|
|
|
+ <input
|
|
|
|
+ type="text"
|
|
|
|
+ v-model="newVendor.phone"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">电子邮箱</label>
|
|
|
|
+ <input
|
|
|
|
+ type="email"
|
|
|
|
+ v-model="newVendor.email"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">传真</label>
|
|
|
|
+ <input
|
|
|
|
+ type="text"
|
|
|
|
+ v-model="newVendor.fax"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div class="md:col-span-2">
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">地址</label>
|
|
|
|
+ <input
|
|
|
|
+ type="text"
|
|
|
|
+ v-model="newVendor.address"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">结算方式</label>
|
|
|
|
+ <select
|
|
|
|
+ v-model="newVendor.paymentMethod"
|
|
|
|
+ class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:bg-gray-700 dark:text-white"
|
|
|
|
+ >
|
|
|
|
+ <option value="cash">现金</option>
|
|
|
|
+ <option value="bank">银行转账</option>
|
|
|
|
+ <option value="check">支票</option>
|
|
|
|
+ </select>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">结算周期</label>
|
|
|
|
+ <select
|
|
|
|
+ v-model="newVendor.paymentCycle"
|
|
|
|
+ class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:bg-gray-700 dark:text-white"
|
|
|
|
+ >
|
|
|
|
+ <option value="weekly">每周</option>
|
|
|
|
+ <option value="monthly">每月</option>
|
|
|
|
+ <option value="quarterly">每季度</option>
|
|
|
|
+ </select>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">信用额度</label>
|
|
|
|
+ <input
|
|
|
|
+ type="number"
|
|
|
|
+ v-model="newVendor.creditLimit"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">状态</label>
|
|
|
|
+ <select
|
|
|
|
+ v-model="newVendor.status"
|
|
|
|
+ class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:bg-gray-700 dark:text-white"
|
|
|
|
+ >
|
|
|
|
+ <option value="active">正常合作</option>
|
|
|
|
+ <option value="inactive">暂停合作</option>
|
|
|
|
+ <option value="blacklisted">黑名单</option>
|
|
|
|
+ </select>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="md:col-span-2">
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">备注</label>
|
|
|
|
+ <textarea
|
|
|
|
+ v-model="newVendor.remarks"
|
|
|
|
+ rows="3"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ ></textarea>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <div class="flex justify-end space-x-3">
|
|
|
|
+ <button
|
|
|
|
+ type="button"
|
|
|
|
+ @click="showAddVendor = false"
|
|
|
|
+ class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
|
|
|
+ >
|
|
|
|
+ 取消
|
|
|
|
+ </button>
|
|
|
|
+ <button
|
|
|
|
+ type="submit"
|
|
|
|
+ class="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
|
|
|
+ >
|
|
|
|
+ 添加
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </form>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 删除确认弹窗 -->
|
|
|
|
+ <div v-if="showDeleteConfirm" class="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center z-50">
|
|
|
|
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-md w-full">
|
|
|
|
+ <div class="p-6">
|
|
|
|
+ <div class="flex justify-between items-center mb-6">
|
|
|
|
+ <h3 class="text-lg font-medium text-gray-900 dark:text-white">确认删除</h3>
|
|
|
|
+ <button
|
|
|
|
+ @click="showDeleteConfirm = false"
|
|
|
|
+ class="text-gray-400 hover:text-gray-500 dark:hover:text-gray-300"
|
|
|
|
+ >
|
|
|
|
+ <XIcon class="h-6 w-6" />
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400 mb-6">
|
|
|
|
+ 确定要删除供应商 {{ selectedVendor?.name }} 吗?此操作不可撤销。
|
|
|
|
+ </p>
|
|
|
|
+
|
|
|
|
+ <div class="flex justify-end space-x-3">
|
|
|
|
+ <button
|
|
|
|
+ @click="showDeleteConfirm = false"
|
|
|
|
+ class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
|
|
|
+ >
|
|
|
|
+ 取消
|
|
|
|
+ </button>
|
|
|
|
+ <button
|
|
|
|
+ @click="confirmDelete"
|
|
|
|
+ class="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
|
|
|
|
+ >
|
|
|
|
+ 删除
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 高级筛选弹窗 -->
|
|
|
|
+ <div v-if="showAdvancedFilter" class="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center z-50">
|
|
|
|
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full">
|
|
|
|
+ <div class="p-6">
|
|
|
|
+ <div class="flex justify-between items-center mb-6">
|
|
|
|
+ <h3 class="text-lg font-medium text-gray-900 dark:text-white">高级筛选</h3>
|
|
|
|
+ <button
|
|
|
|
+ @click="showAdvancedFilter = false"
|
|
|
|
+ class="text-gray-400 hover:text-gray-500 dark:hover:text-gray-300"
|
|
|
|
+ >
|
|
|
|
+ <XIcon class="h-6 w-6" />
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <form @submit.prevent="handleFilter" class="space-y-6">
|
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">供应商编号</label>
|
|
|
|
+ <input
|
|
|
|
+ type="text"
|
|
|
|
+ v-model="filters.id"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ placeholder="输入供应商编号"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">供应商名称</label>
|
|
|
|
+ <input
|
|
|
|
+ type="text"
|
|
|
|
+ v-model="filters.name"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ placeholder="输入供应商名称"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">供应商等级</label>
|
|
|
|
+ <select
|
|
|
|
+ v-model="filters.level"
|
|
|
|
+ class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:bg-gray-700 dark:text-white"
|
|
|
|
+ >
|
|
|
|
+ <option value="">所有等级</option>
|
|
|
|
+ <option value="A">A级</option>
|
|
|
|
+ <option value="B">B级</option>
|
|
|
|
+ <option value="C">C级</option>
|
|
|
|
+ </select>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">状态</label>
|
|
|
|
+ <select
|
|
|
|
+ v-model="filters.status"
|
|
|
|
+ class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:bg-gray-700 dark:text-white"
|
|
|
|
+ >
|
|
|
|
+ <option value="">所有状态</option>
|
|
|
|
+ <option value="active">正常合作</option>
|
|
|
|
+ <option value="inactive">暂停合作</option>
|
|
|
|
+ <option value="blacklisted">黑名单</option>
|
|
|
|
+ </select>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">结算方式</label>
|
|
|
|
+ <select
|
|
|
|
+ v-model="filters.paymentMethod"
|
|
|
|
+ class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:bg-gray-700 dark:text-white"
|
|
|
|
+ >
|
|
|
|
+ <option value="">所有结算方式</option>
|
|
|
|
+ <option value="cash">现金</option>
|
|
|
|
+ <option value="bank">银行转账</option>
|
|
|
|
+ <option value="check">支票</option>
|
|
|
|
+ </select>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">结算周期</label>
|
|
|
|
+ <select
|
|
|
|
+ v-model="filters.paymentCycle"
|
|
|
|
+ class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:bg-gray-700 dark:text-white"
|
|
|
|
+ >
|
|
|
|
+ <option value="">所有结算周期</option>
|
|
|
|
+ <option value="weekly">每周</option>
|
|
|
|
+ <option value="monthly">每月</option>
|
|
|
|
+ <option value="quarterly">每季度</option>
|
|
|
|
+ </select>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">最小信用额度</label>
|
|
|
|
+ <input
|
|
|
|
+ type="number"
|
|
|
|
+ v-model="filters.minCreditLimit"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ placeholder="输入最小信用额度"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">最大信用额度</label>
|
|
|
|
+ <input
|
|
|
|
+ type="number"
|
|
|
|
+ v-model="filters.maxCreditLimit"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ placeholder="输入最大信用额度"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">开始日期</label>
|
|
|
|
+ <input
|
|
|
|
+ type="date"
|
|
|
|
+ v-model="filters.startDate"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">结束日期</label>
|
|
|
|
+ <input
|
|
|
|
+ type="date"
|
|
|
|
+ v-model="filters.endDate"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <div class="flex justify-end space-x-3">
|
|
|
|
+ <button
|
|
|
|
+ type="button"
|
|
|
|
+ @click="resetFilters"
|
|
|
|
+ class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
|
|
|
+ >
|
|
|
|
+ 重置
|
|
|
|
+ </button>
|
|
|
|
+ <button
|
|
|
|
+ type="submit"
|
|
|
|
+ class="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
|
|
|
+ >
|
|
|
|
+ 筛选
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </form>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 错误提示组件 -->
|
|
|
|
+ <div v-if="showError" class="fixed top-4 right-4 z-50">
|
|
|
|
+ <div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
|
|
|
|
+ <strong class="font-bold">错误!</strong>
|
|
|
|
+ <span class="block sm:inline">{{ errorMessage }}</span>
|
|
|
|
+ <span class="absolute top-0 bottom-0 right-0 px-4 py-3" @click="showError = false">
|
|
|
|
+ <XIcon class="h-5 w-5 text-red-500" />
|
|
|
|
+ </span>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 成功提示组件 -->
|
|
|
|
+ <div v-if="showSuccess" class="fixed top-4 right-4 z-50">
|
|
|
|
+ <div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative" role="alert">
|
|
|
|
+ <strong class="font-bold">成功!</strong>
|
|
|
|
+ <span class="block sm:inline">{{ successMessage }}</span>
|
|
|
|
+ <span class="absolute top-0 bottom-0 right-0 px-4 py-3" @click="showSuccess = false">
|
|
|
|
+ <XIcon class="h-5 w-5 text-green-500" />
|
|
|
|
+ </span>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 供应商评价弹窗 -->
|
|
|
|
+ <div v-if="showVendorRatings" class="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center z-50">
|
|
|
|
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-y-auto">
|
|
|
|
+ <div class="p-6">
|
|
|
|
+ <div class="flex justify-between items-center mb-6">
|
|
|
|
+ <h3 class="text-lg font-medium text-gray-900 dark:text-white">供应商评价</h3>
|
|
|
|
+ <button
|
|
|
|
+ @click="showVendorRatings = false"
|
|
|
|
+ class="text-gray-400 hover:text-gray-500 dark:hover:text-gray-300"
|
|
|
|
+ >
|
|
|
|
+ <XIcon class="h-6 w-6" />
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 评价统计 -->
|
|
|
|
+ <div class="mb-6 bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
|
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">平均评分</p>
|
|
|
|
+ <p class="text-2xl font-semibold text-gray-900 dark:text-white">{{ averageRating }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">评价总数</p>
|
|
|
|
+ <p class="text-2xl font-semibold text-gray-900 dark:text-white">{{ selectedVendor?.ratings?.length || 0 }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">好评率</p>
|
|
|
|
+ <p class="text-2xl font-semibold text-gray-900 dark:text-white">{{ positiveRatingPercentage }}%</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">最近评价</p>
|
|
|
|
+ <p class="text-2xl font-semibold text-gray-900 dark:text-white">{{ formatDate(selectedVendor?.ratings?.[0]?.date) }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 评分分布 -->
|
|
|
|
+ <div class="mb-6">
|
|
|
|
+ <h4 class="text-sm font-medium text-gray-500 dark:text-gray-400 mb-4">评分分布</h4>
|
|
|
|
+ <div class="space-y-2">
|
|
|
|
+ <div v-for="(count, rating) in ratingDistribution" :key="rating" class="flex items-center">
|
|
|
|
+ <span class="w-8 text-sm text-gray-500 dark:text-gray-400">{{ rating }}星</span>
|
|
|
|
+ <div class="flex-1 h-2 bg-gray-200 dark:bg-gray-600 rounded-full mx-2">
|
|
|
|
+ <div
|
|
|
|
+ class="h-2 rounded-full bg-yellow-400"
|
|
|
|
+ :style="{ width: `${(count / (selectedVendor?.ratings?.length || 1)) * 100}%` }"
|
|
|
|
+ ></div>
|
|
|
|
+ </div>
|
|
|
|
+ <span class="w-8 text-sm text-gray-500 dark:text-gray-400">{{ count }}</span>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 评价列表 -->
|
|
|
|
+ <div class="mb-6">
|
|
|
|
+ <h4 class="text-sm font-medium text-gray-500 dark:text-gray-400 mb-4">评价列表</h4>
|
|
|
|
+ <div class="space-y-4">
|
|
|
|
+ <div v-for="rating in selectedVendor?.ratings" :key="rating.id" class="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
|
|
|
|
+ <div class="flex justify-between items-start mb-2">
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
+ <div class="flex">
|
|
|
|
+ <StarIcon
|
|
|
|
+ v-for="i in 5"
|
|
|
|
+ :key="i"
|
|
|
|
+ class="h-5 w-5"
|
|
|
|
+ :class="i <= rating.score ? 'text-yellow-400' : 'text-gray-300 dark:text-gray-500'"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <span class="ml-2 text-sm text-gray-500 dark:text-gray-400">{{ formatDate(rating.date) }}</span>
|
|
|
|
+ </div>
|
|
|
|
+ <button
|
|
|
|
+ v-if="rating.userId === 'current_user'"
|
|
|
|
+ @click="handleDeleteRating(rating)"
|
|
|
|
+ class="text-red-500 hover:text-red-700 dark:hover:text-red-400"
|
|
|
|
+ >
|
|
|
|
+ <Trash2Icon class="h-4 w-4" />
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ <p class="text-sm text-gray-900 dark:text-white">{{ rating.comment }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 添加评价 -->
|
|
|
|
+ <div>
|
|
|
|
+ <h4 class="text-sm font-medium text-gray-500 dark:text-gray-400 mb-4">添加评价</h4>
|
|
|
|
+ <form @submit.prevent="handleSubmitRating" class="space-y-4">
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">评分</label>
|
|
|
|
+ <div class="flex">
|
|
|
|
+ <button
|
|
|
|
+ v-for="i in 5"
|
|
|
|
+ :key="i"
|
|
|
|
+ type="button"
|
|
|
|
+ @click="newRating.score = i"
|
|
|
|
+ class="focus:outline-none"
|
|
|
|
+ >
|
|
|
|
+ <StarIcon
|
|
|
|
+ class="h-8 w-8"
|
|
|
|
+ :class="i <= newRating.score ? 'text-yellow-400' : 'text-gray-300 dark:text-gray-500'"
|
|
|
|
+ />
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">评价内容</label>
|
|
|
|
+ <textarea
|
|
|
|
+ v-model="newRating.comment"
|
|
|
|
+ rows="3"
|
|
|
|
+ class="w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ placeholder="请输入评价内容"
|
|
|
|
+ ></textarea>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="flex justify-end">
|
|
|
|
+ <button
|
|
|
|
+ type="submit"
|
|
|
|
+ class="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
|
|
|
+ >
|
|
|
|
+ 提交评价
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </form>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 供应商历史记录弹窗 -->
|
|
|
|
+ <div v-if="showVendorHistory" class="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center z-50">
|
|
|
|
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-y-auto">
|
|
|
|
+ <div class="p-6">
|
|
|
|
+ <div class="flex justify-between items-center mb-6">
|
|
|
|
+ <h3 class="text-lg font-medium text-gray-900 dark:text-white">供应商历史记录</h3>
|
|
|
|
+ <button
|
|
|
|
+ @click="showVendorHistory = false"
|
|
|
|
+ class="text-gray-400 hover:text-gray-500 dark:hover:text-gray-300"
|
|
|
|
+ >
|
|
|
|
+ <XIcon class="h-6 w-6" />
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 历史记录统计 -->
|
|
|
|
+ <div class="mb-6 bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
|
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">总记录数</p>
|
|
|
|
+ <p class="text-2xl font-semibold text-gray-900 dark:text-white">{{ selectedVendor?.history?.length || 0 }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">最近更新</p>
|
|
|
|
+ <p class="text-2xl font-semibold text-gray-900 dark:text-white">{{ formatDate(selectedVendor?.history?.[0]?.date) }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">信息变更</p>
|
|
|
|
+ <p class="text-2xl font-semibold text-gray-900 dark:text-white">{{ infoChangesCount }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">状态变更</p>
|
|
|
|
+ <p class="text-2xl font-semibold text-gray-900 dark:text-white">{{ statusChangesCount }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 历史记录列表 -->
|
|
|
|
+ <div class="space-y-4">
|
|
|
|
+ <div v-for="record in selectedVendor?.history" :key="record.id" class="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
|
|
|
|
+ <div class="flex justify-between items-start mb-2">
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
+ <div class="p-2 rounded-full" :class="getHistoryTypeClass(record.type)">
|
|
|
|
+ <component
|
|
|
|
+ :is="getHistoryTypeIcon(record.type)"
|
|
|
|
+ class="h-5 w-5"
|
|
|
|
+ :class="getHistoryTypeIconClass(record.type)"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div class="ml-3">
|
|
|
|
+ <p class="text-sm font-medium text-gray-900 dark:text-white">{{ getHistoryTypeText(record.type) }}</p>
|
|
|
|
+ <p class="text-xs text-gray-500 dark:text-gray-400">{{ formatDate(record.date) }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <span class="text-xs text-gray-500 dark:text-gray-400">{{ record.user }}</span>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="mt-2">
|
|
|
|
+ <p class="text-sm text-gray-900 dark:text-white">{{ record.description }}</p>
|
|
|
|
+ <div v-if="record.changes" class="mt-2 space-y-1">
|
|
|
|
+ <div v-for="(value, key) in record.changes" :key="key" class="text-xs text-gray-500 dark:text-gray-400">
|
|
|
|
+ <span class="font-medium">{{ getFieldName(key) }}:</span>
|
|
|
|
+ <span class="ml-1">{{ formatChangeValue(value) }}</span>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 供应商合同弹窗 -->
|
|
|
|
+ <div v-if="showVendorContracts" class="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center z-50">
|
|
|
|
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-y-auto">
|
|
|
|
+ <div class="p-6">
|
|
|
|
+ <div class="flex justify-between items-center mb-6">
|
|
|
|
+ <h3 class="text-lg font-medium text-gray-900 dark:text-white">供应商合同管理</h3>
|
|
|
|
+ <button
|
|
|
|
+ @click="showVendorContracts = false"
|
|
|
|
+ class="text-gray-400 hover:text-gray-500 dark:hover:text-gray-300"
|
|
|
|
+ >
|
|
|
|
+ <XIcon class="h-6 w-6" />
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 合同统计 -->
|
|
|
|
+ <div class="mb-6 bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
|
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">合同总数</p>
|
|
|
|
+ <p class="text-2xl font-semibold text-gray-900 dark:text-white">{{ selectedVendor?.contracts?.length || 0 }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">有效合同</p>
|
|
|
|
+ <p class="text-2xl font-semibold text-gray-900 dark:text-white">{{ validContractsCount }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">即将到期</p>
|
|
|
|
+ <p class="text-2xl font-semibold text-gray-900 dark:text-white">{{ expiringContractsCount }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">已过期</p>
|
|
|
|
+ <p class="text-2xl font-semibold text-gray-900 dark:text-white">{{ expiredContractsCount }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 合同列表 -->
|
|
|
|
+ <div class="mb-6">
|
|
|
|
+ <div class="flex justify-between items-center mb-4">
|
|
|
|
+ <h4 class="text-sm font-medium text-gray-500 dark:text-gray-400">合同列表</h4>
|
|
|
|
+ <button
|
|
|
|
+ @click="handleAddContract"
|
|
|
|
+ class="px-3 py-1 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 flex items-center space-x-2"
|
|
|
|
+ >
|
|
|
|
+ <PlusIcon class="h-4 w-4" />
|
|
|
|
+ <span>添加合同</span>
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="space-y-4">
|
|
|
|
+ <div v-for="contract in selectedVendor?.contracts" :key="contract.id" class="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
|
|
|
|
+ <div class="flex justify-between items-start mb-2">
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm font-medium text-gray-900 dark:text-white">{{ contract.name }}</p>
|
|
|
|
+ <p class="text-xs text-gray-500 dark:text-gray-400">合同编号: {{ contract.id }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="flex space-x-2">
|
|
|
|
+ <button
|
|
|
|
+ @click="handleViewContract(contract)"
|
|
|
|
+ class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300"
|
|
|
|
+ >
|
|
|
|
+ 查看
|
|
|
|
+ </button>
|
|
|
|
+ <button
|
|
|
|
+ @click="handleEditContract(contract)"
|
|
|
|
+ class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300"
|
|
|
|
+ >
|
|
|
|
+ 编辑
|
|
|
|
+ </button>
|
|
|
|
+ <button
|
|
|
|
+ @click="handleDeleteContract(contract)"
|
|
|
|
+ class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300"
|
|
|
|
+ >
|
|
|
|
+ 删除
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mt-2">
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-xs text-gray-500 dark:text-gray-400">签订日期</p>
|
|
|
|
+ <p class="text-sm text-gray-900 dark:text-white">{{ formatDate(contract.signDate) }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-xs text-gray-500 dark:text-gray-400">生效日期</p>
|
|
|
|
+ <p class="text-sm text-gray-900 dark:text-white">{{ formatDate(contract.startDate) }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-xs text-gray-500 dark:text-gray-400">到期日期</p>
|
|
|
|
+ <p class="text-sm text-gray-900 dark:text-white">{{ formatDate(contract.endDate) }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="mt-2">
|
|
|
|
+ <span :class="getContractStatusClass(contract.status)" class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full">
|
|
|
|
+ {{ getContractStatusText(contract.status) }}
|
|
|
|
+ </span>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 合同详情弹窗 -->
|
|
|
|
+ <div v-if="showContractDetails" class="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center z-50">
|
|
|
|
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-y-auto">
|
|
|
|
+ <div class="p-6">
|
|
|
|
+ <div class="flex justify-between items-center mb-6">
|
|
|
|
+ <h3 class="text-lg font-medium text-gray-900 dark:text-white">合同详情</h3>
|
|
|
|
+ <button
|
|
|
|
+ @click="showContractDetails = false"
|
|
|
|
+ class="text-gray-400 hover:text-gray-500 dark:hover:text-gray-300"
|
|
|
|
+ >
|
|
|
|
+ <XIcon class="h-6 w-6" />
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 基本信息 -->
|
|
|
|
+ <div class="mb-6">
|
|
|
|
+ <h4 class="text-sm font-medium text-gray-500 dark:text-gray-400 mb-4">基本信息</h4>
|
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">合同编号</p>
|
|
|
|
+ <p class="text-sm font-medium text-gray-900 dark:text-white">{{ selectedContract?.id }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">合同名称</p>
|
|
|
|
+ <p class="text-sm font-medium text-gray-900 dark:text-white">{{ selectedContract?.name }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">合同类型</p>
|
|
|
|
+ <p class="text-sm font-medium text-gray-900 dark:text-white">{{ getContractTypeText(selectedContract?.type) }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">合同状态</p>
|
|
|
|
+ <p class="text-sm font-medium text-gray-900 dark:text-white">{{ getContractStatusText(selectedContract?.status) }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">签订日期</p>
|
|
|
|
+ <p class="text-sm font-medium text-gray-900 dark:text-white">{{ formatDate(selectedContract?.signDate) }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">生效日期</p>
|
|
|
|
+ <p class="text-sm font-medium text-gray-900 dark:text-white">{{ formatDate(selectedContract?.startDate) }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">到期日期</p>
|
|
|
|
+ <p class="text-sm font-medium text-gray-900 dark:text-white">{{ formatDate(selectedContract?.endDate) }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400">合同金额</p>
|
|
|
|
+ <p class="text-sm font-medium text-gray-900 dark:text-white">¥{{ selectedContract?.amount }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 合同条款 -->
|
|
|
|
+ <div class="mb-6">
|
|
|
|
+ <h4 class="text-sm font-medium text-gray-500 dark:text-gray-400 mb-4">合同条款</h4>
|
|
|
|
+ <div class="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
|
|
|
|
+ <p class="text-sm text-gray-900 dark:text-white whitespace-pre-line">{{ selectedContract?.terms }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 附件 -->
|
|
|
|
+ <div>
|
|
|
|
+ <h4 class="text-sm font-medium text-gray-500 dark:text-gray-400 mb-4">附件</h4>
|
|
|
|
+ <div class="space-y-2">
|
|
|
|
+ <div v-for="attachment in selectedContract?.attachments" :key="attachment.id" class="flex items-center justify-between bg-gray-50 dark:bg-gray-700 p-3 rounded-lg">
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
+ <FileIcon class="h-5 w-5 text-gray-400 mr-2" />
|
|
|
|
+ <span class="text-sm text-gray-900 dark:text-white">{{ attachment.name }}</span>
|
|
|
|
+ </div>
|
|
|
|
+ <button
|
|
|
|
+ @click="handleDownloadAttachment(attachment)"
|
|
|
|
+ class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300"
|
|
|
|
+ >
|
|
|
|
+ 下载
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 编辑合同弹窗 -->
|
|
|
|
+ <div v-if="showEditContract" class="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center z-50">
|
|
|
|
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full">
|
|
|
|
+ <div class="p-6">
|
|
|
|
+ <div class="flex justify-between items-center mb-6">
|
|
|
|
+ <h3 class="text-lg font-medium text-gray-900 dark:text-white">编辑合同</h3>
|
|
|
|
+ <button
|
|
|
|
+ @click="showEditContract = false"
|
|
|
|
+ class="text-gray-400 hover:text-gray-500 dark:hover:text-gray-300"
|
|
|
|
+ >
|
|
|
|
+ <XIcon class="h-6 w-6" />
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <form @submit.prevent="handleSubmitEditContract" class="space-y-6">
|
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">合同名称</label>
|
|
|
|
+ <input
|
|
|
|
+ type="text"
|
|
|
|
+ v-model="editContract.name"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">合同类型</label>
|
|
|
|
+ <select
|
|
|
|
+ v-model="editContract.type"
|
|
|
|
+ class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:bg-gray-700 dark:text-white"
|
|
|
|
+ >
|
|
|
|
+ <option value="purchase">采购合同</option>
|
|
|
|
+ <option value="service">服务合同</option>
|
|
|
|
+ <option value="framework">框架协议</option>
|
|
|
|
+ </select>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">签订日期</label>
|
|
|
|
+ <input
|
|
|
|
+ type="date"
|
|
|
|
+ v-model="editContract.signDate"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">生效日期</label>
|
|
|
|
+ <input
|
|
|
|
+ type="date"
|
|
|
|
+ v-model="editContract.startDate"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">到期日期</label>
|
|
|
|
+ <input
|
|
|
|
+ type="date"
|
|
|
|
+ v-model="editContract.endDate"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">合同金额</label>
|
|
|
|
+ <input
|
|
|
|
+ type="number"
|
|
|
|
+ v-model="editContract.amount"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div class="md:col-span-2">
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">合同条款</label>
|
|
|
|
+ <textarea
|
|
|
|
+ v-model="editContract.terms"
|
|
|
|
+ rows="5"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ ></textarea>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <div class="flex justify-end space-x-3">
|
|
|
|
+ <button
|
|
|
|
+ type="button"
|
|
|
|
+ @click="showEditContract = false"
|
|
|
|
+ class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
|
|
|
+ >
|
|
|
|
+ 取消
|
|
|
|
+ </button>
|
|
|
|
+ <button
|
|
|
|
+ type="submit"
|
|
|
|
+ class="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
|
|
|
+ >
|
|
|
|
+ 保存
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </form>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 添加合同弹窗 -->
|
|
|
|
+ <div v-if="showAddContract" class="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center z-50">
|
|
|
|
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full">
|
|
|
|
+ <div class="p-6">
|
|
|
|
+ <div class="flex justify-between items-center mb-6">
|
|
|
|
+ <h3 class="text-lg font-medium text-gray-900 dark:text-white">添加合同</h3>
|
|
|
|
+ <button
|
|
|
|
+ @click="showAddContract = false"
|
|
|
|
+ class="text-gray-400 hover:text-gray-500 dark:hover:text-gray-300"
|
|
|
|
+ >
|
|
|
|
+ <XIcon class="h-6 w-6" />
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <form @submit.prevent="handleSubmitAddContract" class="space-y-6">
|
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">合同名称</label>
|
|
|
|
+ <input
|
|
|
|
+ type="text"
|
|
|
|
+ v-model="newContract.name"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">合同类型</label>
|
|
|
|
+ <select
|
|
|
|
+ v-model="newContract.type"
|
|
|
|
+ class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:bg-gray-700 dark:text-white"
|
|
|
|
+ >
|
|
|
|
+ <option value="purchase">采购合同</option>
|
|
|
|
+ <option value="service">服务合同</option>
|
|
|
|
+ <option value="framework">框架协议</option>
|
|
|
|
+ </select>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">签订日期</label>
|
|
|
|
+ <input
|
|
|
|
+ type="date"
|
|
|
|
+ v-model="newContract.signDate"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">生效日期</label>
|
|
|
|
+ <input
|
|
|
|
+ type="date"
|
|
|
|
+ v-model="newContract.startDate"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">到期日期</label>
|
|
|
|
+ <input
|
|
|
|
+ type="date"
|
|
|
|
+ v-model="newContract.endDate"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">合同金额</label>
|
|
|
|
+ <input
|
|
|
|
+ type="number"
|
|
|
|
+ v-model="newContract.amount"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div class="md:col-span-2">
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">合同条款</label>
|
|
|
|
+ <textarea
|
|
|
|
+ v-model="newContract.terms"
|
|
|
|
+ rows="5"
|
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-white"
|
|
|
|
+ ></textarea>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="md:col-span-2">
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">上传附件</label>
|
|
|
|
+ <div class="mt-1 flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 dark:border-gray-600 border-dashed rounded-md">
|
|
|
|
+ <div class="space-y-1 text-center">
|
|
|
|
+ <UploadIcon class="mx-auto h-12 w-12 text-gray-400" />
|
|
|
|
+ <div class="flex text-sm text-gray-600 dark:text-gray-400">
|
|
|
|
+ <label class="relative cursor-pointer bg-white dark:bg-gray-800 rounded-md font-medium text-indigo-600 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500">
|
|
|
|
+ <span>上传文件</span>
|
|
|
|
+ <input
|
|
|
|
+ type="file"
|
|
|
|
+ class="sr-only"
|
|
|
|
+ @change="handleFileUpload"
|
|
|
|
+ multiple
|
|
|
|
+ />
|
|
|
|
+ </label>
|
|
|
|
+ <p class="pl-1">或拖拽文件到此处</p>
|
|
|
|
+ </div>
|
|
|
|
+ <p class="text-xs text-gray-500 dark:text-gray-400">
|
|
|
|
+ PDF, DOC, DOCX, XLS, XLSX 格式,最大 10MB
|
|
|
|
+ </p>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <div class="flex justify-end space-x-3">
|
|
|
|
+ <button
|
|
|
|
+ type="button"
|
|
|
|
+ @click="showAddContract = false"
|
|
|
|
+ class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
|
|
|
+ >
|
|
|
|
+ 取消
|
|
|
|
+ </button>
|
|
|
|
+ <button
|
|
|
|
+ type="submit"
|
|
|
|
+ class="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
|
|
|
+ >
|
|
|
|
+ 添加
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </form>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 删除合同确认弹窗 -->
|
|
|
|
+ <div v-if="showDeleteContractConfirm" class="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center z-50">
|
|
|
|
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-md w-full">
|
|
|
|
+ <div class="p-6">
|
|
|
|
+ <div class="flex justify-between items-center mb-6">
|
|
|
|
+ <h3 class="text-lg font-medium text-gray-900 dark:text-white">确认删除</h3>
|
|
|
|
+ <button
|
|
|
|
+ @click="showDeleteContractConfirm = false"
|
|
|
|
+ class="text-gray-400 hover:text-gray-500 dark:hover:text-gray-300"
|
|
|
|
+ >
|
|
|
|
+ <XIcon class="h-6 w-6" />
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <p class="text-sm text-gray-500 dark:text-gray-400 mb-6">
|
|
|
|
+ 确定要删除合同 {{ selectedContract?.name }} 吗?此操作不可撤销。
|
|
|
|
+ </p>
|
|
|
|
+
|
|
|
|
+ <div class="flex justify-end space-x-3">
|
|
|
|
+ <button
|
|
|
|
+ @click="showDeleteContractConfirm = false"
|
|
|
|
+ class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
|
|
|
+ >
|
|
|
|
+ 取消
|
|
|
|
+ </button>
|
|
|
|
+ <button
|
|
|
|
+ @click="confirmDeleteContract"
|
|
|
|
+ class="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
|
|
|
|
+ >
|
|
|
|
+ 删除
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+</template>
|
|
|
|
+
|
|
|
|
+<script setup>
|
|
|
|
+import { ref, computed, onMounted, watch } from 'vue'
|
|
|
|
+import {
|
|
|
|
+ SearchIcon,
|
|
|
|
+ PlusIcon,
|
|
|
|
+ FilterIcon,
|
|
|
|
+ DownloadIcon,
|
|
|
|
+ UploadIcon,
|
|
|
|
+ XIcon,
|
|
|
|
+ UsersIcon,
|
|
|
|
+ CheckCircleIcon,
|
|
|
|
+ CreditCardIcon,
|
|
|
|
+ AlertCircleIcon,
|
|
|
|
+ StarIcon,
|
|
|
|
+ Trash2Icon,
|
|
|
|
+ EditIcon,
|
|
|
|
+ AlertTriangleIcon,
|
|
|
|
+ ClockIcon,
|
|
|
|
+ UserIcon,
|
|
|
|
+ FileIcon
|
|
|
|
+} from 'lucide-vue-next'
|
|
|
|
+
|
|
|
|
+// 供应商数据
|
|
|
|
+const vendors = ref([
|
|
|
|
+ {
|
|
|
|
+ id: 'V202401010001',
|
|
|
|
+ name: '供应商A',
|
|
|
|
+ level: 'A',
|
|
|
|
+ contactPerson: '张三',
|
|
|
|
+ phone: '13800138000',
|
|
|
|
+ email: 'zhangsan@example.com',
|
|
|
|
+ fax: '010-12345678',
|
|
|
|
+ address: '北京市朝阳区xxx街道xxx号',
|
|
|
|
+ paymentMethod: 'bank',
|
|
|
|
+ paymentCycle: 'monthly',
|
|
|
|
+ creditLimit: 100000,
|
|
|
|
+ usedCredit: 50000,
|
|
|
|
+ status: 'active',
|
|
|
|
+ cooperationDate: '2024-01-01',
|
|
|
|
+ remarks: '长期合作供应商'
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ id: 'V202401010002',
|
|
|
|
+ name: '供应商B',
|
|
|
|
+ level: 'B',
|
|
|
|
+ contactPerson: '李四',
|
|
|
|
+ phone: '13900139000',
|
|
|
|
+ email: 'lisi@example.com',
|
|
|
|
+ fax: '010-87654321',
|
|
|
|
+ address: '上海市浦东新区xxx街道xxx号',
|
|
|
|
+ paymentMethod: 'cash',
|
|
|
|
+ paymentCycle: 'weekly',
|
|
|
|
+ creditLimit: 50000,
|
|
|
|
+ usedCredit: 20000,
|
|
|
|
+ status: 'active',
|
|
|
|
+ cooperationDate: '2024-01-02',
|
|
|
|
+ remarks: '新合作供应商'
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ id: 'V202401010003',
|
|
|
|
+ name: '供应商C',
|
|
|
|
+ level: 'C',
|
|
|
|
+ contactPerson: '王五',
|
|
|
|
+ phone: '13700137000',
|
|
|
|
+ email: 'wangwu@example.com',
|
|
|
|
+ fax: '010-98765432',
|
|
|
|
+ address: '广州市天河区xxx街道xxx号',
|
|
|
|
+ paymentMethod: 'check',
|
|
|
|
+ paymentCycle: 'quarterly',
|
|
|
|
+ creditLimit: 20000,
|
|
|
|
+ usedCredit: 10000,
|
|
|
|
+ status: 'inactive',
|
|
|
|
+ cooperationDate: '2024-01-03',
|
|
|
|
+ remarks: '暂停合作'
|
|
|
|
+ }
|
|
|
|
+])
|
|
|
|
+
|
|
|
|
+// 获取状态样式
|
|
|
|
+const getStatusClass = (status) => {
|
|
|
|
+ const classes = {
|
|
|
|
+ active: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
|
|
|
|
+ inactive: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200',
|
|
|
|
+ blacklisted: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'
|
|
|
|
+ }
|
|
|
|
+ return classes[status] || 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200'
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 获取状态文本
|
|
|
|
+const getStatusText = (status) => {
|
|
|
|
+ const texts = {
|
|
|
|
+ active: '正常合作',
|
|
|
|
+ inactive: '暂停合作',
|
|
|
|
+ blacklisted: '黑名单'
|
|
|
|
+ }
|
|
|
|
+ return texts[status] || '未知'
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 格式化日期
|
|
|
|
+const formatDate = (date) => {
|
|
|
|
+ return new Date(date).toLocaleString()
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 供应商详情相关
|
|
|
|
+const showVendorDetails = ref(false)
|
|
|
|
+const selectedVendor = ref(null)
|
|
|
|
+
|
|
|
|
+// 编辑供应商相关
|
|
|
|
+const showEditVendor = ref(false)
|
|
|
|
+const editVendor = ref({
|
|
|
|
+ id: '',
|
|
|
|
+ name: '',
|
|
|
|
+ level: '',
|
|
|
|
+ contactPerson: '',
|
|
|
|
+ phone: '',
|
|
|
|
+ email: '',
|
|
|
|
+ fax: '',
|
|
|
|
+ address: '',
|
|
|
|
+ paymentMethod: '',
|
|
|
|
+ paymentCycle: '',
|
|
|
|
+ creditLimit: 0,
|
|
|
|
+ usedCredit: 0,
|
|
|
|
+ status: '',
|
|
|
|
+ cooperationDate: '',
|
|
|
|
+ remarks: ''
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+// 添加供应商相关
|
|
|
|
+const showAddVendor = ref(false)
|
|
|
|
+const newVendor = ref({
|
|
|
|
+ name: '',
|
|
|
|
+ level: 'A',
|
|
|
|
+ contactPerson: '',
|
|
|
|
+ phone: '',
|
|
|
|
+ email: '',
|
|
|
|
+ fax: '',
|
|
|
|
+ address: '',
|
|
|
|
+ paymentMethod: 'bank',
|
|
|
|
+ paymentCycle: 'monthly',
|
|
|
|
+ creditLimit: 0,
|
|
|
|
+ usedCredit: 0,
|
|
|
|
+ status: 'active',
|
|
|
|
+ cooperationDate: new Date().toISOString().split('T')[0],
|
|
|
|
+ remarks: ''
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+// 删除确认相关
|
|
|
|
+const showDeleteConfirm = ref(false)
|
|
|
|
+
|
|
|
|
+// 高级筛选相关
|
|
|
|
+const showAdvancedFilter = ref(false)
|
|
|
|
+const filters = ref({
|
|
|
|
+ id: '',
|
|
|
|
+ name: '',
|
|
|
|
+ level: '',
|
|
|
|
+ status: '',
|
|
|
|
+ paymentMethod: '',
|
|
|
|
+ paymentCycle: '',
|
|
|
|
+ minCreditLimit: '',
|
|
|
|
+ maxCreditLimit: '',
|
|
|
|
+ startDate: '',
|
|
|
|
+ endDate: ''
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+// 分页相关
|
|
|
|
+const pagination = ref({
|
|
|
|
+ currentPage: 1,
|
|
|
|
+ pageSize: 10,
|
|
|
|
+ total: 0
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+// 计算分页数据
|
|
|
|
+const paginatedVendors = computed(() => {
|
|
|
|
+ const start = (pagination.value.currentPage - 1) * pagination.value.pageSize
|
|
|
|
+ const end = start + pagination.value.pageSize
|
|
|
|
+ return vendors.value.slice(start, end)
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+// 计算总页数
|
|
|
|
+const totalPages = computed(() => {
|
|
|
|
+ return Math.ceil(vendors.value.length / pagination.value.pageSize)
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+// 处理查看详情
|
|
|
|
+const handleViewDetails = (vendor) => {
|
|
|
|
+ selectedVendor.value = vendor
|
|
|
|
+ showVendorDetails.value = true
|
|
|
|
+ showVendorRatings.value = true
|
|
|
|
+ showVendorHistory.value = true
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 处理编辑供应商
|
|
|
|
+const handleEditVendor = (vendor) => {
|
|
|
|
+ editVendor.value = { ...vendor }
|
|
|
|
+ showEditVendor.value = true
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 表单验证相关
|
|
|
|
+const validateForm = (formData) => {
|
|
|
|
+ const errors = {}
|
|
|
|
+
|
|
|
|
+ // 验证供应商名称
|
|
|
|
+ if (!formData.name) {
|
|
|
|
+ errors.name = '供应商名称不能为空'
|
|
|
|
+ } else if (formData.name.length < 2) {
|
|
|
|
+ errors.name = '供应商名称至少需要2个字符'
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 验证联系人
|
|
|
|
+ if (!formData.contactPerson) {
|
|
|
|
+ errors.contactPerson = '联系人不能为空'
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 验证联系电话
|
|
|
|
+ if (!formData.phone) {
|
|
|
|
+ errors.phone = '联系电话不能为空'
|
|
|
|
+ } else if (!/^1[3-9]\d{9}$/.test(formData.phone)) {
|
|
|
|
+ errors.phone = '请输入有效的手机号码'
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 验证电子邮箱
|
|
|
|
+ if (!formData.email) {
|
|
|
|
+ errors.email = '电子邮箱不能为空'
|
|
|
|
+ } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
|
|
|
|
+ errors.email = '请输入有效的电子邮箱'
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 验证信用额度
|
|
|
|
+ if (formData.creditLimit === undefined || formData.creditLimit === '') {
|
|
|
|
+ errors.creditLimit = '信用额度不能为空'
|
|
|
|
+ } else if (isNaN(formData.creditLimit) || formData.creditLimit < 0) {
|
|
|
|
+ errors.creditLimit = '信用额度必须是非负数'
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 验证已用额度
|
|
|
|
+ if (formData.usedCredit === undefined || formData.usedCredit === '') {
|
|
|
|
+ errors.usedCredit = '已用额度不能为空'
|
|
|
|
+ } else if (isNaN(formData.usedCredit) || formData.usedCredit < 0) {
|
|
|
|
+ errors.usedCredit = '已用额度必须是非负数'
|
|
|
|
+ } else if (formData.usedCredit > formData.creditLimit) {
|
|
|
|
+ errors.usedCredit = '已用额度不能超过信用额度'
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return errors
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 错误提示相关
|
|
|
|
+const showError = ref(false)
|
|
|
|
+const errorMessage = ref('')
|
|
|
|
+const showSuccess = ref(false)
|
|
|
|
+const successMessage = ref('')
|
|
|
|
+
|
|
|
|
+// 显示错误提示
|
|
|
|
+const showErrorMessage = (message) => {
|
|
|
|
+ errorMessage.value = message
|
|
|
|
+ showError.value = true
|
|
|
|
+ setTimeout(() => {
|
|
|
|
+ showError.value = false
|
|
|
|
+ }, 5000)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 显示成功提示
|
|
|
|
+const showSuccessMessage = (message) => {
|
|
|
|
+ successMessage.value = message
|
|
|
|
+ showSuccess.value = true
|
|
|
|
+ setTimeout(() => {
|
|
|
|
+ showSuccess.value = false
|
|
|
|
+ }, 3000)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 修改处理提交编辑
|
|
|
|
+const handleSubmitEdit = () => {
|
|
|
|
+ const errors = validateForm(editVendor.value)
|
|
|
|
+ if (Object.keys(errors).length > 0) {
|
|
|
|
+ showErrorMessage(Object.values(errors).join('\n'))
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const index = vendors.value.findIndex(v => v.id === editVendor.value.id)
|
|
|
|
+ if (index !== -1) {
|
|
|
|
+ const oldVendor = vendors.value[index]
|
|
|
|
+ const changes = {}
|
|
|
|
+
|
|
|
|
+ // 比较变更
|
|
|
|
+ Object.keys(editVendor.value).forEach(key => {
|
|
|
|
+ if (oldVendor[key] !== editVendor.value[key]) {
|
|
|
|
+ changes[key] = {
|
|
|
|
+ old: oldVendor[key],
|
|
|
|
+ new: editVendor.value[key]
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ // 添加历史记录
|
|
|
|
+ addHistoryRecord(
|
|
|
|
+ editVendor.value,
|
|
|
|
+ 'info',
|
|
|
|
+ '更新供应商信息',
|
|
|
|
+ changes
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+ vendors.value[index] = { ...editVendor.value }
|
|
|
|
+ saveToLocalStorage()
|
|
|
|
+ showSuccessMessage('供应商信息更新成功')
|
|
|
|
+ }
|
|
|
|
+ showEditVendor.value = false
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 修改处理提交添加
|
|
|
|
+const handleSubmitAdd = () => {
|
|
|
|
+ const errors = validateForm(newVendor.value)
|
|
|
|
+ if (Object.keys(errors).length > 0) {
|
|
|
|
+ showErrorMessage(Object.values(errors).join('\n'))
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const vendor = {
|
|
|
|
+ ...newVendor.value,
|
|
|
|
+ id: `V${new Date().getTime()}`
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 添加历史记录
|
|
|
|
+ addHistoryRecord(
|
|
|
|
+ vendor,
|
|
|
|
+ 'info',
|
|
|
|
+ '新增供应商'
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+ vendors.value.push(vendor)
|
|
|
|
+ saveToLocalStorage()
|
|
|
|
+ pagination.value.currentPage = 1
|
|
|
|
+ showAddVendor.value = false
|
|
|
|
+ showSuccessMessage('供应商添加成功')
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 处理删除供应商
|
|
|
|
+const handleDeleteVendor = (vendor) => {
|
|
|
|
+ selectedVendor.value = vendor
|
|
|
|
+ showDeleteConfirm.value = true
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 确认删除
|
|
|
|
+const confirmDelete = () => {
|
|
|
|
+ const index = vendors.value.findIndex(v => v.id === selectedVendor.value.id)
|
|
|
|
+ if (index !== -1) {
|
|
|
|
+ // 添加历史记录
|
|
|
|
+ addHistoryRecord(
|
|
|
|
+ selectedVendor.value,
|
|
|
|
+ 'status',
|
|
|
|
+ '删除供应商'
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+ vendors.value.splice(index, 1)
|
|
|
|
+ saveToLocalStorage()
|
|
|
|
+ if (paginatedVendors.value.length === 0 && pagination.value.currentPage > 1) {
|
|
|
|
+ pagination.value.currentPage--
|
|
|
|
+ }
|
|
|
|
+ showSuccessMessage('供应商删除成功')
|
|
|
|
+ }
|
|
|
|
+ showDeleteConfirm.value = false
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 本地存储相关
|
|
|
|
+const STORAGE_KEY = 'ecommerce_vendors'
|
|
|
|
+
|
|
|
|
+// 保存数据到本地存储
|
|
|
|
+const saveToLocalStorage = () => {
|
|
|
|
+ try {
|
|
|
|
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(vendors.value))
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('保存数据失败:', error)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 从本地存储加载数据
|
|
|
|
+const loadFromLocalStorage = () => {
|
|
|
|
+ try {
|
|
|
|
+ const data = localStorage.getItem(STORAGE_KEY)
|
|
|
|
+ if (data) {
|
|
|
|
+ vendors.value = JSON.parse(data)
|
|
|
|
+ }
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('加载数据失败:', error)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 在组件挂载时加载数据
|
|
|
|
+onMounted(() => {
|
|
|
|
+ loadFromLocalStorage()
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+// 在数据变化时保存数据
|
|
|
|
+watch(vendors, () => {
|
|
|
|
+ saveToLocalStorage()
|
|
|
|
+}, { deep: true })
|
|
|
|
+
|
|
|
|
+// 处理筛选
|
|
|
|
+const handleFilter = () => {
|
|
|
|
+ // 实现筛选逻辑
|
|
|
|
+ const filteredVendors = vendors.value.filter(vendor => {
|
|
|
|
+ // 供应商编号筛选
|
|
|
|
+ if (filters.value.id && !vendor.id.includes(filters.value.id)) {
|
|
|
|
+ return false
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 供应商名称筛选
|
|
|
|
+ if (filters.value.name && !vendor.name.includes(filters.value.name)) {
|
|
|
|
+ return false
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 供应商等级筛选
|
|
|
|
+ if (filters.value.level && vendor.level !== filters.value.level) {
|
|
|
|
+ return false
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 状态筛选
|
|
|
|
+ if (filters.value.status && vendor.status !== filters.value.status) {
|
|
|
|
+ return false
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 结算方式筛选
|
|
|
|
+ if (filters.value.paymentMethod && vendor.paymentMethod !== filters.value.paymentMethod) {
|
|
|
|
+ return false
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 结算周期筛选
|
|
|
|
+ if (filters.value.paymentCycle && vendor.paymentCycle !== filters.value.paymentCycle) {
|
|
|
|
+ return false
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 信用额度范围筛选
|
|
|
|
+ if (filters.value.minCreditLimit && vendor.creditLimit < Number(filters.value.minCreditLimit)) {
|
|
|
|
+ return false
|
|
|
|
+ }
|
|
|
|
+ if (filters.value.maxCreditLimit && vendor.creditLimit > Number(filters.value.maxCreditLimit)) {
|
|
|
|
+ return false
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 合作时间范围筛选
|
|
|
|
+ if (filters.value.startDate && new Date(vendor.cooperationDate) < new Date(filters.value.startDate)) {
|
|
|
|
+ return false
|
|
|
|
+ }
|
|
|
|
+ if (filters.value.endDate && new Date(vendor.cooperationDate) > new Date(filters.value.endDate)) {
|
|
|
|
+ return false
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return true
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ // 更新显示的数据
|
|
|
|
+ vendors.value = filteredVendors
|
|
|
|
+ // 重置页码
|
|
|
|
+ pagination.value.currentPage = 1
|
|
|
|
+ showAdvancedFilter.value = false
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 重置筛选条件
|
|
|
|
+const resetFilters = () => {
|
|
|
|
+ filters.value = {
|
|
|
|
+ id: '',
|
|
|
|
+ name: '',
|
|
|
|
+ level: '',
|
|
|
|
+ status: '',
|
|
|
|
+ paymentMethod: '',
|
|
|
|
+ paymentCycle: '',
|
|
|
|
+ minCreditLimit: '',
|
|
|
|
+ maxCreditLimit: '',
|
|
|
|
+ startDate: '',
|
|
|
|
+ endDate: ''
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 处理上一页
|
|
|
|
+const handlePrevPage = () => {
|
|
|
|
+ if (pagination.value.currentPage > 1) {
|
|
|
|
+ pagination.value.currentPage--
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 处理下一页
|
|
|
|
+const handleNextPage = () => {
|
|
|
|
+ if (pagination.value.currentPage < totalPages.value) {
|
|
|
|
+ pagination.value.currentPage++
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 搜索相关
|
|
|
|
+const searchQuery = ref('')
|
|
|
|
+const searchStatus = ref('')
|
|
|
|
+const searchLevel = ref('')
|
|
|
|
+
|
|
|
|
+// 处理搜索
|
|
|
|
+const handleSearch = () => {
|
|
|
|
+ const filteredVendors = vendors.value.filter(vendor => {
|
|
|
|
+ // 搜索关键词匹配
|
|
|
|
+ if (searchQuery.value) {
|
|
|
|
+ const query = searchQuery.value.toLowerCase()
|
|
|
|
+ const matchName = vendor.name.toLowerCase().includes(query)
|
|
|
|
+ const matchContact = vendor.contactPerson.toLowerCase().includes(query)
|
|
|
|
+ const matchPhone = vendor.phone.includes(query)
|
|
|
|
+ const matchEmail = vendor.email.toLowerCase().includes(query)
|
|
|
|
+ if (!matchName && !matchContact && !matchPhone && !matchEmail) {
|
|
|
|
+ return false
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 状态筛选
|
|
|
|
+ if (searchStatus.value && vendor.status !== searchStatus.value) {
|
|
|
|
+ return false
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 等级筛选
|
|
|
|
+ if (searchLevel.value && vendor.level !== searchLevel.value) {
|
|
|
|
+ return false
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return true
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ // 更新显示的数据
|
|
|
|
+ vendors.value = filteredVendors
|
|
|
|
+ // 重置页码
|
|
|
|
+ pagination.value.currentPage = 1
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 重置搜索
|
|
|
|
+const resetSearch = () => {
|
|
|
|
+ searchQuery.value = ''
|
|
|
|
+ searchStatus.value = ''
|
|
|
|
+ searchLevel.value = ''
|
|
|
|
+ handleSearch()
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 处理导入
|
|
|
|
+const handleImport = () => {
|
|
|
|
+ try {
|
|
|
|
+ const input = document.createElement('input')
|
|
|
|
+ input.type = 'file'
|
|
|
|
+ input.accept = '.json'
|
|
|
|
+
|
|
|
|
+ input.onchange = (e) => {
|
|
|
|
+ const file = e.target.files[0]
|
|
|
|
+ if (file) {
|
|
|
|
+ const reader = new FileReader()
|
|
|
|
+
|
|
|
|
+ reader.onload = (e) => {
|
|
|
|
+ try {
|
|
|
|
+ const data = JSON.parse(e.target.result)
|
|
|
|
+
|
|
|
|
+ if (Array.isArray(data)) {
|
|
|
|
+ // 验证每个供应商数据
|
|
|
|
+ const invalidVendors = data.filter(vendor => {
|
|
|
|
+ const errors = validateForm(vendor)
|
|
|
|
+ return Object.keys(errors).length > 0
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ if (invalidVendors.length > 0) {
|
|
|
|
+ showErrorMessage('导入的数据中包含无效的供应商信息,请检查数据格式')
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ vendors.value = data
|
|
|
|
+ saveToLocalStorage()
|
|
|
|
+ pagination.value.currentPage = 1
|
|
|
|
+ showSuccessMessage('供应商数据导入成功')
|
|
|
|
+ } else {
|
|
|
|
+ throw new Error('导入的数据必须是数组格式')
|
|
|
|
+ }
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('导入失败:', error)
|
|
|
|
+ showErrorMessage('导入失败:' + error.message)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ reader.onerror = () => {
|
|
|
|
+ console.error('文件读取失败')
|
|
|
|
+ showErrorMessage('文件读取失败')
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ reader.readAsText(file)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ input.click()
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('导入失败:', error)
|
|
|
|
+ showErrorMessage('导入失败:' + error.message)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 处理导出
|
|
|
|
+const handleExport = () => {
|
|
|
|
+ try {
|
|
|
|
+ // 准备导出数据
|
|
|
|
+ const exportData = vendors.value.map(vendor => ({
|
|
|
|
+ id: vendor.id,
|
|
|
|
+ name: vendor.name,
|
|
|
|
+ level: vendor.level,
|
|
|
|
+ contactPerson: vendor.contactPerson,
|
|
|
|
+ phone: vendor.phone,
|
|
|
|
+ email: vendor.email,
|
|
|
|
+ fax: vendor.fax,
|
|
|
|
+ address: vendor.address,
|
|
|
|
+ paymentMethod: vendor.paymentMethod,
|
|
|
|
+ paymentCycle: vendor.paymentCycle,
|
|
|
|
+ creditLimit: vendor.creditLimit,
|
|
|
|
+ usedCredit: vendor.usedCredit,
|
|
|
|
+ status: vendor.status,
|
|
|
|
+ cooperationDate: vendor.cooperationDate,
|
|
|
|
+ remarks: vendor.remarks
|
|
|
|
+ }))
|
|
|
|
+
|
|
|
|
+ // 转换为JSON字符串
|
|
|
|
+ const data = JSON.stringify(exportData, null, 2)
|
|
|
|
+
|
|
|
|
+ // 创建Blob对象
|
|
|
|
+ const blob = new Blob([data], { type: 'application/json' })
|
|
|
|
+ const url = URL.createObjectURL(blob)
|
|
|
|
+
|
|
|
|
+ // 创建下载链接
|
|
|
|
+ const a = document.createElement('a')
|
|
|
|
+ a.href = url
|
|
|
|
+ a.download = `vendors_${new Date().toISOString().split('T')[0]}.json`
|
|
|
|
+
|
|
|
|
+ // 触发下载
|
|
|
|
+ document.body.appendChild(a)
|
|
|
|
+ a.click()
|
|
|
|
+ document.body.removeChild(a)
|
|
|
|
+
|
|
|
|
+ // 释放URL
|
|
|
|
+ URL.revokeObjectURL(url)
|
|
|
|
+ showSuccessMessage('供应商数据导出成功')
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('导出失败:', error)
|
|
|
|
+ showErrorMessage('导出失败:' + error.message)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 统计相关计算属性
|
|
|
|
+const activeVendorsCount = computed(() => {
|
|
|
|
+ return vendors.value.filter(v => v.status === 'active').length
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+const totalCreditLimit = computed(() => {
|
|
|
|
+ return vendors.value.reduce((sum, v) => sum + v.creditLimit, 0)
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+const totalUsedCredit = computed(() => {
|
|
|
|
+ return vendors.value.reduce((sum, v) => sum + v.usedCredit, 0)
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+const levelDistribution = computed(() => {
|
|
|
|
+ const distribution = { A: 0, B: 0, C: 0 }
|
|
|
|
+ vendors.value.forEach(v => {
|
|
|
|
+ distribution[v.level]++
|
|
|
|
+ })
|
|
|
|
+ return distribution
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+const statusDistribution = computed(() => {
|
|
|
|
+ const distribution = { active: 0, inactive: 0, blacklisted: 0 }
|
|
|
|
+ vendors.value.forEach(v => {
|
|
|
|
+ distribution[v.status]++
|
|
|
|
+ })
|
|
|
|
+ return distribution
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+// 评价相关
|
|
|
|
+const showVendorRatings = ref(false)
|
|
|
|
+const newRating = ref({
|
|
|
|
+ score: 0,
|
|
|
|
+ comment: '',
|
|
|
|
+ date: new Date().toISOString(),
|
|
|
|
+ userId: 'current_user'
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+// 计算平均评分
|
|
|
|
+const averageRating = computed(() => {
|
|
|
|
+ if (!selectedVendor.value?.ratings?.length) return 0
|
|
|
|
+ const sum = selectedVendor.value.ratings.reduce((acc, rating) => acc + rating.score, 0)
|
|
|
|
+ return (sum / selectedVendor.value.ratings.length).toFixed(1)
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+// 计算好评率
|
|
|
|
+const positiveRatingPercentage = computed(() => {
|
|
|
|
+ if (!selectedVendor.value?.ratings?.length) return 0
|
|
|
|
+ const positiveCount = selectedVendor.value.ratings.filter(rating => rating.score >= 4).length
|
|
|
|
+ return ((positiveCount / selectedVendor.value.ratings.length) * 100).toFixed(1)
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+// 计算评分分布
|
|
|
|
+const ratingDistribution = computed(() => {
|
|
|
|
+ const distribution = { 5: 0, 4: 0, 3: 0, 2: 0, 1: 0 }
|
|
|
|
+ selectedVendor.value?.ratings?.forEach(rating => {
|
|
|
|
+ distribution[rating.score]++
|
|
|
|
+ })
|
|
|
|
+ return distribution
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+// 处理提交评价
|
|
|
|
+const handleSubmitRating = () => {
|
|
|
|
+ if (!newRating.value.score) {
|
|
|
|
+ showErrorMessage('请选择评分')
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ if (!newRating.value.comment) {
|
|
|
|
+ showErrorMessage('请输入评价内容')
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const rating = {
|
|
|
|
+ ...newRating.value,
|
|
|
|
+ id: `R${new Date().getTime()}`
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!selectedVendor.value.ratings) {
|
|
|
|
+ selectedVendor.value.ratings = []
|
|
|
|
+ }
|
|
|
|
+ selectedVendor.value.ratings.unshift(rating)
|
|
|
|
+
|
|
|
|
+ // 添加历史记录
|
|
|
|
+ addHistoryRecord(
|
|
|
|
+ selectedVendor.value,
|
|
|
|
+ 'rating',
|
|
|
|
+ '添加评价',
|
|
|
|
+ {
|
|
|
|
+ score: rating.score,
|
|
|
|
+ comment: rating.comment
|
|
|
|
+ }
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+ // 更新供应商数据
|
|
|
|
+ const index = vendors.value.findIndex(v => v.id === selectedVendor.value.id)
|
|
|
|
+ if (index !== -1) {
|
|
|
|
+ vendors.value[index] = { ...selectedVendor.value }
|
|
|
|
+ saveToLocalStorage()
|
|
|
|
+ showSuccessMessage('评价提交成功')
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 重置表单
|
|
|
|
+ newRating.value = {
|
|
|
|
+ score: 0,
|
|
|
|
+ comment: '',
|
|
|
|
+ date: new Date().toISOString(),
|
|
|
|
+ userId: 'current_user'
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 处理删除评价
|
|
|
|
+const handleDeleteRating = (rating) => {
|
|
|
|
+ const index = selectedVendor.value.ratings.findIndex(r => r.id === rating.id)
|
|
|
|
+ if (index !== -1) {
|
|
|
|
+ // 添加历史记录
|
|
|
|
+ addHistoryRecord(
|
|
|
|
+ selectedVendor.value,
|
|
|
|
+ 'rating',
|
|
|
|
+ '删除评价',
|
|
|
|
+ {
|
|
|
|
+ score: rating.score,
|
|
|
|
+ comment: rating.comment
|
|
|
|
+ }
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+ selectedVendor.value.ratings.splice(index, 1)
|
|
|
|
+
|
|
|
|
+ // 更新供应商数据
|
|
|
|
+ const vendorIndex = vendors.value.findIndex(v => v.id === selectedVendor.value.id)
|
|
|
|
+ if (vendorIndex !== -1) {
|
|
|
|
+ vendors.value[vendorIndex] = { ...selectedVendor.value }
|
|
|
|
+ saveToLocalStorage()
|
|
|
|
+ showSuccessMessage('评价删除成功')
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 历史记录相关
|
|
|
|
+const showVendorHistory = ref(false)
|
|
|
|
+
|
|
|
|
+// 计算信息变更次数
|
|
|
|
+const infoChangesCount = computed(() => {
|
|
|
|
+ if (!selectedVendor.value?.history) return 0
|
|
|
|
+ return selectedVendor.value.history.filter(record => record.type === 'info').length
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+// 计算状态变更次数
|
|
|
|
+const statusChangesCount = computed(() => {
|
|
|
|
+ if (!selectedVendor.value?.history) return 0
|
|
|
|
+ return selectedVendor.value.history.filter(record => record.type === 'status').length
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+// 获取历史记录类型样式
|
|
|
|
+const getHistoryTypeClass = (type) => {
|
|
|
|
+ const classes = {
|
|
|
|
+ info: 'bg-blue-100 dark:bg-blue-900',
|
|
|
|
+ status: 'bg-yellow-100 dark:bg-yellow-900',
|
|
|
|
+ credit: 'bg-purple-100 dark:bg-purple-900',
|
|
|
|
+ rating: 'bg-green-100 dark:bg-green-900'
|
|
|
|
+ }
|
|
|
|
+ return classes[type] || 'bg-gray-100 dark:bg-gray-900'
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 获取历史记录类型图标
|
|
|
|
+const getHistoryTypeIcon = (type) => {
|
|
|
|
+ const icons = {
|
|
|
|
+ info: EditIcon,
|
|
|
|
+ status: AlertTriangleIcon,
|
|
|
|
+ credit: CreditCardIcon,
|
|
|
|
+ rating: StarIcon
|
|
|
|
+ }
|
|
|
|
+ return icons[type] || ClockIcon
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 获取历史记录类型图标样式
|
|
|
|
+const getHistoryTypeIconClass = (type) => {
|
|
|
|
+ const classes = {
|
|
|
|
+ info: 'text-blue-600 dark:text-blue-300',
|
|
|
|
+ status: 'text-yellow-600 dark:text-yellow-300',
|
|
|
|
+ credit: 'text-purple-600 dark:text-purple-300',
|
|
|
|
+ rating: 'text-green-600 dark:text-green-300'
|
|
|
|
+ }
|
|
|
|
+ return classes[type] || 'text-gray-600 dark:text-gray-300'
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 获取历史记录类型文本
|
|
|
|
+const getHistoryTypeText = (type) => {
|
|
|
|
+ const texts = {
|
|
|
|
+ info: '信息变更',
|
|
|
|
+ status: '状态变更',
|
|
|
|
+ credit: '信用额度变更',
|
|
|
|
+ rating: '评价变更'
|
|
|
|
+ }
|
|
|
|
+ return texts[type] || '其他变更'
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 获取字段名称
|
|
|
|
+const getFieldName = (key) => {
|
|
|
|
+ const names = {
|
|
|
|
+ name: '供应商名称',
|
|
|
|
+ level: '供应商等级',
|
|
|
|
+ contactPerson: '联系人',
|
|
|
|
+ phone: '联系电话',
|
|
|
|
+ email: '电子邮箱',
|
|
|
|
+ fax: '传真',
|
|
|
|
+ address: '地址',
|
|
|
|
+ paymentMethod: '结算方式',
|
|
|
|
+ paymentCycle: '结算周期',
|
|
|
|
+ creditLimit: '信用额度',
|
|
|
|
+ usedCredit: '已用额度',
|
|
|
|
+ status: '状态',
|
|
|
|
+ remarks: '备注'
|
|
|
|
+ }
|
|
|
|
+ return names[key] || key
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 格式化变更值
|
|
|
|
+const formatChangeValue = (value) => {
|
|
|
|
+ if (typeof value === 'object') {
|
|
|
|
+ return `${value.old} → ${value.new}`
|
|
|
|
+ }
|
|
|
|
+ return value
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 添加历史记录
|
|
|
|
+const addHistoryRecord = (vendor, type, description, changes = null) => {
|
|
|
|
+ if (!vendor.history) {
|
|
|
|
+ vendor.history = []
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const record = {
|
|
|
|
+ id: `H${new Date().getTime()}`,
|
|
|
|
+ type,
|
|
|
|
+ date: new Date().toISOString(),
|
|
|
|
+ user: '当前用户',
|
|
|
|
+ description,
|
|
|
|
+ changes
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ vendor.history.unshift(record)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 合同相关
|
|
|
|
+const showVendorContracts = ref(false)
|
|
|
|
+const showContractDetails = ref(false)
|
|
|
|
+const showEditContract = ref(false)
|
|
|
|
+const showAddContract = ref(false)
|
|
|
|
+const showDeleteContractConfirm = ref(false)
|
|
|
|
+const selectedContract = ref(null)
|
|
|
|
+const editContract = ref({
|
|
|
|
+ id: '',
|
|
|
|
+ name: '',
|
|
|
|
+ type: '',
|
|
|
|
+ signDate: '',
|
|
|
|
+ startDate: '',
|
|
|
|
+ endDate: '',
|
|
|
|
+ amount: 0,
|
|
|
|
+ terms: '',
|
|
|
|
+ status: 'active'
|
|
|
|
+})
|
|
|
|
+const newContract = ref({
|
|
|
|
+ name: '',
|
|
|
|
+ type: 'purchase',
|
|
|
|
+ signDate: new Date().toISOString().split('T')[0],
|
|
|
|
+ startDate: new Date().toISOString().split('T')[0],
|
|
|
|
+ endDate: '',
|
|
|
|
+ amount: 0,
|
|
|
|
+ terms: '',
|
|
|
|
+ status: 'active'
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+// 获取合同状态样式
|
|
|
|
+const getContractStatusClass = (status) => {
|
|
|
|
+ const classes = {
|
|
|
|
+ active: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
|
|
|
|
+ expired: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200',
|
|
|
|
+ expiring: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200'
|
|
|
|
+ }
|
|
|
|
+ return classes[status] || 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200'
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 获取合同状态文本
|
|
|
|
+const getContractStatusText = (status) => {
|
|
|
|
+ const texts = {
|
|
|
|
+ active: '有效',
|
|
|
|
+ expired: '已过期',
|
|
|
|
+ expiring: '即将到期'
|
|
|
|
+ }
|
|
|
|
+ return texts[status] || '未知'
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 获取合同类型文本
|
|
|
|
+const getContractTypeText = (type) => {
|
|
|
|
+ const texts = {
|
|
|
|
+ purchase: '采购合同',
|
|
|
|
+ service: '服务合同',
|
|
|
|
+ framework: '框架协议'
|
|
|
|
+ }
|
|
|
|
+ return texts[type] || '未知'
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 计算有效合同数量
|
|
|
|
+const validContractsCount = computed(() => {
|
|
|
|
+ if (!selectedVendor.value?.contracts) return 0
|
|
|
|
+ return selectedVendor.value.contracts.filter(c => c.status === 'active').length
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+// 计算即将到期合同数量
|
|
|
|
+const expiringContractsCount = computed(() => {
|
|
|
|
+ if (!selectedVendor.value?.contracts) return 0
|
|
|
|
+ const now = new Date()
|
|
|
|
+ const thirtyDaysLater = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000)
|
|
|
|
+ return selectedVendor.value.contracts.filter(c => {
|
|
|
|
+ const endDate = new Date(c.endDate)
|
|
|
|
+ return endDate > now && endDate <= thirtyDaysLater
|
|
|
|
+ }).length
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+// 计算已过期合同数量
|
|
|
|
+const expiredContractsCount = computed(() => {
|
|
|
|
+ if (!selectedVendor.value?.contracts) return 0
|
|
|
|
+ const now = new Date()
|
|
|
|
+ return selectedVendor.value.contracts.filter(c => new Date(c.endDate) < now).length
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+// 处理查看合同
|
|
|
|
+const handleViewContract = (contract) => {
|
|
|
|
+ selectedContract.value = contract
|
|
|
|
+ showContractDetails.value = true
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 处理编辑合同
|
|
|
|
+const handleEditContract = (contract) => {
|
|
|
|
+ editContract.value = { ...contract }
|
|
|
|
+ showEditContract.value = true
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 处理添加合同
|
|
|
|
+const handleAddContract = () => {
|
|
|
|
+ showAddContract.value = true
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 处理删除合同
|
|
|
|
+const handleDeleteContract = (contract) => {
|
|
|
|
+ selectedContract.value = contract
|
|
|
|
+ showDeleteContractConfirm.value = true
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 处理文件上传
|
|
|
|
+const handleFileUpload = (event) => {
|
|
|
|
+ const files = event.target.files
|
|
|
|
+ if (files.length > 0) {
|
|
|
|
+ // 处理文件上传逻辑
|
|
|
|
+ console.log('Files uploaded:', files)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 处理下载附件
|
|
|
|
+const handleDownloadAttachment = (attachment) => {
|
|
|
|
+ // 实现下载逻辑
|
|
|
|
+ console.log('Download attachment:', attachment)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 处理提交编辑合同
|
|
|
|
+const handleSubmitEditContract = () => {
|
|
|
|
+ // 实现编辑合同逻辑
|
|
|
|
+ const index = selectedVendor.value.contracts.findIndex(c => c.id === editContract.value.id)
|
|
|
|
+ if (index !== -1) {
|
|
|
|
+ selectedVendor.value.contracts[index] = { ...editContract.value }
|
|
|
|
+ saveToLocalStorage()
|
|
|
|
+ showSuccessMessage('合同更新成功')
|
|
|
|
+ }
|
|
|
|
+ showEditContract.value = false
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 处理提交添加合同
|
|
|
|
+const handleSubmitAddContract = () => {
|
|
|
|
+ // 实现添加合同逻辑
|
|
|
|
+ const contract = {
|
|
|
|
+ ...newContract.value,
|
|
|
|
+ id: `C${new Date().getTime()}`
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!selectedVendor.value.contracts) {
|
|
|
|
+ selectedVendor.value.contracts = []
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ selectedVendor.value.contracts.push(contract)
|
|
|
|
+ saveToLocalStorage()
|
|
|
|
+ showSuccessMessage('合同添加成功')
|
|
|
|
+ showAddContract.value = false
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 确认删除合同
|
|
|
|
+const confirmDeleteContract = () => {
|
|
|
|
+ const index = selectedVendor.value.contracts.findIndex(c => c.id === selectedContract.value.id)
|
|
|
|
+ if (index !== -1) {
|
|
|
|
+ selectedVendor.value.contracts.splice(index, 1)
|
|
|
|
+ saveToLocalStorage()
|
|
|
|
+ showSuccessMessage('合同删除成功')
|
|
|
|
+ }
|
|
|
|
+ showDeleteContractConfirm.value = false
|
|
|
|
+}
|
|
|
|
+</script>
|
|
|
|
+
|
|
|
|
+<style scoped>
|
|
|
|
+/* 样式内容 */
|
|
|
|
+</style>
|