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.
3871 lines
158 KiB
3871 lines
158 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: 100%;
|
|
max-height: 100vh;
|
|
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 {
|
|
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-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 active" data-type="supply">货源审核</button>
|
|
<button class="type-btn" data-type="supplier">供应商审核</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="status-nav supplier-status-nav" style="display: none;">
|
|
<button class="status-btn active" data-status="underreview">审核中</button>
|
|
<button class="status-btn" data-status="reviewfailed">审核失败</button>
|
|
<button class="status-btn" data-status="approved">审核通过</button>
|
|
<button class="status-btn" data-status="incooperation">合作中</button>
|
|
<button class="status-btn" data-status="notcooperative">未合作</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: center; margin-bottom: 10px; background-color: #fafafa; transition: all 0.3s ease;">
|
|
<div style="font-size: 14px; color: #666; margin-bottom: 5px;">拖拽图片到此处或点击添加图片</div>
|
|
<div style="font-size: 12px; color: #999;">支持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;">最多上传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';
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
// 当前页码和每页显示数量
|
|
let currentPage = 1;
|
|
const pageSize = 10;
|
|
// 当前选中的货源ID
|
|
let currentSupplyId = null;
|
|
// 当前选中的状态(pending_review: 待审核, published: 已审核, rejected: 已拒绝)
|
|
let currentStatus = 'pending_review';
|
|
// 当前审核类型:'supply'(货源审核)或 'supplier'(供应商审核)
|
|
let currentType = 'supply';
|
|
// 联系人数据
|
|
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 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();
|
|
|
|
if (currentType === 'supply') {
|
|
currentStatus = 'pending_review';
|
|
loadSupplies();
|
|
} else {
|
|
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', () => {
|
|
// 如果是供应商审核状态下,忽略货源审核的状态按钮
|
|
if (currentType === 'supplier' && !btn.closest('.supplier-status-nav')) {
|
|
return;
|
|
}
|
|
// 如果是货源审核状态下,忽略供应商审核的状态按钮
|
|
if (currentType === 'supply' && !btn.closest('.supply-status-nav')) {
|
|
return;
|
|
}
|
|
|
|
// 更新同类状态按钮的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;
|
|
|
|
if (currentType === 'supply') {
|
|
loadSupplies();
|
|
} else {
|
|
loadSuppliers();
|
|
}
|
|
});
|
|
});
|
|
|
|
// 类型切换器点击事件
|
|
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 = currentType === 'supply' ? 'flex' : 'none';
|
|
document.querySelector('.supplier-status-nav').style.display = currentType === 'supply' ? 'none' : 'flex';
|
|
|
|
// 重置状态和页面
|
|
currentPage = 1;
|
|
if (currentType === 'supply') {
|
|
currentStatus = 'pending_review';
|
|
// 触发待审核状态按钮的点击事件
|
|
const pendingReviewBtn = document.querySelector('.supply-status-nav button[data-status="pending_review"]');
|
|
if (pendingReviewBtn) pendingReviewBtn.click();
|
|
} else {
|
|
currentStatus = 'underreview';
|
|
// 触发审核中状态按钮的点击事件
|
|
const underreviewBtn = document.querySelector('.supplier-status-nav button[data-status="underreview"]');
|
|
if (underreviewBtn) underreviewBtn.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) {
|
|
if (currentType === 'supply') {
|
|
showApproveModal(id);
|
|
} else {
|
|
// 检查按钮文本或样式来区分操作类型
|
|
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 (currentType === 'supply') {
|
|
showRejectModal(id);
|
|
} else {
|
|
// 检查按钮文本或样式来区分操作类型
|
|
if (e.target.textContent.trim() === '终止合作') {
|
|
showTerminateModal(id);
|
|
} else {
|
|
showSupplierRejectModal(id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// 创建货源按钮点击事件 - 跳转到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() {
|
|
supplyListEl.innerHTML = '<div class="loading">加载中...</div>';
|
|
|
|
try {
|
|
const searchKeyword = searchInputEl.value.trim();
|
|
const phoneNumber = phoneInputEl.value.trim();
|
|
|
|
// 获取当前登录用户信息
|
|
const userInfo = JSON.parse(localStorage.getItem('userInfo'));
|
|
const userId = userInfo ? userInfo.userId : null;
|
|
|
|
// 构建查询参数,添加时间戳防止缓存
|
|
let queryParams = `page=${currentPage}&pageSize=${pageSize}&status=${currentStatus}&_t=${Date.now()}`;
|
|
if (searchKeyword) {
|
|
queryParams += `&keyword=${encodeURIComponent(searchKeyword)}`;
|
|
}
|
|
if (phoneNumber) {
|
|
queryParams += `&phoneNumber=${encodeURIComponent(phoneNumber)}`;
|
|
}
|
|
// 审核页面显示所有用户的货源,不需要添加sellerId筛选
|
|
|
|
// 使用相对路径,避免硬编码地址导致的跨电脑访问问题
|
|
const response = await fetch(`/api/supplies?${queryParams}`);
|
|
|
|
if (!response.ok) {
|
|
throw new Error('服务器响应异常');
|
|
}
|
|
|
|
// 直接使用Text格式先获取原始响应,确保正确解析
|
|
const responseText = await response.text();
|
|
console.log('原始响应文本:', responseText);
|
|
|
|
// 然后再解析为JSON
|
|
let data;
|
|
try {
|
|
data = JSON.parse(responseText);
|
|
console.log('成功解析JSON数据');
|
|
} catch (parseError) {
|
|
console.error('JSON解析失败:', parseError);
|
|
console.error('原始响应:', responseText);
|
|
throw new Error('返回数据格式错误');
|
|
}
|
|
|
|
// 详细打印数据结构
|
|
console.log('完整API返回数据类型:', typeof data);
|
|
console.log('完整API返回数据结构:', JSON.stringify(data, null, 2));
|
|
|
|
// 添加详细的数据结构检查,特别是图片字段
|
|
if (data && data.data && data.data.supplies && Array.isArray(data.data.supplies)) {
|
|
console.log('响应数据包含supplies数组,共', data.data.supplies.length, '条记录');
|
|
// 检查前3条记录的结构,特别关注图片相关字段
|
|
data.data.supplies.slice(0, 3).forEach((item, index) => {
|
|
console.log(`第${index + 1}条记录的图片相关字段:`);
|
|
console.log('- imageUrls:', item.imageUrls);
|
|
console.log('- images:', item.images);
|
|
console.log('- imgUrls:', item.imgUrls);
|
|
console.log('- picUrls:', item.picUrls);
|
|
console.log('- imageUrl:', item.imageUrl);
|
|
console.log('- image:', item.image);
|
|
console.log('- imgUrl:', item.imgUrl);
|
|
console.log('- picUrl:', item.picUrl);
|
|
});
|
|
} else if (data && data.supplies && Array.isArray(data.supplies)) {
|
|
console.log('响应数据包含supplies数组,共', data.supplies.length, '条记录');
|
|
data.supplies.slice(0, 3).forEach((item, index) => {
|
|
console.log(`第${index + 1}条记录的图片相关字段:`);
|
|
console.log('- imageUrls:', item.imageUrls);
|
|
console.log('- images:', item.images);
|
|
console.log('- imgUrls:', item.imgUrls);
|
|
console.log('- picUrls:', item.picUrls);
|
|
console.log('- imageUrl:', item.imageUrl);
|
|
console.log('- image:', item.image);
|
|
console.log('- imgUrl:', item.imgUrl);
|
|
console.log('- picUrl:', item.picUrl);
|
|
});
|
|
} else if (data && data.data && Array.isArray(data.data)) {
|
|
console.log('响应数据包含data数组,共', data.data.length, '条记录');
|
|
data.data.slice(0, 3).forEach((item, index) => {
|
|
console.log(`第${index + 1}条记录的图片相关字段:`);
|
|
console.log('- imageUrls:', item.imageUrls);
|
|
console.log('- images:', item.images);
|
|
console.log('- imgUrls:', item.imgUrls);
|
|
console.log('- picUrls:', item.picUrls);
|
|
console.log('- imageUrl:', item.imageUrl);
|
|
console.log('- image:', item.image);
|
|
console.log('- imgUrl:', item.imgUrl);
|
|
console.log('- picUrl:', item.picUrl);
|
|
});
|
|
} else if (Array.isArray(data)) {
|
|
console.log('响应数据是数组,共', data.length, '条记录');
|
|
data.slice(0, 3).forEach((item, index) => {
|
|
console.log(`第${index + 1}条记录的图片相关字段:`);
|
|
console.log('- imageUrls:', item.imageUrls);
|
|
console.log('- images:', item.images);
|
|
console.log('- imgUrls:', item.imgUrls);
|
|
console.log('- picUrls:', item.picUrls);
|
|
console.log('- imageUrl:', item.imageUrl);
|
|
console.log('- image:', item.image);
|
|
console.log('- imgUrl:', item.imgUrl);
|
|
console.log('- picUrl:', item.picUrl);
|
|
});
|
|
} else {
|
|
console.log('响应数据既不是预期的数组格式也不包含supplies字段');
|
|
}
|
|
|
|
// 宽松处理,适应多种可能的数据格式
|
|
let suppliesList = [];
|
|
let totalCount = 0;
|
|
|
|
// 情况1: 标准格式 {success: true, data: {list: [], total: n}} - 后端实际返回格式
|
|
if (data.success === true && data.data && data.data.list && Array.isArray(data.data.list)) {
|
|
suppliesList = data.data.list;
|
|
totalCount = data.data.total || suppliesList.length;
|
|
console.log('情况1 - 后端实际返回格式: 从data.data.list中获取了', suppliesList.length, '条记录');
|
|
}
|
|
// 情况2: 标准格式 {success: true, data: {supplies: [], total: n}}
|
|
else if (data.success === true && data.data && data.data.supplies && Array.isArray(data.data.supplies)) {
|
|
suppliesList = data.data.supplies;
|
|
totalCount = data.data.total || suppliesList.length;
|
|
console.log('情况2 - 备用格式: 从data.data.supplies中获取了', suppliesList.length, '条记录');
|
|
}
|
|
// 情况2: 简化格式 {data: []} 或 {supplies: []}
|
|
else if (data.data && Array.isArray(data.data)) {
|
|
suppliesList = data.data;
|
|
totalCount = suppliesList.length;
|
|
console.log('情况2 - 简化格式: 从data.data中获取了', suppliesList.length, '条记录');
|
|
}
|
|
else if (data.supplies && Array.isArray(data.supplies)) {
|
|
suppliesList = data.supplies;
|
|
totalCount = data.total || suppliesList.length;
|
|
console.log('情况2 - 简化格式: 从data.supplies中获取了', suppliesList.length, '条记录');
|
|
}
|
|
// 情况3: 直接返回数组
|
|
else if (Array.isArray(data)) {
|
|
suppliesList = data;
|
|
totalCount = suppliesList.length;
|
|
console.log('情况3 - 直接数组: 从根级获取了', suppliesList.length, '条记录');
|
|
}
|
|
// 情况4: 检查data是否为对象数组的可能性
|
|
else if (data.data && typeof data.data === 'object' && !Array.isArray(data.data) && Object.keys(data.data).length > 0) {
|
|
// 尝试将对象转换为数组(可能是索引对象)
|
|
const values = Object.values(data.data);
|
|
if (values.length > 0 && typeof values[0] === 'object') {
|
|
suppliesList = values;
|
|
totalCount = suppliesList.length;
|
|
console.log('情况4 - 对象转数组: 从data.data对象转换为数组,获取了', suppliesList.length, '条记录');
|
|
}
|
|
}
|
|
// 其他情况
|
|
else {
|
|
console.warn('未识别的数据格式,尝试提取可能的数组字段:');
|
|
|
|
// 检查常见的数组字段名称
|
|
const arrayFields = ['supplies', 'products', 'items', 'data', 'list', 'records', 'results'];
|
|
|
|
for (const field of arrayFields) {
|
|
if (data[field] && Array.isArray(data[field])) {
|
|
suppliesList = data[field];
|
|
totalCount = data.total || suppliesList.length;
|
|
console.log(`发现数组字段${field}: 获取了`, suppliesList.length, '条记录');
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 显示数据详情
|
|
console.log('总记录数:', totalCount);
|
|
|
|
// 更新总货源数量显示
|
|
const totalCountEl = document.getElementById('totalCount');
|
|
if (totalCountEl) {
|
|
totalCountEl.textContent = totalCount;
|
|
}
|
|
|
|
if (suppliesList.length > 0) {
|
|
console.log('第一个数据项的所有字段:', Object.keys(suppliesList[0]));
|
|
console.log('第一个数据项详情:', JSON.stringify(suppliesList[0], null, 2));
|
|
}
|
|
|
|
console.log('最终处理结果:', {
|
|
suppliesLength: suppliesList.length,
|
|
totalCount: totalCount
|
|
});
|
|
|
|
// 先加载联系人数据,再渲染货源列表,确保联系人下拉框能正确显示选中状态
|
|
await loadContacts();
|
|
renderSupplies(suppliesList);
|
|
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>
|
|
<p class="error-details">详细错误: ${error.message}</p>
|
|
<button class="btn-primary" onclick="loadSupplies()">重新加载</button>
|
|
</div>`;
|
|
}
|
|
}
|
|
|
|
// 渲染货源列表
|
|
function renderSupplies(supplies) {
|
|
console.log('渲染数据类型:', typeof supplies, '数据是否为数组:', Array.isArray(supplies));
|
|
console.log('渲染数据详情:', JSON.stringify(supplies, null, 2));
|
|
|
|
// 确保supplies是数组
|
|
if (!supplies || !Array.isArray(supplies)) {
|
|
console.error('supplies不是有效的数组:', supplies);
|
|
supplyListEl.innerHTML = `<div class="empty-state">
|
|
<p>数据格式错误,请刷新页面重试</p>
|
|
</div>`;
|
|
return;
|
|
}
|
|
|
|
if (supplies.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="loadSupplies()">刷新列表</button>
|
|
</div>`;
|
|
return;
|
|
}
|
|
|
|
// 显示第一个数据项的所有字段,方便调试
|
|
console.log('第一个数据项的所有字段:', Object.keys(supplies[0]));
|
|
|
|
supplyListEl.innerHTML = supplies.map((product, index) => {
|
|
// 使用更灵活的字段映射,考虑多种可能的字段名称
|
|
// ID字段
|
|
const id = product.id || product.product_id || product.p_id || product.item_id || `unknown-${index}`;
|
|
|
|
// 名称字段 - 更全面的映射
|
|
const name = product.name || product.title || product.productName || product.product_name
|
|
|| product.goodsName || product.goods_name || product.itemName || product.item_name
|
|
|| '未命名商品';
|
|
|
|
// 价格字段 - 支持更多可能的名称
|
|
const price = product.price || product.cost || product.unitPrice || product.unit_price
|
|
|| product.selling_price || product.amount || '0.00';
|
|
|
|
// 数量字段 - 更多映射选项
|
|
const quantity = product.quantity || product.stock || product.inventory || product.count
|
|
|| product.num || product.amount || '0';
|
|
|
|
// 重量字段
|
|
const weight = product.weight || product.grossWeight || product.netWeight || product.gross_weight
|
|
|| product.net_weight || product.unit_weight || '0';
|
|
|
|
// 蛋黄字段
|
|
const yolk = product.yolk || product.yolk_info || product.egg_yolk || '未知';
|
|
|
|
// 描述字段 - 更全面的映射
|
|
const description = product.description || product.remark || product.detail || product.info
|
|
|| product.intro || product.note || '';
|
|
|
|
// 创建时间 - 支持更多时间字段名称
|
|
const createdAt = product.createdAt || product.created_at || product.create_time || product.add_time
|
|
|| product.upload_time || new Date().toISOString();
|
|
|
|
// 状态字段 - 更灵活的状态处理
|
|
const status = product.status || product.state || product.audit_status || 'pending';
|
|
|
|
// 拒绝理由字段
|
|
const rejectReason = product.rejectReason || product.reject_reason || product.reason || product.note || '';
|
|
|
|
// 地区字段
|
|
const region = product.region || product.address || product.location || '';
|
|
|
|
// 渲染图片
|
|
let imageHtml = '';
|
|
let imageUrls = [];
|
|
|
|
// 1. 优先使用优化后的imageUrls字段(云存储架构的主要字段)
|
|
if (product.imageUrls && Array.isArray(product.imageUrls) && product.imageUrls.length > 0) {
|
|
// 过滤并验证URLs
|
|
imageUrls = product.imageUrls.filter(url => {
|
|
if (!url || typeof url !== 'string') return false;
|
|
const trimmedUrl = url.trim();
|
|
return trimmedUrl && (trimmedUrl.startsWith('http://') || trimmedUrl.startsWith('https://'));
|
|
});
|
|
} else if (product.imageUrls && typeof product.imageUrls === 'string') {
|
|
try {
|
|
// 处理JSON字符串格式的图片URLs
|
|
const parsedImages = JSON.parse(product.imageUrls);
|
|
if (Array.isArray(parsedImages)) {
|
|
imageUrls = parsedImages.filter(url => {
|
|
if (!url || typeof url !== 'string') return false;
|
|
const trimmedUrl = url.trim();
|
|
return trimmedUrl && (trimmedUrl.startsWith('http://') || trimmedUrl.startsWith('https://'));
|
|
});
|
|
}
|
|
} catch (e) {
|
|
console.log('解析imageUrls字符串失败:', e);
|
|
}
|
|
}
|
|
|
|
// 2. 检查图片元数据(云存储架构的扩展字段)
|
|
if (product.imageMetadata) {
|
|
console.log(`产品ID ${id} 图片元数据:`, product.imageMetadata);
|
|
// 可以根据元数据执行特定操作,如显示来源标记等
|
|
}
|
|
|
|
// 3. 如果主字段没有有效图片,尝试其他可能的图片字段
|
|
if (imageUrls.length === 0) {
|
|
const possibleImageFields = ['images', 'imgUrls', 'picUrls', 'pictures', 'imageUrl', 'image', 'imgUrl', 'picUrl', 'picture'];
|
|
for (const field of possibleImageFields) {
|
|
if (product[field]) {
|
|
if (Array.isArray(product[field])) {
|
|
const validUrls = product[field].filter(url => {
|
|
if (!url || typeof url !== 'string') return false;
|
|
const trimmedUrl = url.trim();
|
|
return trimmedUrl && (trimmedUrl.startsWith('http://') || trimmedUrl.startsWith('https://'));
|
|
});
|
|
if (validUrls.length > 0) {
|
|
imageUrls = validUrls;
|
|
break;
|
|
}
|
|
} else if (typeof product[field] === 'string') {
|
|
const trimmedUrl = product[field].trim();
|
|
if (trimmedUrl && (trimmedUrl.startsWith('http://') || trimmedUrl.startsWith('https://'))) {
|
|
imageUrls = [trimmedUrl];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 4. 如果仍然没有找到图片,使用内联SVG数据URI作为占位图,避免依赖外部服务
|
|
if (imageUrls.length === 0) {
|
|
// 基于产品ID生成一致的占位图颜色
|
|
const color = `hsl(${(id || 0) * 37 % 360}, 70%, 80%)`;
|
|
const textColor = '666666';
|
|
// 使用内联SVG数据URI,避免外部服务依赖
|
|
const placeholder = `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="#${textColor}" text-anchor="middle" dominant-baseline="middle">暂无图片</text>
|
|
</svg>
|
|
`)}`;
|
|
imageUrls = [placeholder];
|
|
console.log(`产品ID ${id} 使用内联SVG占位图`);
|
|
}
|
|
|
|
// 显示所有可用图片,不再限制数量
|
|
// imageUrls = imageUrls.slice(0, 1); // 移除数量限制
|
|
|
|
// 状态处理更灵活
|
|
let statusClass = '';
|
|
let statusText = '';
|
|
|
|
// 根据当前类型应用不同状态映射
|
|
if (currentType === 'supply') {
|
|
// 货源审核状态
|
|
if (status === 'pending_review') {
|
|
statusClass = 'status-pending';
|
|
statusText = '审核中';
|
|
} else if (status === 'published') {
|
|
statusClass = 'status-published';
|
|
statusText = '已上架';
|
|
} else if (status === 'rejected') {
|
|
statusClass = 'status-rejected';
|
|
statusText = '审核失败';
|
|
} else if (status === 'sold_out') {
|
|
statusClass = 'status-soldout';
|
|
statusText = '已售罄';
|
|
} else if (status === 'hidden') {
|
|
statusClass = 'status-hidden';
|
|
statusText = '已下架';
|
|
}
|
|
} else if (currentType === 'supplier') {
|
|
// 供应商审核状态
|
|
if (status === 'underreview') {
|
|
statusClass = 'status-underreview';
|
|
statusText = '审核中';
|
|
} else if (status === 'reviewfailed') {
|
|
statusClass = 'status-reviewfailed';
|
|
statusText = '审核失败';
|
|
} else if (status === 'approved') {
|
|
statusClass = 'status-approved';
|
|
statusText = '审核通过';
|
|
} else if (status === 'incooperation') {
|
|
statusClass = 'status-incooperation';
|
|
statusText = '合作中';
|
|
} else if (status === 'notcooperative') {
|
|
statusClass = 'status-notcooperative';
|
|
statusText = '未合作';
|
|
}
|
|
}
|
|
|
|
// 根据当前类型判断可审核状态
|
|
let canReview = false;
|
|
if (currentType === 'supply') {
|
|
// 货源只有在pending_review状态下可以审核
|
|
canReview = status === 'pending_review';
|
|
} else if (currentType === 'supplier') {
|
|
// 供应商只有在underreview状态下可以审核
|
|
canReview = status === 'underreview';
|
|
}
|
|
|
|
// 生成HTML,简化结构确保能正确显示
|
|
return `
|
|
<div class="supply-item" data-id="${id}">
|
|
<div class="supply-header">
|
|
<div class="supply-title">${name}</div>
|
|
<div class="supply-status ${statusClass}">${statusText}</div>
|
|
</div>
|
|
|
|
<!-- 图片展示区域 -->
|
|
<div class="supply-images">
|
|
${imageUrls.map(url => {
|
|
// 直接使用原始URL,不再强制替换阿里云OSS图片
|
|
let displayUrl = url;
|
|
// 为所有URL添加时间戳防止缓存
|
|
displayUrl = `${displayUrl}?t=${new Date().getTime()}`;
|
|
console.log(`产品ID ${id} 使用原始图片URL: ${displayUrl}`);
|
|
|
|
return `
|
|
<div class="supply-image-item" style="background-color: transparent;">
|
|
<!-- 图片加载前显示骨架屏 -->
|
|
<div class="image-skeleton"></div>
|
|
<img
|
|
src="${displayUrl}"
|
|
alt="${name}"
|
|
loading="lazy"
|
|
onerror="handleImageError(this, '${id}')"
|
|
onload="hideImageSkeleton(this)"
|
|
data-product-image="true"
|
|
style="display: block; opacity: 1; transition: none; cursor: pointer;"
|
|
>
|
|
<!-- 可选:显示图片来源标记 -->
|
|
${url.includes('placeholder') ?
|
|
'<span class="placeholder-tag">占位图</span>' : ''}
|
|
</div>
|
|
`;
|
|
}).join('')}
|
|
</div>
|
|
|
|
<!-- 第一行:蛋黄、数量、重量、价格 -->
|
|
<div class="supply-info">
|
|
<div class="info-item">
|
|
<div class="info-label">蛋黄</div>
|
|
<div class="info-value">${yolk}</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">数量</div>
|
|
<div class="info-value">${quantity}</div>
|
|
<!-- 地区放在数量下面 -->
|
|
<br>
|
|
<br>
|
|
<div class="info-label">地区</div>
|
|
<div class="info-value">${region || '未知'}</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">重量</div>
|
|
<div class="info-value">${weight} kg</div>
|
|
<!-- 用户信息放在重量下面 -->
|
|
<br>
|
|
<br>
|
|
<div class="info-label">用户信息</div>
|
|
<div class="info-value">昵称: ${product.nickName || '未知'}</div>
|
|
<div class="info-value">电话: ${product.phoneNumber || '未知'}</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">价格</div>
|
|
<div class="info-value">¥${price}</div>
|
|
<!-- 创建时间放在价格下面 -->
|
|
<br>
|
|
<br>
|
|
<div class="info-label">创建时间</div>
|
|
<div class="info-value">${formatTime(createdAt)}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ID单独一行,上移十二个字距离 -->
|
|
<div class="supply-info" style="margin-top: -60px;">
|
|
<div class="info-item">
|
|
<div class="info-label">ID</div>
|
|
<div class="info-value">${id}</div>
|
|
</div>
|
|
</div>
|
|
|
|
${description ? `
|
|
<div class="info-item">
|
|
<div class="info-label">描述</div>
|
|
<div class="info-value">${description}</div>
|
|
</div>
|
|
` : ''}
|
|
|
|
${rejectReason ? `
|
|
<div class="supply-reject-reason">
|
|
<div class="label">拒绝理由:</div>
|
|
<div class="reason">${rejectReason}</div>
|
|
</div>
|
|
` : ''}
|
|
|
|
<!-- 联系人分配区域 -->
|
|
${currentType === 'supply' ? `
|
|
${status !== 'pending_review' ? `
|
|
<div class="supply-contact">
|
|
<div class="contact-label">联系人:</div>
|
|
<div class="contact-info">
|
|
<div class="contact-name">${product.product_contact || '未分配'}</div>
|
|
${product.contact_phone ? `<div class="contact-phone">${product.contact_phone}</div>` : ''}
|
|
</div>
|
|
</div>
|
|
` : ''}
|
|
` : `
|
|
<div class="supply-contact">
|
|
<div class="contact-label">联系人:</div>
|
|
${canReview ? `
|
|
<select class="contact-select" data-id="${id}">
|
|
<option value="">请选择联系人</option>
|
|
${Array.isArray(contacts) ? contacts.map(contact => {
|
|
// 检查产品表中的联系人是否匹配当前联系人(支持姓名、销售人-姓名、电话号码三种匹配方式)
|
|
const isSelected = product.product_contact && (
|
|
product.product_contact === `${contact.salesPerson} - ${contact.name}` ||
|
|
product.product_contact === `${contact.name}` ||
|
|
product.contact_phone === contact.phoneNumber
|
|
);
|
|
return `
|
|
<option value="${contact.id}" ${isSelected ? 'selected' : ''}>
|
|
${contact.name} (${contact.phoneNumber})
|
|
</option>
|
|
`;
|
|
}).join('') : ''}
|
|
${product.product_contact ? `
|
|
${(!Array.isArray(contacts) || !contacts.some(contact =>
|
|
product.product_contact === `${contact.salesPerson} - ${contact.name}` ||
|
|
product.product_contact === `${contact.name}` ||
|
|
product.contact_phone === contact.phoneNumber)) ? `
|
|
<option value="custom" selected>
|
|
${product.product_contact} (${product.contact_phone || '未知号码'})
|
|
</option>
|
|
` : ''}
|
|
` : ''}
|
|
</select>
|
|
` : `
|
|
<div class="contact-info">
|
|
<div class="contact-name">${product.product_contact || '未分配'}</div>
|
|
${product.contact_phone ? `<div class="contact-phone">${product.contact_phone}</div>` : ''}
|
|
</div>
|
|
`}
|
|
</div>
|
|
`}
|
|
|
|
<div class="action-buttons">
|
|
${canReview ? `
|
|
<button class="btn btn-primary" data-id="${id}">
|
|
通过
|
|
</button>
|
|
<button class="btn btn-danger" data-id="${id}">
|
|
拒绝
|
|
</button>
|
|
` : `
|
|
<div class="audit-time">
|
|
审核时间:${formatTime(product.audit_time || createdAt)}
|
|
</div>
|
|
`}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
console.log('成功渲染了', supplies.length, '条数据');
|
|
|
|
// 绑定联系人选择事件
|
|
bindContactSelectEvents();
|
|
}
|
|
|
|
// 绑定联系人选择事件
|
|
function bindContactSelectEvents() {
|
|
const selectElements = document.querySelectorAll('.contact-select');
|
|
selectElements.forEach(select => {
|
|
// 移除已存在的事件监听器(防止重复绑定)
|
|
select.removeEventListener('change', handleContactChange);
|
|
// 添加新的事件监听器
|
|
select.addEventListener('change', handleContactChange);
|
|
});
|
|
}
|
|
|
|
// 处理联系人选择变化
|
|
function handleContactChange(event) {
|
|
const select = event.target;
|
|
const supplyId = select.dataset.id;
|
|
const contactId = parseInt(select.value); // 将contactId转换为数字类型
|
|
|
|
if (!contactId) {
|
|
// 如果选择的是默认选项,不执行更新
|
|
select.dataset.selected = '';
|
|
return;
|
|
}
|
|
|
|
// 获取选中的联系人信息
|
|
const selectedContact = contacts.find(contact => contact.id === contactId);
|
|
if (!selectedContact) {
|
|
console.error('未找到选中的联系人信息:', contactId);
|
|
return;
|
|
}
|
|
|
|
// 更新下拉框的选中状态
|
|
select.dataset.selected = contactId;
|
|
|
|
// 调用API更新货源的联系人信息
|
|
updateSupplyContact(supplyId, selectedContact);
|
|
}
|
|
|
|
// 更新货源的联系人信息
|
|
function updateSupplyContact(supplyId, contact) {
|
|
fetch(`/api/supplies/${supplyId}/contact`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
productContact: `${contact.name}`,
|
|
contactPhone: contact.phoneNumber
|
|
})
|
|
})
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
throw new Error('服务器响应异常');
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(data => {
|
|
if (data.success) {
|
|
console.log('联系人分配成功:', data);
|
|
// 显示成功提示
|
|
showSuccessModal('联系人分配成功');
|
|
} else {
|
|
console.error('联系人分配失败:', data.message);
|
|
alert('联系人分配失败: ' + (data.message || '未知错误'));
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('更新联系人信息失败:', error);
|
|
alert('网络错误,请稍后重试');
|
|
});
|
|
}
|
|
|
|
// 渲染分页
|
|
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;
|
|
if (currentType === 'supply') {
|
|
loadSupplies();
|
|
} else {
|
|
loadSuppliers();
|
|
}
|
|
}
|
|
|
|
// 处理搜索
|
|
function handleSearch() {
|
|
if (currentType === 'supply') {
|
|
loadSupplies();
|
|
} else {
|
|
loadSuppliers();
|
|
}
|
|
}
|
|
|
|
// 处理刷新
|
|
function handleRefresh() {
|
|
if (currentType === 'supply') {
|
|
loadSupplies();
|
|
} else {
|
|
loadSuppliers();
|
|
}
|
|
}
|
|
|
|
// 显示拒绝理由弹窗
|
|
function showRejectModal(supplyId) {
|
|
currentSupplyId = supplyId;
|
|
rejectReasonEl.value = '';
|
|
charCountEl.textContent = '0';
|
|
charCountEl.style.color = '#999';
|
|
rejectModalEl.style.display = 'flex';
|
|
rejectReasonEl.focus();
|
|
}
|
|
|
|
// 关闭拒绝理由弹窗
|
|
function closeRejectModal() {
|
|
rejectModalEl.style.display = 'none';
|
|
currentSupplyId = null;
|
|
}
|
|
|
|
// 确认拒绝
|
|
async function confirmReject() {
|
|
const reason = rejectReasonEl.value.trim();
|
|
|
|
if (!reason) {
|
|
alert('请输入拒绝理由');
|
|
return;
|
|
}
|
|
|
|
if (reason.length > 500) {
|
|
alert('拒绝理由不能超过500个字符');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// 判断是货源拒绝还是供应商拒绝
|
|
let apiUrl = '';
|
|
if (currentSupplyId) {
|
|
apiUrl = `/api/supplies/${currentSupplyId}/reject?_t=${Date.now()}`;
|
|
} else if (currentSupplierId) {
|
|
apiUrl = `/api/suppliers/${currentSupplierId}/reject?_t=${Date.now()}`;
|
|
} else {
|
|
alert('未找到待处理的ID');
|
|
return;
|
|
}
|
|
|
|
// 添加时间戳参数防止缓存
|
|
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();
|
|
// 强制重新加载数据,确保与数据库同步
|
|
console.log('拒绝成功,重新加载数据...');
|
|
// 清除可能的缓存状态
|
|
if ('caches' in window) {
|
|
if (currentSupplyId) {
|
|
caches.delete(`/api/supplies?page=${currentPage}&pageSize=${pageSize}&status=${currentStatus}`).catch(e => console.warn('清除缓存失败:', e));
|
|
// 重新加载数据
|
|
loadSupplies();
|
|
} else if (currentSupplierId) {
|
|
caches.delete(`/api/suppliers?page=${currentPage}&pageSize=${pageSize}&status=${currentStatus}`).catch(e => console.warn('清除缓存失败:', e));
|
|
// 重新加载数据
|
|
loadSuppliers(); // 注意:这里使用相同的函数名,确保loadSuppliers能处理两种情况
|
|
}
|
|
}
|
|
// 显示自定义成功弹窗
|
|
showSuccessModal('拒绝成功');
|
|
} else {
|
|
alert(data.message || '拒绝失败');
|
|
}
|
|
} catch (error) {
|
|
console.error('拒绝货源失败:', error);
|
|
alert('网络错误,请稍后重试');
|
|
}
|
|
}
|
|
|
|
// 显示通过确认弹窗
|
|
function showApproveModal(supplyId) {
|
|
currentSupplyId = supplyId;
|
|
approveModalEl.style.display = 'flex';
|
|
}
|
|
|
|
// 关闭通过确认弹窗
|
|
function closeApproveModal() {
|
|
approveModalEl.style.display = 'none';
|
|
currentSupplyId = 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 loadContacts() {
|
|
try {
|
|
const response = await fetch('/api/contacts');
|
|
if (!response.ok) {
|
|
throw new Error('服务器响应异常');
|
|
}
|
|
const data = await response.json();
|
|
// 确保contacts是一个数组
|
|
contacts = data.data || [];
|
|
console.log('联系人数据加载成功:', contacts);
|
|
// 加载联系人数据后,更新所有下拉框
|
|
updateContactSelects();
|
|
} catch (error) {
|
|
console.error('加载联系人数据失败:', error);
|
|
// 出错时将contacts设为空数组,避免后续错误
|
|
contacts = [];
|
|
}
|
|
}
|
|
|
|
// 更新所有联系人选择下拉框
|
|
function updateContactSelects() {
|
|
const selectElements = document.querySelectorAll('.contact-select');
|
|
selectElements.forEach(select => {
|
|
// 获取对应的产品ID
|
|
const supplyId = select.dataset.id;
|
|
|
|
// 清空现有选项
|
|
select.innerHTML = '';
|
|
|
|
// 添加默认选项
|
|
const defaultOption = document.createElement('option');
|
|
defaultOption.value = '';
|
|
defaultOption.textContent = '请选择联系人';
|
|
select.appendChild(defaultOption);
|
|
|
|
// 添加联系人选项,只显示有电话号码的联系人
|
|
contacts.forEach(contact => {
|
|
// 确保联系人有电话号码
|
|
if (contact.phoneNumber && contact.phoneNumber.trim() !== '') {
|
|
const option = document.createElement('option');
|
|
option.value = contact.id;
|
|
option.textContent = `${contact.name} (${contact.phoneNumber})`;
|
|
select.appendChild(option);
|
|
}
|
|
});
|
|
|
|
// 查找对应的产品数据,设置选中状态
|
|
const supplyItem = document.querySelector(`.supply-item[data-id="${supplyId}"]`);
|
|
if (supplyItem) {
|
|
// 从DOM中获取产品数据(如果有)
|
|
const productData = supplyItem.dataset;
|
|
// 或者可以重新查询API获取最新数据
|
|
// 这里我们直接使用现有的产品数据
|
|
fetch(`/api/supplies?search=${supplyId}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success && data.list && data.list.length > 0) {
|
|
const product = data.list[0];
|
|
if (product.product_contact) {
|
|
// 查找匹配的联系人ID,支持新旧格式
|
|
const matchedContact = contacts.find(contact =>
|
|
`${contact.salesPerson} - ${contact.name}` === product.product_contact ||
|
|
contact.name === product.product_contact ||
|
|
contact.phoneNumber === product.contact_phone
|
|
);
|
|
if (matchedContact) {
|
|
select.value = matchedContact.id;
|
|
select.dataset.selected = matchedContact.id;
|
|
} else {
|
|
// 如果没有匹配的联系人,添加一个自定义选项显示产品表中的联系人信息
|
|
const customOption = document.createElement('option');
|
|
customOption.value = 'custom';
|
|
customOption.textContent = `${product.product_contact} (${product.contact_phone})`;
|
|
customOption.selected = true;
|
|
select.appendChild(customOption);
|
|
select.dataset.selected = 'custom';
|
|
}
|
|
}
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('获取产品联系人信息失败:', error);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
// 绑定成功弹窗的确认按钮
|
|
document.getElementById('successConfirmBtn').addEventListener('click', closeSuccessModal);
|
|
|
|
// 确认通过
|
|
async function confirmApprove() {
|
|
// 检查是货源审核还是供应商审核
|
|
let isSupply = false;
|
|
let isSupplier = false;
|
|
let id = null;
|
|
|
|
if (currentSupplyId) {
|
|
isSupply = true;
|
|
id = currentSupplyId;
|
|
|
|
// 检查当前货源是否已经分配了联系人
|
|
const selectElement = document.querySelector(`.contact-select[data-id="${id}"]`);
|
|
if (selectElement) {
|
|
const contactId = selectElement.dataset.selected || selectElement.value;
|
|
if (!contactId) {
|
|
alert('请先为该货源分配联系人,然后再进行审核通过操作');
|
|
return;
|
|
}
|
|
} else {
|
|
// 如果没有找到下拉框(可能已经审核过),可以跳过检查
|
|
console.log('未找到联系人下拉框,可能该货源已审核');
|
|
}
|
|
} else if (currentSupplierId) {
|
|
isSupplier = true;
|
|
id = currentSupplierId;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// 使用相对路径确保请求正确发送
|
|
const apiUrl = isSupply
|
|
? `/api/supplies/${id}/approve`
|
|
: `/api/suppliers/${id}/approve`;
|
|
const response = await fetch(apiUrl, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({}) // 确保发送空对象作为请求体,避免req.body为undefined
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('服务器响应异常');
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
closeApproveModal();
|
|
// 强制重新加载数据,确保与数据库同步
|
|
console.log('审核通过成功,重新加载数据...');
|
|
// 清除可能的缓存状态
|
|
if ('caches' in window) {
|
|
if (isSupply) {
|
|
caches.delete(`/api/supplies?page=${currentPage}&pageSize=${pageSize}&status=${currentStatus}`).catch(e => console.warn('清除缓存失败:', e));
|
|
} else if (isSupplier) {
|
|
caches.delete(`/api/suppliers?page=${currentPage}&pageSize=${pageSize}&status=${currentStatus}`).catch(e => console.warn('清除缓存失败:', e));
|
|
}
|
|
}
|
|
// 重新加载数据
|
|
loadSupplies();
|
|
// 显示自定义成功弹窗
|
|
showSuccessModal(isSupply ? '通过成功' : '供应商通过成功');
|
|
} 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';
|
|
|
|
// 记录详细错误日志
|
|
console.warn(`产品ID ${productId} 图片加载失败!`, {
|
|
originalSrc: imgElement.src,
|
|
errorInfo: imgElement.error ? imgElement.error.message : '未知错误'
|
|
});
|
|
|
|
// 使用内联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';
|
|
console.log('图片加载成功并显示:', imgElement.src);
|
|
}
|
|
|
|
// 创建货源相关函数
|
|
// 显示创建货源模态框
|
|
function showCreateSupplyModal() {
|
|
// 重置表单
|
|
supplyNameEl.value = '';
|
|
supplyPriceEl.value = '';
|
|
supplyMinOrderEl.value = '';
|
|
supplyYolkEl.value = '红心';
|
|
supplySpecEl.value = '格子装';
|
|
supplyRegionEl.value = '';
|
|
supplyGrossWeightEl.value = '';
|
|
supplyImages = [];
|
|
supplyImagesEl.innerHTML = '';
|
|
|
|
// 显示模态框
|
|
createSupplyModalEl.style.display = 'flex';
|
|
}
|
|
|
|
// 关闭创建货源模态框
|
|
function closeCreateSupplyModal() {
|
|
createSupplyModalEl.style.display = 'none';
|
|
}
|
|
|
|
// 处理图片上传
|
|
function handleImageUpload(e) {
|
|
const files = Array.from(e.target.files);
|
|
if (files.length === 0) return;
|
|
|
|
// 限制图片数量
|
|
const remainingSlots = 5 - supplyImages.length;
|
|
if (files.length > remainingSlots) {
|
|
alert(`最多只能上传5张图片,还可以上传${remainingSlots}张`);
|
|
return;
|
|
}
|
|
|
|
// 处理每张图片
|
|
files.forEach(file => {
|
|
// 检查文件类型
|
|
if (!file.type.startsWith('image/')) {
|
|
alert('只能上传图片文件');
|
|
return;
|
|
}
|
|
|
|
// 检查文件大小(限制5MB)
|
|
if (file.size > 5 * 1024 * 1024) {
|
|
alert('图片大小不能超过5MB');
|
|
return;
|
|
}
|
|
|
|
// 预览图片
|
|
const reader = new FileReader();
|
|
reader.onload = (event) => {
|
|
const imageUrl = event.target.result;
|
|
supplyImages.push({
|
|
file: file,
|
|
url: imageUrl
|
|
});
|
|
|
|
// 更新图片预览
|
|
renderImagePreviews();
|
|
};
|
|
reader.readAsDataURL(file);
|
|
});
|
|
|
|
// 清空文件输入,允许再次选择相同的文件
|
|
supplyImageUploadEl.value = '';
|
|
}
|
|
|
|
// 渲染图片预览
|
|
function renderImagePreviews() {
|
|
supplyImagesEl.innerHTML = '';
|
|
|
|
supplyImages.forEach((image, index) => {
|
|
const previewContainer = document.createElement('div');
|
|
previewContainer.style.position = 'relative';
|
|
previewContainer.style.marginRight = '10px';
|
|
previewContainer.style.marginBottom = '10px';
|
|
previewContainer.style.display = 'inline-block';
|
|
|
|
const img = document.createElement('img');
|
|
img.src = image.url;
|
|
img.style.width = '100px';
|
|
img.style.height = '100px';
|
|
img.style.objectFit = 'cover';
|
|
img.style.borderRadius = '4px';
|
|
|
|
const deleteBtn = document.createElement('button');
|
|
deleteBtn.textContent = '×';
|
|
deleteBtn.style.position = 'absolute';
|
|
deleteBtn.style.top = '-5px';
|
|
deleteBtn.style.right = '-5px';
|
|
deleteBtn.style.width = '20px';
|
|
deleteBtn.style.height = '20px';
|
|
deleteBtn.style.border = 'none';
|
|
deleteBtn.style.borderRadius = '50%';
|
|
deleteBtn.style.backgroundColor = 'rgba(255, 0, 0, 0.8)';
|
|
deleteBtn.style.color = 'white';
|
|
deleteBtn.style.fontSize = '14px';
|
|
deleteBtn.style.display = 'flex';
|
|
deleteBtn.style.alignItems = 'center';
|
|
deleteBtn.style.justifyContent = 'center';
|
|
deleteBtn.style.cursor = 'pointer';
|
|
deleteBtn.style.zIndex = '10';
|
|
|
|
// 删除图片
|
|
deleteBtn.addEventListener('click', () => {
|
|
supplyImages.splice(index, 1);
|
|
renderImagePreviews();
|
|
});
|
|
|
|
previewContainer.appendChild(img);
|
|
previewContainer.appendChild(deleteBtn);
|
|
supplyImagesEl.appendChild(previewContainer);
|
|
});
|
|
}
|
|
|
|
// 确认创建货源
|
|
async function confirmCreateSupply() {
|
|
// 获取表单数据
|
|
const name = supplyNameEl.value.trim();
|
|
const price = supplyPriceEl.value.trim();
|
|
const minOrder = supplyMinOrderEl.value.trim();
|
|
const yolk = supplyYolkEl.value;
|
|
const spec = supplySpecEl.value;
|
|
const region = supplyRegionEl.value.trim();
|
|
const grossWeight = supplyGrossWeightEl.value.trim();
|
|
const quality = document.getElementById('supplyQuality').value;
|
|
const contact = document.getElementById('supplyContact').value;
|
|
|
|
// 验证表单
|
|
if (!name) {
|
|
alert('请输入商品名称');
|
|
return;
|
|
}
|
|
if (!price) {
|
|
alert('请输入价格');
|
|
return;
|
|
}
|
|
if (!minOrder) {
|
|
alert('请输入最小起订量');
|
|
return;
|
|
}
|
|
if (!quality) {
|
|
alert('请选择鸡蛋品质');
|
|
return;
|
|
}
|
|
|
|
// 获取当前登录用户信息
|
|
const userInfo = JSON.parse(localStorage.getItem('userInfo'));
|
|
const sellerId = userInfo ? userInfo.userId : null;
|
|
|
|
if (!sellerId) {
|
|
alert('请先登录或联系管理员获取权限');
|
|
window.location.href = 'login.html';
|
|
return;
|
|
}
|
|
|
|
// 创建商品数据
|
|
const productData = {
|
|
productName: name,
|
|
price: parseFloat(price),
|
|
quantity: parseInt(minOrder),
|
|
grossWeight: grossWeight,
|
|
yolk: yolk,
|
|
specification: spec,
|
|
quality: quality,
|
|
region: region,
|
|
rejectReason: '',
|
|
imageUrls: [],
|
|
sellerId: sellerId
|
|
};
|
|
|
|
try {
|
|
// 显示加载状态
|
|
confirmCreateSupplyBtnEl.textContent = '创建中...';
|
|
confirmCreateSupplyBtnEl.disabled = true;
|
|
|
|
// 第一步:准备图片数据 - 提取base64字符串数组
|
|
productData.imageUrls = supplyImages.map(image => image.url);
|
|
console.log('准备上传的图片数据:', productData.imageUrls);
|
|
|
|
// 第二步:创建商品
|
|
console.log('创建商品数据:', productData);
|
|
// 调用实际的API
|
|
const response = await fetch('/api/supplies/create', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(productData)
|
|
});
|
|
|
|
const result = await response.json();
|
|
if (!result.success) {
|
|
throw new Error(result.message || '创建商品失败');
|
|
}
|
|
|
|
// 关闭模态框
|
|
closeCreateSupplyModal();
|
|
|
|
// 显示成功提示
|
|
showSuccessModal('货源创建成功');
|
|
|
|
// 刷新货源列表
|
|
if (currentType === 'supply') {
|
|
loadSupplies();
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('创建货源失败:', error);
|
|
alert('创建货源失败,请稍后重试:' + error.message);
|
|
} finally {
|
|
// 恢复按钮状态
|
|
confirmCreateSupplyBtnEl.textContent = '确认创建';
|
|
confirmCreateSupplyBtnEl.disabled = false;
|
|
}
|
|
}
|
|
|
|
// 供应商相关数据处理函数
|
|
// 当前选中的供应商ID
|
|
let currentSupplierId = null;
|
|
|
|
// 加载供应商列表
|
|
async function loadSuppliers() {
|
|
supplyListEl.innerHTML = '<div class="loading">加载中...</div>';
|
|
|
|
try {
|
|
const searchKeyword = searchInputEl.value.trim();
|
|
const phoneNumber = phoneInputEl.value.trim();
|
|
|
|
// 构建查询参数,添加时间戳防止缓存
|
|
let queryParams = `page=${currentPage}&pageSize=${pageSize}&status=${currentStatus}&_t=${Date.now()}`;
|
|
if (searchKeyword) {
|
|
queryParams += `&keyword=${encodeURIComponent(searchKeyword)}`;
|
|
}
|
|
if (phoneNumber) {
|
|
queryParams += `&phoneNumber=${encodeURIComponent(phoneNumber)}`;
|
|
}
|
|
|
|
// 使用相对路径,避免硬编码地址导致的跨电脑访问问题
|
|
const response = await fetch(`/api/suppliers?${queryParams}`);
|
|
|
|
if (!response.ok) {
|
|
throw new Error('服务器响应异常');
|
|
}
|
|
|
|
// 直接使用Text格式先获取原始响应,确保正确解析
|
|
const responseText = await response.text();
|
|
console.log('原始响应文本:', responseText);
|
|
|
|
// 然后再解析为JSON
|
|
let data;
|
|
try {
|
|
data = JSON.parse(responseText);
|
|
console.log('成功解析JSON数据');
|
|
} catch (parseError) {
|
|
console.error('JSON解析失败:', parseError);
|
|
console.error('原始响应:', responseText);
|
|
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;
|
|
}
|
|
|
|
// 渲染数据
|
|
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>
|
|
<p class="error-details">详细错误: ${error.message}</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 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>
|
|
</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>
|
|
` : ''}
|
|
|
|
<!-- 操作按钮 -->
|
|
<div class="action-buttons">
|
|
${canReview ? `
|
|
<button class="btn btn-primary" data-id="${id}" onclick="showSupplierApproveModal('${id}')">
|
|
通过
|
|
</button>
|
|
<button class="btn btn-danger" data-id="${id}" onclick="showSupplierRejectModal('${id}')">
|
|
拒绝
|
|
</button>
|
|
` : canTerminate ? `
|
|
<button class="btn btn-danger" data-id="${id}" onclick="showTerminateModal('${id}')">
|
|
终止合作
|
|
</button>
|
|
<div class="audit-time">
|
|
审核时间:${formatTime(auditTime)}
|
|
</div>
|
|
` : canCooperate ? `
|
|
<button class="btn btn-primary" data-id="${id}" onclick="confirmSupplierToCooperation('${id}')">
|
|
开始合作
|
|
</button>
|
|
<div class="audit-time">
|
|
审核时间:${formatTime(auditTime)}
|
|
</div>
|
|
` : `
|
|
<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;
|
|
currentSupplyId = null; // 清除货源ID,确保是供应商审核操作
|
|
approveModalEl.style.display = 'flex';
|
|
}
|
|
|
|
// 显示供应商拒绝理由弹窗
|
|
function showSupplierRejectModal(supplierId) {
|
|
currentSupplierId = supplierId;
|
|
currentSupplyId = null; // 清除货源ID,确保是供应商拒绝操作
|
|
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('网络错误,请稍后重试');
|
|
}
|
|
}
|
|
|
|
// 为终止合作模态框添加事件监听
|
|
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';
|
|
}
|
|
});
|
|
}
|
|
|
|
// 将函数暴露到全局
|
|
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.loadSuppliers = loadSuppliers;
|
|
// 修复showImageViewer未定义问题,先定义函数再赋值
|
|
function showImageViewer(imageUrl) {
|
|
const viewer = document.getElementById('imageViewer');
|
|
const viewerImage = document.getElementById('viewerImage');
|
|
viewerImage.src = imageUrl;
|
|
viewer.classList.add('active');
|
|
viewer.style.display = 'flex';
|
|
document.body.style.overflow = 'hidden'; // 阻止背景滚动
|
|
|
|
// 重置缩放和位置
|
|
scale = 1;
|
|
translateX = 0;
|
|
translateY = 0;
|
|
|
|
if (viewerImage.style.transform) {
|
|
viewerImage.style.transform = 'scale(1) translate(0, 0)';
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<!-- 图片查看器模态框 -->
|
|
<div class="image-viewer" id="imageViewer">
|
|
<button class="image-viewer-prev" onclick="showPrevImage(event)"><</button>
|
|
<div class="image-viewer-content">
|
|
<img id="viewerImage" src="" alt="大图查看">
|
|
<span class="image-counter" id="imageCounter"></span>
|
|
</div>
|
|
<button class="image-viewer-next" onclick="showNextImage(event)">></button>
|
|
<button class="image-viewer-close" onclick="closeImageViewer()">×</button>
|
|
</div>
|
|
|
|
<script>
|
|
// 图片查看器功能
|
|
let currentImageIndex = 0;
|
|
let currentImageUrls = [];
|
|
let currentImageUrl = '';
|
|
|
|
// 缩放和拖动相关变量
|
|
let scale = 1;
|
|
|
|
// 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) {
|
|
// 获取该产品项中所有图片
|
|
const allImages = supplyItem.querySelectorAll('.supply-images img');
|
|
currentImageUrls = Array.from(allImages).map(img => img.src.split('?')[0]);
|
|
|
|
// 找到当前图片在数组中的索引
|
|
currentImageIndex = currentImageUrls.findIndex(url => url === currentImageUrl);
|
|
if (currentImageIndex === -1) currentImageIndex = 0;
|
|
} else {
|
|
// 检查是否是供应商证明材料的图片
|
|
const proofItem = imgElement.closest('.proof-item');
|
|
if (proofItem && proofItem.closest('.supplier-detail-content')) {
|
|
// 查找同一份供应商证明中的所有图片
|
|
const allSupplierImages = document.querySelectorAll('.supplier-detail-content .proof-item img[data-product-image="true"]');
|
|
currentImageUrls = Array.from(allSupplierImages).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;
|
|
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})`;
|
|
}
|
|
|
|
// 设置图片查看器事件
|
|
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;
|
|
|
|
// 限制缩放范围,最小为照片原始大小
|
|
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 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>
|