Compare commits
2 Commits
eb4765cd10
...
c8a1f72bde
| Author | SHA1 | Date | |
|---|---|---|---|
| c8a1f72bde | |||
| 94811b52e2 |
@@ -57,6 +57,12 @@ routerAdd('GET', '/manage/product-manage', function (e) {
|
||||
.thumb { width: 84px; height: 84px; border-radius: 10px; border: 1px solid #dbe3f0; object-fit: cover; background: #fff; }
|
||||
.thumb-wrap { display: flex; gap: 12px; align-items: flex-start; flex-wrap: wrap; }
|
||||
.muted { color: #64748b; font-size: 12px; }
|
||||
.modal-mask { position: fixed; inset: 0; display: none; align-items: center; justify-content: center; padding: 16px; background: rgba(15, 23, 42, 0.42); z-index: 9999; }
|
||||
.modal-mask.show { display: flex; }
|
||||
.modal-card { width: min(560px, 100%); background: #fff; border: 1px solid #e5e7eb; border-radius: 16px; box-shadow: 0 24px 70px rgba(15, 23, 42, 0.2); padding: 18px; }
|
||||
.modal-title { margin: 0 0 14px; font-size: 22px; color: #0f172a; }
|
||||
.modal-grid { display: grid; grid-template-columns: 1fr; gap: 10px; }
|
||||
.modal-actions { margin-top: 14px; display: flex; justify-content: flex-end; gap: 10px; }
|
||||
.loading-mask { position: fixed; inset: 0; display: none; align-items: center; justify-content: center; padding: 24px; background: rgba(15, 23, 42, 0.42); backdrop-filter: blur(4px); z-index: 9998; }
|
||||
.loading-mask.show { display: flex; }
|
||||
.loading-card { min-width: min(92vw, 360px); padding: 24px 22px; border-radius: 20px; background: rgba(255,255,255,0.98); box-shadow: 0 28px 70px rgba(15, 23, 42, 0.22); border: 1px solid #dbe3f0; text-align: center; }
|
||||
@@ -258,6 +264,26 @@ routerAdd('GET', '/manage/product-manage', function (e) {
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="modal-mask" id="copyModal">
|
||||
<div class="modal-card" role="dialog" aria-modal="true" aria-labelledby="copyModalTitle">
|
||||
<h3 class="modal-title" id="copyModalTitle">复制产品</h3>
|
||||
<div class="modal-grid">
|
||||
<div>
|
||||
<label for="copyNameInput">新产品名称</label>
|
||||
<input id="copyNameInput" placeholder="请输入新产品名称" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="copyModelInput">新产品型号</label>
|
||||
<input id="copyModelInput" placeholder="请输入新产品型号" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button class="btn btn-light" id="copyCancelBtn" type="button">取消</button>
|
||||
<button class="btn btn-primary" id="copyConfirmBtn" type="button">确定复制</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="loading-mask" id="loadingMask">
|
||||
<div class="loading-card">
|
||||
<div class="loading-spinner"></div>
|
||||
@@ -289,6 +315,9 @@ routerAdd('GET', '/manage/product-manage', function (e) {
|
||||
const prodStatusOptionsEl = document.getElementById('prodStatusOptions')
|
||||
const prodStatusSelectedEl = document.getElementById('prodStatusSelected')
|
||||
const paramsBodyEl = document.getElementById('paramsBody')
|
||||
const copyModalEl = document.getElementById('copyModal')
|
||||
const copyNameInputEl = document.getElementById('copyNameInput')
|
||||
const copyModelInputEl = document.getElementById('copyModelInput')
|
||||
const loadingMaskEl = document.getElementById('loadingMask')
|
||||
const loadingTextEl = document.getElementById('loadingText')
|
||||
const iconPreviewEl = document.getElementById('iconPreview')
|
||||
@@ -339,7 +368,7 @@ routerAdd('GET', '/manage/product-manage', function (e) {
|
||||
parameterRows: [],
|
||||
currentIconAttachment: null,
|
||||
pendingIconFile: null,
|
||||
removedIconId: '',
|
||||
copySourceProductId: '',
|
||||
}
|
||||
|
||||
const multiFieldConfig = {
|
||||
@@ -1013,7 +1042,6 @@ routerAdd('GET', '/manage/product-manage', function (e) {
|
||||
state.selections.tags = []
|
||||
state.parameterRows = []
|
||||
state.currentIconAttachment = null
|
||||
state.removedIconId = ''
|
||||
clearPendingIconPreview()
|
||||
setIconPreview('')
|
||||
renderProductStatusSelector()
|
||||
@@ -1060,7 +1088,6 @@ routerAdd('GET', '/manage/product-manage', function (e) {
|
||||
state.parameterRows = normalizeParameterRows(item.prod_list_parameters)
|
||||
state.currentIconAttachment = item.prod_list_icon_attachment || null
|
||||
clearPendingIconPreview()
|
||||
state.removedIconId = ''
|
||||
|
||||
renderProductStatusSelector()
|
||||
renderAllMultiOptionLists()
|
||||
@@ -1101,12 +1128,115 @@ routerAdd('GET', '/manage/product-manage', function (e) {
|
||||
+ '<td data-label="更新时间"><span class="muted">' + escapeHtml(item.updated || '') + '</span></td>'
|
||||
+ '<td data-label="操作"><div class="table-actions">'
|
||||
+ '<button class="btn btn-light" type="button" onclick="window.__editProduct(\\'' + encodeURIComponent(item.prod_list_id) + '\\')">编辑</button>'
|
||||
+ '<button class="btn btn-secondary" type="button" onclick="window.__copyProduct(\\'' + encodeURIComponent(item.prod_list_id) + '\\')">复制</button>'
|
||||
+ '<button class="btn btn-danger" type="button" onclick="window.__deleteProduct(\\'' + encodeURIComponent(item.prod_list_id) + '\\')">删除</button>'
|
||||
+ '</div></td>'
|
||||
+ '</tr>'
|
||||
}).join('')
|
||||
}
|
||||
|
||||
function buildCopyPayload(source, nextName, nextModel) {
|
||||
return {
|
||||
prod_list_id: '',
|
||||
prod_list_name: normalizeText(nextName),
|
||||
prod_list_modelnumber: normalizeText(nextModel),
|
||||
prod_list_icon: source && source.prod_list_icon ? normalizeText(source.prod_list_icon) : '',
|
||||
prod_list_description: source && source.prod_list_description ? normalizeText(source.prod_list_description) : '',
|
||||
prod_list_feature: source && source.prod_list_feature ? normalizeText(source.prod_list_feature) : '',
|
||||
prod_list_parameters: normalizeParameterRows(source ? source.prod_list_parameters : []),
|
||||
prod_list_plantype: source && source.prod_list_plantype ? normalizeText(source.prod_list_plantype) : '',
|
||||
prod_list_category: source && source.prod_list_category ? normalizeText(source.prod_list_category) : '',
|
||||
prod_list_sort: source && (source.prod_list_sort || source.prod_list_sort === 0) ? Number(source.prod_list_sort) : 0,
|
||||
prod_list_comm_type: source && source.prod_list_comm_type ? normalizeText(source.prod_list_comm_type) : '',
|
||||
prod_list_series: source && source.prod_list_series ? normalizeText(source.prod_list_series) : '',
|
||||
prod_list_power_supply: source && source.prod_list_power_supply ? normalizeText(source.prod_list_power_supply) : '',
|
||||
prod_list_tags: source && source.prod_list_tags ? normalizeText(source.prod_list_tags) : '',
|
||||
prod_list_status: source && source.prod_list_status ? normalizeText(source.prod_list_status) : '有效',
|
||||
prod_list_basic_price: source && !(source.prod_list_basic_price === null || typeof source.prod_list_basic_price === 'undefined')
|
||||
? String(source.prod_list_basic_price)
|
||||
: '',
|
||||
prod_list_remark: source && source.prod_list_remark ? normalizeText(source.prod_list_remark) : '',
|
||||
}
|
||||
}
|
||||
|
||||
function openCopyModal(source) {
|
||||
state.copySourceProductId = source ? normalizeText(source.prod_list_id) : ''
|
||||
const defaultName = normalizeText(source && source.prod_list_name)
|
||||
const defaultModel = normalizeText(source && source.prod_list_modelnumber)
|
||||
|
||||
copyNameInputEl.value = defaultName ? (defaultName + '-副本') : ''
|
||||
copyModelInputEl.value = defaultModel
|
||||
copyModalEl.classList.add('show')
|
||||
|
||||
setTimeout(function () {
|
||||
copyNameInputEl.focus()
|
||||
copyNameInputEl.select()
|
||||
}, 0)
|
||||
}
|
||||
|
||||
function closeCopyModal() {
|
||||
state.copySourceProductId = ''
|
||||
copyModalEl.classList.remove('show')
|
||||
copyNameInputEl.value = ''
|
||||
copyModelInputEl.value = ''
|
||||
}
|
||||
|
||||
async function copyProduct(productId) {
|
||||
const source = state.list.find(function (item) {
|
||||
return normalizeText(item.prod_list_id) === normalizeText(productId)
|
||||
})
|
||||
|
||||
if (!source) {
|
||||
setStatus('未找到要复制的产品。', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
openCopyModal(source)
|
||||
}
|
||||
|
||||
async function confirmCopyProduct() {
|
||||
const sourceId = normalizeText(state.copySourceProductId)
|
||||
if (!sourceId) {
|
||||
setStatus('复制失败:未找到源产品。', 'error')
|
||||
closeCopyModal()
|
||||
return
|
||||
}
|
||||
|
||||
const source = state.list.find(function (item) {
|
||||
return normalizeText(item.prod_list_id) === sourceId
|
||||
})
|
||||
if (!source) {
|
||||
setStatus('复制失败:源产品不存在。', 'error')
|
||||
closeCopyModal()
|
||||
return
|
||||
}
|
||||
|
||||
const normalizedName = normalizeText(copyNameInputEl.value)
|
||||
if (!normalizedName) {
|
||||
setStatus('复制失败:新产品名称不能为空。', 'error')
|
||||
copyNameInputEl.focus()
|
||||
return
|
||||
}
|
||||
|
||||
const payload = buildCopyPayload(source, normalizedName, copyModelInputEl.value)
|
||||
|
||||
setStatus('正在复制产品...', '')
|
||||
showLoading('正在复制产品,请稍候...')
|
||||
try {
|
||||
const created = await requestJson('/product/create', payload)
|
||||
closeCopyModal()
|
||||
await loadProducts()
|
||||
if (created && created.prod_list_id) {
|
||||
await enterEditMode(created.prod_list_id)
|
||||
}
|
||||
setStatus('复制产品成功。', 'success')
|
||||
} catch (err) {
|
||||
setStatus(err.message || '复制产品失败', 'error')
|
||||
} finally {
|
||||
hideLoading()
|
||||
}
|
||||
}
|
||||
|
||||
async function loadProducts() {
|
||||
setStatus('正在加载产品列表...', '')
|
||||
showLoading('正在加载产品列表...')
|
||||
@@ -1216,13 +1346,6 @@ routerAdd('GET', '/manage/product-manage', function (e) {
|
||||
saved = await requestJson('/product/create', payload)
|
||||
}
|
||||
|
||||
if (state.removedIconId) {
|
||||
try {
|
||||
await requestJson('/attachment/delete', { attachments_id: state.removedIconId })
|
||||
} catch (_error) {}
|
||||
state.removedIconId = ''
|
||||
}
|
||||
|
||||
await loadProducts()
|
||||
if (saved && saved.prod_list_id) {
|
||||
await enterEditMode(saved.prod_list_id)
|
||||
@@ -1266,6 +1389,9 @@ routerAdd('GET', '/manage/product-manage', function (e) {
|
||||
window.__deleteProduct = function (encodedProductId) {
|
||||
deleteProduct(decodeURIComponent(encodedProductId))
|
||||
}
|
||||
window.__copyProduct = function (encodedProductId) {
|
||||
copyProduct(decodeURIComponent(encodedProductId))
|
||||
}
|
||||
|
||||
document.addEventListener('change', function (event) {
|
||||
const target = event.target
|
||||
@@ -1418,15 +1544,32 @@ routerAdd('GET', '/manage/product-manage', function (e) {
|
||||
}
|
||||
|
||||
document.getElementById('clearIconBtn').addEventListener('click', function () {
|
||||
if (state.currentIconAttachment && state.currentIconAttachment.attachments_id) {
|
||||
state.removedIconId = state.currentIconAttachment.attachments_id
|
||||
}
|
||||
state.currentIconAttachment = null
|
||||
clearPendingIconPreview()
|
||||
fields.iconFile.value = ''
|
||||
setIconPreview('')
|
||||
})
|
||||
|
||||
document.getElementById('copyCancelBtn').addEventListener('click', function () {
|
||||
closeCopyModal()
|
||||
})
|
||||
|
||||
document.getElementById('copyConfirmBtn').addEventListener('click', function () {
|
||||
confirmCopyProduct()
|
||||
})
|
||||
|
||||
copyModalEl.addEventListener('click', function (event) {
|
||||
if (event.target === copyModalEl) {
|
||||
closeCopyModal()
|
||||
}
|
||||
})
|
||||
|
||||
copyModelInputEl.addEventListener('keydown', function (event) {
|
||||
if (event.key === 'Enter') {
|
||||
confirmCopyProduct()
|
||||
}
|
||||
})
|
||||
|
||||
document.getElementById('submitBtn').addEventListener('click', function () {
|
||||
submitProduct()
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user