feat: 添加产品列表集合初始化脚本,包含字段定义、索引创建及校验逻辑
This commit is contained in:
@@ -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`)
|
||||
|
||||
@@ -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`)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
26
pocket-base/bai_api_pb_hooks/bai_api_routes/product/list.js
Normal file
26
pocket-base/bai_api_pb_hooks/bai_api_routes/product/list.js
Normal 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)
|
||||
}
|
||||
})
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
1403
pocket-base/bai_web_pb_hooks/pages/product-manage.js
Normal file
1403
pocket-base/bai_web_pb_hooks/pages/product-manage.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user