@@ -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) {
+ '
' + escapeHtml(item.updated || '') + ' | '
+ '
'
+ ''
+ + ''
+ ''
+ ' | '
+ ''
}).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()
})