feat: 更新字典管理功能;优化图片上传和展示逻辑,支持多图上传和拖拽排序;扩展文档接口,新增多个字段以支持更丰富的文档信息
This commit is contained in:
@@ -150,11 +150,13 @@ function normalizeDictionaryItem(item, index) {
|
||||
throw createAppError(400, '第 ' + (index + 1) + ' 项 sortOrder 必须为数字')
|
||||
}
|
||||
|
||||
const imageIdList = normalizeAttachmentIdList(current.image, '第 ' + (index + 1) + ' 项 image')
|
||||
|
||||
return {
|
||||
enum: String(current.enum),
|
||||
description: String(current.description),
|
||||
sortOrder: sortOrderNumber,
|
||||
image: current.image ? String(current.image) : '',
|
||||
image: imageIdList.join('|'),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
const enums = safeJsonParse(record.getString('dict_word_enum'), [])
|
||||
const descriptions = safeJsonParse(record.getString('dict_word_description'), [])
|
||||
@@ -29,21 +53,31 @@ function normalizeItemsFromRecord(record) {
|
||||
continue
|
||||
}
|
||||
|
||||
const imageAttachmentId = typeof images[i] === 'undefined' ? '' : String(images[i] || '')
|
||||
let imageAttachment = null
|
||||
if (imageAttachmentId) {
|
||||
const rawImageValue = typeof images[i] === 'undefined' ? '' : String(images[i] || '')
|
||||
const imageAttachmentIds = parseAttachmentIdList(rawImageValue)
|
||||
const imageAttachments = []
|
||||
const imageUrls = []
|
||||
|
||||
for (let imageIndex = 0; imageIndex < imageAttachmentIds.length; imageIndex += 1) {
|
||||
const attachmentId = imageAttachmentIds[imageIndex]
|
||||
try {
|
||||
imageAttachment = documentService.getAttachmentDetail(imageAttachmentId)
|
||||
const imageAttachment = documentService.getAttachmentDetail(attachmentId)
|
||||
imageAttachments.push(imageAttachment)
|
||||
imageUrls.push(imageAttachment ? imageAttachment.attachments_url : '')
|
||||
} catch (_error) {
|
||||
imageAttachment = null
|
||||
imageAttachments.push(null)
|
||||
imageUrls.push('')
|
||||
}
|
||||
}
|
||||
|
||||
items.push({
|
||||
enum: typeof enums[i] === 'undefined' ? '' : String(enums[i]),
|
||||
description: typeof descriptions[i] === 'undefined' ? '' : String(descriptions[i]),
|
||||
image: imageAttachmentId,
|
||||
imageUrl: imageAttachment ? imageAttachment.attachments_url : '',
|
||||
imageAttachment: imageAttachment,
|
||||
image: imageAttachmentIds.join('|'),
|
||||
imageIds: imageAttachmentIds,
|
||||
imageUrls: imageUrls,
|
||||
imageAttachments: imageAttachments,
|
||||
imageUrl: imageUrls.length ? imageUrls[0] : '',
|
||||
sortOrder: Number(sortOrders[i] || 0),
|
||||
})
|
||||
}
|
||||
@@ -89,7 +123,7 @@ function fillDictionaryItems(record, items) {
|
||||
for (let i = 0; i < items.length; i += 1) {
|
||||
enums.push(items[i].enum)
|
||||
descriptions.push(items[i].description)
|
||||
images.push(items[i].image || '')
|
||||
images.push(parseAttachmentIdList(items[i].image).join('|'))
|
||||
sortOrders.push(items[i].sortOrder)
|
||||
}
|
||||
|
||||
|
||||
@@ -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-more { margin-top: 8px; display: none; }
|
||||
.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; }
|
||||
.icon-btn { min-width: 36px; padding: 6px 10px; line-height: 1; font-size: 18px; }
|
||||
.drop-tip { color: #64748b; font-size: 12px; margin-top: 6px; }
|
||||
@@ -158,10 +168,8 @@ routerAdd('GET', '/manage/dictionary-manage', function (e) {
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>描述</th>
|
||||
<th>配置</th>
|
||||
<th>图片</th>
|
||||
<th>排序</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="itemsBody"></tbody>
|
||||
@@ -204,6 +212,7 @@ routerAdd('GET', '/manage/dictionary-manage', function (e) {
|
||||
enumSeed: '',
|
||||
enumCounter: 1,
|
||||
expandedPreviewKey: '',
|
||||
draggingImage: null,
|
||||
}
|
||||
|
||||
const statusEl = document.getElementById('status')
|
||||
@@ -432,11 +441,21 @@ routerAdd('GET', '/manage/dictionary-manage', function (e) {
|
||||
enum: String((item && item.enum) || '').trim(),
|
||||
description: String((item && item.description) || ''),
|
||||
image: (item && item.image) || '',
|
||||
imageUrl: (item && item.imageUrl) || '',
|
||||
imageAttachment: (item && item.imageAttachment) || null,
|
||||
imageIds: [],
|
||||
imageUrls: [],
|
||||
imageAttachments: [],
|
||||
imageUrl: '',
|
||||
imageAttachment: null,
|
||||
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)) {
|
||||
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) {
|
||||
const desc = escapeHtml(item && item.description ? item.description : '(无描述)')
|
||||
const imageHtml = item && item.imageUrl
|
||||
? '<img class="thumb previewable" src="' + escapeHtml(item.imageUrl) + '" alt="" onclick="window.__previewImage(\\'' + escapeJsString(item.imageUrl) + '\\')" />'
|
||||
const imageUrls = normalizeImageUrlList(item && item.imageUrls, item && item.imageUrl, normalizeAttachmentIdList(item && item.image).length)
|
||||
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>'
|
||||
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) {
|
||||
@@ -516,16 +597,19 @@ routerAdd('GET', '/manage/dictionary-manage', function (e) {
|
||||
remarkInput.value = record ? (record.dict_word_remark || '') : ''
|
||||
state.items = record && Array.isArray(record.items) && record.items.length
|
||||
? record.items.map(function (item) {
|
||||
return {
|
||||
return applyItemImageSummary({
|
||||
enum: item.enum,
|
||||
description: item.description,
|
||||
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 || '',
|
||||
imageAttachment: item.imageAttachment || null,
|
||||
sortOrder: item.sortOrder,
|
||||
}
|
||||
})
|
||||
: [{ enum: '', description: '', image: '', imageUrl: '', imageAttachment: null, sortOrder: 1 }]
|
||||
})
|
||||
: [applyItemImageSummary({ enum: '', description: '', image: '', imageIds: [], imageUrls: [], imageAttachments: [], imageUrl: '', imageAttachment: null, sortOrder: 1 })]
|
||||
normalizeItemEnums()
|
||||
renderItemsEditor()
|
||||
editorModal.classList.add('show')
|
||||
@@ -562,27 +646,49 @@ routerAdd('GET', '/manage/dictionary-manage', function (e) {
|
||||
|
||||
function renderItemsEditor() {
|
||||
itemsBody.innerHTML = state.items.map(function (item, index) {
|
||||
const imageCell = item.imageUrl
|
||||
? '<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 imageIds = normalizeAttachmentIdList(item.imageIds || item.image)
|
||||
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>'
|
||||
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)">'
|
||||
+ imageCell
|
||||
+ '<div class="image-upload-row">'
|
||||
+ '<input type="file" accept="image/*" onchange="window.__uploadItemImage(' + index + ', this)" />'
|
||||
+ '<button class="btn btn-light icon-btn" type="button" title="删除图片" onclick="window.__clearItemImage(' + index + ')">🗑️</button>'
|
||||
+ '<input type="file" accept="image/*" multiple onchange="window.__uploadItemImages(' + index + ', this)" />'
|
||||
+ '<button class="btn btn-light" type="button" onclick="window.__clearItemImage(' + index + ')">清空全部</button>'
|
||||
+ '</div>'
|
||||
+ '<div class="drop-tip">支持拖拽图片到本区域上传</div>'
|
||||
+ '<div class="drop-tip">支持拖拽多张图片上传;图片可拖动调整顺序。</div>'
|
||||
+ '</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>'
|
||||
}).join('')
|
||||
}
|
||||
|
||||
async function setItemImageFromFile(index, file) {
|
||||
if (!file) {
|
||||
async function addItemImagesFromFiles(index, files) {
|
||||
const validFiles = Array.from(files || []).filter(function (file) {
|
||||
return !!file
|
||||
})
|
||||
|
||||
if (!validFiles.length) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -594,10 +700,22 @@ routerAdd('GET', '/manage/dictionary-manage', function (e) {
|
||||
setStatus('正在上传字典项图片...', '')
|
||||
showLoading('正在上传字典项图片,请稍候...')
|
||||
try {
|
||||
const attachment = await uploadAttachment(file, 'dict-item-' + (index + 1))
|
||||
state.items[index].image = attachment.attachments_id
|
||||
state.items[index].imageUrl = attachment.attachments_url || ''
|
||||
state.items[index].imageAttachment = attachment
|
||||
for (let fileIndex = 0; fileIndex < validFiles.length; fileIndex += 1) {
|
||||
const attachment = await uploadAttachment(validFiles[fileIndex], 'dict-item-' + (index + 1) + '-' + (fileIndex + 1))
|
||||
if (!state.items[index]) {
|
||||
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()
|
||||
setStatus('字典项图片上传成功。', 'success')
|
||||
} catch (err) {
|
||||
@@ -616,6 +734,9 @@ routerAdd('GET', '/manage/dictionary-manage', function (e) {
|
||||
enum: state.items[index] ? String(state.items[index].enum || '') : '',
|
||||
description: descriptionInput.value.trim(),
|
||||
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 || '') : '',
|
||||
imageAttachment: state.items[index] ? (state.items[index].imageAttachment || null) : null,
|
||||
sortOrder: Number(sortOrderInput.value || index + 1),
|
||||
@@ -628,12 +749,14 @@ routerAdd('GET', '/manage/dictionary-manage', function (e) {
|
||||
if (!rows.length) {
|
||||
return
|
||||
}
|
||||
state.items = collectItemsFromEditor()
|
||||
state.items = collectItemsFromEditor().map(function (item) {
|
||||
return applyItemImageSummary(item)
|
||||
})
|
||||
}
|
||||
|
||||
async function uploadItemImage(index, inputEl) {
|
||||
const file = inputEl && inputEl.files && inputEl.files[0]
|
||||
await setItemImageFromFile(index, file)
|
||||
async function uploadItemImages(index, inputEl) {
|
||||
const files = inputEl && inputEl.files ? inputEl.files : []
|
||||
await addItemImagesFromFiles(index, files)
|
||||
}
|
||||
|
||||
async function loadList() {
|
||||
@@ -777,15 +900,109 @@ routerAdd('GET', '/manage/dictionary-manage', function (e) {
|
||||
imageViewerImg.src = url
|
||||
imageViewer.classList.add('show')
|
||||
}
|
||||
window.__uploadItemImage = uploadItemImage
|
||||
window.__uploadItemImages = uploadItemImages
|
||||
window.__allowItemDrop = function (event) {
|
||||
event.preventDefault()
|
||||
}
|
||||
window.__dropItemImage = function (index, event) {
|
||||
event.preventDefault()
|
||||
const files = event.dataTransfer && event.dataTransfer.files
|
||||
const file = files && files[0]
|
||||
setItemImageFromFile(index, file)
|
||||
addItemImagesFromFiles(index, files)
|
||||
}
|
||||
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) {
|
||||
syncItemsStateFromEditor()
|
||||
@@ -793,16 +1010,19 @@ routerAdd('GET', '/manage/dictionary-manage', function (e) {
|
||||
return
|
||||
}
|
||||
state.items[index].image = ''
|
||||
state.items[index].imageIds = []
|
||||
state.items[index].imageUrls = []
|
||||
state.items[index].imageAttachments = []
|
||||
state.items[index].imageUrl = ''
|
||||
state.items[index].imageAttachment = null
|
||||
renderItemsEditor()
|
||||
setStatus('已清空该枚举项图片。', 'success')
|
||||
setStatus('已清空该枚举项所有图片。', 'success')
|
||||
}
|
||||
window.__removeItem = function (index) {
|
||||
syncItemsStateFromEditor()
|
||||
state.items.splice(index, 1)
|
||||
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()
|
||||
renderItemsEditor()
|
||||
@@ -839,7 +1059,7 @@ routerAdd('GET', '/manage/dictionary-manage', function (e) {
|
||||
document.getElementById('addItemBtn').addEventListener('click', function () {
|
||||
syncItemsStateFromEditor()
|
||||
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()
|
||||
scrollEditorModalToBottom()
|
||||
})
|
||||
|
||||
@@ -753,10 +753,31 @@ paths:
|
||||
updated: 记录更新时间 | string
|
||||
document_id: 文档业务ID | string
|
||||
document_create: 文档创建时间,由数据库自动生成 | string
|
||||
document_effect_date: 文档生效日期 | string
|
||||
document_expiry_date: 文档到期日期 | string
|
||||
document_title: 文档标题 | 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_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:
|
||||
value:
|
||||
page: 页码 | integer
|
||||
@@ -771,10 +792,31 @@ paths:
|
||||
updated: 记录更新时间 | string
|
||||
document_id: 文档业务ID | string
|
||||
document_create: 文档创建时间,由数据库自动生成 | string
|
||||
document_effect_date: 文档生效日期 | string
|
||||
document_expiry_date: 文档到期日期 | string
|
||||
document_title: 文档标题 | 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_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
|
||||
collectionId: pbc_3636602973
|
||||
collectionName: tbl_document
|
||||
@@ -782,10 +824,31 @@ paths:
|
||||
updated: '2026-03-28 07:20:00.000Z'
|
||||
document_id: DOC-1774680568340-TeUSQn
|
||||
document_create: '2026-03-28 08:22:48.000Z'
|
||||
document_effect_date: ''
|
||||
document_expiry_date: ''
|
||||
document_title: 易从碳达人节能系统,为酒店每天每间房省二元,以智能推动酒店ESG双碳落地!上海酒店用品展我们在E7A01等您!!
|
||||
document_type: DICT-1774599144591-hAEFQj@UT1
|
||||
document_subtitle: ''
|
||||
document_summary: ''
|
||||
document_content: ''
|
||||
document_image: ATT-1774680568287-zuhJWN
|
||||
document_video: ''
|
||||
document_file: ''
|
||||
document_status: 有效
|
||||
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':
|
||||
description: 查询参数错误
|
||||
content:
|
||||
@@ -1433,13 +1496,46 @@ components:
|
||||
type: string
|
||||
description: "文档创建时间,由数据库自动生成"
|
||||
example: 文档创建时间,由数据库自动生成 | string
|
||||
document_effect_date:
|
||||
type: string
|
||||
description: "文档生效日期"
|
||||
example: 文档生效日期 | string
|
||||
document_expiry_date:
|
||||
type: string
|
||||
description: "文档到期日期"
|
||||
example: 文档到期日期 | string
|
||||
document_title:
|
||||
type: string
|
||||
description: "文档标题"
|
||||
example: 文档标题 | string
|
||||
document_type:
|
||||
type: string
|
||||
description: "文档类型,多选时按 system_dict_id@dict_word_enum|... 保存"
|
||||
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:
|
||||
type: string
|
||||
description: "文档状态,仅 `有效` / `过期`"
|
||||
@@ -1448,6 +1544,58 @@ components:
|
||||
type: string
|
||||
description: "上传者 openid"
|
||||
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:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/PocketBaseRecordBase'
|
||||
@@ -1460,10 +1608,31 @@ components:
|
||||
updated: 记录更新时间 | string
|
||||
document_id: 文档业务ID | string
|
||||
document_create: 文档创建时间,由数据库自动生成 | string
|
||||
document_effect_date: 文档生效日期 | string
|
||||
document_expiry_date: 文档到期日期 | string
|
||||
document_title: 文档标题 | 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_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:
|
||||
type: object
|
||||
required:
|
||||
@@ -1510,10 +1679,31 @@ components:
|
||||
updated: 记录更新时间 | string
|
||||
document_id: 文档业务ID | string
|
||||
document_create: 文档创建时间,由数据库自动生成 | string
|
||||
document_effect_date: 文档生效日期 | string
|
||||
document_expiry_date: 文档到期日期 | string
|
||||
document_title: 文档标题 | 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_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:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
Reference in New Issue
Block a user