chore: sync marketing pages, CMS extensions, tests and project docs
同步工作区剩余变更,主要包括: - 营销页面组件与布局持续优化(about/news/services/solutions/team 等) - 详情页四层叙事组件、布局组件、UI 组件调整 - CMS 数据模型、API 路由、权限、工作流、站内通知、媒体管理扩展 - 新增/补充单元测试与 E2E 测试(cms-workflow.spec.ts 等) - ESLint 9 迁移、jest/tsconfig 配置更新、依赖调整 - 新增 ADR、CMS 评估文档、Release Review / Acceptance 报告 - 移除水墨装饰组件与大体积未使用字体文件
This commit is contained in:
+336
-197
@@ -2,11 +2,13 @@ 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 { CASE_STUDIES } from '../src/lib/constants/cases';
|
||||
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 '../src/lib/constants/services';
|
||||
import { PRODUCTS } from '../src/lib/constants/products';
|
||||
import { SOLUTIONS } from '../src/lib/constants/solutions';
|
||||
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';
|
||||
@@ -32,10 +34,10 @@ async function seedItems(
|
||||
const item = items[i]!;
|
||||
const slug = String(item[slugField] || item.id || `${modelCode}-${i}`);
|
||||
const title = String(item[titleField] || `未命名 ${i + 1}`);
|
||||
const { id: _id, slug: _s, title: _t, ...data } = item as Record<string, unknown>;
|
||||
const data = item as Record<string, unknown>;
|
||||
|
||||
await prisma.contentItem.upsert({
|
||||
where: { modelCode_slug: { modelCode, slug } },
|
||||
where: { modelCode_slug_locale: { modelCode, slug, locale: 'zh-CN' } },
|
||||
update: {
|
||||
title,
|
||||
data: JSON.stringify(data),
|
||||
@@ -59,13 +61,132 @@ async function seedItems(
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
await prisma.user.upsert({
|
||||
const adminUser = await prisma.user.upsert({
|
||||
where: { username: 'admin' },
|
||||
update: {},
|
||||
create: {
|
||||
@@ -78,33 +199,145 @@ async function main() {
|
||||
});
|
||||
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),
|
||||
{
|
||||
model: {
|
||||
code: 'case-study',
|
||||
name: '案例研究',
|
||||
description: '客户成功案例与实践分享',
|
||||
isPageType: true,
|
||||
urlPattern: '/cases/{slug}',
|
||||
hasVersions: true,
|
||||
hasDraft: true,
|
||||
icon: 'briefcase',
|
||||
fields: [
|
||||
{ name: 'client', type: 'text', label: '客户名称', required: true },
|
||||
{ name: 'industry', type: 'text', label: '所属行业', required: true },
|
||||
{ name: 'companySize', type: 'text', label: '公司规模', required: true },
|
||||
{ name: 'subtitle', type: 'text', label: '副标题', required: true },
|
||||
{ name: 'challenge', type: 'textarea', label: '挑战', required: true },
|
||||
{ name: 'solution', type: 'textarea', label: '解决方案', required: true },
|
||||
{ name: 'result', type: 'textarea', label: '实施成果', required: true },
|
||||
{ name: 'color', type: 'text', label: '主题色', defaultValue: 'brand' },
|
||||
{ name: 'featured', type: 'boolean', label: '精选案例', defaultValue: false },
|
||||
],
|
||||
},
|
||||
},
|
||||
// === about-page ===
|
||||
{
|
||||
model: {
|
||||
@@ -265,21 +498,22 @@ async function main() {
|
||||
{
|
||||
model: {
|
||||
code: 'legal-page',
|
||||
name: '法律条款',
|
||||
description: '隐私政策和服务条款的页面内容',
|
||||
isPageType: true,
|
||||
urlPattern: '/legal/{slug}',
|
||||
hasVersions: true,
|
||||
name: '法律页面',
|
||||
description: '隐私政策/服务条款页面内容配置',
|
||||
isPageType: false,
|
||||
urlPattern: '',
|
||||
hasVersions: false,
|
||||
hasDraft: true,
|
||||
icon: 'file-text',
|
||||
fields: [
|
||||
{ name: 'pageTitle', type: 'text', label: '页面标题', required: true },
|
||||
{ name: 'pageSubtitle', type: 'text', label: '页面副标题' },
|
||||
{ name: 'sections', type: 'array', label: '内容章节', fields: [
|
||||
{ name: 'title', type: 'text', label: '章节标题' },
|
||||
{ name: 'content', type: 'textarea', label: '章节内容' },
|
||||
{ name: 'pageType', type: 'text', label: '页面类型', required: true, options: [
|
||||
{ label: '隐私政策', value: 'privacy' },
|
||||
{ label: '服务条款', value: 'terms' },
|
||||
]},
|
||||
{ name: 'lastUpdated', type: 'text', label: '最后更新日期' },
|
||||
{ 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 标签' },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -322,11 +556,15 @@ async function main() {
|
||||
{
|
||||
slug: 'home',
|
||||
title: '首页 Hero Banner',
|
||||
eyebrow: '企业数字化转型',
|
||||
heading: '让每一家企业都拥有数据驱动的决策能力',
|
||||
subheading: '12 年深耕,从战略规划到系统落地,陪伴制造、零售、医疗、金融、教育、物流六大行业客户实现可衡量的业务改善。',
|
||||
headingTop: '四川睿新致远科技有限公司',
|
||||
headingBottom: '让技术成为业务增长引擎',
|
||||
description: '12 年企业数字化深耕,从战略规划到系统落地,陪伴制造、零售、医疗、金融、教育、物流六大行业客户实现可衡量的业务改善。',
|
||||
ctaLabel: '预约咨询',
|
||||
ctaHref: '/contact',
|
||||
secondaryCtaLabel: '了解我们的方法论',
|
||||
secondaryCtaHref: '/services/consulting',
|
||||
statValue: '500+',
|
||||
statSubtext: '企业数字化转型实践',
|
||||
ctaTitleLine1: '每一次合作,',
|
||||
@@ -397,6 +635,10 @@ async function main() {
|
||||
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} 个解决方案`);
|
||||
@@ -411,7 +653,7 @@ async function main() {
|
||||
const title = String(s.label || `指标 ${i + 1}`);
|
||||
const { id: _id, ...data } = s;
|
||||
await prisma.contentItem.upsert({
|
||||
where: { modelCode_slug: { modelCode: 'stat-item', slug } },
|
||||
where: { modelCode_slug_locale: { modelCode: 'stat-item', slug, locale: 'zh-CN' } },
|
||||
update: {
|
||||
title,
|
||||
data: JSON.stringify({ ...data, accentColor: s.color }),
|
||||
@@ -490,46 +732,42 @@ async function main() {
|
||||
{
|
||||
slug: 'default',
|
||||
title: '团队介绍',
|
||||
heroSubtitle: '团队介绍',
|
||||
heroSubtitle: '核心团队',
|
||||
heroTitle: '既懂技术又懂业务的\n复合型团队',
|
||||
heroDescription: '我们的成员不只是写代码的工程师——我们是能理解您业务场景、能和您一起想清楚问题的合作伙伴。',
|
||||
heroCtaText: '与我们交流',
|
||||
heroCtaLink: '/contact',
|
||||
heroSecondaryCtaText: '了解公司',
|
||||
heroSecondaryCtaLink: '/about',
|
||||
heroPrimaryCtaLabel: '加入我们',
|
||||
heroPrimaryCtaHref: '/contact',
|
||||
heroSecondaryCtaLabel: '了解公司',
|
||||
heroSecondaryCtaHref: '/about',
|
||||
stats: [
|
||||
{ value: 12, suffix: '+', label: '年行业经验' },
|
||||
{ value: 10, suffix: '+', label: '核心成员' },
|
||||
{ value: 5, suffix: '+', label: '行业覆盖' },
|
||||
],
|
||||
strengths: [
|
||||
{ icon: 'Shield', number: '01', title: '12 年+ 行业深耕', description: '核心团队长期从事技术咨询、企业数字化等领域,服务覆盖金融、制造、零售、政务、农业等多个行业。' },
|
||||
{ icon: 'Building2', number: '02', title: '大型 IT 企业背景', description: '开发团队成员来自多个大型传统 IT 企业,具备扎实的工程能力、规范化的交付流程和严格的质量意识。' },
|
||||
{ icon: 'Users', number: '03', title: '复合型技术团队', description: '既懂技术又懂业务,能够深入理解客户的真实场景和痛点,提供真正可落地的解决方案。' },
|
||||
{ icon: 'Code', number: '04', title: '全栈技术能力', description: '从前端到后端、从云原生到数据智能、从移动端到物联网——应对各种复杂技术挑战。' },
|
||||
{ icon: 'Target', number: '05', title: '结果导向交付', description: '不以"项目上线"为终点,以"客户业务是否真正改善"为衡量标准。每一个交付成果都追求可量化的业务价值。' },
|
||||
{ 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: [
|
||||
{ icon: 'Layers', number: '01', title: '扁平化协作', description: '没有冗长的汇报链条,每个人都有机会直接参与决策。信息透明,沟通高效。' },
|
||||
{ icon: 'BookOpen', number: '02', title: '持续学习', description: '技术日新月异,我们保持对新技术的好奇。内部分享、技术沙龙、外部培训——学习是日常的一部分。' },
|
||||
{ icon: 'Sparkles', number: '03', title: '客户成功', description: '我们的成就感来自客户的成功。客户的业务增长,就是我们最好的成绩单。' },
|
||||
{ iconName: 'Layers', number: '01', title: '扁平化协作', description: '没有冗长的汇报链条,每个人都有机会直接参与决策。信息透明,沟通高效。' },
|
||||
{ iconName: 'BookOpen', number: '02', title: '持续学习', description: '技术日新月异,我们保持对新技术的好奇。内部分享、技术沙龙、外部培训——学习是日常的一部分。' },
|
||||
{ iconName: 'Sparkles', number: '03', title: '客户成功', description: '我们的成就感来自客户的成功。客户的业务增长,就是我们最好的成绩单。' },
|
||||
],
|
||||
teamAboutTitle: '关于我们的团队',
|
||||
teamAboutParagraphs: [
|
||||
'我们的核心团队长期从事技术咨询、企业数字化等行业,拥有 12 年以上的深厚积累。',
|
||||
'开发团队成员来自于多个大型传统 IT 企业,具备扎实的工程能力和规范化的交付经验。',
|
||||
'我们相信,优秀的技术咨询不仅需要过硬的技术能力,更需要深入理解客户的业务场景和真实需求。每一位成员都是既懂技术又懂业务的复合型人才。',
|
||||
aboutTitle: '关于我们的团队',
|
||||
aboutParagraphs: [
|
||||
{ text: '我们的核心团队长期从事技术咨询、企业数字化等行业,拥有 12 年以上的深厚积累。' },
|
||||
{ text: '开发团队成员来自于多个大型传统 IT 企业,具备扎实的工程能力和规范化的交付经验。' },
|
||||
{ text: '我们相信,优秀的技术咨询不仅需要过硬的技术能力,更需要深入理解客户的业务场景和真实需求。每一位成员都是既懂技术又懂业务的复合型人才。' },
|
||||
],
|
||||
strengthsSectionTitle: '我们的底气,从何而来',
|
||||
strengthsSectionDescription: '不是自吹自擂,而是多年实战积累的真实能力',
|
||||
cultureSectionTitle: '我们怎样工作',
|
||||
cultureSectionDescription: '好的团队文化,最终体现在为客户创造的价值上',
|
||||
ctaTitle: '想成为我们的一员?',
|
||||
ctaDescription: '我们始终在寻找既懂技术又热爱业务的伙伴。如果您认同我们的理念,欢迎聊聊。',
|
||||
ctaPrimaryText: '联系我们',
|
||||
ctaPrimaryLink: '/contact',
|
||||
ctaSecondaryText: '了解公司',
|
||||
ctaSecondaryLink: '/about',
|
||||
ctaPrimaryLabel: '联系我们',
|
||||
ctaPrimaryHref: '/contact',
|
||||
ctaSecondaryLabel: '了解公司',
|
||||
ctaSecondaryHref: '/about',
|
||||
},
|
||||
]);
|
||||
console.log(' ✅ 团队页面');
|
||||
@@ -542,21 +780,18 @@ async function main() {
|
||||
heroSubtitle: '联系我们',
|
||||
heroTitle: '随时欢迎\n聊一聊',
|
||||
heroDescription: '工作日 2 小时内快速响应,首次咨询免费。\n没有销售压力,只是坐下来把问题理清楚。',
|
||||
companyName: '四川睿新致远科技有限公司',
|
||||
companyEmail: 'contact@novalon.cn',
|
||||
companyAddress: '中国四川省成都市龙泉驿区幸福路12号',
|
||||
workHoursWeekday: '周一至周五',
|
||||
workHoursTime: '9:00 - 18:00',
|
||||
promises: [
|
||||
{ text: '工作日 2 小时内快速响应您的咨询' },
|
||||
{ text: '提供免费的业务咨询和方案评估服务' },
|
||||
{ text: '根据您的需求量身定制最优解决方案' },
|
||||
email: 'contact@novalon.cn',
|
||||
address: '中国四川省成都市龙泉驿区幸福路12号',
|
||||
commitmentItems: [
|
||||
'工作日 2 小时内快速响应您的咨询',
|
||||
'提供免费的业务咨询和方案评估服务',
|
||||
'根据您的需求量身定制最优解决方案',
|
||||
],
|
||||
contactTitle: '联系方式',
|
||||
emailLabel: '邮箱',
|
||||
addressLabel: '地址',
|
||||
workHoursTitle: '工作时间',
|
||||
dayLabel: '工作日',
|
||||
dayLabel: '周一至周五',
|
||||
hours: '9:00 - 18:00',
|
||||
commitmentTitle: '服务承诺',
|
||||
formTitle: '发送消息',
|
||||
@@ -564,8 +799,13 @@ async function main() {
|
||||
submittingLabel: '发送中…',
|
||||
successTitle: '消息已发送',
|
||||
successMessage: '感谢您的留言,我们会尽快与您联系。',
|
||||
ctaSubtitle: '更直接的方式',
|
||||
ctaTitle: '还是想直接聊?',
|
||||
ctaDescription: '表单填起来太麻烦?直接发邮件到 contact@novalon.cn,我们同样会快速回复。',
|
||||
ctaPrimaryLabel: '发送邮件',
|
||||
ctaPrimaryHref: 'mailto:contact@novalon.cn',
|
||||
ctaSecondaryLabel: '返回首页',
|
||||
ctaSecondaryHref: '/',
|
||||
},
|
||||
]);
|
||||
console.log(' ✅ 联系页面');
|
||||
@@ -573,44 +813,22 @@ async function main() {
|
||||
console.log('📝 导入法律页面...');
|
||||
await seedItems('legal-page', [
|
||||
{
|
||||
slug: 'default',
|
||||
title: '法律条款',
|
||||
pageSubtitle: '隐私政策与服务条款',
|
||||
slug: 'privacy',
|
||||
title: '隐私政策',
|
||||
pageType: 'privacy',
|
||||
heroTitle: '隐私政策',
|
||||
heroDescription: '我们重视您的隐私,致力于保护您的个人信息安全',
|
||||
lastUpdated: '2026-02-26',
|
||||
privacyPolicy: {
|
||||
title: '隐私政策',
|
||||
subtitle: '我们重视您的隐私,致力于保护您的个人信息安全',
|
||||
lastUpdated: '2026-02-26',
|
||||
sections: [
|
||||
{ title: '引言', content: '四川睿新致远科技有限公司(以下简称"我们"、"公司")深知个人信息对您的重要性,并会尽全力保护您的个人信息安全可靠。我们致力于维持您对我们的信任,恪守以下原则,保护您的个人信息:权责一致原则、目的明确原则、选择同意原则、最少够用原则、确保安全原则、主体参与原则、公开透明原则等。本隐私政策适用于您通过四川睿新致远科技有限公司官方网站、移动应用、产品服务等渠道访问和使用我们的产品和服务时,我们收集和使用您的个人信息的情形。' },
|
||||
{ title: '一、我们如何收集和使用您的个人信息', content: '我们会遵循正当、合法、必要的原则,仅为实现产品功能,向您提供服务之目的,收集和使用您的个人信息。\n\n1.1 我们收集的个人信息:\n• 账户信息:当您注册账户时,我们可能收集您的姓名、电子邮箱地址、手机号码、公司名称等。\n• 联系信息:当您通过联系表单或客服与我们沟通时,我们可能收集您的姓名、电子邮箱地址、手机号码、留言内容等。\n• 使用信息:我们可能收集您使用我们产品和服务的信息,包括访问时间、浏览记录、操作日志等。\n• 设备信息:我们可能收集您使用的设备信息,包括设备型号、操作系统、浏览器类型等。\n\n1.2 我们如何使用您的个人信息:\n• 提供产品和服务:向您提供我们的产品和服务,处理您的请求和订单。\n• 客户服务:与您联系,提供客户支持,回复您的咨询和反馈。\n• 改进产品和服务:分析使用情况,改进我们的产品和服务质量。\n• 安全保障:检测、预防和处理安全事件,保护您和我们的合法权益。\n• 法律合规:遵守法律法规要求,履行法律义务。' },
|
||||
{ title: '二、我们如何共享、转让、公开披露您的个人信息', content: '2.1 共享:我们不会向其他任何公司、组织和个人分享您的个人信息,但以下情况除外:在获取明确同意的情况下分享;根据法律法规、法律程序、强制性的行政或司法要求所必须的情况进行提供;在涉及合并、收购或破产清算时,如涉及到个人信息转让,我们会要求新的持有您个人信息的公司、组织继续受本隐私政策的约束。\n\n2.2 转让:我们不会将您的个人信息转让给任何公司、组织和个人,但以下情况除外:在获取明确同意的情况下转让;根据适用的法律法规、法律程序、强制性的行政或司法要求所必须的情况进行提供;在涉及合并、收购或破产清算时。\n\n2.3 公开披露:我们仅会在以下情况下公开披露您的个人信息:获得您的明确同意;基于法律法规或法律程序的要求;在涉及合并、收购或破产清算时;为保护我们或他人的合法权益免受损害。' },
|
||||
{ title: '三、我们如何保护您的个人信息', content: '我们已使用符合业界标准的安全防护措施保护您提供的个人信息,防止数据遭到未经授权访问、公开披露、使用、修改、损坏或丢失。我们会采取一切合理可行的措施,保护您的个人信息:\n• 使用加密技术确保数据传输和存储安全。\n• 限制访问权限,仅授权人员可访问个人信息。\n• 定期进行安全审计和风险评估。\n• 建立数据备份和恢复机制。\n• 制定应急响应预案,及时处理安全事件。' },
|
||||
{ title: '四、您的权利', content: '按照中国相关的法律、法规、标准,以及其他国家、地区的通行做法,我们保障您对自己的个人信息行使以下权利:访问您的个人信息;更正您的个人信息;删除您的个人信息;改变您授权同意的范围;注销您的账户;获取您的个人信息副本。如您需要行使上述权利,请通过本隐私政策提供的联系方式与我们联系。' },
|
||||
{ title: '五、未成年人保护', content: '我们非常重视对未成年人个人信息的保护。如果您是18周岁以下的未成年人,在使用我们的产品和服务前,应事先取得您家长或法定监护人的同意。如您是未成年人的监护人,当您对您所监护的未成年人的个人信息处理存在疑问时,请通过本隐私政策提供的联系方式与我们联系。' },
|
||||
{ title: '六、隐私政策的更新', content: '我们可能适时更新本隐私政策的条款,该等更新构成本隐私政策的一部分。如该等更新造成您在本隐私政策下权利的实质减少,我们将在更新生效前通过在主页上显著位置提示或向您发送电子邮件或其他方式通知您,在该种情况下,若您继续使用我们的服务,即表示同意受经修订的本隐私政策的约束。' },
|
||||
{ title: '七、Cookie 和网站分析工具', content: '7.1 Cookie 使用说明:我们使用 Cookie 和类似技术来提供、保护和改进我们的服务。Cookie 是存储在您设备上的小型文本文件,帮助我们识别您的设备、记住您的偏好设置。\n\nCookie类型及用途:\n• 必要Cookie:网站基本功能运行,会话期间,必需\n• 分析Cookie:了解网站使用情况改进服务,14个月,非必需\n• 营销Cookie:个性化广告(当前未使用),非必需\n\n7.2 Google Analytics 使用说明:我们使用 Google Analytics 4(由 Google LLC 提供)分析网站使用情况。收集数据包括:访问的页面和停留时间;设备类型、浏览器类型;地理位置(国家/城市级别,IP地址已匿名化);访问来源。\n已采取的保护措施:IP地址匿名化;数据保留期限设为14个月;禁用广告个性化功能;禁用Google信号;不与Google其他服务共享数据用于广告目的。\n\n7.3 您的选择:您可以在首次访问时选择接受或拒绝分析Cookie;可以点击"Cookie设置"按钮随时更改偏好;可以通过浏览器设置删除或阻止Cookie。\n\n7.4 数据删除请求:如您希望删除我们持有的您的个人数据,请通过 privacy@novalon.cn 联系我们。联系地址:中国四川省成都市龙泉驿区幸福路12号。' },
|
||||
{ title: '八、如何联系我们', content: '如果您对本隐私政策有任何疑问、意见或建议,或需要行使您的权利,请通过以下方式与我们联系:\n\n公司名称:四川睿新致远科技有限公司\n联系邮箱:contact@novalon.cn\n隐私邮箱:privacy@novalon.cn\n联系地址:中国四川省成都市龙泉驿区幸福路12号' },
|
||||
],
|
||||
},
|
||||
termsOfService: {
|
||||
title: '服务条款',
|
||||
subtitle: '请仔细阅读以下服务条款,使用我们的服务即表示您同意这些条款',
|
||||
lastUpdated: '2026-02-26',
|
||||
sections: [
|
||||
{ title: '引言', content: '欢迎使用四川睿新致远科技有限公司(以下简称"我们"、"公司")提供的产品和服务。在使用我们的产品和服务之前,请您仔细阅读并理解本服务条款。如果您不同意本服务条款的任何内容,请停止使用我们的产品和服务。本服务条款是您与四川睿新致远科技有限公司之间就使用我们的产品和服务所订立的协议。我们有权根据需要不时修改本服务条款,修改后的条款一旦公布即代替原条款,恕不另行通知。' },
|
||||
{ title: '一、服务内容', content: '我们提供的产品和服务包括但不限于:软件开发、云服务、数据分析、信息安全、企业级软件产品(如ERP、CRM、后台管理系统、BI等)等。具体服务内容以我们官方网站或产品文档为准。我们保留随时修改、暂停或终止部分或全部服务的权利,无需事先通知。对于因服务修改、暂停或终止而给您造成的任何损失,我们不承担任何责任,除非法律另有规定。' },
|
||||
{ title: '二、用户注册与账户', content: '2.1 注册资格:您确认,在您完成注册程序或以其他方式实际使用本服务时,您应当是具备完全民事权利能力和完全民事行为能力的自然人、法人或其他组织。若您不具备前述主体资格,则您及您的监护人应承担因此而导致的一切后果,且我们有权注销或永久冻结您的账户。\n\n2.2 账户安全:您有责任维护您账户的安全性和保密性。您不得向任何第三方泄露您的账户信息,也不得与他人共享您的账户。如果您发现任何未经授权使用您账户的情况,应立即通知我们。您对您账户下发生的所有活动负责。我们对因您未能维护账户安全而造成的任何损失不承担责任。' },
|
||||
{ title: '三、用户行为规范', content: '在使用我们的产品和服务时,您同意遵守以下行为规范:\n• 遵守所有适用的法律法规和本服务条款。\n• 不得利用我们的产品和服务进行任何违法或不当活动。\n• 不得干扰或破坏我们的产品和服务或与我们的产品和服务相连的服务器和网络。\n• 不得上传、传播或存储任何违法、有害、威胁、诽谤、骚扰、侵权或其他不当内容。\n• 不得侵犯他人的知识产权、隐私权或其他合法权益。\n• 不得进行任何形式的商业欺诈或诈骗活动。\n• 不得恶意收集或获取其他用户的信息。\n如果我们认定您违反了本服务条款或任何适用法律,我们有权在不事先通知的情况下,暂停或终止您的账户,并拒绝您现在或将来使用我们的产品和服务。' },
|
||||
{ title: '四、知识产权', content: '我们的产品和服务中包含的所有内容,包括但不限于软件、设计、文字、图片、音频、视频、商标、服务标识等,均受著作权法、商标法、专利法或其他适用法律的保护。除非另有明确说明,我们拥有或持有我们产品和服务中所有内容的所有知识产权。您不得以任何形式复制、修改、传播、展示、执行、创作衍生作品、转让、出售或以其他方式使用这些内容,除非获得我们的明确书面许可。您在使用我们的产品和服务时产生的任何内容,您仍保留其知识产权,但您授予我们全球性、非独占性、免版税的许可,以使用、复制、修改、传播、展示和执行这些内容,仅限于提供和改进我们的产品和服务。' },
|
||||
{ title: '五、服务费用与支付', content: '5.1 费用标准:我们的产品和服务可能需要支付费用。具体费用标准以我们官方网站或产品文档公布的价格为准。我们保留随时调整价格的权利,调整后的价格适用于调整后的新订单或续费。\n\n5.2 支付方式:我们接受多种支付方式,包括但不限于银行转账、支付宝、微信支付等。\n\n5.3 退款政策:除非另有明确说明,我们提供的产品和服务一经售出,不予退款。如因我们的原因导致产品或服务无法正常使用,我们将根据实际情况提供相应的补偿或解决方案。' },
|
||||
{ title: '六、免责声明', content: '我们的产品和服务按"现状"和"可用"基础提供,不提供任何明示或暗示的保证,包括但不限于对适销性、适用性、非侵权性或准确性、可靠性的保证。\n\n我们不对以下情况承担责任:\n• 因您违反本服务条款或任何适用法律而导致的任何损失或损害。\n• 因不可抗力、网络故障、设备故障等不可控因素导致的服务中断或数据丢失。\n• 因第三方服务或内容导致的任何损失或损害。\n• 因您使用或无法使用我们的产品和服务而导致的任何间接、附带、特殊或后果性损害。' },
|
||||
{ title: '七、服务终止', content: '您可以随时停止使用我们的产品和服务,并注销您的账户。我们也有权在不事先通知的情况下,因以下原因暂停或终止您的账户:\n• 您违反本服务条款或任何适用法律。\n• 我们出于安全、法律或商业考虑,认为有必要终止您的账户。\n• 您长时间未使用您的账户。\n• 我们停止提供相关的产品和服务。' },
|
||||
{ title: '八、争议解决', content: '本服务条款的订立、执行、解释及争议解决均适用中华人民共和国法律。如就本服务条款发生任何争议,双方应首先通过友好协商解决;协商不成的,任何一方均可向公司所在地有管辖权的人民法院提起诉讼。' },
|
||||
{ title: '九、其他条款', content: '• 本服务条款构成您与我们就使用我们的产品和服务所达成的完整协议,取代之前的所有口头或书面协议。\n• 如本服务条款的任何条款被认定为无效或不可执行,该条款应被限制或排除,以使其有效和可执行,其余条款继续有效。\n• 我们未行使或延迟行使本服务条款项下的任何权利或规定,不构成对该权利或规定的放弃。\n• 本服务条款的标题仅为方便而设,不影响本服务条款的解释。' },
|
||||
{ title: '十、联系我们', content: '如果您对本服务条款有任何疑问、意见或建议,请通过以下方式与我们联系:\n\n公司名称:四川睿新致远科技有限公司\n联系邮箱:contact@novalon.cn\n联系地址:中国四川省成都市龙泉驿区幸福路12号' },
|
||||
],
|
||||
},
|
||||
content: PRIVACY_POLICY_HTML,
|
||||
},
|
||||
{
|
||||
slug: 'terms',
|
||||
title: '服务条款',
|
||||
pageType: 'terms',
|
||||
heroTitle: '服务条款',
|
||||
heroDescription: '请仔细阅读以下服务条款,使用我们的服务即表示您同意这些条款',
|
||||
lastUpdated: '2026-02-26',
|
||||
content: TERMS_OF_SERVICE_HTML,
|
||||
},
|
||||
]);
|
||||
console.log(' ✅ 法律页面');
|
||||
@@ -648,87 +866,8 @@ async function main() {
|
||||
|
||||
// ============ 5. 创建默认内容区域 ============
|
||||
console.log('📝 创建默认内容区域...');
|
||||
const defaultZones = [
|
||||
{
|
||||
code: 'home-stats',
|
||||
name: '首页 - 数据指标',
|
||||
pageCode: 'home',
|
||||
zoneKey: 'stats',
|
||||
allowedModels: ['stat-item'],
|
||||
items: statList.slice(0, 4).map((_: unknown, i: number) => ({
|
||||
itemId: `stat-${i}`,
|
||||
sortOrder: i,
|
||||
variant: 'default',
|
||||
})),
|
||||
},
|
||||
{
|
||||
code: 'home-services',
|
||||
name: '首页 - 服务展示',
|
||||
pageCode: 'home',
|
||||
zoneKey: 'services',
|
||||
allowedModels: ['service'],
|
||||
items: SERVICES.slice(0, 6).map((_: unknown, i: number) => {
|
||||
const s = _ as Record<string, unknown>;
|
||||
return { itemId: String(s.id || `service-${i}`), sortOrder: i, variant: 'default' };
|
||||
}),
|
||||
},
|
||||
{
|
||||
code: 'home-solutions',
|
||||
name: '首页 - 解决方案',
|
||||
pageCode: 'home',
|
||||
zoneKey: 'solutions',
|
||||
allowedModels: ['solution'],
|
||||
items: SOLUTIONS.slice(0, 6).map((_: unknown, i: number) => {
|
||||
const s = _ as Record<string, unknown>;
|
||||
return { itemId: String(s.id || `solution-${i}`), sortOrder: i, variant: 'default' };
|
||||
}),
|
||||
},
|
||||
{
|
||||
code: 'home-cases',
|
||||
name: '首页 - 精选案例',
|
||||
pageCode: 'home',
|
||||
zoneKey: 'cases',
|
||||
allowedModels: ['case-study'],
|
||||
items: CASE_STUDIES.filter((cs) => cs.featured).slice(0, 3).map((cs, i: number) => ({
|
||||
itemId: cs.id,
|
||||
sortOrder: i,
|
||||
variant: 'default',
|
||||
})),
|
||||
},
|
||||
{
|
||||
code: 'home-news',
|
||||
name: '首页 - 新闻动态',
|
||||
pageCode: 'home',
|
||||
zoneKey: 'news',
|
||||
allowedModels: ['news'],
|
||||
items: NEWS.slice(0, 3).map((_: unknown, i: number) => {
|
||||
const n = _ as Record<string, unknown>;
|
||||
return { itemId: String(n.id || `news-${i}`), sortOrder: i, variant: 'default' };
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
for (const zone of defaultZones) {
|
||||
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}`);
|
||||
}
|
||||
await seedHomeZones();
|
||||
console.log(' ✅ 首页内容区域创建完成');
|
||||
|
||||
// ============ 统计 ============
|
||||
const itemCount = await prisma.contentItem.count();
|
||||
|
||||
Reference in New Issue
Block a user