10404dbb36
同步工作区剩余变更,主要包括: - 营销页面组件与布局持续优化(about/news/services/solutions/team 等) - 详情页四层叙事组件、布局组件、UI 组件调整 - CMS 数据模型、API 路由、权限、工作流、站内通知、媒体管理扩展 - 新增/补充单元测试与 E2E 测试(cms-workflow.spec.ts 等) - ESLint 9 迁移、jest/tsconfig 配置更新、依赖调整 - 新增 ADR、CMS 评估文档、Release Review / Acceptance 报告 - 移除水墨装饰组件与大体积未使用字体文件
891 lines
35 KiB
TypeScript
891 lines
35 KiB
TypeScript
import 'dotenv/config';
|
||
import { PrismaClient } from '../src/generated/prisma/client';
|
||
import { hashPassword } from '../src/lib/auth';
|
||
import { CONTENT_TYPE_CONFIGS } from '../src/lib/cms/content-types';
|
||
import { PRIVACY_POLICY_HTML, TERMS_OF_SERVICE_HTML } from './seeds/legal-pages';
|
||
import { CASE_STUDIES } from './seeds/case-studies';
|
||
import { NEWS } from '../src/lib/constants/news';
|
||
import { SERVICES } from './seeds/services';
|
||
import { PRODUCTS } from './seeds/products';
|
||
import { STANDALONE_PRODUCTS } from './seeds/standalone-products';
|
||
import { SOLUTIONS } from './seeds/solutions';
|
||
import { STATS } from '../src/lib/constants/stats';
|
||
import { COMPANY_INFO } from '../src/lib/constants/company';
|
||
import { NAVIGATION_V2, MEGA_DROPDOWN_DATA } from '../src/lib/constants/navigation';
|
||
|
||
const prisma = new PrismaClient();
|
||
|
||
async function getModelId(code: string): Promise<string> {
|
||
const model = await prisma.contentModel.findUnique({ where: { code } });
|
||
if (!model) throw new Error(`模型 ${code} 未找到`);
|
||
return model.id;
|
||
}
|
||
|
||
async function seedItems(
|
||
modelCode: string,
|
||
items: Record<string, unknown>[],
|
||
options: { slugField?: string; titleField?: string } = {},
|
||
) {
|
||
const modelId = await getModelId(modelCode);
|
||
const slugField = options.slugField || 'slug';
|
||
const titleField = options.titleField || 'title';
|
||
|
||
for (let i = 0; i < items.length; i++) {
|
||
const item = items[i]!;
|
||
const slug = String(item[slugField] || item.id || `${modelCode}-${i}`);
|
||
const title = String(item[titleField] || `未命名 ${i + 1}`);
|
||
const data = item as Record<string, unknown>;
|
||
|
||
await prisma.contentItem.upsert({
|
||
where: { modelCode_slug_locale: { modelCode, slug, locale: 'zh-CN' } },
|
||
update: {
|
||
title,
|
||
data: JSON.stringify(data),
|
||
sortOrder: i,
|
||
status: 'published',
|
||
publishedAt: new Date(),
|
||
},
|
||
create: {
|
||
modelId,
|
||
modelCode,
|
||
title,
|
||
slug,
|
||
data: JSON.stringify(data),
|
||
sortOrder: i,
|
||
status: 'published',
|
||
publishedAt: new Date(),
|
||
createdBy: 'system',
|
||
updatedBy: 'system',
|
||
},
|
||
});
|
||
}
|
||
}
|
||
|
||
async function seedHomeZones() {
|
||
const heroItem = await prisma.contentItem.findFirst({
|
||
where: { modelCode: 'hero-banner', status: 'published' },
|
||
orderBy: { sortOrder: 'asc' },
|
||
});
|
||
|
||
const statItems = await prisma.contentItem.findMany({
|
||
where: { modelCode: 'stat-item', status: 'published' },
|
||
orderBy: { sortOrder: 'asc' },
|
||
take: 4,
|
||
});
|
||
|
||
const serviceItems = await prisma.contentItem.findMany({
|
||
where: { modelCode: 'service', status: 'published' },
|
||
orderBy: { sortOrder: 'asc' },
|
||
take: 6,
|
||
});
|
||
|
||
const solutionItems = await prisma.contentItem.findMany({
|
||
where: { modelCode: 'solution', status: 'published' },
|
||
orderBy: { sortOrder: 'asc' },
|
||
take: 6,
|
||
});
|
||
|
||
const caseItems = await prisma.contentItem.findMany({
|
||
where: { modelCode: 'case-study', status: 'published' },
|
||
orderBy: { sortOrder: 'asc' },
|
||
});
|
||
const featuredCaseItems = caseItems
|
||
.filter((item) => {
|
||
try {
|
||
const data = JSON.parse(item.data) as Record<string, unknown>;
|
||
return data.featured === true;
|
||
} catch {
|
||
return false;
|
||
}
|
||
})
|
||
.slice(0, 3);
|
||
|
||
const newsItems = await prisma.contentItem.findMany({
|
||
where: { modelCode: 'news', status: 'published' },
|
||
orderBy: { sortOrder: 'asc' },
|
||
take: 3,
|
||
});
|
||
|
||
const zones = [
|
||
{
|
||
code: 'home-hero',
|
||
name: '首页 - Hero Banner',
|
||
pageCode: 'home',
|
||
zoneKey: 'hero',
|
||
allowedModels: ['hero-banner'],
|
||
items: heroItem ? [{ itemId: heroItem.id, sortOrder: 0, variant: 'default' }] : [],
|
||
},
|
||
{
|
||
code: 'home-stats',
|
||
name: '首页 - 数据指标',
|
||
pageCode: 'home',
|
||
zoneKey: 'stats',
|
||
allowedModels: ['stat-item'],
|
||
items: statItems.map((item, i) => ({ itemId: item.id, sortOrder: i, variant: 'default' })),
|
||
},
|
||
{
|
||
code: 'home-services',
|
||
name: '首页 - 服务展示',
|
||
pageCode: 'home',
|
||
zoneKey: 'services',
|
||
allowedModels: ['service'],
|
||
items: serviceItems.map((item, i) => ({ itemId: item.id, sortOrder: i, variant: 'default' })),
|
||
},
|
||
{
|
||
code: 'home-solutions',
|
||
name: '首页 - 解决方案',
|
||
pageCode: 'home',
|
||
zoneKey: 'solutions',
|
||
allowedModels: ['solution'],
|
||
items: solutionItems.map((item, i) => ({ itemId: item.id, sortOrder: i, variant: 'default' })),
|
||
},
|
||
{
|
||
code: 'home-cases',
|
||
name: '首页 - 精选案例',
|
||
pageCode: 'home',
|
||
zoneKey: 'cases',
|
||
allowedModels: ['case-study'],
|
||
items: featuredCaseItems.map((item, i) => ({ itemId: item.id, sortOrder: i, variant: 'default' })),
|
||
},
|
||
{
|
||
code: 'home-news',
|
||
name: '首页 - 新闻动态',
|
||
pageCode: 'home',
|
||
zoneKey: 'news',
|
||
allowedModels: ['news'],
|
||
items: newsItems.map((item, i) => ({ itemId: item.id, sortOrder: i, variant: 'default' })),
|
||
},
|
||
];
|
||
|
||
for (const zone of zones) {
|
||
await prisma.contentZone.upsert({
|
||
where: { code: zone.code },
|
||
update: {
|
||
name: zone.name,
|
||
pageCode: zone.pageCode,
|
||
zoneKey: zone.zoneKey,
|
||
allowedModels: JSON.stringify(zone.allowedModels),
|
||
items: JSON.stringify(zone.items),
|
||
},
|
||
create: {
|
||
code: zone.code,
|
||
name: zone.name,
|
||
pageCode: zone.pageCode,
|
||
zoneKey: zone.zoneKey,
|
||
allowedModels: JSON.stringify(zone.allowedModels),
|
||
items: JSON.stringify(zone.items),
|
||
},
|
||
});
|
||
console.log(` ✅ 区域: ${zone.name}`);
|
||
}
|
||
}
|
||
|
||
async function main() {
|
||
console.log('🌱 开始数据库初始化...');
|
||
|
||
// ============ 1. 创建管理员用户 ============
|
||
console.log('📝 创建管理员用户...');
|
||
const adminPassword = await hashPassword('admin123');
|
||
const adminUser = await prisma.user.upsert({
|
||
where: { username: 'admin' },
|
||
update: {},
|
||
create: {
|
||
username: 'admin',
|
||
password: adminPassword,
|
||
nickname: '管理员',
|
||
role: 'admin',
|
||
status: 1,
|
||
},
|
||
});
|
||
console.log(' ✅ 管理员: admin / admin123');
|
||
|
||
// ============ 1.5 创建 RBAC 角色与权限 ============
|
||
console.log('📝 创建 RBAC 角色与权限...');
|
||
const roleCodes = ['super_admin', 'content_admin', 'content_editor', 'reviewer', 'readonly'];
|
||
for (const code of roleCodes) {
|
||
const nameMap: Record<string, string> = {
|
||
super_admin: '超级管理员',
|
||
content_admin: '内容管理员',
|
||
content_editor: '内容编辑',
|
||
reviewer: '审核员',
|
||
readonly: '只读用户',
|
||
};
|
||
await prisma.role.upsert({
|
||
where: { code },
|
||
update: {},
|
||
create: { code, name: nameMap[code] || code },
|
||
});
|
||
}
|
||
|
||
const contentModels = [
|
||
'news',
|
||
'product',
|
||
'solution',
|
||
'service',
|
||
'case-study',
|
||
'team',
|
||
'legal',
|
||
'about-page',
|
||
'hero-banner',
|
||
'stat-item',
|
||
'standalone-product',
|
||
'media',
|
||
];
|
||
|
||
// 为 super_admin 之外的角色创建权限
|
||
const permissionMatrix: Array<{ roleCode: string; actions: string[]; models?: string[] }> = [
|
||
{
|
||
roleCode: 'content_admin',
|
||
actions: ['create', 'read', 'update', 'delete', 'publish'],
|
||
models: contentModels,
|
||
},
|
||
{
|
||
roleCode: 'content_editor',
|
||
actions: ['create', 'read', 'update'],
|
||
models: contentModels,
|
||
},
|
||
{
|
||
roleCode: 'reviewer',
|
||
actions: ['read', 'publish'],
|
||
models: contentModels,
|
||
},
|
||
{
|
||
roleCode: 'readonly',
|
||
actions: ['read'],
|
||
models: contentModels,
|
||
},
|
||
];
|
||
|
||
for (const { roleCode, actions, models } of permissionMatrix) {
|
||
if (!models) continue;
|
||
for (const modelCode of models) {
|
||
for (const action of actions) {
|
||
await prisma.permission.upsert({
|
||
where: {
|
||
roleCode_modelCode_action: {
|
||
roleCode,
|
||
modelCode,
|
||
action,
|
||
},
|
||
},
|
||
update: {},
|
||
create: {
|
||
roleCode,
|
||
modelCode,
|
||
action,
|
||
},
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
// 为 admin 用户分配 super_admin 角色
|
||
await prisma.userRole.upsert({
|
||
where: {
|
||
userId_roleCode: {
|
||
userId: adminUser.id,
|
||
roleCode: 'super_admin',
|
||
},
|
||
},
|
||
update: {},
|
||
create: {
|
||
userId: adminUser.id,
|
||
roleCode: 'super_admin',
|
||
},
|
||
});
|
||
|
||
// 为 E2E 测试创建固定角色账号
|
||
console.log('📝 创建 E2E 测试用户...');
|
||
const editorPassword = await hashPassword('editor123');
|
||
const reviewerPassword = await hashPassword('reviewer123');
|
||
const e2eEditor = await prisma.user.upsert({
|
||
where: { username: 'e2e_editor' },
|
||
update: {},
|
||
create: {
|
||
username: 'e2e_editor',
|
||
password: editorPassword,
|
||
nickname: 'E2E 编辑',
|
||
role: 'content_editor',
|
||
status: 1,
|
||
},
|
||
});
|
||
const e2eReviewer = await prisma.user.upsert({
|
||
where: { username: 'e2e_reviewer' },
|
||
update: {},
|
||
create: {
|
||
username: 'e2e_reviewer',
|
||
password: reviewerPassword,
|
||
nickname: 'E2E 审核',
|
||
role: 'reviewer',
|
||
status: 1,
|
||
},
|
||
});
|
||
await prisma.userRole.upsert({
|
||
where: { userId_roleCode: { userId: e2eEditor.id, roleCode: 'content_editor' } },
|
||
update: {},
|
||
create: { userId: e2eEditor.id, roleCode: 'content_editor' },
|
||
});
|
||
await prisma.userRole.upsert({
|
||
where: { userId_roleCode: { userId: e2eReviewer.id, roleCode: 'reviewer' } },
|
||
update: {},
|
||
create: { userId: e2eReviewer.id, roleCode: 'reviewer' },
|
||
});
|
||
console.log(' ✅ E2E 测试用户: e2e_editor / editor123, e2e_reviewer / reviewer123');
|
||
|
||
console.log(' ✅ RBAC 角色与权限初始化完成');
|
||
|
||
// ============ 2. 创建内容模型 ============
|
||
console.log('📝 创建内容模型...');
|
||
const modelConfigs = [
|
||
...Object.values(CONTENT_TYPE_CONFIGS),
|
||
// === about-page ===
|
||
{
|
||
model: {
|
||
code: 'about-page',
|
||
name: '关于我们',
|
||
description: '公司介绍、核心价值、发展历程与资质认证信息',
|
||
isPageType: true,
|
||
urlPattern: '/about',
|
||
hasVersions: true,
|
||
hasDraft: true,
|
||
icon: 'building2',
|
||
fields: [
|
||
{ name: 'heroSubtitle', type: 'text', label: 'Hero 标签', required: true },
|
||
{ name: 'heroTitle', type: 'text', label: 'Hero 主标题', required: true },
|
||
{ name: 'heroDescription', type: 'textarea', label: 'Hero 描述', required: true },
|
||
{ name: 'whoWeAreTitle', type: 'text', label: '"我们是谁"标题' },
|
||
{ name: 'whoWeAreDescription', type: 'textarea', label: '"我们是谁"描述' },
|
||
{ name: 'promise', type: 'textarea', label: '承诺标语', required: true },
|
||
{ name: 'coreValues', type: 'array', label: '核心价值观', fields: [
|
||
{ name: 'number', type: 'text', label: '编号' },
|
||
{ name: 'title', type: 'text', label: '标题' },
|
||
{ name: 'description', type: 'textarea', label: '描述' },
|
||
]},
|
||
{ name: 'milestones', type: 'array', label: '发展历程', fields: [
|
||
{ name: 'year', type: 'text', label: '年份' },
|
||
{ name: 'title', type: 'text', label: '标题' },
|
||
{ name: 'description', type: 'textarea', label: '描述' },
|
||
{ name: 'highlight', type: 'text', label: '亮点' },
|
||
]},
|
||
{ name: 'keyMetrics', type: 'array', label: '关键指标', fields: [
|
||
{ name: 'value', type: 'text', label: '数值' },
|
||
{ name: 'label', type: 'text', label: '标签' },
|
||
{ name: 'sub', type: 'text', label: '副文本' },
|
||
]},
|
||
{ name: 'certifications', type: 'array', label: '资质认证', fields: [
|
||
{ name: 'name', type: 'text', label: '名称' },
|
||
{ name: 'desc', type: 'text', label: '描述' },
|
||
{ name: 'icon', type: 'text', label: '图标名称' },
|
||
]},
|
||
{ name: 'partners', type: 'array', label: '合作伙伴', fields: [
|
||
{ name: 'name', type: 'text', label: '名称' },
|
||
]},
|
||
{ name: 'ctaTitle', type: 'text', label: 'CTA 标题' },
|
||
{ name: 'ctaDescription', type: 'textarea', label: 'CTA 描述' },
|
||
],
|
||
},
|
||
},
|
||
// === team-page ===
|
||
{
|
||
model: {
|
||
code: 'team-page',
|
||
name: '团队介绍',
|
||
description: '团队实力、文化氛围、数据指标',
|
||
isPageType: true,
|
||
urlPattern: '/team',
|
||
hasVersions: true,
|
||
hasDraft: true,
|
||
icon: 'users',
|
||
fields: [
|
||
{ name: 'heroSubtitle', type: 'text', label: 'Hero 标签', required: true },
|
||
{ name: 'heroTitle', type: 'textarea', label: 'Hero 主标题', required: true },
|
||
{ name: 'heroDescription', type: 'textarea', label: 'Hero 描述', required: true },
|
||
{ name: 'stats', type: 'array', label: '统计指标', fields: [
|
||
{ name: 'value', type: 'number', label: '数值' },
|
||
{ name: 'suffix', type: 'text', label: '后缀' },
|
||
{ name: 'label', type: 'text', label: '标签' },
|
||
]},
|
||
{ name: 'strengths', type: 'array', label: '团队优势', fields: [
|
||
{ name: 'icon', type: 'text', label: '图标名称' },
|
||
{ name: 'number', type: 'text', label: '编号' },
|
||
{ name: 'title', type: 'text', label: '标题' },
|
||
{ name: 'description', type: 'textarea', label: '描述' },
|
||
]},
|
||
{ name: 'culture', type: 'array', label: '团队文化', fields: [
|
||
{ name: 'icon', type: 'text', label: '图标名称' },
|
||
{ name: 'number', type: 'text', label: '编号' },
|
||
{ name: 'title', type: 'text', label: '标题' },
|
||
{ name: 'description', type: 'textarea', label: '描述' },
|
||
]},
|
||
{ name: 'teamAboutTitle', type: 'text', label: '团队介绍标题' },
|
||
{ name: 'ctaTitle', type: 'text', label: 'CTA 标题' },
|
||
{ name: 'ctaDescription', type: 'textarea', label: 'CTA 描述' },
|
||
],
|
||
},
|
||
},
|
||
// === contact-page ===
|
||
{
|
||
model: {
|
||
code: 'contact-page',
|
||
name: '联系我们',
|
||
description: '联系信息、工作时间与服务承诺',
|
||
isPageType: true,
|
||
urlPattern: '/contact',
|
||
hasVersions: true,
|
||
hasDraft: true,
|
||
icon: 'phone',
|
||
fields: [
|
||
{ name: 'heroSubtitle', type: 'text', label: 'Hero 标签', required: true },
|
||
{ name: 'heroTitle', type: 'textarea', label: 'Hero 主标题', required: true },
|
||
{ name: 'heroDescription', type: 'textarea', label: 'Hero 描述', required: true },
|
||
{ name: 'companyName', type: 'text', label: '公司名称', required: true },
|
||
{ name: 'companyEmail', type: 'text', label: '联系邮箱', required: true },
|
||
{ name: 'companyAddress', type: 'text', label: '公司地址', required: true },
|
||
{ name: 'workHoursWeekday', type: 'text', label: '工作日描述' },
|
||
{ name: 'workHoursTime', type: 'text', label: '工作时间段' },
|
||
{ name: 'promises', type: 'array', label: '服务承诺', fields: [
|
||
{ name: 'text', type: 'textarea', label: '承诺内容' },
|
||
]},
|
||
{ name: 'ctaTitle', type: 'text', label: 'CTA 标题' },
|
||
{ name: 'ctaDescription', type: 'textarea', label: 'CTA 描述' },
|
||
],
|
||
},
|
||
},
|
||
// === site-config ===
|
||
{
|
||
model: {
|
||
code: 'site-config',
|
||
name: '站点配置',
|
||
description: '公司基本信息、SEO 元数据等全局配置',
|
||
isPageType: false,
|
||
urlPattern: '',
|
||
hasVersions: false,
|
||
hasDraft: true,
|
||
icon: 'settings',
|
||
fields: [
|
||
{ name: 'name', type: 'text', label: '公司全称', required: true },
|
||
{ name: 'shortName', type: 'text', label: '公司简称', required: true },
|
||
{ name: 'displayName', type: 'text', label: '展示名称', required: true },
|
||
{ name: 'slogan', type: 'text', label: '品牌标语', required: true },
|
||
{ name: 'description', type: 'textarea', label: '公司简介', required: true },
|
||
{ name: 'founded', type: 'text', label: '成立年份' },
|
||
{ name: 'location', type: 'text', label: '所在地' },
|
||
{ name: 'email', type: 'text', label: '联系邮箱', required: true },
|
||
{ name: 'address', type: 'text', label: '办公地址', required: true },
|
||
{ name: 'icp', type: 'text', label: 'ICP 备案号' },
|
||
{ name: 'police', type: 'text', label: '公安备案号' },
|
||
],
|
||
},
|
||
},
|
||
// === navigation ===
|
||
{
|
||
model: {
|
||
code: 'navigation',
|
||
name: '导航菜单',
|
||
description: '主导航项和下拉菜单内容',
|
||
isPageType: false,
|
||
urlPattern: '',
|
||
hasVersions: false,
|
||
hasDraft: true,
|
||
icon: 'menu',
|
||
fields: [
|
||
{ name: 'mainNav', type: 'json', label: '主导航项', required: true },
|
||
{ name: 'megaDropdown', type: 'json', label: '下拉菜单数据', required: true },
|
||
],
|
||
},
|
||
},
|
||
// === legal-page ===
|
||
{
|
||
model: {
|
||
code: 'legal-page',
|
||
name: '法律页面',
|
||
description: '隐私政策/服务条款页面内容配置',
|
||
isPageType: false,
|
||
urlPattern: '',
|
||
hasVersions: false,
|
||
hasDraft: true,
|
||
icon: 'file-text',
|
||
fields: [
|
||
{ name: 'pageType', type: 'text', label: '页面类型', required: true, options: [
|
||
{ label: '隐私政策', value: 'privacy' },
|
||
{ label: '服务条款', value: 'terms' },
|
||
]},
|
||
{ name: 'heroTitle', type: 'text', label: '页面标题', required: true },
|
||
{ name: 'heroDescription', type: 'textarea', label: '页面描述' },
|
||
{ name: 'lastUpdated', type: 'text', label: '最后更新日期', required: true },
|
||
{ name: 'content', type: 'richtext', label: '页面正文(HTML)', required: true, description: '隐私政策/服务条款完整正文,支持 HTML 标签' },
|
||
],
|
||
},
|
||
},
|
||
];
|
||
|
||
for (let i = 0; i < modelConfigs.length; i++) {
|
||
const config = modelConfigs[i]!;
|
||
await prisma.contentModel.upsert({
|
||
where: { code: config.model.code },
|
||
update: {
|
||
name: config.model.name,
|
||
description: config.model.description,
|
||
fields: JSON.stringify(config.model.fields),
|
||
isPageType: config.model.isPageType,
|
||
urlPattern: config.model.urlPattern || '',
|
||
hasVersions: config.model.hasVersions,
|
||
hasDraft: config.model.hasDraft,
|
||
icon: config.model.icon || '',
|
||
sortOrder: i,
|
||
},
|
||
create: {
|
||
code: config.model.code,
|
||
name: config.model.name,
|
||
description: config.model.description,
|
||
fields: JSON.stringify(config.model.fields),
|
||
isPageType: config.model.isPageType,
|
||
urlPattern: config.model.urlPattern || '',
|
||
hasVersions: config.model.hasVersions,
|
||
hasDraft: config.model.hasDraft,
|
||
icon: config.model.icon || '',
|
||
sortOrder: i,
|
||
},
|
||
});
|
||
console.log(` ✅ 模型: ${config.model.name} (${config.model.code})`);
|
||
}
|
||
|
||
// ============ 3. 导入内容数据 ============
|
||
console.log('📝 导入首页 Hero Banner...');
|
||
await seedItems('hero-banner', [
|
||
{
|
||
slug: 'home',
|
||
title: '首页 Hero Banner',
|
||
eyebrow: '企业数字化转型',
|
||
heading: '让每一家企业都拥有数据驱动的决策能力',
|
||
subheading: '12 年深耕,从战略规划到系统落地,陪伴制造、零售、医疗、金融、教育、物流六大行业客户实现可衡量的业务改善。',
|
||
headingTop: '四川睿新致远科技有限公司',
|
||
headingBottom: '让技术成为业务增长引擎',
|
||
ctaLabel: '预约咨询',
|
||
ctaHref: '/contact',
|
||
secondaryCtaLabel: '了解我们的方法论',
|
||
secondaryCtaHref: '/services/consulting',
|
||
statValue: '500+',
|
||
statSubtext: '企业数字化转型实践',
|
||
ctaTitleLine1: '每一次合作,',
|
||
ctaTitleLine2: '真正转化为业务增长',
|
||
},
|
||
]);
|
||
console.log(' ✅ 首页 Hero Banner');
|
||
|
||
console.log('📝 导入案例研究...');
|
||
await seedItems('case-study', CASE_STUDIES as unknown as Record<string, unknown>[]);
|
||
console.log(` ✅ ${CASE_STUDIES.length} 个案例`);
|
||
|
||
console.log('📝 导入新闻...');
|
||
await seedItems('news', NEWS as unknown as Record<string, unknown>[]);
|
||
console.log(` ✅ ${NEWS.length} 条新闻`);
|
||
|
||
console.log('📝 导入服务...');
|
||
const homeServiceFields: Record<string, { subtitle: string; desc: string; highlights: string[]; metrics: { value: string; label: string }[]; href: string }> = {
|
||
consulting: {
|
||
subtitle: '战略咨询',
|
||
desc: '数字化转型战略规划与落地路径设计,让技术投资真正驱动业务增长。从愿景制定到执行落地,陪伴企业穿越转型周期。',
|
||
highlights: ['数字化成熟度评估', '转型路线图设计', '技术选型评估', '组织变革管理'],
|
||
metrics: [
|
||
{ value: '50+', label: '咨询项目' },
|
||
{ value: '90%+', label: '方案落地率' },
|
||
],
|
||
href: '/services/consulting',
|
||
},
|
||
software: {
|
||
subtitle: '企业软件',
|
||
desc: 'ERP、CRM、BI 等核心系统的评估、实施与定制化开发,基于行业最佳实践打造适配企业实际的数字化底座。',
|
||
highlights: ['ERP 实施优化', 'CRM 客户管理', 'BI 商业智能', '系统集成'],
|
||
metrics: [
|
||
{ value: '95%+', label: '准时交付率' },
|
||
{ value: '<4h', label: '平均响应' },
|
||
],
|
||
href: '/services/software',
|
||
},
|
||
data: {
|
||
subtitle: '技术服务',
|
||
desc: '系统集成、数据中台、云原生架构设计与技术外包服务,以工程化能力保障系统的稳定性、可扩展性与持续迭代。',
|
||
highlights: ['数据中台建设', '云原生迁移', '研发效能提升', '7x24 运维'],
|
||
metrics: [
|
||
{ value: '20+', label: '数据源对接' },
|
||
{ value: '99.9%', label: '系统可用率' },
|
||
],
|
||
href: '/services/data',
|
||
},
|
||
solutions: {
|
||
subtitle: 'AI 赋能',
|
||
desc: 'AI 成熟度评估、场景落地与大模型应用开发,帮助企业找到 AI 与业务的最佳结合点,实现智能化升级。',
|
||
highlights: ['AI 战略规划', '大模型应用开发', '智能流程自动化', 'AI 模型训练'],
|
||
metrics: [
|
||
{ value: '30+', label: 'AI 场景落地' },
|
||
{ value: '85%', label: '平均效率提升' },
|
||
],
|
||
href: '/services/solutions',
|
||
},
|
||
};
|
||
const servicesForSeed = SERVICES.map((s) => ({
|
||
...s,
|
||
...homeServiceFields[s.id],
|
||
}));
|
||
await seedItems('service', servicesForSeed as unknown as Record<string, unknown>[]);
|
||
console.log(` ✅ ${SERVICES.length} 个服务`);
|
||
|
||
console.log('📝 导入产品...');
|
||
await seedItems('product', PRODUCTS as unknown as Record<string, unknown>[]);
|
||
console.log(` ✅ ${PRODUCTS.length} 个产品`);
|
||
|
||
console.log('📝 导入独立产品...');
|
||
await seedItems('standalone-product', STANDALONE_PRODUCTS as unknown as Record<string, unknown>[]);
|
||
console.log(` ✅ ${STANDALONE_PRODUCTS.length} 个独立产品`);
|
||
|
||
console.log('📝 导入解决方案...');
|
||
await seedItems('solution', SOLUTIONS as unknown as Record<string, unknown>[]);
|
||
console.log(` ✅ ${SOLUTIONS.length} 个解决方案`);
|
||
|
||
// 数据指标需要特殊处理
|
||
console.log('📝 导入数据指标...');
|
||
const statList = Array.isArray(STATS) ? STATS : (STATS as { stats?: unknown[] }).stats || [];
|
||
const statModelId = await getModelId('stat-item');
|
||
for (let i = 0; i < statList.length; i++) {
|
||
const s = statList[i] as Record<string, unknown>;
|
||
const slug = `stat-${i}`;
|
||
const title = String(s.label || `指标 ${i + 1}`);
|
||
const { id: _id, ...data } = s;
|
||
await prisma.contentItem.upsert({
|
||
where: { modelCode_slug_locale: { modelCode: 'stat-item', slug, locale: 'zh-CN' } },
|
||
update: {
|
||
title,
|
||
data: JSON.stringify({ ...data, accentColor: s.color }),
|
||
sortOrder: i,
|
||
status: 'published',
|
||
publishedAt: new Date(),
|
||
},
|
||
create: {
|
||
modelId: statModelId,
|
||
modelCode: 'stat-item',
|
||
title,
|
||
slug,
|
||
data: JSON.stringify({ ...data, accentColor: s.color }),
|
||
sortOrder: i,
|
||
status: 'published',
|
||
publishedAt: new Date(),
|
||
createdBy: 'system',
|
||
updatedBy: 'system',
|
||
},
|
||
});
|
||
}
|
||
console.log(` ✅ ${statList.length} 个数据指标`);
|
||
|
||
// ============ 4. 导入页面内容 ============
|
||
console.log('📝 导入关于页面...');
|
||
await seedItems('about-page', [
|
||
{
|
||
slug: 'default',
|
||
title: '关于我们',
|
||
heroSubtitle: '关于我们',
|
||
heroTitle: '12 年深耕\n500+ 企业\n的转型伙伴',
|
||
heroDescription: '我们不把项目完成当作终点。您的业务增长了吗?您的团队能力提升了吗?\n这才是我们真正在意的事。',
|
||
heroCtaText: '预约咨询',
|
||
heroCtaLink: '/contact',
|
||
heroSecondaryCtaText: '了解服务',
|
||
heroSecondaryCtaLink: '/services',
|
||
whoWeAreTitle: '我们是谁',
|
||
whoWeAreDescription: '我们坚持对行业趋势的深度研究,不追逐昙花一现的概念。每一次方案,都源于对您业务场景的洞察;每一次连接,都为了让技术真正服务于您的未来。',
|
||
promise: '不卖您用不上的技术,不说不懂业务的术语,不做路过就忘的一锤子买卖。',
|
||
coreValues: [
|
||
{ number: '01', title: '结果导向', description: '不以「项目上线」为终点,以「客户业务是否真正改善」为衡量标准。每一次交付,都追求可量化的价值。' },
|
||
{ number: '02', title: '长期主义', description: '不追逐风口,只做真正为客户创造价值的事。您的下一次难题,还愿意第一个想到我们——这才是我们追求的成功。' },
|
||
{ number: '03', title: '技术驱动', description: '用扎实的工程能力和行业经验赢得信任。技术不是炫技的工具,而是解决真实问题的手段。' },
|
||
],
|
||
milestones: [
|
||
{ year: '2014', title: '团队启航', description: '核心团队始于大型企业数字化转型项目,深耕 ERP、CRM 实施与定制开发。', highlight: '服务首批 20+ 企业客户' },
|
||
{ year: '2018', title: '业务扩展', description: '从单一实施服务向全链路咨询转型,建立战略咨询 + 技术落地的双轮驱动模式。', highlight: '客户突破 100 家' },
|
||
{ year: '2021', title: '产品化起步', description: '开始自研产品矩阵,从项目制向「产品+服务」双模式升级。', highlight: '3 款自研产品上线' },
|
||
{ year: '2023', title: '行业深耕', description: '聚焦制造、零售、医疗三大核心行业,建立行业解决方案库与最佳实践沉淀。', highlight: '客户续约率 95%+' },
|
||
{ year: '2026', title: '睿新致远成立', description: '四川睿新致远科技有限公司在成都正式成立,以全新品牌开启下一段征程。', highlight: '全新启航,步履不停' },
|
||
],
|
||
keyMetrics: [
|
||
{ value: '500+', label: '服务企业', sub: '六大行业覆盖' },
|
||
{ value: '200+', label: '专业顾问', sub: '平均行业经验 8 年' },
|
||
{ value: '98%', label: '客户满意度', sub: '97% 续约率' },
|
||
{ value: '12年', label: '行业深耕', sub: 'since 2014' },
|
||
],
|
||
certifications: [
|
||
{ name: 'ISO 27001', description: '信息安全管理体系认证', icon: 'Shield' },
|
||
{ name: 'ISO 9001', description: '质量管理体系认证', icon: 'Award' },
|
||
{ name: 'CMMI 3级', description: '软件能力成熟度认证', icon: 'Zap' },
|
||
{ name: '高新技术企业', description: '国家高新技术企业认定', icon: 'Trophy' },
|
||
],
|
||
partners: [
|
||
'SAP', 'Oracle', 'Salesforce', 'Microsoft',
|
||
'阿里云', '腾讯云', '华为云', '金蝶',
|
||
],
|
||
ctaTitle: '准备好开始了吗?',
|
||
ctaDescription: '无论您是想了解合作机会,还是希望深入探讨具体方案,都欢迎随时联系。',
|
||
},
|
||
]);
|
||
console.log(' ✅ 关于页面');
|
||
|
||
console.log('📝 导入团队页面...');
|
||
await seedItems('team-page', [
|
||
{
|
||
slug: 'default',
|
||
title: '团队介绍',
|
||
heroSubtitle: '核心团队',
|
||
heroTitle: '既懂技术又懂业务的\n复合型团队',
|
||
heroDescription: '我们的成员不只是写代码的工程师——我们是能理解您业务场景、能和您一起想清楚问题的合作伙伴。',
|
||
heroPrimaryCtaLabel: '加入我们',
|
||
heroPrimaryCtaHref: '/contact',
|
||
heroSecondaryCtaLabel: '了解公司',
|
||
heroSecondaryCtaHref: '/about',
|
||
stats: [
|
||
{ value: 12, suffix: '+', label: '年行业经验' },
|
||
{ value: 10, suffix: '+', label: '核心成员' },
|
||
{ value: 5, suffix: '+', label: '行业覆盖' },
|
||
],
|
||
strengths: [
|
||
{ iconName: 'Shield', number: '01', title: '12 年+ 行业深耕', description: '核心团队长期从事技术咨询、企业数字化等领域,服务覆盖金融、制造、零售、政务、农业等多个行业。' },
|
||
{ iconName: 'Building2', number: '02', title: '大型 IT 企业背景', description: '开发团队成员来自多个大型传统 IT 企业,具备扎实的工程能力、规范化的交付流程和严格的质量意识。' },
|
||
{ iconName: 'Users', number: '03', title: '复合型技术团队', description: '既懂技术又懂业务,能够深入理解客户的真实场景和痛点,提供真正可落地的解决方案。' },
|
||
{ iconName: 'Code', number: '04', title: '全栈技术能力', description: '从前端到后端、从云原生到数据智能、从移动端到物联网——应对各种复杂技术挑战。' },
|
||
{ iconName: 'Target', number: '05', title: '结果导向交付', description: '不以"项目上线"为终点,以"客户业务是否真正改善"为衡量标准。每一个交付成果都追求可量化的业务价值。' },
|
||
],
|
||
culture: [
|
||
{ iconName: 'Layers', number: '01', title: '扁平化协作', description: '没有冗长的汇报链条,每个人都有机会直接参与决策。信息透明,沟通高效。' },
|
||
{ iconName: 'BookOpen', number: '02', title: '持续学习', description: '技术日新月异,我们保持对新技术的好奇。内部分享、技术沙龙、外部培训——学习是日常的一部分。' },
|
||
{ iconName: 'Sparkles', number: '03', title: '客户成功', description: '我们的成就感来自客户的成功。客户的业务增长,就是我们最好的成绩单。' },
|
||
],
|
||
aboutTitle: '关于我们的团队',
|
||
aboutParagraphs: [
|
||
{ text: '我们的核心团队长期从事技术咨询、企业数字化等行业,拥有 12 年以上的深厚积累。' },
|
||
{ text: '开发团队成员来自于多个大型传统 IT 企业,具备扎实的工程能力和规范化的交付经验。' },
|
||
{ text: '我们相信,优秀的技术咨询不仅需要过硬的技术能力,更需要深入理解客户的业务场景和真实需求。每一位成员都是既懂技术又懂业务的复合型人才。' },
|
||
],
|
||
ctaTitle: '想成为我们的一员?',
|
||
ctaDescription: '我们始终在寻找既懂技术又热爱业务的伙伴。如果您认同我们的理念,欢迎聊聊。',
|
||
ctaPrimaryLabel: '联系我们',
|
||
ctaPrimaryHref: '/contact',
|
||
ctaSecondaryLabel: '了解公司',
|
||
ctaSecondaryHref: '/about',
|
||
},
|
||
]);
|
||
console.log(' ✅ 团队页面');
|
||
|
||
console.log('📝 导入联系页面...');
|
||
await seedItems('contact-page', [
|
||
{
|
||
slug: 'default',
|
||
title: '联系我们',
|
||
heroSubtitle: '联系我们',
|
||
heroTitle: '随时欢迎\n聊一聊',
|
||
heroDescription: '工作日 2 小时内快速响应,首次咨询免费。\n没有销售压力,只是坐下来把问题理清楚。',
|
||
email: 'contact@novalon.cn',
|
||
address: '中国四川省成都市龙泉驿区幸福路12号',
|
||
commitmentItems: [
|
||
'工作日 2 小时内快速响应您的咨询',
|
||
'提供免费的业务咨询和方案评估服务',
|
||
'根据您的需求量身定制最优解决方案',
|
||
],
|
||
contactTitle: '联系方式',
|
||
emailLabel: '邮箱',
|
||
addressLabel: '地址',
|
||
workHoursTitle: '工作时间',
|
||
dayLabel: '周一至周五',
|
||
hours: '9:00 - 18:00',
|
||
commitmentTitle: '服务承诺',
|
||
formTitle: '发送消息',
|
||
submitLabel: '发送消息',
|
||
submittingLabel: '发送中…',
|
||
successTitle: '消息已发送',
|
||
successMessage: '感谢您的留言,我们会尽快与您联系。',
|
||
ctaSubtitle: '更直接的方式',
|
||
ctaTitle: '还是想直接聊?',
|
||
ctaDescription: '表单填起来太麻烦?直接发邮件到 contact@novalon.cn,我们同样会快速回复。',
|
||
ctaPrimaryLabel: '发送邮件',
|
||
ctaPrimaryHref: 'mailto:contact@novalon.cn',
|
||
ctaSecondaryLabel: '返回首页',
|
||
ctaSecondaryHref: '/',
|
||
},
|
||
]);
|
||
console.log(' ✅ 联系页面');
|
||
|
||
console.log('📝 导入法律页面...');
|
||
await seedItems('legal-page', [
|
||
{
|
||
slug: 'privacy',
|
||
title: '隐私政策',
|
||
pageType: 'privacy',
|
||
heroTitle: '隐私政策',
|
||
heroDescription: '我们重视您的隐私,致力于保护您的个人信息安全',
|
||
lastUpdated: '2026-02-26',
|
||
content: PRIVACY_POLICY_HTML,
|
||
},
|
||
{
|
||
slug: 'terms',
|
||
title: '服务条款',
|
||
pageType: 'terms',
|
||
heroTitle: '服务条款',
|
||
heroDescription: '请仔细阅读以下服务条款,使用我们的服务即表示您同意这些条款',
|
||
lastUpdated: '2026-02-26',
|
||
content: TERMS_OF_SERVICE_HTML,
|
||
},
|
||
]);
|
||
console.log(' ✅ 法律页面');
|
||
|
||
console.log('📝 导入站点配置...');
|
||
await seedItems('site-config', [
|
||
{
|
||
slug: 'default',
|
||
title: '站点配置',
|
||
name: COMPANY_INFO.name,
|
||
shortName: COMPANY_INFO.shortName,
|
||
displayName: COMPANY_INFO.displayName,
|
||
slogan: COMPANY_INFO.slogan,
|
||
description: COMPANY_INFO.description,
|
||
founded: COMPANY_INFO.founded,
|
||
location: COMPANY_INFO.location,
|
||
email: COMPANY_INFO.email,
|
||
address: COMPANY_INFO.address,
|
||
icp: COMPANY_INFO.icp,
|
||
police: COMPANY_INFO.police,
|
||
},
|
||
]);
|
||
console.log(' ✅ 站点配置');
|
||
|
||
console.log('📝 导入导航菜单...');
|
||
await seedItems('navigation', [
|
||
{
|
||
slug: 'default',
|
||
title: '导航菜单',
|
||
mainNav: NAVIGATION_V2,
|
||
megaDropdown: MEGA_DROPDOWN_DATA,
|
||
},
|
||
]);
|
||
console.log(' ✅ 导航菜单');
|
||
|
||
// ============ 5. 创建默认内容区域 ============
|
||
console.log('📝 创建默认内容区域...');
|
||
await seedHomeZones();
|
||
console.log(' ✅ 首页内容区域创建完成');
|
||
|
||
// ============ 统计 ============
|
||
const itemCount = await prisma.contentItem.count();
|
||
const modelCount = await prisma.contentModel.count();
|
||
const zoneCount = await prisma.contentZone.count();
|
||
|
||
console.log('\n🎉 数据库初始化完成!');
|
||
console.log(` 📊 内容模型: ${modelCount}`);
|
||
console.log(` 📄 内容条目: ${itemCount}`);
|
||
console.log(` 🗂️ 内容区域: ${zoneCount}`);
|
||
console.log(` 👤 管理员: admin / admin123`);
|
||
}
|
||
|
||
main()
|
||
.catch((e) => {
|
||
console.error('❌ 种子数据初始化失败:', e);
|
||
process.exit(1);
|
||
})
|
||
.finally(async () => {
|
||
await prisma.$disconnect();
|
||
}); |