feat: 添加系统刷新令牌请求和用户统计响应的 OpenAPI 规范

feat: 添加微信认证相关的 OpenAPI 规范,包括用户信息、登录请求和个人资料请求

feat: 添加 is_delete 字段迁移脚本,支持在集合中添加软删除字段

feat: 添加软删除规则应用脚本,确保所有相关集合的查询规则包含软删除条件

feat: 添加购物车和订单业务 ID 自动生成的迁移脚本,确保字段类型和自动生成规则正确
This commit is contained in:
2026-04-07 20:02:10 +08:00
parent cafd69ea2c
commit cd0373be3c
57 changed files with 6037 additions and 3343 deletions

View File

@@ -521,6 +521,7 @@ function validateProductMutationBody(e, isUpdate) {
}
return {
id: payload.id || '',
prod_list_id: payload.prod_list_id || '',
prod_list_name: payload.prod_list_name || '',
prod_list_modelnumber: payload.prod_list_modelnumber || '',

View File

@@ -304,47 +304,52 @@ function exportOrderRecord(record) {
}
function exportAdminCartRecord(record) {
const productInfo = buildProductInfo(findProductRecordByBusinessId(record.cart_product_id))
const productId = record && typeof record.getString === 'function'
? record.getString('cart_product_id')
: String(record && record.cart_product_id || '')
const productInfo = buildProductInfo(findProductRecordByBusinessId(productId))
return {
pb_id: String(record.id || ''),
cart_id: String(record.cart_id || ''),
cart_number: String(record.cart_number || ''),
cart_create: String(record.cart_create || ''),
cart_owner: String(record.cart_owner || ''),
cart_product_id: String(record.cart_product_id || ''),
cart_product_quantity: Number(record.cart_product_quantity || 0),
cart_status: String(record.cart_status || ''),
cart_at_price: Number(record.cart_at_price || 0),
cart_remark: String(record.cart_remark || ''),
pb_id: String(record && record.id || ''),
cart_id: record && typeof record.getString === 'function' ? record.getString('cart_id') : String(record && record.cart_id || ''),
cart_number: record && typeof record.getString === 'function' ? record.getString('cart_number') : String(record && record.cart_number || ''),
cart_create: record && typeof record.get === 'function' ? String(record.get('cart_create') || '') : String(record && record.cart_create || ''),
cart_owner: record && typeof record.getString === 'function' ? record.getString('cart_owner') : String(record && record.cart_owner || ''),
cart_product_id: productId,
cart_product_quantity: record && typeof record.get === 'function' ? Number(record.get('cart_product_quantity') || 0) : Number(record && record.cart_product_quantity || 0),
cart_status: record && typeof record.getString === 'function' ? record.getString('cart_status') : String(record && record.cart_status || ''),
cart_at_price: record && typeof record.get === 'function' ? Number(record.get('cart_at_price') || 0) : Number(record && record.cart_at_price || 0),
cart_remark: record && typeof record.getString === 'function' ? record.getString('cart_remark') : String(record && record.cart_remark || ''),
product_name: productInfo.prod_list_name,
product_modelnumber: productInfo.prod_list_modelnumber,
product_basic_price: productInfo.prod_list_basic_price,
created: String(record.created || ''),
updated: String(record.updated || ''),
created: String(record && record.created || ''),
updated: String(record && record.updated || ''),
}
}
function exportAdminOrderRecord(record) {
return {
pb_id: String(record.id || ''),
order_id: String(record.order_id || ''),
order_number: String(record.order_number || ''),
order_create: String(record.order_create || ''),
order_owner: String(record.order_owner || ''),
order_source: String(record.order_source || ''),
order_status: String(record.order_status || ''),
order_source_id: String(record.order_source_id || ''),
order_snap: parseJsonFieldForOutput(record.order_snap),
order_amount: Number(record.order_amount || 0),
order_remark: String(record.order_remark || ''),
created: String(record.created || ''),
updated: String(record.updated || ''),
pb_id: String(record && record.id || ''),
order_id: record && typeof record.getString === 'function' ? record.getString('order_id') : String(record && record.order_id || ''),
order_number: record && typeof record.getString === 'function' ? record.getString('order_number') : String(record && record.order_number || ''),
order_create: record && typeof record.get === 'function' ? String(record.get('order_create') || '') : String(record && record.order_create || ''),
order_owner: record && typeof record.getString === 'function' ? record.getString('order_owner') : String(record && record.order_owner || ''),
order_source: record && typeof record.getString === 'function' ? record.getString('order_source') : String(record && record.order_source || ''),
order_status: record && typeof record.getString === 'function' ? record.getString('order_status') : String(record && record.order_status || ''),
order_source_id: record && typeof record.getString === 'function' ? record.getString('order_source_id') : String(record && record.order_source_id || ''),
order_snap: record && typeof record.get === 'function' ? parseJsonFieldForOutput(record.get('order_snap')) : parseJsonFieldForOutput(record && record.order_snap),
order_amount: record && typeof record.get === 'function' ? Number(record.get('order_amount') || 0) : Number(record && record.order_amount || 0),
order_remark: record && typeof record.getString === 'function' ? record.getString('order_remark') : String(record && record.order_remark || ''),
created: String(record && record.created || ''),
updated: String(record && record.updated || ''),
}
}
function exportAdminManageUser(userRecord, groupedCarts, groupedOrders) {
const openid = String(userRecord.openid || '')
const openid = userRecord && typeof userRecord.getString === 'function'
? userRecord.getString('openid')
: String(userRecord && userRecord.openid || '')
const carts = groupedCarts[openid] || []
const orders = groupedOrders[openid] || []
let cartTotalQuantity = 0
@@ -358,40 +363,44 @@ function exportAdminManageUser(userRecord, groupedCarts, groupedOrders) {
}
return {
pb_id: String(userRecord.id || ''),
pb_id: String(userRecord && userRecord.id || ''),
openid: openid,
users_id: String(userRecord.users_id || ''),
users_name: String(userRecord.users_name || ''),
users_phone: String(userRecord.users_phone || ''),
users_level: String(userRecord.users_level || ''),
users_level_name: userService.resolveUserLevelName(String(userRecord.users_level || '')),
users_type: String(userRecord.users_type || ''),
users_idtype: String(userRecord.users_idtype || ''),
users_id_number: String(userRecord.users_id_number || ''),
users_status: String(userRecord.users_status || ''),
users_rank_level: userRecord.users_rank_level === null || typeof userRecord.users_rank_level === 'undefined'
users_id: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_id') : String(userRecord && userRecord.users_id || ''),
users_name: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_name') : String(userRecord && userRecord.users_name || ''),
users_phone: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_phone') : String(userRecord && userRecord.users_phone || ''),
users_level: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_level') : String(userRecord && userRecord.users_level || ''),
users_level_name: userService.resolveUserLevelName(userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_level') : String(userRecord && userRecord.users_level || '')),
users_type: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_type') : String(userRecord && userRecord.users_type || ''),
users_idtype: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_idtype') : String(userRecord && userRecord.users_idtype || ''),
users_id_number: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_id_number') : String(userRecord && userRecord.users_id_number || ''),
users_status: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_status') : String(userRecord && userRecord.users_status || ''),
users_rank_level: !userRecord || typeof userRecord.get !== 'function'
? (userRecord && (userRecord.users_rank_level === null || typeof userRecord.users_rank_level === 'undefined') ? null : Number(userRecord && userRecord.users_rank_level))
: (userRecord.get('users_rank_level') === null || typeof userRecord.get('users_rank_level') === 'undefined')
? null
: Number(userRecord.users_rank_level),
users_auth_type: userRecord.users_auth_type === null || typeof userRecord.users_auth_type === 'undefined'
: Number(userRecord.get('users_rank_level')),
users_auth_type: !userRecord || typeof userRecord.get !== 'function'
? (userRecord && (userRecord.users_auth_type === null || typeof userRecord.users_auth_type === 'undefined') ? null : Number(userRecord && userRecord.users_auth_type))
: (userRecord.get('users_auth_type') === null || typeof userRecord.get('users_auth_type') === 'undefined')
? null
: Number(userRecord.users_auth_type),
users_tag: String(userRecord.users_tag || ''),
company_id: String(userRecord.company_id || ''),
users_parent_id: String(userRecord.users_parent_id || ''),
users_promo_code: String(userRecord.users_promo_code || ''),
usergroups_id: String(userRecord.usergroups_id || ''),
users_picture: String(userRecord.users_picture || ''),
users_id_pic_a: String(userRecord.users_id_pic_a || ''),
users_id_pic_b: String(userRecord.users_id_pic_b || ''),
users_title_picture: String(userRecord.users_title_picture || ''),
: Number(userRecord.get('users_auth_type')),
users_tag: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_tag') : String(userRecord && userRecord.users_tag || ''),
company_id: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('company_id') : String(userRecord && userRecord.company_id || ''),
users_parent_id: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_parent_id') : String(userRecord && userRecord.users_parent_id || ''),
users_promo_code: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_promo_code') : String(userRecord && userRecord.users_promo_code || ''),
usergroups_id: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('usergroups_id') : String(userRecord && userRecord.usergroups_id || ''),
users_picture: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_picture') : String(userRecord && userRecord.users_picture || ''),
users_id_pic_a: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_id_pic_a') : String(userRecord && userRecord.users_id_pic_a || ''),
users_id_pic_b: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_id_pic_b') : String(userRecord && userRecord.users_id_pic_b || ''),
users_title_picture: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_title_picture') : String(userRecord && userRecord.users_title_picture || ''),
cart_count: carts.length,
cart_total_quantity: cartTotalQuantity,
order_count: orders.length,
order_total_amount: orderTotalAmount,
carts: carts,
orders: orders,
created: String(userRecord.created || ''),
updated: String(userRecord.updated || ''),
created: String(userRecord && userRecord.created || ''),
updated: String(userRecord && userRecord.updated || ''),
}
}
@@ -728,7 +737,9 @@ function listManageUsersCartOrders(payload) {
const groupedOrders = {}
for (let i = 0; i < cartRecords.length; i += 1) {
const owner = String(cartRecords[i].cart_owner || '')
const owner = cartRecords[i] && typeof cartRecords[i].getString === 'function'
? cartRecords[i].getString('cart_owner')
: String(cartRecords[i] && cartRecords[i].cart_owner || '')
if (!groupedCarts[owner]) {
groupedCarts[owner] = []
}
@@ -736,7 +747,9 @@ function listManageUsersCartOrders(payload) {
}
for (let i = 0; i < orderRecords.length; i += 1) {
const owner = String(orderRecords[i].order_owner || '')
const owner = orderRecords[i] && typeof orderRecords[i].getString === 'function'
? orderRecords[i].getString('order_owner')
: String(orderRecords[i] && orderRecords[i].order_owner || '')
if (!groupedOrders[owner]) {
groupedOrders[owner] = []
}

View File

@@ -171,6 +171,39 @@ function findAttachmentRecordByAttachmentId(attachmentId) {
return records.length ? records[0] : null
}
function fetchAllRecordsByFilter(collectionName, filter, sort, params) {
const batchSize = 200
const result = []
let offset = 0
while (true) {
const batch = $app.findRecordsByFilter(
collectionName,
filter || '',
sort || '',
batchSize,
offset,
params || {}
)
if (!batch.length) {
break
}
for (let i = 0; i < batch.length; i += 1) {
result.push(batch[i])
}
if (batch.length < batchSize) {
break
}
offset += batch.length
}
return result
}
function resolveAttachmentList(value) {
const ids = parseAttachmentIdList(value)
const attachments = []
@@ -402,7 +435,7 @@ function deleteAttachment(attachmentId) {
throw createAppError(404, '未找到待删除的附件')
}
const documentRecords = $app.findRecordsByFilter('tbl_document', '', '', 500, 0)
const documentRecords = fetchAllRecordsByFilter('tbl_document', '', '')
for (let i = 0; i < documentRecords.length; i += 1) {
const current = documentRecords[i]
const imageIds = parseAttachmentIdList(current.getString('document_image'))
@@ -434,7 +467,7 @@ function deleteAttachment(attachmentId) {
}
function listDocuments(payload) {
const allRecords = $app.findRecordsByFilter('tbl_document', '', '', 500, 0)
const allRecords = fetchAllRecordsByFilter('tbl_document', '', '')
const titleKeyword = String(payload.title_keyword || '').toLowerCase().trim()
const status = String(payload.status || '')
const type = String(payload.document_type || '')

View File

@@ -9,10 +9,57 @@ function buildBusinessId(prefix) {
return prefix + '-' + new Date().getTime() + '-' + $security.randomString(6)
}
function buildPocketBaseRecordId(raw) {
const normalized = normalizeText(raw).toLowerCase()
if (/^[a-z0-9]{15}$/.test(normalized)) {
return normalized
}
const fallback = String($security.randomString(15) || '').toLowerCase().replace(/[^a-z0-9]/g, 'a')
if (fallback.length >= 15) {
return fallback.slice(0, 15)
}
return (fallback + 'aaaaaaaaaaaaaaa').slice(0, 15)
}
function normalizeText(value) {
return String(value || '').replace(/^\s+|\s+$/g, '')
}
function fetchAllRecordsByFilter(collectionName, filter, sort, params) {
const batchSize = 200
const result = []
let offset = 0
while (true) {
const batch = $app.findRecordsByFilter(
collectionName,
filter || '',
sort || '',
batchSize,
offset,
params || {}
)
if (!batch.length) {
break
}
for (let i = 0; i < batch.length; i += 1) {
result.push(batch[i])
}
if (batch.length < batchSize) {
break
}
offset += batch.length
}
return result
}
function normalizeOptionalNumberValue(value, fieldName) {
if (value === '' || value === null || typeof value === 'undefined') {
return null
@@ -562,7 +609,7 @@ function exportProductRecord(record, extra) {
function listProducts(payload) {
try {
const allRecords = $app.findRecordsByFilter('tbl_product_list', '', '', 500, 0)
const allRecords = fetchAllRecordsByFilter('tbl_product_list', '', '')
const keyword = normalizeText(payload.keyword).toLowerCase()
const status = normalizeText(payload.status)
const category = normalizeText(payload.prod_list_category)
@@ -620,7 +667,7 @@ function getProductDetail(productId) {
throw createAppError(404, '未找到对应产品')
}
const allRecords = $app.findRecordsByFilter('tbl_product_list', '', '', 500, 0)
const allRecords = fetchAllRecordsByFilter('tbl_product_list', '', '')
const allItems = []
for (let i = 0; i < allRecords.length; i += 1) {
allItems.push(exportProductRecord(allRecords[i]))
@@ -644,6 +691,7 @@ function createProduct(_userOpenid, payload) {
const collection = $app.findCollectionByNameOrId('tbl_product_list')
const record = new Record(collection)
record.set('id', buildPocketBaseRecordId(payload && payload.id))
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))

View File

@@ -48,6 +48,14 @@
.profile-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin-bottom: 14px; }
.field-block { display: grid; gap: 6px; }
.field-label { font-size: 13px; color: #64748b; }
.attachment-panel { border: 1px solid #dbe3f0; border-radius: 14px; padding: 12px; background: #f8fbff; }
.attachment-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.attachment-preview { margin-top: 10px; display: flex; align-items: center; gap: 12px; min-height: 72px; }
.attachment-thumb { width: 72px; height: 72px; border-radius: 12px; border: 1px solid #dbe3f0; background: #fff; object-fit: cover; }
.attachment-empty-thumb { width: 72px; height: 72px; border-radius: 12px; border: 1px solid #dbe3f0; background: #fff; color: #94a3b8; display: flex; align-items: center; justify-content: center; font-size: 12px; text-align: center; padding: 8px; }
.attachment-meta { min-width: 0; display: grid; gap: 4px; }
.attachment-link { color: #2563eb; text-decoration: none; font-size: 13px; font-weight: 600; word-break: break-all; }
.attachment-hidden-input { display: none; }
.detail-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 14px; }
.section + .section { margin-top: 14px; }
.section-title { margin: 0 0 10px; font-size: 18px; color: #0f172a; }
@@ -113,6 +121,12 @@
users: [],
selectedOpenid: '',
userLevelOptions: [],
attachmentDetails: {
userUsersPicture: null,
userUsersIdPicA: null,
userUsersIdPicB: null,
userUsersTitlePicture: null,
},
}
const statusEl = document.getElementById('status')
@@ -172,6 +186,144 @@
}) || null
}
async function parseJsonSafe(res) {
const contentType = String(res.headers.get('content-type') || '').toLowerCase()
const rawText = await res.text()
const isJson = contentType.indexOf('application/json') !== -1
if (!rawText) {
return { json: null, text: '', isJson: false }
}
if (isJson) {
try {
return { json: JSON.parse(rawText), text: rawText, isJson: true }
} catch (_error) {}
}
return { json: null, text: rawText, isJson: false }
}
async function uploadAttachment(file, fieldKey) {
const token = getToken()
if (!token) {
window.location.replace('/pb/manage/login')
throw new Error('登录状态已失效,请重新登录')
}
const form = new FormData()
form.append('attachments_link', file)
form.append('attachments_filename', file.name || '')
form.append('attachments_filetype', file.type || '')
form.append('attachments_size', String(file.size || 0))
form.append('attachments_status', 'active')
form.append('attachments_remark', 'cart-order-manage:' + fieldKey)
const res = await fetch(API_BASE + '/attachment/upload', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + token,
},
body: form,
})
const parsed = await parseJsonSafe(res)
const data = parsed.json || {}
if (!res.ok) {
if (res.status === 413) {
throw new Error('上传图片失败:文件过大或服务端 bodyLimit 未生效')
}
if (!parsed.isJson && parsed.text) {
throw new Error('上传图片失败:服务端返回了非 JSON 响应')
}
throw new Error((data && (data.errMsg || data.message)) || '上传图片失败')
}
return data
}
async function loadAttachmentDetail(attachmentId) {
const id = normalizeText(attachmentId)
if (!id) {
return null
}
try {
return await requestJson('/attachment/detail', { attachments_id: id })
} catch (_error) {
return {
attachments_id: id,
attachments_filename: id,
attachments_url: '',
}
}
}
async function refreshSelectedAttachmentDetails() {
const user = getSelectedUser()
if (!user) {
state.attachmentDetails = {
userUsersPicture: null,
userUsersIdPicA: null,
userUsersIdPicB: null,
userUsersTitlePicture: null,
}
renderDetail()
return
}
const currentOpenid = normalizeText(user.openid)
const results = await Promise.all([
loadAttachmentDetail(user.users_picture),
loadAttachmentDetail(user.users_id_pic_a),
loadAttachmentDetail(user.users_id_pic_b),
loadAttachmentDetail(user.users_title_picture),
])
if (normalizeText(state.selectedOpenid) !== currentOpenid) {
return
}
state.attachmentDetails = {
userUsersPicture: results[0],
userUsersIdPicA: results[1],
userUsersIdPicB: results[2],
userUsersTitlePicture: results[3],
}
renderDetail()
}
function renderAttachmentUploader(fieldId, label, value) {
const detail = state.attachmentDetails[fieldId] || null
const url = detail && detail.attachments_url ? detail.attachments_url : ''
const filename = detail && (detail.attachments_filename || detail.attachments_id)
? (detail.attachments_filename || detail.attachments_id)
: normalizeText(value)
return '<div class="field-block full">'
+ '<label class="field-label" for="' + fieldId + '">' + escapeHtml(label) + '</label>'
+ '<div class="attachment-panel">'
+ '<input id="' + fieldId + '" value="' + escapeHtml(value || '') + '" />'
+ '<input class="attachment-hidden-input" id="' + fieldId + 'File" type="file" accept="image/*" data-upload-field="' + escapeHtml(fieldId) + '" />'
+ '<div class="attachment-actions">'
+ '<button class="btn btn-light" type="button" data-upload-trigger="' + escapeHtml(fieldId) + '">上传图片</button>'
+ '<button class="btn btn-light" type="button" data-clear-attachment="' + escapeHtml(fieldId) + '">清空ID</button>'
+ '</div>'
+ '<div class="attachment-preview">'
+ (url
? '<img class="attachment-thumb" src="' + escapeHtml(url) + '" alt="' + escapeHtml(label) + '" />'
: '<div class="attachment-empty-thumb">暂无预览</div>')
+ '<div class="attachment-meta">'
+ '<div class="muted">当前附件ID' + escapeHtml(value || '-') + '</div>'
+ (url
? '<a class="attachment-link" href="' + escapeHtml(url) + '" target="_blank" rel="noreferrer">查看图片:' + escapeHtml(filename || '附件') + '</a>'
: '<div class="muted">上传后将先保存到 tbl_attachments再自动回填附件ID</div>')
+ '</div>'
+ '</div>'
+ '</div>'
+ '</div>'
}
function renderUserList() {
if (!state.users.length) {
userListEl.innerHTML = '<div class="empty">暂无匹配用户。</div>'
@@ -248,11 +400,11 @@
+ '<div class="field-block"><label class="field-label" for="userUsersPromoCode">推广码</label><input id="userUsersPromoCode" value="' + escapeHtml(user.users_promo_code || '') + '" /></div>'
+ '<div class="field-block"><label class="field-label" for="userUsergroupsId">用户组ID</label><input id="userUsergroupsId" value="' + escapeHtml(user.usergroups_id || '') + '" /></div>'
+ '<div class="field-block"><label class="field-label" for="userUsersIdNumber">证件号</label><input id="userUsersIdNumber" value="' + escapeHtml(user.users_id_number || '') + '" /></div>'
+ '<div class="field-block"><label class="field-label" for="userUsersPicture">头像附件ID</label><input id="userUsersPicture" value="' + escapeHtml(user.users_picture || '') + '" /></div>'
+ '<div class="field-block"><label class="field-label" for="userUsersIdPicA">证件正面附件ID</label><input id="userUsersIdPicA" value="' + escapeHtml(user.users_id_pic_a || '') + '" /></div>'
+ '<div class="field-block"><label class="field-label" for="userUsersIdPicB">证件反面附件ID</label><input id="userUsersIdPicB" value="' + escapeHtml(user.users_id_pic_b || '') + '" /></div>'
+ '<div class="field-block"><label class="field-label" for="userUsersTitlePicture">资质附件ID</label><input id="userUsersTitlePicture" value="' + escapeHtml(user.users_title_picture || '') + '" /></div>'
+ '</div>'
+ renderAttachmentUploader('userUsersPicture', '头像附件ID', user.users_picture || '')
+ renderAttachmentUploader('userUsersIdPicA', '证件正面附件ID', user.users_id_pic_a || '')
+ renderAttachmentUploader('userUsersIdPicB', '证件反面附件ID', user.users_id_pic_b || '')
+ renderAttachmentUploader('userUsersTitlePicture', '资质附件ID', user.users_title_picture || '')
+ '<div class="detail-actions"><button class="btn btn-primary" id="saveUserBtn" type="button">保存用户信息</button></div>'
+ '</div>'
}
@@ -321,6 +473,7 @@
}
renderUserList()
renderDetail()
refreshSelectedAttachmentDetails()
setStatus('加载完成,共 ' + state.users.length + ' 位用户。', 'success')
} catch (err) {
renderUserList()
@@ -375,6 +528,35 @@
}
}
async function handleAttachmentUpload(fieldId, file) {
if (!file) {
return
}
const input = document.getElementById(fieldId)
if (!input) {
return
}
const labelMap = {
userUsersPicture: '头像',
userUsersIdPicA: '证件正面',
userUsersIdPicB: '证件反面',
userUsersTitlePicture: '资质附件',
}
try {
setStatus('正在上传' + (labelMap[fieldId] || '附件') + '...', '')
const uploaded = await uploadAttachment(file, fieldId)
input.value = uploaded.attachments_id || ''
state.attachmentDetails[fieldId] = uploaded
renderDetail()
setStatus((labelMap[fieldId] || '附件') + '上传成功,请点击“保存用户信息”完成写入。', 'success')
} catch (err) {
setStatus(err.message || '上传附件失败', 'error')
}
}
userListEl.addEventListener('click', function (event) {
const target = event.target && event.target.closest ? event.target.closest('[data-openid]') : null
if (!target) {
@@ -383,13 +565,55 @@
state.selectedOpenid = normalizeText(target.getAttribute('data-openid'))
renderUserList()
renderDetail()
refreshSelectedAttachmentDetails()
})
detailWrapEl.addEventListener('click', function (event) {
const target = event.target
if (target && target.id === 'saveUserBtn') {
saveSelectedUser()
return
}
if (target && target.getAttribute) {
const uploadTrigger = target.getAttribute('data-upload-trigger')
if (uploadTrigger) {
const fileInput = document.getElementById(uploadTrigger + 'File')
if (fileInput) {
fileInput.click()
}
return
}
const clearAttachment = target.getAttribute('data-clear-attachment')
if (clearAttachment) {
const input = document.getElementById(clearAttachment)
const fileInput = document.getElementById(clearAttachment + 'File')
if (input) {
input.value = ''
}
if (fileInput) {
fileInput.value = ''
}
state.attachmentDetails[clearAttachment] = null
renderDetail()
}
}
})
detailWrapEl.addEventListener('change', function (event) {
const target = event.target
if (!target || !target.getAttribute) {
return
}
const fieldId = target.getAttribute('data-upload-field')
if (!fieldId) {
return
}
const file = target.files && target.files[0] ? target.files[0] : null
handleAttachmentUpload(fieldId, file)
})
document.getElementById('searchBtn').addEventListener('click', loadUsers)

View File

@@ -28,7 +28,7 @@
.btn-danger { background: #dc2626; color: #fff; }
.btn-light { background: #f8fafc; color: #334155; border: 1px solid #dbe3f0; }
.panel { border-radius: 20px; padding: 18px; }
.toolbar { display: grid; grid-template-columns: 1.3fr 1fr 1fr auto auto; gap: 12px; margin-bottom: 18px; }
.toolbar { display: grid; grid-template-columns: 1.3fr auto 1fr 1fr auto auto; gap: 12px; margin-bottom: 18px; }
input, textarea, select { width: 100%; border: 1px solid #cbd5e1; border-radius: 12px; padding: 10px 12px; font-size: 14px; background: #fff; }
textarea { min-height: 88px; resize: vertical; }
table { width: 100%; border-collapse: collapse; overflow: hidden; border-radius: 16px; }
@@ -113,6 +113,7 @@
<section class="panel">
<div class="toolbar">
<input id="keywordInput" placeholder="按字典名称模糊搜索" />
<select id="categorySelect"><option value="">全部分类</option></select>
<input id="detailInput" placeholder="查询指定字典名称" />
<button class="btn btn-secondary" id="listBtn" type="button">查询全部</button>
<button class="btn btn-light" id="detailBtn" type="button">查询指定</button>
@@ -217,6 +218,7 @@
const statusEl = document.getElementById('status')
const keywordInput = document.getElementById('keywordInput')
const categorySelect = document.getElementById('categorySelect')
const detailInput = document.getElementById('detailInput')
const tableBody = document.getElementById('tableBody')
const editorModal = document.getElementById('editorModal')
@@ -405,6 +407,44 @@
.replace(/'/g, '&#39;')
}
function extractCategory(dictName) {
var name = String(dictName || '')
if (name.length >= 2) {
return name.substring(0, 2)
}
return ''
}
function populateCategorySelect(list) {
var categories = {}
for (var i = 0; i < list.length; i++) {
var cat = extractCategory(list[i].dict_name)
if (cat) {
categories[cat] = true
}
}
var sorted = Object.keys(categories).sort()
var current = categorySelect.value
categorySelect.innerHTML = '<option value="">全部分类</option>'
for (var j = 0; j < sorted.length; j++) {
var opt = document.createElement('option')
opt.value = sorted[j]
opt.textContent = sorted[j]
categorySelect.appendChild(opt)
}
if (current && categories[current]) {
categorySelect.value = current
}
}
function filterByCategory(list) {
var selected = categorySelect.value
if (!selected) return list
return list.filter(function (item) {
return extractCategory(item.dict_name) === selected
})
}
function randomEnumSeed() {
const first = enumChars[Math.floor(Math.random() * enumChars.length)]
const second = enumChars[Math.floor(Math.random() * enumChars.length)]
@@ -775,7 +815,8 @@
const data = await request(API_BASE + '/dictionary/list', { keyword: keywordInput.value.trim() })
state.list = data.items || []
state.expandedPreviewKey = ''
renderTable(state.list)
populateCategorySelect(state.list)
renderTable(filterByCategory(state.list))
setStatus('查询成功,共 ' + state.list.length + ' 条。', 'success')
} catch (err) {
setStatus(err.message || '查询失败', 'error')
@@ -797,7 +838,8 @@
const data = await request(API_BASE + '/dictionary/detail', { dict_name: dictName })
state.list = [data]
state.expandedPreviewKey = ''
renderTable(state.list)
populateCategorySelect(state.list)
renderTable(filterByCategory(state.list))
setStatus('查询详情成功。', 'success')
} catch (err) {
setStatus(err.message || '查询失败', 'error')
@@ -1108,9 +1150,13 @@
})
document.getElementById('listBtn').addEventListener('click', loadList)
categorySelect.addEventListener('change', function () {
renderTable(filterByCategory(state.list))
})
document.getElementById('detailBtn').addEventListener('click', loadDetail)
document.getElementById('resetBtn').addEventListener('click', function () {
keywordInput.value = ''
categorySelect.value = ''
detailInput.value = ''
state.list = []
renderTable([])

View File

@@ -84,6 +84,16 @@
.loading-card { min-width: min(92vw, 360px); padding: 24px 22px; border-radius: 20px; background: rgba(255,255,255,0.98); box-shadow: 0 28px 70px rgba(15, 23, 42, 0.22); border: 1px solid #dbe3f0; text-align: center; }
.loading-spinner { width: 44px; height: 44px; margin: 0 auto 14px; border-radius: 999px; border: 4px solid #dbeafe; border-top-color: #2563eb; animation: docSpin 0.9s linear infinite; }
.loading-text { color: #0f172a; font-size: 15px; font-weight: 700; }
.pagination { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; margin-top: 16px; }
.pagination-info { color: #475569; font-size: 14px; }
.pagination-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.pagination-size { display: inline-flex; align-items: center; gap: 8px; color: #475569; font-size: 14px; }
.pagination-btn { min-width: 42px; padding: 8px 12px; border-radius: 10px; border: 1px solid #dbe3f0; background: #fff; color: #334155; cursor: pointer; font-weight: 600; }
.pagination-select { min-width: 88px; width: auto; padding: 8px 12px; border-radius: 10px; border: 1px solid #dbe3f0; background: #fff; color: #334155; font-size: 14px; }
.pagination-btn.active { background: #2563eb; border-color: #2563eb; color: #fff; }
.pagination-btn:disabled { cursor: not-allowed; color: #94a3b8; background: #f8fafc; }
.pagination-btn:not(:disabled):hover { border-color: #94a3b8; background: #f8fafc; }
.pagination-btn.active:hover { background: #2563eb; border-color: #2563eb; }
html[data-theme="dark"] .editor-banner { background: rgba(30, 41, 59, 0.86); color: #bfdbfe; }
html[data-theme="dark"] .file-box { background: rgba(15, 23, 42, 0.92); border-color: rgba(148, 163, 184, 0.22); box-shadow: inset 0 0 0 1px rgba(148, 163, 184, 0.08); }
html[data-theme="dark"] .choice-switch { background: rgba(15, 23, 42, 0.86); border-color: rgba(148, 163, 184, 0.26); }
@@ -97,6 +107,15 @@
html[data-theme="dark"] .file-preview,
html[data-theme="dark"] .file-card-icon { background: rgba(2, 6, 23, 0.9); border-color: rgba(148, 163, 184, 0.24); color: #e2e8f0; }
html[data-theme="dark"] .file-card-icon:hover { background: rgba(30, 41, 59, 0.82); border-color: rgba(148, 163, 184, 0.4); }
html[data-theme="dark"] .pagination-info,
html[data-theme="dark"] .pagination-size { color: #cbd5e1; }
html[data-theme="dark"] .pagination-btn { background: rgba(15, 23, 42, 0.92); border-color: rgba(148, 163, 184, 0.24); color: #e2e8f0; box-shadow: inset 0 1px 0 rgba(255,255,255,0.04); }
html[data-theme="dark"] .pagination-btn:not(:disabled):hover { background: rgba(30, 41, 59, 0.96); border-color: rgba(96, 165, 250, 0.5); color: #f8fafc; }
html[data-theme="dark"] .pagination-btn.active,
html[data-theme="dark"] .pagination-btn.active:hover { background: linear-gradient(180deg, #3b82f6 0%, #2563eb 100%); border-color: #3b82f6; color: #eff6ff; box-shadow: 0 10px 24px rgba(37, 99, 235, 0.28); }
html[data-theme="dark"] .pagination-btn:disabled { background: rgba(15, 23, 42, 0.58); border-color: rgba(71, 85, 105, 0.45); color: #64748b; box-shadow: none; }
html[data-theme="dark"] .pagination-select { background: rgba(15, 23, 42, 0.92); border-color: rgba(148, 163, 184, 0.24); color: #f8fafc; box-shadow: inset 0 1px 0 rgba(255,255,255,0.04); }
html[data-theme="dark"] .pagination-select:focus { outline: none; border-color: #60a5fa; box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.18); }
@keyframes docSpin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@media (max-width: 960px) {
.grid, .file-group { grid-template-columns: 1fr; }
@@ -295,6 +314,7 @@
</tbody>
</table>
</div>
<div class="pagination" id="listPagination"></div>
</section>
</div>
@@ -342,6 +362,7 @@
const hotelTypeTagsEl = document.getElementById('hotelTypeTags')
const listTitleKeywordEl = document.getElementById('listTitleKeyword')
const listTypeFilterEl = document.getElementById('listTypeFilter')
const listPaginationEl = document.getElementById('listPagination')
const imageViewerEl = document.getElementById('imageViewer')
const imageViewerImgEl = document.getElementById('imageViewerImg')
const loadingMaskEl = document.getElementById('loadingMask')
@@ -388,6 +409,7 @@
const state = {
list: [],
fullList: [],
filteredList: [],
mode: 'idle',
editingId: '',
editingSource: null,
@@ -413,6 +435,12 @@
titleKeyword: '',
type: '',
},
pagination: {
page: 1,
pageSize: 10,
total: 0,
totalPages: 1,
},
}
function setStatus(message, type) {
@@ -1005,6 +1033,102 @@
})
}
function clampPage(page, totalPages) {
const normalizedPage = Number(page) || 1
const normalizedTotalPages = Math.max(1, Number(totalPages) || 1)
return Math.min(Math.max(1, normalizedPage), normalizedTotalPages)
}
function getVisiblePageNumbers(page, totalPages) {
const result = []
const start = Math.max(1, page - 2)
const end = Math.min(totalPages, start + 4)
const adjustedStart = Math.max(1, end - 4)
for (let current = adjustedStart; current <= end; current += 1) {
result.push(current)
}
return result
}
function renderPageSizeSelector() {
const options = [10, 20, 50, 100, 500]
return '<label class="pagination-size">每页'
+ '<select class="pagination-select" onchange="window.__setDocumentPageSize(this.value)">'
+ options.map(function (size) {
return '<option value="' + size + '"' + (Number(state.pagination.pageSize) === size ? ' selected' : '') + '>' + size + '</option>'
}).join('')
+ '</select>'
+ '条</label>'
}
function renderPagination() {
if (!listPaginationEl) {
return
}
const total = Number(state.pagination.total || 0)
const page = Number(state.pagination.page || 1)
const totalPages = Math.max(1, Number(state.pagination.totalPages || 1))
const sizeSelector = renderPageSizeSelector()
if (!total) {
listPaginationEl.innerHTML = '<div class="pagination-info">共 0 条</div>'
+ '<div class="pagination-actions">' + sizeSelector + '</div>'
return
}
const visiblePageNumbers = getVisiblePageNumbers(page, totalPages)
const startIndex = (page - 1) * state.pagination.pageSize + 1
const endIndex = Math.min(total, page * state.pagination.pageSize)
const buttons = []
buttons.push('<button class="pagination-btn" type="button" onclick="window.__gotoDocumentPage(' + (page - 1) + ')" ' + (page <= 1 ? 'disabled' : '') + '>上一页</button>')
if (visiblePageNumbers[0] > 1) {
buttons.push('<button class="pagination-btn" type="button" onclick="window.__gotoDocumentPage(1)">1</button>')
if (visiblePageNumbers[0] > 2) {
buttons.push('<button class="pagination-btn" type="button" disabled>...</button>')
}
}
for (let i = 0; i < visiblePageNumbers.length; i += 1) {
const current = visiblePageNumbers[i]
buttons.push('<button class="pagination-btn' + (current === page ? ' active' : '') + '" type="button" onclick="window.__gotoDocumentPage(' + current + ')">' + current + '</button>')
}
if (visiblePageNumbers.length && visiblePageNumbers[visiblePageNumbers.length - 1] < totalPages) {
if (visiblePageNumbers[visiblePageNumbers.length - 1] < totalPages - 1) {
buttons.push('<button class="pagination-btn" type="button" disabled>...</button>')
}
buttons.push('<button class="pagination-btn" type="button" onclick="window.__gotoDocumentPage(' + totalPages + ')">' + totalPages + '</button>')
}
buttons.push('<button class="pagination-btn" type="button" onclick="window.__gotoDocumentPage(' + (page + 1) + ')" ' + (page >= totalPages ? 'disabled' : '') + '>下一页</button>')
listPaginationEl.innerHTML = '<div class="pagination-info">共 ' + total + ' 条,显示第 ' + startIndex + '-' + endIndex + ' 条,第 ' + page + '/' + totalPages + ' 页</div>'
+ '<div class="pagination-actions">' + sizeSelector + buttons.join('') + '</div>'
}
function updateDocumentListView() {
const filteredList = applyListFiltersToList(state.fullList)
const pageSize = Math.max(1, Number(state.pagination.pageSize || 10))
const total = filteredList.length
const totalPages = Math.max(1, Math.ceil(total / pageSize))
const currentPage = clampPage(state.pagination.page, totalPages)
const startIndex = (currentPage - 1) * pageSize
state.filteredList = filteredList
state.pagination.page = currentPage
state.pagination.total = total
state.pagination.totalPages = totalPages
state.list = filteredList.slice(startIndex, startIndex + pageSize)
renderTable()
renderPagination()
}
function renderTable() {
if (!state.list.length) {
tableBody.innerHTML = '<tr><td colspan="5" class="empty">暂无文档数据。</td></tr>'
@@ -1132,6 +1256,27 @@
}
}
function goToDocumentPage(page) {
const nextPage = clampPage(page, state.pagination.totalPages)
if (nextPage === state.pagination.page) {
return
}
state.pagination.page = nextPage
updateDocumentListView()
}
function setDocumentPageSize(pageSize) {
const nextPageSize = Number(pageSize) || 10
if (nextPageSize === state.pagination.pageSize) {
return
}
state.pagination.pageSize = nextPageSize
state.pagination.page = 1
updateDocumentListView()
}
async function loadDocuments() {
setStatus('正在加载文档列表...', '')
showLoading('正在加载文档列表...')
@@ -1140,9 +1285,8 @@
document_type: state.listFilters.type,
})
state.fullList = Array.isArray(data.items) ? data.items : []
state.list = applyListFiltersToList(state.fullList)
renderTable()
setStatus('文档列表已刷新,共 ' + state.list.length + ' 条。', 'success')
updateDocumentListView()
setStatus('文档列表已刷新,共 ' + state.pagination.total + ' 条。', 'success')
} catch (err) {
setStatus(err.message || '加载列表失败', 'error')
} finally {
@@ -1177,8 +1321,11 @@
}
}
async function queryDocumentsWithAutoSave() {
async function queryDocumentsWithAutoSave(resetPage) {
syncListFiltersFromInputs()
if (resetPage) {
state.pagination.page = 1
}
await saveAndHideEditorBeforeListQuery()
applyListFiltersToInputs()
await loadDocuments()
@@ -1490,23 +1637,33 @@
updateSelection(fieldName, target.value, !!target.checked)
})
document.getElementById('reloadBtn').addEventListener('click', queryDocumentsWithAutoSave)
document.getElementById('searchBtn').addEventListener('click', queryDocumentsWithAutoSave)
window.__gotoDocumentPage = goToDocumentPage
window.__setDocumentPageSize = setDocumentPageSize
document.getElementById('reloadBtn').addEventListener('click', function () {
queryDocumentsWithAutoSave(false)
})
document.getElementById('searchBtn').addEventListener('click', function () {
queryDocumentsWithAutoSave(true)
})
document.getElementById('clearSearchBtn').addEventListener('click', function () {
state.listFilters.titleKeyword = ''
state.listFilters.type = ''
state.pagination.page = 1
applyListFiltersToInputs()
queryDocumentsWithAutoSave()
queryDocumentsWithAutoSave(false)
})
if (listTitleKeywordEl) {
listTitleKeywordEl.addEventListener('keydown', function (event) {
if (event.key === 'Enter') {
queryDocumentsWithAutoSave()
queryDocumentsWithAutoSave(true)
}
})
}
if (listTypeFilterEl) {
listTypeFilterEl.addEventListener('change', queryDocumentsWithAutoSave)
listTypeFilterEl.addEventListener('change', function () {
queryDocumentsWithAutoSave(true)
})
}
document.getElementById('createModeBtn').addEventListener('click', function () {
enterCreateMode()

View File

@@ -76,6 +76,25 @@
.loading-card { min-width: min(92vw, 360px); padding: 24px 22px; border-radius: 20px; background: rgba(255,255,255,0.98); box-shadow: 0 28px 70px rgba(15, 23, 42, 0.22); border: 1px solid #dbe3f0; text-align: center; }
.loading-spinner { width: 44px; height: 44px; margin: 0 auto 14px; border-radius: 999px; border: 4px solid #dbeafe; border-top-color: #2563eb; animation: spin 0.9s linear infinite; }
.loading-text { color: #0f172a; font-size: 15px; font-weight: 700; }
.pagination { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; margin-top: 16px; }
.pagination-info { color: #475569; font-size: 14px; }
.pagination-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.pagination-size { display: inline-flex; align-items: center; gap: 8px; color: #475569; font-size: 14px; }
.pagination-btn { min-width: 42px; padding: 8px 12px; border-radius: 10px; border: 1px solid #dbe3f0; background: #fff; color: #334155; cursor: pointer; font-weight: 600; }
.pagination-select { min-width: 88px; width: auto; padding: 8px 12px; border-radius: 10px; border: 1px solid #dbe3f0; background: #fff; color: #334155; font-size: 14px; }
.pagination-btn.active { background: #2563eb; border-color: #2563eb; color: #fff; }
.pagination-btn:disabled { cursor: not-allowed; color: #94a3b8; background: #f8fafc; }
.pagination-btn:not(:disabled):hover { border-color: #94a3b8; background: #f8fafc; }
.pagination-btn.active:hover { background: #2563eb; border-color: #2563eb; }
html[data-theme="dark"] .pagination-info,
html[data-theme="dark"] .pagination-size { color: #cbd5e1; }
html[data-theme="dark"] .pagination-btn { background: rgba(15, 23, 42, 0.92); border-color: rgba(148, 163, 184, 0.24); color: #e2e8f0; box-shadow: inset 0 1px 0 rgba(255,255,255,0.04); }
html[data-theme="dark"] .pagination-btn:not(:disabled):hover { background: rgba(30, 41, 59, 0.96); border-color: rgba(96, 165, 250, 0.5); color: #f8fafc; }
html[data-theme="dark"] .pagination-btn.active,
html[data-theme="dark"] .pagination-btn.active:hover { background: linear-gradient(180deg, #3b82f6 0%, #2563eb 100%); border-color: #3b82f6; color: #eff6ff; box-shadow: 0 10px 24px rgba(37, 99, 235, 0.28); }
html[data-theme="dark"] .pagination-btn:disabled { background: rgba(15, 23, 42, 0.58); border-color: rgba(71, 85, 105, 0.45); color: #64748b; box-shadow: none; }
html[data-theme="dark"] .pagination-select { background: rgba(15, 23, 42, 0.92); border-color: rgba(148, 163, 184, 0.24); color: #f8fafc; box-shadow: inset 0 1px 0 rgba(255,255,255,0.04); }
html[data-theme="dark"] .pagination-select:focus { outline: none; border-color: #60a5fa; box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.18); }
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@media (max-width: 960px) {
.grid, .toolbar, .option-list { grid-template-columns: 1fr; }
@@ -309,6 +328,7 @@
</tbody>
</table>
</div>
<div class="pagination" id="listPagination"></div>
</section>
</div>
@@ -375,6 +395,7 @@
const statusFilterEl = document.getElementById('statusFilter')
const categoryFilterEl = document.getElementById('categoryFilter')
const prodListSortHintEl = document.getElementById('prodListSortHint')
const listPaginationEl = document.getElementById('listPagination')
const fields = {
keyword: document.getElementById('keywordInput'),
@@ -404,6 +425,7 @@
const state = {
list: [],
fullList: [],
mode: 'idle',
editingId: '',
editingSource: null,
@@ -423,6 +445,12 @@
currentIconAttachments: [],
pendingIconFiles: [],
copySourceProductId: '',
pagination: {
page: 1,
pageSize: 10,
total: 0,
totalPages: 1,
},
}
const multiFieldConfig = {
@@ -609,24 +637,42 @@
}
}
async function requestProductListFallback(payload) {
async function fetchAllFallbackProductRecords() {
const token = getToken()
const res = await fetch(getApiUrl('/collections/tbl_product_list/records?page=1&perPage=500&sort=-updated'), {
method: 'GET',
headers: {
Authorization: token ? ('Bearer ' + token) : '',
},
})
const perPage = 200
const result = []
let page = 1
const parsed = await parseJsonSafe(res)
if (!res.ok || !parsed.isJson || !parsed.json) {
throw new Error('产品列表回退查询失败')
while (true) {
const res = await fetch(getApiUrl('/collections/tbl_product_list/records?page=' + page + '&perPage=' + perPage + '&sort=-updated'), {
method: 'GET',
headers: {
Authorization: token ? ('Bearer ' + token) : '',
},
})
const parsed = await parseJsonSafe(res)
if (!res.ok || !parsed.isJson || !parsed.json) {
throw new Error('产品列表回退查询失败')
}
const rawItems = Array.isArray(parsed.json.items) ? parsed.json.items : []
for (let i = 0; i < rawItems.length; i += 1) {
result.push(normalizeFallbackProductRecord(rawItems[i]))
}
if (rawItems.length < perPage) {
break
}
page += 1
}
const rawItems = Array.isArray(parsed.json.items) ? parsed.json.items : []
const normalized = rawItems.map(function (item) {
return normalizeFallbackProductRecord(item)
})
return result
}
async function requestProductListFallback(payload) {
const normalized = await fetchAllFallbackProductRecords()
const keyword = normalizeText(payload && payload.keyword).toLowerCase()
const status = normalizeText(payload && payload.status)
@@ -656,6 +702,101 @@
return filtered
}
function clampPage(page, totalPages) {
const normalizedPage = Number(page) || 1
const normalizedTotalPages = Math.max(1, Number(totalPages) || 1)
return Math.min(Math.max(1, normalizedPage), normalizedTotalPages)
}
function getVisiblePageNumbers(page, totalPages) {
const result = []
const start = Math.max(1, page - 2)
const end = Math.min(totalPages, start + 4)
const adjustedStart = Math.max(1, end - 4)
for (let current = adjustedStart; current <= end; current += 1) {
result.push(current)
}
return result
}
function renderPageSizeSelector() {
const options = [10, 20, 50, 100, 500]
return '<label class="pagination-size">每页'
+ '<select class="pagination-select" onchange="window.__setProductPageSize(this.value)">'
+ options.map(function (size) {
return '<option value="' + size + '"' + (Number(state.pagination.pageSize) === size ? ' selected' : '') + '>' + size + '</option>'
}).join('')
+ '</select>'
+ '条</label>'
}
function renderPagination() {
if (!listPaginationEl) {
return
}
const total = Number(state.pagination.total || 0)
const page = Number(state.pagination.page || 1)
const totalPages = Math.max(1, Number(state.pagination.totalPages || 1))
const sizeSelector = renderPageSizeSelector()
if (!total) {
listPaginationEl.innerHTML = '<div class="pagination-info">共 0 条</div>'
+ '<div class="pagination-actions">' + sizeSelector + '</div>'
return
}
const visiblePageNumbers = getVisiblePageNumbers(page, totalPages)
const startIndex = (page - 1) * state.pagination.pageSize + 1
const endIndex = Math.min(total, page * state.pagination.pageSize)
const buttons = []
buttons.push('<button class="pagination-btn" type="button" onclick="window.__gotoProductPage(' + (page - 1) + ')" ' + (page <= 1 ? 'disabled' : '') + '>上一页</button>')
if (visiblePageNumbers[0] > 1) {
buttons.push('<button class="pagination-btn" type="button" onclick="window.__gotoProductPage(1)">1</button>')
if (visiblePageNumbers[0] > 2) {
buttons.push('<button class="pagination-btn" type="button" disabled>...</button>')
}
}
for (let i = 0; i < visiblePageNumbers.length; i += 1) {
const current = visiblePageNumbers[i]
buttons.push('<button class="pagination-btn' + (current === page ? ' active' : '') + '" type="button" onclick="window.__gotoProductPage(' + current + ')">' + current + '</button>')
}
if (visiblePageNumbers.length && visiblePageNumbers[visiblePageNumbers.length - 1] < totalPages) {
if (visiblePageNumbers[visiblePageNumbers.length - 1] < totalPages - 1) {
buttons.push('<button class="pagination-btn" type="button" disabled>...</button>')
}
buttons.push('<button class="pagination-btn" type="button" onclick="window.__gotoProductPage(' + totalPages + ')">' + totalPages + '</button>')
}
buttons.push('<button class="pagination-btn" type="button" onclick="window.__gotoProductPage(' + (page + 1) + ')" ' + (page >= totalPages ? 'disabled' : '') + '>下一页</button>')
listPaginationEl.innerHTML = '<div class="pagination-info">共 ' + total + ' 条,显示第 ' + startIndex + '-' + endIndex + ' 条,第 ' + page + '/' + totalPages + ' 页</div>'
+ '<div class="pagination-actions">' + sizeSelector + buttons.join('') + '</div>'
}
function updateProductListView() {
const pageSize = Math.max(1, Number(state.pagination.pageSize || 10))
const total = state.fullList.length
const totalPages = Math.max(1, Math.ceil(total / pageSize))
const currentPage = clampPage(state.pagination.page, totalPages)
const startIndex = (currentPage - 1) * pageSize
state.pagination.page = currentPage
state.pagination.total = total
state.pagination.totalPages = totalPages
state.list = state.fullList.slice(startIndex, startIndex + pageSize)
renderTable()
renderPagination()
renderSortRankHint()
}
async function parseJsonSafe(res) {
const contentType = String(res.headers.get('content-type') || '').toLowerCase()
const rawText = await res.text()
@@ -713,6 +854,16 @@
return String(value || '').trim()
}
function generatePocketBaseRecordId() {
const alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789'
let output = ''
for (let i = 0; i < 15; i += 1) {
const idx = Math.floor(Math.random() * alphabet.length)
output += alphabet[idx]
}
return output
}
function splitPipe(value) {
return String(value || '')
.split('|')
@@ -858,7 +1009,7 @@
const targetSort = Math.floor(sortValue)
const targetId = state.mode === 'edit' ? normalizeText(state.editingId) : '__draft__'
const sameCategoryItems = state.list
const sameCategoryItems = state.fullList
.filter(function (item) {
return normalizeText(item.prod_list_category) === category && normalizeText(item.prod_list_id) !== targetId
})
@@ -1714,6 +1865,7 @@
function buildCopyPayload(source, nextName, nextModel) {
return {
id: generatePocketBaseRecordId(),
prod_list_id: '',
prod_list_name: normalizeText(nextName),
prod_list_modelnumber: normalizeText(nextModel),
@@ -1761,7 +1913,7 @@
}
async function copyProduct(productId) {
const source = state.list.find(function (item) {
const source = state.fullList.find(function (item) {
return normalizeText(item.prod_list_id) === normalizeText(productId)
})
@@ -1781,7 +1933,7 @@
return
}
const source = state.list.find(function (item) {
const source = state.fullList.find(function (item) {
return normalizeText(item.prod_list_id) === sourceId
})
if (!source) {
@@ -1830,20 +1982,18 @@
status: payload.status,
prod_list_category: payload.prod_list_category,
})
state.list = Array.isArray(data.items) ? data.items : []
renderTable()
renderSortRankHint()
setStatus('产品列表已刷新,共 ' + state.list.length + ' 条。', 'success')
state.fullList = Array.isArray(data.items) ? data.items : []
updateProductListView()
setStatus('产品列表已刷新,共 ' + state.pagination.total + ' 条。', 'success')
} catch (err) {
try {
state.list = await requestProductListFallback({
state.fullList = await requestProductListFallback({
keyword: normalizeText(fields.keyword.value),
status: normalizeText(statusFilterEl.value),
prod_list_category: normalizeText(categoryFilterEl.value),
})
renderTable()
renderSortRankHint()
setStatus('产品列表已通过回退链路刷新,共 ' + state.list.length + ' 条。', 'success')
updateProductListView()
setStatus('产品列表已通过回退链路刷新,共 ' + state.pagination.total + ' 条。', 'success')
} catch (_fallbackErr) {
setStatus(err.message || '加载产品列表失败', 'error')
}
@@ -1878,11 +2028,35 @@
}
}
async function queryProductsWithAutoSave() {
async function queryProductsWithAutoSave(resetPage) {
if (resetPage) {
state.pagination.page = 1
}
await saveAndHideEditorBeforeProductQuery()
await loadProducts()
}
function goToProductPage(page) {
const nextPage = clampPage(page, state.pagination.totalPages)
if (nextPage === state.pagination.page) {
return
}
state.pagination.page = nextPage
updateProductListView()
}
function setProductPageSize(pageSize) {
const nextPageSize = Number(pageSize) || 10
if (nextPageSize === state.pagination.pageSize) {
return
}
state.pagination.pageSize = nextPageSize
state.pagination.page = 1
updateProductListView()
}
async function enterEditMode(productId) {
showLoading('正在加载产品详情...')
try {
@@ -1948,6 +2122,7 @@
}
const payload = {
id: state.mode === 'edit' ? '' : generatePocketBaseRecordId(),
prod_list_id: state.mode === 'edit' ? state.editingId : '',
prod_list_name: normalizeText(fields.name.value),
prod_list_modelnumber: normalizeText(fields.model.value),
@@ -2218,14 +2393,37 @@
}
})
window.__gotoProductPage = goToProductPage
window.__setProductPageSize = setProductPageSize
document.getElementById('reloadBtn').addEventListener('click', function () {
queryProductsWithAutoSave()
queryProductsWithAutoSave(false)
})
document.getElementById('searchBtn').addEventListener('click', function () {
queryProductsWithAutoSave()
queryProductsWithAutoSave(true)
})
if (fields.keyword) {
fields.keyword.addEventListener('keydown', function (event) {
if (event.key === 'Enter') {
queryProductsWithAutoSave(true)
}
})
}
if (statusFilterEl) {
statusFilterEl.addEventListener('change', function () {
queryProductsWithAutoSave(true)
})
}
if (categoryFilterEl) {
categoryFilterEl.addEventListener('change', function () {
queryProductsWithAutoSave(true)
})
}
document.getElementById('createModeBtn').addEventListener('click', function () {
enterCreateMode()
})

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,135 @@
attachmentRecords:
get:
operationId: getPocketBaseAttachmentRecords
tags:
- 附件信息
summary: 根据 attachments_id 查询单条或多条附件信息
description: |
使用 PocketBase 原生 records list 接口查询 `tbl_attachments`。
当前线上权限规则:
- `listRule = is_delete = 0`,因此任何客户端都可直接读取未软删除附件
- 原生 `create/update/delete` 仍仅管理员或管理后台用户允许
标准调用方式有两种:
1. 按 `attachments_id` 查询单条:
- `filter=attachments_id="ATT-1774599142438-8n1UcU"`
- `perPage=1`
- `page=1`
2. 按多个 `attachments_id` 批量查询:
- 使用 `||` 组合多个等值条件
- 例如:`filter=attachments_id="ATT-1774599142438-8n1UcU" || attachments_id="ATT-1774599143999-7pQkLm"`
- 传 `perPage` 为预期返回条数,`page=1`
注意:
- 这是 PocketBase 原生返回结构,不是 hooks 统一 `{ statusCode, errMsg, data }` 包装
- `attachments_link` 返回的是 PocketBase 文件字段值,不是完整下载地址
- 若需文件流地址,可按 PocketBase 标准文件路径自行拼接:`/pb/api/files/{collectionId}/{recordId}/{attachments_link}`
parameters:
- name: filter
in: query
required: false
description: |
PocketBase 标准过滤表达式。
- 按 `attachments_id` 精确查询单条:`attachments_id="ATT-1774599142438-8n1UcU"`
- 按多个 `attachments_id` 批量查询:`attachments_id="ATT-1774599142438-8n1UcU" || attachments_id="ATT-1774599143999-7pQkLm"`
- 不传该参数时,返回分页列表
schema:
type: string
example: attachments_id="ATT-1774599142438-8n1UcU"
- name: page
in: query
required: false
description: 页码
schema:
type: integer
minimum: 1
default: 1
- name: perPage
in: query
required: false
description: 每页条数;单查建议为 `1`,批量查询建议设置为预期条数
schema:
type: integer
minimum: 1
default: 20
responses:
'200':
description: 查询成功
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseAttachmentListResponse'
examples:
byAttachmentsId:
value:
page: 页码 | integer
perPage: 每页条数 | integer
totalItems: 总记录数 | integer
totalPages: 总页数 | integer
items:
- id: PocketBase记录主键 | string
collectionId: 集合ID | string
collectionName: 集合名称 | string
attachments_id: 附件业务ID | string
attachments_link: PocketBase文件字段值可拼接文件流地址 | string
attachments_filename: 原始文件名 | string
attachments_filetype: 文件类型或MIME | string
attachments_size: 文件大小 | number
attachments_owner: 上传者业务标识 | string
attachments_md5: 文件MD5 | string
attachments_ocr: OCR识别结果 | string
attachments_status: 附件状态 | string
attachments_remark: 备注 | string
byAttachmentsIds:
value:
page: 页码 | integer
perPage: 每页条数 | integer
totalItems: 总记录数 | integer
totalPages: 总页数 | integer
items:
- id: PocketBase记录主键 | string
collectionId: 集合ID | string
collectionName: 集合名称 | string
attachments_id: ATT-1774599142438-8n1UcU
attachments_link: PocketBase文件字段值可拼接文件流地址 | string
attachments_filename: 原始文件名 | string
attachments_filetype: 文件类型或MIME | string
attachments_size: 文件大小 | number
attachments_owner: 上传者业务标识 | string
attachments_md5: 文件MD5 | string
attachments_ocr: OCR识别结果 | string
attachments_status: 附件状态 | string
attachments_remark: 备注 | string
- id: PocketBase记录主键 | string
collectionId: 集合ID | string
collectionName: 集合名称 | string
attachments_id: ATT-1774599143999-7pQkLm
attachments_link: PocketBase文件字段值可拼接文件流地址 | string
attachments_filename: 原始文件名 | string
attachments_filetype: 文件类型或MIME | string
attachments_size: 文件大小 | number
attachments_owner: 上传者业务标识 | string
attachments_md5: 文件MD5 | string
attachments_ocr: OCR识别结果 | string
attachments_status: 附件状态 | string
attachments_remark: 备注 | string
'400':
description: 查询参数错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'403':
description: 集合规则被锁定或服务端权限设置异常
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'500':
description: 服务端错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'

View File

@@ -0,0 +1,335 @@
cartList:
post:
operationId: postCartList
tags:
- 购物车
summary: 按索引字段模糊查询购物车列表
description: |
调用自定义 hooks API 查询当前登录用户的购物车列表。
查询规则:
- 仅允许查询当前 `Authorization` 对应用户自己的购物车记录
- 支持通过 `keyword` 对多个索引相关字段做统一模糊搜索,当前实现覆盖:
- `cart_id`
- `cart_number`
- `cart_product_id`
- `product_name`
- 支持按 `cart_status` 精确过滤
- 支持按 `cart_number` 精确过滤
目标软删除契约:
- 目标行为应仅返回 `is_delete = 0` 的记录
- 当前仓库实现尚未显式追加 `is_delete = 0` 过滤,请以实际后端代码为准
security:
- BearerAuth: []
requestBody:
required: false
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/CartListRequest'
responses:
'200':
description: 查询成功
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/CartListResponse'
'400':
description: 参数错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'401':
description: token 缺失、无效或已过期
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'415':
description: 请求体必须为 application/json
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'500':
description: 服务端错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
cartDetail:
post:
operationId: postCartDetail
tags:
- 购物车
summary: 按 cart_id 精确查询购物车详情
description: |
调用自定义 hooks API 按 `cart_id` 查询单条购物车记录。
查询规则:
- 仅允许访问当前 `Authorization` 对应用户自己的购物车记录
- 查询键为业务 ID `cart_id`,不是 PocketBase 原生 `recordId`
目标软删除契约:
- 目标行为应仅允许查询 `is_delete = 0` 的记录
- 当前仓库实现尚未显式追加 `is_delete = 0` 过滤,请以实际后端代码为准
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/CartDetailRequest'
responses:
'200':
description: 查询成功
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/CartRecord'
'400':
description: 参数错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'401':
description: token 缺失、无效或已过期
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'403':
description: 无权访问该购物车记录
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'404':
description: 未找到对应购物车记录
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'415':
description: 请求体必须为 application/json
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'500':
description: 服务端错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
cartCreate:
post:
operationId: postCartCreate
tags:
- 购物车
summary: 新增购物车记录
description: |
调用自定义 hooks API 新增一条购物车记录。
创建规则:
- 服务端自动根据当前 token 写入 `cart_owner`
- 服务端自动生成 `cart_id`
- 若未传 `cart_number`,服务端会自动生成展示编号
- `cart_product_id`、`cart_product_quantity`、`cart_at_price` 为必填
目标软删除契约:
- 新建记录应默认为 `is_delete = 0`
- 当前仓库导出响应中尚未显式返回 `is_delete` 字段
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/CartCreateRequest'
responses:
'200':
description: 创建成功
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/CartRecord'
'400':
description: 参数错误、产品不存在或创建失败
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'401':
description: token 缺失、无效或已过期
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'415':
description: 请求体必须为 application/json
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'429':
description: 重复请求过于频繁
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'500':
description: 服务端错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
cartUpdate:
post:
operationId: postCartUpdate
tags:
- 购物车
summary: 修改购物车记录
description: |
调用自定义 hooks API 按 `cart_id` 更新购物车记录。
更新规则:
- 仅允许修改当前 `Authorization` 对应用户自己的购物车记录
- `cart_id` 为必填,用于精确定位目标记录
- 其余字段均为可选增量更新
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/CartUpdateRequest'
responses:
'200':
description: 更新成功
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/CartRecord'
'400':
description: 参数错误、产品不存在或更新失败
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'401':
description: token 缺失、无效或已过期
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'403':
description: 无权访问该购物车记录
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'404':
description: 未找到待修改的购物车记录
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'415':
description: 请求体必须为 application/json
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'429':
description: 重复请求过于频繁
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'500':
description: 服务端错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
cartDelete:
post:
operationId: postCartDelete
tags:
- 购物车
summary: 软删除购物车记录
description: |
目标契约:按 `cart_id` 软删除购物车记录,将 `is_delete` 标记为 `1`,而不是物理删除。
当前仓库实现差异:
- 当前 `cartOrderService.deleteCart()` 仍直接调用 `$app.delete(record)` 做物理删除
- 因此当前后端实现与本软删除契约不一致
- 若要严格按本文档执行,需先同步调整后端服务实现,并在列表/详情接口中补充 `is_delete = 0` 过滤
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/CartDeleteRequest'
responses:
'200':
description: 删除成功
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/CartDeleteResponse'
'400':
description: 参数错误或删除失败
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'401':
description: token 缺失、无效或已过期
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'403':
description: 无权访问该购物车记录
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'404':
description: 未找到待删除的购物车记录
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'415':
description: 请求体必须为 application/json
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'429':
description: 重复请求过于频繁
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'500':
description: 服务端错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'

View File

@@ -0,0 +1,250 @@
cartRecords:
get:
operationId: getPocketBaseCartRecords
tags:
- 购物车
summary: 查询当前用户购物车列表或按业务 ID 精确查询
description: |
使用 PocketBase 原生 records list 接口查询 `tbl_cart`。
当前线上权限规则:
- `listRule = @request.auth.id != "" && cart_owner = @request.auth.openid && is_delete = 0`
- 因此调用方只能读到 `cart_owner` 等于自己 `openid` 且未软删除的记录
标准调用方式:
1. 查询当前登录用户全部购物车:
- 不传 `filter`
- 可选传 `sort=-cart_create`
2. 按业务 ID 精确查单条:
- `filter=cart_id="CART-..."`
- `perPage=1`
- `page=1`
注意:
- 这是 PocketBase 原生返回结构,不是 hooks 统一包装
- 即使不传 `filter`,返回结果也会继续受 `listRule` 限制
security:
- BearerAuth: []
parameters:
- name: filter
in: query
required: false
description: |
PocketBase 标准过滤表达式。
- 查当前用户全部购物车时:不传
- 按 `cart_id` 精确查单条时:`cart_id="CART-1770000000000-abc123"`
schema:
type: string
example: cart_id="CART-1770000000000-abc123"
- name: page
in: query
required: false
schema:
type: integer
minimum: 1
default: 1
- name: perPage
in: query
required: false
schema:
type: integer
minimum: 1
default: 20
- name: sort
in: query
required: false
description: PocketBase 原生排序表达式,推荐 `-cart_create`
schema:
type: string
example: -cart_create
responses:
'200':
description: 查询成功
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseCartListResponse'
'400':
description: 查询参数错误或不满足 listRule
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'401':
description: token 缺失、无效或已过期
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'403':
description: 集合规则被锁定或服务端权限设置异常
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'500':
description: 服务端错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
post:
operationId: postPocketBaseCartRecord
tags:
- 购物车
summary: 创建购物车记录
description: |
使用 PocketBase 原生 records create 接口向 `tbl_cart` 新增记录。
当前线上权限规则:
- `createRule = @request.auth.id != "" && @request.body.cart_owner = @request.auth.openid`
- 因此客户端创建时必须显式提交 `cart_owner`,并且值必须等于当前 token 对应的 `openid`
这意味着:
- 不能依赖服务端自动补 owner
- 不能省略 `cart_owner`
- 不满足规则时 PocketBase 会直接返回 `400`
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseCartCreateRequest'
responses:
'200':
description: 创建成功
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseCartRecord'
'400':
description: 参数错误、违反字段约束或不满足 createRule
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'401':
description: token 缺失、无效或已过期
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'403':
description: 集合规则被锁定或服务端权限设置异常
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'500':
description: 服务端错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
cartRecordById:
patch:
operationId: patchPocketBaseCartRecordByRecordId
tags:
- 购物车
summary: 更新购物车记录
description: |
使用 PocketBase 原生 records update 接口更新 `tbl_cart`。
标准调用流程:
1. 先通过 `GET /pb/api/collections/tbl_cart/records?filter=cart_id="..."&perPage=1&page=1` 找到原生 `recordId`
2. 再调用当前 `PATCH /pb/api/collections/tbl_cart/records/{recordId}`
当前线上权限规则:
- `updateRule = @request.auth.id != "" && cart_owner = @request.auth.openid`
- 调用方只能修改自己的购物车记录
security:
- BearerAuth: []
parameters:
- name: recordId
in: path
required: true
schema:
type: string
example: l2r3nq7rqhuob0h
requestBody:
required: true
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseCartUpdateRequest'
responses:
'200':
description: 更新成功
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseCartRecord'
'400':
description: 参数错误或违反字段约束
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'401':
description: token 缺失、无效或已过期
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'404':
description: 记录不存在或不满足 updateRule
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'500':
description: 服务端错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
delete:
operationId: deletePocketBaseCartRecordByRecordId
tags:
- 购物车
summary: 删除购物车记录
description: |
使用 PocketBase 原生 records delete 接口删除 `tbl_cart`。
当前线上权限规则:
- `deleteRule = @request.auth.id != "" && cart_owner = @request.auth.openid`
- 调用方只能删除自己的购物车记录
security:
- BearerAuth: []
parameters:
- name: recordId
in: path
required: true
schema:
type: string
example: l2r3nq7rqhuob0h
responses:
'204':
description: 删除成功
'401':
description: token 缺失、无效或已过期
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'404':
description: 记录不存在或不满足 deleteRule
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'500':
description: 服务端错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'

View File

@@ -0,0 +1,290 @@
companyRecords:
post:
operationId: postPocketBaseCompanyRecord
tags:
- 企业信息
summary: 创建公司
description: |
使用 PocketBase 原生 records create 接口向 `tbl_company` 新增一行记录。
当前线上权限规则:
- `createRule = ""`,因此任何客户端都可直接创建
- 其他原生操作中,`update/delete/view` 仅管理员或管理后台用户允许
注意:
- 这是 PocketBase 原生返回结构,不是 hooks 统一 `{ statusCode, errMsg, data }` 包装
- `company_id` 由数据库自动生成,客户端创建时不需要传
- `company_id` 仍带唯一索引,可用于后续按业务 id 查询
requestBody:
required: true
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseCompanyCreateRequest'
examples:
default:
value:
company_name: 微信侧测试企业
company_type: 渠道商
company_entity: 张三
company_usci: '91310000123456789A'
company_nationality: 中国
company_nationality_code: CN
company_province: 上海
company_province_code: '310000'
company_city: 上海
company_city_code: '310100'
company_district: 浦东新区
company_district_code: '310115'
company_postalcode: '200000'
company_add: 上海市浦东新区XX路1号
company_status: 有效
company_level: A
company_owner_openid: wx-openid-owner-001
company_remark: 小程序公开创建示例
responses:
'200':
description: 创建成功
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseCompanyRecord'
'400':
description: 参数错误或违反当前集合约束
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'403':
description: 集合规则被锁定或服务端权限设置异常
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'500':
description: 服务端错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
get:
operationId: getPocketBaseCompanyRecords
tags:
- 企业信息
summary: 查询整个 tbl_company 列表 / 根据 company_id 查询对应公司信息
description: |
使用 PocketBase 原生 records list 接口查询 `tbl_company`。
当前线上权限规则:
- `listRule = is_delete = 0`,因此默认只返回未软删除数据,且整个列表查询与条件查询都公开可读
- `createRule = ""`,因此创建也公开可调用
- `view/update/delete` 仅管理员或管理后台用户允许
标准调用方式有两种:
1. 根据 `company_id` 查询对应公司信息:
- `filter=company_id="WX-COMPANY-10001"`
- `perPage=1`
- `page=1`
2. 查询整个 `tbl_company` 列表:
- 不传 `filter`
- 按需传 `page`、`perPage`
注意:
- 这是 PocketBase 原生返回结构,不是 hooks 统一 `{ statusCode, errMsg, data }` 包装
- PocketBase 原生标准接口里,“按 `company_id` 查询单条”和“查询整个列表”共用同一个 `GET /records` 路径,因此文档以同一个 GET operation 展示两种调用模式
parameters:
- name: filter
in: query
required: false
description: |
PocketBase 标准过滤表达式。
- 根据 `company_id` 查询单条时:`company_id="WX-COMPANY-10001"`
- 查询整个列表时:不传该参数
schema:
type: string
example: company_id="WX-COMPANY-10001"
- name: page
in: query
required: false
description: 页码
schema:
type: integer
minimum: 1
default: 1
- name: perPage
in: query
required: false
description: 每页条数;按 `company_id` 单查时建议固定为 `1`
schema:
type: integer
minimum: 1
default: 20
responses:
'200':
description: 查询成功
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseCompanyListResponse'
examples:
byCompanyId:
value:
page: 页码 | integer
perPage: 每页条数 | integer
totalItems: 总记录数 | integer
totalPages: 总页数 | integer
items:
- id: PocketBase记录主键 | string
collectionId: 集合ID | string
collectionName: 集合名称 | string
created: 记录创建时间 | string
updated: 记录更新时间 | string
company_id: 公司业务id由数据库自动生成 | string
company_name: 公司名称 | string
company_type: 公司类型 | string
company_entity: 公司法人 | string
company_usci: 统一社会信用代码 | string
company_nationality: 国家名称 | string
company_nationality_code: 国家编码 | string
company_province: 省份名称 | string
company_province_code: 省份编码 | string
company_city: 城市名称 | string
company_city_code: 城市编码 | string
company_district: 区县名称 | string
company_district_code: 区县编码 | string
company_postalcode: 邮政编码 | string
company_add: 公司地址 | string
company_status: 公司状态 | string
company_level: 公司等级 | string
company_owner_openid: 公司所有者openid | string
company_remark: 备注 | string
listAll:
value:
page: 页码 | integer
perPage: 每页条数 | integer
totalItems: 总记录数 | integer
totalPages: 总页数 | integer
items:
- id: PocketBase记录主键 | string
collectionId: 集合ID | string
collectionName: 集合名称 | string
created: 记录创建时间 | string
updated: 记录更新时间 | string
company_id: 公司业务id由数据库自动生成 | string
company_name: 公司名称 | string
company_type: 公司类型 | string
company_entity: 公司法人 | string
company_usci: 统一社会信用代码 | string
company_nationality: 国家名称 | string
company_nationality_code: 国家编码 | string
company_province: 省份名称 | string
company_province_code: 省份编码 | string
company_city: 城市名称 | string
company_city_code: 城市编码 | string
company_district: 区县名称 | string
company_district_code: 区县编码 | string
company_postalcode: 邮政编码 | string
company_add: 公司地址 | string
company_status: 公司状态 | string
company_level: 公司等级 | string
company_owner_openid: 公司所有者openid | string
company_remark: 备注 | string
notFoundByCompanyId:
value:
page: 页码 | integer
perPage: 每页条数 | integer
totalItems: 总记录数 | integer
totalPages: 总页数 | integer
items: []
'400':
description: 查询参数错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'403':
description: 集合规则被锁定或服务端权限设置异常
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'500':
description: 服务端错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
companyRecordById:
patch:
operationId: patchPocketBaseCompanyRecordByRecordId
tags:
- 企业信息
summary: 通过 company_id 定位后修改公司信息
description: |
这是 PocketBase 原生标准更新接口,实际写入路径参数仍然必须使用记录主键 `recordId`。
如果前端手里只有 `company_id`,标准调用流程是:
1. 先调用 `GET /pb/api/collections/tbl_company/records?filter=company_id="..."&perPage=1&page=1`
2. 从返回结果 `items[0].id` 中取出 PocketBase 原生记录主键
3. 再调用当前 `PATCH /pb/api/collections/tbl_company/records/{recordId}` 完成更新
当前线上权限规则:
- `updateRule` 仅管理员或管理后台用户允许
- 普通公开调用不能直接更新
parameters:
- name: recordId
in: path
required: true
description: 通过 `company_id` 查询结果拿到的 PocketBase 记录主键 `id`
schema:
type: string
example: l2r3nq7rqhuob0h
requestBody:
required: true
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseCompanyUpdateRequest'
examples:
default:
value:
company_name: 微信侧测试企业(更新)
company_status: 有效
company_level: S
company_owner_openid: wx-openid-owner-001
company_district: 徐汇区
company_district_code: '310104'
company_remark: 通过 company_id 先定位再修改
responses:
'200':
description: 更新成功
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseCompanyRecord'
'400':
description: 参数错误或违反集合约束
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'403':
description: 当前调用方没有 update 权限
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'404':
description: 记录不存在
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'500':
description: 服务端错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'

View File

@@ -0,0 +1,222 @@
documentRecords:
get:
operationId: getPocketBaseDocumentRecords
tags:
- 文档信息
summary: 分页查询文档列表 / 按 system_dict_id 与 enum 双条件分页筛选文档
description: |
使用 PocketBase 原生 records list 接口查询 `tbl_document`。
当前线上权限规则:
- `listRule = is_delete = 0`,因此任何客户端都可直接分页查询未软删除文档
- `viewRule = is_delete = 0`,因此单条详情也只可读取未软删除文档
- `create/update/delete` 仍仅管理员或管理后台用户允许
`document_type` 的存储格式为:
- `system_dict_id@dict_word_enum|system_dict_id@dict_word_enum`
业务上这里是两个独立条件,并且查询时两个条件都要满足:
- 条件 1包含某个 `system_dict_id`
- 条件 2包含某个 `enum`
PocketBase 原生标准接口实际只有一个 `filter` 参数,因此应在同一个 `filter` 中写成两个 `contains` 条件,例如:
- `system_dict_id = DICT-1774599144591-hAEFQj`
- `enum = UT1`
- 最终:`document_type ~ "DICT-1774599144591-hAEFQj" && document_type ~ "@UT1"`
这条写法已经按线上真实数据验证通过。
排序说明:
- 当前线上统一按 `document_create` 排序
- 若要“最新上传的排在最前面”,请传 `sort=-document_create`
标准调用方式有两种:
1. 查询整个文档列表:
- 不传 `filter`
- 按需传 `page`、`perPage`
- 若要按最新上传倒序,传 `sort=-document_create`
2. 根据 `system_dict_id` 和 `enum` 两个业务条件分页筛选:
- 直接传 `filter=document_type ~ "<system_dict_id>" && document_type ~ "@<enum>"`
- 传 `page`、`perPage`
- 若要按最新上传倒序,传 `sort=-document_create`
注意:
- 这是 PocketBase 原生返回结构,不是 hooks 统一 `{ statusCode, errMsg, data }` 包装
- 如果需要更复杂的条件组合,可继续使用 PocketBase 原生 `filter` 语法自行扩展
parameters:
- name: filter
in: query
required: false
description: |
PocketBase 标准过滤表达式。
- 查全部列表时:不传
- 按业务条件筛选时,同时写两个 `contains` 条件
- 第二个条件建议带上 `@` 前缀,避免误命中
- 例如:`document_type ~ "DICT-1774599144591-hAEFQj" && document_type ~ "@UT1"`
schema:
type: string
example: document_type ~ "DICT-1774599144591-hAEFQj" && document_type ~ "@UT1"
- 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 原生排序表达式。
当前线上建议使用:
- `-document_create`:按最新上传倒序返回
schema:
type: string
example: -document_create
responses:
'200':
description: 查询成功
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseDocumentListResponse'
examples:
listAll:
value:
page: 页码 | integer
perPage: 每页条数 | integer
totalItems: 总记录数 | integer
totalPages: 总页数 | integer
items:
- id: PocketBase记录主键 | string
collectionId: 集合ID | string
collectionName: 集合名称 | string
created: 记录创建时间 | string
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: 0
document_download_count: 0
document_favorite_count: 0
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
perPage: 每页条数 | integer
totalItems: 总记录数 | integer
totalPages: 总页数 | integer
items:
- id: PocketBase记录主键 | string
collectionId: 集合ID | string
collectionName: 集合名称 | string
created: 记录创建时间 | string
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: 0
document_download_count: 0
document_favorite_count: 0
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
created: '2026-03-28 07:20:00.000Z'
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:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'403':
description: 集合规则被锁定或服务端权限设置异常
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'500':
description: 服务端错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'

View File

@@ -0,0 +1,334 @@
orderList:
post:
operationId: postOrderList
tags:
- 订单
summary: 按索引字段模糊查询订单列表
description: |
调用自定义 hooks API 查询当前登录用户的订单列表。
查询规则:
- 仅允许查询当前 `Authorization` 对应用户自己的订单记录
- 支持通过 `keyword` 对多个索引相关字段做统一模糊搜索,当前实现覆盖:
- `order_id`
- `order_number`
- `order_source_id`
- 支持按 `order_status` 精确过滤
- 支持按 `order_source` 精确过滤
目标软删除契约:
- 目标行为应仅返回 `is_delete = 0` 的记录
- 当前仓库实现尚未显式追加 `is_delete = 0` 过滤,请以实际后端代码为准
security:
- BearerAuth: []
requestBody:
required: false
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/OrderListRequest'
responses:
'200':
description: 查询成功
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/OrderListResponse'
'400':
description: 参数错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'401':
description: token 缺失、无效或已过期
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'415':
description: 请求体必须为 application/json
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'500':
description: 服务端错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
orderDetail:
post:
operationId: postOrderDetail
tags:
- 订单
summary: 按 order_id 精确查询订单详情
description: |
调用自定义 hooks API 按 `order_id` 查询单条订单记录。
查询规则:
- 仅允许访问当前 `Authorization` 对应用户自己的订单记录
- 查询键为业务 ID `order_id`,不是 PocketBase 原生 `recordId`
目标软删除契约:
- 目标行为应仅允许查询 `is_delete = 0` 的记录
- 当前仓库实现尚未显式追加 `is_delete = 0` 过滤,请以实际后端代码为准
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/OrderDetailRequest'
responses:
'200':
description: 查询成功
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/OrderRecord'
'400':
description: 参数错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'401':
description: token 缺失、无效或已过期
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'403':
description: 无权访问该订单记录
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'404':
description: 未找到对应订单记录
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'415':
description: 请求体必须为 application/json
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'500':
description: 服务端错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
orderCreate:
post:
operationId: postOrderCreate
tags:
- 订单
summary: 新增订单记录
description: |
调用自定义 hooks API 新增一条订单记录。
创建规则:
- 服务端自动根据当前 token 写入 `order_owner`
- 服务端自动生成 `order_id`
- 若未传 `order_number`,服务端会自动生成展示编号
- `order_source`、`order_source_id`、`order_snap`、`order_amount` 为必填
目标软删除契约:
- 新建记录应默认为 `is_delete = 0`
- 当前仓库导出响应中尚未显式返回 `is_delete` 字段
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/OrderCreateRequest'
responses:
'200':
description: 创建成功
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/OrderRecord'
'400':
description: 参数错误或创建失败
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'401':
description: token 缺失、无效或已过期
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'415':
description: 请求体必须为 application/json
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'429':
description: 重复请求过于频繁
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'500':
description: 服务端错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
orderUpdate:
post:
operationId: postOrderUpdate
tags:
- 订单
summary: 修改订单记录
description: |
调用自定义 hooks API 按 `order_id` 更新订单记录。
更新规则:
- 仅允许修改当前 `Authorization` 对应用户自己的订单记录
- `order_id` 为必填,用于精确定位目标记录
- 其余字段均为可选增量更新
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/OrderUpdateRequest'
responses:
'200':
description: 更新成功
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/OrderRecord'
'400':
description: 参数错误或更新失败
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'401':
description: token 缺失、无效或已过期
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'403':
description: 无权访问该订单记录
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'404':
description: 未找到待修改的订单记录
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'415':
description: 请求体必须为 application/json
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'429':
description: 重复请求过于频繁
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'500':
description: 服务端错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
orderDelete:
post:
operationId: postOrderDelete
tags:
- 订单
summary: 软删除订单记录
description: |
目标契约:按 `order_id` 软删除订单记录,将 `is_delete` 标记为 `1`,而不是物理删除。
当前仓库实现差异:
- 当前 `cartOrderService.deleteOrder()` 仍直接调用 `$app.delete(record)` 做物理删除
- 因此当前后端实现与本软删除契约不一致
- 若要严格按本文档执行,需先同步调整后端服务实现,并在列表/详情接口中补充 `is_delete = 0` 过滤
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/OrderDeleteRequest'
responses:
'200':
description: 删除成功
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/OrderDeleteResponse'
'400':
description: 参数错误或删除失败
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'401':
description: token 缺失、无效或已过期
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'403':
description: 无权访问该订单记录
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'404':
description: 未找到待删除的订单记录
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'415':
description: 请求体必须为 application/json
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'429':
description: 重复请求过于频繁
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'500':
description: 服务端错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'

View File

@@ -0,0 +1,241 @@
orderRecords:
get:
operationId: getPocketBaseOrderRecords
tags:
- 订单
summary: 查询当前用户订单列表或按业务 ID 精确查询
description: |
使用 PocketBase 原生 records list 接口查询 `tbl_order`。
当前线上权限规则:
- `listRule = @request.auth.id != "" && order_owner = @request.auth.openid && is_delete = 0`
- 因此调用方只能读到 `order_owner` 等于自己 `openid` 且未软删除的记录
标准调用方式:
1. 查询当前登录用户全部订单:
- 不传 `filter`
- 可选传 `sort=-order_create`
2. 按业务 ID 精确查单条:
- `filter=order_id="ORDER-..."`
- `perPage=1`
- `page=1`
security:
- BearerAuth: []
parameters:
- name: filter
in: query
required: false
schema:
type: string
example: order_id="ORDER-1770000000000-abc123"
- name: page
in: query
required: false
schema:
type: integer
minimum: 1
default: 1
- name: perPage
in: query
required: false
schema:
type: integer
minimum: 1
default: 20
- name: sort
in: query
required: false
description: PocketBase 原生排序表达式,推荐 `-order_create`
schema:
type: string
example: -order_create
responses:
'200':
description: 查询成功
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseOrderListResponse'
'400':
description: 查询参数错误或不满足 listRule
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'401':
description: token 缺失、无效或已过期
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'403':
description: 集合规则被锁定或服务端权限设置异常
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'500':
description: 服务端错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
post:
operationId: postPocketBaseOrderRecord
tags:
- 订单
summary: 创建订单记录
description: |
使用 PocketBase 原生 records create 接口向 `tbl_order` 新增记录。
当前线上权限规则:
- `createRule = @request.auth.id != "" && @request.body.order_owner = @request.auth.openid`
- 因此客户端创建时必须显式提交 `order_owner`,并且值必须等于当前 token 对应的 `openid`
这意味着:
- 不能依赖服务端自动补 owner
- 不能省略 `order_owner`
- 不满足规则时 PocketBase 会直接返回 `400`
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseOrderCreateRequest'
responses:
'200':
description: 创建成功
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseOrderRecord'
'400':
description: 参数错误、违反字段约束或不满足 createRule
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'401':
description: token 缺失、无效或已过期
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'403':
description: 集合规则被锁定或服务端权限设置异常
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'500':
description: 服务端错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
orderRecordById:
patch:
operationId: patchPocketBaseOrderRecordByRecordId
tags:
- 订单
summary: 更新订单记录
description: |
使用 PocketBase 原生 records update 接口更新 `tbl_order`。
标准调用流程:
1. 先通过 `GET /pb/api/collections/tbl_order/records?filter=order_id="..."&perPage=1&page=1` 找到原生 `recordId`
2. 再调用当前 `PATCH /pb/api/collections/tbl_order/records/{recordId}`
当前线上权限规则:
- `updateRule = @request.auth.id != "" && order_owner = @request.auth.openid`
- 调用方只能修改自己的订单记录
security:
- BearerAuth: []
parameters:
- name: recordId
in: path
required: true
schema:
type: string
example: l2r3nq7rqhuob0h
requestBody:
required: true
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseOrderUpdateRequest'
responses:
'200':
description: 更新成功
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseOrderRecord'
'400':
description: 参数错误或违反字段约束
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'401':
description: token 缺失、无效或已过期
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'404':
description: 记录不存在或不满足 updateRule
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'500':
description: 服务端错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
delete:
operationId: deletePocketBaseOrderRecordByRecordId
tags:
- 订单
summary: 删除订单记录
description: |
使用 PocketBase 原生 records delete 接口删除 `tbl_order`。
当前线上权限规则:
- `deleteRule = @request.auth.id != "" && order_owner = @request.auth.openid`
- 调用方只能删除自己的订单记录
security:
- BearerAuth: []
parameters:
- name: recordId
in: path
required: true
schema:
type: string
example: l2r3nq7rqhuob0h
responses:
'204':
description: 删除成功
'401':
description: token 缺失、无效或已过期
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'404':
description: 记录不存在或不满足 deleteRule
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'500':
description: 服务端错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'

View File

@@ -0,0 +1,119 @@
productRecords:
get:
operationId: getPocketBaseProductListRecords
tags:
- 产品信息
summary: 根据产品分类精确筛选并按分类排序值升序返回产品列表
description: |
使用 PocketBase 原生 records list 接口查询 `tbl_product_list`。
当前接口约定:
- 默认仅返回 `is_delete = 0` 的未软删除产品
- 条件:按 `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: '../../openapi-wx.yaml#/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:
- name: <属性名>|<string>
value: <属性值>|<string>
prod_list_plantype: <产品方案>|<string>
prod_list_category: <产品分类>|<string>
prod_list_sort: 10
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: 1999
prod_list_vip_price:
- viplevel: VIP1
price: 1899
prod_list_remark: <备注>|<string>
'400':
description: 查询参数错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'403':
description: 集合规则被锁定或服务端权限设置异常
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'
'500':
description: 服务端错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseNativeError'

View File

@@ -0,0 +1,89 @@
usersCount:
post:
security: []
operationId: postSystemUsersCount
tags:
- 系统
summary: 查询用户总数
description: 统计 `tbl_auth_users` 集合中的记录总数。
responses:
'200':
description: 查询成功
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/UsersCountResponse'
'400':
description: 请求参数错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'500':
description: 服务端错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
refreshToken:
post:
security:
- BearerAuth: []
- {}
operationId: postSystemRefreshToken
tags:
- 系统
summary: 刷新认证 token
description: |
当当前 `Authorization` 仍有效时,直接基于当前 auth 用户续签。
当 token 失效时,可传入 `users_wx_code` 走微信 code 重新签发。
requestBody:
required: false
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/SystemRefreshTokenRequest'
responses:
'200':
description: 刷新成功
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/RefreshTokenResponse'
'400':
description: 参数错误或微信 code 换取失败
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'401':
description: token 无效,且未提供有效的 `users_wx_code`
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'404':
description: 当前用户不存在
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'415':
description: 请求体不是 JSON
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'429':
description: 请求过于频繁
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'500':
description: 服务端错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'

View File

@@ -0,0 +1,117 @@
wechatLogin:
post:
security: []
operationId: postWechatLogin
tags:
- 微信认证
summary: 微信登录或首次注册
description: |
使用微信 `users_wx_code` 换取微信 openid。
若 `tbl_auth_users` 中不存在对应用户,则自动创建新 auth 用户并返回 token。
首次注册时,`users_level` 默认保持为空,不自动写入会员等级。
requestBody:
required: true
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/WechatLoginRequest'
responses:
'200':
description: 登录或注册成功
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/AuthSuccessResponse'
'400':
description: 参数错误或保存用户失败
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'401':
description: 认证失败
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'415':
description: 请求体不是 JSON
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'429':
description: 请求过于频繁
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'500':
description: 服务端错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
wechatProfile:
post:
security:
- BearerAuth: []
operationId: postWechatProfile
tags:
- 微信认证
summary: 更新微信用户资料
description: |
基于当前 `Authorization` 对应的 auth 用户按“非空字段增量更新”资料。
更新规则:
- 所有字段都不是必填
- 如果传了 `users_phone_code`,服务端优先调用微信接口换取真实手机号并写入 `users_phone`
- 如果没传 `users_phone_code`,但传了 `users_phone`,则直接将该手机号写入数据库
- 如果上传了 `users_picture`、`users_id_pic_a`、`users_id_pic_b`、`users_title_picture`,会按附件 ID 进行关联校验并更新
- 若当前用户类型为 `游客`,且本次未显式传 `users_type`,服务端会自动升级为 `注册用户`
- 如果某个字段未传或传空,则不会清空数据库中的已有值
- 只有请求体里非空的字段才会更新到数据库
requestBody:
required: true
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/WechatProfileRequest'
responses:
'200':
description: 更新成功
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/WechatProfileResponse'
'400':
description: 参数错误、手机号已被占用、附件 ID 无效、微信手机号换取失败或资料更新失败
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'401':
description: token 无效或缺少 openid
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'415':
description: 请求体不是 JSON
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'429':
description: 请求过于频繁
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'
'500':
description: 服务端错误
content:
application/json:
schema:
$ref: '../../openapi-wx.yaml#/components/schemas/ErrorResponse'

View File

@@ -0,0 +1,125 @@
PocketBaseAttachmentRecord:
type: object
properties:
id:
type: string
description: PocketBase 记录主键
example: PocketBase记录主键 | string
collectionId:
type: string
description: 集合ID
example: 集合ID | string
collectionName:
type: string
description: 集合名称
example: 集合名称 | string
attachments_id:
type: string
description: 附件业务 ID
example: 附件业务ID | string
attachments_link:
type: string
description: PocketBase 文件字段值,可按标准文件路径拼接文件流地址
example: PocketBase文件字段值可拼接文件流地址 | string
attachments_filename:
type: string
description: 原始文件名
example: 原始文件名 | string
attachments_filetype:
type: string
description: 文件类型 / MIME
example: 文件类型或MIME | string
attachments_size:
type:
- number
- integer
- string
description: 文件大小
example: 文件大小 | number
attachments_owner:
type: string
description: 上传者业务标识
example: 上传者业务标识 | string
attachments_md5:
type: string
description: 文件 MD5
example: 文件MD5 | string
attachments_ocr:
type: string
description: OCR 识别结果
example: OCR识别结果 | string
attachments_status:
type: string
description: 附件状态
example: 附件状态 | string
attachments_remark:
type: string
description: 备注
example: 备注 | string
example:
id: PocketBase记录主键 | string
collectionId: 集合ID | string
collectionName: 集合名称 | string
attachments_id: 附件业务ID | string
attachments_link: PocketBase文件字段值可拼接文件流地址 | string
attachments_filename: 原始文件名 | string
attachments_filetype: 文件类型或MIME | string
attachments_size: 文件大小 | number
attachments_owner: 上传者业务标识 | string
attachments_md5: 文件MD5 | string
attachments_ocr: OCR识别结果 | string
attachments_status: 附件状态 | string
attachments_remark: 备注 | string
PocketBaseAttachmentListResponse:
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: '../../openapi-wx.yaml#/components/schemas/PocketBaseAttachmentRecord'
example:
page: 页码 | integer
perPage: 每页条数 | integer
totalItems: 总记录数 | integer
totalPages: 总页数 | integer
items:
- id: PocketBase记录主键 | string
collectionId: 集合ID | string
collectionName: 集合名称 | string
attachments_id: 附件业务ID | string
attachments_link: PocketBase文件字段值可拼接文件流地址 | string
attachments_filename: 原始文件名 | string
attachments_filetype: 文件类型或MIME | string
attachments_size: 文件大小 | number
attachments_owner: 上传者业务标识 | string
attachments_md5: 文件MD5 | string
attachments_ocr: OCR识别结果 | string
attachments_status: 附件状态 | string
attachments_remark: 备注 | string

View File

@@ -0,0 +1,185 @@
CartRecord:
allOf:
- $ref: '../../openapi-wx.yaml#/components/schemas/HookRecordBase'
- type: object
properties:
cart_id:
type: string
description: 购物车业务 ID
example: CART-1770000000000-abc123
cart_number:
type: string
description: 购物车名称或分组号
example: wx-user-20260403153000
cart_create:
type: string
description: 购物车项创建时间,由数据库自动生成
example: 2026-04-03 15:30:00.000Z
cart_owner:
type: string
description: 购物车所有者 openid
example: wx-openid-user-001
cart_product_id:
type: string
description: 产品业务 ID
example: PROD-1770000000000-abcd12
cart_product_quantity:
type:
- integer
- number
description: 产品数量
example: 2
cart_status:
type: string
description: 购物车状态
example: 有效
cart_at_price:
type:
- integer
- number
description: 加入购物车时价格
example: 1999
cart_remark:
type: string
description: 备注
example: 小程序加入购物车示例
is_delete:
type:
- integer
- number
description: 软删除标记;目标契约字段,当前 hooks 响应可能尚未显式透出
example: 0
product_name:
type: string
description: 产品名称(服务端联动补充)
example: BAI 智能主机
product_modelnumber:
type: string
description: 产品型号(服务端联动补充)
example: BAI-HOST-01
product_basic_price:
type:
- integer
- number
- 'null'
description: 产品基础价格(服务端联动补充)
example: 1999
CartListRequest:
type: object
properties:
keyword:
type: string
description: 对 `cart_id`、`cart_number`、`cart_product_id` 等索引相关字段的统一模糊搜索关键字
example: CART-1770
cart_status:
type: string
description: 购物车状态精确过滤
example: 有效
cart_number:
type: string
description: 购物车名称或分组号精确过滤
example: wx-user-20260403153000
CartDetailRequest:
type: object
required:
- cart_id
properties:
cart_id:
type: string
description: 购物车业务 ID
example: CART-1770000000000-abc123
CartCreateRequest:
type: object
required:
- cart_product_id
- cart_product_quantity
- cart_at_price
properties:
cart_number:
type: string
description: 可选;未传时服务端自动生成
cart_product_id:
type: string
description: 产品业务 ID
example: PROD-1770000000000-abcd12
cart_product_quantity:
type:
- integer
- number
description: 产品数量,需为正整数
example: 2
cart_status:
type: string
description: 可选;未传时默认 `有效`
example: 有效
cart_at_price:
type:
- integer
- number
description: 加入购物车时价格
example: 1999
cart_remark:
type: string
description: 备注
example: 小程序加入购物车示例
CartUpdateRequest:
type: object
required:
- cart_id
properties:
cart_id:
type: string
description: 购物车业务 ID
example: CART-1770000000000-abc123
cart_number:
type: string
cart_product_id:
type: string
cart_product_quantity:
type:
- integer
- number
cart_status:
type: string
cart_at_price:
type:
- integer
- number
cart_remark:
type: string
CartDeleteRequest:
type: object
required:
- cart_id
properties:
cart_id:
type: string
description: 购物车业务 ID
example: CART-1770000000000-abc123
CartListResponse:
type: object
properties:
items:
type: array
items:
$ref: '../../openapi-wx.yaml#/components/schemas/CartRecord'
CartDeleteResponse:
type: object
properties:
cart_id:
type: string
description: 购物车业务 ID
example: CART-1770000000000-abc123
is_delete:
type:
- integer
- number
description: 目标软删除标记值;当前实现可能仍返回物理删除结果
example: 1

View File

@@ -0,0 +1,134 @@
PocketBaseCartFields:
type: object
properties:
cart_id:
type: string
description: 购物车业务 ID
example: CART-1770000000000-abc123
cart_number:
type: string
description: 购物车名称或分组号
example: wx-user-20260403153000
cart_create:
type: string
description: 购物车项创建时间,由数据库自动生成
example: 2026-04-03 15:30:00.000Z
cart_owner:
type: string
description: 购物车所有者 openid
example: wx-openid-user-001
cart_product_id:
type: string
description: 产品业务 ID
example: PROD-1770000000000-abcd12
cart_product_quantity:
type:
- integer
- number
description: 产品数量
example: 2
cart_status:
type: string
description: 购物车状态
example: 有效
cart_at_price:
type:
- number
- integer
description: 加入购物车时价格
example: 1999
cart_remark:
type: string
description: 备注
example: 小程序加入购物车示例
PocketBaseCartRecord:
allOf:
- $ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseRecordBase'
- $ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseCartFields'
PocketBaseCartCreateRequest:
type: object
required:
- cart_id
- cart_number
- cart_owner
- cart_product_id
- cart_product_quantity
- cart_status
- cart_at_price
properties:
cart_id:
type: string
cart_number:
type: string
cart_owner:
type: string
description: 必须显式提交,且值必须等于当前 token 对应 openid
cart_product_id:
type: string
cart_product_quantity:
type:
- integer
- number
cart_status:
type: string
cart_at_price:
type:
- number
- integer
cart_remark:
type: string
PocketBaseCartUpdateRequest:
type: object
properties:
cart_number:
type: string
cart_owner:
type: string
description: 若提交,必须仍等于当前 token 对应 openid
cart_product_id:
type: string
cart_product_quantity:
type:
- integer
- number
cart_status:
type: string
cart_at_price:
type:
- number
- integer
cart_remark:
type: string
PocketBaseCartListResponse:
type: object
required:
- page
- perPage
- totalItems
- totalPages
- items
properties:
page:
type:
- integer
- string
perPage:
type:
- integer
- string
totalItems:
type:
- integer
- string
totalPages:
type:
- integer
- string
items:
type: array
items:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseCartRecord'

View File

@@ -0,0 +1,125 @@
ApiResponseBase:
type: object
required:
- statusCode
- errMsg
- data
properties:
statusCode:
type:
- integer
- string
description: "业务状态码"
example: 业务状态码 | integer
errMsg:
type: string
description: "业务提示信息"
example: 业务提示信息 | string
data:
description: "业务响应数据"
type: object
additionalProperties: true
example:
statusCode: 业务状态码 | integer
errMsg: 业务提示信息 | string
data:
任意业务字段: 业务响应数据 | object
ErrorResponse:
type: object
required:
- statusCode
- errMsg
- data
properties:
statusCode:
type:
- integer
- string
description: "业务状态码"
example: 业务状态码 | integer
errMsg:
type: string
description: "业务提示信息"
example: 失败原因提示 | string
data:
description: "业务响应数据"
type: object
additionalProperties: true
example:
statusCode: 业务状态码 | integer
errMsg: 失败原因提示 | string
data:
任意错误字段: 错误附加信息 | object
HookRecordBase:
type: object
properties:
pb_id:
type: string
description: PocketBase 记录主键 id
example: l2r3nq7rqhuob0h
created:
type: string
description: PocketBase 系统创建时间
example: 2026-04-03 15:30:00.000Z
updated:
type: string
description: PocketBase 系统更新时间
example: 2026-04-03 15:35:00.000Z
PocketBaseNativeError:
type: object
properties:
code:
type:
- integer
- string
description: "业务状态码"
example: 错误状态码 | integer
message:
type: string
example: PocketBase原生错误信息 | string
data:
description: "业务响应数据"
type: object
additionalProperties: true
example:
code: 错误状态码 | integer
message: PocketBase原生错误信息 | string
data:
任意错误字段: 原生错误附加信息 | object
PocketBaseRecordBase:
type: object
required:
- id
- collectionId
- collectionName
- created
- updated
properties:
id:
type: string
description: "PocketBase 记录主键"
example: PocketBase记录主键 | string
collectionId:
type: string
example: 集合ID | string
collectionName:
type: string
example: 集合名称 | string
created:
type: string
description: "记录创建时间"
example: 记录创建时间 | string
updated:
type: string
description: "记录更新时间"
example: 记录更新时间 | string
example:
id: PocketBase记录主键 | string
collectionId: 集合ID | string
collectionName: 集合名称 | string
created: 记录创建时间 | string
updated: 记录更新时间 | string

View File

@@ -0,0 +1,412 @@
CompanyInfo:
anyOf:
- type: object
description: 用户所属公司信息;当用户尚未绑定公司时返回 `null`
properties:
pb_id:
type: string
description: PocketBase 记录主键 id
example: PocketBase记录主键id | string
company_id:
type: string
description: 公司业务 id由数据库自动生成
example: 公司业务id由数据库自动生成 | string
company_name:
type: string
description: 公司名称
example: 公司名称 | string
company_type:
type: string
description: 公司类型
example: 公司类型 | string
company_entity:
type: string
description: 公司法人
example: 公司法人 | string
company_usci:
type: string
description: 统一社会信用代码
example: 统一社会信用代码 | string
company_nationality:
type: string
description: 国家名称
example: 国家名称 | string
company_nationality_code:
type: string
description: 国家编码
example: 国家编码 | string
company_province:
type: string
description: 省份名称
example: 省份名称 | string
company_province_code:
type: string
description: 省份编码
example: 省份编码 | string
company_city:
type: string
description: 城市名称
example: 城市名称 | string
company_city_code:
type: string
description: 城市编码
example: 城市编码 | string
company_district:
type: string
description: 区/县名称
example: 区县名称 | string
company_district_code:
type: string
description: 区/县编码
example: 区县编码 | string
company_postalcode:
type: string
description: 邮政编码
example: 邮政编码 | string
company_add:
type: string
description: 公司地址
example: 公司地址 | string
company_status:
type: string
description: 公司状态
example: 公司状态 | string
company_level:
type: string
description: 公司等级
example: 公司等级 | string
company_owner_openid:
type: string
description: 公司所有者 openid
example: 公司所有者openid | string
company_remark:
type: string
description: 备注
example: 备注 | string
created:
type: string
description: 记录创建时间
example: 记录创建时间 | string
updated:
type: string
description: 记录更新时间
example: 记录更新时间 | string
example:
pb_id: PocketBase记录主键id | string
company_id: 公司业务id由数据库自动生成 | string
company_name: 公司名称 | string
company_type: 公司类型 | string
company_entity: 公司法人 | string
company_usci: 统一社会信用代码 | string
company_nationality: 国家名称 | string
company_nationality_code: 国家编码 | string
company_province: 省份名称 | string
company_province_code: 省份编码 | string
company_city: 城市名称 | string
company_city_code: 城市编码 | string
company_district: 区县名称 | string
company_district_code: 区县编码 | string
company_postalcode: 邮政编码 | string
company_add: 公司地址 | string
company_status: 公司状态 | string
company_level: 公司等级 | string
company_owner_openid: 公司所有者openid | string
company_remark: 备注 | string
created: 记录创建时间 | string
updated: 记录更新时间 | string
- type: 'null'
PocketBaseCompanyFields:
type: object
properties:
company_id:
type: string
description: 公司业务 id由数据库自动生成
example: 公司业务id由数据库自动生成 | string
company_name:
type: string
description: 公司名称
example: 公司名称 | string
company_type:
type: string
description: 公司类型
example: 公司类型 | string
company_entity:
type: string
description: 公司法人
example: 公司法人 | string
company_usci:
type: string
description: 统一社会信用代码
example: 统一社会信用代码 | string
company_nationality:
type: string
description: 国家名称
example: 国家名称 | string
company_nationality_code:
type: string
description: 国家编码
example: 国家编码 | string
company_province:
type: string
description: 省份名称
example: 省份名称 | string
company_province_code:
type: string
description: 省份编码
example: 省份编码 | string
company_city:
type: string
description: 城市名称
example: 城市名称 | string
company_city_code:
type: string
description: 城市编码
example: 城市编码 | string
company_district:
type: string
description: 区/县名称
example: 区县名称 | string
company_district_code:
type: string
description: 区/县编码
example: 区县编码 | string
company_postalcode:
type: string
description: 邮政编码
example: 邮政编码 | string
company_add:
type: string
description: 公司地址
example: 公司地址 | string
company_status:
type: string
description: 公司状态
example: 公司状态 | string
company_level:
type: string
description: 公司等级
example: 公司等级 | string
company_owner_openid:
type: string
description: 公司所有者 openid
example: 公司所有者openid | string
company_remark:
type: string
description: 备注
example: 备注 | string
PocketBaseCompanyRecord:
allOf:
- $ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseRecordBase'
- $ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseCompanyFields'
example:
id: PocketBase记录主键 | string
collectionId: 集合ID | string
collectionName: 集合名称 | string
created: 记录创建时间 | string
updated: 记录更新时间 | string
company_id: 公司业务id由数据库自动生成 | string
company_name: 公司名称 | string
company_type: 公司类型 | string
company_entity: 公司法人 | string
company_usci: 统一社会信用代码 | string
company_nationality: 国家名称 | string
company_nationality_code: 国家编码 | string
company_province: 省份名称 | string
company_province_code: 省份编码 | string
company_city: 城市名称 | string
company_city_code: 城市编码 | string
company_district: 区县名称 | string
company_district_code: 区县编码 | string
company_postalcode: 邮政编码 | string
company_add: 公司地址 | string
company_status: 公司状态 | string
company_level: 公司等级 | string
company_owner_openid: 公司所有者openid | string
company_remark: 备注 | string
PocketBaseCompanyCreateRequest:
type: object
properties:
company_name:
description: "公司名称"
type: string
company_type:
description: "公司类型"
type: string
company_entity:
description: "公司法人"
type: string
company_usci:
description: "统一社会信用代码"
type: string
company_nationality:
description: "国家名称"
type: string
company_nationality_code:
description: "国家编码"
type: string
company_province:
description: "省份名称"
type: string
company_province_code:
description: "省份编码"
type: string
company_city:
description: "城市名称"
type: string
company_city_code:
description: "城市编码"
type: string
company_district:
description: "区 / 县名称"
type: string
company_district_code:
description: "区 / 县编码"
type: string
company_postalcode:
description: "邮编"
type: string
company_add:
description: "地址"
type: string
company_status:
description: "公司状态"
type: string
company_level:
description: "公司等级"
type: string
company_owner_openid:
description: "公司所有者 openid"
type: string
company_remark:
description: "备注"
type: string
additionalProperties: false
PocketBaseCompanyUpdateRequest:
type: object
properties:
company_id:
description: "所属公司业务 ID"
type: string
company_name:
description: "公司名称"
type: string
company_type:
description: "公司类型"
type: string
company_entity:
description: "公司法人"
type: string
company_usci:
description: "统一社会信用代码"
type: string
company_nationality:
description: "国家名称"
type: string
company_nationality_code:
description: "国家编码"
type: string
company_province:
description: "省份名称"
type: string
company_province_code:
description: "省份编码"
type: string
company_city:
description: "城市名称"
type: string
company_city_code:
description: "城市编码"
type: string
company_district:
description: "区 / 县名称"
type: string
company_district_code:
description: "区 / 县编码"
type: string
company_postalcode:
description: "邮编"
type: string
company_add:
description: "地址"
type: string
company_status:
description: "公司状态"
type: string
company_level:
description: "公司等级"
type: string
company_owner_openid:
description: "公司所有者 openid"
type: string
company_remark:
description: "备注"
type: string
additionalProperties: false
PocketBaseCompanyListResponse:
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: '../../openapi-wx.yaml#/components/schemas/PocketBaseCompanyRecord'
example:
page: 页码 | integer
perPage: 每页条数 | integer
totalItems: 总记录数 | integer
totalPages: 总页数 | integer
items:
- id: PocketBase记录主键 | string
collectionId: 集合ID | string
collectionName: 集合名称 | string
created: 记录创建时间 | string
updated: 记录更新时间 | string
company_id: 公司业务id由数据库自动生成 | string
company_name: 公司名称 | string
company_type: 公司类型 | string
company_entity: 公司法人 | string
company_usci: 统一社会信用代码 | string
company_nationality: 国家名称 | string
company_nationality_code: 国家编码 | string
company_province: 省份名称 | string
company_province_code: 省份编码 | string
company_city: 城市名称 | string
company_city_code: 城市编码 | string
company_district: 区县名称 | string
company_district_code: 区县编码 | string
company_postalcode: 邮政编码 | string
company_add: 公司地址 | string
company_status: 公司状态 | string
company_level: 公司等级 | string
company_owner_openid: 公司所有者openid | string
company_remark: 备注 | string

View File

@@ -0,0 +1,221 @@
PocketBaseDocumentFields:
type: object
properties:
document_id:
type: string
description: "文档业务 ID"
example: 文档业务ID | string
document_create:
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: "文档状态,仅 `有效` / `过期`"
example: 文档状态 | string
document_owner:
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: 0
document_download_count:
type: number
description: "下载次数"
example: 0
document_favorite_count:
type: number
description: "收藏次数"
example: 0
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: '../../openapi-wx.yaml#/components/schemas/PocketBaseRecordBase'
- $ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseDocumentFields'
example:
id: PocketBase记录主键 | string
collectionId: 集合ID | string
collectionName: 集合名称 | string
created: 记录创建时间 | string
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: 0
document_download_count: 0
document_favorite_count: 0
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:
- 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: '../../openapi-wx.yaml#/components/schemas/PocketBaseDocumentRecord'
example:
page: 页码 | integer
perPage: 每页条数 | integer
totalItems: 总记录数 | integer
totalPages: 总页数 | integer
items:
- id: PocketBase记录主键 | string
collectionId: 集合ID | string
collectionName: 集合名称 | string
created: 记录创建时间 | string
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: 0
document_download_count: 0
document_favorite_count: 0
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

View File

@@ -0,0 +1,191 @@
OrderRecord:
allOf:
- $ref: '../../openapi-wx.yaml#/components/schemas/HookRecordBase'
- type: object
properties:
order_id:
type: string
description: 订单业务 ID
example: ORDER-1770000000000-abc123
order_number:
type: string
description: 订单编号
example: wx-user-20260403153500
order_create:
type: string
description: 订单创建时间,由数据库自动生成
example: 2026-04-03 15:35:00.000Z
order_owner:
type: string
description: 订单所有者 openid
example: wx-openid-user-001
order_source:
type: string
description: 订单来源
example: 购物车
order_status:
type: string
description: 订单状态
example: 订单已生成
order_source_id:
type: string
description: 来源关联业务 ID
example: CART-1770000000000-abc123
order_snap:
description: 订单快照 JSON
oneOf:
- type: object
additionalProperties: true
- type: array
items:
type: object
additionalProperties: true
order_amount:
type:
- integer
- number
description: 订单金额
example: 3998
order_remark:
type: string
description: 备注
example: 小程序订单示例
is_delete:
type:
- integer
- number
description: 软删除标记;目标契约字段,当前 hooks 响应可能尚未显式透出
example: 0
OrderListRequest:
type: object
properties:
keyword:
type: string
description: 对 `order_id`、`order_number`、`order_source_id` 等索引相关字段的统一模糊搜索关键字
example: ORDER-1770
order_status:
type: string
description: 订单状态精确过滤
example: 订单已生成
order_source:
type: string
description: 订单来源精确过滤
example: 购物车
OrderDetailRequest:
type: object
required:
- order_id
properties:
order_id:
type: string
description: 订单业务 ID
example: ORDER-1770000000000-abc123
OrderCreateRequest:
type: object
required:
- order_source
- order_source_id
- order_snap
- order_amount
properties:
order_number:
type: string
description: 可选;未传时服务端自动生成
order_source:
type: string
description: 订单来源
example: 购物车
order_status:
type: string
description: 可选;未传时默认 `订单已生成`
example: 订单已生成
order_source_id:
type: string
description: 来源关联业务 ID
example: CART-1770000000000-abc123
order_snap:
description: 订单快照 JSON
oneOf:
- type: object
additionalProperties: true
- type: array
items:
type: object
additionalProperties: true
order_amount:
type:
- integer
- number
description: 订单金额
example: 3998
order_remark:
type: string
description: 备注
example: 小程序订单示例
OrderUpdateRequest:
type: object
required:
- order_id
properties:
order_id:
type: string
description: 订单业务 ID
example: ORDER-1770000000000-abc123
order_number:
type: string
order_source:
type: string
order_status:
type: string
order_source_id:
type: string
order_snap:
oneOf:
- type: object
additionalProperties: true
- type: array
items:
type: object
additionalProperties: true
order_amount:
type:
- integer
- number
order_remark:
type: string
OrderDeleteRequest:
type: object
required:
- order_id
properties:
order_id:
type: string
description: 订单业务 ID
example: ORDER-1770000000000-abc123
OrderListResponse:
type: object
properties:
items:
type: array
items:
$ref: '../../openapi-wx.yaml#/components/schemas/OrderRecord'
OrderDeleteResponse:
type: object
properties:
order_id:
type: string
description: 订单业务 ID
example: ORDER-1770000000000-abc123
is_delete:
type:
- integer
- number
description: 目标软删除标记值;当前实现可能仍返回物理删除结果
example: 1

View File

@@ -0,0 +1,154 @@
PocketBaseOrderFields:
type: object
properties:
order_id:
type: string
description: 订单业务 ID
example: ORDER-1770000000000-abc123
order_number:
type: string
description: 订单编号
example: wx-user-20260403153500
order_create:
type: string
description: 订单创建时间,由数据库自动生成
example: 2026-04-03 15:35:00.000Z
order_owner:
type: string
description: 订单所有者 openid
example: wx-openid-user-001
order_source:
type: string
description: 订单来源
example: 购物车
order_status:
type: string
description: 订单状态
example: 订单已生成
order_source_id:
type: string
description: 来源关联业务 ID
example: CART-1770000000000-abc123
order_snap:
description: 订单快照 JSON
oneOf:
- type: object
additionalProperties: true
- type: array
items:
type: object
additionalProperties: true
order_amount:
type:
- number
- integer
description: 订单金额
example: 3998
order_remark:
type: string
description: 备注
example: 小程序订单示例
PocketBaseOrderRecord:
allOf:
- $ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseRecordBase'
- $ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseOrderFields'
PocketBaseOrderCreateRequest:
type: object
required:
- order_id
- order_number
- order_owner
- order_source
- order_status
- order_source_id
- order_snap
- order_amount
properties:
order_id:
type: string
order_number:
type: string
order_owner:
type: string
description: 必须显式提交,且值必须等于当前 token 对应 openid
order_source:
type: string
order_status:
type: string
order_source_id:
type: string
order_snap:
oneOf:
- type: object
additionalProperties: true
- type: array
items:
type: object
additionalProperties: true
order_amount:
type:
- number
- integer
order_remark:
type: string
PocketBaseOrderUpdateRequest:
type: object
properties:
order_number:
type: string
order_owner:
type: string
description: 若提交,必须仍等于当前 token 对应 openid
order_source:
type: string
order_status:
type: string
order_source_id:
type: string
order_snap:
oneOf:
- type: object
additionalProperties: true
- type: array
items:
type: object
additionalProperties: true
order_amount:
type:
- number
- integer
order_remark:
type: string
PocketBaseOrderListResponse:
type: object
required:
- page
- perPage
- totalItems
- totalPages
- items
properties:
page:
type:
- integer
- string
perPage:
type:
- integer
- string
totalItems:
type:
- integer
- string
totalPages:
type:
- integer
- string
items:
type: array
items:
$ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseOrderRecord'

View File

@@ -0,0 +1,203 @@
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: array
description: 产品参数数组,每项包含 name/value
items:
type: object
properties:
name:
type: string
example: <属性名>|<string>
value:
type: string
example: <属性值>|<string>
example:
- name: <属性名>|<string>
value: <属性值>|<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: 10
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: 1999
prod_list_vip_price:
type: array
description: 会员价数组,每项包含会员等级枚举值与价格
items:
type: object
properties:
viplevel:
type: string
example: <会员等级枚举值>|<string>
price:
type:
- number
- integer
example: 1899
example:
- viplevel: VIP1
price: 1899
prod_list_remark:
type: string
description: 备注
example: <备注>|<string>
PocketBaseProductListRecord:
allOf:
- $ref: '../../openapi-wx.yaml#/components/schemas/PocketBaseRecordBase'
- $ref: '../../openapi-wx.yaml#/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:
- name: <属性名>|<string>
value: <属性值>|<string>
prod_list_plantype: <产品方案>|<string>
prod_list_category: <产品分类>|<string>
prod_list_sort: 10
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: 1999
prod_list_vip_price:
- viplevel: VIP1
price: 1899
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: '../../openapi-wx.yaml#/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:
- name: <属性名>|<string>
value: <属性值>|<string>
prod_list_plantype: <产品方案>|<string>
prod_list_category: <产品分类>|<string>
prod_list_sort: 10
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: 1999
prod_list_vip_price:
- viplevel: VIP1
price: 1899
prod_list_remark: <备注>|<string>

View File

@@ -0,0 +1,55 @@
SystemRefreshTokenRequest:
type: object
properties:
users_wx_code:
type:
- string
- 'null'
description: |
可选。
当前 token 失效时,可通过该 code 重新签发 token。
example: 0a1b2c3d4e5f6g
RefreshTokenResponse:
allOf:
- $ref: '../../openapi-wx.yaml#/components/schemas/ApiResponseBase'
- type: object
required:
- token
properties:
data:
type: object
additionalProperties: true
description: "业务响应数据"
example: {}
token:
type: string
description: 新签发的 PocketBase 原生 auth token
example:
statusCode: 业务状态码 | integer
errMsg: 刷新成功 | string
data: {}
token: 新签发的PocketBase原生auth token | string
UsersCountData:
type: object
properties:
total_users:
type:
- integer
- string
example: 用户总数 | integer
UsersCountResponse:
allOf:
- $ref: '../../openapi-wx.yaml#/components/schemas/ApiResponseBase'
- type: object
properties:
data:
description: "业务响应数据"
$ref: '../../openapi-wx.yaml#/components/schemas/UsersCountData'
example:
statusCode: 业务状态码 | integer
errMsg: 业务提示信息 | string
data:
total_users: 用户总数 | integer

View File

@@ -0,0 +1,389 @@
UserInfo:
type: object
properties:
pb_id:
description: "PocketBase 记录主键 id"
type: string
users_convers_id:
description: "会话侧用户 ID"
type: string
users_id:
description: "用户业务 ID"
type: string
users_idtype:
description: "身份来源类型或证件类型"
anyOf:
- type: string
enum:
- WeChat
- ManagePlatform
- type: string
users_id_number:
description: "证件号"
type: string
users_type:
description: "用户类型"
anyOf:
- type: string
enum:
- 游客
- 注册用户
- type: string
users_name:
description: "用户姓名 / 昵称"
type: string
users_status:
description: "用户状态"
type:
- string
- number
users_rank_level:
description: "用户星级数值"
type:
- number
- integer
- string
users_auth_type:
description: "账户类型"
type:
- number
- integer
- string
users_phone:
description: "手机号"
type: string
users_phone_masked:
type: string
users_level:
type: string
description: 用户等级
users_level_name:
type: string
description: 用户等级名称,按 `users_level -> 数据-会员等级` 字典描述实时解析
users_tag:
type: string
description: 用户标签
users_picture:
type: string
description: 用户头像附件的 `attachments_id`
users_picture_url:
type: string
description: 根据 `users_picture -> tbl_attachments` 自动解析出的头像文件流链接
users_id_pic_a:
type: string
description: 证件正面附件的 `attachments_id`
users_id_pic_a_url:
type: string
description: 根据 `users_id_pic_a -> tbl_attachments` 自动解析出的文件流链接
users_id_pic_b:
type: string
description: 证件反面附件的 `attachments_id`
users_id_pic_b_url:
type: string
description: 根据 `users_id_pic_b -> tbl_attachments` 自动解析出的文件流链接
users_title_picture:
type: string
description: 资质附件的 `attachments_id`
users_title_picture_url:
type: string
description: 根据 `users_title_picture -> tbl_attachments` 自动解析出的文件流链接
openid:
type: string
description: 全平台统一身份标识
company_id:
type: string
description: 公司业务 id存储 `tbl_company.company_id`
users_parent_id:
type: string
description: 上级用户业务 id
users_promo_code:
type: string
description: 推广码
usergroups_id:
type: string
description: 用户组业务 id
company:
$ref: '../../openapi-wx.yaml#/components/schemas/CompanyInfo'
created:
type: string
description: 用户创建时间
updated:
type: string
description: 用户更新时间
example:
pb_id: PocketBase记录主键id | string
users_convers_id: 会话侧用户ID | string
users_id: 用户业务ID | string
users_idtype: 用户身份来源类型 | string
users_id_number: 证件号 | string
users_type: 用户类型 | string
users_name: 用户姓名或昵称 | string
users_status: 用户状态 | string
users_rank_level: 用户星级数值 | number
users_auth_type: 账户类型 | number
users_phone: 手机号 | string
users_phone_masked: 手机号脱敏值 | string
users_level: 用户等级 | string
users_level_name: 用户等级名称 | string
users_tag: 用户标签 | string
users_picture: 用户头像附件ID | string
users_picture_url: 用户头像文件流链接 | string
users_id_pic_a: 证件正面附件ID | string
users_id_pic_a_url: 证件正面文件流链接 | string
users_id_pic_b: 证件反面附件ID | string
users_id_pic_b_url: 证件反面文件流链接 | string
users_title_picture: 资质附件ID | string
users_title_picture_url: 资质附件文件流链接 | string
openid: 全平台统一身份标识 | string
company_id: 公司业务id | string
users_parent_id: 上级用户业务id | string
users_promo_code: 推广码 | string
usergroups_id: 用户组业务id | string
company:
pb_id: PocketBase记录主键id | string
company_id: 公司业务id由数据库自动生成 | string
company_name: 公司名称 | string
company_type: 公司类型 | string
company_entity: 公司法人 | string
company_usci: 统一社会信用代码 | string
company_nationality: 国家名称 | string
company_nationality_code: 国家编码 | string
company_province: 省份名称 | string
company_province_code: 省份编码 | string
company_city: 城市名称 | string
company_city_code: 城市编码 | string
company_district: 区县名称 | string
company_district_code: 区县编码 | string
company_postalcode: 邮政编码 | string
company_add: 公司地址 | string
company_status: 公司状态 | string
company_level: 公司等级 | string
company_owner_openid: 公司所有者openid | string
company_remark: 备注 | string
created: 记录创建时间 | string
updated: 记录更新时间 | string
created: 用户创建时间 | string
updated: 用户更新时间 | string
WechatLoginRequest:
type: object
required:
- users_wx_code
properties:
users_wx_code:
type: string
description: 微信小程序登录临时凭证 code
example: 0a1b2c3d4e5f6g
WechatProfileRequest:
type: object
properties:
users_name:
type: string
description: "用户姓名 / 昵称"
example: 张三
users_phone_code:
type: string
description: 可选。若传入,服务端优先通过微信接口换取真实手机号并写入数据库
example: 2b7d9f2e3c4a5b6d7e8f
users_phone:
type: string
description: 可选。未传 `users_phone_code` 时,可直接写入手机号
example: '13800138000'
users_type:
type: string
description: 可选。用户类型;仅在传入非空值时更新
example: 服务商
company_id:
type: string
description: 可选。公司业务 id仅在传入非空值时更新
example: WX-COMPANY-10001
users_tag:
type: string
description: 可选。用户标签;非空时才更新
example: 核心客户
users_picture:
type: string
description: 可选。用户头像附件的 `attachments_id`
example: ATT-1743123456789-abc123
users_id_pic_a:
type: string
description: 可选。证件正面附件的 `attachments_id`
users_id_pic_b:
type: string
description: 可选。证件反面附件的 `attachments_id`
users_title_picture:
type: string
description: 可选。资质附件的 `attachments_id`
AuthSuccessData:
type: object
properties:
status:
anyOf:
- type: string
enum:
- register_success
- login_success
- type: string
is_info_complete:
type:
- boolean
- string
user:
$ref: '../../openapi-wx.yaml#/components/schemas/UserInfo'
AuthSuccessResponse:
allOf:
- $ref: '../../openapi-wx.yaml#/components/schemas/ApiResponseBase'
- type: object
required:
- token
properties:
data:
description: "业务响应数据"
$ref: '../../openapi-wx.yaml#/components/schemas/AuthSuccessData'
token:
type: string
description: PocketBase 原生 auth token
example:
statusCode: 业务状态码 | integer
errMsg: 业务提示信息 | string
data:
status: 登录或注册状态 | string
is_info_complete: 资料是否完整 | boolean
user:
pb_id: PocketBase记录主键id | string
users_convers_id: 会话侧用户ID | string
users_id: 用户业务ID | string
users_idtype: 用户身份来源类型 | string
users_id_number: 证件号 | string
users_type: 用户类型 | string
users_name: 用户姓名或昵称 | string
users_status: 用户状态 | string
users_rank_level: 用户星级数值 | number
users_auth_type: 账户类型 | number
users_phone: 手机号 | string
users_phone_masked: 手机号脱敏值 | string
users_level: 用户等级 | string
users_level_name: 用户等级名称 | string
users_tag: 用户标签 | string
users_picture: 用户头像附件ID | string
users_picture_url: 用户头像文件流链接 | string
users_id_pic_a: 证件正面附件ID | string
users_id_pic_a_url: 证件正面文件流链接 | string
users_id_pic_b: 证件反面附件ID | string
users_id_pic_b_url: 证件反面文件流链接 | string
users_title_picture: 资质附件ID | string
users_title_picture_url: 资质附件文件流链接 | string
openid: 全平台统一身份标识 | string
company_id: 公司业务id | string
users_parent_id: 上级用户业务id | string
users_promo_code: 推广码 | string
usergroups_id: 用户组业务id | string
company:
pb_id: PocketBase记录主键id | string
company_id: 公司业务id由数据库自动生成 | string
company_name: 公司名称 | string
company_type: 公司类型 | string
company_entity: 公司法人 | string
company_usci: 统一社会信用代码 | string
company_nationality: 国家名称 | string
company_nationality_code: 国家编码 | string
company_province: 省份名称 | string
company_province_code: 省份编码 | string
company_city: 城市名称 | string
company_city_code: 城市编码 | string
company_district: 区县名称 | string
company_district_code: 区县编码 | string
company_postalcode: 邮政编码 | string
company_add: 公司地址 | string
company_status: 公司状态 | string
company_level: 公司等级 | string
company_owner_openid: 公司所有者openid | string
company_remark: 备注 | string
created: 记录创建时间 | string
updated: 记录更新时间 | string
created: 用户创建时间 | string
updated: 用户更新时间 | string
token: PocketBase原生认证token | string
WechatProfileResponseData:
type: object
properties:
status:
anyOf:
- type: string
enum:
- update_success
- type: string
user:
$ref: '../../openapi-wx.yaml#/components/schemas/UserInfo'
WechatProfileResponse:
allOf:
- $ref: '../../openapi-wx.yaml#/components/schemas/ApiResponseBase'
- type: object
properties:
data:
description: "业务响应数据"
$ref: '../../openapi-wx.yaml#/components/schemas/WechatProfileResponseData'
example:
statusCode: 业务状态码 | integer
errMsg: 业务提示信息 | string
data:
status: 资料更新状态 | string
user:
pb_id: PocketBase记录主键id | string
users_convers_id: 会话侧用户ID | string
users_id: 用户业务ID | string
users_idtype: 用户身份来源类型 | string
users_id_number: 证件号 | string
users_type: 用户类型 | string
users_name: 用户姓名或昵称 | string
users_status: 用户状态 | string
users_rank_level: 用户星级数值 | number
users_auth_type: 账户类型 | number
users_phone: 手机号 | string
users_phone_masked: 手机号脱敏值 | string
users_level: 用户等级 | string
users_level_name: 用户等级名称 | string
users_tag: 用户标签 | string
users_picture: 用户头像附件ID | string
users_picture_url: 用户头像文件流链接 | string
users_id_pic_a: 证件正面附件ID | string
users_id_pic_a_url: 证件正面文件流链接 | string
users_id_pic_b: 证件反面附件ID | string
users_id_pic_b_url: 证件反面文件流链接 | string
users_title_picture: 资质附件ID | string
users_title_picture_url: 资质附件文件流链接 | string
openid: 全平台统一身份标识 | string
company_id: 公司业务id | string
users_parent_id: 上级用户业务id | string
users_promo_code: 推广码 | string
usergroups_id: 用户组业务id | string
company:
pb_id: PocketBase记录主键id | string
company_id: 公司业务id由数据库自动生成 | string
company_name: 公司名称 | string
company_type: 公司类型 | string
company_entity: 公司法人 | string
company_usci: 统一社会信用代码 | string
company_nationality: 国家名称 | string
company_nationality_code: 国家编码 | string
company_province: 省份名称 | string
company_province_code: 省份编码 | string
company_city: 城市名称 | string
company_city_code: 城市编码 | string
company_district: 区县名称 | string
company_district_code: 区县编码 | string
company_postalcode: 邮政编码 | string
company_add: 公司地址 | string
company_status: 公司状态 | string
company_level: 公司等级 | string
company_owner_openid: 公司所有者openid | string
company_remark: 备注 | string
created: 记录创建时间 | string
updated: 记录更新时间 | string
created: 用户创建时间 | string
updated: 用户更新时间 | string