补齐数据库迁移链路并收紧初始化默认值

This commit is contained in:
WuYongJie 2026-05-25 16:59:59 +08:00
parent 25316809db
commit 1aeadcc425
22 changed files with 842 additions and 226 deletions

View File

@ -0,0 +1,143 @@
-- CreateEnum
CREATE TYPE "Role" AS ENUM ('USER', 'ADMIN');
-- CreateTable
CREATE TABLE IF NOT EXISTS "users" (
"id" TEXT NOT NULL,
"email" TEXT NOT NULL,
"username" TEXT NOT NULL,
"password" TEXT NOT NULL,
"role" "Role" NOT NULL DEFAULT 'USER',
"isActive" BOOLEAN NOT NULL DEFAULT false,
"canDownload" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE IF NOT EXISTS "materials" (
"id" TEXT NOT NULL,
"previewUrl" TEXT NOT NULL,
"productModel" TEXT NOT NULL,
"categoryName" TEXT NOT NULL,
"sceneName" TEXT NOT NULL,
"materialType" TEXT NOT NULL DEFAULT '3DMAX',
"renderer" TEXT NOT NULL DEFAULT 'CR',
"authorName" TEXT NOT NULL,
"zipFileName" TEXT NOT NULL,
"zipFilePath" TEXT NOT NULL,
"zipFileSize" BIGINT NOT NULL DEFAULT 0,
"downloadCount" INTEGER NOT NULL DEFAULT 0,
"likeCount" INTEGER NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"userId" TEXT NOT NULL,
CONSTRAINT "materials_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE IF NOT EXISTS "material_likes" (
"id" TEXT NOT NULL,
"materialId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "material_likes_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE IF NOT EXISTS "categories" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "categories_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE IF NOT EXISTS "scenes" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "scenes_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE IF NOT EXISTS "system_settings" (
"id" TEXT NOT NULL,
"headerBgColor" TEXT NOT NULL DEFAULT '#1f2937',
"logoUrl" TEXT NOT NULL DEFAULT 'http://s3.1080p.hk/shuzhou/2025/05/18/8ebadf0a7cc34eaea00e8619b8efb827.png',
"siteName" TEXT NOT NULL DEFAULT 'FLNXI素材库',
"updatedAt" TIMESTAMP(3) NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "system_settings_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX IF NOT EXISTS "users_email_key" ON "users"("email");
-- CreateIndex
CREATE UNIQUE INDEX IF NOT EXISTS "users_username_key" ON "users"("username");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "materials_userId_idx" ON "materials"("userId");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "materials_createdAt_idx" ON "materials"("createdAt");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "materials_categoryName_idx" ON "materials"("categoryName");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "materials_sceneName_idx" ON "materials"("sceneName");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "materials_materialType_idx" ON "materials"("materialType");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "materials_downloadCount_idx" ON "materials"("downloadCount");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "materials_likeCount_idx" ON "materials"("likeCount");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "material_likes_materialId_idx" ON "material_likes"("materialId");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "material_likes_userId_idx" ON "material_likes"("userId");
-- CreateIndex
CREATE UNIQUE INDEX IF NOT EXISTS "material_likes_materialId_userId_key" ON "material_likes"("materialId", "userId");
-- CreateIndex
CREATE UNIQUE INDEX IF NOT EXISTS "categories_name_key" ON "categories"("name");
-- CreateIndex
CREATE UNIQUE INDEX IF NOT EXISTS "scenes_name_key" ON "scenes"("name");
-- AddForeignKey(Postgres 不支持 IF NOT EXISTS,因此使用 DO 块捕获重复异常)
DO $$
BEGIN
ALTER TABLE "materials" ADD CONSTRAINT "materials_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
EXCEPTION
WHEN duplicate_object THEN NULL;
END $$;
DO $$
BEGIN
ALTER TABLE "material_likes" ADD CONSTRAINT "material_likes_materialId_fkey" FOREIGN KEY ("materialId") REFERENCES "materials"("id") ON DELETE CASCADE ON UPDATE CASCADE;
EXCEPTION
WHEN duplicate_object THEN NULL;
END $$;
DO $$
BEGIN
ALTER TABLE "material_likes" ADD CONSTRAINT "material_likes_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
EXCEPTION
WHEN duplicate_object THEN NULL;
END $$;

View File

@ -0,0 +1,50 @@
-- 基线迁移:为上传模板体系创建基础表,便于后续增量迁移在 shadow 数据库中正常回放
-- 使用 IF NOT EXISTS 保证在已有线上库上执行时是幂等、安全的
-- UploadTemplate:上传模板主表
CREATE TABLE IF NOT EXISTS "UploadTemplate" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"status" TEXT NOT NULL DEFAULT 'draft',
"materialType" TEXT NOT NULL DEFAULT '其他',
"materialKind" TEXT NOT NULL DEFAULT 'IMAGE',
"version" INTEGER NOT NULL DEFAULT 1,
"fieldGroups" TEXT,
"displayConfig" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "UploadTemplate_pkey" PRIMARY KEY ("id")
);
-- UploadField:模板字段表
CREATE TABLE IF NOT EXISTS "UploadField" (
"id" TEXT NOT NULL,
"templateId" TEXT NOT NULL,
"key" TEXT NOT NULL,
"label" TEXT NOT NULL,
"type" TEXT NOT NULL,
"required" BOOLEAN NOT NULL DEFAULT false,
"orderIndex" INTEGER NOT NULL DEFAULT 0,
"layoutWidth" TEXT,
"validations" TEXT,
"optionsSource" TEXT,
"optionsStatic" TEXT,
"allowedExtensions" TEXT,
"visibleOnHomeFilter" BOOLEAN NOT NULL DEFAULT false,
"isSearchable" BOOLEAN NOT NULL DEFAULT false,
"isReadOnly" BOOLEAN NOT NULL DEFAULT false,
"addToCategory" BOOLEAN NOT NULL DEFAULT false,
"conditions" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "UploadField_pkey" PRIMARY KEY ("id")
);
-- 关联关系:字段隶属于模板
ALTER TABLE "UploadField"
ADD CONSTRAINT "UploadField_templateId_fkey"
FOREIGN KEY ("templateId") REFERENCES "UploadTemplate"("id")
ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -0,0 +1,53 @@
-- 确保上传模板/字段基础表存在(若之前迁移缺失时可兜底创建)
CREATE TABLE IF NOT EXISTS "UploadTemplate" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"status" TEXT NOT NULL DEFAULT 'draft',
"materialType" TEXT NOT NULL DEFAULT '其他',
"materialKind" TEXT NOT NULL DEFAULT 'IMAGE',
"version" INTEGER NOT NULL DEFAULT 1,
"fieldGroups" TEXT,
"displayConfig" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "UploadTemplate_pkey" PRIMARY KEY ("id")
);
CREATE TABLE IF NOT EXISTS "UploadField" (
"id" TEXT NOT NULL,
"templateId" TEXT NOT NULL,
"key" TEXT NOT NULL,
"label" TEXT NOT NULL,
"type" TEXT NOT NULL,
"required" BOOLEAN NOT NULL DEFAULT false,
"orderIndex" INTEGER NOT NULL DEFAULT 0,
"layoutWidth" TEXT,
"validations" TEXT,
"optionsSource" TEXT,
"optionsStatic" TEXT,
"allowedExtensions" TEXT,
"visibleOnHomeFilter" BOOLEAN NOT NULL DEFAULT false,
"isSearchable" BOOLEAN NOT NULL DEFAULT false,
"isReadOnly" BOOLEAN NOT NULL DEFAULT false,
"addToCategory" BOOLEAN NOT NULL DEFAULT false,
"conditions" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "UploadField_pkey" PRIMARY KEY ("id")
);
DO $$
BEGIN
ALTER TABLE "UploadField"
ADD CONSTRAINT "UploadField_templateId_fkey"
FOREIGN KEY ("templateId") REFERENCES "UploadTemplate"("id")
ON DELETE CASCADE ON UPDATE CASCADE;
EXCEPTION
WHEN duplicate_object THEN NULL;
END $$;
-- AlterTable(幂等,确保列存在)
ALTER TABLE "UploadField" ADD COLUMN IF NOT EXISTS "addToCategory" BOOLEAN NOT NULL DEFAULT false;

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "system_settings" ADD COLUMN IF NOT EXISTS "hiddenBuiltinTemplates" TEXT;

View File

@ -0,0 +1,69 @@
-- 将 hiddenBuiltinTemplates 字段重命名为 enabledBuiltinTemplates
-- 逻辑反转:之前存储隐藏的列表,现在存储启用的列表
-- 默认全部启用(空数组或null表示全部启用)
-- 所有预制模板key
DO $$
DECLARE
hidden_list TEXT;
enabled_list TEXT;
all_keys TEXT[] := ARRAY['3dmax-material', 'psd-material', 'ai-material', 'cad-material', 'c4d-material', 'video-material', 'texture-material', 'model-material', 'standard-accessory-material', 'other-material'];
hidden_array TEXT[];
enabled_array TEXT[];
key_item TEXT;
BEGIN
-- 检查字段是否存在
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'system_settings'
AND column_name = 'hiddenBuiltinTemplates'
) THEN
-- 获取当前的 hiddenBuiltinTemplates 值
SELECT "hiddenBuiltinTemplates" INTO hidden_list FROM "system_settings" LIMIT 1;
-- 如果 hiddenBuiltinTemplates 存在且有值,转换为启用列表
IF hidden_list IS NOT NULL AND hidden_list != '' THEN
BEGIN
-- 解析隐藏列表
SELECT ARRAY(SELECT json_array_elements_text(hidden_list::json)) INTO hidden_array;
-- 将隐藏的列表转换为启用的列表(取反:所有key减去隐藏的key)
enabled_array := ARRAY(
SELECT unnest(all_keys)
EXCEPT
SELECT unnest(hidden_array)
);
-- 转换为JSON数组
enabled_list := COALESCE(
(SELECT json_agg(value) FROM unnest(enabled_array) AS value),
'[]'::json
)::text;
EXCEPTION WHEN OTHERS THEN
-- 解析失败,设置为空数组(表示全部启用)
enabled_list := '[]';
END;
ELSE
-- 如果没有隐藏列表,表示全部启用,设置为空数组
enabled_list := '[]';
END IF;
-- 重命名字段
ALTER TABLE "system_settings"
RENAME COLUMN "hiddenBuiltinTemplates" TO "enabledBuiltinTemplates";
-- 更新值
UPDATE "system_settings"
SET "enabledBuiltinTemplates" = enabled_list;
ELSE
-- 如果字段不存在,直接创建新字段
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'system_settings'
AND column_name = 'enabledBuiltinTemplates'
) THEN
ALTER TABLE "system_settings"
ADD COLUMN "enabledBuiltinTemplates" TEXT DEFAULT '[]';
END IF;
END IF;
END $$;

View File

@ -0,0 +1,7 @@
-- 添加 materialType 字段到上传模板,默认值为“其他”
ALTER TABLE "UploadTemplate"
ADD COLUMN IF NOT EXISTS "materialType" TEXT NOT NULL DEFAULT '其他';
-- 为已有数据回填 materialType(保持默认值)
UPDATE "UploadTemplate"
SET "materialType" = COALESCE(NULLIF("materialType", ''), '其他');

View File

@ -0,0 +1,3 @@
-- 为 system_settings 表新增分类字段管理配置列
ALTER TABLE "system_settings"
ADD COLUMN IF NOT EXISTS "categoryManagedFields" TEXT;

View File

@ -0,0 +1,27 @@
-- AlterTable
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "brandName" TEXT;
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "seriesName" TEXT;
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "skuCode" TEXT;
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "materialTexture" TEXT;
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "renderTime" TEXT;
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "imageResolution" TEXT;
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "colorMode" TEXT;
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "displayImages" TEXT NOT NULL DEFAULT '';
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "platformName" TEXT NOT NULL DEFAULT '';
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "previewUrls" TEXT;
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "metadata" TEXT;
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "templateId" TEXT;
-- CreateIndex
CREATE INDEX IF NOT EXISTS "materials_platformName_idx" ON "materials"("platformName");
-- AddForeignKey
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'materials_templateId_fkey'
) THEN
ALTER TABLE "materials" ADD CONSTRAINT "materials_templateId_fkey" FOREIGN KEY ("templateId") REFERENCES "UploadTemplate"("id") ON DELETE SET NULL ON UPDATE CASCADE;
END IF;
END $$;

View File

@ -0,0 +1,38 @@
-- CreateTable
CREATE TABLE "FieldDefinition" (
"id" TEXT NOT NULL,
"key" TEXT NOT NULL,
"label" TEXT NOT NULL,
"type" TEXT NOT NULL DEFAULT 'select',
"description" TEXT,
"source" TEXT NOT NULL DEFAULT 'custom',
"sourceTemplateIds" TEXT,
"isStandard" BOOLEAN NOT NULL DEFAULT false,
"isFilterEnabled" BOOLEAN NOT NULL DEFAULT false,
"filterDisplayName" TEXT,
"filterOrder" INTEGER NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "FieldDefinition_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "FieldOption" (
"id" TEXT NOT NULL,
"fieldId" TEXT NOT NULL,
"value" TEXT NOT NULL,
"label" TEXT NOT NULL,
"orderIndex" INTEGER NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "FieldOption_pkey" PRIMARY KEY ("id"),
CONSTRAINT "FieldOption_fieldId_fkey" FOREIGN KEY ("fieldId") REFERENCES "FieldDefinition"("id") ON DELETE CASCADE ON UPDATE NO ACTION
);
-- CreateIndex
CREATE UNIQUE INDEX "FieldDefinition_key_key" ON "FieldDefinition"("key");
-- CreateIndex
CREATE UNIQUE INDEX "FieldOption_fieldId_value_key" ON "FieldOption"("fieldId", "value");

View File

@ -0,0 +1,9 @@
-- 为素材表增加视频相关字段,用于后续视频预览 / 转码能力
ALTER TABLE "materials"
ADD COLUMN "videoSourceUrl" TEXT,
ADD COLUMN "videoPreview540Url" TEXT,
ADD COLUMN "videoPreviewClipUrl" TEXT,
ADD COLUMN "videoCoverUrl" TEXT,
ADD COLUMN "videoDuration" INTEGER,
ADD COLUMN "videoProcessingStatus" TEXT;

View File

@ -0,0 +1,11 @@
-- 为上传模板增加 materialKind 字段,用于区分图片/视频等素材大类
ALTER TABLE "UploadTemplate"
ADD COLUMN IF NOT EXISTS "materialKind" TEXT NOT NULL DEFAULT 'IMAGE';
-- 依据 materialType 初步归类已有模板
UPDATE "UploadTemplate"
SET "materialKind" = CASE
WHEN "materialType" = '视频' THEN 'VIDEO'
WHEN "materialType" = '其他' THEN 'OTHER'
ELSE 'IMAGE'
END;

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "materialKind" TEXT NOT NULL DEFAULT 'IMAGE';

View File

@ -0,0 +1,18 @@
-- Add ownership fields to materials for clean user/team attribution
-- ownerType: 素材归属类型(USER / TEAM),默认 TEAM
-- ownerId: 归属主体ID(团队空间ID或用户ID),默认 default-team
-- createdByUserId: 最初上传者用户ID
-- lastTransferredAt: 最近一次所有权移交时间
ALTER TABLE "materials"
ADD COLUMN IF NOT EXISTS "ownerType" TEXT NOT NULL DEFAULT 'TEAM',
ADD COLUMN IF NOT EXISTS "ownerId" TEXT NOT NULL DEFAULT 'default-team',
ADD COLUMN IF NOT EXISTS "createdByUserId" TEXT,
ADD COLUMN IF NOT EXISTS "lastTransferredAt" TIMESTAMPTZ;
-- 索引:加速基于归属的查询
CREATE INDEX IF NOT EXISTS "materials_ownerType_ownerId_idx"
ON "materials" ("ownerType", "ownerId");
CREATE INDEX IF NOT EXISTS "materials_createdByUserId_idx"
ON "materials" ("createdByUserId");

View File

@ -0,0 +1,291 @@
/*
Warnings:
- You are about to drop the column `logoUrl` on the `system_settings` table. All the data in the column will be lost.
- A unique constraint covering the columns `[dingtalkUserId]` on the table `users` will be added. If there are existing duplicate values, this will fail.
- A unique constraint covering the columns `[phone]` on the table `users` will be added. If there are existing duplicate values, this will fail.
*/
-- AlterTable
ALTER TABLE "FieldDefinition" ALTER COLUMN "updatedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "FieldOption" ALTER COLUMN "updatedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "UploadField" ALTER COLUMN "updatedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "UploadTemplate" ALTER COLUMN "updatedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "categories" ADD COLUMN "options" TEXT;
-- AlterTable
ALTER TABLE "materials" ADD COLUMN "deletedAt" TIMESTAMP(3),
ADD COLUMN "isPremium" BOOLEAN NOT NULL DEFAULT false,
ALTER COLUMN "zipFileName" SET DEFAULT '',
ALTER COLUMN "zipFilePath" SET DEFAULT '',
ALTER COLUMN "lastTransferredAt" SET DATA TYPE TIMESTAMP(3);
-- AlterTable
ALTER TABLE "scenes" ADD COLUMN "options" TEXT;
-- AlterTable
ALTER TABLE "system_settings" DROP COLUMN "logoUrl",
ADD COLUMN "enableAiImageEdit" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "enableRegistration" BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN "enableTunnelService" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "enableUserActivation" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "headerLogoUrl" TEXT NOT NULL DEFAULT 'http://s3.1080p.hk/shuzhou/2025/05/18/8ebadf0a7cc34eaea00e8619b8efb827.png',
ADD COLUMN "loginLogoUrl" TEXT NOT NULL DEFAULT 'http://s3.1080p.hk/shuzhou/2025/05/18/8ebadf0a7cc34eaea00e8619b8efb827.png',
ADD COLUMN "premiumRatio" DOUBLE PRECISION NOT NULL DEFAULT 10.0,
ADD COLUMN "s3AccessKey" TEXT NOT NULL DEFAULT '',
ADD COLUMN "s3Bucket" TEXT NOT NULL DEFAULT '',
ADD COLUMN "s3Endpoint" TEXT NOT NULL DEFAULT '',
ADD COLUMN "s3PublicUrl" TEXT NOT NULL DEFAULT '',
ADD COLUMN "s3Region" TEXT NOT NULL DEFAULT 'us-east-1',
ADD COLUMN "s3SecretKey" TEXT NOT NULL DEFAULT '';
-- AlterTable
ALTER TABLE "users" ADD COLUMN "avatar" TEXT,
ADD COLUMN "department" TEXT NOT NULL DEFAULT '',
ADD COLUMN "dingtalkUserId" TEXT,
ADD COLUMN "isSuperAdmin" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "loginType" TEXT NOT NULL DEFAULT 'normal',
ADD COLUMN "managedDepartment" TEXT,
ADD COLUMN "parentId" TEXT,
ADD COLUMN "phone" TEXT,
ADD COLUMN "realName" TEXT;
-- CreateTable
CREATE TABLE "FilterItem" (
"id" TEXT NOT NULL,
"fieldKey" TEXT NOT NULL,
"label" TEXT NOT NULL,
"group" TEXT,
"orderIndex" INTEGER NOT NULL DEFAULT 0,
"multiSelect" BOOLEAN NOT NULL DEFAULT false,
"showOnHome" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "FilterItem_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "material_favorites" (
"id" TEXT NOT NULL,
"materialId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "material_favorites_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "material_download_logs" (
"id" TEXT NOT NULL,
"materialId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"downloadedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "material_download_logs_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "material_logs" (
"id" TEXT NOT NULL,
"materialId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"action" TEXT NOT NULL,
"changes" JSONB,
"timestamp" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "material_logs_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "platforms" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"options" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "platforms_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "departments" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "departments_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "danmakus" (
"id" TEXT NOT NULL,
"content" TEXT NOT NULL,
"username" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "danmakus_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "invitations" (
"id" TEXT NOT NULL,
"code" TEXT NOT NULL,
"createdBy" TEXT NOT NULL,
"usedBy" TEXT,
"usedAt" TIMESTAMP(3),
"expiresAt" TIMESTAMP(3),
"maxUses" INTEGER NOT NULL DEFAULT 1,
"currentUses" INTEGER NOT NULL DEFAULT 0,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "invitations_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "material_favorites_materialId_idx" ON "material_favorites"("materialId");
-- CreateIndex
CREATE INDEX "material_favorites_userId_idx" ON "material_favorites"("userId");
-- CreateIndex
CREATE UNIQUE INDEX "material_favorites_materialId_userId_key" ON "material_favorites"("materialId", "userId");
-- CreateIndex
CREATE INDEX "material_download_logs_materialId_idx" ON "material_download_logs"("materialId");
-- CreateIndex
CREATE INDEX "material_download_logs_userId_idx" ON "material_download_logs"("userId");
-- CreateIndex
CREATE INDEX "material_download_logs_downloadedAt_idx" ON "material_download_logs"("downloadedAt");
-- CreateIndex
CREATE INDEX "material_download_logs_materialId_userId_downloadedAt_idx" ON "material_download_logs"("materialId", "userId", "downloadedAt");
-- CreateIndex
CREATE INDEX "material_logs_materialId_idx" ON "material_logs"("materialId");
-- CreateIndex
CREATE INDEX "material_logs_userId_idx" ON "material_logs"("userId");
-- CreateIndex
CREATE INDEX "material_logs_timestamp_idx" ON "material_logs"("timestamp");
-- CreateIndex
CREATE UNIQUE INDEX "platforms_name_key" ON "platforms"("name");
-- CreateIndex
CREATE UNIQUE INDEX "departments_name_key" ON "departments"("name");
-- CreateIndex
CREATE INDEX "danmakus_createdAt_idx" ON "danmakus"("createdAt");
-- CreateIndex
CREATE UNIQUE INDEX "invitations_code_key" ON "invitations"("code");
-- CreateIndex
CREATE INDEX "invitations_code_idx" ON "invitations"("code");
-- CreateIndex
CREATE INDEX "invitations_createdBy_idx" ON "invitations"("createdBy");
-- CreateIndex
CREATE INDEX "FieldDefinition_source_idx" ON "FieldDefinition"("source");
-- CreateIndex
CREATE INDEX "FieldDefinition_isStandard_idx" ON "FieldDefinition"("isStandard");
-- CreateIndex
CREATE INDEX "FieldDefinition_isFilterEnabled_idx" ON "FieldDefinition"("isFilterEnabled");
-- CreateIndex
CREATE INDEX "FieldDefinition_filterOrder_idx" ON "FieldDefinition"("filterOrder");
-- CreateIndex
CREATE INDEX "FieldDefinition_isFilterEnabled_filterOrder_idx" ON "FieldDefinition"("isFilterEnabled", "filterOrder");
-- CreateIndex
CREATE INDEX "FieldOption_fieldId_idx" ON "FieldOption"("fieldId");
-- CreateIndex
CREATE INDEX "FieldOption_fieldId_orderIndex_idx" ON "FieldOption"("fieldId", "orderIndex");
-- CreateIndex
CREATE INDEX "UploadField_templateId_idx" ON "UploadField"("templateId");
-- CreateIndex
CREATE INDEX "UploadField_key_idx" ON "UploadField"("key");
-- CreateIndex
CREATE INDEX "UploadField_type_idx" ON "UploadField"("type");
-- CreateIndex
CREATE INDEX "UploadField_visibleOnHomeFilter_idx" ON "UploadField"("visibleOnHomeFilter");
-- CreateIndex
CREATE INDEX "UploadField_isSearchable_idx" ON "UploadField"("isSearchable");
-- CreateIndex
CREATE INDEX "UploadField_addToCategory_idx" ON "UploadField"("addToCategory");
-- CreateIndex
CREATE INDEX "UploadField_orderIndex_idx" ON "UploadField"("orderIndex");
-- CreateIndex
CREATE INDEX "UploadField_templateId_orderIndex_idx" ON "UploadField"("templateId", "orderIndex");
-- CreateIndex
CREATE INDEX "UploadTemplate_status_idx" ON "UploadTemplate"("status");
-- CreateIndex
CREATE INDEX "UploadTemplate_materialType_idx" ON "UploadTemplate"("materialType");
-- CreateIndex
CREATE INDEX "UploadTemplate_materialKind_idx" ON "UploadTemplate"("materialKind");
-- CreateIndex
CREATE INDEX "UploadTemplate_createdAt_idx" ON "UploadTemplate"("createdAt");
-- CreateIndex
CREATE INDEX "UploadTemplate_updatedAt_idx" ON "UploadTemplate"("updatedAt");
-- CreateIndex
CREATE INDEX "materials_deletedAt_idx" ON "materials"("deletedAt");
-- CreateIndex
CREATE UNIQUE INDEX "users_dingtalkUserId_key" ON "users"("dingtalkUserId");
-- CreateIndex
CREATE UNIQUE INDEX "users_phone_key" ON "users"("phone");
-- AddForeignKey
ALTER TABLE "users" ADD CONSTRAINT "users_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "material_favorites" ADD CONSTRAINT "material_favorites_materialId_fkey" FOREIGN KEY ("materialId") REFERENCES "materials"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "material_favorites" ADD CONSTRAINT "material_favorites_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "material_download_logs" ADD CONSTRAINT "material_download_logs_materialId_fkey" FOREIGN KEY ("materialId") REFERENCES "materials"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "material_download_logs" ADD CONSTRAINT "material_download_logs_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "material_logs" ADD CONSTRAINT "material_logs_materialId_fkey" FOREIGN KEY ("materialId") REFERENCES "materials"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "material_logs" ADD CONSTRAINT "material_logs_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -0,0 +1,13 @@
-- CreateTable
CREATE TABLE "system_configs" (
"id" TEXT NOT NULL,
"key" TEXT NOT NULL,
"value" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "system_configs_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "system_configs_key_key" ON "system_configs"("key");

View File

@ -0,0 +1,5 @@
-- 添加音频素材相关字段
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "audioDuration" TEXT;
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "audioCategory" TEXT;
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "audioScene" TEXT;
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "audioUrl" TEXT;

View File

@ -0,0 +1,14 @@
-- 默认 Logo 不再指向旧对象存储,避免新安装继承旧环境地址
ALTER TABLE "system_settings"
ALTER COLUMN "headerLogoUrl" SET DEFAULT '/logo/logo2.png';
ALTER TABLE "system_settings"
ALTER COLUMN "loginLogoUrl" SET DEFAULT '/logo/logo2.png';
UPDATE "system_settings"
SET "headerLogoUrl" = '/logo/logo2.png'
WHERE "headerLogoUrl" LIKE 'http://s3.1080p.hk/%';
UPDATE "system_settings"
SET "loginLogoUrl" = '/logo/logo2.png'
WHERE "loginLogoUrl" LIKE 'http://s3.1080p.hk/%';

View File

@ -0,0 +1,14 @@
-- 默认 Logo 不再指向不存在的公共静态资源,避免新安装和旧数据继承死路径
ALTER TABLE "system_settings"
ALTER COLUMN "headerLogoUrl" SET DEFAULT '';
ALTER TABLE "system_settings"
ALTER COLUMN "loginLogoUrl" SET DEFAULT '';
UPDATE "system_settings"
SET "headerLogoUrl" = ''
WHERE "headerLogoUrl" IN ('/logo/logo1.png', '/logo/logo2.png', '/logo/logo1', '/logo/logo2');
UPDATE "system_settings"
SET "loginLogoUrl" = ''
WHERE "loginLogoUrl" IN ('/logo/logo1.png', '/logo/logo2.png', '/logo/logo1', '/logo/logo2');

View File

@ -79,6 +79,7 @@ model MaterialShareToken {
@@index([revokedAt])
@@map("material_share_tokens")
}
model UploadTemplate {
id String @id @default(uuid())
name String
@ -372,8 +373,8 @@ model SystemSettings {
premiumRatio Float @default(10.0)
updatedAt DateTime @updatedAt
createdAt DateTime @default(now())
headerLogoUrl String @default("http://s3.1080p.hk/shuzhou/2025/05/18/8ebadf0a7cc34eaea00e8619b8efb827.png")
loginLogoUrl String @default("http://s3.1080p.hk/shuzhou/2025/05/18/8ebadf0a7cc34eaea00e8619b8efb827.png")
headerLogoUrl String @default("")
loginLogoUrl String @default("")
s3AccessKey String @default("")
s3Bucket String @default("")
s3Endpoint String @default("")

View File

@ -17,113 +17,13 @@
import { PrismaClient } from '@prisma/client'
import bcrypt from 'bcryptjs'
import {
BUILTIN_TEMPLATE_KEYS,
createBuiltinTemplate,
} from '../lib/upload-template-presets'
const prisma = new PrismaClient()
// ============================================================================
// 内置模板配置(与 lib/system-integrity.ts 保持一致)
// ============================================================================
const BUILTIN_TEMPLATE_KEYS = [
'3dmax-material',
'psd-material',
'audio-material',
'video-material',
'model-material',
'other-material',
] as const
type BuiltinTemplateKey = (typeof BUILTIN_TEMPLATE_KEYS)[number]
const BUILTIN_TEMPLATE_CONFIG: Record<
BuiltinTemplateKey,
{
name: string
description: string
materialType: string
materialKind: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER'
fields: Array<{
key: string
label: string
type: string
required: boolean
layoutWidth: string
visibleOnHomeFilter: boolean
isSearchable: boolean
addToCategory?: boolean
}>
}
> = {
'3dmax-material': {
name: '3DMAX 素材模板',
description: '适用于 3D 模型类素材',
materialType: '3DMAX',
materialKind: 'IMAGE',
fields: [
{ key: 'productModel', label: 'SPU型号', type: 'text', required: true, layoutWidth: 'full', visibleOnHomeFilter: false, isSearchable: true },
{ key: 'categoryName', label: '使用店铺', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
{ key: 'sceneName', label: '场景类别', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
{ key: 'renderer', label: '渲染器', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: false, isSearchable: true, addToCategory: true },
],
},
'psd-material': {
name: 'PSD 素材模板',
description: '针对平面设计 PSD 素材',
materialType: 'PSD',
materialKind: 'IMAGE',
fields: [
{ key: 'productModel', label: 'SPU型号', type: 'text', required: true, layoutWidth: 'full', visibleOnHomeFilter: false, isSearchable: true },
{ key: 'categoryName', label: '使用店铺', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
{ key: 'sceneName', label: '场景类别', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
],
},
'audio-material': {
name: '音频素材模板',
description: '适用于音频文件素材,支持 MP3、WAV、FLAC、AAC 等格式',
materialType: '音频',
materialKind: 'AUDIO',
fields: [
{ key: 'productModel', label: '音频名称', type: 'text', required: true, layoutWidth: 'full', visibleOnHomeFilter: false, isSearchable: true },
{ key: 'audioCategory', label: '音频分类', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
{ key: 'audioScene', label: '使用场景', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
{ key: 'authorName', label: '作者', type: 'text', required: false, layoutWidth: 'half', visibleOnHomeFilter: false, isSearchable: true },
],
},
'video-material': {
name: '视频素材模板',
description: '适用于视频文件素材',
materialType: '视频',
materialKind: 'VIDEO',
fields: [
{ key: 'productModel', label: 'SPU型号', type: 'text', required: true, layoutWidth: 'full', visibleOnHomeFilter: false, isSearchable: true },
{ key: 'categoryName', label: '使用店铺', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
{ key: 'sceneName', label: '场景类别', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
],
},
'model-material': {
name: '模特素材模板',
description: '用于模特/人像素材',
materialType: '模特',
materialKind: 'IMAGE',
fields: [
{ key: 'productModel', label: 'SPU型号', type: 'text', required: true, layoutWidth: 'full', visibleOnHomeFilter: false, isSearchable: true },
{ key: 'categoryName', label: '使用店铺', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
{ key: 'sceneName', label: '场景类别', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
],
},
'other-material': {
name: '其他素材模板',
description: '适用于其他类型的素材',
materialType: '其他',
materialKind: 'OTHER',
fields: [
{ key: 'productModel', label: 'SPU型号', type: 'text', required: true, layoutWidth: 'full', visibleOnHomeFilter: false, isSearchable: true },
{ key: 'categoryName', label: '使用店铺', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
{ key: 'sceneName', label: '场景类别', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
],
},
}
// ============================================================================
// 种子函数
// ============================================================================
@ -134,8 +34,8 @@ async function seedBuiltinTemplates() {
let skipped = 0
for (const key of BUILTIN_TEMPLATE_KEYS) {
const templateId = `builtin-${key}`
const config = BUILTIN_TEMPLATE_CONFIG[key]
const template = createBuiltinTemplate(key)
const templateId = template.id
// 检查是否已存在
const existing = await prisma.uploadTemplate.findUnique({
@ -153,17 +53,17 @@ async function seedBuiltinTemplates() {
await tx.uploadTemplate.create({
data: {
id: templateId,
name: config.name,
description: config.description,
name: template.name,
description: template.description,
status: 'active',
materialType: config.materialType,
materialKind: config.materialKind,
materialType: template.materialType,
materialKind: template.materialKind,
version: 1,
},
})
for (let i = 0; i < config.fields.length; i++) {
const field = config.fields[i]
for (let i = 0; i < template.fields.length; i++) {
const field = template.fields[i]
await tx.uploadField.create({
data: {
templateId,
@ -191,8 +91,8 @@ async function seedBuiltinTemplates() {
async function seedDefaultAdmin() {
console.log('正在检查默认管理员...')
const adminEmail = process.env.INIT_ADMIN_EMAIL || 'admin@flnxi.com'
const adminPassword = process.env.INIT_ADMIN_PASSWORD || '88888888'
const adminEmail = process.env.INIT_ADMIN_EMAIL || 'admin@local.flnxi'
const configuredPassword = process.env.INIT_ADMIN_PASSWORD?.trim()
// 检查是否已有管理员
const existingAdmin = await prisma.user.findFirst({
@ -204,8 +104,14 @@ async function seedDefaultAdmin() {
return
}
if (!configuredPassword) {
console.log(' 跳过: 未设置 INIT_ADMIN_PASSWORD,不在 seed 日志中生成或输出管理员密码')
console.log(' 请通过系统激活/初始化流程生成一次性管理员密码,或设置 INIT_ADMIN_PASSWORD 后重新执行 seed')
return
}
// 创建默认管理员
const hashedPassword = await bcrypt.hash(adminPassword, 10)
const hashedPassword = await bcrypt.hash(configuredPassword, 10)
const admin = await prisma.user.create({
data: {
email: adminEmail,
@ -220,6 +126,29 @@ async function seedDefaultAdmin() {
})
console.log(` 创建: 管理员 ${admin.email}`)
await prisma.systemConfig.upsert({
where: { key: 'initial_admin_state' },
update: {
value: JSON.stringify({
userId: admin.id,
email: admin.email,
mustChangePassword: true,
createdAt: new Date().toISOString(),
}),
},
create: {
key: 'initial_admin_state',
value: JSON.stringify({
userId: admin.id,
email: admin.email,
mustChangePassword: true,
createdAt: new Date().toISOString(),
}),
},
})
console.log(' 初始管理员密码来源: 环境变量 INIT_ADMIN_PASSWORD(已写入哈希,不在日志中输出明文)')
}
async function seedDefaultData() {

View File

@ -9,74 +9,14 @@
*/
import { PrismaClient } from '@prisma/client'
import {
BUILTIN_TEMPLATE_KEYS,
BUILTIN_TEMPLATE_NAMES,
createBuiltinTemplate,
} from '../lib/upload-template-presets'
const prisma = new PrismaClient()
// 内置模板配置(直接定义,避免导入问题)
const BUILTIN_TEMPLATE_KEYS = [
'3dmax-material',
'psd-material',
'audio-material',
'video-material',
'model-material',
'other-material',
] as const
const BUILTIN_TEMPLATE_NAMES: Record<string, string> = {
'3dmax-material': '3DMAX 素材模板',
'psd-material': 'PSD 素材模板',
'audio-material': '音频素材模板',
'video-material': '视频素材模板',
'model-material': '模特素材模板',
'other-material': '其他素材模板',
}
const BUILTIN_TEMPLATE_DESCRIPTIONS: Record<string, string> = {
'3dmax-material': '适用于 3D 模型类素材',
'psd-material': '针对平面设计 PSD 素材',
'audio-material': '适用于音频文件素材,支持 MP3、WAV、FLAC、AAC 等格式',
'video-material': '适用于视频文件素材',
'model-material': '用于模特/人像素材',
'other-material': '适用于其他类型的素材',
}
const BUILTIN_TEMPLATE_TYPES: Record<string, string> = {
'3dmax-material': '3DMAX',
'psd-material': 'PSD',
'audio-material': '音频',
'video-material': '视频',
'model-material': '模特',
'other-material': '其他',
}
const BUILTIN_TEMPLATE_KINDS: Record<string, string> = {
'3dmax-material': 'IMAGE',
'psd-material': 'IMAGE',
'audio-material': 'AUDIO',
'video-material': 'VIDEO',
'model-material': 'IMAGE',
'other-material': 'OTHER',
}
const BUILTIN_TEMPLATE_FIELDS: Record<string, string[]> = {
'3dmax-material': ['productModel', 'categoryName', 'sceneName', 'renderer'],
'psd-material': ['productModel', 'categoryName', 'sceneName'],
'audio-material': ['productModel', 'audioCategory', 'audioScene', 'authorName'],
'video-material': ['productModel', 'categoryName', 'sceneName'],
'model-material': ['productModel', 'categoryName', 'sceneName'],
'other-material': ['productModel', 'categoryName', 'sceneName'],
}
const STANDARD_FIELDS: Record<string, any> = {
productModel: { key: 'productModel', label: 'SPU型号', type: 'text', required: true, layoutWidth: 'full', visibleOnHomeFilter: false, isSearchable: true },
categoryName: { key: 'categoryName', label: '使用店铺', type: 'select', required: false, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
sceneName: { key: 'sceneName', label: '场景类别', type: 'select', required: false, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
renderer: { key: 'renderer', label: '渲染器', type: 'select', required: false, layoutWidth: 'full', visibleOnHomeFilter: false, isSearchable: true, addToCategory: true },
audioCategory: { key: 'audioCategory', label: '音频分类', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
audioScene: { key: 'audioScene', label: '使用场景', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
authorName: { key: 'authorName', label: '作者', type: 'text', required: false, layoutWidth: 'half', visibleOnHomeFilter: false, isSearchable: true },
}
async function fixMissingTemplates() {
console.log('开始修复缺失的模板记录...\n')
@ -112,24 +52,21 @@ async function fixMissingTemplates() {
console.log('\n步骤 2: 创建缺失的内置模板...')
for (const key of missingKeys) {
const templateId = `builtin-${key}`
const name = BUILTIN_TEMPLATE_NAMES[key]
const description = BUILTIN_TEMPLATE_DESCRIPTIONS[key]
const materialType = BUILTIN_TEMPLATE_TYPES[key]
const materialKind = BUILTIN_TEMPLATE_KINDS[key]
const fieldKeys = BUILTIN_TEMPLATE_FIELDS[key] || []
const presetTemplate = createBuiltinTemplate(key)
const templateId = presetTemplate.id
const name = presetTemplate.name
console.log(`\n创建模板: ${name} (${templateId})`)
// 创建模板记录
const template = await prisma.uploadTemplate.create({
const createdTemplate = await prisma.uploadTemplate.create({
data: {
id: templateId,
name,
description,
description: presetTemplate.description,
status: 'active',
materialType,
materialKind,
materialType: presetTemplate.materialType,
materialKind: presetTemplate.materialKind,
version: 1,
},
})
@ -137,50 +74,30 @@ async function fixMissingTemplates() {
console.log(` ✓ 模板记录已创建`)
// 创建字段记录
const requireRenderer = key === '3dmax-material'
const fieldOverrides: Record<string, Partial<{ required: boolean }>> = {
productModel: { required: true },
categoryName: { required: true },
sceneName: { required: true },
}
if (requireRenderer) {
fieldOverrides.renderer = { required: true }
}
for (let i = 0; i < fieldKeys.length; i++) {
const fieldKey = fieldKeys[i]
const fieldConfig = STANDARD_FIELDS[fieldKey]
if (!fieldConfig) {
console.warn(` ⚠ 字段配置不存在: ${fieldKey}`)
continue
}
const override = fieldOverrides[fieldKey] || {}
for (let i = 0; i < presetTemplate.fields.length; i++) {
const field = presetTemplate.fields[i]
await prisma.uploadField.create({
data: {
templateId: template.id,
key: fieldConfig.key,
label: fieldConfig.label,
type: fieldConfig.type,
required: override.required ?? fieldConfig.required,
templateId: createdTemplate.id,
key: field.key,
label: field.label,
type: field.type,
required: field.required,
orderIndex: i,
layoutWidth: fieldConfig.layoutWidth,
layoutWidth: field.layoutWidth,
validations: null,
optionsSource: null,
optionsStatic: fieldConfig.optionsStatic || null,
optionsStatic: field.optionsStatic || null,
allowedExtensions: null,
visibleOnHomeFilter: fieldConfig.visibleOnHomeFilter,
isSearchable: fieldConfig.isSearchable,
addToCategory: fieldConfig.addToCategory || false,
visibleOnHomeFilter: field.visibleOnHomeFilter,
isSearchable: field.isSearchable,
addToCategory: field.addToCategory || false,
conditions: null,
},
})
}
console.log(` ✓ ${fieldKeys.length} 个字段已创建`)
console.log(` ✓ ${presetTemplate.fields.length} 个字段已创建`)
}
// 3. 验证修复结果

View File

@ -20,7 +20,7 @@ const AUDIO_TEMPLATE_ID = 'builtin-audio-material'
const AUDIO_FIELDS = [
{ key: 'productModel', label: '音频名称', type: 'text', required: true, layoutWidth: 'full', visibleOnHomeFilter: false, isSearchable: true, addToCategory: false },
{ key: 'audioCategory', label: '音频分类', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
{ key: 'audioScene', label: '使用场景', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
{ key: 'audioScene', label: '音频使用场景', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
{ key: 'authorName', label: '作者', type: 'text', required: false, layoutWidth: 'half', visibleOnHomeFilter: false, isSearchable: true, addToCategory: false },
]