2026-03-20 18:32:58 +08:00
|
|
|
import PocketBase from 'pocketbase';
|
|
|
|
|
|
|
|
|
|
// ================= 配置区 =================
|
|
|
|
|
// 确保使用不带 /api 的根地址,并且端口是你后台管理的实际端口
|
|
|
|
|
const PB_URL = 'http://new.blv-oa.com:8000';
|
|
|
|
|
const ADMIN_EMAIL = '450481891@qq.com'; // 必须是超级管理员账号,不是普通用户!
|
|
|
|
|
const ADMIN_PASSWORD = 'Momo123456';
|
|
|
|
|
// ==========================================
|
|
|
|
|
|
|
|
|
|
const pb = new PocketBase(PB_URL);
|
|
|
|
|
|
|
|
|
|
const collections = [
|
|
|
|
|
{
|
|
|
|
|
name: 'tbl_system_dict',
|
|
|
|
|
type: 'base',
|
2026-03-29 16:21:34 +08:00
|
|
|
listRule: '',
|
|
|
|
|
viewRule: '',
|
|
|
|
|
createRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")',
|
|
|
|
|
updateRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")',
|
|
|
|
|
deleteRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")',
|
2026-03-20 18:32:58 +08:00
|
|
|
fields: [
|
|
|
|
|
{ name: 'system_dict_id', type: 'text', required: true },
|
2026-03-26 17:59:13 +08:00
|
|
|
{ name: 'dict_name', type: 'text', required: true },
|
2026-03-20 18:32:58 +08:00
|
|
|
{ name: 'dict_word_enum', type: 'text' },
|
|
|
|
|
{ name: 'dict_word_description', type: 'text' },
|
|
|
|
|
{ name: 'dict_word_is_enabled', type: 'bool' },
|
2026-03-26 17:59:13 +08:00
|
|
|
{ name: 'dict_word_sort_order', type: 'text' },
|
2026-03-20 18:32:58 +08:00
|
|
|
{ name: 'dict_word_parent_id', type: 'text' },
|
|
|
|
|
{ name: 'dict_word_remark', type: 'text' }
|
|
|
|
|
],
|
|
|
|
|
indexes: [
|
|
|
|
|
'CREATE UNIQUE INDEX idx_system_dict_id ON tbl_system_dict (system_dict_id)',
|
2026-03-26 17:59:13 +08:00
|
|
|
'CREATE UNIQUE INDEX idx_dict_name ON tbl_system_dict (dict_name)',
|
2026-03-20 18:32:58 +08:00
|
|
|
'CREATE INDEX idx_dict_word_parent_id ON tbl_system_dict (dict_word_parent_id)'
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'tbl_company',
|
|
|
|
|
type: 'base',
|
|
|
|
|
fields: [
|
2026-03-29 16:21:34 +08:00
|
|
|
{ name: 'company_id', type: 'text', required: true, autogeneratePattern: 'WX-COMPANY-[0-9]{13}' },
|
2026-03-20 18:32:58 +08:00
|
|
|
{ name: 'company_name', type: 'text' },
|
|
|
|
|
{ name: 'company_type', type: 'text' },
|
|
|
|
|
{ name: 'company_entity', type: 'text' },
|
|
|
|
|
{ name: 'company_usci', type: 'text' },
|
|
|
|
|
{ name: 'company_nationality', type: 'text' },
|
2026-03-29 16:21:34 +08:00
|
|
|
{ name: 'company_nationality_code', type: 'text' },
|
2026-03-20 18:32:58 +08:00
|
|
|
{ name: 'company_province', type: 'text' },
|
2026-03-29 16:21:34 +08:00
|
|
|
{ name: 'company_province_code', type: 'text' },
|
2026-03-20 18:32:58 +08:00
|
|
|
{ name: 'company_city', type: 'text' },
|
2026-03-29 16:21:34 +08:00
|
|
|
{ name: 'company_city_code', type: 'text' },
|
|
|
|
|
{ name: 'company_district', type: 'text' },
|
|
|
|
|
{ name: 'company_district_code', type: 'text' },
|
2026-03-20 18:32:58 +08:00
|
|
|
{ name: 'company_postalcode', type: 'text' },
|
|
|
|
|
{ name: 'company_add', type: 'text' },
|
|
|
|
|
{ name: 'company_status', type: 'text' },
|
|
|
|
|
{ name: 'company_level', type: 'text' },
|
2026-03-29 16:21:34 +08:00
|
|
|
{ name: 'company_owner_openid', type: 'text' },
|
2026-03-20 18:32:58 +08:00
|
|
|
{ name: 'company_remark', type: 'text' }
|
|
|
|
|
],
|
|
|
|
|
indexes: [
|
|
|
|
|
'CREATE UNIQUE INDEX idx_company_id ON tbl_company (company_id)',
|
2026-03-29 16:21:34 +08:00
|
|
|
'CREATE INDEX idx_company_usci ON tbl_company (company_usci)',
|
2026-03-29 19:57:04 +08:00
|
|
|
'CREATE UNIQUE INDEX idx_company_owner_openid ON tbl_company (company_owner_openid)'
|
2026-03-20 18:32:58 +08:00
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'tbl_user_groups',
|
|
|
|
|
type: 'base',
|
|
|
|
|
fields: [
|
|
|
|
|
{ name: 'usergroups_id', type: 'text', required: true },
|
|
|
|
|
{ name: 'usergroups_name', type: 'text' },
|
|
|
|
|
{ name: 'usergroups_level', type: 'number' },
|
|
|
|
|
{ name: 'usergroups_remark', type: 'text' }
|
|
|
|
|
],
|
|
|
|
|
indexes: [
|
|
|
|
|
'CREATE UNIQUE INDEX idx_usergroups_id ON tbl_user_groups (usergroups_id)'
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'tbl_users',
|
|
|
|
|
type: 'base',
|
|
|
|
|
fields: [
|
|
|
|
|
{ name: 'users_id', type: 'text', required: true },
|
|
|
|
|
{ name: 'users_name', type: 'text' },
|
|
|
|
|
{ name: 'users_idtype', type: 'text' },
|
|
|
|
|
{ name: 'users_id_number', type: 'text' },
|
|
|
|
|
{ name: 'users_phone', type: 'text' },
|
|
|
|
|
{ name: 'users_wx_openid', type: 'text' },
|
|
|
|
|
{ name: 'users_level', type: 'text' },
|
|
|
|
|
{ name: 'users_type', type: 'text' },
|
|
|
|
|
{ name: 'users_status', type: 'text' },
|
|
|
|
|
{ name: 'company_id', type: 'text' },
|
|
|
|
|
{ name: 'users_parent_id', type: 'text' },
|
|
|
|
|
{ name: 'users_promo_code', type: 'text' },
|
2026-03-28 15:13:04 +08:00
|
|
|
{ name: 'users_id_pic_a', type: 'text' },
|
|
|
|
|
{ name: 'users_id_pic_b', type: 'text' },
|
|
|
|
|
{ name: 'users_title_picture', type: 'text' },
|
|
|
|
|
{ name: 'users_picture', type: 'text' },
|
2026-03-20 18:32:58 +08:00
|
|
|
{ name: 'usergroups_id', type: 'text' }
|
|
|
|
|
],
|
|
|
|
|
indexes: [
|
|
|
|
|
'CREATE UNIQUE INDEX idx_users_id ON tbl_users (users_id)',
|
|
|
|
|
'CREATE UNIQUE INDEX idx_users_phone ON tbl_users (users_phone)',
|
|
|
|
|
'CREATE UNIQUE INDEX idx_users_wx_openid ON tbl_users (users_wx_openid)',
|
|
|
|
|
'CREATE INDEX idx_users_company_id ON tbl_users (company_id)',
|
|
|
|
|
'CREATE INDEX idx_users_usergroups_id ON tbl_users (usergroups_id)',
|
|
|
|
|
'CREATE INDEX idx_users_parent_id ON tbl_users (users_parent_id)'
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
async function init() {
|
|
|
|
|
try {
|
|
|
|
|
console.log('🔄 正在登录管理员账号...');
|
|
|
|
|
await pb.admins.authWithPassword(ADMIN_EMAIL, ADMIN_PASSWORD);
|
|
|
|
|
console.log('✅ 登录成功!开始初始化表结构与索引...\n');
|
|
|
|
|
|
|
|
|
|
for (const collectionData of collections) {
|
|
|
|
|
await createOrUpdateCollection(collectionData);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await verifyCollections(collections);
|
|
|
|
|
console.log('\n🎉 所有表结构及索引初始化并校验完成!');
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('❌ 初始化失败:', error.response?.data || error.message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 幂等创建/更新集合
|
|
|
|
|
async function createOrUpdateCollection(collectionData) {
|
|
|
|
|
console.log(`🔄 正在创建表: ${collectionData.name} ...`);
|
|
|
|
|
const payload = {
|
|
|
|
|
name: collectionData.name,
|
|
|
|
|
type: collectionData.type,
|
2026-03-29 16:21:34 +08:00
|
|
|
listRule: collectionData.listRule,
|
|
|
|
|
viewRule: collectionData.viewRule,
|
|
|
|
|
createRule: collectionData.createRule,
|
|
|
|
|
updateRule: collectionData.updateRule,
|
|
|
|
|
deleteRule: collectionData.deleteRule,
|
2026-03-20 18:32:58 +08:00
|
|
|
fields: collectionData.fields,
|
|
|
|
|
indexes: collectionData.indexes
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await pb.collections.create(payload);
|
|
|
|
|
console.log(`✅ ${collectionData.name} 表及索引创建完成。`);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const nameErrorCode = error.response?.data?.name?.code;
|
|
|
|
|
if (
|
|
|
|
|
error.status === 400
|
|
|
|
|
&& (nameErrorCode === 'validation_not_unique' || nameErrorCode === 'validation_collection_name_exists')
|
|
|
|
|
) {
|
|
|
|
|
const existing = await pb.collections.getOne(collectionData.name);
|
|
|
|
|
await pb.collections.update(existing.id, payload);
|
|
|
|
|
console.log(`♻️ ${collectionData.name} 表已存在,已按最新结构更新。`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function verifyCollections(targetCollections) {
|
|
|
|
|
console.log('\n🔍 开始校验表结构与索引...');
|
|
|
|
|
|
|
|
|
|
for (const target of targetCollections) {
|
|
|
|
|
const remote = await pb.collections.getOne(target.name);
|
|
|
|
|
const remoteFieldNames = new Set((remote.fields || []).map((field) => field.name));
|
|
|
|
|
const missingFields = target.fields
|
|
|
|
|
.map((field) => field.name)
|
|
|
|
|
.filter((fieldName) => !remoteFieldNames.has(fieldName));
|
|
|
|
|
|
|
|
|
|
const remoteIndexes = new Set(remote.indexes || []);
|
|
|
|
|
const missingIndexes = target.indexes.filter((indexSql) => !remoteIndexes.has(indexSql));
|
|
|
|
|
|
|
|
|
|
if (missingFields.length === 0 && missingIndexes.length === 0) {
|
|
|
|
|
console.log(`✅ ${target.name} 校验通过。`);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log(`❌ ${target.name} 校验失败:`);
|
|
|
|
|
if (missingFields.length > 0) {
|
|
|
|
|
console.log(` - 缺失字段: ${missingFields.join(', ')}`);
|
|
|
|
|
}
|
|
|
|
|
if (missingIndexes.length > 0) {
|
|
|
|
|
console.log(` - 缺失索引: ${missingIndexes.join(' | ')}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
throw new Error(`${target.name} 结构与预期不一致`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-28 15:13:04 +08:00
|
|
|
init();
|