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.

2438 lines
88 KiB

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>货源审核系统</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
color: #333;
}
.container {
max-width: 1200px;
margin: 0 auto;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 20px;
}
h1 {
text-align: center;
color: #1890ff;
margin-bottom: 30px;
}
.search-bar {
margin-bottom: 20px;
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.search-bar input {
flex: 1;
min-width: 200px;
padding: 10px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 14px;
}
.search-bar button {
padding: 10px 20px;
background: rgba(24, 144, 255, 0.9);
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
color: white;
border: 1px solid rgba(255, 255, 255, 0.5);
border-radius: 4px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: transform 0.3s ease, box-shadow 0.3s ease, background-color 0.3s ease;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
background-image: linear-gradient(135deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 100%);
}
.search-bar button:hover {
background: rgba(24, 144, 255, 1);
transform: scale(1.05);
box-shadow: 0 8px 15px rgba(0, 0, 0, 0.15);
}
.supply-item {
border: 1px solid #e8e8e8;
border-radius: 4px;
padding: 20px;
margin-bottom: 20px;
transition: all 0.3s;
}
/* 联系人分配区域样式 */
.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 {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.9);
z-index: 1000;
justify-content: center;
align-items: center;
cursor: pointer;
}
.image-viewer.active {
display: flex;
}
.image-viewer-content {
position: relative;
max-width: 90%;
max-height: 90%;
cursor: default;
}
.image-viewer img {
max-width: 100%;
max-height: 80vh;
object-fit: contain;
transform-origin: center center;
transition: transform 0.1s ease;
}
.image-viewer-close {
position: absolute;
top: 20px;
right: 20px;
width: 40px;
height: 40px;
background-color: rgba(255, 255, 255, 0.2);
color: white;
border: none;
border-radius: 50%;
font-size: 24px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.3s;
}
.image-viewer-close:hover {
background-color: rgba(255, 255, 255, 0.3);
}
.image-viewer-prev,
.image-viewer-next {
position: absolute;
top: 50%;
width: 40px;
height: 40px;
background-color: rgba(255, 255, 255, 0.2);
color: white;
border: none;
border-radius: 50%;
font-size: 24px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.3s;
transform: translateY(-50%);
}
.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.3);
}
.image-counter {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 5px 15px;
border-radius: 20px;
font-size: 14px;
right: 0;
bottom: 0;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 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: 15px;
}
.supply-title {
font-size: 18px;
font-weight: 500;
color: #333;
}
.supply-status {
padding: 4px 12px;
border-radius: 4px;
font-size: 12px;
color: #fff;
}
.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: 15px;
margin-bottom: 20px;
}
.info-item {
display: flex;
flex-direction: column;
}
.info-label {
font-size: 12px;
color: #999;
margin-bottom: 4px;
}
.info-value {
font-size: 14px;
color: #333;
}
.supply-images {
display: flex;
gap: 10px;
margin-bottom: 20px;
overflow-x: auto;
padding-bottom: 10px;
}
.supply-image {
width: 100px;
height: 100px;
object-fit: cover;
border-radius: 4px;
}
.supply-reject-reason {
margin-top: 15px;
padding: 10px;
background-color: #fff2f0;
border: 1px solid #ffccc7;
border-radius: 4px;
}
.supply-reject-reason .label {
font-size: 12px;
color: #ff4d4f;
margin-bottom: 5px;
}
.supply-reject-reason .reason {
font-size: 14px;
color: #333;
}
.action-buttons {
display: flex;
gap: 10px;
}
.btn {
padding: 8px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
}
.btn-primary {
background: rgba(24, 144, 255, 0.8);
color: white;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 4px 6px rgba(24, 144, 255, 0.3),
0 0 0 1px rgba(24, 144, 255, 0.1) inset;
position: relative;
overflow: hidden;
}
.btn-primary::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
.btn-primary:hover {
background: rgba(24, 144, 255, 0.9);
transform: scale(1.05);
box-shadow: 0 6px 12px rgba(24, 144, 255, 0.4),
0 0 0 1px rgba(24, 144, 255, 0.2) inset;
}
.btn-primary:hover::before {
left: 100%;
}
.btn-danger {
background: rgba(255, 77, 79, 0.8);
color: white;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 4px 6px rgba(255, 77, 79, 0.3),
0 0 0 1px rgba(255, 77, 79, 0.1) inset;
position: relative;
overflow: hidden;
}
.btn-danger::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
.btn-danger:hover {
background: rgba(255, 77, 79, 0.9);
transform: scale(1.05);
box-shadow: 0 6px 12px rgba(255, 77, 79, 0.4),
0 0 0 1px rgba(255, 77, 79, 0.2) inset;
}
.btn-danger:hover::before {
left: 100%;
}
.btn:disabled {
background-color: #d9d9d9;
cursor: not-allowed;
}
.btn-default {
background: rgba(255, 255, 255, 0.8);
color: #666;
backdrop-filter: blur(10px);
border: 1px solid rgba(0, 0, 0, 0.1);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1),
0 0 0 1px rgba(255, 255, 255, 0.5) inset;
position: relative;
overflow: hidden;
}
.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 {
background: rgba(255, 255, 255, 0.9);
transform: scale(1.05);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15),
0 0 0 1px rgba(255, 255, 255, 0.8) inset;
color: #333;
}
.btn-default:hover::before {
left: 100%;
}
.btn-primary {
background: rgba(66, 153, 225, 0.8);
color: #fff;
backdrop-filter: blur(10px);
border: 1px solid rgba(66, 153, 225, 0.5);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1),
0 0 0 1px rgba(255, 255, 255, 0.3) inset;
position: relative;
overflow: hidden;
transition: all 0.3s ease;
}
.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 {
background: rgba(66, 153, 225, 0.9);
transform: scale(1.05);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2),
0 0 0 1px rgba(255, 255, 255, 0.5) inset;
}
.btn-primary:hover::before {
left: 100%;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
align-items: center;
justify-content: center;
}
.modal-content {
background-color: white;
border-radius: 4px;
padding: 30px;
width: 90%;
max-width: 500px;
}
.modal-header {
font-size: 18px;
font-weight: 500;
margin-bottom: 20px;
text-align: center;
}
.modal-body {
margin-bottom: 20px;
}
.modal-body textarea {
width: 100%;
height: 120px;
padding: 10px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 14px;
resize: vertical;
box-sizing: border-box;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
.modal-footer .btn {
min-width: 80px;
}
.empty-state {
text-align: center;
padding: 60px 20px;
color: #666;
background-color: #fafafa;
border-radius: 8px;
margin: 20px 0;
}
.empty-state img {
width: 120px;
margin-bottom: 20px;
opacity: 0.5;
}
.empty-icon {
font-size: 48px;
margin-bottom: 16px;
}
.empty-state h3 {
margin: 0 0 8px 0;
color: #333;
font-size: 18px;
font-weight: 500;
}
.empty-state p {
margin: 8px 0;
line-height: 1.5;
}
.error-details {
font-size: 12px;
color: #999;
margin: 8px 0 16px 0;
max-width: 600px;
margin-left: auto;
margin-right: auto;
}
.empty-state .btn-primary {
margin-top: 16px;
padding: 8px 24px;
font-size: 14px;
cursor: pointer;
}
.loading {
text-align: center;
padding: 40px;
color: #999;
}
.pagination {
display: flex;
justify-content: center;
margin-top: 30px;
gap: 5px;
}
.pagination button {
padding: 5px 12px;
border: 1px solid #d9d9d9;
background-color: white;
border-radius: 4px;
cursor: pointer;
}
.pagination button:hover:not(:disabled) {
border-color: #1890ff;
color: #1890ff;
}
.pagination button:disabled {
cursor: not-allowed;
opacity: 0.5;
}
.pagination button.active {
background-color: #1890ff;
color: white;
border-color: #1890ff;
}
/* 状态导航栏样式 */
.status-nav {
display: flex;
margin: 20px 0;
background-color: white;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
overflow: hidden;
position: relative;
}
.status-btn {
flex: 1;
padding: 12px 20px;
border: 1px solid transparent;
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
color: #666;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
box-sizing: border-box;
margin: 0;
overflow: hidden;
}
.status-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent);
transition: left 0.5s ease;
}
.status-btn:hover {
color: #1890ff;
background: rgba(24, 144, 255, 0.1);
}
.status-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent);
transition: left 0.5s ease;
}
.status-btn:hover::before {
left: 100%;
}
.status-btn.active {
color: #1890ff;
font-weight: bold;
background: rgba(24, 144, 255, 0.2);
border-color: rgba(24, 144, 255, 0.5);
}
.status-btn.active::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 2px;
background-color: #1890ff;
}
/* 类型切换器样式 */
.type-nav {
display: flex;
margin: 20px 0;
background-color: white;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.type-btn {
flex: 1;
padding: 12px 20px;
border: 1px solid transparent;
background: rgba(255, 255, 255, 0.9);
color: #666;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
}
.type-btn:hover {
color: #1890ff;
background: rgba(24, 144, 255, 0.1);
}
.type-btn.active {
color: #1890ff;
font-weight: bold;
background: rgba(24, 144, 255, 0.2);
border-color: rgba(24, 144, 255, 0.5);
}
.type-btn.active::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 3px;
background-color: #1890ff;
}
/* 供应商状态样式 */
.status-underreview {
background-color: #faad14;
}
.status-reviewfailed {
background-color: #ff4d4f;
}
.status-approved {
background-color: #52c41a;
}
.status-incooperation {
background-color: #1890ff;
}
.status-notcooperative {
background-color: #d9d9d9;
}
/* 证明材料展示样式 */
.proof-materials {
margin-top: 15px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
}
.proof-item {
border: 1px solid #e8e8e8;
border-radius: 4px;
padding: 12px;
transition: all 0.3s;
}
.proof-item:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.proof-title {
font-size: 14px;
color: #666;
margin-bottom: 8px;
font-weight: 500;
}
.proof-link {
color: #1890ff;
text-decoration: none;
font-size: 14px;
word-break: break-all;
}
.proof-link:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div class="container">
<!-- 登录信息展示 -->
<div id="userInfo" style="text-align: right; margin-bottom: 20px; padding: 10px; background-color: #f0f8ff; border-radius: 4px;">
<span id="userName">未登录</span>
<button id="logoutBtn" style="margin-left: 15px; padding: 5px 12px; background-color: #f5222d; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px;">退出登录</button>
</div>
<h1>审核系统</h1>
<!-- 审核类型切换器 -->
<div class="type-nav" style="
display: flex;
margin-bottom: 20px;
background-color: white;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
overflow: hidden;
">
<button class="type-btn" id="managementBtn" style="border-right: 1px solid rgba(0, 0, 0, 0.1);">货源管理</button>
<button class="type-btn active" data-type="supply">货源审核</button>
<button class="type-btn" onclick="window.location.href='SupplierReview.html'" style="border-left: 1px solid rgba(0, 0, 0, 0.1);">供应商审核</button>
<button class="type-btn" id="createSupplyBtn" style="background-color: #52c41a; color: white; border-left: 1px solid rgba(0, 0, 0, 0.1);">创建货源</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 supply-status-nav">
<button class="status-btn active" data-status="pending_review">待审核</button>
<button class="status-btn" data-status="published">已审核</button>
<button class="status-btn" data-status="rejected">已拒绝</button>
<button class="status-btn" data-status="hidden">已下架</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="createSupplyModal" class="modal">
<div class="modal-content" style="max-width: 600px;">
<div class="modal-header">创建货源</div>
<div class="modal-body">
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: 500;">商品名称</label>
<input type="text" id="supplyName" placeholder="请输入商品名称" style="width: 100%; padding: 10px; border: 1px solid #d9d9d9; border-radius: 4px; font-size: 14px;">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: 500;">价格</label>
<input type="number" id="supplyPrice" placeholder="请输入价格" step="0.01" style="width: 100%; padding: 10px; border: 1px solid #d9d9d9; border-radius: 4px; font-size: 14px;">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: 500;">最小起订量</label>
<input type="number" id="supplyMinOrder" placeholder="请输入最小起订量" style="width: 100%; padding: 10px; border: 1px solid #d9d9d9; border-radius: 4px; font-size: 14px;">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: 500;">蛋黄类型</label>
<select id="supplyYolk" style="width: 100%; padding: 10px; border: 1px solid #d9d9d9; border-radius: 4px; font-size: 14px;">
<option value="红心">红心</option>
<option value="黄心">黄心</option>
<option value="双色">双色</option>
</select>
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: 500;">规格</label>
<select id="supplySpec" style="width: 100%; padding: 10px; border: 1px solid #d9d9d9; border-radius: 4px; font-size: 14px;">
<option value="格子装">格子装</option>
<option value="散托">散托</option>
<option value="不限规格">不限规格</option>
<option value="净重47+">净重47+</option>
<option value="净重46-47">净重46-47</option>
<option value="净重45-46">净重45-46</option>
<option value="净重44-45">净重44-45</option>
<option value="净重43-44">净重43-44</option>
<option value="净重42-43">净重42-43</option>
<option value="净重41-42">净重41-42</option>
<option value="净重40-41">净重40-41</option>
<option value="净重39-40">净重39-40</option>
<option value="净重38-39">净重38-39</option>
<option value="净重37-39">净重37-39</option>
<option value="净重37-38">净重37-38</option>
<option value="净重36-38">净重36-38</option>
<option value="净重36-37">净重36-37</option>
<option value="净重35-36">净重35-36</option>
<option value="净重34-35">净重34-35</option>
<option value="净重33-34">净重33-34</option>
<option value="净重32-33">净重32-33</option>
<option value="净重32-34">净重32-34</option>
<option value="净重31-32">净重31-32</option>
<option value="净重30-35">净重30-35</option>
<option value="净重30-34">净重30-34</option>
<option value="净重30-32">净重30-32</option>
<option value="净重30-31">净重30-31</option>
<option value="净重29-31">净重29-31</option>
<option value="净重29-30">净重29-30</option>
<option value="净重28-29">净重28-29</option>
<option value="净重28以下">净重28以下</option>
<option value="毛重52以上">毛重52以上</option>
<option value="毛重50-51">毛重50-51</option>
<option value="毛重48-49">毛重48-49</option>
<option value="毛重47-48">毛重47-48</option>
<option value="毛重46-47">毛重46-47</option>
<option value="毛重45-47">毛重45-47</option>
<option value="毛重45-46">毛重45-46</option>
<option value="毛重44-45">毛重44-45</option>
<option value="毛重43-44">毛重43-44</option>
<option value="毛重42-43">毛重42-43</option>
<option value="毛重41-42">毛重41-42</option>
<option value="毛重40-41">毛重40-41</option>
<option value="毛重38-39">毛重38-39</option>
<option value="毛重36-37">毛重36-37</option>
<option value="毛重34-35">毛重34-35</option>
<option value="毛重32-33">毛重32-33</option>
<option value="毛重30-31">毛重30-31</option>
<option value="毛重30以下">毛重30以下</option>
</select>
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: 500;">地区</label>
<input type="text" id="supplyRegion" placeholder="请输入地区" style="width: 100%; padding: 10px; border: 1px solid #d9d9d9; border-radius: 4px; font-size: 14px;">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: 500;">毛重</label>
<input type="text" id="supplyGrossWeight" placeholder="请输入毛重" style="width: 100%; padding: 10px; border: 1px solid #d9d9d9; border-radius: 4px; font-size: 14px;">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: 500;">联系人</label>
<select id="supplyContact" class="contact-select" style="width: 100%; padding: 10px; border: 1px solid #d9d9d9; border-radius: 4px; font-size: 14px;">
<option value="">请选择联系人</option>
</select>
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: 500;">鸡蛋品质</label>
<select id="supplyQuality" style="width: 100%; padding: 10px; border: 1px solid #d9d9d9; border-radius: 4px; font-size: 14px;">
<option value="">请选择鸡蛋品质</option>
<option value="一级">一级</option>
<option value="二级">二级</option>
<option value="三级">三级</option>
</select>
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: 500;">商品图片</label>
<div id="supplyImages" style="display: flex; gap: 10px; margin-bottom: 10px; flex-wrap: wrap;">
<!-- 图片预览将动态添加到这里 -->
</div>
<div id="imageDropArea" style="border: 2px dashed #d9d9d9; border-radius: 4px; padding: 20px; text-align: left; margin-bottom: 10px; background-color: #fafafa; transition: all 0.3s ease;">
<div style="font-size: 14px; color: #666; margin-bottom: 5px; text-align: center;">拖拽图片到此处或点击添加图片</div>
<div style="font-size: 12px; color: #666; margin: 10px 0; padding: 8px; background-color: #f5f5f5; border-radius: 4px;">
<div>图片要求:</div>
<div>1. 箱子堆头图(不得出现地址、电话及货源信息,直营包场除外);</div>
<div>2. 鸡蛋外观图;</div>
<div>3. 敲开鸡蛋后清晰显示蛋清、蛋黄状态,以体现新鲜度;</div>
<div>4. 其他能佐证蛋重、品种的辅助图片。</div>
</div>
<div style="font-size: 12px; color: #999; text-align: left;">支持JPG、PNG等图片格式,最多上传5张</div>
</div>
<input type="file" id="supplyImageUpload" multiple accept="image/*" style="display: none;">
<button id="addImageBtn" class="btn btn-default" style="margin-right: 10px;">添加图片</button>
<span style="font-size: 12px; color: #999; text-align: left;">最多上传5张图片</span>
</div>
</div>
<div class="modal-footer">
<button id="cancelCreateSupplyBtn" class="btn btn-default">取消</button>
<button id="confirmCreateSupplyBtn" 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 currentSupplyId = null;
// 当前选中的状态(pending_review: 待审核, published: 已审核, rejected: 已拒绝)
let currentStatus = 'pending_review';
// 联系人数据
let contacts = [];
// 创建货源相关变量
let supplyImages = [];
// 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');
// 创建货源相关DOM元素
const createSupplyBtnEl = document.getElementById('createSupplyBtn');
const managementBtnEl = document.getElementById('managementBtn');
const createSupplyModalEl = document.getElementById('createSupplyModal');
const supplyNameEl = document.getElementById('supplyName');
const supplyPriceEl = document.getElementById('supplyPrice');
const supplyMinOrderEl = document.getElementById('supplyMinOrder');
const supplyYolkEl = document.getElementById('supplyYolk');
const supplySpecEl = document.getElementById('supplySpec');
const supplyRegionEl = document.getElementById('supplyRegion');
const supplyGrossWeightEl = document.getElementById('supplyGrossWeight');
const supplyImagesEl = document.getElementById('supplyImages');
const supplyImageUploadEl = document.getElementById('supplyImageUpload');
const addImageBtnEl = document.getElementById('addImageBtn');
const cancelCreateSupplyBtnEl = document.getElementById('cancelCreateSupplyBtn');
const confirmCreateSupplyBtnEl = document.getElementById('confirmCreateSupplyBtn');
// 初始化页面
window.onload = () => {
// 登录检查
if (!checkLogin()) {
return;
}
initPage();
bindEvents();
};
// 初始化页面函数
async function initPage() {
currentPage = 1;
// 先加载联系人数据,确保下拉框有数据
await loadContacts();
currentStatus = 'pending_review';
loadSupplies();
}
// 绑定事件
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;
loadSupplies();
});
});
// 类型切换器点击事件
const typeBtns = document.querySelectorAll('.type-nav button');
typeBtns.forEach(btn => {
btn.addEventListener('click', () => {
// 更新类型按钮的active状态
typeBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
// 更新当前类型
currentType = btn.getAttribute('data-type');
// 显示对应的状态导航栏
document.querySelector('.supply-status-nav').style.display = 'flex';
// 重置状态和页面
currentPage = 1;
currentStatus = 'pending_review';
// 触发待审核状态按钮的点击事件
const pendingReviewBtn = document.querySelector('.supply-status-nav button[data-status="pending_review"]');
if (pendingReviewBtn) pendingReviewBtn.click();
});
});
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) {
showApproveModal(id);
}
}
// 拒绝按钮
if (e.target.classList.contains('btn-danger') && e.target.closest('.action-buttons')) {
const id = e.target.getAttribute('data-id');
if (id) {
showRejectModal(id);
}
}
});
// 货源管理按钮点击事件 - 跳转到Management.html页面
managementBtnEl.addEventListener('click', () => {
window.location.href = 'Management.html';
});
// 创建货源按钮点击事件 - 跳转到supply.html页面
createSupplyBtnEl.addEventListener('click', () => {
window.location.href = 'supply.html';
});
// 图片上传相关事件
// 点击添加图片按钮
addImageBtnEl.addEventListener('click', () => {
supplyImageUploadEl.click();
});
// 点击拖拽区域触发文件选择
document.getElementById('imageDropArea').addEventListener('click', () => {
supplyImageUploadEl.click();
});
// 文件选择事件
supplyImageUploadEl.addEventListener('change', handleImageUpload);
// 拖拽上传事件
const imageDropArea = document.getElementById('imageDropArea');
imageDropArea.addEventListener('dragenter', (e) => {
e.preventDefault();
e.stopPropagation();
imageDropArea.style.borderColor = '#1890ff';
imageDropArea.style.backgroundColor = '#e6f7ff';
});
imageDropArea.addEventListener('dragover', (e) => {
e.preventDefault();
e.stopPropagation();
});
imageDropArea.addEventListener('dragleave', (e) => {
e.preventDefault();
e.stopPropagation();
imageDropArea.style.borderColor = '#d9d9d9';
imageDropArea.style.backgroundColor = '#fafafa';
});
imageDropArea.addEventListener('drop', (e) => {
e.preventDefault();
e.stopPropagation();
imageDropArea.style.borderColor = '#d9d9d9';
imageDropArea.style.backgroundColor = '#fafafa';
const files = Array.from(e.dataTransfer.files);
// 过滤出图片文件
const imageFiles = files.filter(file => file.type.startsWith('image/'));
// 模拟文件选择事件
const event = new Event('change', { bubbles: true });
// 将文件添加到input中
const dataTransfer = new DataTransfer();
imageFiles.forEach(file => dataTransfer.items.add(file));
supplyImageUploadEl.files = dataTransfer.files;
// 触发文件选择事件
supplyImageUploadEl.dispatchEvent(event);
});
// 复制粘贴上传事件
document.addEventListener('paste', (e) => {
// 检查是否在创建货源模态框中
if (createSupplyModalEl.style.display !== 'flex') {
return;
}
// 检查是否有粘贴的图片
const items = e.clipboardData.items;
const imageFiles = [];
for (let i = 0; i < items.length; i++) {
if (items[i].type.startsWith('image/')) {
const file = items[i].getAsFile();
if (file) {
imageFiles.push(file);
}
}
}
if (imageFiles.length > 0) {
// 模拟文件选择事件
const event = new Event('change', { bubbles: true });
const dataTransfer = new DataTransfer();
imageFiles.forEach(file => dataTransfer.items.add(file));
supplyImageUploadEl.files = dataTransfer.files;
// 触发文件选择事件
supplyImageUploadEl.dispatchEvent(event);
}
});
// 取消创建货源按钮事件
cancelCreateSupplyBtnEl.addEventListener('click', closeCreateSupplyModal);
// 确认创建货源按钮事件
confirmCreateSupplyBtnEl.addEventListener('click', confirmCreateSupply);
}
// 加载货源列表
async function loadSupplies() {
try {
supplyListEl.innerHTML = '<div class="loading">加载中...</div>';
// 构建请求URL
const url = `/api/supplies?status=${currentStatus}&page=${currentPage}&pageSize=${pageSize}&search=${encodeURIComponent(searchInputEl.value)}&phone=${encodeURIComponent(phoneInputEl.value)}`;
// 发送请求
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
const result = await response.json();
if (result.success) {
const supplies = result.data.list;
const total = result.data.total;
// 更新总数显示
const totalCountEl = document.getElementById('totalCount');
if (totalCountEl) {
totalCountEl.textContent = total;
}
// 渲染货源列表
renderSupplyList(supplies);
// 渲染分页
renderPagination(total);
} else {
supplyListEl.innerHTML = `<div class="empty-state"><div class="empty-icon">⚠️</div><h3>加载失败</h3><p>${result.message || '获取货源数据失败,请稍后重试'}</p></div>`;
}
} catch (error) {
console.error('加载货源失败:', error);
supplyListEl.innerHTML = '<div class="empty-state"><div class="empty-icon">⚠️</div><h3>加载失败</h3><p>网络错误,请稍后重试</p></div>';
}
}
// 渲染货源列表
function renderSupplyList(supplies) {
if (!supplies || !Array.isArray(supplies)) {
console.error('supplies不是有效的数组:', supplies);
supplyListEl.innerHTML = `<div class="empty-state">
<p>数据格式错误,请刷新页面重试</p>
</div>`;
return;
}
if (supplies.length === 0) {
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="loadSupplies()">刷新列表</button>
</div>`;
return;
}
supplyListEl.innerHTML = supplies.map((supply, index) => {
// 状态处理
let statusClass = 'status-pending';
let statusText = '待审核';
if (supply.status === 'published') {
statusClass = 'status-published';
statusText = '已审核';
} else if (supply.status === 'rejected') {
statusClass = 'status-rejected';
statusText = '已拒绝';
} else if (supply.status === 'hidden') {
statusClass = 'status-hidden';
statusText = '已下架';
}
// 图片处理
const imageUrls = Array.isArray(supply.imageUrls) ? supply.imageUrls :
(typeof supply.imageUrls === 'string' ? [supply.imageUrls] :
(supply.imageUrls ? [supply.imageUrls] : []));
// 联系人处理
const contacts = Array.isArray(supply.contacts) ? supply.contacts : [];
return `
<div class="supply-item" data-id="${supply.id}">
<div class="supply-header">
<div class="supply-title">${supply.productName}</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">${supply.id}</div>
<br>
<div class="info-label">创建时间</div>
<div class="info-value">${formatTime(supply.created_at)}</div>
</div>
<div class="info-item">
<div class="info-label">供应商</div>
<div class="info-value">${supply.company || '未知供应商'}</div>
<br>
<div class="info-label">联系人</div>
<div class="info-value">${supply.contact || '未知联系人'}</div>
</div>
<div class="info-item">
<div class="info-label">价格</div>
<div class="info-value">¥${supply.price || '0'}</div>
<br>
<div class="info-label">地区</div>
<div class="info-value">${supply.region || '未知地区'}</div>
</div>
<div class="info-item">
<div class="info-label">规格</div>
<div class="info-value">${supply.spec || '未知规格'}</div>
<br>
<div class="info-label">最小起订量</div>
<div class="info-value">${supply.minOrder || '1'}件</div>
</div>
</div>
<!-- 图片展示 -->
${imageUrls.length > 0 ? `
<div class="supply-images">
${imageUrls.slice(0, 3).map(imgUrl => `
<img src="${imgUrl}" alt="货源图片" class="supply-image" onclick="openImageViewer(this)" data-product-image="true">
`).join('')}
${imageUrls.length > 3 ? `<div class="more-images">+${imageUrls.length - 3}</div>` : ''}
</div>
` : ''}
<!-- 拒绝理由 -->
${supply.rejectReason ? `
<div class="supply-reject-reason">
<div class="label">拒绝理由:</div>
<div class="reason">${supply.rejectReason}</div>
</div>
` : ''}
<!-- 操作按钮 -->
<div class="action-buttons">
${supply.status === 'pending_review' ? `
<button class="btn btn-primary" data-id="${supply.id}" onclick="showApproveModal('${supply.id}')">
通过
</button>
<button class="btn btn-danger" data-id="${supply.id}" onclick="showRejectModal('${supply.id}')">
拒绝
</button>
` : ''}
</div>
</div>
`;
}).join('');
}
// 渲染分页
function renderPagination(total) {
const totalPages = Math.ceil(total / pageSize);
let html = '';
if (totalPages <= 1) {
paginationEl.innerHTML = '';
return;
}
// 上一页按钮
html += `<button ${currentPage === 1 ? 'disabled' : ''} onclick="changePage(${currentPage - 1})">&lt;</button>`;
// 页码按钮
const startPage = Math.max(1, currentPage - 2);
const endPage = Math.min(totalPages, currentPage + 2);
if (startPage > 1) {
html += `<button onclick="changePage(1)">1</button>`;
if (startPage > 2) {
html += `<span>...</span>`;
}
}
for (let i = startPage; i <= endPage; i++) {
html += `<button ${i === currentPage ? 'class="active"' : ''} onclick="changePage(${i})">${i}</button>`;
}
if (endPage < totalPages) {
if (endPage < totalPages - 1) {
html += `<span>...</span>`;
}
html += `<button onclick="changePage(${totalPages})">${totalPages}</button>`;
}
// 下一页按钮
html += `<button ${currentPage === totalPages ? 'disabled' : ''} onclick="changePage(${currentPage + 1})">&gt;</button>`;
paginationEl.innerHTML = html;
}
// 页面跳转
function changePage(page) {
currentPage = page;
loadSupplies();
}
// 格式化时间
function formatTime(timeStr) {
if (!timeStr) return '未知时间';
const date = new Date(timeStr);
return date.toLocaleString('zh-CN');
}
// 显示通过模态框
function showApproveModal(id) {
currentSupplyId = id;
approveModalEl.style.display = 'flex';
}
// 关闭通过模态框
function closeApproveModal() {
approveModalEl.style.display = 'none';
currentSupplyId = null;
}
// 确认通过
async function confirmApprove() {
if (!currentSupplyId) return;
try {
const response = await fetch(`/api/supplies/${currentSupplyId}/approve`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
});
const result = await response.json();
if (result.success) {
closeApproveModal();
loadSupplies();
alert('审核通过成功');
} else {
alert('审核通过失败:' + (result.message || '未知错误'));
}
} catch (error) {
console.error('审核通过失败:', error);
alert('网络错误,请稍后重试');
}
}
// 显示拒绝模态框
function showRejectModal(id) {
currentSupplyId = id;
rejectReasonEl.value = '';
charCountEl.textContent = '0';
rejectModalEl.style.display = 'flex';
}
// 关闭拒绝模态框
function closeRejectModal() {
rejectModalEl.style.display = 'none';
currentSupplyId = null;
}
// 确认拒绝
async function confirmReject() {
if (!currentSupplyId) return;
const reason = rejectReasonEl.value.trim();
if (!reason) {
alert('请输入拒绝理由');
return;
}
try {
const response = await fetch(`/api/supplies/${currentSupplyId}/reject`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`
},
body: JSON.stringify({ rejectReason: reason })
});
const result = await response.json();
if (result.success) {
closeRejectModal();
loadSupplies();
alert('拒绝成功');
} else {
alert('拒绝失败:' + (result.message || '未知错误'));
}
} catch (error) {
console.error('拒绝失败:', error);
alert('网络错误,请稍后重试');
}
}
// 搜索处理
function handleSearch() {
currentPage = 1;
loadSupplies();
}
// 刷新处理
function handleRefresh() {
currentPage = 1;
searchInputEl.value = '';
phoneInputEl.value = '';
loadSupplies();
}
// 修复showImageViewer未定义问题
function showImageViewer(imgElement) {
openImageViewer(imgElement);
}
</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; // 旋转角度变量
// showImageViewer函数已在下面正确定义,这里删除重复定义
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();
// 设置图片查看器事件
setupImageViewerEvents();
}
// 添加图片点击事件委托,只处理带有特定属性的图片
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; // 每次旋转90度
updateImageTransform();
// 旋转后重新计算位置,确保图片完全显示
adjustImagePosition();
}
// 调整图片位置和缩放,确保完全显示
function adjustImagePosition() {
const viewerImage = document.getElementById('viewerImage');
const viewerContent = document.querySelector('.image-viewer-content');
// 重置缩放和位置
scale = 1;
pointX = 0;
pointY = 0;
updateImageTransform();
// 获取图片原始尺寸
const imgNaturalWidth = viewerImage.naturalWidth;
const imgNaturalHeight = viewerImage.naturalHeight;
// 获取容器尺寸
const containerWidth = viewerContent.clientWidth;
const containerHeight = viewerContent.clientHeight;
// 计算旋转后的宽高(90度倍数旋转)
let rotatedWidth, rotatedHeight;
if ([90, 270].includes(rotation % 360)) {
// 旋转90度或270度,宽高互换
rotatedWidth = imgNaturalHeight;
rotatedHeight = imgNaturalWidth;
} else {
// 0度或180度,宽高不变
rotatedWidth = imgNaturalWidth;
rotatedHeight = imgNaturalHeight;
}
// 计算适合容器的缩放比例
const scaleX = containerWidth / rotatedWidth;
const scaleY = containerHeight / rotatedHeight;
const fitScale = Math.min(scaleX, scaleY, 1); // 不超过原始大小
// 应用缩放
scale = fitScale;
updateImageTransform();
}
// 设置图片查看器事件
let imageViewerEventsSetup = false;
function setupImageViewerEvents() {
// 避免重复添加事件监听器
if (imageViewerEventsSetup) {
return;
}
const viewerImage = document.getElementById('viewerImage');
const viewerContent = document.querySelector('.image-viewer-content');
const viewerPrev = document.querySelector('.image-viewer-prev');
const viewerNext = document.querySelector('.image-viewer-next');
const viewerClose = document.querySelector('.image-viewer-close');
// 为所有内部元素添加事件冒泡阻止,防止点击图片或按钮触发背景关闭
viewerContent.addEventListener('click', function(event) {
event.stopPropagation();
});
viewerImage.addEventListener('click', function(event) {
event.stopPropagation();
});
if (viewerPrev) {
viewerPrev.addEventListener('click', function(event) {
event.stopPropagation();
});
}
if (viewerNext) {
viewerNext.addEventListener('click', function(event) {
event.stopPropagation();
});
}
if (viewerClose) {
viewerClose.addEventListener('click', function(event) {
event.stopPropagation();
});
}
// 鼠标滚轮缩放事件 - 增强浏览器兼容性
if (viewerContent.addEventListener) {
// 现代浏览器
viewerContent.addEventListener('wheel', handleWheel, { passive: false });
// 旧版 Firefox
viewerContent.addEventListener('DOMMouseScroll', handleWheel, { passive: false });
} else {
// IE
viewerContent.attachEvent('onmousewheel', handleWheel);
}
// 鼠标拖动事件
viewerImage.addEventListener('mousedown', handleMouseDown);
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
document.addEventListener('mouseleave', handleMouseUp);
// 移动端触摸事件支持
viewerImage.addEventListener('touchstart', handleTouchStart, { passive: false });
viewerImage.addEventListener('touchmove', handleTouchMove, { passive: false });
viewerImage.addEventListener('touchend', handleTouchEnd);
imageViewerEventsSetup = true;
}
// 处理鼠标滚轮缩放
function handleWheel(e) {
// 阻止默认行为
if (e.preventDefault) {
e.preventDefault();
} else {
e.returnValue = false; // IE
}
// 标准化滚轮事件数据
let delta = 0;
if (e.deltaY !== undefined) {
delta = e.deltaY;
} else if (e.wheelDelta !== undefined) {
delta = -e.wheelDelta; // Chrome, IE
} else if (e.detail !== undefined) {
delta = e.detail; // Firefox
}
// 获取鼠标在图片内容区域的位置
const viewerContent = document.querySelector('.image-viewer-content');
const rect = viewerContent.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
// 计算鼠标相对于图片的位置(修复方向问题)
const imageRect = document.getElementById('viewerImage').getBoundingClientRect();
const relativeX = mouseX - (imageRect.left - rect.left + imageRect.width / 2);
const relativeY = mouseY - (imageRect.top - rect.top + imageRect.height / 2);
// 计算缩放中心点
const beforeScale = scale;
const wheelDelta = delta > 0 ? 0.9 : 1.1;
scale *= wheelDelta;
// 限制缩放范围,最小为0.1倍,最大为10倍
scale = Math.min(Math.max(scale, 0.1), 10);
// 当缩放到接近原始大小时,平滑调整位置偏移,确保照片始终可见
if (scale <= 1.1) {
// 仅在缩放比例接近1时才调整位置,小于0.9时不调整
if (scale >= 0.9) {
// 根据当前缩放比例计算位置偏移的衰减因子
const decayFactor = (scale - 0.9) / 0.2;
// 平滑过渡,避免照片在缩小过程中消失
pointX *= decayFactor;
pointY *= decayFactor;
} else if (scale === 1) {
// 当精确缩放到原始大小时,位置偏移完全重置
pointX = 0;
pointY = 0;
}
}
// 根据鼠标位置调整偏移量,实现以鼠标位置为中心的缩放(修复方向问题)
const scaleDiff = scale - beforeScale;
pointX -= relativeX * scaleDiff;
pointY -= relativeY * scaleDiff;
updateImageTransform();
// 阻止事件冒泡
return false;
}
// 处理鼠标按下事件
function handleMouseDown(e) {
e.preventDefault();
panning = true;
start = { x: e.clientX - pointX, y: e.clientY - pointY };
}
// 处理鼠标移动事件
function handleMouseMove(e) {
e.preventDefault();
if (!panning) return;
pointX = e.clientX - start.x;
pointY = e.clientY - start.y;
updateImageTransform();
}
// 处理鼠标释放事件
function handleMouseUp(e) {
e.preventDefault();
panning = false;
}
// 处理触摸开始事件
function handleTouchStart(e) {
e.preventDefault();
const touches = e.touches;
// 单点触摸 - 拖动
if (touches.length === 1) {
panning = true;
start = {
x: touches[0].clientX - pointX,
y: touches[0].clientY - pointY
};
}
// 双点触摸 - 缩放
else if (touches.length === 2) {
initialDistance = getDistance(touches[0], touches[1]);
initialScale = scale;
}
}
// 处理触摸移动事件
function handleTouchMove(e) {
e.preventDefault();
const touches = e.touches;
// 单点触摸 - 拖动
if (touches.length === 1 && panning) {
pointX = touches[0].clientX - start.x;
pointY = touches[0].clientY - start.y;
updateImageTransform();
}
// 双点触摸 - 缩放
else if (touches.length === 2 && initialDistance) {
const currentDistance = getDistance(touches[0], touches[1]);
const scaleFactor = currentDistance / initialDistance;
const beforeScale = scale;
scale = initialScale * scaleFactor;
// 限制缩放范围
scale = Math.min(Math.max(scale, 1), 10);
// 当缩放到接近原始大小时,平滑调整位置偏移
if (scale <= 1.1) {
const decayFactor = (scale - 1) / 0.1;
if (scale === 1) {
pointX = 0;
pointY = 0;
} else {
pointX *= decayFactor;
pointY *= decayFactor;
}
}
// 计算触摸中心点
const viewerContent = document.querySelector('.image-viewer-content');
const rect = viewerContent.getBoundingClientRect();
const centerX = (touches[0].clientX + touches[1].clientX) / 2;
const centerY = (touches[0].clientY + touches[1].clientY) / 2;
const mouseX = centerX - rect.left;
const mouseY = centerY - rect.top;
// 计算相对于图片的位置
const imageRect = document.getElementById('viewerImage').getBoundingClientRect();
const relativeX = mouseX - (imageRect.width / 2 + imageRect.left - rect.left);
const relativeY = mouseY - (imageRect.height / 2 + imageRect.top - rect.top);
// 根据触摸中心点调整偏移量
const scaleDiff = scale - beforeScale;
pointX += relativeX * scaleDiff;
pointY += relativeY * scaleDiff;
updateImageTransform();
}
}
// 处理触摸结束事件
function handleTouchEnd() {
panning = false;
initialDistance = null;
}
// 计算两点之间的距离
function getDistance(touch1, touch2) {
const dx = touch2.clientX - touch1.clientX;
const dy = touch2.clientY - touch1.clientY;
return Math.sqrt(dx * dx + dy * dy);
}
// 点击遮罩层关闭查看器
document.getElementById('imageViewer').addEventListener('click', function (event) {
if (event.target === this) {
closeImageViewer();
}
});
// 键盘控制
document.addEventListener('keydown', function (event) {
const viewer = document.getElementById('imageViewer');
if (!viewer.classList.contains('active')) return;
if (event.key === 'Escape') {
closeImageViewer();
} else if (event.key === 'ArrowLeft') {
showPrevImage(event);
} else if (event.key === 'ArrowRight') {
showNextImage(event);
}
});
</script>
</body>
</html>