feat: 添加产品列表集合初始化脚本,包含字段定义、索引创建及校验逻辑

This commit is contained in:
2026-04-01 11:59:58 +08:00
parent 7a21b3e5db
commit c9c4b4aaf8
20 changed files with 2806 additions and 1 deletions

View File

@@ -36,6 +36,11 @@ require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/document/detail.js`)
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/document/create.js`)
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/document/update.js`)
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/document/delete.js`)
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/product/list.js`)
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/product/detail.js`)
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/product/create.js`)
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/product/update.js`)
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/product/delete.js`)
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/document-history/list.js`)
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/sdk-permission/context.js`)
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/sdk-permission/role-save.js`)

View File

@@ -1,5 +1,6 @@
require(`${__hooks}/bai_web_pb_hooks/pages/index.js`)
require(`${__hooks}/bai_web_pb_hooks/pages/login.js`)
require(`${__hooks}/bai_web_pb_hooks/pages/document-manage.js`)
require(`${__hooks}/bai_web_pb_hooks/pages/product-manage.js`)
require(`${__hooks}/bai_web_pb_hooks/pages/dictionary-manage.js`)
require(`${__hooks}/bai_web_pb_hooks/pages/sdk-permission-manage.js`)

View File

@@ -0,0 +1,25 @@
routerAdd('POST', '/api/product/create', function (e) {
const guards = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js`)
const productService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/productService.js`)
const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
const { success, fail } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/response.js`)
try {
guards.requireJson(e)
const authState = guards.requireManagePlatformUser(e)
guards.duplicateGuard(e)
const payload = guards.validateProductMutationBody(e, false)
const data = productService.createProduct(authState.openid, payload)
return success(e, '新增产品成功', data)
} catch (err) {
const status = (err && err.statusCode) || (err && err.status) || 400
logger.error('新增产品失败', {
status: status,
errMsg: (err && err.message) || '未知错误',
data: (err && err.data) || {},
})
return fail(e, (err && err.message) || '操作失败', (err && err.data) || {}, status)
}
})

View File

@@ -0,0 +1,25 @@
routerAdd('POST', '/api/product/delete', function (e) {
const guards = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js`)
const productService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/productService.js`)
const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
const { success, fail } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/response.js`)
try {
guards.requireJson(e)
const authState = guards.requireManagePlatformUser(e)
guards.duplicateGuard(e)
const payload = guards.validateProductDeleteBody(e)
const data = productService.deleteProduct(authState.openid, payload.prod_list_id)
return success(e, '删除产品成功', data)
} catch (err) {
const status = (err && err.statusCode) || (err && err.status) || 400
logger.error('删除产品失败', {
status: status,
errMsg: (err && err.message) || '未知错误',
data: (err && err.data) || {},
})
return fail(e, (err && err.message) || '操作失败', (err && err.data) || {}, status)
}
})

View File

@@ -0,0 +1,24 @@
routerAdd('POST', '/api/product/detail', function (e) {
const guards = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js`)
const productService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/productService.js`)
const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
const { success, fail } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/response.js`)
try {
guards.requireJson(e)
guards.requireManagePlatformUser(e)
const payload = guards.validateProductDetailBody(e)
const data = productService.getProductDetail(payload.prod_list_id)
return success(e, '查询产品详情成功', data)
} catch (err) {
const status = (err && err.statusCode) || (err && err.status) || 400
logger.error('查询产品详情失败', {
status: status,
errMsg: (err && err.message) || '未知错误',
data: (err && err.data) || {},
})
return fail(e, (err && err.message) || '操作失败', (err && err.data) || {}, status)
}
})

View File

@@ -0,0 +1,26 @@
routerAdd('POST', '/api/product/list', function (e) {
const guards = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js`)
const productService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/productService.js`)
const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
const { success, fail } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/response.js`)
try {
guards.requireJson(e)
guards.requireManagePlatformUser(e)
const payload = guards.validateProductListBody(e)
const data = productService.listProducts(payload)
return success(e, '查询产品列表成功', {
items: data,
})
} catch (err) {
const status = (err && err.statusCode) || (err && err.status) || 400
logger.error('查询产品列表失败', {
status: status,
errMsg: (err && err.message) || '未知错误',
data: (err && err.data) || {},
})
return fail(e, (err && err.message) || '操作失败', (err && err.data) || {}, status)
}
})

View File

@@ -0,0 +1,25 @@
routerAdd('POST', '/api/product/update', function (e) {
const guards = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js`)
const productService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/productService.js`)
const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
const { success, fail } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/response.js`)
try {
guards.requireJson(e)
const authState = guards.requireManagePlatformUser(e)
guards.duplicateGuard(e)
const payload = guards.validateProductMutationBody(e, true)
const data = productService.updateProduct(authState.openid, payload)
return success(e, '修改产品成功', data)
} catch (err) {
const status = (err && err.statusCode) || (err && err.status) || 400
logger.error('修改产品失败', {
status: status,
errMsg: (err && err.message) || '未知错误',
data: (err && err.data) || {},
})
return fail(e, (err && err.message) || '操作失败', (err && err.data) || {}, status)
}
})

View File

@@ -347,6 +347,92 @@ function validateDocumentDeleteBody(e) {
return validateDocumentDetailBody(e)
}
function normalizeProductParameters(value) {
if (value === null || typeof value === 'undefined' || value === '') {
return {}
}
if (typeof value !== 'object' || Array.isArray(value)) {
throw createAppError(400, 'prod_list_parameters 必须为对象')
}
const result = {}
const keys = Object.keys(value)
for (let i = 0; i < keys.length; i += 1) {
const key = String(keys[i] || '').trim()
if (!key) {
continue
}
const current = value[keys[i]]
result[key] = current === null || typeof current === 'undefined' ? '' : String(current)
}
return result
}
function validateProductListBody(e) {
const payload = parseBody(e)
return {
keyword: payload.keyword || '',
status: payload.status || '',
prod_list_category: payload.prod_list_category || '',
}
}
function validateProductDetailBody(e) {
const payload = parseBody(e)
if (!payload.prod_list_id) {
throw createAppError(400, 'prod_list_id 为必填项')
}
return {
prod_list_id: String(payload.prod_list_id || '').trim(),
}
}
function validateProductMutationBody(e, isUpdate) {
const payload = parseBody(e)
if (isUpdate && !payload.prod_list_id) {
throw createAppError(400, 'prod_list_id 为必填项')
}
if (!payload.prod_list_name) {
throw createAppError(400, 'prod_list_name 为必填项')
}
if (!payload.prod_list_category) {
throw createAppError(400, 'prod_list_category 为必填项')
}
return {
prod_list_id: payload.prod_list_id || '',
prod_list_name: payload.prod_list_name || '',
prod_list_modelnumber: payload.prod_list_modelnumber || '',
prod_list_icon: payload.prod_list_icon || '',
prod_list_description: payload.prod_list_description || '',
prod_list_feature: payload.prod_list_feature || '',
prod_list_parameters: normalizeProductParameters(payload.prod_list_parameters),
prod_list_plantype: payload.prod_list_plantype || '',
prod_list_category: payload.prod_list_category || '',
prod_list_sort: typeof payload.prod_list_sort === 'undefined' ? 0 : payload.prod_list_sort,
prod_list_comm_type: payload.prod_list_comm_type || '',
prod_list_series: payload.prod_list_series || '',
prod_list_power_supply: payload.prod_list_power_supply || '',
prod_list_tags: payload.prod_list_tags || '',
prod_list_status: payload.prod_list_status || '',
prod_list_basic_price: typeof payload.prod_list_basic_price === 'undefined' ? '' : payload.prod_list_basic_price,
prod_list_remark: payload.prod_list_remark || '',
}
}
function validateProductDeleteBody(e) {
return validateProductDetailBody(e)
}
function validateDocumentHistoryListBody(e) {
const payload = parseBody(e)
@@ -518,6 +604,10 @@ module.exports = {
validateDocumentDetailBody,
validateDocumentMutationBody,
validateDocumentDeleteBody,
validateProductListBody,
validateProductDetailBody,
validateProductMutationBody,
validateProductDeleteBody,
validateDocumentHistoryListBody,
validateSdkPermissionContextBody,
validateSdkPermissionRoleBody,

View File

@@ -0,0 +1,474 @@
const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
const { createAppError } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/appError.js`)
const documentService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/documentService.js`)
function buildBusinessId(prefix) {
return prefix + '-' + new Date().getTime() + '-' + $security.randomString(6)
}
function normalizeText(value) {
return String(value || '').replace(/^\s+|\s+$/g, '')
}
function normalizeOptionalNumberValue(value, fieldName) {
if (value === '' || value === null || typeof value === 'undefined') {
return null
}
const num = Number(value)
if (!Number.isFinite(num)) {
throw createAppError(400, fieldName + ' 必须为数字')
}
return num
}
function normalizeSortValue(value) {
if (value === '' || value === null || typeof value === 'undefined') {
return 0
}
const num = Number(value)
if (!Number.isFinite(num)) {
throw createAppError(400, 'prod_list_sort 必须为数字')
}
return Math.floor(num)
}
function normalizePipeValues(value) {
return String(value || '')
.split('|')
.map(function (item) {
return normalizeText(item)
})
.filter(function (item) {
return !!item
})
}
function joinUniquePipeValues(value) {
const source = Array.isArray(value) ? value : normalizePipeValues(value)
const result = []
for (let i = 0; i < source.length; i += 1) {
const current = normalizeText(source[i])
if (!current || result.indexOf(current) !== -1) {
continue
}
result.push(current)
}
return result.join('|')
}
function normalizeRequiredCategory(value) {
const values = normalizePipeValues(value)
if (!values.length) {
throw createAppError(400, 'prod_list_category 为必填项')
}
return values[0]
}
function buildCategoryRankMap(records) {
const grouped = {}
for (let i = 0; i < records.length; i += 1) {
const category = normalizeText(records[i].prod_list_category)
if (!category) {
continue
}
if (!grouped[category]) {
grouped[category] = []
}
grouped[category].push(records[i])
}
const rankMap = {}
const categories = Object.keys(grouped)
for (let i = 0; i < categories.length; i += 1) {
const category = categories[i]
const items = grouped[category]
items.sort(function (a, b) {
const sortDiff = Number(a.prod_list_sort || 0) - Number(b.prod_list_sort || 0)
if (sortDiff !== 0) {
return sortDiff
}
const updateDiff = String(b.updated || '').localeCompare(String(a.updated || ''))
if (updateDiff !== 0) {
return updateDiff
}
return String(a.prod_list_id || '').localeCompare(String(b.prod_list_id || ''))
})
for (let j = 0; j < items.length; j += 1) {
rankMap[items[j].prod_list_id] = j + 1
}
}
return rankMap
}
function normalizeParameters(value) {
if (value === null || typeof value === 'undefined' || value === '') {
return {}
}
if (typeof value !== 'object' || Array.isArray(value)) {
throw createAppError(400, 'prod_list_parameters 必须是对象')
}
const result = {}
const keys = Object.keys(value)
for (let i = 0; i < keys.length; i += 1) {
const key = normalizeText(keys[i])
if (!key) {
continue
}
const current = value[keys[i]]
if (current === null || typeof current === 'undefined') {
result[key] = ''
continue
}
result[key] = String(current)
}
return result
}
function normalizeParametersForOutput(value) {
if (value === null || typeof value === 'undefined' || value === '') {
return {}
}
let source = value
if (typeof source === 'string') {
try {
source = JSON.parse(source)
} catch (_error) {
const raw = normalizeText(source)
if (raw.indexOf('map[') === 0 && raw.endsWith(']')) {
const body = raw.slice(4, -1).trim()
if (!body) {
return {}
}
const result = {}
const pairs = body.split(/\s+/)
for (let i = 0; i < pairs.length; i += 1) {
const pair = pairs[i]
const separatorIndex = pair.indexOf(':')
if (separatorIndex <= 0) {
continue
}
const key = normalizeText(pair.slice(0, separatorIndex))
if (!key) {
continue
}
const val = pair.slice(separatorIndex + 1)
result[key] = val === null || typeof val === 'undefined' ? '' : String(val)
}
return result
}
return {}
}
}
if (Array.isArray(source)) {
const mapped = {}
for (let i = 0; i < source.length; i += 1) {
const item = source[i] && typeof source[i] === 'object' ? source[i] : null
if (!item) {
continue
}
const key = normalizeText(item.key)
if (!key) {
continue
}
mapped[key] = item.value === null || typeof item.value === 'undefined' ? '' : String(item.value)
}
return mapped
}
if (typeof source !== 'object') {
return {}
}
// Some PocketBase/Goja map-like values are not directly enumerable; roundtrip to plain object.
try {
source = JSON.parse(JSON.stringify(source))
} catch (_error) {}
const result = {}
const keys = Object.keys(source)
for (let i = 0; i < keys.length; i += 1) {
const key = normalizeText(keys[i])
if (!key) {
continue
}
const current = source[keys[i]]
result[key] = current === null || typeof current === 'undefined' ? '' : String(current)
}
return result
}
function ensureAttachmentExists(attachmentId, fieldName) {
const value = normalizeText(attachmentId)
if (!value) {
return
}
try {
documentService.getAttachmentDetail(value)
} catch (_err) {
throw createAppError(400, fieldName + ' 对应附件不存在:' + value)
}
}
function findProductRecordByBusinessId(productId) {
const records = $app.findRecordsByFilter('tbl_product_list', 'prod_list_id = {:productId}', '', 1, 0, {
productId: productId,
})
return records.length ? records[0] : null
}
function exportProductRecord(record, extra) {
const iconId = record.getString('prod_list_icon')
let iconAttachment = null
if (iconId) {
try {
iconAttachment = documentService.getAttachmentDetail(iconId)
} catch (_error) {
iconAttachment = null
}
}
const parametersText = normalizeText(record.getString('prod_list_parameters'))
const parametersFromText = parametersText ? normalizeParametersForOutput(parametersText) : {}
const parametersRaw = record.get('prod_list_parameters')
const parametersFromRaw = normalizeParametersForOutput(parametersRaw)
const parameters = Object.keys(parametersFromText).length ? parametersFromText : parametersFromRaw
return {
pb_id: record.id,
prod_list_id: record.getString('prod_list_id'),
prod_list_name: record.getString('prod_list_name'),
prod_list_modelnumber: record.getString('prod_list_modelnumber'),
prod_list_icon: iconId,
prod_list_icon_attachment: iconAttachment,
prod_list_icon_url: iconAttachment ? iconAttachment.attachments_url : '',
prod_list_description: record.getString('prod_list_description'),
prod_list_feature: record.getString('prod_list_feature'),
prod_list_parameters: parameters,
prod_list_plantype: record.getString('prod_list_plantype'),
prod_list_category: record.getString('prod_list_category'),
prod_list_sort: Number(record.get('prod_list_sort') || 0),
prod_list_category_rank: extra && extra.categoryRank ? Number(extra.categoryRank) : 0,
prod_list_comm_type: record.getString('prod_list_comm_type'),
prod_list_series: record.getString('prod_list_series'),
prod_list_power_supply: record.getString('prod_list_power_supply'),
prod_list_tags: record.getString('prod_list_tags'),
prod_list_basic_price: record.get('prod_list_basic_price'),
prod_list_status: record.getString('prod_list_status'),
prod_list_remark: record.getString('prod_list_remark'),
created: String(record.created || ''),
updated: String(record.updated || ''),
}
}
function listProducts(payload) {
const allRecords = $app.findRecordsByFilter('tbl_product_list', '', '', 500, 0)
const keyword = normalizeText(payload.keyword).toLowerCase()
const status = normalizeText(payload.status)
const category = normalizeText(payload.prod_list_category)
const result = []
const allItems = []
for (let i = 0; i < allRecords.length; i += 1) {
allItems.push(exportProductRecord(allRecords[i]))
}
const rankMap = buildCategoryRankMap(allItems)
for (let i = 0; i < allItems.length; i += 1) {
const source = allItems[i]
const item = Object.assign({}, source, {
prod_list_category_rank: rankMap[source.prod_list_id] || 0,
})
const matchedKeyword = !keyword
|| item.prod_list_id.toLowerCase().indexOf(keyword) !== -1
|| item.prod_list_name.toLowerCase().indexOf(keyword) !== -1
|| item.prod_list_modelnumber.toLowerCase().indexOf(keyword) !== -1
|| item.prod_list_tags.toLowerCase().indexOf(keyword) !== -1
const matchedStatus = !status || item.prod_list_status === status
const matchedCategory = !category || item.prod_list_category === category
if (matchedKeyword && matchedStatus && matchedCategory) {
result.push(item)
}
}
result.sort(function (a, b) {
return String(b.updated || '').localeCompare(String(a.updated || ''))
})
return result
}
function getProductDetail(productId) {
const record = findProductRecordByBusinessId(productId)
if (!record) {
throw createAppError(404, '未找到对应产品')
}
const allRecords = $app.findRecordsByFilter('tbl_product_list', '', '', 500, 0)
const allItems = []
for (let i = 0; i < allRecords.length; i += 1) {
allItems.push(exportProductRecord(allRecords[i]))
}
const rankMap = buildCategoryRankMap(allItems)
return exportProductRecord(record, {
categoryRank: rankMap[record.getString('prod_list_id')] || 0,
})
}
function createProduct(_userOpenid, payload) {
const targetProductId = normalizeText(payload.prod_list_id) || buildBusinessId('PROD')
const duplicated = findProductRecordByBusinessId(targetProductId)
if (duplicated) {
throw createAppError(400, 'prod_list_id 已存在')
}
ensureAttachmentExists(payload.prod_list_icon, 'prod_list_icon')
const collection = $app.findCollectionByNameOrId('tbl_product_list')
const record = new Record(collection)
record.set('prod_list_id', targetProductId)
record.set('prod_list_name', normalizeText(payload.prod_list_name))
record.set('prod_list_modelnumber', normalizeText(payload.prod_list_modelnumber))
record.set('prod_list_icon', normalizeText(payload.prod_list_icon))
record.set('prod_list_description', normalizeText(payload.prod_list_description))
record.set('prod_list_feature', normalizeText(payload.prod_list_feature))
record.set('prod_list_parameters', normalizeParameters(payload.prod_list_parameters))
record.set('prod_list_plantype', normalizeText(payload.prod_list_plantype))
record.set('prod_list_category', normalizeRequiredCategory(payload.prod_list_category))
record.set('prod_list_sort', normalizeSortValue(payload.prod_list_sort))
record.set('prod_list_comm_type', normalizeText(payload.prod_list_comm_type))
record.set('prod_list_series', normalizeText(payload.prod_list_series))
record.set('prod_list_power_supply', normalizeText(payload.prod_list_power_supply))
record.set('prod_list_tags', joinUniquePipeValues(payload.prod_list_tags))
record.set('prod_list_status', normalizeText(payload.prod_list_status))
record.set('prod_list_basic_price', normalizeOptionalNumberValue(payload.prod_list_basic_price, 'prod_list_basic_price'))
record.set('prod_list_remark', normalizeText(payload.prod_list_remark))
try {
$app.save(record)
} catch (err) {
throw createAppError(400, '新增产品失败', {
originalMessage: (err && err.message) || '未知错误',
originalData: (err && err.data) || {},
})
}
logger.info('产品创建成功', {
prod_list_id: record.getString('prod_list_id'),
})
const rankMap = buildCategoryRankMap(listProducts({}))
return exportProductRecord(record, {
categoryRank: rankMap[record.getString('prod_list_id')] || 0,
})
}
function updateProduct(_userOpenid, payload) {
const targetId = normalizeText(payload.prod_list_id)
if (!targetId) {
throw createAppError(400, 'prod_list_id 为必填项')
}
const record = findProductRecordByBusinessId(targetId)
if (!record) {
throw createAppError(404, '未找到待修改的产品')
}
ensureAttachmentExists(payload.prod_list_icon, 'prod_list_icon')
record.set('prod_list_name', normalizeText(payload.prod_list_name))
record.set('prod_list_modelnumber', normalizeText(payload.prod_list_modelnumber))
record.set('prod_list_icon', normalizeText(payload.prod_list_icon))
record.set('prod_list_description', normalizeText(payload.prod_list_description))
record.set('prod_list_feature', normalizeText(payload.prod_list_feature))
record.set('prod_list_parameters', normalizeParameters(payload.prod_list_parameters))
record.set('prod_list_plantype', normalizeText(payload.prod_list_plantype))
record.set('prod_list_category', normalizeRequiredCategory(payload.prod_list_category))
record.set('prod_list_sort', normalizeSortValue(payload.prod_list_sort))
record.set('prod_list_comm_type', normalizeText(payload.prod_list_comm_type))
record.set('prod_list_series', normalizeText(payload.prod_list_series))
record.set('prod_list_power_supply', normalizeText(payload.prod_list_power_supply))
record.set('prod_list_tags', joinUniquePipeValues(payload.prod_list_tags))
record.set('prod_list_status', normalizeText(payload.prod_list_status))
record.set('prod_list_basic_price', normalizeOptionalNumberValue(payload.prod_list_basic_price, 'prod_list_basic_price'))
record.set('prod_list_remark', normalizeText(payload.prod_list_remark))
try {
$app.save(record)
} catch (err) {
throw createAppError(400, '更新产品失败', {
originalMessage: (err && err.message) || '未知错误',
originalData: (err && err.data) || {},
})
}
logger.info('产品更新成功', {
prod_list_id: targetId,
})
const rankMap = buildCategoryRankMap(listProducts({}))
return exportProductRecord(record, {
categoryRank: rankMap[record.getString('prod_list_id')] || 0,
})
}
function deleteProduct(_userOpenid, productId) {
const targetId = normalizeText(productId)
if (!targetId) {
throw createAppError(400, 'prod_list_id 为必填项')
}
const record = findProductRecordByBusinessId(targetId)
if (!record) {
throw createAppError(404, '未找到待删除的产品')
}
try {
$app.delete(record)
} catch (err) {
throw createAppError(400, '删除产品失败', {
originalMessage: (err && err.message) || '未知错误',
originalData: (err && err.data) || {},
})
}
logger.info('产品删除成功', {
prod_list_id: targetId,
})
return {
prod_list_id: targetId,
}
}
module.exports = {
listProducts,
getProductDetail,
createProduct,
updateProduct,
deleteProduct,
}

View File

@@ -19,11 +19,14 @@ routerAdd('GET', '/manage', function (e) {
.wrap { max-width: 1440px; margin: 0 auto; padding: 24px 14px; }
.hero { background: #ffffff; border-radius: 20px; box-shadow: 0 18px 50px rgba(15, 23, 42, 0.08); padding: 22px; border: 1px solid #e5e7eb; }
h1 { margin: 0 0 14px; font-size: 30px; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px; }
.grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; }
.card { background: #f8fafc; border: 1px solid #dbe3f0; border-radius: 16px; padding: 16px; text-align: center; }
.card h2 { margin: 0 0 8px; font-size: 19px; }
.btn { display: inline-flex; align-items: center; justify-content: center; padding: 10px 16px; border-radius: 12px; text-decoration: none; background: #2563eb; color: #fff; font-weight: 600; margin-top: 12px; }
.actions { margin-top: 14px; display: flex; justify-content: flex-start; }
@media (max-width: 960px) {
.grid { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
@@ -39,6 +42,10 @@ routerAdd('GET', '/manage', function (e) {
<h2>文档管理</h2>
<a class="btn" href="/pb/manage/document-manage">进入文档管理</a>
</article>
<article class="card">
<h2>产品管理</h2>
<a class="btn" href="/pb/manage/product-manage">进入产品管理</a>
</article>
<article class="card">
<h2>SDK 权限管理</h2>
<a class="btn" href="/pb/manage/sdk-permission-manage">进入权限管理</a>

File diff suppressed because it is too large Load Diff

View File

@@ -22,6 +22,8 @@ tags:
description: 通过 PocketBase 原生 records API 访问 `tbl_company`
- name: 附件信息
description: 通过 PocketBase 原生 records API 访问 `tbl_attachments`
- name: 产品信息
description: 通过 PocketBase 原生 records API 访问 `tbl_product_list`
- name: 文档信息
description: 通过 PocketBase 原生 records API 访问 `tbl_document`
paths:
@@ -645,6 +647,119 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/PocketBaseNativeError'
/pb/api/collections/tbl_product_list/records:
get:
operationId: getPocketBaseProductListRecords
tags:
- 产品信息
summary: 根据产品分类精确筛选并按分类排序值升序返回产品列表
description: |
使用 PocketBase 原生 records list 接口查询 `tbl_product_list`。
当前接口约定:
- 条件:按 `prod_list_category` 精确匹配筛选
- 排序:按 `prod_list_sort` 从小到大排序
标准调用参数建议:
- `filter=prod_list_category="<产品分类>"`
- `sort=prod_list_sort`
注意:
- 这是 PocketBase 原生返回结构,不是 hooks 统一 `{ statusCode, errMsg, data }` 包装
- 若不传 `sort`,将由 PocketBase 默认排序策略决定返回顺序
parameters:
- name: filter
in: query
required: true
description: |
PocketBase 标准过滤表达式,当前要求按产品分类精确值筛选。
推荐写法:`prod_list_category="<产品分类>"`
schema:
type: string
example: prod_list_category="<产品分类>"
- name: page
in: query
required: false
description: 页码
schema:
type: integer
minimum: 1
default: 1
- name: perPage
in: query
required: false
description: 每页条数
schema:
type: integer
minimum: 1
default: 20
- name: sort
in: query
required: false
description: |
PocketBase 原生排序表达式。
当前要求使用:
- `prod_list_sort`:按分类排序值从小到大
schema:
type: string
example: prod_list_sort
responses:
'200':
description: 查询成功
content:
application/json:
schema:
$ref: '#/components/schemas/PocketBaseProductListListResponse'
examples:
byCategoryAscSort:
value:
page: <页码>|<integer>
perPage: <每页条数>|<integer>
totalItems: <总记录数>|<integer>
totalPages: <总页数>|<integer>
items:
- id: <PocketBase记录主键>|<string>
collectionId: <集合ID>|<string>
collectionName: <集合名称>|<string>
created: <记录创建时间>|<string>
updated: <记录更新时间>|<string>
prod_list_id: <产品列表业务ID>|<string>
prod_list_name: <产品名称>|<string>
prod_list_modelnumber: <产品型号>|<string>
prod_list_icon: <产品图标附件ID>|<string>
prod_list_description: <产品说明>|<string>
prod_list_feature: <产品特色>|<string>
prod_list_parameters: <产品参数JSON>|<object>
prod_list_plantype: <产品方案>|<string>
prod_list_category: <产品分类>|<string>
prod_list_sort: <排序值>|<number>
prod_list_comm_type: <通讯类型>|<string>
prod_list_series: <产品系列>|<string>
prod_list_power_supply: <供电方式>|<string>
prod_list_tags: <产品标签>|<string>
prod_list_status: <产品状态>|<string>
prod_list_basic_price: <基础价格>|<number>
prod_list_remark: <备注>|<string>
'400':
description: 查询参数错误
content:
application/json:
schema:
$ref: '#/components/schemas/PocketBaseNativeError'
'403':
description: 集合规则被锁定或服务端权限设置异常
content:
application/json:
schema:
$ref: '#/components/schemas/PocketBaseNativeError'
'500':
description: 服务端错误
content:
application/json:
schema:
$ref: '#/components/schemas/PocketBaseNativeError'
/pb/api/collections/tbl_document/records:
get:
operationId: getPocketBaseDocumentRecords
@@ -1485,6 +1600,173 @@ components:
attachments_ocr: OCR识别结果 | string
attachments_status: 附件状态 | string
attachments_remark: 备注 | string
PocketBaseProductListFields:
type: object
properties:
prod_list_id:
type: string
description: 产品列表业务 ID唯一标识
example: <产品列表业务ID>|<string>
prod_list_name:
type: string
description: 产品名称
example: <产品名称>|<string>
prod_list_modelnumber:
type: string
description: 产品型号
example: <产品型号>|<string>
prod_list_icon:
type: string
description: 产品图标附件 ID保存 `tbl_attachments.attachments_id`
example: <产品图标附件ID>|<string>
prod_list_description:
type: string
description: 产品说明
example: <产品说明>|<string>
prod_list_feature:
type: string
description: 产品特色
example: <产品特色>|<string>
prod_list_parameters:
type: object
additionalProperties: true
description: 产品参数JSON 对象/数组)
example:
key: <参数值>|<string>
prod_list_plantype:
type: string
description: 产品方案
example: <产品方案>|<string>
prod_list_category:
type: string
description: 产品分类(必填,单选)
example: <产品分类>|<string>
prod_list_sort:
type:
- number
- integer
description: 排序值(同分类内按升序)
example: <排序值>|<number>
prod_list_comm_type:
type: string
description: 通讯类型
example: <通讯类型>|<string>
prod_list_series:
type: string
description: 产品系列
example: <产品系列>|<string>
prod_list_power_supply:
type: string
description: 供电方式
example: <供电方式>|<string>
prod_list_tags:
type: string
description: 产品标签(辅助检索,以 `|` 聚合)
example: <产品标签>|<string>
prod_list_status:
type: string
description: 产品状态(有效 / 过期 / 主推等)
example: <产品状态>|<string>
prod_list_basic_price:
type:
- number
- integer
description: 基础价格
example: <基础价格>|<number>
prod_list_remark:
type: string
description: 备注
example: <备注>|<string>
PocketBaseProductListRecord:
allOf:
- $ref: '#/components/schemas/PocketBaseRecordBase'
- $ref: '#/components/schemas/PocketBaseProductListFields'
example:
id: <PocketBase记录主键>|<string>
collectionId: <集合ID>|<string>
collectionName: <集合名称>|<string>
created: <记录创建时间>|<string>
updated: <记录更新时间>|<string>
prod_list_id: <产品列表业务ID>|<string>
prod_list_name: <产品名称>|<string>
prod_list_modelnumber: <产品型号>|<string>
prod_list_icon: <产品图标附件ID>|<string>
prod_list_description: <产品说明>|<string>
prod_list_feature: <产品特色>|<string>
prod_list_parameters:
key: <参数值>|<string>
prod_list_plantype: <产品方案>|<string>
prod_list_category: <产品分类>|<string>
prod_list_sort: <排序值>|<number>
prod_list_comm_type: <通讯类型>|<string>
prod_list_series: <产品系列>|<string>
prod_list_power_supply: <供电方式>|<string>
prod_list_tags: <产品标签>|<string>
prod_list_status: <产品状态>|<string>
prod_list_basic_price: <基础价格>|<number>
prod_list_remark: <备注>|<string>
PocketBaseProductListListResponse:
type: object
required:
- page
- perPage
- totalItems
- totalPages
- items
properties:
page:
type:
- integer
- string
example: <页码>|<integer>
perPage:
type:
- integer
- string
example: <每页条数>|<integer>
totalItems:
type:
- integer
- string
example: <总记录数>|<integer>
totalPages:
type:
- integer
- string
example: <总页数>|<integer>
items:
type: array
items:
$ref: '#/components/schemas/PocketBaseProductListRecord'
example:
page: <页码>|<integer>
perPage: <每页条数>|<integer>
totalItems: <总记录数>|<integer>
totalPages: <总页数>|<integer>
items:
- id: <PocketBase记录主键>|<string>
collectionId: <集合ID>|<string>
collectionName: <集合名称>|<string>
created: <记录创建时间>|<string>
updated: <记录更新时间>|<string>
prod_list_id: <产品列表业务ID>|<string>
prod_list_name: <产品名称>|<string>
prod_list_modelnumber: <产品型号>|<string>
prod_list_icon: <产品图标附件ID>|<string>
prod_list_description: <产品说明>|<string>
prod_list_feature: <产品特色>|<string>
prod_list_parameters:
key: <参数值>|<string>
prod_list_plantype: <产品方案>|<string>
prod_list_category: <产品分类>|<string>
prod_list_sort: <排序值>|<number>
prod_list_comm_type: <通讯类型>|<string>
prod_list_series: <产品系列>|<string>
prod_list_power_supply: <供电方式>|<string>
prod_list_tags: <产品标签>|<string>
prod_list_status: <产品状态>|<string>
prod_list_basic_price: <基础价格>|<number>
prod_list_remark: <备注>|<string>
PocketBaseDocumentFields:
type: object
properties: