You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

3187 lines
109 KiB

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, maximum-scale=1.0, minimum-scale=1.0">
<title>供应商审核系统</title>
<style>
/* 全局样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* 导航栏样式 */
.navbar {
background: linear-gradient(135deg, #1890ff 0%, #36cfc9 100%);
color: white;
padding: 16px 0;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
position: sticky;
top: 0;
z-index: 1000;
}
.navbar-container {
max-width: 1200px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
}
.navbar-brand {
display: flex;
align-items: center;
gap: 12px;
}
.navbar-logo {
width: 40px;
height: 40px;
background: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
font-weight: bold;
color: #1890ff;
}
.navbar-title {
font-size: 20px;
font-weight: 600;
}
.navbar-links {
display: flex;
gap: 24px;
align-items: center;
}
.navbar-link {
color: white;
text-decoration: none;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
padding: 8px 16px;
border-radius: 8px;
}
.navbar-link:hover {
background: rgba(255, 255, 255, 0.2);
transform: translateY(-2px);
}
.navbar-link.active {
background: rgba(255, 255, 255, 0.3);
font-weight: 600;
}
/* 主内容区域样式 */
.main-content {
flex: 1;
}
/* 品牌标识样式 */
.brand-banner {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
padding: 40px 0;
text-align: center;
margin-bottom: 30px;
border-radius: 12px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}
.brand-banner h2 {
font-size: 24px;
font-weight: 600;
color: #333;
margin-bottom: 12px;
}
.brand-banner p {
font-size: 16px;
color: #666;
max-width: 800px;
margin: 0 auto;
}
/* 统计卡片样式 */
.stats-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.stats-card {
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
border-radius: 12px;
padding: 24px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
border: 1px solid #e0e0e0;
}
.stats-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
border-color: #1890ff;
}
.stats-number {
font-size: 32px;
font-weight: 600;
color: #1890ff;
margin-bottom: 8px;
}
.stats-label {
font-size: 14px;
color: #666;
font-weight: 500;
}
/* 动画效果 */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.fade-in {
animation: fadeIn 0.6s ease-out;
}
/* 响应式导航栏 */
@media (max-width: 768px) {
.navbar-container {
flex-direction: column;
gap: 16px;
padding: 0 16px;
}
.navbar-links {
flex-wrap: wrap;
justify-content: center;
gap: 12px;
}
.navbar-link {
font-size: 13px;
padding: 6px 12px;
}
.stats-container {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 16px;
}
.stats-card {
padding: 20px;
}
.stats-number {
font-size: 24px;
}
.footer-container {
gap: 30px;
}
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
margin: 0;
padding: 0;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
color: #333;
min-height: 100vh;
}
.container {
max-width: 1200px;
margin: 0 auto;
background-color: #fff;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
padding: 30px;
margin-top: 30px;
margin-bottom: 30px;
backdrop-filter: blur(10px);
}
h1 {
text-align: center;
color: #1890ff;
margin-bottom: 30px;
font-size: 28px;
font-weight: 600;
background: linear-gradient(135deg, #1890ff 0%, #36cfc9 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.search-bar {
margin-bottom: 30px;
display: flex;
gap: 12px;
flex-wrap: wrap;
align-items: center;
padding: 20px;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-radius: 12px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05) inset;
}
.search-bar input {
flex: 1;
min-width: 200px;
padding: 14px 16px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 14px;
transition: all 0.3s ease;
background-color: #fff;
}
.search-bar input:focus {
outline: none;
border-color: #1890ff;
box-shadow: 0 0 0 3px rgba(24, 144, 255, 0.1);
}
.search-bar button {
padding: 14px 24px;
background: linear-gradient(135deg, #1890ff 0%, #36cfc9 100%);
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
position: relative;
overflow: hidden;
}
.search-bar button::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
transition: left 0.5s;
}
.search-bar button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(24, 144, 255, 0.4);
}
.search-bar button:hover::before {
left: 100%;
}
.supply-item {
border: 1px solid #e0e0e0;
border-radius: 12px;
padding: 24px;
margin-bottom: 24px;
transition: all 0.3s ease;
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}
.supply-item:hover {
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
border-color: #1890ff;
}
/* 联系人分配区域样式 */
.supply-contact {
display: flex;
align-items: center;
margin: 15px 0;
padding: 10px;
background-color: #f9f9f9;
border-radius: 8px;
}
.contact-label {
font-weight: bold;
margin-right: 10px;
color: #333;
}
.contact-select {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
background-color: white;
font-size: 14px;
cursor: pointer;
flex: 1;
}
.contact-select:hover {
border-color: #ccc;
}
.contact-select:focus {
outline: none;
border-color: #4CAF50;
box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.2);
}
/* 联系人信息展示样式 */
.contact-info {
display: flex;
flex-direction: column;
gap: 4px;
}
.contact-name {
font-weight: 500;
color: #333;
}
.contact-phone {
font-size: 14px;
color: #666;
}
/* 图片展示区域样式 */
.supply-images {
display: flex;
flex-wrap: wrap;
margin: 12px 0;
gap: 8px;
position: relative;
}
.supply-image-item {
width: 100px;
height: 100px;
border-radius: 4px;
overflow: hidden;
background-color: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
position: relative;
transition: transform 0.3s ease;
cursor: pointer;
}
.supply-image-item:hover {
transform: scale(1.1);
z-index: 10;
}
.supply-image-item img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
transition: none;
transform: translateZ(0);
backface-visibility: hidden;
image-rendering: -webkit-optimize-contrast;
}
.image-skeleton {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(90deg, #f0f0f0 25%, #f8f8f8 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
/* 图片查看器样式 */
.image-viewer {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.9);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
}
.image-viewer.active {
opacity: 1;
visibility: visible;
}
.image-viewer-content {
max-width: 90%;
max-height: 90%;
position: relative;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
touch-action: none;
}
.image-viewer img {
max-width: none;
max-height: none;
width: auto;
height: auto;
object-fit: contain;
transition: transform 0.1s ease;
cursor: grab;
}
.image-viewer img:active {
cursor: grabbing;
}
.image-viewer-close {
position: absolute;
top: 20px;
right: 20px;
width: 40px;
height: 40px;
background-color: rgba(255, 255, 255, 0.2);
border: none;
border-radius: 50%;
color: white;
font-size: 24px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.3s ease;
}
.image-viewer-close:hover {
background-color: rgba(255, 255, 255, 0.4);
}
.image-viewer-prev,
.image-viewer-next,
.image-viewer-rotate {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 40px;
height: 40px;
background-color: rgba(255, 255, 255, 0.2);
border: none;
border-radius: 50%;
color: white;
font-size: 20px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.3s ease;
}
.image-viewer-rotate {
top: auto;
bottom: 20px;
right: 80px;
transform: none;
}
.image-viewer-rotate:hover {
background-color: rgba(255, 255, 255, 0.4);
}
.image-viewer-prev {
left: 20px;
}
.image-viewer-next {
right: 20px;
}
.image-viewer-prev:hover,
.image-viewer-next:hover {
background-color: rgba(255, 255, 255, 0.4);
}
.image-counter {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background-color: rgba(255, 255, 255, 0.9);
color: black;
padding: 5px 15px;
border-radius: 20px;
font-size: 14px;
font-weight: 500;
}
@keyframes loading {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
.placeholder-tag,
.oss-tag {
position: absolute;
bottom: 4px;
right: 4px;
padding: 2px 6px;
background-color: rgba(0, 0, 0, 0.6);
color: white;
font-size: 10px;
border-radius: 2px;
}
.image-error {
filter: grayscale(50%);
}
.more-images {
position: absolute;
top: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.6);
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
}
.supply-item:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.supply-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 16px;
border-bottom: 1px solid #f0f0f0;
}
.supply-title {
font-size: 20px;
font-weight: 600;
color: #333;
margin: 0;
flex: 1;
}
.supply-status {
padding: 8px 16px;
border-radius: 20px;
font-size: 12px;
font-weight: 500;
color: #fff;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.status-pending {
background-color: #faad14;
}
.status-published {
background-color: #52c41a;
}
.status-rejected {
background-color: #ff4d4f;
}
.status-soldout {
background-color: #8c8c8c;
}
.status-hidden {
background-color: #d9d9d9;
}
/* 审核时间样式 */
.audit-time {
display: inline-block;
padding: 8px 16px;
background-color: #1890ff;
color: white;
border-radius: 4px;
font-size: 14px;
font-weight: 500;
text-align: center;
min-width: 200px;
margin-top: 24px; /* 向下平移2个字的距离 */
}
.supply-info {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 24px;
padding: 20px;
background-color: #f8f9fa;
border-radius: 12px;
}
.info-item {
display: flex;
flex-direction: column;
transition: all 0.3s ease;
}
.info-item:hover {
transform: translateY(-2px);
}
.info-label {
font-size: 12px;
font-weight: 500;
color: #666;
margin-bottom: 6px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.info-value {
font-size: 16px;
font-weight: 500;
color: #333;
}
.supply-images {
display: flex;
gap: 12px;
margin-bottom: 24px;
overflow-x: auto;
padding: 16px;
background-color: #f8f9fa;
border-radius: 12px;
scrollbar-width: thin;
scrollbar-color: #1890ff #f0f0f0;
}
.supply-images::-webkit-scrollbar {
height: 6px;
}
.supply-images::-webkit-scrollbar-track {
background: #f0f0f0;
border-radius: 3px;
}
.supply-images::-webkit-scrollbar-thumb {
background: #1890ff;
border-radius: 3px;
}
.supply-images::-webkit-scrollbar-thumb:hover {
background: #40a9ff;
}
.supply-image {
width: 120px;
height: 120px;
object-fit: cover;
border-radius: 8px;
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.supply-image:hover {
transform: scale(1.05);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
.supply-reject-reason {
margin-top: 20px;
padding: 20px;
background: linear-gradient(135deg, #fff2f0 0%, #fff7f7 100%);
border: 2px solid #ffccc7;
border-radius: 12px;
box-shadow: 0 2px 10px rgba(255, 77, 79, 0.1);
}
.supply-reject-reason .label {
font-size: 14px;
font-weight: 600;
color: #ff4d4f;
margin-bottom: 10px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.supply-reject-reason .reason {
font-size: 16px;
color: #333;
line-height: 1.5;
}
.action-buttons {
display: flex;
gap: 12px;
margin-top: 24px;
flex-wrap: wrap;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.btn-primary {
background: linear-gradient(135deg, #1890ff 0%, #36cfc9 100%);
color: white;
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
}
.btn-primary::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
transition: left 0.5s;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(24, 144, 255, 0.4);
}
.btn-primary:hover::before {
left: 100%;
}
.btn-danger {
background: linear-gradient(135deg, #ff4d4f 0%, #ff7875 100%);
color: white;
box-shadow: 0 4px 12px rgba(255, 77, 79, 0.3);
}
.btn-danger::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
transition: left 0.5s;
}
.btn-danger:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(255, 77, 79, 0.4);
}
.btn-danger:hover::before {
left: 100%;
}
.btn:disabled {
background: linear-gradient(135deg, #d9d9d9 0%, #f0f0f0 100%);
color: #999;
cursor: not-allowed;
box-shadow: none;
}
.btn-default {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
color: #666;
border: 2px solid #e0e0e0;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.btn-default::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
transition: left 0.5s;
}
.btn-default:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
color: #333;
border-color: #1890ff;
}
.btn-default:hover::before {
left: 100%;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.7);
z-index: 1000;
align-items: center;
justify-content: center;
backdrop-filter: blur(5px);
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.modal-content {
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
border-radius: 16px;
padding: 32px;
width: 90%;
max-width: 500px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from {
transform: translateY(-50px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.modal-header {
font-size: 20px;
font-weight: 600;
margin-bottom: 24px;
text-align: center;
color: #333;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.modal-body {
margin-bottom: 24px;
}
.modal-body textarea {
width: 100%;
height: 140px;
padding: 16px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 14px;
resize: vertical;
box-sizing: border-box;
transition: all 0.3s ease;
background-color: #fff;
}
.modal-body textarea:focus {
outline: none;
border-color: #1890ff;
box-shadow: 0 0 0 3px rgba(24, 144, 255, 0.1);
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
padding-top: 20px;
border-top: 1px solid #f0f0f0;
}
.modal-footer .btn {
min-width: 100px;
}
.empty-state {
text-align: center;
padding: 80px 30px;
color: #666;
background: linear-gradient(135deg, #fafafa 0%, #f0f2f5 100%);
border-radius: 16px;
margin: 30px 0;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
border: 2px dashed #e0e0e0;
}
.empty-state img {
width: 140px;
margin-bottom: 24px;
opacity: 0.6;
transition: all 0.3s ease;
}
.empty-state img:hover {
opacity: 0.8;
transform: scale(1.05);
}
.empty-icon {
font-size: 64px;
margin-bottom: 20px;
color: #1890ff;
}
.empty-state h3 {
margin: 0 0 12px 0;
color: #333;
font-size: 20px;
font-weight: 600;
}
.empty-state p {
margin: 12px 0;
line-height: 1.6;
font-size: 16px;
}
.error-details {
font-size: 14px;
color: #999;
margin: 12px 0 20px 0;
max-width: 600px;
margin-left: auto;
margin-right: auto;
line-height: 1.5;
}
.empty-state .btn-primary {
margin-top: 24px;
padding: 12px 32px;
font-size: 16px;
cursor: pointer;
}
.loading {
text-align: center;
padding: 60px;
color: #999;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-radius: 12px;
margin: 20px 0;
}
.loading::after {
content: '';
display: inline-block;
width: 40px;
height: 40px;
border: 3px solid #f3f3f3;
border-top: 3px solid #1890ff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-top: 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.pagination {
display: flex;
justify-content: center;
margin-top: 40px;
gap: 8px;
padding: 20px;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-radius: 12px;
}
.pagination button {
padding: 10px 16px;
border: 2px solid #e0e0e0;
background-color: white;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
}
.pagination button:hover:not(:disabled) {
border-color: #1890ff;
color: #1890ff;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.2);
}
.pagination button:disabled {
cursor: not-allowed;
opacity: 0.5;
background-color: #f0f0f0;
}
.pagination button.active {
background: linear-gradient(135deg, #1890ff 0%, #36cfc9 100%);
color: white;
border-color: #1890ff;
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
}
/* 状态导航栏样式 */
.status-nav {
display: flex;
margin: 24px 0;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-radius: 12px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
overflow: hidden;
position: relative;
}
.status-btn {
flex: 1;
padding: 16px 24px;
border: 2px solid transparent;
background: transparent;
color: #666;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
box-sizing: border-box;
margin: 0;
overflow: hidden;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.status-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(24, 144, 255, 0.2), transparent);
transition: left 0.5s ease;
}
.status-btn:hover {
color: #1890ff;
background: rgba(24, 144, 255, 0.1);
border-color: rgba(24, 144, 255, 0.3);
}
.status-btn:hover::before {
left: 100%;
}
.status-btn.active {
color: #1890ff;
font-weight: 600;
background: linear-gradient(135deg, rgba(24, 144, 255, 0.2) 0%, rgba(24, 144, 255, 0.1) 100%);
border-color: #1890ff;
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.2);
}
.status-btn.active::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 3px;
background: linear-gradient(90deg, #1890ff 0%, #36cfc9 100%);
}
/* 类型切换器样式 */
.type-nav {
display: flex;
margin: 24px 0;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-radius: 12px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
overflow: hidden;
}
.type-btn {
flex: 1;
padding: 16px 24px;
border: 2px solid transparent;
background: transparent;
color: #666;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.type-btn:hover {
color: #1890ff;
background: rgba(24, 144, 255, 0.1);
border-color: rgba(24, 144, 255, 0.3);
}
.type-btn.active {
color: #1890ff;
font-weight: 600;
background: linear-gradient(135deg, rgba(24, 144, 255, 0.2) 0%, rgba(24, 144, 255, 0.1) 100%);
border-color: #1890ff;
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.2);
}
.type-btn.active::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 3px;
background: linear-gradient(90deg, #1890ff 0%, #36cfc9 100%);
}
/* 操作按钮容器样式 */
.action-buttons {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-top: 24px;
align-items: center;
padding: 20px;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-radius: 12px;
}
/* 审核时间样式 */
.audit-time {
padding: 12px 20px;
background: linear-gradient(135deg, #1890ff 0%, #36cfc9 100%);
color: white;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
text-align: center;
min-width: 200px;
margin-top: 0;
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* 手机端响应式样式 */
@media (max-width: 768px) {
/* 调整容器padding */
.container {
padding: 16px;
border-radius: 8px;
margin-top: 16px;
margin-bottom: 16px;
}
/* 调整标题大小 */
h1 {
font-size: 22px;
margin-bottom: 20px;
}
/* 调整状态导航栏 */
.status-nav {
overflow-x: auto;
white-space: nowrap;
padding: 4px;
border-radius: 8px;
}
.status-btn {
flex: 0 0 auto;
padding: 12px 16px;
font-size: 12px;
border-radius: 6px;
}
/* 调整类型导航栏 */
.type-nav {
margin: 16px 0;
padding: 4px;
border-radius: 8px;
}
.type-btn {
padding: 12px 16px;
font-size: 14px;
border-radius: 6px;
}
/* 调整搜索栏 */
.search-bar {
flex-direction: column;
gap: 12px;
padding: 20px;
border-radius: 12px;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05) inset;
}
.search-bar input {
width: 100%;
min-width: auto;
padding: 14px 16px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 14px;
transition: all 0.3s ease;
background-color: #fff;
}
.search-bar input:focus {
outline: none;
border-color: #1890ff;
box-shadow: 0 0 0 3px rgba(24, 144, 255, 0.1);
}
.search-bar button {
width: 100%;
padding: 14px 24px;
background: linear-gradient(135deg, #1890ff 0%, #36cfc9 100%);
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
position: relative;
overflow: hidden;
}
.search-bar button::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
transition: left 0.5s;
}
.search-bar button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(24, 144, 255, 0.4);
}
.search-bar button:hover::before {
left: 100%;
}
/* 调整供应商信息卡片 */
.supply-item {
padding: 20px;
margin-bottom: 20px;
border-radius: 8px;
}
/* 调整供应商基本信息 */
.supply-info {
grid-template-columns: 1fr;
gap: 16px;
padding: 16px;
border-radius: 8px;
}
/* 调整证明材料 */
.proof-materials {
grid-template-columns: 1fr;
gap: 16px;
}
/* 调整按钮布局 - 横向排列 */
.action-buttons {
flex-direction: column;
justify-content: center;
margin-top: 20px;
gap: 10px;
padding: 16px;
border-radius: 8px;
}
.action-buttons .btn {
flex: 1;
width: 100%;
padding: 12px 16px;
font-size: 14px;
text-align: center;
white-space: nowrap;
border-radius: 6px;
}
/* 调整审核时间样式 */
.audit-time {
min-width: auto;
width: 100%;
padding: 12px;
font-size: 14px;
margin-top: 10px;
order: 3;
border-radius: 6px;
}
/* 调整状态标签 */
.supply-status {
padding: 6px 12px;
font-size: 12px;
border-radius: 16px;
}
/* 调整字体大小 */
.supply-title {
font-size: 18px;
}
.info-label {
font-size: 12px;
}
.info-value {
font-size: 14px;
}
/* 调整分页按钮 */
.pagination {
padding: 16px;
border-radius: 8px;
gap: 6px;
}
.pagination button {
padding: 8px 12px;
font-size: 13px;
border-radius: 6px;
}
/* 调整模态框 */
.modal-content {
padding: 24px;
border-radius: 12px;
}
.modal-header {
font-size: 18px;
margin-bottom: 20px;
}
.modal-body textarea {
padding: 14px;
border-radius: 6px;
}
.modal-footer {
flex-direction: column;
gap: 10px;
}
.modal-footer .btn {
width: 100%;
padding: 12px;
border-radius: 6px;
}
}
/* 小屏幕手机的额外调整 */
@media (max-width: 480px) {
/* 调整按钮字体大小 */
.action-buttons .btn {
font-size: 13px;
padding: 10px 14px;
}
/* 调整标题大小 */
h1 {
font-size: 20px;
}
/* 调整类型按钮字体大小 */
.type-btn {
font-size: 12px;
padding: 10px 12px;
}
/* 调整状态按钮字体大小 */
.status-btn {
font-size: 11px;
padding: 10px 14px;
}
/* 调整容器padding */
.container {
padding: 12px;
}
/* 调整供应商卡片padding */
.supply-item {
padding: 16px;
}
}
/* 供应商状态样式 */
.status-underreview {
background: linear-gradient(135deg, #faad14 0%, #ffc53d 100%);
box-shadow: 0 2px 8px rgba(250, 173, 20, 0.3);
}
.status-reviewfailed {
background: linear-gradient(135deg, #ff4d4f 0%, #ff7875 100%);
box-shadow: 0 2px 8px rgba(255, 77, 79, 0.3);
}
.status-approved {
background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
box-shadow: 0 2px 8px rgba(82, 196, 26, 0.3);
}
.status-incooperation {
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 100%);
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.3);
}
.status-notcooperative {
background: linear-gradient(135deg, #d9d9d9 0%, #bfbfbf 100%);
box-shadow: 0 2px 8px rgba(217, 217, 217, 0.3);
}
/* 状态标签通用样式增强 */
.status-pending,
.status-published,
.status-rejected,
.status-soldout,
.status-hidden {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.status-pending {
background: linear-gradient(135deg, #faad14 0%, #ffc53d 100%);
}
.status-published {
background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
}
.status-rejected {
background: linear-gradient(135deg, #ff4d4f 0%, #ff7875 100%);
}
.status-soldout {
background: linear-gradient(135deg, #8c8c8c 0%, #bfbfbf 100%);
}
.status-hidden {
background: linear-gradient(135deg, #d9d9d9 0%, #bfbfbf 100%);
}
/* 证明材料展示样式 */
.proof-materials {
margin-top: 20px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 20px;
}
.proof-item {
border: 1px solid #e0e0e0;
border-radius: 12px;
padding: 16px;
transition: all 0.3s ease;
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}
.proof-item:hover {
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
border-color: #1890ff;
}
.proof-title {
font-size: 14px;
font-weight: 600;
color: #666;
margin-bottom: 12px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.proof-link {
color: #1890ff;
text-decoration: none;
font-size: 14px;
font-weight: 500;
word-break: break-all;
transition: all 0.3s ease;
display: inline-block;
padding: 8px 12px;
background-color: rgba(24, 144, 255, 0.1);
border-radius: 6px;
}
.proof-link:hover {
text-decoration: none;
color: #40a9ff;
background-color: rgba(24, 144, 255, 0.2);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.2);
}
</style>
</head>
<body>
<!-- 导航栏 -->
<nav class="navbar">
<div class="navbar-container">
<div class="navbar-brand">
<div class="navbar-logo"></div>
<div class="navbar-title">供应商审核系统</div>
</div>
<div class="navbar-links">
<a href="SupplierReview.html" class="navbar-link active">供应商审核</a>
<a href="supply.html" class="navbar-link">我的货源</a>
<div id="userInfo" style="display: flex; align-items: center; gap: 12px;">
<span id="userName" style="color: white;">未登录</span>
<button id="logoutBtn" style="padding: 6px 12px; background: rgba(255, 255, 255, 0.2); color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 12px; transition: all 0.3s ease;">退出登录</button>
</div>
</div>
</div>
</nav>
<div class="container">
<!-- 统计卡片 -->
<div class="stats-container fade-in">
<div class="stats-card">
<div class="stats-number" id="totalSuppliers">0</div>
<div class="stats-label">总供应商数</div>
</div>
<div class="stats-card">
<div class="stats-number" id="pendingSuppliers">0</div>
<div class="stats-label">审核中</div>
</div>
<div class="stats-card">
<div class="stats-number" id="approvedSuppliers">0</div>
<div class="stats-label">已通过</div>
</div>
<div class="stats-card">
<div class="stats-number" id="rejectedSuppliers">0</div>
<div class="stats-label">已拒绝</div>
</div>
</div>
<!-- 审核类型切换器 -->
<div class="type-nav" style="
display: flex;
margin-bottom: 20px;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-radius: 12px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
overflow: hidden;
">
<button class="type-btn" id="managementBtn" style="border-right: 1px solid rgba(0, 0, 0, 0.1);" onclick="window.location.href='Management.html'">货源管理</button>
<button class="type-btn active" style="border-left: 1px solid rgba(0, 0, 0, 0.1);">供应商审核</button>
<button class="type-btn" onclick="window.location.href='supply.html'">我的货源</button>
</div>
<div class="search-bar">
<input type="text" id="searchInput" placeholder="搜索供应商名称或ID...">
<input type="text" id="phoneInput" placeholder="按手机号筛选...">
<button id="searchBtn">搜索</button>
<button id="refreshBtn">刷新</button>
</div>
<!-- 状态导航栏 - 供应商 -->
<div class="status-nav supplier-status-nav">
<button class="status-btn active" data-status="underreview">未跟进</button>
<button class="status-btn" data-status="approved">已跟进</button>
</div>
<!-- 总数显示 -->
<div class="total-count" style="
padding: 10px 15px;
background-color: #f5f5f5;
border-radius: 4px;
margin-bottom: 15px;
font-size: 14px;
color: #666;
">
总共 <span id="totalCount" style="color: #1890ff; font-weight: bold;">0</span> 个供应商
</div>
<div id="supplyList">
<div class="loading">加载中...</div>
</div>
<div id="pagination" class="pagination">
<!-- 分页按钮将由JS动态生成 -->
</div>
</div>
<!-- 拒绝理由弹窗 -->
<div id="rejectModal" class="modal">
<div class="modal-content">
<div class="modal-header">拒绝原因</div>
<div class="modal-body">
<textarea id="rejectReason" placeholder="请输入拒绝理由..." maxlength="500"></textarea>
<div style="text-align: right; margin-top: 5px; font-size: 12px; color: #999;">
<span id="charCount">0</span>/500
</div>
</div>
<div class="modal-footer">
<button id="cancelRejectBtn" class="btn btn-default">取消</button>
<button id="confirmRejectBtn" class="btn btn-danger">确认拒绝</button>
</div>
</div>
</div>
<!-- 通过确认弹窗 -->
<div id="approveModal" class="modal">
<div class="modal-content">
<div class="modal-header">确认通过</div>
<div class="modal-body" style="text-align: center; padding: 20px 0;">
<p style="font-size: 16px; margin: 0;">确定要通过该供应商吗?</p>
</div>
<div class="modal-footer">
<button id="cancelApproveBtn" class="btn btn-default">取消</button>
<button id="confirmApproveBtn" class="btn btn-primary">确定</button>
</div>
</div>
</div>
<!-- 成功提示弹窗 -->
<div id="successModal" class="modal">
<div class="modal-content">
<div class="modal-header">操作成功</div>
<div class="modal-body" style="text-align: center; padding: 20px 0;">
<p id="successMessage" style="font-size: 16px; margin: 0;"></p>
</div>
<div class="modal-footer">
<button id="successConfirmBtn" class="btn btn-primary">确定</button>
</div>
</div>
</div>
<!-- 终止合作弹窗 -->
<div id="terminateModal" class="modal">
<div class="modal-content">
<div class="modal-header">终止合作</div>
<div class="modal-body">
<textarea id="terminateReason" placeholder="请输入终止合作的理由..." maxlength="500"></textarea>
<div style="text-align: right; margin-top: 5px; font-size: 12px; color: #999;">
<span id="terminateCharCount">0</span>/500
</div>
</div>
<div class="modal-footer">
<button id="cancelTerminateBtn" class="btn btn-default">取消</button>
<button id="confirmTerminateBtn" class="btn btn-danger">确认终止</button>
</div>
</div>
</div>
<!-- 跟进信息弹窗 -->
<div id="followupModal" class="modal">
<div class="modal-content">
<div class="modal-header">供应商跟进</div>
<div class="modal-body">
<textarea id="followupContent" placeholder="请输入跟进信息..." maxlength="1000" style="height: 150px;"></textarea>
<div style="text-align: right; margin-top: 5px; font-size: 12px; color: #999;">
<span id="followupCharCount">0</span>/1000
</div>
</div>
<div class="modal-footer">
<button id="cancelFollowupBtn" class="btn btn-default">取消</button>
<button id="confirmFollowupBtn" class="btn btn-primary">确认保存</button>
</div>
</div>
</div>
<script>
// 登录检查
function checkLogin() {
const userInfo = localStorage.getItem('userInfo');
const token = localStorage.getItem('token');
if (!userInfo || !token) {
// 未登录,跳转到登录页面
window.location.href = 'login.html';
return false;
}
// 已登录,解析用户信息
const parsedUserInfo = JSON.parse(userInfo);
// 展示登录信息
const userNameEl = document.getElementById('userName');
userNameEl.textContent = `${parsedUserInfo.projectName} - ${parsedUserInfo.name} (${parsedUserInfo.phoneNumber})`;
// 绑定退出登录事件
const logoutBtn = document.getElementById('logoutBtn');
logoutBtn.addEventListener('click', () => {
// 清除登录信息
localStorage.removeItem('userInfo');
localStorage.removeItem('token');
// 跳转到登录页面
window.location.href = 'login.html';
});
// 根据角色控制货源管理按钮的显示
const managementBtnEl = document.getElementById('managementBtn');
if (managementBtnEl) {
if (parsedUserInfo.projectName === '管理员') {
// 管理员显示按钮
managementBtnEl.style.display = 'block';
} else {
// 非管理员隐藏按钮
managementBtnEl.style.display = 'none';
}
}
return true;
}
// 当前页码和每页显示数量
let currentPage = 1;
const pageSize = 10;
// 当前选中的供应商ID
let currentSupplierId = null;
// 当前选中的状态
let currentStatus = 'underreview';
// 当前审核类型:默认供应商审核
let currentType = 'supplier';
// 联系人数据
let contacts = [];
// 更新统计卡片数据
function updateStatistics(suppliers) {
const totalSuppliersEl = document.getElementById('totalSuppliers');
const pendingSuppliersEl = document.getElementById('pendingSuppliers');
const approvedSuppliersEl = document.getElementById('approvedSuppliers');
const rejectedSuppliersEl = document.getElementById('rejectedSuppliers');
if (totalSuppliersEl) {
totalSuppliersEl.textContent = suppliers.length;
}
if (pendingSuppliersEl) {
const pendingCount = suppliers.filter(s =>
s.partnerstatus === 'underreview' ||
s.status === 'underreview'
).length;
pendingSuppliersEl.textContent = pendingCount;
}
if (approvedSuppliersEl) {
const approvedCount = suppliers.filter(s =>
s.partnerstatus === 'approved' ||
s.status === 'approved'
).length;
approvedSuppliersEl.textContent = approvedCount;
}
if (rejectedSuppliersEl) {
const rejectedCount = suppliers.filter(s =>
s.partnerstatus === 'reviewfailed' ||
s.status === 'reviewfailed'
).length;
rejectedSuppliersEl.textContent = rejectedCount;
}
}
// DOM元素
const supplyListEl = document.getElementById('supplyList');
const searchInputEl = document.getElementById('searchInput');
const phoneInputEl = document.getElementById('phoneInput');
const searchBtnEl = document.getElementById('searchBtn');
const refreshBtnEl = document.getElementById('refreshBtn');
const rejectModalEl = document.getElementById('rejectModal');
const rejectReasonEl = document.getElementById('rejectReason');
const cancelRejectBtnEl = document.getElementById('cancelRejectBtn');
const confirmRejectBtnEl = document.getElementById('confirmRejectBtn');
const approveModalEl = document.getElementById('approveModal');
const cancelApproveBtnEl = document.getElementById('cancelApproveBtn');
const confirmApproveBtnEl = document.getElementById('confirmApproveBtn');
const paginationEl = document.getElementById('pagination');
const charCountEl = document.getElementById('charCount');
const statusBtns = document.querySelectorAll('.status-btn');
// 初始化页面
window.onload = () => {
// 登录检查
if (!checkLogin()) {
return;
}
initPage();
bindEvents();
};
// 初始化页面函数
async function initPage() {
currentPage = 1;
currentStatus = 'underreview';
loadSuppliers();
}
// 绑定事件
function bindEvents() {
// 搜索相关事件
searchBtnEl.addEventListener('click', () => {
currentPage = 1;
handleSearch();
});
refreshBtnEl.addEventListener('click', () => {
searchInputEl.value = '';
currentPage = 1;
handleRefresh();
});
// 状态导航栏点击事件
statusBtns.forEach(btn => {
btn.addEventListener('click', () => {
// 更新同类状态按钮的active状态
const sameGroupBtns = btn.closest('.status-nav') ?
btn.closest('.status-nav').querySelectorAll('button') :
[];
sameGroupBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
// 更新当前状态并重新加载数据
currentStatus = btn.getAttribute('data-status');
currentPage = 1;
loadSuppliers();
});
});
searchInputEl.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
currentPage = 1;
handleSearch();
}
});
// 拒绝模态框相关事件
confirmRejectBtnEl.addEventListener('click', function (e) {
e.preventDefault();
confirmReject();
});
cancelRejectBtnEl.addEventListener('click', function (e) {
e.preventDefault();
closeRejectModal();
});
// 通过模态框相关事件
confirmApproveBtnEl.addEventListener('click', function (e) {
e.preventDefault();
confirmApprove();
});
cancelApproveBtnEl.addEventListener('click', function (e) {
e.preventDefault();
closeApproveModal();
});
rejectReasonEl.addEventListener('input', () => {
const length = rejectReasonEl.value.length;
charCountEl.textContent = length;
charCountEl.style.color = length > 400 ? '#ff4d4f' : '#999';
});
// 点击模态框外部关闭模态框
rejectModalEl.addEventListener('click', (e) => {
if (e.target === rejectModalEl) {
closeRejectModal();
}
});
// 为模态框添加键盘事件
document.addEventListener('keydown', (e) => {
if (rejectModalEl.style.display === 'flex') {
if (e.key === 'Escape') {
closeRejectModal();
} else if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
confirmReject();
}
}
});
// 为操作按钮添加事件委托
document.addEventListener('click', (e) => {
// 首先检查点击是否在图片区域内,如果是则不执行按钮点击逻辑
if (e.target.tagName === 'IMG' || e.target.closest('.supply-images') ||
e.target.closest('.image-viewer') || e.target.id === 'viewerImage') {
return;
}
// 按钮事件处理 - 供应商审核操作
if (e.target.classList.contains('btn-primary') && e.target.closest('.action-buttons')) {
const id = e.target.getAttribute('data-id');
if (id) {
// 检查按钮文本或样式来区分操作类型
if (e.target.textContent.trim() === '开始合作') {
confirmSupplierToCooperation(id);
} else {
showSupplierApproveModal(id);
}
}
}
// 拒绝/终止按钮
if (e.target.classList.contains('btn-danger') && e.target.closest('.action-buttons')) {
const id = e.target.getAttribute('data-id');
if (id) {
// 检查按钮文本或样式来区分操作类型
if (e.target.textContent.trim() === '终止合作') {
showTerminateModal(id);
} else {
showSupplierRejectModal(id);
}
}
}
// 跟进按钮
if (e.target.classList.contains('btn-default') && e.target.closest('.action-buttons')) {
const id = e.target.getAttribute('data-id');
if (id && e.target.textContent.trim() === '跟进') {
showFollowupModal(id);
}
}
});
}
// 处理搜索
function handleSearch() {
loadSuppliers();
}
// 处理刷新
function handleRefresh() {
loadSuppliers();
}
// 显示拒绝理由弹窗
function showRejectModal(supplierId) {
currentSupplierId = supplierId;
rejectReasonEl.value = '';
charCountEl.textContent = '0';
charCountEl.style.color = '#999';
rejectModalEl.style.display = 'flex';
rejectReasonEl.focus();
}
// 关闭拒绝理由弹窗
function closeRejectModal() {
rejectModalEl.style.display = 'none';
currentSupplierId = null;
}
// 确认拒绝
async function confirmReject() {
const reason = rejectReasonEl.value.trim();
if (!reason) {
alert('请输入拒绝理由');
return;
}
if (reason.length > 500) {
alert('拒绝理由不能超过500个字符');
return;
}
try {
let apiUrl = `/api/suppliers/${currentSupplierId}/reject?_t=${Date.now()}`;
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
'Expires': '0'
},
body: JSON.stringify({ rejectReason: reason })
});
if (!response.ok) {
throw new Error('服务器响应异常');
}
const data = await response.json();
if (data.success) {
closeRejectModal();
loadSuppliers();
showSuccessModal('拒绝成功');
} else {
alert(data.message || '拒绝失败');
}
} catch (error) {
console.error('拒绝供应商失败:', error);
alert('网络错误,请稍后重试');
}
}
// 显示通过确认弹窗
function showApproveModal(supplierId) {
currentSupplierId = supplierId;
approveModalEl.style.display = 'flex';
}
// 关闭通过确认弹窗
function closeApproveModal() {
approveModalEl.style.display = 'none';
currentSupplierId = null;
}
// 显示成功提示弹窗
function showSuccessModal(message) {
const successModalEl = document.getElementById('successModal');
const successMessageEl = document.getElementById('successMessage');
successMessageEl.textContent = message;
successModalEl.style.display = 'flex';
}
// 关闭成功提示弹窗
function closeSuccessModal() {
const successModalEl = document.getElementById('successModal');
successModalEl.style.display = 'none';
}
// 确认通过
async function confirmApprove() {
try {
const apiUrl = `/api/suppliers/${currentSupplierId}/approve`;
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({})
});
if (!response.ok) {
throw new Error('服务器响应异常');
}
const data = await response.json();
if (data.success) {
closeApproveModal();
loadSuppliers();
showSuccessModal('供应商通过成功');
} else {
alert(data.message || '通过失败');
}
} catch (error) {
console.error('批准供应商失败:', error);
alert('网络错误,请稍后重试');
}
}
// 格式化时间
function formatTime(timeStr) {
if (!timeStr) return '未知';
const date = new Date(timeStr);
if (isNaN(date.getTime())) return '未知';
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}/${month}/${day} ${hours}:${minutes}`;
}
// 处理图片加载错误
function handleImageError(imgElement, productId) {
// 防止重复触发错误处理
if (imgElement.dataset.errorHandled) return;
imgElement.dataset.errorHandled = 'true';
// 使用内联SVG数据URI作为备用占位图
const hue = (productId || 0) * 37 % 360;
const color = `hsl(${hue}, 60%, 70%)`;
const errorTextColor = '#ff4d4f';
imgElement.src = `data:image/svg+xml,${encodeURIComponent(`
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600">
<rect width="600" height="600" fill="${color}"/>
<text x="300" y="300" font-family="Arial" font-size="24" fill="${errorTextColor}" text-anchor="middle" dominant-baseline="middle">图片加载失败</text>
</svg>
`)}`;
// 确保错误图片可见
imgElement.style.display = 'block';
imgElement.style.opacity = '1';
imgElement.style.border = '2px solid red';
// 添加错误标记
imgElement.classList.add('image-error');
}
// 隐藏图片骨架屏
function hideImageSkeleton(imgElement) {
const skeleton = imgElement.parentElement.querySelector('.image-skeleton');
if (skeleton) {
skeleton.style.display = 'none';
}
// 确保图片可见
imgElement.style.display = 'block';
imgElement.style.opacity = '1';
}
// 供应商相关数据处理函数
// 加载供应商列表
async function loadSuppliers() {
supplyListEl.innerHTML = '<div class="loading">加载中...</div>';
try {
const searchKeyword = searchInputEl.value.trim();
const phoneNumber = phoneInputEl.value.trim();
// 获取当前登录者信息
const userInfo = localStorage.getItem('userInfo');
const parsedUserInfo = JSON.parse(userInfo);
const currentUserName = parsedUserInfo.name;
const currentUserPhone = parsedUserInfo.phoneNumber;
const isAdmin = parsedUserInfo.projectName === '管理员';
// 构建查询参数,添加时间戳防止缓存
let queryParams = `page=${currentPage}&pageSize=${pageSize}&status=${currentStatus}&_t=${Date.now()}`;
if (searchKeyword) {
queryParams += `&keyword=${encodeURIComponent(searchKeyword)}`;
}
if (phoneNumber) {
queryParams += `&phoneNumber=${encodeURIComponent(phoneNumber)}`;
}
// 添加当前登录者信息,用于对接人匹配(非管理员才需要)
if (!isAdmin) {
queryParams += `&currentUserName=${encodeURIComponent(currentUserName)}`;
queryParams += `&currentUserPhone=${encodeURIComponent(currentUserPhone)}`;
}
// 使用相对路径,避免硬编码地址导致的跨电脑访问问题
const response = await fetch(`/api/suppliers?${queryParams}`);
if (!response.ok) {
throw new Error('服务器响应异常');
}
// 直接解析为JSON
let data;
try {
data = await response.json();
} catch (parseError) {
throw new Error('返回数据格式错误');
}
// 宽松处理,适应多种可能的数据格式
let suppliersList = [];
let totalCount = 0;
// 情况1: 标准格式 {success: true, data: {list: [], total: n}} - 后端实际返回格式
if (data.success === true && data.data && data.data.list && Array.isArray(data.data.list)) {
suppliersList = data.data.list;
totalCount = data.data.total || suppliersList.length;
console.log('情况1 - 后端实际返回格式: 从data.data.list中获取了', suppliersList.length, '条记录');
}
// 情况2: 标准格式 {success: true, data: {suppliers: [], total: n}}
else if (data.success === true && data.data && data.data.suppliers && Array.isArray(data.data.suppliers)) {
suppliersList = data.data.suppliers;
totalCount = data.data.total || suppliersList.length;
}
// 情况2: 简化格式 {data: []} 或 {suppliers: []}
else if (data.data && Array.isArray(data.data)) {
suppliersList = data.data;
totalCount = suppliersList.length;
}
else if (data.suppliers && Array.isArray(data.suppliers)) {
suppliersList = data.suppliers;
totalCount = data.total || suppliersList.length;
}
// 情况3: 直接返回数组
else if (Array.isArray(data)) {
suppliersList = data;
totalCount = suppliersList.length;
}
// 更新总数显示
const totalCountEl = document.getElementById('totalCount');
if (totalCountEl) {
totalCountEl.textContent = totalCount;
}
// 更新统计卡片数据
updateStatistics(suppliersList);
// 对供应商列表进行排序,按照创建时间倒序排列(最新的在前)
suppliersList.sort((a, b) => {
// 获取创建时间,支持多种可能的字段名称
const aCreatedAt = a.created_at || a.create_time || a.createdAt || new Date().toISOString();
const bCreatedAt = b.created_at || b.create_time || b.createdAt || new Date().toISOString();
// 转换为时间戳并比较,降序排列
return new Date(bCreatedAt).getTime() - new Date(aCreatedAt).getTime();
});
// 渲染数据
renderSuppliers(suppliersList);
renderPagination(totalCount, currentPage, Math.ceil(totalCount / pageSize));
} catch (error) {
console.error('加载供应商失败:', error);
let errorMessage = '加载失败,请稍后重试';
if (error.message.includes('JSON解析失败')) {
errorMessage = '数据格式错误,可能是服务器响应异常';
} else if (error.message.includes('服务器响应异常')) {
errorMessage = '服务器连接失败,请检查网络';
}
supplyListEl.innerHTML = `<div class="empty-state">
<div class="empty-icon">⚠️</div>
<h3>加载失败</h3>
<p>${errorMessage}</p>
<button class="btn-primary" onclick="loadSuppliers()">重新加载</button>
</div>`;
}
}
// 渲染供应商列表
function renderSuppliers(suppliers) {
console.log('渲染供应商数据类型:', typeof suppliers, '数据是否为数组:', Array.isArray(suppliers));
console.log('渲染供应商数据详情:', JSON.stringify(suppliers, null, 2));
// 确保suppliers是数组
if (!suppliers || !Array.isArray(suppliers)) {
console.error('suppliers不是有效的数组:', suppliers);
supplyListEl.innerHTML = `<div class="empty-state">
<p>数据格式错误,请刷新页面重试</p>
</div>`;
return;
}
if (suppliers.length === 0) {
console.log('没有供应商数据可渲染');
supplyListEl.innerHTML = `<div class="empty-state">
<div class="empty-icon">👥</div>
<h3>暂无供应商数据</h3>
<p>${searchInputEl.value.trim() || phoneInputEl.value.trim() ? '没有找到匹配的供应商' : '当前没有供应商数据'}</p>
<button class="btn-primary" onclick="loadSuppliers()">刷新列表</button>
</div>`;
return;
}
supplyListEl.innerHTML = suppliers.map((supplier, index) => {
// 使用更灵活的字段映射
const id = supplier.userId || supplier.id || supplier.user_id || `unknown-${index}`;
const name = supplier.userName || supplier.name || supplier.company || '未命名供应商';
const phone = supplier.phoneNumber || supplier.phone || '未知电话';
const company = supplier.company || '未设置公司';
const collaborationId = supplier.collaborationid || '未知合作商ID';
const cooperation = supplier.cooperation || '未知合作模式';
const liaison = supplier.liaison || '未设置对接人';
// 地址信息
const province = supplier.province || '';
const city = supplier.city || '';
const district = supplier.district || '';
const detailedAddress = supplier.detailedaddress || '';
const fullAddress = generateAddress(province, city, district, detailedAddress);
// 证明材料
const businessLicenseUrl = supplier.businesslicenseurl || '';
const proofUrl = supplier.proofurl || '';
const brandUrl = supplier.brandurl || '';
// 状态
const status = supplier.partnerstatus || supplier.status || 'underreview';
const createdAt = supplier.created_at || supplier.create_time || new Date().toISOString();
const rejectReason = supplier.rejectReason || supplier.reject_reason || supplier.reason || '';
const auditTime = supplier.audit_time || createdAt;
// 状态处理
let statusClass = 'status-underreview';
let statusText = '审核中';
if (status === 'reviewfailed' || status === '已拒绝') {
statusClass = 'status-reviewfailed';
statusText = '审核失败';
} else if (status === 'approved' || status === '已通过') {
statusClass = 'status-approved';
statusText = '审核通过';
} else if (status === 'incooperation' || status === '合作中') {
statusClass = 'status-incooperation';
statusText = '合作中';
} else if (status === 'notcooperative' || status === '未合作') {
statusClass = 'status-notcooperative';
statusText = '未合作';
}
// 审核按钮控制
const canReview = status === 'underreview';
const canTerminate = status === 'incooperation';
const canCooperate = status === 'approved';
return `
<div class="supply-item" data-id="${id}">
<div class="supply-header">
<div class="supply-title">${company}</div>
<div class="supply-status ${statusClass}">${statusText}</div>
</div>
<!-- 供应商基本信息 -->
<div class="supply-info">
<div class="info-item">
<div class="info-label">合作商ID</div>
<div class="info-value">${collaborationId}</div>
<br>
<div class="info-label">合作模式</div>
<div class="info-value">${cooperation}</div>
</div>
<div class="info-item">
<div class="info-label">联系人</div>
<div class="info-value">${name}</div>
<br>
<div class="info-label">电话</div>
<div class="info-value">${phone}</div>
</div>
<div class="info-item">
<div class="info-label">地址</div>
<div class="info-value">${fullAddress}</div>
<br>
<div class="info-label">对接人</div>
<div class="info-value">${liaison}</div>
</div>
<div class="info-item">
<div class="info-label">创建时间</div>
<div class="info-value">${formatTime(createdAt)}</div>
<br>
<div class="info-label">ID</div>
<div class="info-value">${id}</div>
</div>
</div>
<!-- 证明材料展示 -->
${generateProofMaterialsHtml(businessLicenseUrl, proofUrl, brandUrl)}
<!-- 拒绝理由 -->
${rejectReason ? `
<div class="supply-reject-reason">
<div class="label">拒绝理由:</div>
<div class="reason">${rejectReason}</div>
</div>
` : ''}
<!-- 跟进信息 -->
${supplier.seller_followup ? `
<div class="supply-followup" style="margin-top: 15px; padding: 10px; background-color: #f0f9ff; border: 1px solid #bae7ff; border-radius: 4px;">
<div class="label" style="font-size: 12px; color: #1890ff; margin-bottom: 5px;">跟进信息:</div>
<div class="followup-content" style="font-size: 14px; color: #333;">${supplier.seller_followup}</div>
</div>
` : ''}
<!-- 操作按钮 -->
<div class="action-buttons">
${canReview ? `
<button class="btn btn-default" data-id="${id}">
跟进
</button>
<button class="btn btn-primary" data-id="${id}">
通过
</button>
<button class="btn btn-danger" data-id="${id}">
拒绝
</button>
` : canTerminate ? `
<button class="btn btn-default" data-id="${id}">
跟进
</button>
<button class="btn btn-danger" data-id="${id}">
终止合作
</button>
<div class="audit-time">
审核时间:${formatTime(auditTime)}
</div>
` : canCooperate ? `
<button class="btn btn-default" data-id="${id}">
跟进
</button>
<button class="btn btn-primary" data-id="${id}">
开始合作
</button>
<div class="audit-time">
审核时间:${formatTime(auditTime)}
</div>
` : `
<button class="btn btn-default" data-id="${id}">
跟进
</button>
<div class="audit-time">
审核时间:${formatTime(auditTime)}
</div>
`}
</div>
</div>
`;
}).join('');
}
// 生成地址信息
function generateAddress(province, city, district, detailedAddress) {
const addressParts = [province, city, district, detailedAddress].filter(Boolean);
return addressParts.length > 0 ? addressParts.join('') : '未设置地址';
}
// 生成证明材料HTML
function generateProofMaterialsHtml(businessLicenseUrl, proofUrl, brandUrl) {
let materials = [];
// 检查是否为图片文件的函数
function isImageUrl(url) {
if (!url) return false;
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'];
const lowerUrl = url.toLowerCase();
return imageExtensions.some(ext => lowerUrl.includes(ext));
}
// 处理单个URL的函数
function processUrl(url, title) {
if (!url) return '';
// 处理多个URL的情况(用逗号分隔)
const urls = url.split(',').map(u => u.trim()).filter(u => u);
if (urls.length === 0) return '';
let itemsHtml = '';
urls.forEach((singleUrl, index) => {
const displayIndex = urls.length > 1 ? ` (${index + 1})` : '';
if (isImageUrl(singleUrl)) {
// 图片类型,显示预览图
itemsHtml += `
<div class="proof-item">
<div class="proof-title">${title}${displayIndex}</div>
<div class="image-preview" style="margin: 10px 0; position: relative; display: inline-block;">
<!-- 添加data-product-image属性以支持缩放功能 -->
<img src="${singleUrl}" alt="${title}" onclick="openImageViewer(this)" class="preview-thumbnail" style="max-width: 200px; max-height: 200px; cursor: pointer; border: 1px solid #eee; border-radius: 4px;" data-product-image="true">
<div class="image-overlay" onclick="openImageViewer(this.previousElementSibling)" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.3); display: flex; align-items: center; justify-content: center; opacity: 0; transition: opacity 0.3s; border-radius: 4px; box-sizing: border-box;">
<span style="color: white; font-size: 14px;">点击查看</span>
</div>
</div>
<a href="${singleUrl}" target="_blank" class="proof-link">下载原图</a>
</div>
`;
} else {
// 非图片类型,仅提供链接
itemsHtml += `
<div class="proof-item">
<div class="proof-title">${title}${displayIndex}</div>
<a href="${singleUrl}" target="_blank" class="proof-link">查看${title}</a>
</div>
`;
}
});
return itemsHtml;
}
materials.push(processUrl(businessLicenseUrl, '营业执照'));
materials.push(processUrl(proofUrl, '证明材料'));
materials.push(processUrl(brandUrl, '品牌授权链'));
// 过滤空字符串
materials = materials.filter(m => m);
if (materials.length === 0) {
return '';
}
return `
<div class="proof-materials" style="margin-top: 20px; padding-top: 20px; border-top: 1px solid #eee;">
${materials.join('')}
</div>
`;
}
// 供应商相关模态框函数
// 显示供应商通过确认弹窗
function showSupplierApproveModal(supplierId) {
currentSupplierId = supplierId;
// 检查供应商是否有跟进信息
const supplyItem = document.querySelector(`.supply-item[data-id="${supplierId}"]`);
if (supplyItem) {
const hasFollowup = supplyItem.querySelector('.supply-followup') !== null;
// 如果没有跟进信息,提示用户需要先跟进并直接弹出跟进弹窗
if (!hasFollowup) {
alert('该供应商尚未跟进,需要先添加跟进信息才能通过!');
showFollowupModal(supplierId);
return;
}
}
approveModalEl.style.display = 'flex';
}
// 显示供应商拒绝理由弹窗
function showSupplierRejectModal(supplierId) {
currentSupplierId = supplierId;
rejectReasonEl.value = '';
charCountEl.textContent = '0';
charCountEl.style.color = '#999';
rejectModalEl.style.display = 'flex';
rejectReasonEl.focus();
}
// 显示终止合作弹窗
function showTerminateModal(supplierId) {
currentSupplierId = supplierId;
// 使用终止合作专用的文本框和字符计数
const terminateReasonEl = document.getElementById('terminateReason');
const terminateCharCountEl = document.getElementById('terminateCharCount');
if (terminateReasonEl) {
terminateReasonEl.value = '';
terminateReasonEl.focus();
}
if (terminateCharCountEl) {
terminateCharCountEl.textContent = '0';
}
const terminateModalEl = document.getElementById('terminateModal');
if (terminateModalEl) {
terminateModalEl.style.display = 'flex';
}
}
// 确认终止合作
async function confirmTerminate() {
const terminateReasonEl = document.getElementById('terminateReason');
const terminateModalEl = document.getElementById('terminateModal');
const reason = terminateReasonEl ? terminateReasonEl.value.trim() : '';
if (!reason) {
alert('请输入终止合作理由');
return;
}
try {
const response = await fetch(`/api/suppliers/${currentSupplierId}/terminate?_t=${Date.now()}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ reason })
});
if (!response.ok) {
throw new Error('服务器响应异常');
}
const data = await response.json();
if (data.success) {
if (terminateModalEl) {
terminateModalEl.style.display = 'none';
}
loadSuppliers();
showSuccessModal('终止合作成功');
} else {
alert(data.message || '终止合作失败');
}
} catch (error) {
console.error('终止合作失败:', error);
alert('网络错误,请稍后重试');
}
}
// 确认供应商开始合作
async function confirmSupplierToCooperation(supplierId) {
try {
const response = await fetch(`/api/suppliers/${supplierId}/cooperate?_t=${Date.now()}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
});
if (!response.ok) {
throw new Error('服务器响应异常');
}
const data = await response.json();
if (data.success) {
loadSuppliers();
showSuccessModal('开始合作成功');
} else {
alert(data.message || '开始合作失败');
}
} catch (error) {
console.error('开始合作失败:', error);
alert('网络错误,请稍后重试');
}
}
// 显示跟进信息弹窗
function showFollowupModal(supplierId) {
currentSupplierId = supplierId;
const followupModalEl = document.getElementById('followupModal');
const followupContentEl = document.getElementById('followupContent');
const followupCharCountEl = document.getElementById('followupCharCount');
if (followupContentEl) {
followupContentEl.value = '';
followupContentEl.focus();
}
if (followupCharCountEl) {
followupCharCountEl.textContent = '0';
}
if (followupModalEl) {
followupModalEl.style.display = 'flex';
}
}
// 确认保存跟进信息
async function confirmFollowup() {
const followupContentEl = document.getElementById('followupContent');
const followupModalEl = document.getElementById('followupModal');
const content = followupContentEl ? followupContentEl.value.trim() : '';
if (!content) {
alert('请输入跟进信息');
return;
}
try {
const response = await fetch(`/api/suppliers/${currentSupplierId}/followup?_t=${Date.now()}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ content })
});
if (!response.ok) {
throw new Error('服务器响应异常');
}
const data = await response.json();
if (data.success) {
if (followupModalEl) {
followupModalEl.style.display = 'none';
}
loadSuppliers();
showSuccessModal('跟进信息保存成功');
} else {
alert(data.message || '保存跟进信息失败');
}
} catch (error) {
console.error('保存跟进信息失败:', error);
alert('网络错误,请稍后重试');
}
}
// 为终止合作模态框添加事件监听
const terminateModalEl = document.getElementById('terminateModal');
if (terminateModalEl) {
const terminateReasonEl = document.getElementById('terminateReason');
const terminateCharCountEl = document.getElementById('terminateCharCount');
const cancelTerminateBtnEl = document.getElementById('cancelTerminateBtn');
const confirmTerminateBtnEl = document.getElementById('confirmTerminateBtn');
// 字符计数
if (terminateReasonEl && terminateCharCountEl) {
terminateReasonEl.addEventListener('input', () => {
const length = terminateReasonEl.value.length;
terminateCharCountEl.textContent = length;
terminateCharCountEl.style.color = length > 400 ? '#ff4d4f' : '#999';
});
}
// 取消按钮
if (cancelTerminateBtnEl) {
cancelTerminateBtnEl.addEventListener('click', function(e) {
e.preventDefault();
terminateModalEl.style.display = 'none';
});
}
// 确认终止按钮
if (confirmTerminateBtnEl) {
confirmTerminateBtnEl.addEventListener('click', function(e) {
e.preventDefault();
confirmTerminate();
});
}
// 点击模态框外部关闭
terminateModalEl.addEventListener('click', (e) => {
if (e.target === terminateModalEl) {
terminateModalEl.style.display = 'none';
}
});
}
// 为跟进信息模态框添加事件监听
const followupModalEl = document.getElementById('followupModal');
if (followupModalEl) {
const followupContentEl = document.getElementById('followupContent');
const followupCharCountEl = document.getElementById('followupCharCount');
const cancelFollowupBtnEl = document.getElementById('cancelFollowupBtn');
const confirmFollowupBtnEl = document.getElementById('confirmFollowupBtn');
// 字符计数
if (followupContentEl && followupCharCountEl) {
followupContentEl.addEventListener('input', () => {
const length = followupContentEl.value.length;
followupCharCountEl.textContent = length;
followupCharCountEl.style.color = length > 800 ? '#ff4d4f' : '#999';
});
}
// 取消按钮
if (cancelFollowupBtnEl) {
cancelFollowupBtnEl.addEventListener('click', function(e) {
e.preventDefault();
followupModalEl.style.display = 'none';
});
}
// 确认保存按钮
if (confirmFollowupBtnEl) {
confirmFollowupBtnEl.addEventListener('click', function(e) {
e.preventDefault();
confirmFollowup();
});
}
// 点击模态框外部关闭
followupModalEl.addEventListener('click', (e) => {
if (e.target === followupModalEl) {
followupModalEl.style.display = 'none';
}
});
}
// 绑定成功弹窗的确认按钮
document.getElementById('successConfirmBtn').addEventListener('click', closeSuccessModal);
// 渲染分页
function renderPagination(total, current, totalPages) {
let paginationHTML = '';
// 上一页
paginationHTML += `<button onclick="changePage(${current - 1})" ${current === 1 ? 'disabled' : ''}>
上一页
</button>`;
// 页码按钮
const startPage = Math.max(1, current - 2);
const endPage = Math.min(totalPages, startPage + 4);
for (let i = startPage; i <= endPage; i++) {
paginationHTML += `<button onclick="changePage(${i})" ${i === current ? 'class="active"' : ''}>
${i}
</button>`;
}
// 下一页
paginationHTML += `<button onclick="changePage(${current + 1})" ${current === totalPages ? 'disabled' : ''}>
下一页
</button>`;
paginationEl.innerHTML = paginationHTML;
}
// 改变页码
function changePage(page) {
if (page < 1) return;
currentPage = page;
loadSuppliers();
}
// 将函数暴露到全局
window.showApproveModal = showApproveModal;
window.closeApproveModal = closeApproveModal;
window.confirmApprove = confirmApprove;
window.showRejectModal = showRejectModal;
window.closeRejectModal = closeRejectModal;
window.confirmReject = confirmReject;
window.changePage = changePage;
window.showSupplierApproveModal = showSupplierApproveModal;
window.showSupplierRejectModal = showSupplierRejectModal;
window.showTerminateModal = showTerminateModal;
window.confirmTerminate = confirmTerminate;
window.confirmSupplierToCooperation = confirmSupplierToCooperation;
window.showFollowupModal = showFollowupModal;
window.confirmFollowup = confirmFollowup;
window.loadSuppliers = loadSuppliers;
</script>
<!-- 图片查看器模态框 -->
<div class="image-viewer" id="imageViewer">
<button class="image-viewer-prev" onclick="showPrevImage(event)">&lt;</button>
<div class="image-viewer-content">
<img id="viewerImage" src="data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%22100%22 height=%22100%22%3E%3Crect width=%22100%22 height=%22100%22 fill=%22%23f5f5f5%22/%3E%3Ctext x=%2250%22 y=%2250%22 font-family=%22Arial%22 font-size=%2212%22 fill=%22%23999%22 text-anchor=%22middle%22 dominant-baseline=%22middle%22%3E暂无图片%3C/text%3E%3C/svg%3E" alt="大图查看">
<span class="image-counter" id="imageCounter"></span>
</div>
<button class="image-viewer-next" onclick="showNextImage(event)">&gt;</button>
<button class="image-viewer-close" onclick="closeImageViewer()">&times;</button>
<button class="image-viewer-rotate" onclick="rotateImage()"></button>
</div>
<script>
// 图片查看器功能
let currentImageIndex = 0;
let currentImageUrls = [];
let currentImageUrl = '';
// 缩放和拖动相关变量
let scale = 1;
let rotation = 0; // 旋转角度变量
let lastScale = 1;
let panning = false;
let pointX = 0;
let pointY = 0;
let start = { x: 0, y: 0 };
let origin = { x: 0, y: 0 };
// 触摸相关变量
let initialDistance = null;
let initialScale = 1;
function openImageViewer(imgElement) {
const viewer = document.getElementById('imageViewer');
const viewerImage = document.getElementById('viewerImage');
const imageCounter = document.getElementById('imageCounter');
// 获取当前图片URL
currentImageUrl = imgElement.src.split('?')[0]; // 移除时间戳
// 获取当前产品的所有图片URLs
// 查找当前图片所属的产品项
const supplyItem = imgElement.closest('.supply-item');
if (supplyItem) {
// 获取该产品项中所有带有data-product-image属性的图片,包括证明材料
const allImages = supplyItem.querySelectorAll('img[data-product-image="true"]');
currentImageUrls = Array.from(allImages).map(img => img.src.split('?')[0]);
// 找到当前图片在数组中的索引
currentImageIndex = currentImageUrls.findIndex(url => url === currentImageUrl);
if (currentImageIndex === -1) currentImageIndex = 0;
} else {
// 其他情况,只显示当前图片
currentImageUrls = [currentImageUrl];
currentImageIndex = 0;
}
viewerImage.src = currentImageUrls[currentImageIndex];
// 更新计数器
imageCounter.textContent = `${currentImageIndex + 1}/${currentImageUrls.length}`;
// 显示查看器
viewer.classList.add('active');
// 禁止页面滚动
document.body.style.overflow = 'hidden';
// 重置缩放和拖动状态
resetImageTransform();
}
// 添加图片点击事件委托,只处理带有特定属性的图片
document.addEventListener('click', function(event) {
// 只处理带有data-product-image属性的图片点击
if (event.target.tagName === 'IMG' && event.target.hasAttribute('data-product-image')) {
openImageViewer(event.target);
event.stopPropagation(); // 阻止冒泡到document的其他监听器
}
});
// 为图片查看器添加点击事件,正确处理事件冒泡
document.getElementById('imageViewer').addEventListener('click', function(event) {
// 只有点击空白背景时才关闭
if (event.target === this) {
closeImageViewer();
} else {
// 阻止内部元素的点击事件冒泡,防止触发外部可能的全局事件
event.stopPropagation();
}
});
// 为查看器内所有可点击元素添加阻止冒泡
['viewerImage', 'prevImageBtn', 'nextImageBtn', 'closeViewerBtn'].forEach(id => {
const element = document.getElementById(id);
if (element) {
element.addEventListener('click', function(event) {
event.stopPropagation();
});
}
});
function closeImageViewer(event) {
if (event) event.stopPropagation(); // 阻止事件冒泡
const viewer = document.getElementById('imageViewer');
viewer.classList.remove('active');
// 恢复页面滚动
document.body.style.overflow = 'auto';
// 重置图片数据
currentImageUrls = [];
currentImageIndex = 0;
// 重置缩放和拖动状态
resetImageTransform();
}
function showPrevImage(event) {
event.stopPropagation();
if (currentImageUrls.length > 1) {
currentImageIndex = (currentImageIndex - 1 + currentImageUrls.length) % currentImageUrls.length;
document.getElementById('viewerImage').src = currentImageUrls[currentImageIndex];
document.getElementById('imageCounter').textContent = `${currentImageIndex + 1}/${currentImageUrls.length}`;
// 重置缩放、拖动和旋转状态
resetImageTransform();
}
}
function showNextImage(event) {
event.stopPropagation();
if (currentImageUrls.length > 1) {
currentImageIndex = (currentImageIndex + 1) % currentImageUrls.length;
document.getElementById('viewerImage').src = currentImageUrls[currentImageIndex];
document.getElementById('imageCounter').textContent = `${currentImageIndex + 1}/${currentImageUrls.length}`;
// 重置缩放、拖动和旋转状态
resetImageTransform();
}
}
// 重置图片变换
function resetImageTransform() {
const viewerImage = document.getElementById('viewerImage');
scale = 1;
rotation = 0; // 重置旋转角度
lastScale = 1;
pointX = 0;
pointY = 0;
origin = { x: 0, y: 0 };
updateImageTransform();
}
// 更新图片变换
function updateImageTransform() {
const viewerImage = document.getElementById('viewerImage');
viewerImage.style.transform = `translate(${pointX}px, ${pointY}px) scale(${scale}) rotate(${rotation}deg)`;
}
// 旋转图片
function rotateImage() {
rotation += 90;
if (rotation >= 360) rotation = 0;
updateImageTransform();
}
// 鼠标滚轮缩放
document.getElementById('viewerImage').addEventListener('wheel', function(e) {
e.preventDefault();
const viewerImage = document.getElementById('viewerImage');
const rect = viewerImage.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
// 计算鼠标相对于图片中心的位置
const mouseX = e.clientX - centerX;
const mouseY = e.clientY - centerY;
// 计算缩放因子
const zoomIntensity = 0.1;
const zoom = Math.exp(e.deltaY * -zoomIntensity * 0.01);
// 计算新的缩放比例
const newScale = Math.max(0.1, Math.min(10, scale * zoom));
// 计算缩放后的偏移量,使鼠标位置保持相对不变
const scaleFactor = newScale / scale;
pointX = (mouseX + pointX) * scaleFactor - mouseX;
pointY = (mouseY + pointY) * scaleFactor - mouseY;
// 更新缩放比例
scale = newScale;
// 更新图片变换
updateImageTransform();
});
// 触摸事件处理(缩放和拖动)
let touchStartDistance = 0;
let touchStartMidpoint = { x: 0, y: 0 };
let touchStartPointX = 0;
let touchStartPointY = 0;
document.getElementById('imageViewer').addEventListener('touchstart', function(e) {
e.preventDefault();
if (e.touches.length === 1) {
// 单点触摸 - 拖动
touchStartPointX = pointX;
touchStartPointY = pointY;
start.x = e.touches[0].clientX;
start.y = e.touches[0].clientY;
panning = true;
} else if (e.touches.length === 2) {
// 双点触摸 - 缩放
const dx = e.touches[0].clientX - e.touches[1].clientX;
const dy = e.touches[0].clientY - e.touches[1].clientY;
touchStartDistance = Math.sqrt(dx * dx + dy * dy);
// 计算触摸中点
touchStartMidpoint.x = (e.touches[0].clientX + e.touches[1].clientX) / 2;
touchStartMidpoint.y = (e.touches[0].clientY + e.touches[1].clientY) / 2;
// 记录当前的偏移量
touchStartPointX = pointX;
touchStartPointY = pointY;
lastScale = scale;
}
});
document.getElementById('imageViewer').addEventListener('touchmove', function(e) {
e.preventDefault();
if (e.touches.length === 1 && panning) {
// 单点触摸 - 拖动
const dx = e.touches[0].clientX - start.x;
const dy = e.touches[0].clientY - start.y;
pointX = touchStartPointX + dx;
pointY = touchStartPointY + dy;
updateImageTransform();
} else if (e.touches.length === 2) {
// 双点触摸 - 缩放
const dx = e.touches[0].clientX - e.touches[1].clientX;
const dy = e.touches[0].clientY - e.touches[1].clientY;
const distance = Math.sqrt(dx * dx + dy * dy);
// 计算缩放比例
const scaleFactor = distance / touchStartDistance;
scale = Math.max(0.1, Math.min(10, lastScale * scaleFactor));
// 计算缩放后的偏移量,使触摸中点保持相对不变
const currentMidpoint = {
x: (e.touches[0].clientX + e.touches[1].clientX) / 2,
y: (e.touches[0].clientY + e.touches[1].clientY) / 2
};
const deltaX = currentMidpoint.x - touchStartMidpoint.x;
const deltaY = currentMidpoint.y - touchStartMidpoint.y;
pointX = touchStartPointX + deltaX;
pointY = touchStartPointY + deltaY;
updateImageTransform();
}
});
document.getElementById('imageViewer').addEventListener('touchend', function(e) {
panning = false;
});
// 鼠标拖动功能
document.getElementById('viewerImage').addEventListener('mousedown', function(e) {
e.preventDefault();
panning = true;
start.x = e.clientX - pointX;
start.y = e.clientY - pointY;
});
document.addEventListener('mousemove', function(e) {
if (!panning) return;
pointX = e.clientX - start.x;
pointY = e.clientY - start.y;
updateImageTransform();
});
document.addEventListener('mouseup', function() {
panning = false;
});
// 点击重置按钮(可选)
function resetZoom() {
resetImageTransform();
}
</script>
</body>
</html>