feat: 更新字典管理功能;优化图片上传和展示逻辑,支持多图上传和拖拽排序;扩展文档接口,新增多个字段以支持更丰富的文档信息

This commit is contained in:
2026-03-31 16:23:21 +08:00
parent 6f7efbc799
commit 7a21b3e5db
4 changed files with 492 additions and 46 deletions

View File

@@ -150,11 +150,13 @@ function normalizeDictionaryItem(item, index) {
throw createAppError(400, '第 ' + (index + 1) + ' 项 sortOrder 必须为数字') throw createAppError(400, '第 ' + (index + 1) + ' 项 sortOrder 必须为数字')
} }
const imageIdList = normalizeAttachmentIdList(current.image, '第 ' + (index + 1) + ' 项 image')
return { return {
enum: String(current.enum), enum: String(current.enum),
description: String(current.description), description: String(current.description),
sortOrder: sortOrderNumber, sortOrder: sortOrderNumber,
image: current.image ? String(current.image) : '', image: imageIdList.join('|'),
} }
} }

View File

@@ -16,6 +16,30 @@ function safeJsonParse(text, fallback) {
} }
} }
function parseAttachmentIdList(value) {
if (Array.isArray(value)) {
return value
.map(function (item) {
return String(item || '').trim()
})
.filter(function (item) {
return !!item
})
}
const text = String(value || '').trim()
if (!text) return []
return text
.split('|')
.map(function (item) {
return String(item || '').trim()
})
.filter(function (item) {
return !!item
})
}
function normalizeItemsFromRecord(record) { function normalizeItemsFromRecord(record) {
const enums = safeJsonParse(record.getString('dict_word_enum'), []) const enums = safeJsonParse(record.getString('dict_word_enum'), [])
const descriptions = safeJsonParse(record.getString('dict_word_description'), []) const descriptions = safeJsonParse(record.getString('dict_word_description'), [])
@@ -29,21 +53,31 @@ function normalizeItemsFromRecord(record) {
continue continue
} }
const imageAttachmentId = typeof images[i] === 'undefined' ? '' : String(images[i] || '') const rawImageValue = typeof images[i] === 'undefined' ? '' : String(images[i] || '')
let imageAttachment = null const imageAttachmentIds = parseAttachmentIdList(rawImageValue)
if (imageAttachmentId) { const imageAttachments = []
const imageUrls = []
for (let imageIndex = 0; imageIndex < imageAttachmentIds.length; imageIndex += 1) {
const attachmentId = imageAttachmentIds[imageIndex]
try { try {
imageAttachment = documentService.getAttachmentDetail(imageAttachmentId) const imageAttachment = documentService.getAttachmentDetail(attachmentId)
imageAttachments.push(imageAttachment)
imageUrls.push(imageAttachment ? imageAttachment.attachments_url : '')
} catch (_error) { } catch (_error) {
imageAttachment = null imageAttachments.push(null)
imageUrls.push('')
} }
} }
items.push({ items.push({
enum: typeof enums[i] === 'undefined' ? '' : String(enums[i]), enum: typeof enums[i] === 'undefined' ? '' : String(enums[i]),
description: typeof descriptions[i] === 'undefined' ? '' : String(descriptions[i]), description: typeof descriptions[i] === 'undefined' ? '' : String(descriptions[i]),
image: imageAttachmentId, image: imageAttachmentIds.join('|'),
imageUrl: imageAttachment ? imageAttachment.attachments_url : '', imageIds: imageAttachmentIds,
imageAttachment: imageAttachment, imageUrls: imageUrls,
imageAttachments: imageAttachments,
imageUrl: imageUrls.length ? imageUrls[0] : '',
sortOrder: Number(sortOrders[i] || 0), sortOrder: Number(sortOrders[i] || 0),
}) })
} }
@@ -89,7 +123,7 @@ function fillDictionaryItems(record, items) {
for (let i = 0; i < items.length; i += 1) { for (let i = 0; i < items.length; i += 1) {
enums.push(items[i].enum) enums.push(items[i].enum)
descriptions.push(items[i].description) descriptions.push(items[i].description)
images.push(items[i].image || '') images.push(parseAttachmentIdList(items[i].image).join('|'))
sortOrders.push(items[i].sortOrder) sortOrders.push(items[i].sortOrder)
} }

View File

@@ -64,7 +64,17 @@ routerAdd('GET', '/manage/dictionary-manage', function (e) {
.enum-preview-item { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; } .enum-preview-item { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.enum-preview-more { margin-top: 8px; display: none; } .enum-preview-more { margin-top: 8px; display: none; }
.enum-preview-toggle { margin-top: 6px; } .enum-preview-toggle { margin-top: 6px; }
.image-upload-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 8px; margin-top: 10px; } .item-image-grid { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; }
.image-tile { width: 86px; border: 1px solid #dbe3f0; border-radius: 12px; background: #fff; padding: 6px; display: flex; flex-direction: column; gap: 6px; cursor: grab; }
.image-tile.dragging { opacity: 0.55; }
.image-tile-thumb { width: 100%; height: 56px; border-radius: 8px; border: 1px solid #e2e8f0; background: #f8fafc; display: flex; align-items: center; justify-content: center; overflow: hidden; font-size: 12px; color: #64748b; }
.image-tile-thumb img { width: 100%; height: 100%; object-fit: cover; }
.image-tile-actions { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.image-tile-order { font-size: 11px; color: #64748b; }
.item-field-stack { display: flex; flex-direction: column; gap: 10px; min-width: 260px; }
.item-field-label { font-size: 12px; color: #64748b; margin-bottom: 4px; }
.item-delete-wrap { margin-top: 2px; }
.image-upload-row { display: grid; grid-template-columns: minmax(0, 1fr); align-items: center; gap: 8px; margin-top: 10px; }
.image-upload-row input[type="file"] { width: 100%; min-width: 0; } .image-upload-row input[type="file"] { width: 100%; min-width: 0; }
.icon-btn { min-width: 36px; padding: 6px 10px; line-height: 1; font-size: 18px; } .icon-btn { min-width: 36px; padding: 6px 10px; line-height: 1; font-size: 18px; }
.drop-tip { color: #64748b; font-size: 12px; margin-top: 6px; } .drop-tip { color: #64748b; font-size: 12px; margin-top: 6px; }
@@ -158,10 +168,8 @@ routerAdd('GET', '/manage/dictionary-manage', function (e) {
<table> <table>
<thead> <thead>
<tr> <tr>
<th>描述</th> <th>配置</th>
<th>图片</th> <th>图片</th>
<th>排序</th>
<th>操作</th>
</tr> </tr>
</thead> </thead>
<tbody id="itemsBody"></tbody> <tbody id="itemsBody"></tbody>
@@ -204,6 +212,7 @@ routerAdd('GET', '/manage/dictionary-manage', function (e) {
enumSeed: '', enumSeed: '',
enumCounter: 1, enumCounter: 1,
expandedPreviewKey: '', expandedPreviewKey: '',
draggingImage: null,
} }
const statusEl = document.getElementById('status') const statusEl = document.getElementById('status')
@@ -432,11 +441,21 @@ routerAdd('GET', '/manage/dictionary-manage', function (e) {
enum: String((item && item.enum) || '').trim(), enum: String((item && item.enum) || '').trim(),
description: String((item && item.description) || ''), description: String((item && item.description) || ''),
image: (item && item.image) || '', image: (item && item.image) || '',
imageUrl: (item && item.imageUrl) || '', imageIds: [],
imageAttachment: (item && item.imageAttachment) || null, imageUrls: [],
imageAttachments: [],
imageUrl: '',
imageAttachment: null,
sortOrder: Number((item && item.sortOrder) || 0), sortOrder: Number((item && item.sortOrder) || 0),
} }
const imageIds = normalizeAttachmentIdList(item && (item.imageIds || item.image))
const imageUrls = normalizeImageUrlList(item && item.imageUrls, item && item.imageUrl, imageIds.length)
current.imageIds = imageIds
current.imageUrls = imageUrls
current.imageAttachments = normalizeImageAttachmentList(item && item.imageAttachments, item && item.imageAttachment, imageIds.length)
applyItemImageSummary(current)
if (!current.enum || used.has(current.enum)) { if (!current.enum || used.has(current.enum)) {
current.enum = nextAutoEnum(used) current.enum = nextAutoEnum(used)
} }
@@ -450,12 +469,74 @@ routerAdd('GET', '/manage/dictionary-manage', function (e) {
}) })
} }
function normalizeAttachmentIdList(value) {
if (Array.isArray(value)) {
return value.map(function (item) {
return String(item || '').trim()
}).filter(function (item) {
return !!item
})
}
const text = String(value || '').trim()
if (!text) {
return []
}
return text.split('|').map(function (item) {
return String(item || '').trim()
}).filter(function (item) {
return !!item
})
}
function normalizeImageUrlList(urls, singleUrl, expectedLength) {
const list = Array.isArray(urls)
? urls.map(function (item) { return String(item || '') })
: (singleUrl ? [String(singleUrl)] : [])
while (list.length < expectedLength) {
list.push('')
}
return list.slice(0, Math.max(expectedLength, list.length))
}
function normalizeImageAttachmentList(attachments, singleAttachment, expectedLength) {
const list = Array.isArray(attachments)
? attachments.slice()
: (singleAttachment ? [singleAttachment] : [])
while (list.length < expectedLength) {
list.push(null)
}
return list.slice(0, Math.max(expectedLength, list.length))
}
function applyItemImageSummary(item) {
const imageIds = normalizeAttachmentIdList(item && item.imageIds)
item.imageIds = imageIds
item.image = imageIds.join('|')
item.imageUrls = normalizeImageUrlList(item && item.imageUrls, item && item.imageUrl, imageIds.length)
item.imageAttachments = normalizeImageAttachmentList(item && item.imageAttachments, item && item.imageAttachment, imageIds.length)
item.imageUrl = item.imageUrls.length ? item.imageUrls[0] : ''
item.imageAttachment = item.imageAttachments.length ? item.imageAttachments[0] : null
return item
}
function renderEnumPreviewItem(item) { function renderEnumPreviewItem(item) {
const desc = escapeHtml(item && item.description ? item.description : '(无描述)') const desc = escapeHtml(item && item.description ? item.description : '(无描述)')
const imageHtml = item && item.imageUrl const imageUrls = normalizeImageUrlList(item && item.imageUrls, item && item.imageUrl, normalizeAttachmentIdList(item && item.image).length)
? '<img class="thumb previewable" src="' + escapeHtml(item.imageUrl) + '" alt="" onclick="window.__previewImage(\\'' + escapeJsString(item.imageUrl) + '\\')" />' const imageHtml = imageUrls.length
? '<div class="thumb-row">' + imageUrls.map(function (url) {
if (!url) {
return '<span class="thumb" style="display:inline-flex;align-items:center;justify-content:center;color:#94a3b8;">无</span>'
}
return '<img class="thumb previewable" src="' + escapeHtml(url) + '" alt="" onclick="window.__previewImage(\\'' + escapeJsString(url) + '\\')" />'
}).join('') + '</div>'
: '<span class="muted">无图</span>' : '<span class="muted">无图</span>'
return '<div class="enum-preview-item">' + imageHtml + '<span>' + desc + '</span></div>' return '<div class="enum-preview-item"><div>' + imageHtml + '</div><span>' + desc + '</span></div>'
} }
function renderItemsPreview(items, previewKey, isExpanded) { function renderItemsPreview(items, previewKey, isExpanded) {
@@ -516,16 +597,19 @@ routerAdd('GET', '/manage/dictionary-manage', function (e) {
remarkInput.value = record ? (record.dict_word_remark || '') : '' remarkInput.value = record ? (record.dict_word_remark || '') : ''
state.items = record && Array.isArray(record.items) && record.items.length state.items = record && Array.isArray(record.items) && record.items.length
? record.items.map(function (item) { ? record.items.map(function (item) {
return { return applyItemImageSummary({
enum: item.enum, enum: item.enum,
description: item.description, description: item.description,
image: item.image || '', image: item.image || '',
imageIds: normalizeAttachmentIdList(item.imageIds || item.image),
imageUrls: Array.isArray(item.imageUrls) ? item.imageUrls : (item.imageUrl ? [item.imageUrl] : []),
imageAttachments: Array.isArray(item.imageAttachments) ? item.imageAttachments : (item.imageAttachment ? [item.imageAttachment] : []),
imageUrl: item.imageUrl || '', imageUrl: item.imageUrl || '',
imageAttachment: item.imageAttachment || null, imageAttachment: item.imageAttachment || null,
sortOrder: item.sortOrder, sortOrder: item.sortOrder,
} })
}) })
: [{ enum: '', description: '', image: '', imageUrl: '', imageAttachment: null, sortOrder: 1 }] : [applyItemImageSummary({ enum: '', description: '', image: '', imageIds: [], imageUrls: [], imageAttachments: [], imageUrl: '', imageAttachment: null, sortOrder: 1 })]
normalizeItemEnums() normalizeItemEnums()
renderItemsEditor() renderItemsEditor()
editorModal.classList.add('show') editorModal.classList.add('show')
@@ -562,27 +646,49 @@ routerAdd('GET', '/manage/dictionary-manage', function (e) {
function renderItemsEditor() { function renderItemsEditor() {
itemsBody.innerHTML = state.items.map(function (item, index) { itemsBody.innerHTML = state.items.map(function (item, index) {
const imageCell = item.imageUrl const imageIds = normalizeAttachmentIdList(item.imageIds || item.image)
? '<div class="thumb-row"><img class="thumb previewable" src="' + escapeHtml(item.imageUrl) + '" alt="" onclick="window.__previewImage(\\'' + escapeJsString(item.imageUrl) + '\\')" /><div class="thumb-meta">' + escapeHtml(item.image || '') + '</div></div>' const imageUrls = normalizeImageUrlList(item.imageUrls, item.imageUrl, imageIds.length)
const imageCell = imageIds.length
? '<div class="item-image-grid">' + imageIds.map(function (imageId, imageIndex) {
const imageUrl = imageUrls[imageIndex] || ''
const thumbHtml = imageUrl
? '<img src="' + escapeHtml(imageUrl) + '" alt="" onclick="window.__previewImage(\\'' + escapeJsString(imageUrl) + '\\')" />'
: '<span>无图</span>'
return '<div class="image-tile" draggable="true" ondragstart="window.__startItemImageDrag(' + index + ',' + imageIndex + ', event)" ondragend="window.__endItemImageDrag(event)" ondragover="window.__allowItemImageDrop(event)" ondrop="window.__dropItemImageAt(' + index + ',' + imageIndex + ', event)">'
+ '<div class="image-tile-thumb">' + thumbHtml + '</div>'
+ '<div class="image-tile-actions">'
+ '<span class="image-tile-order">#' + (imageIndex + 1) + '</span>'
+ '<button class="btn btn-light icon-btn" type="button" title="删除图片" onclick="window.__removeItemImage(' + index + ',' + imageIndex + ')">🗑</button>'
+ '</div>'
+ '</div>'
}).join('') + '</div>'
: '<div class="muted">未上传图片</div>' : '<div class="muted">未上传图片</div>'
return '<tr>' return '<tr>'
+ '<td><input data-item-field="description" data-index="' + index + '" value="' + escapeHtml(item.description) + '" /></td>' + '<td>'
+ '<div class="item-field-stack">'
+ '<div><div class="item-field-label">描述</div><input data-item-field="description" data-index="' + index + '" value="' + escapeHtml(item.description) + '" /></div>'
+ '<div><div class="item-field-label">排序</div><input type="number" data-item-field="sortOrder" data-index="' + index + '" value="' + escapeHtml(item.sortOrder) + '" /></div>'
+ '<div class="item-delete-wrap"><button class="btn btn-danger" type="button" onclick="window.__removeItem(' + index + ')">删除</button></div>'
+ '</div>'
+ '</td>'
+ '<td ondragover="window.__allowItemDrop(event)" ondrop="window.__dropItemImage(' + index + ', event)">' + '<td ondragover="window.__allowItemDrop(event)" ondrop="window.__dropItemImage(' + index + ', event)">'
+ imageCell + imageCell
+ '<div class="image-upload-row">' + '<div class="image-upload-row">'
+ '<input type="file" accept="image/*" onchange="window.__uploadItemImage(' + index + ', this)" />' + '<input type="file" accept="image/*" multiple onchange="window.__uploadItemImages(' + index + ', this)" />'
+ '<button class="btn btn-light icon-btn" type="button" title="删除图片" onclick="window.__clearItemImage(' + index + ')">🗑️</button>' + '<button class="btn btn-light" type="button" onclick="window.__clearItemImage(' + index + ')">清空全部</button>'
+ '</div>' + '</div>'
+ '<div class="drop-tip">支持拖拽图片到本区域上传</div>' + '<div class="drop-tip">支持拖拽多张图片上传;图片可拖动调整顺序。</div>'
+ '</td>' + '</td>'
+ '<td><input type="number" data-item-field="sortOrder" data-index="' + index + '" value="' + escapeHtml(item.sortOrder) + '" /></td>'
+ '<td><button class="btn btn-danger" type="button" onclick="window.__removeItem(' + index + ')">删除</button></td>'
+ '</tr>' + '</tr>'
}).join('') }).join('')
} }
async function setItemImageFromFile(index, file) { async function addItemImagesFromFiles(index, files) {
if (!file) { const validFiles = Array.from(files || []).filter(function (file) {
return !!file
})
if (!validFiles.length) {
return return
} }
@@ -594,10 +700,22 @@ routerAdd('GET', '/manage/dictionary-manage', function (e) {
setStatus('正在上传字典项图片...', '') setStatus('正在上传字典项图片...', '')
showLoading('正在上传字典项图片,请稍候...') showLoading('正在上传字典项图片,请稍候...')
try { try {
const attachment = await uploadAttachment(file, 'dict-item-' + (index + 1)) for (let fileIndex = 0; fileIndex < validFiles.length; fileIndex += 1) {
state.items[index].image = attachment.attachments_id const attachment = await uploadAttachment(validFiles[fileIndex], 'dict-item-' + (index + 1) + '-' + (fileIndex + 1))
state.items[index].imageUrl = attachment.attachments_url || '' if (!state.items[index]) {
state.items[index].imageAttachment = attachment break
}
state.items[index].imageIds = normalizeAttachmentIdList(state.items[index].imageIds || state.items[index].image)
state.items[index].imageUrls = normalizeImageUrlList(state.items[index].imageUrls, state.items[index].imageUrl, state.items[index].imageIds.length)
state.items[index].imageAttachments = normalizeImageAttachmentList(state.items[index].imageAttachments, state.items[index].imageAttachment, state.items[index].imageIds.length)
state.items[index].imageIds.push(attachment.attachments_id || '')
state.items[index].imageUrls.push(attachment.attachments_url || '')
state.items[index].imageAttachments.push(attachment)
applyItemImageSummary(state.items[index])
}
renderItemsEditor() renderItemsEditor()
setStatus('字典项图片上传成功。', 'success') setStatus('字典项图片上传成功。', 'success')
} catch (err) { } catch (err) {
@@ -616,6 +734,9 @@ routerAdd('GET', '/manage/dictionary-manage', function (e) {
enum: state.items[index] ? String(state.items[index].enum || '') : '', enum: state.items[index] ? String(state.items[index].enum || '') : '',
description: descriptionInput.value.trim(), description: descriptionInput.value.trim(),
image: state.items[index] ? (state.items[index].image || '') : '', image: state.items[index] ? (state.items[index].image || '') : '',
imageIds: state.items[index] ? normalizeAttachmentIdList(state.items[index].imageIds || state.items[index].image) : [],
imageUrls: state.items[index] ? normalizeImageUrlList(state.items[index].imageUrls, state.items[index].imageUrl, normalizeAttachmentIdList(state.items[index].imageIds || state.items[index].image).length) : [],
imageAttachments: state.items[index] ? normalizeImageAttachmentList(state.items[index].imageAttachments, state.items[index].imageAttachment, normalizeAttachmentIdList(state.items[index].imageIds || state.items[index].image).length) : [],
imageUrl: state.items[index] ? (state.items[index].imageUrl || '') : '', imageUrl: state.items[index] ? (state.items[index].imageUrl || '') : '',
imageAttachment: state.items[index] ? (state.items[index].imageAttachment || null) : null, imageAttachment: state.items[index] ? (state.items[index].imageAttachment || null) : null,
sortOrder: Number(sortOrderInput.value || index + 1), sortOrder: Number(sortOrderInput.value || index + 1),
@@ -628,12 +749,14 @@ routerAdd('GET', '/manage/dictionary-manage', function (e) {
if (!rows.length) { if (!rows.length) {
return return
} }
state.items = collectItemsFromEditor() state.items = collectItemsFromEditor().map(function (item) {
return applyItemImageSummary(item)
})
} }
async function uploadItemImage(index, inputEl) { async function uploadItemImages(index, inputEl) {
const file = inputEl && inputEl.files && inputEl.files[0] const files = inputEl && inputEl.files ? inputEl.files : []
await setItemImageFromFile(index, file) await addItemImagesFromFiles(index, files)
} }
async function loadList() { async function loadList() {
@@ -777,15 +900,109 @@ routerAdd('GET', '/manage/dictionary-manage', function (e) {
imageViewerImg.src = url imageViewerImg.src = url
imageViewer.classList.add('show') imageViewer.classList.add('show')
} }
window.__uploadItemImage = uploadItemImage window.__uploadItemImages = uploadItemImages
window.__allowItemDrop = function (event) { window.__allowItemDrop = function (event) {
event.preventDefault() event.preventDefault()
} }
window.__dropItemImage = function (index, event) { window.__dropItemImage = function (index, event) {
event.preventDefault() event.preventDefault()
const files = event.dataTransfer && event.dataTransfer.files const files = event.dataTransfer && event.dataTransfer.files
const file = files && files[0] addItemImagesFromFiles(index, files)
setItemImageFromFile(index, file) }
window.__startItemImageDrag = function (itemIndex, imageIndex, event) {
state.draggingImage = {
itemIndex: itemIndex,
imageIndex: imageIndex,
}
if (event && event.dataTransfer) {
event.dataTransfer.effectAllowed = 'move'
}
const tile = event && event.currentTarget
if (tile && tile.classList) {
tile.classList.add('dragging')
}
}
window.__endItemImageDrag = function (event) {
const tile = event && event.currentTarget
if (tile && tile.classList) {
tile.classList.remove('dragging')
}
}
window.__allowItemImageDrop = function (event) {
event.preventDefault()
if (event && event.dataTransfer) {
event.dataTransfer.dropEffect = 'move'
}
}
window.__dropItemImageAt = function (itemIndex, targetImageIndex, event) {
event.preventDefault()
const dragging = state.draggingImage
state.draggingImage = null
if (!dragging || dragging.itemIndex !== itemIndex) {
return
}
syncItemsStateFromEditor()
const item = state.items[itemIndex]
if (!item) {
return
}
const imageIds = normalizeAttachmentIdList(item.imageIds || item.image)
if (!imageIds.length) {
return
}
const sourceIndex = dragging.imageIndex
if (sourceIndex === targetImageIndex || sourceIndex < 0 || sourceIndex >= imageIds.length || targetImageIndex < 0 || targetImageIndex >= imageIds.length) {
return
}
const imageUrls = normalizeImageUrlList(item.imageUrls, item.imageUrl, imageIds.length)
const imageAttachments = normalizeImageAttachmentList(item.imageAttachments, item.imageAttachment, imageIds.length)
const movedId = imageIds.splice(sourceIndex, 1)[0]
const movedUrl = imageUrls.splice(sourceIndex, 1)[0]
const movedAttachment = imageAttachments.splice(sourceIndex, 1)[0]
imageIds.splice(targetImageIndex, 0, movedId)
imageUrls.splice(targetImageIndex, 0, movedUrl)
imageAttachments.splice(targetImageIndex, 0, movedAttachment)
item.imageIds = imageIds
item.imageUrls = imageUrls
item.imageAttachments = imageAttachments
applyItemImageSummary(item)
renderItemsEditor()
}
window.__removeItemImage = function (index, imageIndex) {
syncItemsStateFromEditor()
const item = state.items[index]
if (!item) {
return
}
const imageIds = normalizeAttachmentIdList(item.imageIds || item.image)
if (imageIndex < 0 || imageIndex >= imageIds.length) {
return
}
const imageUrls = normalizeImageUrlList(item.imageUrls, item.imageUrl, imageIds.length)
const imageAttachments = normalizeImageAttachmentList(item.imageAttachments, item.imageAttachment, imageIds.length)
imageIds.splice(imageIndex, 1)
imageUrls.splice(imageIndex, 1)
imageAttachments.splice(imageIndex, 1)
item.imageIds = imageIds
item.imageUrls = imageUrls
item.imageAttachments = imageAttachments
applyItemImageSummary(item)
renderItemsEditor()
} }
window.__clearItemImage = function (index) { window.__clearItemImage = function (index) {
syncItemsStateFromEditor() syncItemsStateFromEditor()
@@ -793,16 +1010,19 @@ routerAdd('GET', '/manage/dictionary-manage', function (e) {
return return
} }
state.items[index].image = '' state.items[index].image = ''
state.items[index].imageIds = []
state.items[index].imageUrls = []
state.items[index].imageAttachments = []
state.items[index].imageUrl = '' state.items[index].imageUrl = ''
state.items[index].imageAttachment = null state.items[index].imageAttachment = null
renderItemsEditor() renderItemsEditor()
setStatus('已清空该枚举项图片。', 'success') setStatus('已清空该枚举项所有图片。', 'success')
} }
window.__removeItem = function (index) { window.__removeItem = function (index) {
syncItemsStateFromEditor() syncItemsStateFromEditor()
state.items.splice(index, 1) state.items.splice(index, 1)
if (!state.items.length) { if (!state.items.length) {
state.items.push({ enum: '', description: '', image: '', imageUrl: '', imageAttachment: null, sortOrder: 1 }) state.items.push(applyItemImageSummary({ enum: '', description: '', image: '', imageIds: [], imageUrls: [], imageAttachments: [], imageUrl: '', imageAttachment: null, sortOrder: 1 }))
} }
normalizeItemEnums() normalizeItemEnums()
renderItemsEditor() renderItemsEditor()
@@ -839,7 +1059,7 @@ routerAdd('GET', '/manage/dictionary-manage', function (e) {
document.getElementById('addItemBtn').addEventListener('click', function () { document.getElementById('addItemBtn').addEventListener('click', function () {
syncItemsStateFromEditor() syncItemsStateFromEditor()
const used = new Set(state.items.map(function (item) { return String(item.enum || '') })) const used = new Set(state.items.map(function (item) { return String(item.enum || '') }))
state.items.push({ enum: nextAutoEnum(used), description: '', image: '', imageUrl: '', imageAttachment: null, sortOrder: state.items.length + 1 }) state.items.push(applyItemImageSummary({ enum: nextAutoEnum(used), description: '', image: '', imageIds: [], imageUrls: [], imageAttachments: [], imageUrl: '', imageAttachment: null, sortOrder: state.items.length + 1 }))
renderItemsEditor() renderItemsEditor()
scrollEditorModalToBottom() scrollEditorModalToBottom()
}) })

View File

@@ -753,10 +753,31 @@ paths:
updated: 记录更新时间 | string updated: 记录更新时间 | string
document_id: 文档业务ID | string document_id: 文档业务ID | string
document_create: 文档创建时间,由数据库自动生成 | string document_create: 文档创建时间,由数据库自动生成 | string
document_effect_date: 文档生效日期 | string
document_expiry_date: 文档到期日期 | string
document_title: 文档标题 | string document_title: 文档标题 | string
document_type: 文档类型按system_dict_id@dict_word_enum保存 | string document_type: 文档类型按system_dict_id@dict_word_enum保存 | string
document_subtitle: 文档副标题 | string
document_summary: 文档摘要 | string
document_content: 正文内容 | string
document_image: 图片附件ID串底层按|分隔 | string
document_video: 视频附件ID串底层按|分隔 | string
document_file: 文件附件ID串底层按|分隔 | string
document_status: 文档状态 | string document_status: 文档状态 | string
document_owner: 上传者openid | string document_owner: 上传者openid | string
document_relation_model: 关联机型标识 | string
document_keywords: 关键词,多选按|分隔 | string
document_share_count: 分享次数 | number
document_download_count: 下载次数 | number
document_favorite_count: 收藏次数 | number
document_embedding_status: 文档嵌入状态 | string
document_embedding_error: 文档嵌入错误原因 | string
document_embedding_lasttime: 最后一次嵌入更新时间 | string
document_vector_version: 向量版本号或模型名称 | string
document_product_categories: 产品关联文档,多选按|分隔 | string
document_application_scenarios: 筛选依据,多选按|分隔 | string
document_hotel_type: 适用场景,多选按|分隔 | string
document_remark: 备注 | string
filterByTypeToken: filterByTypeToken:
value: value:
page: 页码 | integer page: 页码 | integer
@@ -771,10 +792,31 @@ paths:
updated: 记录更新时间 | string updated: 记录更新时间 | string
document_id: 文档业务ID | string document_id: 文档业务ID | string
document_create: 文档创建时间,由数据库自动生成 | string document_create: 文档创建时间,由数据库自动生成 | string
document_effect_date: 文档生效日期 | string
document_expiry_date: 文档到期日期 | string
document_title: 文档标题 | string document_title: 文档标题 | string
document_type: 文档类型按system_dict_id@dict_word_enum保存 | string document_type: 文档类型按system_dict_id@dict_word_enum保存 | string
document_subtitle: 文档副标题 | string
document_summary: 文档摘要 | string
document_content: 正文内容 | string
document_image: 图片附件ID串底层按|分隔 | string
document_video: 视频附件ID串底层按|分隔 | string
document_file: 文件附件ID串底层按|分隔 | string
document_status: 文档状态 | string document_status: 文档状态 | string
document_owner: 上传者openid | string document_owner: 上传者openid | string
document_relation_model: 关联机型标识 | string
document_keywords: 关键词,多选按|分隔 | string
document_share_count: 分享次数 | number
document_download_count: 下载次数 | number
document_favorite_count: 收藏次数 | number
document_embedding_status: 文档嵌入状态 | string
document_embedding_error: 文档嵌入错误原因 | string
document_embedding_lasttime: 最后一次嵌入更新时间 | string
document_vector_version: 向量版本号或模型名称 | string
document_product_categories: 产品关联文档,多选按|分隔 | string
document_application_scenarios: 筛选依据,多选按|分隔 | string
document_hotel_type: 适用场景,多选按|分隔 | string
document_remark: 备注 | string
- id: ofy47wp9mmm0aub - id: ofy47wp9mmm0aub
collectionId: pbc_3636602973 collectionId: pbc_3636602973
collectionName: tbl_document collectionName: tbl_document
@@ -782,10 +824,31 @@ paths:
updated: '2026-03-28 07:20:00.000Z' updated: '2026-03-28 07:20:00.000Z'
document_id: DOC-1774680568340-TeUSQn document_id: DOC-1774680568340-TeUSQn
document_create: '2026-03-28 08:22:48.000Z' document_create: '2026-03-28 08:22:48.000Z'
document_effect_date: ''
document_expiry_date: ''
document_title: 易从碳达人节能系统,为酒店每天每间房省二元,以智能推动酒店ESG双碳落地!上海酒店用品展我们在E7A01等您!! document_title: 易从碳达人节能系统,为酒店每天每间房省二元,以智能推动酒店ESG双碳落地!上海酒店用品展我们在E7A01等您!!
document_type: DICT-1774599144591-hAEFQj@UT1 document_type: DICT-1774599144591-hAEFQj@UT1
document_subtitle: ''
document_summary: ''
document_content: ''
document_image: ATT-1774680568287-zuhJWN
document_video: ''
document_file: ''
document_status: 有效 document_status: 有效
document_owner: su13106859882 document_owner: su13106859882
document_relation_model: ''
document_keywords: ''
document_share_count: 0
document_download_count: 0
document_favorite_count: 0
document_embedding_status: ''
document_embedding_error: ''
document_embedding_lasttime: ''
document_vector_version: ''
document_product_categories: ''
document_application_scenarios: ''
document_hotel_type: ''
document_remark: ''
'400': '400':
description: 查询参数错误 description: 查询参数错误
content: content:
@@ -1433,13 +1496,46 @@ components:
type: string type: string
description: "文档创建时间,由数据库自动生成" description: "文档创建时间,由数据库自动生成"
example: 文档创建时间,由数据库自动生成 | string example: 文档创建时间,由数据库自动生成 | string
document_effect_date:
type: string
description: "文档生效日期"
example: 文档生效日期 | string
document_expiry_date:
type: string
description: "文档到期日期"
example: 文档到期日期 | string
document_title: document_title:
type: string type: string
description: "文档标题" description: "文档标题"
example: 文档标题 | string example: 文档标题 | string
document_type: document_type:
type: string type: string
description: "文档类型,多选时按 system_dict_id@dict_word_enum|... 保存"
example: 文档类型按system_dict_id@dict_word_enum保存 | string example: 文档类型按system_dict_id@dict_word_enum保存 | string
document_subtitle:
type: string
description: "文档副标题"
example: 文档副标题 | string
document_summary:
type: string
description: "文档摘要"
example: 文档摘要 | string
document_content:
type: string
description: "正文内容,保存 Markdown"
example: 正文内容 | string
document_image:
type: string
description: "图片附件 ID 集合,底层以 | 分隔"
example: 图片附件ID串底层按|分隔 | string
document_video:
type: string
description: "视频附件 ID 集合,底层以 | 分隔"
example: 视频附件ID串底层按|分隔 | string
document_file:
type: string
description: "文件附件 ID 集合,底层以 | 分隔"
example: 文件附件ID串底层按|分隔 | string
document_status: document_status:
type: string type: string
description: "文档状态,仅 `有效` / `过期`" description: "文档状态,仅 `有效` / `过期`"
@@ -1448,6 +1544,58 @@ components:
type: string type: string
description: "上传者 openid" description: "上传者 openid"
example: 上传者openid | string example: 上传者openid | string
document_relation_model:
type: string
description: "关联机型 / 模型标识"
example: 关联机型标识 | string
document_keywords:
type: string
description: "关键词,多选后以 | 分隔"
example: 关键词,多选按|分隔 | string
document_share_count:
type: number
description: "分享次数"
example: 分享次数 | number
document_download_count:
type: number
description: "下载次数"
example: 下载次数 | number
document_favorite_count:
type: number
description: "收藏次数"
example: 收藏次数 | number
document_embedding_status:
type: string
description: "文档嵌入状态"
example: 文档嵌入状态 | string
document_embedding_error:
type: string
description: "文档嵌入错误原因"
example: 文档嵌入错误原因 | string
document_embedding_lasttime:
type: string
description: "最后一次嵌入更新时间"
example: 最后一次嵌入更新时间 | string
document_vector_version:
type: string
description: "向量版本号 / 模型名称"
example: 向量版本号或模型名称 | string
document_product_categories:
type: string
description: "产品关联文档,多选后以 | 分隔"
example: 产品关联文档,多选按|分隔 | string
document_application_scenarios:
type: string
description: "筛选依据,多选后以 | 分隔"
example: 筛选依据,多选按|分隔 | string
document_hotel_type:
type: string
description: "适用场景,多选后以 | 分隔"
example: 适用场景,多选按|分隔 | string
document_remark:
type: string
description: "备注"
example: 备注 | string
PocketBaseDocumentRecord: PocketBaseDocumentRecord:
allOf: allOf:
- $ref: '#/components/schemas/PocketBaseRecordBase' - $ref: '#/components/schemas/PocketBaseRecordBase'
@@ -1460,10 +1608,31 @@ components:
updated: 记录更新时间 | string updated: 记录更新时间 | string
document_id: 文档业务ID | string document_id: 文档业务ID | string
document_create: 文档创建时间,由数据库自动生成 | string document_create: 文档创建时间,由数据库自动生成 | string
document_effect_date: 文档生效日期 | string
document_expiry_date: 文档到期日期 | string
document_title: 文档标题 | string document_title: 文档标题 | string
document_type: 文档类型按system_dict_id@dict_word_enum保存 | string document_type: 文档类型按system_dict_id@dict_word_enum保存 | string
document_subtitle: 文档副标题 | string
document_summary: 文档摘要 | string
document_content: 正文内容 | string
document_image: 图片附件ID串底层按|分隔 | string
document_video: 视频附件ID串底层按|分隔 | string
document_file: 文件附件ID串底层按|分隔 | string
document_status: 文档状态 | string document_status: 文档状态 | string
document_owner: 上传者openid | string document_owner: 上传者openid | string
document_relation_model: 关联机型标识 | string
document_keywords: 关键词,多选按|分隔 | string
document_share_count: 分享次数 | number
document_download_count: 下载次数 | number
document_favorite_count: 收藏次数 | number
document_embedding_status: 文档嵌入状态 | string
document_embedding_error: 文档嵌入错误原因 | string
document_embedding_lasttime: 最后一次嵌入更新时间 | string
document_vector_version: 向量版本号或模型名称 | string
document_product_categories: 产品关联文档,多选按|分隔 | string
document_application_scenarios: 筛选依据,多选按|分隔 | string
document_hotel_type: 适用场景,多选按|分隔 | string
document_remark: 备注 | string
PocketBaseDocumentListResponse: PocketBaseDocumentListResponse:
type: object type: object
required: required:
@@ -1510,10 +1679,31 @@ components:
updated: 记录更新时间 | string updated: 记录更新时间 | string
document_id: 文档业务ID | string document_id: 文档业务ID | string
document_create: 文档创建时间,由数据库自动生成 | string document_create: 文档创建时间,由数据库自动生成 | string
document_effect_date: 文档生效日期 | string
document_expiry_date: 文档到期日期 | string
document_title: 文档标题 | string document_title: 文档标题 | string
document_type: 文档类型按system_dict_id@dict_word_enum保存 | string document_type: 文档类型按system_dict_id@dict_word_enum保存 | string
document_subtitle: 文档副标题 | string
document_summary: 文档摘要 | string
document_content: 正文内容 | string
document_image: 图片附件ID串底层按|分隔 | string
document_video: 视频附件ID串底层按|分隔 | string
document_file: 文件附件ID串底层按|分隔 | string
document_status: 文档状态 | string document_status: 文档状态 | string
document_owner: 上传者openid | string document_owner: 上传者openid | string
document_relation_model: 关联机型标识 | string
document_keywords: 关键词,多选按|分隔 | string
document_share_count: 分享次数 | number
document_download_count: 下载次数 | number
document_favorite_count: 收藏次数 | number
document_embedding_status: 文档嵌入状态 | string
document_embedding_error: 文档嵌入错误原因 | string
document_embedding_lasttime: 最后一次嵌入更新时间 | string
document_vector_version: 向量版本号或模型名称 | string
document_product_categories: 产品关联文档,多选按|分隔 | string
document_application_scenarios: 筛选依据,多选按|分隔 | string
document_hotel_type: 适用场景,多选按|分隔 | string
document_remark: 备注 | string
PocketBaseNativeError: PocketBaseNativeError:
type: object type: object
properties: properties: