feat: 添加 SDK 权限管理页面和产品功能字段迁移脚本

- 新增 SDK 权限管理页面,包含角色管理、用户授权和集合权限配置功能。
- 实现字段迁移脚本,向 tbl_product_list 集合添加 prod_list_function 字段,类型为 json。
This commit is contained in:
2026-04-03 18:35:50 +08:00
parent 91fcdcd65a
commit cafd69ea2c
40 changed files with 8127 additions and 6447 deletions

View File

@@ -0,0 +1,149 @@
import { createRequire } from 'module';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import PocketBase from 'pocketbase';
const require = createRequire(import.meta.url);
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
let runtimeConfig = {};
try {
runtimeConfig = require('../pocket-base/bai_api_pb_hooks/bai_api_shared/config/runtime.js');
} catch (_error) {
runtimeConfig = {};
}
function readEnvFile(filePath) {
if (!fs.existsSync(filePath)) return {};
const content = fs.readFileSync(filePath, 'utf8');
const result = {};
for (const rawLine of content.split(/\r?\n/)) {
const line = rawLine.trim();
if (!line || line.startsWith('#')) continue;
const index = line.indexOf('=');
if (index === -1) continue;
const key = line.slice(0, index).trim();
const value = line.slice(index + 1).trim();
result[key] = value;
}
return result;
}
const backendEnv = readEnvFile(path.resolve(__dirname, '..', 'back-end', '.env'));
const pbUrl = String(
process.env.PB_URL
|| backendEnv.POCKETBASE_API_URL
|| runtimeConfig.POCKETBASE_API_URL
|| 'http://127.0.0.1:8090'
).replace(/\/+$/, '');
const authToken = process.env.POCKETBASE_AUTH_TOKEN || runtimeConfig.POCKETBASE_AUTH_TOKEN || '';
if (!authToken) {
console.error('❌ 缺少 POCKETBASE_AUTH_TOKEN无法执行字段迁移。');
process.exit(1);
}
function normalizeFieldPayload(field, targetSpec) {
const payload = {
name: targetSpec && targetSpec.name ? targetSpec.name : field.name,
type: targetSpec && targetSpec.type ? targetSpec.type : field.type,
};
if (field && field.id) {
payload.id = field.id;
}
if (typeof field.required !== 'undefined') {
payload.required = !!field.required;
}
if (payload.type === 'autodate') {
payload.onCreate = typeof field.onCreate === 'boolean' ? field.onCreate : true;
payload.onUpdate = typeof field.onUpdate === 'boolean' ? field.onUpdate : false;
}
return payload;
}
async function run() {
const pb = new PocketBase(pbUrl);
pb.authStore.save(authToken, null);
console.log(`🔄 开始处理字段迁移PocketBase: ${pbUrl}`);
const collections = await pb.collections.getFullList({ sort: '-created' });
const collection = collections.find((item) => item.name === 'tbl_product_list');
if (!collection) {
throw new Error('未找到集合 tbl_product_list');
}
const targetFieldName = 'prod_list_function';
const targetFieldType = 'json';
const existingField = (collection.fields || []).find((field) => field.name === targetFieldName);
if (existingField && existingField.type === targetFieldType) {
console.log('✅ 字段已存在且类型正确,无需变更。');
console.log('✅ 校验完成: tbl_product_list.prod_list_function (json)');
return;
}
const nextFields = [];
let patched = false;
for (let i = 0; i < (collection.fields || []).length; i += 1) {
const field = collection.fields[i];
if (field.name === targetFieldName) {
nextFields.push(normalizeFieldPayload(field, { name: targetFieldName, type: targetFieldType }));
patched = true;
continue;
}
nextFields.push(normalizeFieldPayload(field));
}
if (!patched) {
nextFields.push({
name: targetFieldName,
type: targetFieldType,
required: false,
});
}
await pb.collections.update(collection.id, {
name: collection.name,
type: collection.type,
listRule: collection.listRule,
viewRule: collection.viewRule,
createRule: collection.createRule,
updateRule: collection.updateRule,
deleteRule: collection.deleteRule,
fields: nextFields,
indexes: collection.indexes || [],
});
const updated = await pb.collections.getOne(collection.id);
const verifiedField = (updated.fields || []).find((field) => field.name === targetFieldName);
if (!verifiedField || verifiedField.type !== targetFieldType) {
throw new Error('字段写入后校验失败prod_list_function 未成功写入 json 类型');
}
console.log('✅ 字段迁移成功: tbl_product_list.prod_list_function (json)');
}
run().catch((error) => {
console.error('❌ 迁移失败:', {
status: error && error.status,
message: error && error.message,
response: error && error.response,
});
process.exit(1);
});

View File

@@ -84,7 +84,7 @@
| users_id_number | text | 证件号 |
| users_phone | text | 用户电话号码 |
| users_wx_openid | text | 微信号 |
| users_level | text | 用户等级 |
| users_level | text | 用户等级枚举值(新用户默认空) |
| users_type | text | 用户类型 |
| users_tag | text | 用户标签 |
| users_status | text | 用户状态 |

View File

@@ -11,6 +11,7 @@
"init:dictionary": "node pocketbase.dictionary.js",
"migrate:file-fields": "node pocketbase.file-fields-to-attachments.js",
"migrate:product-params-array": "node migrate-product-parameters-to-array.js",
"migrate:add-product-function-field": "node add-product-function-field.js",
"test:company-native-api": "node test-tbl-company-native-api.js",
"test:company-owner-sync": "node test-company-owner-sync.js"
},

View File

@@ -38,6 +38,7 @@ const collections = [
{ name: 'prod_list_description', type: 'text' },
{ name: 'prod_list_feature', type: 'text' },
{ name: 'prod_list_parameters', type: 'json' },
{ name: 'prod_list_function', type: 'json' },
{ name: 'prod_list_plantype', type: 'text' },
{ name: 'prod_list_category', type: 'text', required: true },
{ name: 'prod_list_sort', type: 'number' },
@@ -47,6 +48,7 @@ const collections = [
{ name: 'prod_list_tags', type: 'text' },
{ name: 'prod_list_status', type: 'text' },
{ name: 'prod_list_basic_price', type: 'number' },
{ name: 'prod_list_vip_price', type: 'json' },
{ name: 'prod_list_remark', type: 'text' },
],
indexes: [