|
|
@ -4806,12 +4806,34 @@ |
|
|
costprices = [supply.costprice || '0']; |
|
|
costprices = [supply.costprice || '0']; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 解析规格状态数据 |
|
|
|
|
|
let specStatuses = []; |
|
|
|
|
|
try { |
|
|
|
|
|
if (supply.spec_status) { |
|
|
|
|
|
if (typeof supply.spec_status === 'string') { |
|
|
|
|
|
// 规格状态存储为英文逗号分隔的字符串 |
|
|
|
|
|
specStatuses = supply.spec_status.split(',').map(s => s.trim()); |
|
|
|
|
|
} else if (Array.isArray(supply.spec_status)) { |
|
|
|
|
|
specStatuses = supply.spec_status; |
|
|
|
|
|
} else { |
|
|
|
|
|
specStatuses = [supply.spec_status]; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
specStatuses = []; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
const maxLength = Math.max(specifications.length, quantities.length, costprices.length); |
|
|
const maxLength = Math.max(specifications.length, quantities.length, costprices.length); |
|
|
for (let i = 0; i < maxLength; i++) { |
|
|
for (let i = 0; i < maxLength; i++) { |
|
|
const spec = specifications[i] || '无'; |
|
|
const spec = specifications[i] || '无'; |
|
|
const quantity = quantities[i] || '0'; |
|
|
const quantity = quantities[i] || '0'; |
|
|
const costprice = costprices[i] || '0'; |
|
|
const costprice = costprices[i] || '0'; |
|
|
specQuantityBoxes += `<div class="spec-quantity-box" style="border: 1px solid #f0f0f0; padding: 10px; border-radius: 8px; background-color: #fafafa; margin-bottom: 10px;">• 规格${i + 1}: ${spec} - 件数: ${quantity}件 - 采购价: ¥${costprice}</div>`; |
|
|
const specStatus = specStatuses[i] || '0'; |
|
|
|
|
|
|
|
|
|
|
|
// 添加售空标识 |
|
|
|
|
|
const soldOutTag = specStatus === '1' ? '<span style="color: #f5222d; font-size: 12px; font-weight: 600; margin-left: 10px;">已售空</span>' : ''; |
|
|
|
|
|
|
|
|
|
|
|
specQuantityBoxes += `<div class="spec-quantity-box" style="border: 1px solid #f0f0f0; padding: 10px; border-radius: 8px; background-color: #fafafa; margin-bottom: 10px;">• 规格${i + 1}: ${spec} - 件数: ${quantity}件 - 采购价: ¥${costprice}${soldOutTag}</div>`; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return ` |
|
|
return ` |
|
|
@ -6851,26 +6873,177 @@ |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 下架货源 |
|
|
// 按规格下架弹窗 |
|
|
async function unpublishSupply(id) { |
|
|
const unpublishSpecModal = document.createElement('div'); |
|
|
// 防止重复点击 |
|
|
unpublishSpecModal.id = 'unpublishSpecModal'; |
|
|
if (isOperating) { |
|
|
unpublishSpecModal.className = 'select-modal'; |
|
|
|
|
|
unpublishSpecModal.innerHTML = ` |
|
|
|
|
|
<div class="select-content"> |
|
|
|
|
|
<div class="select-header"> |
|
|
|
|
|
<h3>选择要下架的规格</h3> |
|
|
|
|
|
<button class="close-btn" onclick="hideUnpublishSpecModal()">×</button> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div class="select-body"> |
|
|
|
|
|
<div id="unpublishSpecOptionsList" class="select-list"></div> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div class="select-footer" style="padding: 16px 20px; display: flex; justify-content: space-between; border-top: 1px solid #f0f0f0; background-color: #fafafa;"> |
|
|
|
|
|
<button onclick="hideUnpublishSpecModal()" style="padding: 10px 24px; background-color: #f5f5f5; color: #666; border: none; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.3s ease;">取消</button> |
|
|
|
|
|
<button onclick="confirmUnpublishSpec()" style="padding: 10px 24px; background-color: #1677ff; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.3s ease;">确定</button> |
|
|
|
|
|
</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
`; |
|
|
|
|
|
document.body.appendChild(unpublishSpecModal); |
|
|
|
|
|
|
|
|
|
|
|
let currentUnpublishSupplyId = null; |
|
|
|
|
|
let selectedSpecIndex = null; |
|
|
|
|
|
|
|
|
|
|
|
// 显示按规格下架弹窗 |
|
|
|
|
|
function showUnpublishSpecModal(supplyId) { |
|
|
|
|
|
currentUnpublishSupplyId = supplyId; |
|
|
|
|
|
const supply = supplyData.supplies.find(s => String(s.id) === String(supplyId)); |
|
|
|
|
|
if (!supply) { |
|
|
|
|
|
alert('找不到要下架的货源数据'); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 解析规格 |
|
|
|
|
|
let specifications = []; |
|
|
|
|
|
if (supply.specification) { |
|
|
|
|
|
if (typeof supply.specification === 'string') { |
|
|
|
|
|
specifications = supply.specification.split(',').filter(spec => spec.trim()); |
|
|
|
|
|
} else if (Array.isArray(supply.specification)) { |
|
|
|
|
|
specifications = supply.specification; |
|
|
|
|
|
} else { |
|
|
|
|
|
specifications = [supply.specification]; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 解析件数 |
|
|
|
|
|
let quantities = []; |
|
|
|
|
|
if (supply.quantity) { |
|
|
|
|
|
if (typeof supply.quantity === 'string') { |
|
|
|
|
|
quantities = supply.quantity.split(',').filter(qty => qty.trim()); |
|
|
|
|
|
} else if (Array.isArray(supply.quantity)) { |
|
|
|
|
|
quantities = supply.quantity; |
|
|
|
|
|
} else { |
|
|
|
|
|
quantities = [supply.quantity]; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 解析采购价 |
|
|
|
|
|
let costprices = []; |
|
|
|
|
|
if (supply.costprice) { |
|
|
|
|
|
if (typeof supply.costprice === 'string') { |
|
|
|
|
|
costprices = supply.costprice.split(',').filter(cp => cp.trim()); |
|
|
|
|
|
} else if (Array.isArray(supply.costprice)) { |
|
|
|
|
|
costprices = supply.costprice; |
|
|
|
|
|
} else { |
|
|
|
|
|
costprices = [supply.costprice]; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 解析规格状态 |
|
|
|
|
|
let specStatuses = []; |
|
|
|
|
|
if (supply.spec_status) { |
|
|
|
|
|
if (typeof supply.spec_status === 'string') { |
|
|
|
|
|
specStatuses = supply.spec_status.split(',').map(s => s.trim()); |
|
|
|
|
|
} else if (Array.isArray(supply.spec_status)) { |
|
|
|
|
|
specStatuses = supply.spec_status; |
|
|
|
|
|
} else { |
|
|
|
|
|
specStatuses = [supply.spec_status]; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 生成规格选项 |
|
|
|
|
|
const optionsList = document.getElementById('unpublishSpecOptionsList'); |
|
|
|
|
|
optionsList.innerHTML = ''; |
|
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < specifications.length; i++) { |
|
|
|
|
|
const spec = specifications[i] || `规格${i+1}`; |
|
|
|
|
|
const qty = quantities[i] || '0件'; |
|
|
|
|
|
const cp = costprices[i] || '¥0'; |
|
|
|
|
|
const status = specStatuses[i] || '0'; |
|
|
|
|
|
|
|
|
|
|
|
const option = document.createElement('div'); |
|
|
|
|
|
option.className = 'select-item'; |
|
|
|
|
|
option.innerHTML = ` |
|
|
|
|
|
<div style="display: flex; align-items: center; gap: 10px;"> |
|
|
|
|
|
<input type="checkbox" name="specIndex" value="${i}" ${status === '1' ? 'disabled' : ''}> |
|
|
|
|
|
<div style="flex: 1;"> |
|
|
|
|
|
<div style="font-weight: 600;">${spec}</div> |
|
|
|
|
|
<div style="font-size: 12px; color: #666;">件数: ${qty} | 采购价: ${cp}</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
${status === '1' ? '<span style="color: #f5222d; font-size: 12px; font-weight: 600;">已售空</span>' : ''} |
|
|
|
|
|
</div> |
|
|
|
|
|
`; |
|
|
|
|
|
|
|
|
|
|
|
// 添加点击事件,点击整个项时切换checkbox状态 |
|
|
|
|
|
option.addEventListener('click', (e) => { |
|
|
|
|
|
// 如果点击的是checkbox本身,不处理 |
|
|
|
|
|
if (e.target.type === 'checkbox') return; |
|
|
|
|
|
|
|
|
|
|
|
const checkbox = option.querySelector('input[type="checkbox"]'); |
|
|
|
|
|
// 只有在checkbox未禁用时才切换状态 |
|
|
|
|
|
if (!checkbox.disabled) { |
|
|
|
|
|
checkbox.checked = !checkbox.checked; |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
optionsList.appendChild(option); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 默认勾选所有未禁用的规格 |
|
|
|
|
|
const checkboxes = optionsList.querySelectorAll('input[type="checkbox"]:not(:disabled)'); |
|
|
|
|
|
checkboxes.forEach(checkbox => { |
|
|
|
|
|
checkbox.checked = true; |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// 显示弹窗 |
|
|
|
|
|
document.getElementById('unpublishSpecModal').classList.add('active'); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 隐藏按规格下架弹窗 |
|
|
|
|
|
function hideUnpublishSpecModal() { |
|
|
|
|
|
document.getElementById('unpublishSpecModal').classList.remove('active'); |
|
|
|
|
|
currentUnpublishSupplyId = null; |
|
|
|
|
|
selectedSpecIndex = null; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 确认按规格下架 |
|
|
|
|
|
async function confirmUnpublishSpec() { |
|
|
|
|
|
// 获取所有选中的规格索引 |
|
|
|
|
|
const selectedCheckboxes = document.querySelectorAll('input[name="specIndex"]:checked'); |
|
|
|
|
|
if (selectedCheckboxes.length === 0) { |
|
|
|
|
|
alert('请选择要下架的规格'); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 将选中的索引收集到数组中 |
|
|
|
|
|
const selectedSpecIndices = Array.from(selectedCheckboxes).map(checkbox => parseInt(checkbox.value)); |
|
|
|
|
|
|
|
|
|
|
|
// 添加确认提示,防止误触 |
|
|
|
|
|
const isConfirmed = confirm('确定要下架所选规格吗?'); |
|
|
|
|
|
if (!isConfirmed) { |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (confirm('确定要下架该货源吗?')) { |
|
|
if (currentUnpublishSupplyId) { |
|
|
try { |
|
|
try { |
|
|
isOperating = true; |
|
|
isOperating = true; |
|
|
|
|
|
|
|
|
// 获取要下架的货源的原始数据 |
|
|
// 获取要下架的货源的原始数据 |
|
|
const originalSupply = supplyData.supplies.find(s => String(s.id) === String(id)); |
|
|
const originalSupply = supplyData.supplies.find(s => String(s.id) === String(currentUnpublishSupplyId)); |
|
|
if (!originalSupply) { |
|
|
if (!originalSupply) { |
|
|
alert('找不到要下架的货源数据'); |
|
|
alert('找不到要下架的货源数据'); |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const response = await fetch(`/api/supplies/${id}/unpublish`, { |
|
|
const response = await fetch(`/api/supplies/${currentUnpublishSupplyId}/unpublish-spec`, { |
|
|
method: 'POST' |
|
|
method: 'POST', |
|
|
|
|
|
headers: { |
|
|
|
|
|
'Content-Type': 'application/json' |
|
|
|
|
|
}, |
|
|
|
|
|
body: JSON.stringify({ specIndex: selectedSpecIndices }) |
|
|
}); |
|
|
}); |
|
|
const result = await response.json(); |
|
|
const result = await response.json(); |
|
|
if (result.success) { |
|
|
if (result.success) { |
|
|
@ -6878,31 +7051,44 @@ |
|
|
const userInfo = checkLogin(); |
|
|
const userInfo = checkLogin(); |
|
|
if (userInfo) { |
|
|
if (userInfo) { |
|
|
// 传入修改后的数据,包含状态变化 |
|
|
// 传入修改后的数据,包含状态变化 |
|
|
await logSupplyOperation('下架', id, originalSupply, result.data); |
|
|
await logSupplyOperation('下架规格', currentUnpublishSupplyId, originalSupply, result.data); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 向WebSocket服务器发送消息,通知其他客户端货源已下架 |
|
|
// 向WebSocket服务器发送消息,通知其他客户端货源已下架 |
|
|
sendWebSocketMessage({ |
|
|
sendWebSocketMessage({ |
|
|
type: 'supply_status_change', |
|
|
type: 'supply_status_change', |
|
|
supplyId: id, |
|
|
supplyId: currentUnpublishSupplyId, |
|
|
action: 'unpublish', |
|
|
action: 'unpublish_spec', |
|
|
status: 'sold_out' |
|
|
specIndex: selectedSpecIndex, |
|
|
|
|
|
status: result.data.status |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
alert('下架成功'); |
|
|
alert('规格下架成功'); |
|
|
loadSupplies(); |
|
|
loadSupplies(); |
|
|
|
|
|
hideUnpublishSpecModal(); |
|
|
} else { |
|
|
} else { |
|
|
alert('下架失败: ' + (result.message || '未知错误')); |
|
|
alert('规格下架失败: ' + (result.message || '未知错误')); |
|
|
} |
|
|
} |
|
|
} catch (error) { |
|
|
} catch (error) { |
|
|
console.error('下架失败:', error); |
|
|
console.error('规格下架失败:', error); |
|
|
alert('下架失败: 网络错误'); |
|
|
alert('规格下架失败: 网络错误'); |
|
|
} finally { |
|
|
} finally { |
|
|
isOperating = false; |
|
|
isOperating = false; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 下架货源 |
|
|
|
|
|
async function unpublishSupply(id) { |
|
|
|
|
|
// 防止重复点击 |
|
|
|
|
|
if (isOperating) { |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 显示按规格下架弹窗 |
|
|
|
|
|
showUnpublishSpecModal(id); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// 显示编辑货源 |
|
|
// 显示编辑货源 |
|
|
// 删除货源 |
|
|
// 删除货源 |
|
|
async function deleteSupply(id) { |
|
|
async function deleteSupply(id) { |
|
|
@ -8480,7 +8666,8 @@ |
|
|
window.onclick = function(event) { |
|
|
window.onclick = function(event) { |
|
|
// 确保只处理click事件,不处理拖拽选择文本导致的mouseup事件 |
|
|
// 确保只处理click事件,不处理拖拽选择文本导致的mouseup事件 |
|
|
if (event.type === 'click') { |
|
|
if (event.type === 'click') { |
|
|
const modals = document.querySelectorAll('.modal'); |
|
|
// 处理类名为modal的模态框 |
|
|
|
|
|
const modals = document.querySelectorAll('.modal, .select-modal'); |
|
|
modals.forEach(modal => { |
|
|
modals.forEach(modal => { |
|
|
if (event.target === modal) { |
|
|
if (event.target === modal) { |
|
|
// 只在真正点击模态框背景时关闭,不在选择文本拖拽到外部时关闭 |
|
|
// 只在真正点击模态框背景时关闭,不在选择文本拖拽到外部时关闭 |
|
|
@ -8491,7 +8678,17 @@ |
|
|
const selection = window.getSelection(); |
|
|
const selection = window.getSelection(); |
|
|
if (selection && selection.toString()) return; |
|
|
if (selection && selection.toString()) return; |
|
|
|
|
|
|
|
|
modal.style.display = 'none'; |
|
|
// 对于普通modal,使用display: none隐藏 |
|
|
|
|
|
if (modal.classList.contains('modal')) { |
|
|
|
|
|
modal.style.display = 'none'; |
|
|
|
|
|
} |
|
|
|
|
|
// 对于select-modal,使用移除active类来隐藏 |
|
|
|
|
|
else if (modal.classList.contains('select-modal')) { |
|
|
|
|
|
modal.classList.remove('active'); |
|
|
|
|
|
// 重置相关变量 |
|
|
|
|
|
currentUnpublishSupplyId = null; |
|
|
|
|
|
selectedSpecIndex = null; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
|