This commit is contained in:
zuowei1216
2025-12-30 14:46:22 +08:00
parent 6c73b31100
commit 12395d8eca
181 changed files with 1255 additions and 114 deletions

View File

@@ -0,0 +1,69 @@
import sys
import os
# Add current directory to sys.path
sys.path.append(os.getcwd())
from app.db import SessionLocal
from app.services.auth_service import auth_service
from app.schemas.auth import UserRegister
from app.core.security import get_password_hash
from app.models.user import User
def create_user(username, password, email=None):
db = SessionLocal()
try:
# Check if user exists
existing = db.query(User).filter(User.username == username).first()
if existing:
print(f"❌ 用户名 '{username}' 已存在")
return
# Prepare registration data
# We bypass the code verification by not providing code,
# but we set email if provided.
# However, auth_service.register sets is_verified=False by default if code is missing.
# We might want to manually set is_verified=True after registration for convenience.
register_data = UserRegister(
username=username,
password=password,
confirm_password=password,
email=email,
device_id="local_script"
)
# Call register service
try:
token = auth_service.register(db, register_data)
print(f"✅ 用户 '{username}' 注册成功!")
# Manually verify the user for local convenience
user = db.query(User).filter(User.username == username).first()
if user:
user.is_verified = True
user.permissions = "admin" # Grant admin permissions for local test user
db.commit()
print(f"✅ 已自动验证邮箱并赋予 admin 权限")
except Exception as e:
print(f"❌ 注册失败: {e}")
finally:
db.close()
if __name__ == "__main__":
if len(sys.path) < 2:
print("Usage: python create_user.py [username] [password]")
username = "admin"
password = "password123"
email = "admin@example.com"
if len(sys.argv) > 1:
username = sys.argv[1]
if len(sys.argv) > 2:
password = sys.argv[2]
print(f"正在创建用户: {username} ...")
create_user(username, password, email)

View File

@@ -0,0 +1,120 @@
# -*- coding: utf-8 -*-
"""
数据库初始化脚本
功能:
1. 检查数据库连接
2. 创建所有定义的表(如果不存在)
3. 检查现有表的字段,如果缺失则自动添加
"""
import os
import sys
import logging
from sqlalchemy import create_engine, inspect, text
from app.core.config import settings
from app.db import Base
# 导入所有模型以确保它们被注册到 Base.metadata
from app.models import user, group, business, session
# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def get_engine():
"""获取数据库引擎"""
# 优先使用环境变量中的配置,如果没有则构建
db_url = settings.DATABASE_URL
if not db_url:
host = os.getenv('DB_HOST', 'localhost')
port = os.getenv('DB_PORT', '3306')
user = os.getenv('DB_USER', 'root')
password = os.getenv('DB_PASSWORD', '')
db_name = os.getenv('DB_NAME', 'designer_db')
db_url = f"mysql+pymysql://{user}:{password}@{host}:{port}/{db_name}"
return create_engine(db_url)
def map_python_type_to_sql(col_type):
"""将 SQLAlchemy 类型映射为 MySQL 类型"""
type_str = str(col_type).lower()
if 'varchar' in type_str:
return type_str
if 'string' in type_str:
length = getattr(col_type, 'length', 255)
return f"varchar({length})"
if 'integer' in type_str or 'int' in type_str:
return "int"
if 'boolean' in type_str:
return "tinyint(1)"
if 'datetime' in type_str:
return "datetime"
if 'date' in type_str:
return "date"
if 'float' in type_str:
return "float"
if 'text' in type_str:
return "text"
return "varchar(255)" # 默认
def init_db():
logger.info("🔄 开始数据库初始化检查...")
try:
engine = get_engine()
inspector = inspect(engine)
# 1. 创建缺失的表
logger.info("📊 检查表结构...")
Base.metadata.create_all(bind=engine)
logger.info("✅ 基础表结构检查完成")
# 2. 检查并补充缺失的列
logger.info("🔍 检查缺失字段...")
existing_tables = inspector.get_table_names()
with engine.connect() as conn:
for table_name, table in Base.metadata.tables.items():
if table_name not in existing_tables:
continue
# 获取数据库中现有的列
existing_columns = [col['name'] for col in inspector.get_columns(table_name)]
# 检查模型定义的列
for column in table.columns:
if column.name not in existing_columns:
logger.info(f" 发现缺失字段: {table_name}.{column.name}")
# 构建 ALTER TABLE 语句
col_type = map_python_type_to_sql(column.type)
default_val = ""
# 处理默认值 (简化处理,只处理常见类型)
if column.default:
arg = column.default.arg
if isinstance(arg, (int, float, bool)):
if isinstance(arg, bool):
arg = 1 if arg else 0
default_val = f" DEFAULT {arg}"
elif isinstance(arg, str):
default_val = f" DEFAULT '{arg}'"
nullable = "NULL" if column.nullable else "NOT NULL"
if column.nullable and not default_val:
default_val = " DEFAULT NULL"
sql = f"ALTER TABLE {table_name} ADD COLUMN {column.name} {col_type} {nullable}{default_val};"
logger.info(f" 🚀 执行: {sql}")
conn.execute(text(sql))
conn.commit()
logger.info("✅ 数据库同步完成!")
except Exception as e:
logger.error(f"❌ 数据库初始化失败: {e}")
# 不抛出异常,以免阻断容器启动(如果是网络波动等临时问题)
# 但在生产环境中可能需要抛出
if __name__ == "__main__":
init_db()

View File

@@ -0,0 +1,170 @@
-- ==========================================
-- Database Initialization Script
-- Generated at: 2025-12-22 17:54:23.463367
-- ==========================================
-- 1. Select Database and Cleanup Tables
USE designer_db;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS check_in_config;
DROP TABLE IF EXISTS check_in_records;
DROP TABLE IF EXISTS features_config;
DROP TABLE IF EXISTS plugin_groups;
DROP TABLE IF EXISTS points_history;
DROP TABLE IF EXISTS vip_config;
DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS user_sessions;
SET FOREIGN_KEY_CHECKS = 1;
-- 2. Create Tables
CREATE TABLE check_in_config (
id INTEGER NOT NULL AUTO_INCREMENT,
consecutive_days INTEGER NOT NULL,
base_points INTEGER NOT NULL,
bonus_points INTEGER NOT NULL,
total_points INTEGER NOT NULL,
enabled BOOL,
created_at DATETIME DEFAULT now(),
updated_at DATETIME,
PRIMARY KEY (id),
UNIQUE (consecutive_days)
);
CREATE TABLE check_in_records (
id INTEGER NOT NULL AUTO_INCREMENT,
user_id INTEGER NOT NULL,
username VARCHAR(50) NOT NULL,
check_in_date DATE NOT NULL,
points_earned INTEGER NOT NULL,
consecutive_days INTEGER NOT NULL,
vip_multiplier FLOAT,
created_at DATETIME DEFAULT now(),
PRIMARY KEY (id)
);
CREATE TABLE features_config (
id INTEGER NOT NULL AUTO_INCREMENT,
feature_key VARCHAR(50) NOT NULL,
feature_name VARCHAR(100) NOT NULL,
category VARCHAR(50),
points_cost INTEGER,
vip_points_cost INTEGER,
svip_points_cost INTEGER,
enabled BOOL,
description TEXT,
created_at DATETIME DEFAULT now(),
updated_at DATETIME,
PRIMARY KEY (id)
);
CREATE TABLE plugin_groups (
id INTEGER NOT NULL AUTO_INCREMENT,
name VARCHAR(64) NOT NULL,
current_version_file VARCHAR(255),
comment TEXT,
PRIMARY KEY (id)
);
CREATE TABLE points_history (
id INTEGER NOT NULL AUTO_INCREMENT,
user_id INTEGER NOT NULL,
username VARCHAR(50) NOT NULL,
type VARCHAR(20) NOT NULL,
amount INTEGER NOT NULL,
balance INTEGER NOT NULL,
description VARCHAR(255),
created_at DATETIME DEFAULT now(),
PRIMARY KEY (id)
);
CREATE TABLE vip_config (
id INTEGER NOT NULL AUTO_INCREMENT,
vip_type VARCHAR(20) NOT NULL,
name VARCHAR(50) NOT NULL,
price FLOAT NOT NULL,
daily_quota INTEGER NOT NULL,
points_multiplier FLOAT,
enabled BOOL,
description TEXT,
created_at DATETIME DEFAULT now(),
updated_at DATETIME,
PRIMARY KEY (id),
UNIQUE (vip_type)
);
CREATE TABLE users (
id INTEGER NOT NULL AUTO_INCREMENT,
username VARCHAR(64) NOT NULL,
hashed_password VARCHAR(128) NOT NULL,
created_at DATETIME NOT NULL DEFAULT now(),
group_id INTEGER,
permissions TEXT,
expire_date DATETIME,
email VARCHAR(255),
is_verified BOOL,
verification_code VARCHAR(6),
reset_token VARCHAR(128),
reset_token_expire DATETIME,
nickname VARCHAR(50),
avatar VARCHAR(500),
points INTEGER,
level INTEGER,
vip_type VARCHAR(20),
vip_expire DATETIME,
vip_daily_quota INTEGER,
vip_quota_reset_date DATE,
total_check_in_days INTEGER,
consecutive_check_in INTEGER,
last_check_in_date DATE,
PRIMARY KEY (id),
FOREIGN KEY(group_id) REFERENCES plugin_groups (id)
);
CREATE TABLE user_sessions (
id INTEGER NOT NULL AUTO_INCREMENT,
user_id INTEGER NOT NULL,
device_id VARCHAR(128) NOT NULL,
active BOOL NOT NULL,
expires_at DATETIME,
created_at DATETIME NOT NULL DEFAULT now(),
login_at DATETIME,
logout_at DATETIME,
duration_seconds INTEGER,
last_seen_at DATETIME,
PRIMARY KEY (id),
FOREIGN KEY(user_id) REFERENCES users (id)
);
-- 3. Insert Initial Data
-- Default User Group
INSERT INTO plugin_groups (name, comment) VALUES ('default', 'Default User Group');
-- VIP Config
INSERT INTO vip_config (vip_type, name, price, daily_quota, points_multiplier) VALUES ('vip', 'VIP会员', 30.0, 20, 1.5);
INSERT INTO vip_config (vip_type, name, price, daily_quota, points_multiplier) VALUES ('svip', 'SVIP会员', 88.0, -1, 2.0);
-- Check-in Config
INSERT INTO checkin_config (consecutive_days, base_points, bonus_points, total_points) VALUES (1, 10, 0, 10);
INSERT INTO checkin_config (consecutive_days, base_points, bonus_points, total_points) VALUES (3, 10, 5, 15);
INSERT INTO checkin_config (consecutive_days, base_points, bonus_points, total_points) VALUES (7, 10, 20, 30);
-- Feature Config
INSERT INTO feature_configs (feature_key, feature_name, points_cost, category) VALUES ('ai_remove_bg', '智能抠图', 10, 'ai');
-- 4. Create Admin User (admin / password123)
INSERT INTO users (
username, hashed_password, email, is_verified, permissions,
group_id, nickname, level, vip_type, vip_expire,
created_at, points, total_check_in_days, consecutive_check_in, vip_daily_quota
) VALUES (
'admin',
'$2b$12$UsFjs3Jwn5BG7u/RJ1efNuTF4zIsjT.pSm1mQEBXGWKR.3Kakhpmq',
'admin@example.com',
1,
'admin,vip,svip',
(SELECT id FROM plugin_groups WHERE name = 'default' LIMIT 1),
'Administrator',
999,
'svip',
'2099-12-31 23:59:59',
NOW(),
0, 0, 0, 0
);

View File

@@ -0,0 +1,292 @@
# -*- coding: utf-8 -*-
"""
完整数据库初始化脚本 (Local Execution)
功能:
1. 重建数据库DROP & CREATE DATABASE
2. 创建所有表结构
3. 创建默认数据用户组、VIP配置、签到配置、功能配置
4. 创建默认管理员账户 (admin/password123)
注意:此脚本设计为在本地或 Docker 容器内运行,直接连接 MySQL。
它会读取环境变量或使用默认配置。
"""
import logging
import os
import sys
import argparse
from datetime import datetime
from sqlalchemy.schema import CreateTable
# 确保可以将当前目录添加到 sys.path
sys.path.append(os.getcwd())
from sqlalchemy import create_engine, text
from sqlalchemy.orm import sessionmaker
from sqlalchemy.engine.url import make_url
# 尝试导入应用模块
try:
from app.core.config import settings
from app.db import Base
from app.models import user, group, business, session
from app.models.user import User
from app.models.group import PluginGroup
from app.models.business import FeatureConfig, VipConfig, CheckInConfig
from app.core.security import get_password_hash
except ImportError:
print("❌ 无法导入应用模块,请确保在 Server 目录下运行此脚本")
print("示例: python scripts/init_full_db.py")
sys.exit(1)
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
def generate_sql_file():
"""生成完整的初始化 SQL 文件"""
logger.info("📝 正在生成 SQL 文件 (init_db.sql) ...")
# 获取数据库引擎 (用于编译 SQL)
# 我们使用一个 mock engine因为我们只想要 SQL 语句
engine = create_engine("mysql+pymysql://", strategy="mock", executor=lambda sql, *args, **kwargs: print(sql.compile(dialect=engine.dialect)))
# 真正的 engine 用于 dialect 编译
compile_engine = create_engine("mysql+pymysql://")
sql_content = []
# 1. 准备数据库
sql_content.append("-- ==========================================")
sql_content.append("-- Database Initialization Script")
sql_content.append(f"-- Generated at: {datetime.now()}")
sql_content.append("-- ==========================================\n")
# 既然 DROP DATABASE 被禁用,我们切换到目标数据库,并尝试删除所有表
sql_content.append("-- 1. Select Database and Cleanup Tables")
sql_content.append("USE designer_db;\n")
# 获取所有表名并生成 DROP TABLE 语句
# 注意:为了处理外键约束,我们先禁用外键检查
sql_content.append("SET FOREIGN_KEY_CHECKS = 0;")
for table in Base.metadata.sorted_tables:
sql_content.append(f"DROP TABLE IF EXISTS {table.name};")
sql_content.append("SET FOREIGN_KEY_CHECKS = 1;\n")
# 2. 创建表结构
sql_content.append("-- 2. Create Tables")
for table in Base.metadata.sorted_tables:
create_table_sql = CreateTable(table).compile(compile_engine)
sql_content.append(str(create_table_sql).strip() + ";\n")
# 3. 插入初始数据
sql_content.append("-- 3. Insert Initial Data")
# 用户组
sql_content.append("-- Default User Group")
sql_content.append("INSERT INTO plugin_groups (name, comment) VALUES ('default', 'Default User Group');")
# VIP 配置
sql_content.append("-- VIP Config")
sql_content.append("INSERT INTO vip_config (vip_type, name, price, daily_quota, points_multiplier) VALUES ('vip', 'VIP会员', 30.0, 20, 1.5);")
sql_content.append("INSERT INTO vip_config (vip_type, name, price, daily_quota, points_multiplier) VALUES ('svip', 'SVIP会员', 88.0, -1, 2.0);")
# 签到配置
sql_content.append("-- Check-in Config")
sql_content.append("INSERT INTO checkin_config (consecutive_days, base_points, bonus_points, total_points) VALUES (1, 10, 0, 10);")
sql_content.append("INSERT INTO checkin_config (consecutive_days, base_points, bonus_points, total_points) VALUES (3, 10, 5, 15);")
sql_content.append("INSERT INTO checkin_config (consecutive_days, base_points, bonus_points, total_points) VALUES (7, 10, 20, 30);")
# 功能配置
sql_content.append("-- Feature Config")
sql_content.append("INSERT INTO feature_configs (feature_key, feature_name, points_cost, category) VALUES ('ai_remove_bg', '智能抠图', 10, 'ai');")
# 4. 创建管理员
sql_content.append("-- 4. Create Admin User (admin / password123)")
# 计算密码哈希 (这里直接计算一次固定的,避免每次运行不一样)
# password123 的 bcrypt hash (示例)
# 为了准确,我们还是用 python 算一下
admin_hash = get_password_hash("password123")
# 获取 default group id (假设是 1因为刚刚插入且是第一个)
sql_content.append(f"""
INSERT INTO users (
username, hashed_password, email, is_verified, permissions,
group_id, nickname, level, vip_type, vip_expire,
created_at, points, total_check_in_days, consecutive_check_in, vip_daily_quota
) VALUES (
'admin',
'{admin_hash}',
'admin@example.com',
1,
'admin,vip,svip',
(SELECT id FROM plugin_groups WHERE name = 'default' LIMIT 1),
'Administrator',
999,
'svip',
'2099-12-31 23:59:59',
NOW(),
0, 0, 0, 0
);
""")
# 写入文件
output_file = "init_db.sql"
with open(output_file, "w", encoding="utf-8") as f:
f.write("\n".join(sql_content))
logger.info(f"✅ SQL 文件已生成: {output_file}")
print(f"\nSQL 文件已生成: {os.path.abspath(output_file)}")
print("您可以直接在 phpMyAdmin 中导入此文件来初始化数据库。")
def get_db_url():
"""获取数据库连接 URL"""
# 优先使用 settings 中的配置
# db_url = settings.DATABASE_URL
# 用户指定的远程数据库
# Host: 103.97.201.136
# Port: 3388 (映射到了容器内的 3306)
# User: designer_user
# Pass: DesignerPass123!
db_url = "mysql+pymysql://designer_user:DesignerPass123!@103.97.201.136:3388/designer_db"
# 如果 settings 是默认的 sqlite尝试构建 mysql 连接(用于本地开发时的强制覆盖)
# if db_url.startswith("sqlite"):
# # 默认开发环境 Docker MySQL 配置
# db_url = "mysql+pymysql://designer_user:DesignerPass123!@localhost:3306/designer_db"
# logger.info(f"⚠️ 检测到 SQLite 配置,切换为默认 MySQL 配置: {db_url}")
logger.info(f"🔌 使用数据库配置: {db_url}")
return db_url
def recreate_database(engine, db_name):
"""重建数据库"""
logger.info(f"🗑️ 正在重建数据库: {db_name}...")
# 获取 root 连接(连接到 mysql 系统库或不指定库)
url = make_url(engine.url)
root_url = url.set(database='mysql')
# 使用 AUTOCOMMIT 隔离级别
root_engine = create_engine(root_url, isolation_level="AUTOCOMMIT")
with root_engine.connect() as conn:
conn.execute(text(f"DROP DATABASE IF EXISTS {db_name}"))
conn.execute(text(f"CREATE DATABASE {db_name} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"))
logger.info(f"✅ 数据库 {db_name} 重建完成")
def seed_initial_data(session):
"""填充初始数据"""
logger.info("🌱 正在填充初始数据...")
# 1. 默认用户组
if session.query(PluginGroup).filter(PluginGroup.name == "default").count() == 0:
logger.info(" - 创建默认用户组 'default'")
default_group = PluginGroup(name="default", comment="Default User Group")
session.add(default_group)
# 2. VIP 配置
if session.query(VipConfig).count() == 0:
logger.info(" - 创建 VIP 配置")
session.add(VipConfig(vip_type="vip", name="VIP会员", price=30.0, daily_quota=20, points_multiplier=1.5))
session.add(VipConfig(vip_type="svip", name="SVIP会员", price=88.0, daily_quota=-1, points_multiplier=2.0))
# 3. 签到配置
if session.query(CheckInConfig).count() == 0:
logger.info(" - 创建签到配置")
session.add(CheckInConfig(consecutive_days=1, base_points=10, bonus_points=0, total_points=10))
session.add(CheckInConfig(consecutive_days=3, base_points=10, bonus_points=5, total_points=15))
session.add(CheckInConfig(consecutive_days=7, base_points=10, bonus_points=20, total_points=30))
# 4. 功能配置
if session.query(FeatureConfig).count() == 0:
logger.info(" - 创建功能配置")
session.add(FeatureConfig(feature_key="ai_remove_bg", feature_name="智能抠图", points_cost=10, category="ai"))
session.commit()
logger.info("✅ 初始数据填充完成")
def create_admin_user(session):
"""创建管理员用户"""
username = "admin"
password = "123456"
email = "admin@example.com"
existing = session.query(User).filter(User.username == username).first()
if existing:
logger.info(f" 管理员用户 '{username}' 已存在,跳过创建")
return
logger.info(f"👤 正在创建管理员用户: {username} ...")
# 获取默认组
default_group = session.query(PluginGroup).filter(PluginGroup.name == "default").first()
group_id = default_group.id if default_group else None
new_user = User(
username=username,
hashed_password=get_password_hash(password),
email=email,
is_verified=True,
permissions="admin,vip,svip", # 赋予所有权限
group_id=group_id,
nickname="Administrator",
level=999,
vip_type="svip",
vip_expire=datetime(2099, 12, 31)
)
session.add(new_user)
session.commit()
logger.info(f"✅ 管理员用户创建成功!(User: {username}, Pass: {password})")
def main():
parser = argparse.ArgumentParser(description='Initialize DesignerCEP Database')
parser.add_argument('--generate-sql', action='store_true', help='Generate init_db.sql file only')
args = parser.parse_args()
if args.generate_sql:
generate_sql_file()
return
logger.info("🚀 开始全量数据库初始化流程")
try:
db_url = get_db_url()
engine = create_engine(db_url)
# 1. 重建数据库
# 注意:这会删除现有数据!
db_name = make_url(db_url).database
recreate_database(engine, db_name)
# 重新连接到新创建的数据库
target_engine = create_engine(db_url)
# 2. 创建表结构
logger.info("🏗️ 正在创建表结构...")
Base.metadata.create_all(bind=target_engine)
logger.info("✅ 表结构创建完成")
# 3. 数据填充
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=target_engine)
session = SessionLocal()
try:
seed_initial_data(session)
create_admin_user(session)
finally:
session.close()
logger.info("✨✨✨ 数据库初始化全部完成! ✨✨✨")
except Exception as e:
logger.error(f"❌ 初始化失败: {str(e)}")
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,7 @@
@echo off
rem D:\Python37\Scripts\pyinstaller.exe -w -i ./newapp.ico run.py
pyinstaller -w -i ./newapp.ico run.py
pause

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@@ -0,0 +1,396 @@
import os
import shutil
import sys
import tempfile
import time
import wmi
import psutil
import threading
import qtpy, platform
import winreg
from qtpy.QtCore import Qt, QMetaObject, Signal, Slot, QEvent
from qtpy.QtWidgets import QWidget, QVBoxLayout, QInputDialog, QHBoxLayout, QToolButton, QLabel, QSizePolicy, QSplashScreen
from PyQt5.QtGui import QIcon, QPixmap
import re
import hashlib
import configparser
from win32com.client import Dispatch
import ezdxf
import zipfile
import PyQt5
from PyQt5.QtWidgets import QApplication, QDialog, QMainWindow, QVBoxLayout, QWidget, QTabWidget, QPushButton, QLabel, QVBoxLayout, QWidget, QHBoxLayout, QFrame, QMessageBox
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import Qt
import qdarktheme
import sys
import subprocess
import re
import hashlib
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QGroupBox, \
QSpacerItem, QSizePolicy, QMessageBox
import pymysql
import requests
BASE_URL = "http://43.134.82.18/psmark"
#BASE_URL = "http://127.0.0.1:5001"
tempdir = ""
def exception_hook(exctype, value, traceback):
# Handle the uncaught exception
# 处理未捕获的异常
QMessageBox.warning(None, "错误", f"发生了未知的异常:{value}")
class LoginDialog(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("PSMARK登录界面")
self.setWindowIcon(QIcon("icons/newapp.ico")) # 设置窗口小图标,替换为您的图标文件路径
self.resize(300, 200)
主布局 = QVBoxLayout()
group1 = QGroupBox("登录验证")
group1_layout = QVBoxLayout()
group2 = QHBoxLayout()
label1 = QLabel("用户名")
self.edit1 = QLineEdit()
self.edit1.setFixedWidth(200)
spacer1 = QSpacerItem(40, 10, QSizePolicy.Expanding, QSizePolicy.Minimum)
group2.addWidget(label1)
group2.addItem(spacer1)
group2.addWidget(self.edit1)
group3 = QHBoxLayout()
label2 = QLabel("密码")
self.edit2 = QLineEdit()
self.edit2.setFixedWidth(200)
self.edit2.setEchoMode(QLineEdit.Password) # 设置密码输入框为密文
spacer2 = QSpacerItem(40, 10, QSizePolicy.Expanding, QSizePolicy.Minimum)
group3.addWidget(label2)
group3.addItem(spacer2)
group3.addWidget(self.edit2)
group4 = QHBoxLayout()
button1 = QPushButton("登录")
button2 = QPushButton("注册")
group4.addWidget(button1)
group4.addWidget(button2)
group1_layout.addLayout(group2)
group1_layout.addLayout(group3)
group1_layout.addLayout(group4)
group1.setLayout(group1_layout)
主布局.addWidget(group1)
group5 = QHBoxLayout()
label3 = QLabel("机器码")
self.edit3 = QLineEdit()
self.edit3.setFixedWidth(200)
self.edit3.setReadOnly(True)
self.edit3.setFocusPolicy(Qt.NoFocus)
# 获取主板序列号并提取数字部分
'''try:
result = subprocess.run(['wmic', 'baseboard', 'get', 'serialnumber'], stdout=subprocess.PIPE,
stderr=subprocess.PIPE, text=True)
motherboard_serial = result.stdout.strip()
# 使用正则表达式提取数字
motherboard_serial = re.sub(r'\D', '', motherboard_serial)
# 使用SHA-256加密特征码
feature_code = hashlib.sha256(motherboard_serial.encode()).hexdigest()
# 去掉特征码中的英文字符
feature_code = re.sub(r'[a-zA-Z]', '', feature_code)
except Exception as e:
feature_code = "Error: " + str(e)'''
# 计算序列号
try:
feature_code = self.get_computer_code()
count = ord(feature_code[0]) + ord(feature_code[1])
for _ in range(count):
feature_code = hashlib.md5(feature_code.encode()).hexdigest().upper()
except Exception as e:
feature_code = "Error: " + str(e)
self.edit3.setText(feature_code) # 将加密后的特征码设置为 "特征码" 输入框的文本
self.rem_user()
spacer3 = QSpacerItem(10, 10, QSizePolicy.Fixed, QSizePolicy.Minimum)
group5.addWidget(label3)
group5.addItem(spacer3)
group5.addWidget(self.edit3)
主布局.addLayout(group5)
self.setLayout(主布局)
# 链接登录的点击事件
button1.clicked.connect(self.slot_login)
# 连接注册按钮的点击事件
button2.clicked.connect(self.register)
def get_computer_code(self):
computer_code = ''
c = wmi.WMI()
for cpu in c.Win32_Processor():
computer_code += cpu.ProcessorId.strip()
key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\SQMClient", 0, winreg.KEY_READ | winreg.KEY_WOW64_64KEY)
# 读取设备ID
computer_code += winreg.QueryValueEx(key, "MachineId")[0].strip()
# 关闭注册表
winreg.CloseKey(key)
computer_code += str(subprocess.check_output('wmic csproduct get uuid').split(b'\n')[1].strip())
return computer_code
def register(self):
dialog = RegisterDialog()
if dialog.exec() != QDialog.Accepted:
return
r = requests.post(BASE_URL + f"/register", data={
"username":dialog.get用户名(),
"password":dialog.get密码(),
"code":self.edit3.text(),
"adminpassword":"qwe123456",
"truename":dialog.get姓名(),
"phone":dialog.get手机号(),
"company":dialog.get公司名(),
"address":dialog.get地址(),
})
if r.text == "success":
QMessageBox.information(self, "成功", "注册成功!")
elif r.text == 'exist':
QMessageBox.critical(self, "错误", "用户已存在!")
else:
QMessageBox.critical(self, "错误", f"注册失败!{r.text}")
#记住密码
def rem_user(self):
global tempdir
code = self.edit3.text()
r = requests.post(BASE_URL + f"/query?code={code}")
with tempfile.TemporaryDirectory() as d:
dname = d
tempdir = dname + str(time.time())
os.makedirs(tempdir)
filepath = os.path.join(tempdir, "result.zip")
with open(filepath, 'wb') as f:
f.write(r.content)
with zipfile.ZipFile(filepath, 'r') as zip_ref:
zip_ref.extractall(tempdir)
with open(os.path.join(tempdir, "userinfo.txt"), 'r', encoding="utf-8") as f:
userinfo = f.read()
if userinfo == 'error':
return
self.edit1.setText(userinfo.split("\n")[0])
self.edit2.setText(userinfo.split("\n")[1])
def slot_login(self):
global tempdir
user_name = self.edit1.text()
user_password = self.edit2.text()
code = self.edit3.text()
# 判断缓存是否存在
with open(os.path.join(tempdir, "userinfo.txt"), 'r', encoding="utf-8") as f:
userinfo = f.read()
if not (userinfo.split("\n")[0] == user_name and userinfo.split("\n")[1] == user_password):
r = requests.post(BASE_URL + f"/query?code={code}&username={user_name}&password={user_password}")
with tempfile.TemporaryDirectory() as d:
dname = d
tempdir = dname + str(time.time())
os.makedirs(tempdir)
filepath = os.path.join(tempdir, "result.zip")
with open(filepath, 'wb') as f:
f.write(r.content)
with zipfile.ZipFile(filepath, 'r') as zip_ref:
zip_ref.extractall(tempdir)
with open(os.path.join(tempdir, "userinfo.txt"), 'r', encoding="utf-8") as f:
userinfo = f.read()
if userinfo == 'error':
QMessageBox.critical(self, "错误", "机器码或账号密码错误!")
return
with zipfile.ZipFile(os.path.join(tempdir, "data.zip"), 'r') as zip_ref:
zip_ref.extractall(tempdir)
#print(tempdir)
cwd = os.getcwd()
sys.path.insert(0, tempdir)
os.chdir(tempdir)
import piece_decorative
piece_decorative.config = configparser.ConfigParser()
os.chdir(cwd)
piece_decorative.config.read('config.ini', encoding='utf-8')
piece_decorative.PSname = piece_decorative.config.get('程序配置', 'PSname')
os.chdir(tempdir)
import newMark
#self.hide()
self.window = newMark.MainWindow()
self.window.show()
self.close()
#print("run")
#newMark.run()
def show_warning_message(self):
# 弹出警告消息框
QMessageBox.critical(self, "错误", "请联系管理员 17520145271")
# warning_message = QMessageBox()
# warning_message.setIcon(QMessageBox.Warning)
# warning_message.setWindowTitle("警告")
# warning_message.setText("请联系管理员 17520145271")
# warning_message.exec_()
from PyQt5.QtWidgets import QDialogButtonBox, QFormLayout
from PyQt5.QtGui import QIntValidator
import re
class RegisterDialog(QDialog):
'''注册对话框'''
def __init__(self):
super(RegisterDialog,self).__init__()
self.init_gui()
def init_gui(self):
#设置dialog窗口标题
self.setWindowTitle("注册")
# 设置界面尺寸大小
self.resize(500, 260)
self.用户名QLineEdit = QLineEdit()
self.密码QLineEdit = QLineEdit()
self.密码QLineEdit.setEchoMode(QLineEdit.Password)
self.确认密码QLineEdit = QLineEdit()
self.确认密码QLineEdit.setEchoMode(QLineEdit.Password)
self.姓名QLineEdit = QLineEdit()
self.手机号QLineEdit = QLineEdit()
#self.手机号QLineEdit.setValidator(QIntValidator())
self.公司名QLineEdit = QLineEdit()
self.地址QLineEdit = QLineEdit()
self.buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal, self)
self.buttons.accepted.connect(self.check)
self.buttons.rejected.connect(self.reject)
# 表单布局,当然也可以使用其它布局方式
layout = QFormLayout(self)
layout.addRow('用户名(用于登录):', self.用户名QLineEdit)
layout.addRow('密码(用于登录):', self.密码QLineEdit)
layout.addRow('确认密码:', self.确认密码QLineEdit)
layout.addRow('姓名:', self.姓名QLineEdit)
layout.addRow('手机号(+86):', self.手机号QLineEdit)
layout.addRow('公司名:', self.公司名QLineEdit)
layout.addRow('地址:', self.地址QLineEdit)
layout.addRow(self.buttons)
def get用户名(self):
return self.用户名QLineEdit.text().strip()
def get密码(self):
return self.密码QLineEdit.text().strip()
def get确认密码(self):
return self.确认密码QLineEdit.text().strip()
def get姓名(self):
return self.姓名QLineEdit.text().strip()
def get手机号(self):
return self.手机号QLineEdit.text().strip()
def get公司名(self):
return self.公司名QLineEdit.text().strip()
def get地址(self):
return self.地址QLineEdit.text().strip()
def check(self):
if self.get用户名() == '' or self.get密码() == '' or self.get姓名() == '' or self.get手机号() == '' or self.get公司名() == '' or self.get地址() == '':
QMessageBox.critical(self, "错误", "信息不完整!")
return
if self.get密码() != self.get确认密码():
QMessageBox.critical(self, "错误", "两次密码不一致!")
return
t = re.compile(r'[1-9][0-9]{10}')
s = re.search(t, self.get手机号())
if (not s) or (not self.get手机号().startswith('1')) or (len(self.get手机号()) != 11):
QMessageBox.critical(self, "错误", "手机号格式错误!")
return
self.accept()
def main():
app3 = QApplication(sys.argv)
sys.excepthook = exception_hook # 设置全局异常处理
splash = QSplashScreen()
splash.setPixmap(QPixmap('./splash.png'))
splash.show()
app3.processEvents()
qdarktheme.setup_theme(
custom_colors={
"[dark]": {
"background": "#4d4d4d",
"foreground": "#ffffff",
"primary": "#ffffff",
"border": "#717070",
}
}
)
login_dialog = LoginDialog()
login_dialog.show()
splash.finish(login_dialog) # 启动画面完成启动
# window = MainWindow()
# window.show()
r = app3.exec_()
time.sleep(1)
os.chdir("C:")
try:
if tempdir != "":
shutil.rmtree(tempdir, ignore_errors=True)
#print("temp dir remove ok:", tempdir)
except Exception as e:
print(e)
sys.exit(r)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,51 @@
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(
['run.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='run',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=['newapp.ico'],
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='run',
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
[程序配置]
PSname = Photoshop.Application.120

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,191 @@
dxf12_jscode = """
批量套数写入()
function 批量套数写入() {
app.preferences.rulerUnits = Units.CM;
var 主文档 = app.activeDocument;
var 主文档名称 = 主文档.name;
// 遍历当前打开的文档
for (var i = 0; i < app.documents.length; i++) {
var document = app.documents[i];
var documentName = document.name;
// 判断文档名称是否与主文档名称不相同
if (documentName !== 主文档名称) {
// 设置当前文档为活动文档
app.activeDocument = document;
var 匹配图层数组 = 遍历图层查找P1();
// 遍历匹配图层数组
for (var j = 0; j < 匹配图层数组.length; j++) {
var 当前匹配图层 = 匹配图层数组[j];
}
// 选中当前匹配图层
// 获取当前选区
var currentSelection = app.activeDocument.selection;
// 确保当前选区不为空且为矩形选区
if (currentSelection != null && currentSelection.hasOwnProperty('bounds')) {
// 进行缩放操作
app.activeDocument.activeLayer = 当前匹配图层;
// 载入选区
载入选区();
// 获取当前选区的坐标
var bounds = currentSelection.bounds;
// 获取当前选区的宽度和高度(以 cm 为单位)
var resolution = app.activeDocument.resolution;
var widthInCM = (bounds[2].as("cm") - bounds[0].as("cm"));
var heightInCM = (bounds[3].as("cm") - bounds[1].as("cm"));
var 当前P1图层宽高信息 = "宽度:" + widthInCM.toFixed(2) + "cm高度" + heightInCM.toFixed(2) + "cm";
// 获取当前裁片套数
var 搜索关键词 = "P1";
var 匹配的图层数量 = 获取匹配图层数量(搜索关键词);
var 当前裁片套数 = "一段" + 匹配的图层数量 + "";
// 创建新图层并设置名称
/// var 新图层 = 主文档.artLayers.add();
// var 新图层 = "当前文档宽度缩水" + 宽度值 + "高度缩水" + 高度值;
// 移动选区到新图层
// currentSelection.cut();
// app.activeDocument.paste();
// alert(当前裁片套数);
// alert( 当前P1图层宽高信息);
// 文件简介写入
文件简介写入(当前裁片套数, 当前P1图层宽高信息 );
// 这里删除了文件简介的写入将缩水值修改按钮
// 调整图像尺寸(宽度值, 高度值);
////这里取消了保存功能 为了防止运行的时候变卡
//activeDocument.save();
} else {
// alert("没有找到匹配的图层。");
}
}
}
app.activeDocument=主文档
// alert("写入信息成功","来自左威的提醒");
}
function 文件简介写入(当前裁片套数, 当前P1图层宽高信息) {
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("property"), stringIDToTypeID("fileInfo"));
r.putEnumerated(stringIDToTypeID("document"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
d1.putString(stringIDToTypeID("caption"), 当前裁片套数);
d1.putString(stringIDToTypeID("keywords"), 当前P1图层宽高信息);
d.putObject(stringIDToTypeID("to"), stringIDToTypeID("fileInfo"), d1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 调整图像尺寸(宽度值, 高度值) {
var 动作描述 = new ActionDescriptor();
动作描述.putUnitDouble(stringIDToTypeID("width"), stringIDToTypeID("percentUnit"), 100 + 宽度值);
动作描述.putUnitDouble(stringIDToTypeID("height"), stringIDToTypeID("percentUnit"), 100 + 高度值);
动作描述.putEnumerated(charIDToTypeID("Intr"), stringIDToTypeID("interpolationType"), stringIDToTypeID("nearestNeighbor"));
executeAction(stringIDToTypeID("imageSize"), 动作描述, DialogModes.NO);
}
function 载入选区() //载入选区
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("transparencyEnum"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 遍历图层查找P1() {
var 匹配图层数组 = [];
var 文档 = app.activeDocument;
// 遍历所有图层
function 遍历所有图层(图层) {
if (图层.typename === "LayerSet") {
for (var i = 0; i < 图层.layers.length; i++) {
遍历所有图层(图层.layers[i]);
}
} else {
var 图层名分割数组 = 图层.name.split("-"); // 假设分割符是 "_"
if (图层名分割数组[0] === "P1") { // 精确匹配
匹配图层数组.push(图层);
}
}
}
// 开始遍历
for (var j = 0; j < 文档.layers.length; j++) {
遍历所有图层(文档.layers[j]);
}
return 匹配图层数组;
}
function 获取匹配图层数量(搜索关键词) {
var 匹配图层数量 = 0;
// 递归遍历图层及其子图层
function 遍历图层(图层) {
if (图层.typename === "LayerSet") {
for (var i = 0; i < 图层.layers.length; i++) {
遍历图层(图层.layers[i]);
}
} else {
// 进行模糊匹配和精确分割匹配
if (图层.name.indexOf(搜索关键词) !== -1 && 精确分割匹配图层(图层.name, 搜索关键词)) {
匹配图层数量++;
}
}
}
// 精确分割匹配图层名
function 精确分割匹配图层(图层名, 搜索词) {
var 图层名分割数组 = 图层名.split("-"); // 假设分割符是 "_"
return 图层名分割数组[0] === 搜索词;
}
var 当前文档 = app.activeDocument;
var 所有图层 = 当前文档.layers;
for (var i = 0; i < 所有图层.length; i++) {
遍历图层(所有图层[i]);
}
return 匹配图层数量;
}
"""

View File

@@ -0,0 +1,982 @@
dxf13_jscode = """
function 裁片射出宽高缩放() {
app.preferences.rulerUnits = Units.PIXELS
var 主文档 = app.activeDocument;
var 主文档名称 = 主文档.name;
// 遍历当前打开的文档
for (var i = 0; i < app.documents.length; i++) {
var document = app.documents[i];
var documentName = document.name;
// 判断文档名称是否与主文档名称不相同
if (documentName !== 主文档名称) {
app.activeDocument = document;
遍历图层();
}
}
function 遍历图层() {
var layerNames = []; // 用于存储图层名称的数组
var currentDocument = app.activeDocument;
for (var j = 0; j < currentDocument.layers.length; j++) {
var layer = currentDocument.layers[j];
var layerName = layer.name;
layerNames.push(layerName);
}
// 逐个处理图层
for (var k = 0; k < layerNames.length; k++) {
var 当前图层名称 = layerNames[k];
// $.writeln("图层名称:" + 当前图层名称);
// alert(当前图层名称);
var parts = 当前图层名称.split("-");
if (parts.length > 0) {
var 裁片名称 = parts[0];
app.activeDocument = 主文档;
$.writeln(裁片名称);
初始化模板裁片名称 = 当前图层名称.split("-");
初始化码数裁片名称 = 当前图层名称.split("_");
大货组名称 =初始化模板裁片名称[0]+("-大货裁片")
实际裁片名称 = 初始化模板裁片名称[0]+"-"+初始化码数裁片名称[2]
$.writeln(大货组名称);
$.writeln(实际裁片名称);
var 空白裁片模板 = app.activeDocument.layerSets.getByName(大货组名称).layers.getByName(实际裁片名称 );
app.activeDocument.activeLayer = 空白裁片模板;
载入选区()
var 边距 = 获取当前选区四边距();
var 当前选区高度=边距.bottom-边距.top
var 当前选区宽度=边距.right-边距.left
var 高度转毫米 = pixelsToMillimeters(当前选区高度);
var 宽度转毫米 = pixelsToMillimeters(当前选区宽度);
var 搜索词 = 裁片名称;
var 匹配图层数组 = 匹配图层名(搜索词);
// 显示匹配的图层列表
if (匹配图层数组.length > 0) {
var 图层列表文本 = "匹配的图层列表:";
for (var i = 0; i < 匹配图层数组.length; i++) {
if (i !== 0) {
图层列表文本 += " ";
}
图层列表文本 += 匹配图层数组[i].name;
}
var 数据解析分割=图层列表文本.split("_");
//var 实际套花名称=名称部分[0]
var 基码图层宽度 = parseFloat(数据解析分割[1]);
var 基码图层高度 = parseFloat(数据解析分割[2]);
var 缩放比例高度=高度转毫米/基码图层高度*100
var 缩放比例宽度=宽度转毫米/基码图层宽度*100
// alert(基码图层宽度);
} else {
alert("没有找到匹配的图层。");
}
/*
$.writeln("上边距:" + 边距.top);
$.writeln("左边距:" + 边距.left);
$.writeln("下边距:" + 边距.bottom);
$.writeln("右边距:" + 边距.right);
*/8
// 示例用法:
var 毫米 = 300;
var 每英寸像素数 = app.activeDocument.resolution; // 获取当前文档的分辨率(每英寸像素数)
var 扩展像素 = 毫米转像素(毫米, 每英寸像素数);
var 裁切上边距= 边距.top-扩展像素
var 裁切左边距= 边距.left-扩展像素
var 裁切下边距= 边距.bottom+扩展像素
var 裁切右边距= 边距.right+扩展像素
$.writeln(裁切上边距);
$.writeln(裁切左边距);
$.writeln(裁切下边距);
$.writeln(裁切右边距);
裁切图层(裁切上边距,裁切左边距,裁切下边距,裁切右边距)
var 空白裁片模板 = app.activeDocument.layerSets.getByName(大货组名称).layers.getByName(实际裁片名称);
app.activeDocument.activeLayer = 空白裁片模板;
载入选区()
var 缩放定位点的中心坐标=获取当前缩放定位点选区四边距()
var 缩放定位点的Y轴坐标=缩放定位点的中心坐标.top2+(缩放定位点的中心坐标.bottom2-缩放定位点的中心坐标.top2)/2
var 缩放定位点的X轴坐标=缩放定位点的中心坐标.left2+(缩放定位点的中心坐标.right2-缩放定位点的中心坐标.left2)/2
$.writeln("Y轴中心坐标"+缩放定位点的Y轴坐标);
$.writeln("X轴中心坐标"+缩放定位点的X轴坐标);
var 裁片 = app.activeDocument.layers.getByName(裁片名称);
app.activeDocument.activeLayer = 裁片
//var 空白裁片模板 = app.activeDocument.layerSets.getByName(大货组名称).layers.getByName(实际裁片名称 );
//app.activeDocument.activeLayer = 空白裁片模板;
取消选择()
图层按照缩放定位点进行宽高缩放(缩放定位点的X轴坐标,缩放定位点的Y轴坐标,缩放比例高度,缩放比例宽度)
// var 裁片 = app.activeDocument.layers.getByName(裁片名称);
// app.activeDocument.activeLayer = 裁片;
var 空白裁片模板 = app.activeDocument.layerSets.getByName(大货组名称).layers.getByName(实际裁片名称 );
app.activeDocument.activeLayer = 空白裁片模板;
载入选区()
var 裁片 = app.activeDocument.layers.getByName(裁片名称);
app.activeDocument.activeLayer = 裁片
添加图层蒙版()
应用图层蒙版()
裁片.copy();
历史记录回退()
app.activeDocument = currentDocument;
图层选择(当前图层名称);
载入选区();
粘贴图层();
取消选择();
// app.refresh();
var 裁片名称 = 当前图层名称.split("_");
if (裁片名称.length > 1) {
var 角度信息 = 裁片名称[1];
if (角度信息 === "180" || 角度信息 === "-180") {
自由变换();
} else if (角度信息 === "-90") {
逆时针90旋转()
} else if (角度信息 === "90") {
顺时针90旋转()
} else {
// 如果以上条件都不满足,则执行默认的代码
}
//历史记录回退缩放函数()
}
app.activeDocument = 主文档;
历史记录回退缩放函数()
}
}
app.activeDocument = currentDocument;
烧花线添加()//alert("当前码拍好")///////////////////////////////////这里可以填写添加烧花线函数
}
//alert("排版完成,请检查文件!!!")
app.activeDocument = 主文档;
}
// 将像素转换为毫米
function pixelsToMillimeters(pixels) {
// 获取当前文档
var doc = app.activeDocument;
// 获取图像的分辨率(像素/英寸)
var resolution = doc.resolution;
// 计算像素转换为毫米
var inches = pixels / resolution;
var millimeters = inches * 25.4;
return millimeters.toFixed(2); // 保留两位小数
}
function 顺时针90旋转() //自由变换
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("freeTransformCenterState"), stringIDToTypeID("quadCenterState"), stringIDToTypeID("QCSAverage"));
var d1 = new ActionDescriptor();
d1.putUnitDouble(stringIDToTypeID("horizontal"), stringIDToTypeID("pixelsUnit"), 0);
d1.putUnitDouble(stringIDToTypeID("vertical"), stringIDToTypeID("pixelsUnit"), 0);
d.putObject(stringIDToTypeID("offset"), stringIDToTypeID("offset"), d1);
d.putUnitDouble(stringIDToTypeID("angle"), stringIDToTypeID("angleUnit"), 90);
d.putBoolean(stringIDToTypeID("linked"), true);
d.putEnumerated(charIDToTypeID("Intr"), stringIDToTypeID("interpolationType"), stringIDToTypeID("nearestNeighbor"));
executeAction(stringIDToTypeID("transform"), d, DialogModes.NO);
}
function 逆时针90旋转() //自由变换
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("freeTransformCenterState"), stringIDToTypeID("quadCenterState"), stringIDToTypeID("QCSAverage"));
var d1 = new ActionDescriptor();
d1.putUnitDouble(stringIDToTypeID("horizontal"), stringIDToTypeID("pixelsUnit"), 0);
d1.putUnitDouble(stringIDToTypeID("vertical"), stringIDToTypeID("pixelsUnit"), 0);
d.putObject(stringIDToTypeID("offset"), stringIDToTypeID("offset"), d1);
d.putUnitDouble(stringIDToTypeID("angle"), stringIDToTypeID("angleUnit"), -90);
d.putBoolean(stringIDToTypeID("linked"), true);
d.putEnumerated(charIDToTypeID("Intr"), stringIDToTypeID("interpolationType"), stringIDToTypeID("nearestNeighbor"));
executeAction(stringIDToTypeID("transform"), d, DialogModes.NO);
}
function 匹配图层名(搜索词) {
// 获取指定图层组中的所有图层
function 获取组中所有图层(组) {
var 图层数组 = [];
var 图层组中图层 = 组.layers;
for (var i = 0; i < 图层组中图层.length; i++) {
var 图层 = 图层组中图层[i];
图层数组.push(图层);
if (图层.typename === "LayerSet") {
var 子图层 = 获取组中所有图层(图层);
图层数组 = 图层数组.concat(子图层);
}
}
return 图层数组;
}
// 获取指定名称的图层组
function 根据名称获取图层组(文档, 组名称) {
var 组 = null;
var 所有图层 = 文档.layers;
for (var i = 0; i < 所有图层.length; i++) {
var 图层 = 所有图层[i];
if (图层.typename === "LayerSet" && 图层.name === 组名称) {
组 = 图层;
break;
}
}
return 组;
}
var 文档 = app.activeDocument;
var 组名称 = "图层基础信息"; // 指定要匹配的图层组名称
var 组 = 根据名称获取图层组(文档, 组名称);
if (组) {
var 图层数组 = 获取组中所有图层(组);
var 模糊匹配图层数组 = [];
// 首先进行模糊匹配
for (var i = 0; i < 图层数组.length; i++) {
var 图层 = 图层数组[i];
if (图层.name.indexOf(搜索词) !== -1) {
模糊匹配图层数组.push(图层);
}
}
// 在模糊匹配结果中进行图层基础信息数组分割过滤
var 精确匹配图层数组 = [];
for (var j = 0; j < 模糊匹配图层数组.length; j++) {
var 模糊匹配图层 = 模糊匹配图层数组[j];
// 进行图层基础信息数组分割过滤
var 图层基础信息数组 = 模糊匹配图层.name.split("_"); // 假设分割符是 "_"
if (图层基础信息数组[0] === 搜索词) {
精确匹配图层数组.push(模糊匹配图层);
}
}
// 返回匹配的图层数组
return 精确匹配图层数组;
} else {
alert('未找到名为"' + 组名称 + '"的图层组。');
return [];
}
}
function 毫米转像素(毫米, 每英寸像素数) {
var 每英寸毫米数 = 25.4;
var 英寸 = 毫米 / 每英寸毫米数;
return Math.round(英寸 * 每英寸像素数);
}
function 图层按照缩放定位点进行宽高缩放(缩放定位点的X轴坐标,缩放定位点的Y轴坐标,缩放比例高度,缩放比例宽度) //自由变换
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("freeTransformCenterState"), stringIDToTypeID("quadCenterState"), stringIDToTypeID("QCSIndependent"));
var d1 = new ActionDescriptor();
d1.putUnitDouble(stringIDToTypeID("horizontal"), stringIDToTypeID("pixelsUnit"), 缩放定位点的X轴坐标);
d1.putUnitDouble(stringIDToTypeID("vertical"), stringIDToTypeID("pixelsUnit"), 缩放定位点的Y轴坐标);
d.putObject(stringIDToTypeID("position"), stringIDToTypeID("point"), d1);
var d2 = new ActionDescriptor();
d2.putUnitDouble(stringIDToTypeID("horizontal"), stringIDToTypeID("pixelsUnit"), 0);
d2.putUnitDouble(stringIDToTypeID("horizontal"), stringIDToTypeID("pixelsUnit"), 0);
d2.putUnitDouble(stringIDToTypeID("vertical"), stringIDToTypeID("pixelsUnit"), 0);
d.putObject(stringIDToTypeID("offset"), stringIDToTypeID("offset"), d2);
d.putUnitDouble(stringIDToTypeID("width"), stringIDToTypeID("percentUnit"), 缩放比例宽度);
d.putUnitDouble(stringIDToTypeID("height"), stringIDToTypeID("percentUnit"), 缩放比例高度);
d.putEnumerated(charIDToTypeID("Intr"), stringIDToTypeID("interpolationType"), stringIDToTypeID("nearestNeighbor"));
executeAction(stringIDToTypeID("transform"), d, DialogModes.NO);
}
function 裁切图层(裁切上边距,裁切左边距,裁切下边距,裁切右边距) //
{
var d = new ActionDescriptor();
var d1 = new ActionDescriptor();
d1.putUnitDouble(stringIDToTypeID("top"), stringIDToTypeID("pixelsUnit"), 裁切上边距);
d1.putUnitDouble(stringIDToTypeID("left"), stringIDToTypeID("pixelsUnit"), 裁切左边距);
d1.putUnitDouble(stringIDToTypeID("bottom"), stringIDToTypeID("pixelsUnit"),裁切下边距);
d1.putUnitDouble(stringIDToTypeID("right"), stringIDToTypeID("pixelsUnit"), 裁切右边距);
d.putObject(stringIDToTypeID("to"), stringIDToTypeID("rectangle"), d1);
d.putUnitDouble(stringIDToTypeID("angle"), stringIDToTypeID("angleUnit"), 0);
d.putBoolean(stringIDToTypeID("delete"), true);
d.putEnumerated(stringIDToTypeID("cropAspectRatioModeKey"), stringIDToTypeID("cropAspectRatioModeClass"), stringIDToTypeID("pureAspectRatio"));
d.putBoolean(stringIDToTypeID("constrainProportions"), false);
executeAction(stringIDToTypeID("crop"), d, DialogModes.NO);
}
function 获取当前缩放定位点选区四边距() {
var currentDocument = app.activeDocument;
var selectionBounds = currentDocument.selection.bounds;
var top2 = selectionBounds[1].value;
var left2 = selectionBounds[0].value;
var bottom2 = selectionBounds[3].value;
var right2 = selectionBounds[2].value;
return {
top2: top2,
left2: left2,
bottom2: bottom2,
right2: right2
};
}
function 获取当前选区四边距() {
var currentDocument = app.activeDocument;
var selectionBounds = currentDocument.selection.bounds;
var top = selectionBounds[1].value;
var left = selectionBounds[0].value;
var bottom = selectionBounds[3].value;
var right = selectionBounds[2].value;
return {
top: top,
left: left,
bottom: bottom,
right: right
};
}
function 历史记录回退缩放函数() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putOffset(stringIDToTypeID("historyState"), -4 );
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 粘贴图层() //粘贴图层
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("paste"), d, DialogModes.NO);
}
function 复制图层() //复制图层
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("copyEvent"), d, DialogModes.NO);
}
function 载入选区() //载入选区
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("transparencyEnum"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 取消选择() //取消选择
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("to"), stringIDToTypeID("ordinal"), stringIDToTypeID("none"));
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 图层选择(当前图层名称) //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putName(stringIDToTypeID("layer"), 当前图层名称);
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(6);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 自由变换() //自由变换
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("freeTransformCenterState"), stringIDToTypeID("quadCenterState"), stringIDToTypeID("QCSAverage"));
var d1 = new ActionDescriptor();
d1.putUnitDouble(stringIDToTypeID("horizontal"), stringIDToTypeID("distanceUnit"), 0);
d1.putUnitDouble(stringIDToTypeID("vertical"), stringIDToTypeID("distanceUnit"), 0);
d.putObject(stringIDToTypeID("offset"), stringIDToTypeID("offset"), d1);
d.putUnitDouble(stringIDToTypeID("width"), stringIDToTypeID("percentUnit"), -100);
d.putUnitDouble(stringIDToTypeID("height"), stringIDToTypeID("percentUnit"), -100);
d.putBoolean(stringIDToTypeID("linked"), true);
d.putEnumerated(charIDToTypeID("Intr"), stringIDToTypeID("interpolationType"), stringIDToTypeID("nearestNeighbor"));
executeAction(stringIDToTypeID("transform"), d, DialogModes.NO);
}
function 选择上一图层() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("forwardEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(8);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 添加图层蒙版() //添加图层蒙版
{
var d = new ActionDescriptor();
d.putClass(stringIDToTypeID("new"), stringIDToTypeID("channel"));
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("mask"));
d.putReference(stringIDToTypeID("at"), r);
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("userMaskEnabled"), stringIDToTypeID("revealSelection"));
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 应用图层蒙版() //应用图层蒙版
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("apply"), true);
executeAction(stringIDToTypeID("delete"), d, DialogModes.NO);
}
function 拼合所有蒙版() //拼合所有蒙版
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("document"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("e805a6ee-6d75-4b62-b6fe-f5873b5fdf20"), d, DialogModes.NO);
}
function 选择蒙版() //选择蒙版
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("mask"));
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("makeVisible"), false);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 历史记录回退() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putOffset(stringIDToTypeID("historyState"), -5);
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 烧花线添加() {
app.activeDocument.suspendHistory("烧花线添加", "烧花线()");
function 烧花线() {
// 遍历当前文档图层
var doc = app.activeDocument;
var layers = doc.layers;
var filteredLayers = [];
// 遍历图层筛选以P开头的图层
for (var i = 0; i < layers.length; i++) {
var layer = layers[i];
if (layer.name.charAt(0) === 'P') {
filteredLayers.push(layer);
}
}
空置图层()
// 输出图层名称
for (var j = 0; j < filteredLayers.length; j++) {
var filteredLayer = filteredLayers[j];
var 裁片底图名称=filteredLayer.name;
多选图层(裁片底图名称);
// alert(filteredLayer.name);
}
合并图层();
置为顶层();
画布大小();
var layer = app.activeDocument.activeLayer;
layer.name = "底图";
恢复默认颜色()
矩形选框像素点()
//色彩范围()
填充();
魔棒烧花线()
新建图层()
var layer2 = app.activeDocument.activeLayer;
layer2.name = "剪口";
扩展2();
恢复止口线默认颜色()
填充();
矩形选框准备删除()
清除();
魔棒();
扩展();
选择反向();
清除();
var 底图 = app.activeDocument.layers.getByName( "底图");
app.activeDocument.activeLayer=底图;
矩形选框准备删除()
清除();
置为底层()
图层样式()
取消选择()
function 多选图层(裁片底图名称) //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putName(stringIDToTypeID("layer"), 裁片底图名称);
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("selectionModifier"), stringIDToTypeID("selectionModifierType"), stringIDToTypeID("addToSelection"));
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(4);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 空置图层() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("selectNoLayers"), d, DialogModes.NO);
}
function 恢复止口线默认颜色() //取消选择
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("color"), stringIDToTypeID("foregroundColor"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
d1.putDouble(stringIDToTypeID("cyan"), 20);
d1.putDouble(stringIDToTypeID("magenta"), 0);
d1.putDouble(stringIDToTypeID("yellowColor"), 0);
d1.putDouble(stringIDToTypeID("black"), 0);
d.putObject(stringIDToTypeID("to"), stringIDToTypeID("CMYKColorClass"), d1);
d.putString(stringIDToTypeID("source"), "photoshopPicker");
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 合并图层() //合并图层
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("mergeLayersNew"), d, DialogModes.NO);
}
function 恢复默认颜色() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("color"), stringIDToTypeID("colors"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("reset"), d, DialogModes.NO);
}
function 魔棒烧花线() //魔棒
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
d1.putUnitDouble(stringIDToTypeID("horizontal"), stringIDToTypeID("distanceUnit"), 0);
d1.putUnitDouble(stringIDToTypeID("vertical"), stringIDToTypeID("distanceUnit"), 0);
d.putObject(stringIDToTypeID("to"), stringIDToTypeID("point"), d1);
d.putInteger(stringIDToTypeID("tolerance"), 6);
d.putBoolean(stringIDToTypeID("contiguous"), false);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 矩形选框像素点() //矩形选框
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
d1.putUnitDouble(stringIDToTypeID("top"), stringIDToTypeID("distanceUnit"), 0);
d1.putUnitDouble(stringIDToTypeID("left"), stringIDToTypeID("distanceUnit"), 0);
d1.putUnitDouble(stringIDToTypeID("bottom"), stringIDToTypeID("distanceUnit"), 0.48);
d1.putUnitDouble(stringIDToTypeID("right"), stringIDToTypeID("distanceUnit"), 0.48);
d.putObject(stringIDToTypeID("to"), stringIDToTypeID("rectangle"), d1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 置为底层() //置为底层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("back"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("move"), d, DialogModes.NO);
}
function 置为顶层() //置为顶层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("front"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("move"), d, DialogModes.NO);
}
function 色彩范围() //色彩范围
{
var d = new ActionDescriptor();
d.putInteger(stringIDToTypeID("fuzziness"), 40);
var d1 = new ActionDescriptor();
d1.putDouble(stringIDToTypeID("luminance"), 0);
d1.putDouble(stringIDToTypeID("a"), 0);
d1.putDouble(stringIDToTypeID("b"), 0);
d.putObject(stringIDToTypeID("minimum"), stringIDToTypeID("labColor"), d1);
var d2 = new ActionDescriptor();
d2.putDouble(stringIDToTypeID("luminance"), 0);
d2.putDouble(stringIDToTypeID("a"), 0);
d2.putDouble(stringIDToTypeID("b"), 0);
d.putObject(stringIDToTypeID("maximum"), stringIDToTypeID("labColor"), d2);
d.putInteger(stringIDToTypeID("colorModel"), 0);
executeAction(stringIDToTypeID("colorRange"), d, DialogModes.NO);
}
function 新建图层() //新建图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("layer"));
d.putReference(stringIDToTypeID("null"), r);
d.putInteger(stringIDToTypeID("layerID"), 33);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 扩展2() //扩展
{
var d = new ActionDescriptor();
d.putUnitDouble(stringIDToTypeID("by"), stringIDToTypeID("pixelsUnit"), 1);
d.putBoolean(stringIDToTypeID("selectionModifyEffectAtCanvasBounds"), false);
executeAction(stringIDToTypeID("expand"), d, DialogModes.NO);
}
function 填充() //填充
{
var d = new ActionDescriptor();
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("fillContents"), stringIDToTypeID("foregroundColor"));
d.putUnitDouble(stringIDToTypeID("opacity"), stringIDToTypeID("percentUnit"), 100);
d.putEnumerated(stringIDToTypeID("mode"), stringIDToTypeID("blendMode"), stringIDToTypeID("normal"));
executeAction(stringIDToTypeID("fill"), d, DialogModes.NO);
}
function 画布大小() //画布大小
{
var d = new ActionDescriptor();
d.putBoolean(stringIDToTypeID("relative"), true);
d.putUnitDouble(stringIDToTypeID("width"), stringIDToTypeID("distanceUnit"), 40);
d.putUnitDouble(stringIDToTypeID("height"), stringIDToTypeID("distanceUnit"), 40);
d.putEnumerated(stringIDToTypeID("horizontal"), stringIDToTypeID("horizontalLocation"), stringIDToTypeID("center"));
d.putEnumerated(stringIDToTypeID("vertical"), stringIDToTypeID("verticalLocation"), stringIDToTypeID("center"));
executeAction(stringIDToTypeID("canvasSize"), d, DialogModes.NO);
}
function 魔棒() //魔棒
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
d1.putUnitDouble(stringIDToTypeID("horizontal"), stringIDToTypeID("pixelsUnit"), 3);
d1.putUnitDouble(stringIDToTypeID("vertical"), stringIDToTypeID("pixelsUnit"), 3);
d.putObject(stringIDToTypeID("to"), stringIDToTypeID("point"), d1);
d.putInteger(stringIDToTypeID("tolerance"), 6);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 矩形选框准备删除() //矩形选框
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
d1.putUnitDouble(stringIDToTypeID("top"), stringIDToTypeID("distanceUnit"), 0);
d1.putUnitDouble(stringIDToTypeID("left"), stringIDToTypeID("distanceUnit"), 0);
d1.putUnitDouble(stringIDToTypeID("bottom"), stringIDToTypeID("distanceUnit"), 0.96);
d1.putUnitDouble(stringIDToTypeID("right"), stringIDToTypeID("distanceUnit"), 0.96);
d.putObject(stringIDToTypeID("to"), stringIDToTypeID("rectangle"), d1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 扩展() //扩展
{
var d = new ActionDescriptor();
d.putUnitDouble(stringIDToTypeID("by"), stringIDToTypeID("pixelsUnit"), 25);
d.putBoolean(stringIDToTypeID("selectionModifyEffectAtCanvasBounds"), false);
executeAction(stringIDToTypeID("expand"), d, DialogModes.NO);
}
function 选择反向() //选择反向
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("inverse"), d, DialogModes.NO);
}
function 清除() //清除
{
app.activeDocument.selection.clear();
}
function 图层样式() //图层样式
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("property"), stringIDToTypeID("layerEffects"));
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
d1.putUnitDouble(stringIDToTypeID("scale"), stringIDToTypeID("percentUnit"), 12);
var d2 = new ActionDescriptor();
d2.putBoolean(stringIDToTypeID("enabled"), true);
d2.putBoolean(stringIDToTypeID("present"), true);
d2.putBoolean(stringIDToTypeID("showInDialog"), true);
d2.putEnumerated(stringIDToTypeID("style"), stringIDToTypeID("frameStyle"), stringIDToTypeID("outsetFrame"));
d2.putEnumerated(stringIDToTypeID("paintType"), stringIDToTypeID("frameFill"), stringIDToTypeID("solidColor"));
d2.putEnumerated(stringIDToTypeID("mode"), stringIDToTypeID("blendMode"), stringIDToTypeID("normal"));
d2.putUnitDouble(stringIDToTypeID("opacity"), stringIDToTypeID("percentUnit"), 100);
d2.putUnitDouble(stringIDToTypeID("size"), stringIDToTypeID("pixelsUnit"), 16);
var d3 = new ActionDescriptor();
d3.putDouble(stringIDToTypeID("red"), 255);
d3.putDouble(stringIDToTypeID("green"), 0);
d3.putDouble(stringIDToTypeID("blue"), 0);
d2.putObject(stringIDToTypeID("color"), stringIDToTypeID("RGBColor"), d3);
d2.putBoolean(stringIDToTypeID("overprint"), false);
d1.putObject(stringIDToTypeID("frameFX"), stringIDToTypeID("frameFX"), d2);
d.putObject(stringIDToTypeID("to"), stringIDToTypeID("layerEffects"), d1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 取消选择() //取消选择
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("to"), stringIDToTypeID("ordinal"), stringIDToTypeID("none"));
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
}
}
"""

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,385 @@
dxf15_jscode = """
function 图层自动编组2() {
app.activeDocument.suspendHistory("图层自动编组", "图层自动编组()");
}
function 图层自动编组(){
var currentDocument = app.activeDocument;
var matchCount = 0; // 匹配到的数值计数
var existingPatternSet = false;
var layerNames = []; // 保存匹配到的图层名称的数组
// 遍历图层
for (var j = 0; j < currentDocument.layers.length; j++) {
var layer = currentDocument.layers[j];
var layerName = layer.name;
// 检查图层名称是否以P开头并且后面跟着数字
if (/^P\d+$/.test(layerName)) {
matchCount++;
layerNames.push(layer); // 将匹配到的图层添加到数组中
}
}
// 输出匹配到的数值个数
$.writeln("匹配到的数值个数:" + matchCount);
// 遍历匹配到的图层名称
for (var i = 0; i < layerNames.length; i++) {
var layerName = layerNames[i].name;
// $.writeln("匹配到的图层名称:" + layerName);
var 当前花样图层 = app.activeDocument.layers.getByName(layerName);
app.activeDocument.activeLayer = 当前花样图层;
图层编组main()
app.activeDocument.activeLayer.name=layerName
}
}
function 图层编组main() //图层编组
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("layerSection"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("from"), r1);
d.putInteger(stringIDToTypeID("layerSectionStart"), 43);
d.putInteger(stringIDToTypeID("layerSectionEnd"), 44);
d.putString(stringIDToTypeID("name"), "main");
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 快速超级链接2() {
app.activeDocument.suspendHistory("快速超链接", "快速超级链接()");
}
function 快速超级链接() {
var 导出目录 = Folder.selectDialog("选择超链接素材目录");
if (!导出目录) {
alert("未选择导出目录。操作已取消。");
return;
}
画布大小()
app.preferences.rulerUnits = Units.PIXELS
var currentDocument = app.activeDocument;
var matchCount = 0; // 匹配到的数值计数
var existingPatternSet = false;
var layerNames = []; // 保存匹配到的图层名称的数组
// 遍历图层
for (var j = 0; j < currentDocument.layers.length; j++) {
var layer = currentDocument.layers[j];
var layerName = layer.name;
// 检查图层名称是否以P开头并且后面跟着数字
if (/^P\d+$/.test(layerName)) {
matchCount++;
layerNames.push(layer); // 将匹配到的图层添加到数组中
}
}
// 输出匹配到的数值个数
// $.writeln("匹配到的数值个数:" + matchCount);
// 遍历匹配到的图层名称
for (var i = 0; i < layerNames.length; i++) {
var layerName = layerNames[i].name;
$.writeln("匹配到的图层名称:" + layerName);
var 当前花样图层 = app.activeDocument.layers.getByName(layerName);
app.activeDocument.activeLayer = 当前花样图层;
切换mask()
载入选区()
var 边距 = 获取当前选区四边距();
var 毫米 = 130;
var 每英寸像素数 = app.activeDocument.resolution; // 获取当前文档的分辨率(每英寸像素数)
var 扩展像素 = 毫米转像素(毫米, 每英寸像素数);
var 裁切上边距= 边距.top-扩展像素
var 裁切左边距= 边距.left-扩展像素
var 裁切下边距= 边距.bottom+扩展像素
var 裁切右边距= 边距.right+扩展像素
var selRegion = [
[裁切左边距,裁切上边距],
[裁切右边距,裁切上边距],
[裁切右边距,裁切下边距],
[裁切左边距,裁切下边距]
];
app.activeDocument.selection.select(selRegion, SelectionType.REPLACE);
新建图层()
app.activeDocument.selection.select(selRegion, SelectionType.REPLACE);
var c = new SolidColor();
c.rgb.hexValue = "FFFFFF";
app.activeDocument.selection.fill(c);
后移一层()
app.activeDocument.activeLayer = 当前花样图层;
切换mask()
载入选区()
删除图层蒙版()
创建剪贴蒙版()
向下合并()
新建文档()
当前花样图层 = app.activeDocument.activeLayer
app.activeDocument.crop( 当前花样图层.bounds, 0);
var 文件路径 = 导出目录 + "/" + layerName + ".tif";
tiffOptions = new TiffSaveOptions();
app.activeDocument.saveAs(new File(文件路径), tiffOptions);
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
app.activeDocument=currentDocument
转换为智能对象()
重新链接到文件(文件路径)
添加图层蒙版()
//当前图层=app.activeDocument.activeLayer
//当前图层.name=layerName
}
}
function 新建文档() //复制图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("document"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("using"), r1);
d.putInteger(stringIDToTypeID("version"), 5);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 转换为智能对象() //转换为智能对象
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("newPlacedLayer"), d, DialogModes.NO);
}
function 重新链接到文件(文件路径) //重新链接到文件
{
var d = new ActionDescriptor();
d.putPath(stringIDToTypeID("null"), new File(文件路径));
d.putInteger(stringIDToTypeID("layerID"), 94);
executeAction(stringIDToTypeID("placedLayerRelinkToFile"), d, DialogModes.NO);
}
function 切换mask() //取消选择
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("mask"));
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("makeVisible"), false);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 毫米转像素(毫米, 每英寸像素数) {
var 每英寸毫米数 = 25.4;
var 英寸 = 毫米 / 每英寸毫米数;
return Math.round(英寸 * 每英寸像素数);
}
function 画布大小() //画布大小
{
var d = new ActionDescriptor();
d.putBoolean(stringIDToTypeID("relative"), true);
d.putUnitDouble(stringIDToTypeID("width"), stringIDToTypeID("distanceUnit"), 850.56);
d.putUnitDouble(stringIDToTypeID("height"), stringIDToTypeID("distanceUnit"), 850.56);
d.putEnumerated(stringIDToTypeID("horizontal"), stringIDToTypeID("horizontalLocation"), stringIDToTypeID("center"));
d.putEnumerated(stringIDToTypeID("vertical"), stringIDToTypeID("verticalLocation"), stringIDToTypeID("center"));
executeAction(stringIDToTypeID("canvasSize"), d, DialogModes.NO);
}
function 载入选区() //载入选区
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 获取当前选区四边距() {
var currentDocument = app.activeDocument;
var selectionBounds = currentDocument.selection.bounds;
var top = selectionBounds[1].value;
var left = selectionBounds[0].value;
var bottom = selectionBounds[3].value;
var right = selectionBounds[2].value;
return {
top: top,
left: left,
bottom: bottom,
right: right
};
}
function 新建图层() //新建图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("layer"));
d.putReference(stringIDToTypeID("null"), r);
d.putInteger(stringIDToTypeID("layerID"), 135);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 后移一层() //后移一层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("previous"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("move"), d, DialogModes.NO);
}
function 删除图层蒙版() //删除图层蒙版
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("mask"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("delete"), d, DialogModes.NO);
}
function 载入选区() //载入选区
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 删除图层蒙版() //删除图层蒙版
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("delete"), d, DialogModes.NO);
}
function 向下合并() //向下合并
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("mergeLayersNew"), d, DialogModes.NO);
}
function 添加图层蒙版() //添加图层蒙版
{
var d = new ActionDescriptor();
d.putClass(stringIDToTypeID("new"), stringIDToTypeID("channel"));
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("mask"));
d.putReference(stringIDToTypeID("at"), r);
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("userMaskEnabled"), stringIDToTypeID("revealSelection"));
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 创建剪贴蒙版() //创建剪贴蒙版
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("groupEvent"), d, DialogModes.NO);
}
"""

View File

@@ -0,0 +1,587 @@
dxf16_jscode = """
function 快速定位码链接() {
app.activeDocument.suspendHistory("快速定位码链接", "花样图层导出()");
}
function 花样图层导出() {
var 导出目录 = Folder.selectDialog("选择外链素材目录");
if (!导出目录) {
alert("未选择导出目录。操作已取消。");
return;
}
花样图层导出为TIF透明底(导出目录)
// 花样图层导出为TIF(导出目录);
}
function 花样图层导出为TIF透明底(导出目录) {
app.preferences.rulerUnits = Units.MM;
var doc = app.activeDocument;
var 扩展毫米数=80
// 获取文档中的所有图层
var allLayers = doc.layers;
var 名称数组 = [];
// 循环遍历所有图层
for (var i = 0; i < allLayers.length; i++) {
// 检查图层是否是图层组
if (allLayers[i] instanceof LayerSet) {
// 获取图层组中的所有子图层
subLayers = allLayers[i].layers;
图层组名称=allLayers[i].name
文档名称=app.activeDocument.name
文档名称去除后缀 = 文档名称.replace(/\.[^\.]+$/, "");
// 检查图层组是否包含子图层
if (subLayers.length > 0) {
// 获取图层组中最后一个子图层的名称
var lastSubLayer = subLayers[subLayers.length - 1];
var lastSubLayerName = lastSubLayer.name;
var lastSubLayerName = lastSubLayer.name;
FastSubLayer = subLayers[0];
FastSubLayername= FastSubLayer.name
//alert(SastSubLayerName)
// 输出图层组名称和最后一个子图层的名称
if (图层组名称 === "填充底图") {
for (var j = 0; j < subLayers.length; j++) {
// alert(subLayers[j].name);
填充底图组内部循环名称 = subLayers[j].name
填充底图循环素材 = app.activeDocument.layerSets.getByName("填充底图").layers.getByName(填充底图组内部循环名称);
app.activeDocument.activeLayer = 填充底图循环素材;
新建文档()
// 合并图层()
载入选区()
裁剪()
名称=文档名称去除后缀+"-("+填充底图组内部循环名称 +")-填充底图"
名称数组.push('"' + 名称 + '"');
制作图案预设(名称)
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
app.activeDocument=doc
// 执行某一个操作(例如,设置图层组的可见性)
// alert("测试")
}
// 手动创建JSON格式的字符串
var jsonStr = '[' + 名称数组.join(', ') + ']';
// 创建一个文件对象指向桌面
var file = new File(导出目录 + "/名称数据.json");
// 打开文件写入JSON字符串然后关闭文件
file.open('w');
file.write(jsonStr);
file.close();
} else {
// 执行别的操作(例如,隐藏其他图层组)
空白裁片模板 = app.activeDocument.layerSets.getByName(图层组名称).layers.getByName(lastSubLayerName);
app.activeDocument.activeLayer = 空白裁片模板;
新建图层()
app.activeDocument.activeLayer.name="最大白边值"
裁片边界 = lastSubLayer.bounds;
扩展值 = 毫米转像素(扩展毫米数); //50cm
裁片边界_左 = 毫米转像素(裁片边界[0]) - 扩展值;
裁片边界_上 = 毫米转像素(裁片边界[1]) - 扩展值;
裁片边界_右 = 毫米转像素(裁片边界[2]) + 扩展值;
裁片边界_下 = 毫米转像素(裁片边界[3]) + 扩展值;
var selRegion = [
[裁片边界_左,裁片边界_上],
[裁片边界_右,裁片边界_上],
[裁片边界_右,裁片边界_下],
[裁片边界_左,裁片边界_下]
];
app.activeDocument.selection.select(selRegion, SelectionType.REPLACE);
背景切换();
恢复白底();
填充();
隐藏图层();
裁片图层组 = app.activeDocument.layerSets.getByName(图层组名称)
app.activeDocument.activeLayer = 裁片图层组 ;
新建文档()
空白裁片模板 = app.activeDocument.layerSets.getByName(图层组名称).layers.getByName(lastSubLayerName);
app.activeDocument.activeLayer = 空白裁片模板;
删除图层()
最大白边值=app.activeDocument.layerSets.getByName(图层组名称).layers.getByName("最大白边值");
app.activeDocument.activeLayer = 最大白边值;
app.activeDocument.crop(最大白边值.bounds, 0);
// 保存为TIF
var 文件路径 = 导出目录 + "/" + 图层组名称 + ".tif";
tiffOptions = new TiffSaveOptions();
app.activeDocument.saveAs(new File(文件路径), tiffOptions);
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
app.activeDocument=doc
最大白边值=app.activeDocument.layerSets.getByName(图层组名称).layers.getByName(FastSubLayername);
app.activeDocument.activeLayer = 最大白边值;
//上移图层()
多选图层()
转换为智能对象()
释放剪贴蒙版()
栅格化图层()
选择下一图层()
新建图层()
背景切换();
恢复白底();
填充();
选择上一图层()
添加图层蒙版()
向下合并()
转换为智能对象()
重新链接到文件(文件路径)
创建剪贴蒙版()
}
}
}
}
}
function 恢复白底() //删除图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("color"), stringIDToTypeID("colors"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("exchange"), d, DialogModes.NO);
}
function 创建剪贴蒙版() //创建剪贴蒙版
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("groupEvent"), d, DialogModes.NO);
}
function 制作图案预设(名称) //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("pattern"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putProperty(stringIDToTypeID("property"), stringIDToTypeID("selection"));
r1.putEnumerated(stringIDToTypeID("document"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("using"), r1);
d.putString(stringIDToTypeID("name"), 名称);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function mergeLayersNew_72799682617188() //
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("mergeLayersNew"), d, DialogModes.NO);
}
function 合并图层() //
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("mergeLayersNew"), d, DialogModes.NO);
}
function 背景切换() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("color"), stringIDToTypeID("colors"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("reset"), d, DialogModes.NO);
}
function 图层可见性show() //图层可见性
{
var d = new ActionDescriptor();
var list = new ActionList();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
list.putReference(r);
d.putList(stringIDToTypeID("null"), list);
executeAction(stringIDToTypeID("show"), d, DialogModes.NO);
}
function 裁剪() //裁剪
{
var d = new ActionDescriptor();
d.putBoolean(stringIDToTypeID("delete"), true);
executeAction(stringIDToTypeID("crop"), d, DialogModes.NO);
}
function 重新链接到文件(文件路径) //重新链接到文件
{
var d = new ActionDescriptor();
d.putPath(stringIDToTypeID("null"), new File(文件路径));
d.putInteger(stringIDToTypeID("layerID"), 94);
executeAction(stringIDToTypeID("placedLayerRelinkToFile"), d, DialogModes.NO);
}
function 转换为智能对象() //转换为智能对象
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("newPlacedLayer"), d, DialogModes.NO);
}
function 载入选区() //载入选区
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("transparencyEnum"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 向下合并() //向下合并
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("mergeLayersNew"), d, DialogModes.NO);
}
function 添加图层蒙版() //添加图层蒙版
{
var d = new ActionDescriptor();
d.putClass(stringIDToTypeID("new"), stringIDToTypeID("channel"));
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("mask"));
d.putReference(stringIDToTypeID("at"), r);
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("userMaskEnabled"), stringIDToTypeID("revealSelection"));
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 上移图层() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("forwardEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(11);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 毫米转像素(毫米)
{
//厘米转像素
doc_w = app.activeDocument.width;
//用户设定的厘米数 支持小数
user_mm = UnitValue(毫米,"mm");
user_px = user_mm.as("px")*app.activeDocument.resolution/72;
return user_px;
}
function 删除图层() //删除图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var list = new ActionList();
list.putInteger(22);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("delete"), d, DialogModes.NO);
}
function 多选图层(SastSubLayerName) //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putName(stringIDToTypeID("layer"), "最大白边值");
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("selectionModifier"), stringIDToTypeID("selectionModifierType"), stringIDToTypeID("addToSelectionContinuous"));
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(115);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 新建图层() //新建图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("layer"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
d1.putBoolean(stringIDToTypeID("group"), true);
d.putObject(stringIDToTypeID("using"), stringIDToTypeID("layer"), d1);
d.putInteger(stringIDToTypeID("layerID"), 22);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 释放剪贴蒙版() //释放剪贴蒙版
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("ungroup"), d, DialogModes.NO);
}
function 添加图层蒙版() //添加图层蒙版
{
var d = new ActionDescriptor();
d.putClass(stringIDToTypeID("new"), stringIDToTypeID("channel"));
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("mask"));
d.putReference(stringIDToTypeID("at"), r);
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("userMaskEnabled"), stringIDToTypeID("revealSelection"));
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 向下合并() //向下合并
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("mergeLayersNew"), d, DialogModes.NO);
}
function 栅格化图层() //栅格化图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("rasterizeLayer"), d, DialogModes.NO);
}
function 选择下一图层() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("backwardEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(314);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 选择上一图层() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("forwardEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(369);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 新建图层() //新建图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("layer"));
d.putReference(stringIDToTypeID("null"), r);
d.putInteger(stringIDToTypeID("layerID"), 373);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 恢复白底() //删除图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("color"), stringIDToTypeID("colors"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("exchange"), d, DialogModes.NO);
}
function 背景切换() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("color"), stringIDToTypeID("colors"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("reset"), d, DialogModes.NO);
}
function 填充() //填充
{
var d = new ActionDescriptor();
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("fillContents"), stringIDToTypeID("foregroundColor"));
d.putUnitDouble(stringIDToTypeID("opacity"), stringIDToTypeID("percentUnit"), 100);
d.putEnumerated(stringIDToTypeID("mode"), stringIDToTypeID("blendMode"), stringIDToTypeID("normal"));
executeAction(stringIDToTypeID("fill"), d, DialogModes.NO);
}
function 填充() //填充
{
var d = new ActionDescriptor();
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("fillContents"), stringIDToTypeID("foregroundColor"));
d.putUnitDouble(stringIDToTypeID("opacity"), stringIDToTypeID("percentUnit"), 100);
d.putEnumerated(stringIDToTypeID("mode"), stringIDToTypeID("blendMode"), stringIDToTypeID("normal"));
executeAction(stringIDToTypeID("fill"), d, DialogModes.NO);
}
function 隐藏图层() //
{
var d = new ActionDescriptor();
var list = new ActionList();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
list.putReference(r);
d.putList(stringIDToTypeID("null"), list);
executeAction(stringIDToTypeID("hide"), d, DialogModes.NO);
}
function 新建文档() //复制图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("document"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("using"), r1);
d.putInteger(stringIDToTypeID("version"), 5);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
"""

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,284 @@
dxf18_jscode = """
function 龙服的快速换图(){
// 强制使用 UTF-8 编码
#target photoshop
$.localize = true;
// 创建对话框
var dialog = new Window("dialog");
dialog.text = "快速换图特定版本";
dialog.orientation = "column";
dialog.alignChildren = ["left","top"];
dialog.spacing = 10;
dialog.margins = 16;
// 大货模板文件夹选择面板
var templatePanel = dialog.add("panel", undefined, "大货模板文件夹选择");
templatePanel.orientation = "row";
templatePanel.alignChildren = ["left","center"];
templatePanel.spacing = 10;
templatePanel.margins = 10;
// 大货模板文件夹路径文本框
var templatePathEditText = templatePanel.add('edittext', undefined, '', { properties: { readonly: true } });
templatePathEditText.preferredSize.width = 300;
// 大货模板路径选择按钮
var selectTemplateButton = templatePanel.add("button", undefined, "选择文件夹");
selectTemplateButton.onClick = function() {
var selectedFolder = Folder.selectDialog("选择大货模板文件夹");
if (selectedFolder != null) {
templatePathEditText.text = selectedFolder.fsName;
// alert( templatePathEditText.text)
updateFileNames(selectedFolder);
}
};
// 切片裁片文件夹选择面板
var slicePanel = dialog.add("panel", undefined, "切片裁片文件夹选择");
slicePanel.orientation = "row";
slicePanel.alignChildren = ["left","center"];
slicePanel.spacing = 10;
slicePanel.margins = 10;
// 切片裁片文件夹路径文本框
var slicePathEditText = slicePanel.add('edittext', undefined, '', { properties: { readonly: true } });
slicePathEditText.preferredSize.width = 300;
// 切片裁片路径选择按钮
var selectSliceButton = slicePanel.add("button", undefined, "选择文件夹");
selectSliceButton.onClick = function() {
var selectedFolder = Folder.selectDialog("选择切片裁片文件夹");
if (selectedFolder != null) {
slicePathEditText.text = selectedFolder.fsName;
// 可以在这里执行切片裁片相关的操作
}
};
// 大货裁片名称面板
var fileNamesPanel = dialog.add("panel", undefined, "大货裁片名称数量");
fileNamesPanel.orientation = "column";
fileNamesPanel.alignChildren = ["left","top"];
fileNamesPanel.spacing = 10;
fileNamesPanel.margins = 10;
// 存储文件名和输入框内容的数组
var userInputData = [];
// 更新文件名和输入框显示
function updateFileNames(folder) {
// 移除之前的所有元素
for (var i = fileNamesPanel.children.length - 1; i >= 0; i--) {
fileNamesPanel.children[i].remove();
}
// 清空数组
userInputData = [];
var files = folder.getFiles();
for (var i = 0; i < files.length; i++) {
// 使用正则表达式提取文件名(去掉路径和后缀)
完整文件路径=files[i].fsName
var fileName = new File(files[i]).name.replace(/\.\w+$/, "");
// 创建新的静态文本框
var fileNameStaticText = fileNamesPanel.add('statictext', undefined, fileName);
fileNameStaticText.preferredSize.width = 200;
// 创建新的输入框
var inputEditText = fileNamesPanel.add('edittext', undefined, '');
inputEditText.preferredSize.width = 100;
// 存储文件名和输入框内容
userInputData.push({
fileName: fileName,
inputText: ''
});
}
// 重新绘制对话框
dialog.layout.layout(true);
dialog.layout.resize();
}
// OK 和 Cancel 按钮
var buttonsGroup = dialog.add("group");
buttonsGroup.orientation = "row";
buttonsGroup.alignChildren = ["fill","top"];
buttonsGroup.spacing = 10;
buttonsGroup.margins = 0;
var okButton = buttonsGroup.add("button", undefined, "执行");
okButton.onClick = function() {
// 在这里执行 OK 按钮的操作
updateUserData();
alertUserInput();
dialog.close();
};
var cancelButton = buttonsGroup.add("button", undefined, "取消");
cancelButton.onClick = function() {
// 在这里执行 Cancel 按钮的操作
dialog.close();
};
// 显示对话框
dialog.show();
// 更新用户输入数据
function updateUserData() {
for (var i = 1; i < fileNamesPanel.children.length; i += 2) {
userInputData[(i - 1) / 2].inputText = fileNamesPanel.children[i].text;
}
}
// 弹出用户输入的内容
function alertUserInput() {
var userInput = "";
for (var i = 0; i < userInputData.length; i++) {
var 文件路径=templatePathEditText.text
var 文件名=userInputData[i].fileName
var 完整文件路径=文件路径+"/"+文件名+".tif"
// alert( 完整文件路径)
var 文件数量= userInputData[i].inputText
var 文件夹路径=slicePathEditText.text
var 文件对象 = new File(完整文件路径);
if (文件对象.exists) {
app.open(文件对象);
} else {
//alert("文件不存在:" + 完整文件路径);
}
更换当前文档裁片组外链(文件夹路径)
图层选择()
activeDocument.activeLayer.textItem.contents=文件数量
选择裁片图层()
合并图层()
另存为(文件夹路径)
activeDocument.close(SaveOptions.DONOTSAVECHANGES);
}
}
alert("换图完成,请检查好文件进行打印大货!!!",dialog.text+"----关于");
dialog.close();
function 另存为(文件夹路径)
{
文档名称=activeDocument.name.replace(/(?:\.[^.]*$|$)/, '');
saveIn=File(文件夹路径+ "/"+文档名称);
tifSaveOpt = new TiffSaveOptions();
tifSaveOpt.imageCompression = TIFFEncoding.TIFFLZW;
tifSaveOpt.byteOrder = ByteOrder.IBM;
asCopy=true
app.activeDocument.saveAs(saveIn,tifSaveOpt,asCopy);
}
function 选择裁片图层() //
{
try {
var d = new ActionDescriptor();
var r = new ActionReference();
r.putName(stringIDToTypeID("layer"), "裁片");
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(74);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
catch (e) {
alert("找不到裁片图层",dialog.text+"----关于");
}
}
function 合并图层() //合并图层
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("mergeLayersNew"), d, DialogModes.NO);
}
function 更换当前文档裁片组外链(文件夹路径)
{
try
{
裁片组 = app.activeDocument.layerSets.getByName("裁片").layers;
}
catch(e)
{
alert("找不到裁片组",dialog.text+"----提示");
}
for(var i=0;i<裁片组.length;i++)
{
裁片 = 裁片组[i];
app.activeDocument.activeLayer = 裁片;
if(裁片.kind == LayerKind.SMARTOBJECT)
{
更换链接智能对象路径(文件夹路径);
}
}
}
function 更换链接智能对象路径(文件夹路径)
{
//获取当前图层外链的智能对象路径
//先获取链接的文件名
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
//~ r.putName(charIDToTypeID("Lyr "), "◆左袖口"); //按名称查找
descLayer = executeActionGet(r);
res = descLayer.getObjectValue(stringIDToTypeID("smartObject"));
链接文件名 = res.getString(stringIDToTypeID("fileReference"));
//$.writeln(链接文件名);
//~ 链接文件路径 = res.getPath(stringIDToTypeID("link"));
//~ $.writeln(链接文件路径);
图片路径 = 文件夹路径 + "/" + 链接文件名;
var d = new ActionDescriptor();
d.putPath(stringIDToTypeID("null"), new File(图片路径));
executeAction(stringIDToTypeID("placedLayerRelinkToFile"), d, DialogModes.NO);
}
function 图层选择() //
{
try {
var d = new ActionDescriptor();
var r = new ActionReference();
r.putName(stringIDToTypeID("layer"), "数量");
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(74);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
catch (e) {
alert("找不到数量图层",dialog.text+"----关于");
}
}
}
"""

View File

@@ -0,0 +1,166 @@
dxf19_jscode = """
// 文件夹路径
function 裁片抓取新的() {
app.activeDocument.suspendHistory("裁片抓取", "置入对象()");
}
function 置入对象() {
var folderPath = "D:/MarkTemp/Pdfmarktemp";
var folder = new Folder(folderPath);
// 获取文件夹中的文件
var files = folder.getFiles();
// 新建一个数组来存储文件名称
var fileNamesArray = [];
for (var i = 0; i < files.length; i++) {
if (files[i] instanceof File) {
var doc = app.activeDocument;
var fileName = files[i].name;
var newDocumentName = fileName.replace(/\.pdf$/, '');
var modifiedString = newDocumentName.replace(/-\d+_\d+/, '');
var newmodifiedString = modifiedString.split("-");
// 检查是否存在相同的文件名
var duplicate = false;
for (var j = 0; j < fileNamesArray.length; j++) {
if (fileNamesArray[j] === modifiedString) {
duplicate = true;
break;
}
}
if (duplicate) {
$.writeln("文件名重复,跳过: " + fileName);
continue;
}
// 将新的文档名添加到文件名数组中
fileNamesArray.push(modifiedString);
$.writeln("文档名称: " + fileName);
var 文件路径 = folderPath + "/" + fileName;
// 调用你的函数
var match = newDocumentName.match(/_(\d+)/);
var 角度信息 = match ? match[1] : "";
if (角度信息 === "180" || 角度信息 === "-180") {
置入对象180度(文件路径);
} else {
// 如果角度信息不是 "180""-180",执行默认逻辑
置入对象0度(文件路径);
}
var extractedPart = newmodifiedString[0];
var 大货裁片组名 = extractedPart + "-大货裁片";
// var 当前花样图层 = app.activeDocument.layers.getByName(newDocumentName);
// app.activeDocument.activeLayer = 当前花样图层;
var 大货裁片组;
try {
大货裁片组 = app.activeDocument.layerSets.getByName(大货裁片组名);
} catch (e) {
大货裁片组 = app.activeDocument.layerSets.add();
大货裁片组.name = 大货裁片组名;
}
// 将当前图层移动到大货裁片图层组内
app.activeDocument.activeLayer.move(大货裁片组, ElementPlacement.INSIDE);
栅格化图层智能对象();
// modifiedString = fileName.replace(/-\d+_\d+/, '');
app.activeDocument.activeLayer.name = modifiedString;
}
}
}
function 栅格化图层智能对象() {
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("rasterizeLayer"), d, DialogModes.NO);
}
function 栅格化图层智能对象() //栅格化图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("rasterizeLayer"), d, DialogModes.NO);
}
function 置入对象0度(文件路径)
{
var d = new ActionDescriptor();
var d1 = new ActionDescriptor();
d1.putEnumerated(stringIDToTypeID("selection"), stringIDToTypeID("pdfSelection"), stringIDToTypeID("page"));
d1.putInteger(stringIDToTypeID("pageNumber"), 1);
d1.putEnumerated(stringIDToTypeID("crop"), stringIDToTypeID("cropTo"), stringIDToTypeID("boundingBox"));
d1.putBoolean(stringIDToTypeID("suppressWarnings"), false);
d1.putBoolean(stringIDToTypeID("antiAlias"), true);
d1.putBoolean(stringIDToTypeID("clippingPath"), true);
d.putObject(stringIDToTypeID("as"), stringIDToTypeID("PDFGenericFormat"), d1);
d.putInteger(stringIDToTypeID("ID"), 4);
d.putPath(stringIDToTypeID("null"), new File(文件路径));
d.putEnumerated(stringIDToTypeID("freeTransformCenterState"), stringIDToTypeID("quadCenterState"), stringIDToTypeID("QCSAverage"));
var d2 = new ActionDescriptor();
d2.putUnitDouble(stringIDToTypeID("horizontal"), stringIDToTypeID("distanceUnit"), 0);
d2.putUnitDouble(stringIDToTypeID("vertical"), stringIDToTypeID("distanceUnit"), 0);
d.putObject(stringIDToTypeID("offset"), stringIDToTypeID("offset"), d2);
//d.putUnitDouble(stringIDToTypeID("width"), stringIDToTypeID("percentUnit"), -100);
// d.putUnitDouble(stringIDToTypeID("height"), stringIDToTypeID("percentUnit"), -100);
d.putBoolean(stringIDToTypeID("antiAlias"), false);
executeAction(stringIDToTypeID("placeEvent"), d, DialogModes.NO);
}
function 置入对象180度(文件路径)
{
var d = new ActionDescriptor();
var d1 = new ActionDescriptor();
d1.putEnumerated(stringIDToTypeID("selection"), stringIDToTypeID("pdfSelection"), stringIDToTypeID("page"));
d1.putInteger(stringIDToTypeID("pageNumber"), 1);
d1.putEnumerated(stringIDToTypeID("crop"), stringIDToTypeID("cropTo"), stringIDToTypeID("boundingBox"));
d1.putBoolean(stringIDToTypeID("suppressWarnings"), false);
d1.putBoolean(stringIDToTypeID("antiAlias"), true);
d1.putBoolean(stringIDToTypeID("clippingPath"), true);
d.putObject(stringIDToTypeID("as"), stringIDToTypeID("PDFGenericFormat"), d1);
d.putInteger(stringIDToTypeID("ID"), 4);
d.putPath(stringIDToTypeID("null"), new File(文件路径));
d.putEnumerated(stringIDToTypeID("freeTransformCenterState"), stringIDToTypeID("quadCenterState"), stringIDToTypeID("QCSAverage"));
var d2 = new ActionDescriptor();
d2.putUnitDouble(stringIDToTypeID("horizontal"), stringIDToTypeID("distanceUnit"), 0);
d2.putUnitDouble(stringIDToTypeID("vertical"), stringIDToTypeID("distanceUnit"), 0);
d.putObject(stringIDToTypeID("offset"), stringIDToTypeID("offset"), d2);
d.putUnitDouble(stringIDToTypeID("width"), stringIDToTypeID("percentUnit"), -100);
d.putUnitDouble(stringIDToTypeID("height"), stringIDToTypeID("percentUnit"), -100);
d.putBoolean(stringIDToTypeID("antiAlias"), false);
executeAction(stringIDToTypeID("placeEvent"), d, DialogModes.NO);
}
"""

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,136 @@
dxf20_jscode = """
function 混排通码延申导出() {
var 主文档 = app.activeDocument;
var 主文档路径 = 主文档.path;
var 新文件夹 = (主文档路径 + "/小片裁片"); // 在桌面上创建一个名为"导出目录"的文件夹
var 导出目录 = Folder(新文件夹);
if (!导出目录.exists) {
导出目录.create();
}
var doc = app.activeDocument;
// 获取所有图层
var layers = doc.layers;
// 存储符合条件的图层组
var matchingLayerSets = [];
// 遍历每个图层
for (var i = 0; i < layers.length; i++) {
var layer = layers[i];
// 如果是图层组,检查名称是否包含 "大货裁片"
if (layer.typename == "LayerSet" && layer.name.indexOf("大货裁片") !== -1) {
matchingLayerSets.push(layer);
// 输出图层组名称
$.writeln("图层组名称:" + layer.name);
图层组名称=layer.name
// 输出图层组内子图层的名称
for (var j = 0; j < layer.layers.length; j++) {
var subLayer = layer.layers[j];
$.writeln(" 子图层名称:" + subLayer.name);
子图层名称=subLayer.name
子图层名称分割=子图层名称.split("-")
素材图名称=子图层名称分割[0]
空白裁片模板 = app.activeDocument.layerSets.getByName(图层组名称).layers.getByName(子图层名称);
app.activeDocument.activeLayer = 空白裁片模板;
载入选区()
素材名称 = app.activeDocument.layers.getByName(素材图名称);
app.activeDocument.activeLayer = 素材名称;
添加图层蒙版()
新建文档()
当前花样图层 = app.activeDocument.activeLayer
app.activeDocument.crop( 当前花样图层.bounds, 0);
var 文件路径 = 导出目录 + "/" + 子图层名称 + ".tif";
tiffOptions = new TiffSaveOptions();
app.activeDocument.saveAs(new File(文件路径), tiffOptions);
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
app.activeDocument=doc
历史记录回退3()
}
}
}
// 输出符合条件的图层组数量
//$.writeln("符合条件的图层组数量:" + matchingLayerSets.length);
}
function 载入选区() //载入选区
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("transparencyEnum"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 添加图层蒙版() //添加图层蒙版
{
var d = new ActionDescriptor();
d.putClass(stringIDToTypeID("new"), stringIDToTypeID("channel"));
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("mask"));
d.putReference(stringIDToTypeID("at"), r);
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("userMaskEnabled"), stringIDToTypeID("revealSelection"));
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 新建文档() //复制图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("document"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("using"), r1);
d.putInteger(stringIDToTypeID("version"), 5);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 历史记录回退3() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putOffset(stringIDToTypeID("historyState"), -2);
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
"""

View File

@@ -0,0 +1,39 @@
dxf21_jscode = """
function 创建裁片排版文档(画布宽,画布高,分辨率,文档名称)
{
app.preferences.rulerUnits = Units.MM; //修改指定单位为毫米
app.documents.add(画布宽, 画布高,分辨率, 文档名称, NewDocumentMode.CMYK);
}
function 置入链接的智能对象(dir, DXFname) {
图片路径 = dir + "/" + DXFname + ".tif"
var d = new ActionDescriptor();
d.putInteger(stringIDToTypeID("ID"), 16);
d.putPath(stringIDToTypeID("null"), new File(图片路径));
d.putBoolean(stringIDToTypeID("linked"), true);
d.putEnumerated(stringIDToTypeID("freeTransformCenterState"), stringIDToTypeID("quadCenterState"), stringIDToTypeID("QCSAverage"));
var d1 = new ActionDescriptor();
executeAction(stringIDToTypeID("placeEvent"), d, DialogModes.NO);
}
function 裁片排版_lay(中心x_mm, 中心y_mm) {
ps中心x_mm = 中心x_mm;
ps中心y_mm = 中心y_mm;
alb = app.activeDocument.activeLayer.bounds;
当前x = (alb[0] + alb[2]) / 2; //mm数
当前y = (alb[1] + alb[3]) / 2; //mm数
app.activeDocument.activeLayer.translate(ps中心x_mm - Number(当前x), ps中心y_mm - Number(当前y)); //全局单位设置为mm即可
}
function 裁片角度(角度) {
app.activeDocument.activeLayer.rotate(角度);
//app.refresh()
}
"""

View File

@@ -0,0 +1,453 @@
dxf22_jscode = """
//PS智能对象换图
function 模特换衣功能(){
var dialog = new Window("dialog");
dialog.text = "模特批量替换";
dialog.preferredSize.width = 400;
dialog.preferredSize.height = 150;
dialog.orientation = "column";
dialog.alignChildren = ["center","top"];
dialog.spacing = 10;
dialog.margins = 16;
// GROUP1
// ======
var group1 = dialog.add("group");
group1.orientation = "row";
group1.alignChildren = ["left","center"];
group1.spacing = 10;
group1.margins = 0;
var statictext1 = group1.add("statictext");
statictext1.text = "模板文件";
statictext1.justify = "center";
var edittext1 = group1.add("edittext");
edittext1.preferredSize.width = 250;
var button1 = group1.add("button");
button1.text = "选择文件";
button1.justify = "center";
// GROUP2
// ======
var group2 = dialog.add("group");
group2.orientation = "row";
group2.alignChildren = ["left","center"];
group2.spacing = 10;
group2.margins = 0;
var statictext2 = group2.add("statictext");
statictext2.text = "素材目录";
statictext2.justify = "center";
var edittext2 = group2.add("edittext");
edittext2.preferredSize.width = 250;
var button2 = group2.add("button");
button2.text = "选择目录";
button2.justify = "center";
// GROUP3
// ======
var group3 = dialog.add("group");
group3.orientation = "row";
group3.alignChildren = ["left","center"];
group3.spacing = 10;
group3.margins = 0;
var statictext3 = group3.add("statictext");
statictext3.text = "导出目录";
statictext3.justify = "center";
var edittext3 = group3.add("edittext");
edittext3.preferredSize.width = 250;
var button3 = group3.add("button");
button3.text = "选择目录";
button3.justify = "center";
var group5 = dialog.add("group");
var cbox1 = group5.add("checkbox");
cbox1.text = "是否遍历子文件夹素材";
cbox1.value = true;
var cbox2 = group5.add("checkbox");
cbox2.text = "是否保存文件结构";
cbox2.value = true;
var cbox3 = group5.add("checkbox");
cbox3.text = "切片导出";
cbox3.value = false;
// GROUP4
// ======
var group4 = dialog.add("group");
group4.orientation = "row";
group4.alignChildren = ["left","center"];
group4.spacing = 10;
group4.margins = 0;
var button4 = group4.add("button");
button4.text = "执行";
button4.justify = "center";
var button5 = group4.add("button");
button5.text = "退出";
button5.justify = "center";
button1.onClick = function()
{
var inputFile= app.openDialog();
if (inputFile != null)
{
edittext1.text = decodeURI(inputFile);
}
}
button2.onClick = function()
{
var inputFolder = Folder.selectDialog("请选择素材目录:");
if (inputFolder != null)
{
edittext2.text = decodeURI(inputFolder);
}
}
button3.onClick = function()
{
var inputFolder = Folder.selectDialog("请选择导出目录:");
if (inputFolder != null)
{
edittext3.text = decodeURI(inputFolder);
}
}
button4.onClick = function()
{
模板路径 = edittext1.text;
素材目录 = edittext2.text;
导出目录 = edittext3.text;
main(模板路径,素材目录,导出目录);
}
button5.onClick = function()
{
dialog.close();
}
function main(模板路径,素材目录,导出目录)
{
if(Folder(导出目录).exists==false){Folder(导出目录).create();}
var doc = app.open(File(模板路径));
//~ 素材文件列表 = Folder(素材目录).getFiles("*.psd");
isSubFolders = cbox1.value;
素材文件列表 = 遍历目录指定类型文件(素材目录,isSubFolders);
alert("当前目录一共有"+素材文件列表.length+"个素材。","提示:");
try
{
lay_替换对象图层名 = "替换对象";
lay_替换对象 = app.activeDocument.artLayers.getByName(lay_替换对象图层名);
}
catch(e)
{
alert("未找到["+lay_替换对象图层名+"]智能对象图层!","提示:");
}
app.activeDocument.activeLayer = lay_替换对象;
for(var i=0;i<素材文件列表.length;i++)
{
scpsd_path = 素材文件列表[i];
//~ lay_替换对象.visible = false;
素材名 = decodeURI(File(scpsd_path).name.replace(/(?:\.[^.]*$|$)/, ''));
//替换内容
var d = new ActionDescriptor();
d.putPath(stringIDToTypeID("null"), new File(scpsd_path));
executeAction(stringIDToTypeID("placedLayerReplaceContents"), d, DialogModes.NO);
if(cbox2.value) //保持结构
{
结构导出目录 = 导出目录+"/" + getRelativePath(decodeURI(File(scpsd_path).path), 素材目录);
if(Folder(结构导出目录).exists==false){Folder(结构导出目录).create();}
保存路径 = 结构导出目录+"/"+素材名+".jpg";
$.writeln(保存路径);
if(cbox3.value) //按切片
{
//~ 按切片导出图片(结构导出目录,app.activeDocument.name.replace(/(?:\.[^.]*$|$)/, ''));
按切片导出图片(结构导出目录,素材名);
}
else
{
保存JPG(保存路径);
}
}
else
{
if(cbox3.value) //按切片
{
//~ 按切片导出图片(导出目录,app.activeDocument.name.replace(/(?:\.[^.]*$|$)/, ''));
按切片导出图片(导出目录,素材名);
}
else
{
保存JPG(导出目录+"/"+素材名+".jpg");
}
}
}
doc.close(SaveOptions.DONOTSAVECHANGES);
alert("处理完成!","提示:");
}
function getRelativePath(targetPath, basePath)
{
var targetFile = new File(targetPath);
var baseFolder = new Folder(basePath);
var relativePath = targetFile.getRelativeURI(baseFolder);
return decodeURI(relativePath); // 解码 URI 编码的路径
}
function 保存JPG(jpg_save_path)
{
// 以JPEG格式保存输出
var jpegOptions = new JPEGSaveOptions();
// 将jpeg质量设置得很低使文件很小
jpegOptions.quality = 12;
app.activeDocument.saveAs(new File(jpg_save_path), jpegOptions,true);
}
function 遍历目录指定类型文件(inputFolder,isSubFolders)
{
if(isSubFolders==undefined)
{
isSubFolders = true;
}
all_files_list = [];
if (inputFolder != null) {
filesArray = scanFolder(inputFolder);
if (filesArray.length > 0)
{
for (i = 0;i<filesArray.length;i++)
{
all_files_list.push(filesArray[i]);
}
}
}
return all_files_list;
function scanFolder(folder)
{
var filesArray = [],
fileList = Folder(folder).getFiles();
var file;
for (var i = 0; i < fileList.length; i++) {
file = fileList[i];
if (file instanceof Folder)
{
if(isSubFolders==false)
{
continue;
}
else
{
filesArray = filesArray.concat(scanFolder(file));
}
}
else if (file instanceof File && file.name.match(/\.(jpg|tif|psd|png|)$/i))
{
filesArray.push(file);
}
}
return filesArray;
}
}
function 按切片导出图片(保存路径,替换词)
{
//~ alert(保存路径);
var d = new ActionDescriptor();
var d1 = new ActionDescriptor();
d1.putEnumerated(charIDToTypeID("Op "), charIDToTypeID("SWOp"), charIDToTypeID("OpSa"));
d1.putBoolean(charIDToTypeID("DIDr"), true);
d1.putPath(stringIDToTypeID("in"), new File(保存路径));
d1.putString(charIDToTypeID("ovFN"), 替换词+".jpg");
d1.putEnumerated(stringIDToTypeID("format"), charIDToTypeID("IRFm"), stringIDToTypeID("JPEG"));
d1.putBoolean(charIDToTypeID("Intr"), false);
d1.putInteger(stringIDToTypeID("quality"), 80);
d1.putInteger(charIDToTypeID("QChS"), 0);
d1.putInteger(charIDToTypeID("QCUI"), 0);
d1.putBoolean(charIDToTypeID("QChT"), false);
d1.putBoolean(charIDToTypeID("QChV"), false);
d1.putBoolean(stringIDToTypeID("optimized"), true);
d1.putInteger(charIDToTypeID("Pass"), 1);
d1.putDouble(stringIDToTypeID("blur"), 0);
d1.putBoolean(charIDToTypeID("Mtt "), true);
d1.putBoolean(charIDToTypeID("EICC"), false);
d1.putInteger(charIDToTypeID("MttR"), 255);
d1.putInteger(charIDToTypeID("MttG"), 255);
d1.putInteger(charIDToTypeID("MttB"), 255);
d1.putBoolean(charIDToTypeID("SHTM"), false);
d1.putBoolean(charIDToTypeID("SImg"), true);
d1.putEnumerated(charIDToTypeID("SWsl"), charIDToTypeID("STsl"), charIDToTypeID("SLAl"));
d1.putEnumerated(charIDToTypeID("SWch"), charIDToTypeID("STch"), charIDToTypeID("CHDc"));
d1.putEnumerated(charIDToTypeID("SWmd"), charIDToTypeID("STmd"), charIDToTypeID("MDCC"));
d1.putBoolean(charIDToTypeID("ohXH"), false);
d1.putBoolean(charIDToTypeID("ohIC"), true);
d1.putBoolean(charIDToTypeID("ohAA"), true);
d1.putBoolean(charIDToTypeID("ohQA"), true);
d1.putBoolean(charIDToTypeID("ohCA"), false);
d1.putBoolean(charIDToTypeID("ohIZ"), true);
d1.putEnumerated(charIDToTypeID("ohTC"), charIDToTypeID("SToc"), charIDToTypeID("OC03"));
d1.putEnumerated(charIDToTypeID("ohAC"), charIDToTypeID("SToc"), charIDToTypeID("OC03"));
d1.putInteger(charIDToTypeID("ohIn"), -1);
d1.putEnumerated(charIDToTypeID("ohLE"), charIDToTypeID("STle"), charIDToTypeID("LE03"));
d1.putEnumerated(charIDToTypeID("ohEn"), charIDToTypeID("STen"), charIDToTypeID("EN00"));
d1.putBoolean(charIDToTypeID("olCS"), false);
d1.putEnumerated(charIDToTypeID("olEC"), charIDToTypeID("STst"), charIDToTypeID("ST00"));
d1.putEnumerated(charIDToTypeID("olWH"), charIDToTypeID("STwh"), charIDToTypeID("WH01"));
d1.putEnumerated(charIDToTypeID("olSV"), charIDToTypeID("STsp"), charIDToTypeID("SP04"));
d1.putEnumerated(charIDToTypeID("olSH"), charIDToTypeID("STsp"), charIDToTypeID("SP04"));
var list = new ActionList();
var d2 = new ActionDescriptor();
d2.putEnumerated(charIDToTypeID("ncTp"), charIDToTypeID("STnc"), charIDToTypeID("NC00"));
list.putObject(charIDToTypeID("SCnc"), d2);
var d3 = new ActionDescriptor();
d3.putEnumerated(charIDToTypeID("ncTp"), charIDToTypeID("STnc"), charIDToTypeID("NC19"));
list.putObject(charIDToTypeID("SCnc"), d3);
var d4 = new ActionDescriptor();
d4.putEnumerated(charIDToTypeID("ncTp"), charIDToTypeID("STnc"), charIDToTypeID("NC28"));
list.putObject(charIDToTypeID("SCnc"), d4);
var d5 = new ActionDescriptor();
d5.putEnumerated(charIDToTypeID("ncTp"), charIDToTypeID("STnc"), charIDToTypeID("NC24"));
list.putObject(charIDToTypeID("SCnc"), d5);
var d6 = new ActionDescriptor();
d6.putEnumerated(charIDToTypeID("ncTp"), charIDToTypeID("STnc"), charIDToTypeID("NC24"));
list.putObject(charIDToTypeID("SCnc"), d6);
var d7 = new ActionDescriptor();
d7.putEnumerated(charIDToTypeID("ncTp"), charIDToTypeID("STnc"), charIDToTypeID("NC24"));
list.putObject(charIDToTypeID("SCnc"), d7);
d1.putList(charIDToTypeID("olNC"), list);
d1.putBoolean(charIDToTypeID("obIA"), false);
d1.putString(charIDToTypeID("obIP"), "");
d1.putEnumerated(charIDToTypeID("obCS"), charIDToTypeID("STcs"), charIDToTypeID("CS01"));
var list1 = new ActionList();
var d8 = new ActionDescriptor();
d8.putEnumerated(charIDToTypeID("ncTp"), charIDToTypeID("STnc"), charIDToTypeID("NC01"));
list1.putObject(charIDToTypeID("SCnc"), d8);
var d9 = new ActionDescriptor();
d9.putEnumerated(charIDToTypeID("ncTp"), charIDToTypeID("STnc"), charIDToTypeID("NC20"));
list1.putObject(charIDToTypeID("SCnc"), d9);
var d10 = new ActionDescriptor();
d10.putEnumerated(charIDToTypeID("ncTp"), charIDToTypeID("STnc"), charIDToTypeID("NC02"));
list1.putObject(charIDToTypeID("SCnc"), d10);
var d11 = new ActionDescriptor();
d11.putEnumerated(charIDToTypeID("ncTp"), charIDToTypeID("STnc"), charIDToTypeID("NC19"));
list1.putObject(charIDToTypeID("SCnc"), d11);
var d12 = new ActionDescriptor();
d12.putEnumerated(charIDToTypeID("ncTp"), charIDToTypeID("STnc"), charIDToTypeID("NC06"));
list1.putObject(charIDToTypeID("SCnc"), d12);
var d13 = new ActionDescriptor();
d13.putEnumerated(charIDToTypeID("ncTp"), charIDToTypeID("STnc"), charIDToTypeID("NC24"));
list1.putObject(charIDToTypeID("SCnc"), d13);
var d14 = new ActionDescriptor();
d14.putEnumerated(charIDToTypeID("ncTp"), charIDToTypeID("STnc"), charIDToTypeID("NC24"));
list1.putObject(charIDToTypeID("SCnc"), d14);
var d15 = new ActionDescriptor();
d15.putEnumerated(charIDToTypeID("ncTp"), charIDToTypeID("STnc"), charIDToTypeID("NC24"));
list1.putObject(charIDToTypeID("SCnc"), d15);
var d16 = new ActionDescriptor();
d16.putEnumerated(charIDToTypeID("ncTp"), charIDToTypeID("STnc"), charIDToTypeID("NC22"));
list1.putObject(charIDToTypeID("SCnc"), d16);
d1.putList(charIDToTypeID("ovNC"), list1);
d1.putBoolean(charIDToTypeID("ovCM"), false);
d1.putBoolean(charIDToTypeID("ovCW"), true);
d1.putBoolean(charIDToTypeID("ovCU"), true);
d1.putBoolean(charIDToTypeID("ovSF"), true);
d1.putBoolean(charIDToTypeID("ovCB"), true);
d.putObject(stringIDToTypeID("using"), stringIDToTypeID("SaveForWeb"), d1);
executeAction(stringIDToTypeID("export"), d, DialogModes.NO);
}
dialog.show();
}
"""

View File

@@ -0,0 +1,474 @@
dxf23_jscode = """
function 自动连晒(){
var dialog = new Window("dialog");
dialog.text = "S/O样自动连晒";
//dialog.text = "SO自动米样拼贴";
dialog.orientation = "row";
dialog.alignChildren = ["left","top"];
dialog.spacing = 10;
dialog.margins = 16;
// GROUP1
// ======
var group1 = dialog.add("group", undefined, {name: "group1"});
group1.orientation = "column";
group1.alignChildren = ["fill","top"];
group1.spacing = 10;
group1.margins = 0;
// PANEL1
// ======
var panel1 = group1.add("panel", undefined, undefined, {name: "panel1"});
panel1.text = "源图像";
panel1.preferredSize.width = 388;
panel1.preferredSize.height = 205;
panel1.orientation = "column";
panel1.alignChildren = ["left","top"];
panel1.spacing = 10;
panel1.margins = 10;
var statictext1 = panel1.add("statictext", undefined, undefined, {name: "statictext1"});
statictext1.text = "使用:";
// GROUP2
// ======
var group2 = panel1.add("group", undefined, {name: "group2"});
group2.orientation = "row";
group2.alignChildren = ["left","center"];
group2.spacing = 10;
group2.margins = 0;
var button1 = group2.add("button", undefined, undefined, {name: "button1"});
button1.text = "选取";
var edittext1 = group2.add('edittext {properties: {name: "edittext1"}}');
edittext1.text = "[选择图像文件夹]";
edittext1.preferredSize.width = 300;
// PANEL2
// ======
var panel2 = group1.add("panel", undefined, undefined, {name: "panel2"});
panel2.text = "SO小样文档";
panel2.preferredSize.height = 160;
panel2.orientation = "column";
panel2.alignChildren = ["left","top"];
panel2.spacing = 10;
panel2.margins = 10;
var statictext2 = panel2.add("statictext", undefined, undefined, {name: "statictext2"});
statictext2.text = "设定:";
// GROUP3
// ======
var group3 = panel2.add("group", undefined, {name: "group3"});
group3.orientation = "row";
group3.alignChildren = ["left","center"];
group3.spacing = 10;
group3.margins = 0;
var statictext3 = group3.add("statictext", undefined, undefined, {name: "statictext3"});
statictext3.text = "宽度(cm):";
var edittext2 = group3.add('edittext {properties: {name: "edittext2"}}');
edittext2.preferredSize.width = 100;
var statictext4 = group3.add("statictext", undefined, undefined, {name: "statictext4"});
statictext4.text = "高度(cm):";
var edittext3 = group3.add('edittext {properties: {name: "edittext3"}}');
edittext3.preferredSize.width = 100;
// GROUP4
// ======
var group4 = panel2.add("group", undefined, {name: "group4"});
group4.orientation = "row";
group4.alignChildren = ["left","center"];
group4.spacing = 10;
group4.margins = 0;
// GROUP5
// ======
var group5 = group1.add("group", undefined, {name: "group5"});
group5.orientation = "row";
group5.alignChildren = ["left","center"];
group5.spacing = 10;
group5.margins = 0;
// PANEL3
// ======
// GROUP7
// ======
var group7 = dialog.add("group", undefined, {name: "group7"});
group7.orientation = "column";
group7.alignChildren = ["fill","top"];
group7.spacing = 10;
group7.margins = 0;
var ok = group7.add("button", undefined, undefined, {name: "ok"});
ok.text = "确认";
var cancel = group7.add("button", undefined, undefined, {name: "cancel"});
cancel.text = "取消";
button1.onClick = function () {
// 打开文件夹选择对话框
var selectedFolder = Folder.selectDialog("选择图像文件夹");
// 检查用户是否取消了选择
if (selectedFolder) {
// 将选择的文件夹路径显示在输入框中
edittext1.text = selectedFolder.fsName;
}
};
ok.onClick = function () {
app.preferences.rulerUnits = Units.PIXELS;
var 素材图片文件夹 = new Folder(edittext1.text);
var 遍历tiff = 素材图片文件夹.getFiles("*.*");
// 新建文件夹
var 新文件夹 = new Folder(edittext1.text + "/SO小样拼贴");
新文件夹.create();
var 新加字文件夹 = new Folder(edittext1.text + "/SO小样拼贴加字");
新加字文件夹.create();
宽度=Number (edittext2.text);
高度=Number (edittext3.text);
for (var i = 0; i < 遍历tiff.length; i++) {
if (遍历tiff[i] instanceof File) {
app.open(遍历tiff[i]);
app.activeDocument.flatten();
var 当前文档 = app.activeDocument;
var 当前文档名称 = 当前文档.name;
图像大小()
预设图案(当前文档名称);
app.activeDocument.crop([UnitValue("0px"), UnitValue("0px"), UnitValue(宽度, "cm"), UnitValue(高度, "cm")]);
填充图案(当前文档名称);
app.activeDocument.flatten();
画布扩展()
// 保存新的图像文件到新建的文件夹中
var 保存路径 = 新文件夹 + "/" + 当前文档名称;
var tifSaveOpt = new TiffSaveOptions();
tifSaveOpt.imageCompression = TIFFEncoding.TIFFLZW;
tifSaveOpt.byteOrder = ByteOrder.IBM;
var asCopy = true;
app.activeDocument.saveAs(new File(保存路径), tifSaveOpt, asCopy);
创建并处理文本图层();
app.activeDocument.flatten();
var 保存路径 = 新加字文件夹 + "/" + 当前文档名称;
var tifSaveOpt = new TiffSaveOptions();
tifSaveOpt.imageCompression = TIFFEncoding.TIFFLZW;
tifSaveOpt.byteOrder = ByteOrder.IBM;
var asCopy = true;
app.activeDocument.saveAs(new File(保存路径), tifSaveOpt, asCopy);
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
}
}
//alert("小样连晒完成")
dialog.close();
// 创建并保存拼贴图像(新文件夹, 幅宽)
};
function 预设图案(当前文档名称) //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("pattern"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putProperty(stringIDToTypeID("property"), stringIDToTypeID("selection"));
r1.putEnumerated(stringIDToTypeID("document"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("using"), r1);
d.putString(stringIDToTypeID("name"), 当前文档名称);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 填充图案(当前文档名称) //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("contentLayer"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
var d2 = new ActionDescriptor();
var d3 = new ActionDescriptor();
d3.putString(stringIDToTypeID("name"), 当前文档名称);
d2.putObject(stringIDToTypeID("pattern"), stringIDToTypeID("pattern"), d3);
d1.putObject(stringIDToTypeID("type"), stringIDToTypeID("patternLayer"), d2);
d.putObject(stringIDToTypeID("using"), stringIDToTypeID("contentLayer"), d1);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 图像大小() //图像大小
{
var d = new ActionDescriptor();
d.putUnitDouble(stringIDToTypeID("resolution"), stringIDToTypeID("densityUnit"), 200);
d.putBoolean(stringIDToTypeID("scaleStyles"), true);
d.putBoolean(stringIDToTypeID("constrainProportions"), true);
d.putEnumerated(charIDToTypeID("Intr"), stringIDToTypeID("interpolationType"), stringIDToTypeID("nearestNeighbor"));
executeAction(stringIDToTypeID("imageSize"), d, DialogModes.NO);
}
function 画布扩展() //
{
var d = new ActionDescriptor();
d.putBoolean(stringIDToTypeID("relative"), true);
d.putUnitDouble(stringIDToTypeID("width"), stringIDToTypeID("distanceUnit"), 14.4);
d.putUnitDouble(stringIDToTypeID("height"), stringIDToTypeID("distanceUnit"), 14.4);
d.putEnumerated(stringIDToTypeID("horizontal"), stringIDToTypeID("horizontalLocation"), stringIDToTypeID("center"));
d.putEnumerated(stringIDToTypeID("vertical"), stringIDToTypeID("verticalLocation"), stringIDToTypeID("center"));
d.putEnumerated(stringIDToTypeID("canvasExtensionColorType"), stringIDToTypeID("canvasExtensionColorType"), stringIDToTypeID("color"));
var d1 = new ActionDescriptor();
d1.putDouble(stringIDToTypeID("red"), 255);
d1.putDouble(stringIDToTypeID("green"), 255);
d1.putDouble(stringIDToTypeID("blue"), 255);
d.putObject(stringIDToTypeID("canvasExtensionColor"), stringIDToTypeID("RGBColor"), d1);
executeAction(stringIDToTypeID("canvasSize"), d, DialogModes.NO);
}
function 创建并处理文本图层() {
// 新建图层
var textLayer = activeDocument.artLayers.add();
// 将新建图层变成文本图层
textLayer.kind = LayerKind.TEXT;
// 将文本内容改为当前文档name
textLayer.textItem.contents = activeDocument.name;
// 字体大小固定值
var 固定字体大小 = 30; // 例如30像素
textLayer.textItem.size = 固定字体大小;
// 文字字体固定值
textLayer.textItem.font = "微软雅黑";
// 计算并调整文本位置
var x = activeDocument.width - textLayer.bounds[2];
var y = textLayer.bounds[1];
textLayer.translate(x, -y);
// 偏移
textLayer.translate(UnitValue("-1cm"), UnitValue("+0.5cm"));
// 复制并栅格化图层
var copyLayer = textLayer.duplicate();
copyLayer.rasterize(RasterizeType.ENTIRELAYER);
// 设置固定颜色值
var 固定颜色 = new SolidColor();
固定颜色.rgb.red = 255; // 红色分量
固定颜色.rgb.green = 255; // 绿色分量
固定颜色.rgb.blue = 255; // 蓝色分量
activeDocument.activeLayer = copyLayer;
activeDocument.selection.fill(固定颜色, ColorBlendMode.NORMAL, 100, true);
// 白边
activeDocument.activeLayer.applyMinimum(10);
后移一层()
向上选择()
向下合并()
名称更改字体()
缩小字体图层至文档一半()
多选背景()
底对齐()
水平居中对齐()
}
// 调用函数
function 后移一层() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("previous"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("move"), d, DialogModes.NO);
}
function 向上选择() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("forwardEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(19);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 向下合并() //向下合并
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("mergeLayersNew"), d, DialogModes.NO);
}
function 名称更改字体() //名称更改
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
d1.putString(stringIDToTypeID("name"), "字体");
d.putObject(stringIDToTypeID("to"), stringIDToTypeID("layer"), d1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 缩小字体图层至文档一半() {
var 文档 = app.activeDocument;
var 字体图层 = null;
// 遍历文档中的图层以找到名为"字体"的图层
for (var i = 0; i < 文档.artLayers.length; i++) {
if (文档.artLayers[i].name === "字体") {
字体图层 = 文档.artLayers[i];
break;
}
}
if (字体图层 !== null) {
// 获取文档的宽度的一半
var 目标宽度 = 文档.width / 2;
// 获取图层的当前宽度
var 图层宽度 = 字体图层.bounds[2] - 字体图层.bounds[0];
// 计算缩放比例
var 缩放比例 = 目标宽度 / 图层宽度 * 100;
// 缩放图层
字体图层.resize(缩放比例, 缩放比例, AnchorPosition.MIDDLECENTER);
} else {
alert("未找到名为'字体'的图层");
}
}
function 多选背景() //自由变换
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putName(stringIDToTypeID("layer"), "背景");
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("selectionModifier"), stringIDToTypeID("selectionModifierType"), stringIDToTypeID("addToSelection"));
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(1);
list.putInteger(13);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 底对齐() //底对齐
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("alignDistributeSelector"), stringIDToTypeID("ADSBottoms"));
d.putBoolean(stringIDToTypeID("alignToCanvas"), false);
executeAction(stringIDToTypeID("align"), d, DialogModes.NO);
}
function 水平居中对齐() //水平居中对齐
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("alignDistributeSelector"), stringIDToTypeID("ADSCentersH"));
d.putBoolean(stringIDToTypeID("alignToCanvas"), false);
executeAction(stringIDToTypeID("align"), d, DialogModes.NO);
}
// 设置单位为像素
dialog.show();
}
"""

View File

@@ -0,0 +1,370 @@
dxf24_jscode = """
// 设置单位为像素
function 自动米样拼贴(){
var dialog = new Window("dialog");
dialog.text = "SO自动米样拼贴";
dialog.orientation = "row";
dialog.alignChildren = ["left","top"];
dialog.spacing = 10;
dialog.margins = 16;
// GROUP1
// ======
var group1 = dialog.add("group", undefined, {name: "group1"});
group1.orientation = "column";
group1.alignChildren = ["fill","top"];
group1.spacing = 10;
group1.margins = 0;
// PANEL1
// ======
var panel1 = group1.add("panel", undefined, undefined, {name: "panel1"});
panel1.text = "源图像";
panel1.preferredSize.width = 388;
panel1.preferredSize.height = 205;
panel1.orientation = "column";
panel1.alignChildren = ["left","top"];
panel1.spacing = 10;
panel1.margins = 10;
var statictext1 = panel1.add("statictext", undefined, undefined, {name: "statictext1"});
statictext1.text = "使用:";
// GROUP2
// ======
var group2 = panel1.add("group", undefined, {name: "group2"});
group2.orientation = "row";
group2.alignChildren = ["left","center"];
group2.spacing = 10;
group2.margins = 0;
var button1 = group2.add("button", undefined, undefined, {name: "button1"});
button1.text = "选取";
var edittext1 = group2.add('edittext {properties: {name: "edittext1"}}');
edittext1.text = "[选择图像文件夹]";
edittext1.preferredSize.width = 300;
// PANEL2
// ======
var panel2 = group1.add("panel", undefined, undefined, {name: "panel2"});
panel2.text = "SO小样文档";
panel2.preferredSize.height = 160;
panel2.orientation = "column";
panel2.alignChildren = ["left","top"];
panel2.spacing = 10;
panel2.margins = 10;
var statictext2 = panel2.add("statictext", undefined, undefined, {name: "statictext2"});
statictext2.text = "设定:";
// GROUP3
// ======
var group3 = panel2.add("group", undefined, {name: "group3"});
group3.orientation = "row";
group3.alignChildren = ["left","center"];
group3.spacing = 10;
group3.margins = 0;
var statictext3 = group3.add("statictext", undefined, undefined, {name: "statictext3"});
statictext3.text = "幅宽(cm):";
var edittext2 = group3.add('edittext {properties: {name: "edittext2"}}');
edittext2.preferredSize.width = 100;
// GROUP4
// ======
var group4 = panel2.add("group", undefined, {name: "group4"});
group4.orientation = "row";
group4.alignChildren = ["left","center"];
group4.spacing = 10;
group4.margins = 0;
// GROUP5
// ======
var group5 = group1.add("group", undefined, {name: "group5"});
group5.orientation = "row";
group5.alignChildren = ["left","center"];
group5.spacing = 10;
group5.margins = 0;
// PANEL3
// ======
// GROUP7
// ======
var group7 = dialog.add("group", undefined, {name: "group7"});
group7.orientation = "column";
group7.alignChildren = ["fill","top"];
group7.spacing = 10;
group7.margins = 0;
var ok = group7.add("button", undefined, undefined, {name: "ok"});
ok.text = "确认";
var cancel = group7.add("button", undefined, undefined, {name: "cancel"});
cancel.text = "取消";
var dialog = new Window("dialog");
dialog.text = "SO自动米样拼贴";
dialog.orientation = "row";
dialog.alignChildren = ["left","top"];
dialog.spacing = 10;
dialog.margins = 16;
// GROUP1
// ======
var group1 = dialog.add("group", undefined, {name: "group1"});
group1.orientation = "column";
group1.alignChildren = ["fill","top"];
group1.spacing = 10;
group1.margins = 0;
// PANEL1
// ======
var panel1 = group1.add("panel", undefined, undefined, {name: "panel1"});
panel1.text = "源图像";
panel1.preferredSize.width = 388;
panel1.preferredSize.height = 205;
panel1.orientation = "column";
panel1.alignChildren = ["left","top"];
panel1.spacing = 10;
panel1.margins = 10;
var statictext1 = panel1.add("statictext", undefined, undefined, {name: "statictext1"});
statictext1.text = "使用:";
// GROUP2
// ======
var group2 = panel1.add("group", undefined, {name: "group2"});
group2.orientation = "row";
group2.alignChildren = ["left","center"];
group2.spacing = 10;
group2.margins = 0;
var button1 = group2.add("button", undefined, undefined, {name: "button1"});
button1.text = "选取";
var edittext1 = group2.add('edittext {properties: {name: "edittext1"}}');
edittext1.text = "[选择图像文件夹]";
edittext1.preferredSize.width = 300;
// PANEL2
// ======
var panel2 = group1.add("panel", undefined, undefined, {name: "panel2"});
panel2.text = "SO小样文档拼贴";
panel2.preferredSize.height = 160;
panel2.orientation = "column";
panel2.alignChildren = ["left","top"];
panel2.spacing = 10;
panel2.margins = 10;
var statictext2 = panel2.add("statictext", undefined, undefined, {name: "statictext2"});
statictext2.text = "设定:";
// GROUP3
// ======
var group3 = panel2.add("group", undefined, {name: "group3"});
group3.orientation = "row";
group3.alignChildren = ["left","center"];
group3.spacing = 10;
group3.margins = 0;
var statictext3 = group3.add("statictext", undefined, undefined, {name: "statictext3"});
statictext3.text = "幅宽(cm):";
var edittext2 = group3.add('edittext {properties: {name: "edittext2"}}');
edittext2.preferredSize.width = 100;
// GROUP4
// ======
var group4 = panel2.add("group", undefined, {name: "group4"});
group4.orientation = "row";
group4.alignChildren = ["left","center"];
group4.spacing = 10;
group4.margins = 0;
// GROUP5
// ======
var group5 = group1.add("group", undefined, {name: "group5"});
group5.orientation = "row";
group5.alignChildren = ["left","center"];
group5.spacing = 10;
group5.margins = 0;
// PANEL3
// ======
// GROUP7
// ======
var group7 = dialog.add("group", undefined, {name: "group7"});
group7.orientation = "column";
group7.alignChildren = ["fill","top"];
group7.spacing = 10;
group7.margins = 0;
var ok = group7.add("button", undefined, undefined, {name: "ok"});
ok.text = "确认";
var cancel = group7.add("button", undefined, undefined, {name: "cancel"});
cancel.text = "取消";
button1.onClick = function () {
// 打开文件夹选择对话框
var selectedFolder = Folder.selectDialog("选择图像文件夹");
// 检查用户是否取消了选择
if (selectedFolder) {
// 将选择的文件夹路径显示在输入框中
edittext1.text = selectedFolder.fsName;
}
};
ok.onClick = function () {
app.preferences.rulerUnits = Units.PIXELS;
//function 创建拼贴图(导入文件夹路径, 文档宽度厘米) {
// 假设这些值是从某处获取或者用户输入的
var 导入文件夹路径 =new Folder (edittext1.text);
var 文档宽度厘米 = Number(edittext2.text);
var 文档高度厘米 = 300; // 例如50厘米
var 分辨率 = 200; // 分辨率设置为150 DPI
// 转换厘米到像素
var 厘米到像素的转换系数 = 2.54;
var 文档宽度像素 = 文档宽度厘米 / 厘米到像素的转换系数 * 分辨率;
var 文档高度像素 = 文档高度厘米 / 厘米到像素的转换系数 * 分辨率;
var 导入文件夹 = new Folder(导入文件夹路径);
var 文件列表 = 导入文件夹.getFiles("*.*");
var 文件名数组 = new Array();
var 文件宽度数组 = new Array();
var 文件高度数组 = new Array();
// 读取文件列表并获取文件信息
for (var 索引1 = 0; 索引1 < 文件列表.length; 索引1++) {
var 文档 = app.open(文件列表[索引1]);
文件名数组[索引1] = 文档.fullName;
文件宽度数组[索引1] = parseInt(文档.width);
文件高度数组[索引1] = parseInt(文档.height);
文档.close();
}
// 根据高度对文件进行排序
for (var 索引1 = 文件高度数组.length - 1; 索引1 > 0; --索引1) {
for (var 索引2 = 0; 索引2 < 索引1; ++索引2) {
if (文件高度数组[索引2] > 文件高度数组[索引2 + 1])
交换数组元素(索引2, 索引2 + 1);
}
}
function 交换数组元素(索引1, 索引2) {
var 临时 = 文件高度数组[索引1];
文件高度数组[索引1] = 文件高度数组[索引2];
文件高度数组[索引2] = 临时;
var 临时2 = 文件宽度数组[索引1];
文件宽度数组[索引1] = 文件宽度数组[索引2];
文件宽度数组[索引2] = 临时2;
var 临时3 = 文件名数组[索引1];
文件名数组[索引1] = 文件名数组[索引2];
文件名数组[索引2] = 临时3;
}
// 创建新文档并添加图片
var 新文档 = app.documents.add(文档宽度像素, 文档高度像素, 分辨率, "拼贴", NewDocumentMode.CMYK, DocumentFill.WHITE);
var 当前顶部 = 0;
var 当前左侧 = 0;
for (var 索引1 = 0; 索引1 < 文件名数组.length; 索引1++) {
var 文档 = app.open(文件名数组[索引1]);
文档.selection.selectAll();
文档.selection.copy();
文档.close();
if ((当前左侧 + 文件宽度数组[索引1]) > 文档宽度像素) {
当前左侧 = 0;
当前顶部 += 文件高度数组[索引1 - 1];
}
var 边界 = [
[当前左侧, 当前顶部],
[当前左侧 + 文件宽度数组[索引1], 当前顶部],
[当前左侧 + 文件宽度数组[索引1], 当前顶部 + 文件高度数组[索引1]],
[当前左侧, 当前顶部 + 文件高度数组[索引1]]
];
新文档.selection.select(边界, SelectionType.REPLACE, 0, true);
新文档.paste();
当前左侧 += 文件宽度数组[索引1];
}
// 选择输出文件夹并保存图片
var 输出文件夹 = new Folder(edittext1.text + "/拼贴");
if (!输出文件夹.exists) 输出文件夹.create();
// 设置TIFF保存选项
var tiffSaveOptions = new TiffSaveOptions();
tiffSaveOptions.imageCompression = TIFFEncoding.TIFFLZW; // 使用LZW压缩
tiffSaveOptions.byteOrder = ByteOrder.IBM; // 设置字节顺序为IBM大端序
// 定义保存的文件路径和名称
var 文件 = new File(输出文件夹 + "/combined.tif");
// 保存当前文档为TIFF格式
新文档.saveAs(文件, tiffSaveOptions, true, Extension.LOWERCASE);
裁切透明像素();
// 关闭文档,不保存更改
//新文档.close(SaveOptions.DONOTSAVECHANGES);
dialog.close();
alert("拼贴完成")
}
function 裁切透明像素() {
var 文档 = app.activeDocument; // 获取当前活动的文档
// 裁切透明像素
// TrimType.TOPLEFT 表示从图像的左上角开始裁切
// 第二个参数 true 表示裁切顶部的透明像素
// 第三个参数 true 表示裁切左侧的透明像素
// 第四个参数 true 表示裁切底部的透明像素
// 第五个参数 true 表示裁切右侧的透明像素
文档.trim(TrimType.TOPLEFT, true, true, true, true);
}
dialog.show();
}
"""

View File

@@ -0,0 +1,6 @@
dxf25_jscode = """
alert("接口测试")
"""

View File

@@ -0,0 +1,218 @@
dxf26_jscode = """
// 弹出文件夹选择框
function 模特换图(){
建立快照()
var folder = Folder.selectDialog("请选择一个文件夹");
var 模特文档 = app.activeDocument;
if (folder) {
// 在选择的文件夹中创建一个新的子文件夹
var targetFolder = new Folder(folder.fullName + "/模特图生成");
if (!targetFolder.exists) {
targetFolder.create();
}
var files = folder.getFiles();
// 获取“贴图位置”图层组中的所有图层名称
var layerSet = 模特文档.layerSets.getByName("贴图位置");
var layerNames = [];
for (var j = 0; j < layerSet.artLayers.length; j++) {
layerNames.push(layerSet.artLayers[j].name);
}
// 遍历并尝试打开每个文件
for (var i = 0; i < files.length; i++) {
var file = files[i];
if (file instanceof File) {
try {
app.open(file);
app.activeDocument.flatten()
var 当前文档 = app.activeDocument;
var 当前文档名称 = 当前文档.name;
图像大小();
预设图案(当前文档名称);
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
app.activeDocument = 模特文档;
var 水平增量 = 200;
var 垂直增量 = 300;
var 水平基数 = 200;
var 垂直基数 = 300;
// 遍历“贴图位置”图层组中的每个图层
for (var k = 0; k < layerNames.length; k++) {
var layname = layerNames[k];
var 贴图位置 = app.activeDocument.layerSets.getByName("贴图位置").layers.getByName(layname);
app.activeDocument.activeLayer = 贴图位置;
载入选区();
填充图案(当前文档名称);
栅格化图层()
取消链接蒙版()
var 水平 = 水平基数 + 水平增量 * k;
var 垂直 = 垂直基数 + 垂直增量 * k;
位移(水平, 垂直);
}
var saveFile = new File(targetFolder.fullName + "/" + file.name.replace(/\.[^\.]+$/, "") + ".tif");
var tiffSaveOptions = new TiffSaveOptions();
tiffSaveOptions.imageCompression = TIFFEncoding.NONE; // 或者根据需要设置其他压缩选项
tiffSaveOptions.alphaChannels = true;
tiffSaveOptions.layers = true;
app.activeDocument.saveAs(saveFile, tiffSaveOptions, true, Extension.LOWERCASE);
// 关闭当前文档
历史记录回退到快照1()
} catch (e) {
alert("无法打开文件: " );
}
}
}
alert("换图完成")
} else {
alert("没有选择文件夹");
}
// 其他函数保持不变
function generateRandomNumber(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function 图像大小() //图像大小
{
var d = new ActionDescriptor();
d.putUnitDouble(stringIDToTypeID("resolution"), stringIDToTypeID("densityUnit"), 150);
d.putBoolean(stringIDToTypeID("scaleStyles"), true);
d.putBoolean(stringIDToTypeID("constrainProportions"), true);
d.putEnumerated(charIDToTypeID("Intr"), stringIDToTypeID("interpolationType"), stringIDToTypeID("nearestNeighbor"));
executeAction(stringIDToTypeID("imageSize"), d, DialogModes.NO);
}
function 取消链接蒙版() //取消链接蒙版
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
d1.putBoolean(stringIDToTypeID("userMaskLinked"), false);
d.putObject(stringIDToTypeID("to"), stringIDToTypeID("layer"), d1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 栅格化图层() //栅格化图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("rasterizeLayer"), d, DialogModes.NO);
}
function 预设图案(当前文档名称) //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("pattern"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putProperty(stringIDToTypeID("property"), stringIDToTypeID("selection"));
r1.putEnumerated(stringIDToTypeID("document"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("using"), r1);
d.putString(stringIDToTypeID("name"), 当前文档名称);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 载入选区() //载入选区
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("transparencyEnum"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 填充图案(当前文档名称) //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("contentLayer"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
var d2 = new ActionDescriptor();
var d3 = new ActionDescriptor();
d3.putString(stringIDToTypeID("name"), 当前文档名称);
d2.putObject(stringIDToTypeID("pattern"), stringIDToTypeID("pattern"), d3);
d1.putObject(stringIDToTypeID("type"), stringIDToTypeID("patternLayer"), d2);
d.putObject(stringIDToTypeID("using"), stringIDToTypeID("contentLayer"), d1);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 位移(水平,垂直) //位移
{
var d = new ActionDescriptor();
d.putInteger(stringIDToTypeID("horizontal"), 水平);
d.putInteger(stringIDToTypeID("vertical"), 垂直);
d.putEnumerated(stringIDToTypeID("fill"), stringIDToTypeID("fillMode"), stringIDToTypeID("wrap"));
executeAction(stringIDToTypeID("offset"), d, DialogModes.NO);
}
function 历史记录回退到快照1() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putName(stringIDToTypeID("snapshotClass"), "快照 1");
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 建立快照() //打开
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("snapshotClass"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putProperty(stringIDToTypeID("historyState"), stringIDToTypeID("currentHistoryState"));
d.putReference(stringIDToTypeID("from"), r1);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
}
"""

View File

@@ -0,0 +1,500 @@
dxf27_jscode = """
function 新的米样缩放()
{
var dialog = new Window("dialog");
dialog.text = "S/O样自动缩放";
//dialog.text = "SO自动米样拼贴";
dialog.orientation = "row";
dialog.alignChildren = ["left","top"];
dialog.spacing = 10;
dialog.margins = 16;
// GROUP1
// ======
var group1 = dialog.add("group", undefined, {name: "group1"});
group1.orientation = "column";
group1.alignChildren = ["fill","top"];
group1.spacing = 10;
group1.margins = 0;
// PANEL1
// ======
var panel1 = group1.add("panel", undefined, undefined, {name: "panel1"});
panel1.text = "源图像";
panel1.preferredSize.width = 388;
panel1.preferredSize.height = 205;
panel1.orientation = "column";
panel1.alignChildren = ["left","top"];
panel1.spacing = 10;
panel1.margins = 10;
var statictext1 = panel1.add("statictext", undefined, undefined, {name: "statictext1"});
statictext1.text = "使用:";
// GROUP2
// ======
var group2 = panel1.add("group", undefined, {name: "group2"});
group2.orientation = "row";
group2.alignChildren = ["left","center"];
group2.spacing = 10;
group2.margins = 0;
var button1 = group2.add("button", undefined, undefined, {name: "button1"});
button1.text = "选取";
var edittext1 = group2.add('edittext {properties: {name: "edittext1"}}');
edittext1.text = "[选择图像文件夹]";
edittext1.preferredSize.width = 300;
// PANEL2
// ======
var panel2 = group1.add("panel", undefined, undefined, {name: "panel2"});
panel2.text = "SO小样文档";
panel2.preferredSize.height = 160;
panel2.orientation = "column";
panel2.alignChildren = ["left","top"];
panel2.spacing = 10;
panel2.margins = 10;
var statictext2 = panel2.add("statictext", undefined, undefined, {name: "statictext2"});
statictext2.text = "设定:";
// GROUP3
// ======
var group3 = panel2.add("group", undefined, {name: "group3"});
group3.orientation = "row";
group3.alignChildren = ["left","center"];
group3.spacing = 10;
group3.margins = 0;
var statictext3 = group3.add("statictext", undefined, undefined, {name: "statictext3"});
statictext3.text = "宽度(cm):";
var edittext2 = group3.add('edittext {properties: {name: "edittext2"}}');
edittext2.preferredSize.width = 100;
var statictext4 = group3.add("statictext", undefined, undefined, {name: "statictext4"});
statictext4.text = "高度(cm):";
var edittext3 = group3.add('edittext {properties: {name: "edittext3"}}');
edittext3.preferredSize.width = 100;
// GROUP4
// ======
var group4 = panel2.add("group", undefined, {name: "group4"});
group4.orientation = "row";
group4.alignChildren = ["left","center"];
group4.spacing = 10;
group4.margins = 0;
// GROUP5
// ======
var group5 = group1.add("group", undefined, {name: "group5"});
group5.orientation = "row";
group5.alignChildren = ["left","center"];
group5.spacing = 10;
group5.margins = 0;
// PANEL3
// ======
// GROUP7
// ======
var group7 = dialog.add("group", undefined, {name: "group7"});
group7.orientation = "column";
group7.alignChildren = ["fill","top"];
group7.spacing = 10;
group7.margins = 0;
var ok = group7.add("button", undefined, undefined, {name: "ok"});
ok.text = "确认";
var cancel = group7.add("button", undefined, undefined, {name: "cancel"});
cancel.text = "取消";
button1.onClick = function () {
// 打开文件夹选择对话框
var selectedFolder = Folder.selectDialog("选择图像文件夹");
// 检查用户是否取消了选择
if (selectedFolder) {
// 将选择的文件夹路径显示在输入框中
edittext1.text = selectedFolder.fsName;
}
};
ok.onClick = function () {
app.preferences.rulerUnits = Units.PIXELS;
var 素材图片文件夹 = new Folder(edittext1.text);
var 遍历tiff = 素材图片文件夹.getFiles("*.*");
// 新建文件夹
var 新文件夹 = new Folder(edittext1.text + "/SO小样拼贴");
新文件夹.create();
var 新加字文件夹 = new Folder(edittext1.text + "/SO小样拼贴加字");
新加字文件夹.create();
宽度=Number (edittext2.text);
高度=Number (edittext3.text);
for (var i = 0; i < 遍历tiff.length; i++) {
if (遍历tiff[i] instanceof File) {
app.open(遍历tiff[i]);
app.activeDocument.flatten();
var 当前文档 = app.activeDocument;
var 当前文档名称 = 当前文档.name;
resizeImageToCm(宽度, 高度,200);
/*
图像大小()
预设图案(当前文档名称);
app.activeDocument.crop([UnitValue("0px"), UnitValue("0px"), UnitValue(宽度, "cm"), UnitValue(高度, "cm")]);
填充图案(当前文档名称);
app.activeDocument.flatten();
画布扩展()
*/
画布扩展()
// 保存新的图像文件到新建的文件夹中
var 保存路径 = 新文件夹 + "/" + 当前文档名称;
var tifSaveOpt = new TiffSaveOptions();
tifSaveOpt.imageCompression = TIFFEncoding.TIFFLZW;
tifSaveOpt.byteOrder = ByteOrder.IBM;
var asCopy = true;
app.activeDocument.saveAs(new File(保存路径), tifSaveOpt, asCopy);
创建并处理文本图层();
app.activeDocument.flatten();
var 保存路径 = 新加字文件夹 + "/" + 当前文档名称;
var tifSaveOpt = new TiffSaveOptions();
tifSaveOpt.imageCompression = TIFFEncoding.TIFFLZW;
tifSaveOpt.byteOrder = ByteOrder.IBM;
var asCopy = true;
app.activeDocument.saveAs(new File(保存路径), tifSaveOpt, asCopy);
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
}
}
//alert("小样连晒完成")
dialog.close();
// 创建并保存拼贴图像(新文件夹, 幅宽)
};
function resizeImageToCm(widthCm, heightCm, resolution) {
// 将厘米转换为像素
var widthInPixels = widthCm * (resolution / 2.54);
var heightInPixels = heightCm * (resolution / 2.54);
// 调整图像大小
app.activeDocument.resizeImage(widthInPixels, heightInPixels, resolution, ResampleMethod.NEARESTNEIGHBOR);
}
// 使用示例:将图像大小调整为 10cm x 15cm分辨率为 300 像素/英寸
function 预设图案(当前文档名称) //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("pattern"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putProperty(stringIDToTypeID("property"), stringIDToTypeID("selection"));
r1.putEnumerated(stringIDToTypeID("document"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("using"), r1);
d.putString(stringIDToTypeID("name"), 当前文档名称);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 填充图案(当前文档名称) //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("contentLayer"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
var d2 = new ActionDescriptor();
var d3 = new ActionDescriptor();
d3.putString(stringIDToTypeID("name"), 当前文档名称);
d2.putObject(stringIDToTypeID("pattern"), stringIDToTypeID("pattern"), d3);
d1.putObject(stringIDToTypeID("type"), stringIDToTypeID("patternLayer"), d2);
d.putObject(stringIDToTypeID("using"), stringIDToTypeID("contentLayer"), d1);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 图像大小() //图像大小
{
var d = new ActionDescriptor();
d.putUnitDouble(stringIDToTypeID("resolution"), stringIDToTypeID("densityUnit"), 200);
d.putBoolean(stringIDToTypeID("scaleStyles"), true);
d.putBoolean(stringIDToTypeID("constrainProportions"), true);
d.putEnumerated(charIDToTypeID("Intr"), stringIDToTypeID("interpolationType"), stringIDToTypeID("nearestNeighbor"));
executeAction(stringIDToTypeID("imageSize"), d, DialogModes.NO);
}
function 画布扩展() //
{
var d = new ActionDescriptor();
d.putBoolean(stringIDToTypeID("relative"), true);
d.putUnitDouble(stringIDToTypeID("width"), stringIDToTypeID("distanceUnit"), 14.4);
d.putUnitDouble(stringIDToTypeID("height"), stringIDToTypeID("distanceUnit"), 14.4);
d.putEnumerated(stringIDToTypeID("horizontal"), stringIDToTypeID("horizontalLocation"), stringIDToTypeID("center"));
d.putEnumerated(stringIDToTypeID("vertical"), stringIDToTypeID("verticalLocation"), stringIDToTypeID("center"));
d.putEnumerated(stringIDToTypeID("canvasExtensionColorType"), stringIDToTypeID("canvasExtensionColorType"), stringIDToTypeID("color"));
var d1 = new ActionDescriptor();
d1.putDouble(stringIDToTypeID("red"), 255);
d1.putDouble(stringIDToTypeID("green"), 255);
d1.putDouble(stringIDToTypeID("blue"), 255);
d.putObject(stringIDToTypeID("canvasExtensionColor"), stringIDToTypeID("RGBColor"), d1);
executeAction(stringIDToTypeID("canvasSize"), d, DialogModes.NO);
}
function 创建并处理文本图层() {
// 新建图层
var textLayer = activeDocument.artLayers.add();
// 将新建图层变成文本图层
textLayer.kind = LayerKind.TEXT;
// 将文本内容改为当前文档name
textLayer.textItem.contents = activeDocument.name;
// 字体大小固定值
var 固定字体大小 = 30; // 例如30像素
textLayer.textItem.size = 固定字体大小;
// 文字字体固定值
textLayer.textItem.font = "微软雅黑";
// 计算并调整文本位置
var x = activeDocument.width - textLayer.bounds[2];
var y = textLayer.bounds[1];
textLayer.translate(x, -y);
// 偏移
textLayer.translate(UnitValue("-1cm"), UnitValue("+0.5cm"));
// 复制并栅格化图层
var copyLayer = textLayer.duplicate();
copyLayer.rasterize(RasterizeType.ENTIRELAYER);
// 设置固定颜色值
var 固定颜色 = new SolidColor();
固定颜色.rgb.red = 255; // 红色分量
固定颜色.rgb.green = 255; // 绿色分量
固定颜色.rgb.blue = 255; // 蓝色分量
activeDocument.activeLayer = copyLayer;
activeDocument.selection.fill(固定颜色, ColorBlendMode.NORMAL, 100, true);
// 白边
activeDocument.activeLayer.applyMinimum(10);
后移一层()
向上选择()
向下合并()
名称更改字体()
缩小字体图层至文档一半()
多选背景()
底对齐()
水平居中对齐()
}
// 调用函数
function 后移一层() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("previous"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("move"), d, DialogModes.NO);
}
function 向上选择() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("forwardEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(19);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 向下合并() //向下合并
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("mergeLayersNew"), d, DialogModes.NO);
}
function 名称更改字体() //名称更改
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
d1.putString(stringIDToTypeID("name"), "字体");
d.putObject(stringIDToTypeID("to"), stringIDToTypeID("layer"), d1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 缩小字体图层至文档一半() {
var 文档 = app.activeDocument;
var 字体图层 = null;
// 遍历文档中的图层以找到名为"字体"的图层
for (var i = 0; i < 文档.artLayers.length; i++) {
if (文档.artLayers[i].name === "字体") {
字体图层 = 文档.artLayers[i];
break;
}
}
if (字体图层 !== null) {
// 获取文档的宽度的一半
var 目标宽度 = 文档.width / 2;
// 获取图层的当前宽度
var 图层宽度 = 字体图层.bounds[2] - 字体图层.bounds[0];
// 计算缩放比例
var 缩放比例 = 目标宽度 / 图层宽度 * 100;
// 缩放图层
字体图层.resize(缩放比例, 缩放比例, AnchorPosition.MIDDLECENTER);
} else {
alert("未找到名为'字体'的图层");
}
}
function 多选背景() //自由变换
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putName(stringIDToTypeID("layer"), "背景");
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("selectionModifier"), stringIDToTypeID("selectionModifierType"), stringIDToTypeID("addToSelection"));
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(1);
list.putInteger(13);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 底对齐() //底对齐
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("alignDistributeSelector"), stringIDToTypeID("ADSBottoms"));
d.putBoolean(stringIDToTypeID("alignToCanvas"), false);
executeAction(stringIDToTypeID("align"), d, DialogModes.NO);
}
function 水平居中对齐() //水平居中对齐
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("alignDistributeSelector"), stringIDToTypeID("ADSCentersH"));
d.putBoolean(stringIDToTypeID("alignToCanvas"), false);
executeAction(stringIDToTypeID("align"), d, DialogModes.NO);
}
// 设置单位为像素
dialog.show();
}
"""

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,778 @@
dxf6_jscode = """
function 前景色修改() //取消选择
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("color"), stringIDToTypeID("foregroundColor"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
d1.putDouble(stringIDToTypeID("cyan"), 20);
d1.putDouble(stringIDToTypeID("magenta"), 0);
d1.putDouble(stringIDToTypeID("yellowColor"), 0);
d1.putDouble(stringIDToTypeID("black"), 0);
d.putObject(stringIDToTypeID("to"), stringIDToTypeID("CMYKColorClass"), d1);
d.putString(stringIDToTypeID("source"), "photoshopPicker");
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 图像切割2() {
app.activeDocument.suspendHistory("图像切割", "图像切割()");
}
function 图像切割() {
画布大小()
app.preferences.rulerUnits = Units.PIXELS
var currentDocument = app.activeDocument;
var matchCount = 0; // 匹配到的数值计数
var existingPatternSet = false;
var layerNames = []; // 保存匹配到的图层名称的数组
// 遍历图层
for (var j = 0; j < currentDocument.layers.length; j++) {
var layer = currentDocument.layers[j];
var layerName = layer.name;
// 检查图层名称是否以P开头并且后面跟着数字
if (/^P\d+$/.test(layerName)) {
matchCount++;
layerNames.push(layer); // 将匹配到的图层添加到数组中
}
}
// 输出匹配到的数值个数
// $.writeln("匹配到的数值个数:" + matchCount);
// 遍历匹配到的图层名称
for (var i = 0; i < layerNames.length; i++) {
var layerName = layerNames[i].name;
$.writeln("匹配到的图层名称:" + layerName);
var 当前花样图层 = app.activeDocument.layers.getByName(layerName);
app.activeDocument.activeLayer = 当前花样图层;
切换mask()
载入选区()
var 边距 = 获取当前选区四边距();
var 毫米 = 130;
var 每英寸像素数 = app.activeDocument.resolution; // 获取当前文档的分辨率(每英寸像素数)
var 扩展像素 = 毫米转像素(毫米, 每英寸像素数);
var 裁切上边距= 边距.top-扩展像素
var 裁切左边距= 边距.left-扩展像素
var 裁切下边距= 边距.bottom+扩展像素
var 裁切右边距= 边距.right+扩展像素
var selRegion = [
[裁切左边距,裁切上边距],
[裁切右边距,裁切上边距],
[裁切右边距,裁切下边距],
[裁切左边距,裁切下边距]
];
app.activeDocument.selection.select(selRegion, SelectionType.REPLACE);
新建图层()
app.activeDocument.selection.select(selRegion, SelectionType.REPLACE);
var c = new SolidColor();
c.rgb.hexValue = "FFFFFF";
app.activeDocument.selection.fill(c);
后移一层()
app.activeDocument.activeLayer = 当前花样图层;
切换mask()
载入选区()
删除图层蒙版()
创建剪贴蒙版()
向下合并()
添加图层蒙版()
当前图层=app.activeDocument.activeLayer
当前图层.name=layerName
}
}
function 切换mask() //取消选择
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("mask"));
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("makeVisible"), false);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 毫米转像素(毫米, 每英寸像素数) {
var 每英寸毫米数 = 25.4;
var 英寸 = 毫米 / 每英寸毫米数;
return Math.round(英寸 * 每英寸像素数);
}
function 画布大小() //画布大小
{
var d = new ActionDescriptor();
d.putBoolean(stringIDToTypeID("relative"), true);
d.putUnitDouble(stringIDToTypeID("width"), stringIDToTypeID("distanceUnit"), 850.56);
d.putUnitDouble(stringIDToTypeID("height"), stringIDToTypeID("distanceUnit"), 850.56);
d.putEnumerated(stringIDToTypeID("horizontal"), stringIDToTypeID("horizontalLocation"), stringIDToTypeID("center"));
d.putEnumerated(stringIDToTypeID("vertical"), stringIDToTypeID("verticalLocation"), stringIDToTypeID("center"));
executeAction(stringIDToTypeID("canvasSize"), d, DialogModes.NO);
}
function 载入选区() //载入选区
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 获取当前选区四边距() {
var currentDocument = app.activeDocument;
var selectionBounds = currentDocument.selection.bounds;
var top = selectionBounds[1].value;
var left = selectionBounds[0].value;
var bottom = selectionBounds[3].value;
var right = selectionBounds[2].value;
return {
top: top,
left: left,
bottom: bottom,
right: right
};
}
function 新建图层() //新建图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("layer"));
d.putReference(stringIDToTypeID("null"), r);
d.putInteger(stringIDToTypeID("layerID"), 135);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 后移一层() //后移一层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("previous"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("move"), d, DialogModes.NO);
}
function 删除图层蒙版() //删除图层蒙版
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("mask"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("delete"), d, DialogModes.NO);
}
function 载入选区() //载入选区
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 删除图层蒙版() //删除图层蒙版
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("delete"), d, DialogModes.NO);
}
function 向下合并() //向下合并
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("mergeLayersNew"), d, DialogModes.NO);
}
function 添加图层蒙版() //添加图层蒙版
{
var d = new ActionDescriptor();
d.putClass(stringIDToTypeID("new"), stringIDToTypeID("channel"));
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("mask"));
d.putReference(stringIDToTypeID("at"), r);
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("userMaskEnabled"), stringIDToTypeID("revealSelection"));
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 创建剪贴蒙版() //创建剪贴蒙版
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("groupEvent"), d, DialogModes.NO);
}
function 右下对齐2() {
app.activeDocument.suspendHistory("右下对齐", "右下对齐()");
}
function 右下对齐() {
var groupName = 获取当前图层组名称();
if (groupName !== null) {
app.preferences.rulerUnits = Units.PIXELS;
var splitGroupName = groupName.split("-");
var firstPart = splitGroupName[0];
} else {
// 如果未获取到当前图层组名称,退出程序
// alert("未获取到当前图层组名称!");
return;
}
try {
当前花样图层 = app.activeDocument.layers.getByName(firstPart);
} catch (e) {
// 处理异常情况
alert("没有找到对应的花样裁片: ");
return; // 中断函数执行
}
app.activeDocument.activeLayer = 当前花样图层;
切换mask();
载入选区蒙版()
// 应用图层蒙版();
// 载入选区();
var 边距 = 获取当前选区四边距();
var 获取左右的中心坐标 = (边距.right - 边距.left) / 2 + 边距.left;
var 获取右上的坐标 = 边距.right
$.writeln("中心坐标=" + 获取左右的中心坐标);
var currentDocument = app.activeDocument;
var height = currentDocument.height.value;
var 上边距新 = 0;
var 左边距新 = 获取右上的坐标-1 ;
var 下边距新 = height;
var 右边距新 = 获取右上的坐标 + 1;
var 边距 = 获取当前选区四边距();
$.writeln("上边距:" + 边距.top);
$.writeln("左边距:" + 边距.left);
$.writeln("下边距:" + 边距.bottom);
$.writeln("右边距:" + 边距.right);
//历史记录回退领口函数();
新建选区(上边距新, 左边距新, 下边距新, 右边距新);
app.activeDocument.activeLayer = 当前花样图层;
切换mask();
选区减去();
获取右上的坐标 = 获取当前选区四边距();
获取右上的坐标y坐标信息 = 获取右上的坐标.bottom; //////////////////////以上的是获取花样的的中心坐标信息
获取右上的坐标x坐标信息 = 获取右上的坐标.left;
// $.writeln("居中领口y坐标信息" + 获取到花样图层当前居中领口y坐标信息);
//$.writeln("居中领口x坐标信息" + 获取到花样图层当前居中领口x坐标信息);
//////////////////////以上的是获取花样的的中心坐标信息
var currentDocument = app.activeDocument;
var targetLayerSet = currentDocument.layerSets.getByName(groupName);
if (targetLayerSet) {
var layers = targetLayerSet.layers;
if (layers.length > 0) {
for (var i = 0; i < layers.length; i++) {
var layer = layers[i];
var 裁片图层 = layer.name;
$.writeln("裁片图层坐标信息=" + 裁片图层);
try {
var 当前裁片图层 = app.activeDocument.layerSets.getByName(groupName).layers.getByName(裁片图层);
} catch (e) {
// 处理异常情况
alert("没有找到对应的花样裁片 " );
return; // 中断函数执行
}
app.activeDocument.activeLayer = 当前裁片图层;
载入选区11()
//var selectedLayer = app.activeDocument.activeLayer;
// var bounds = selectedLayer.bounds;
/*
图层上边距新 = 0;
图层左边距新 = 右right ;
图层下边距新 = height;
图层右边距新 = 右right +1;
左left = bounds[0].value;
上top = bounds[1].value;
右right = bounds[2].value;
下bottom = bounds[3].value;
中心坐标centerX = (bounds[2].value - bounds[0].value) / 2 + bounds[0].value;
中心坐标centerY = (bounds[3].value - bounds[1].value) / 2 + bounds[1].value;
*/
// $.writeln("中心坐标centerX" + 中心坐标centerX);
// $.writeln("中心坐标centerY" +中心坐标cent*erY);
获取花样右上的坐标裁片位置坐标 = 获取当前选区四边距();
图层上边距新 = 0;
图层左边距新 = 获取花样右上的坐标裁片位置坐标.right-1 ;
图层下边距新 = height;
图层右边距新 = 获取花样右上的坐标裁片位置坐标.right +1;
新建选区(图层上边距新, 图层左边距新, 图层下边距新, 图层右边距新);
app.activeDocument.activeLayer = 当前裁片图层;
载入选区交叉图层()
获取右上的坐标裁片位置坐标 = 获取当前选区四边距();
获取右上的坐标裁片位置坐标x=获取右上的坐标裁片位置坐标.left
获取右上的坐标裁片位置坐标y=获取右上的坐标裁片位置坐标.bottom
位移距离PXy = 获取右上的坐标y坐标信息 - 获取右上的坐标裁片位置坐标y;
位移距离PXx = 获取右上的坐标x坐标信息 - 获取右上的坐标裁片位置坐标x;
$.writeln(位移距离PXy);
$.writeln(位移距离PXx);
取消选择()
自由变换2(位移距离PXy,位移距离PXx)
//alert("移动")
}
}
}
取消选择();
}
function 获取当前图层组名称() {
var currentDocument = app.activeDocument;
var currentLayer = currentDocument.activeLayer;
if (currentLayer.typename === "LayerSet") {
var groupName = currentLayer.name;
return groupName;
} else {
alert("当前图层不是图层组。");
return null;
}
}
function 载入选区交叉图层() //载入选区
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("transparencyEnum"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("with"), r1);
executeAction(charIDToTypeID("Intr"), d, DialogModes.NO);
}
function 载入选区蒙版() //载入选区
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 遍历图层组内的图层(图层组名称) {
var currentDocument = app.activeDocument;
var targetLayerSet = currentDocument.layerSets.getByName(图层组名称);
if (targetLayerSet) {
var layers = targetLayerSet.layers;
if (layers.length > 0) {
for (var i = 0; i < layers.length; i++) {
var layer = layers[i];
// 在这里对每个图层进行进一步的操作
$.writeln("图层名称:" + layer.name);
}
} else {
$.writeln("图层组中没有任何图层。");
}
} else {
$.writeln("找不到指定名称的图层组。");
}
}
function 新建选区(上边距, 左边距, 下边距, 右边距) {
var currentDocument = app.activeDocument;
var top = 上边距;
var left = 左边距;
var bottom = 下边距;
var right = 右边距;
var selectionRegion = Array(Array(left, top), Array(right, top), Array(right, bottom), Array(left, bottom));
currentDocument.selection.select(selectionRegion);
}
function 选区减去() //载入选区
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("with"), r1);
executeAction(charIDToTypeID("Intr"), d, DialogModes.NO);
}
// 将像素转换为毫米
function pixelsToMillimeters(pixels) {
// 获取当前文档
var doc = app.activeDocument;
// 获取图像的分辨率(像素/英寸)
var resolution = doc.resolution;
// 计算像素转换为毫米
var inches = pixels / resolution;
var millimeters = inches * 25.4;
return millimeters.toFixed(2); // 保留两位小数
}
function 切换mask() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("mask"));
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("makeVisible"), false);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 获取当前选区四边距() {
var currentDocument = app.activeDocument;
var selectionBounds = currentDocument.selection.bounds;
var top = selectionBounds[1].value;
var left = selectionBounds[0].value;
var bottom = selectionBounds[3].value;
var right = selectionBounds[2].value;
return {
top: top,
left: left,
bottom: bottom,
right: right
};
}
function 获取当前文档四边距() {
var currentDocument = app.activeDocument;
var documentBounds = currentDocument.bounds;
var top = documentBounds[1].value;
var left = documentBounds[0].value;
var bottom = documentBounds[3].value;
var right = documentBounds[2].value;
return {
top: top,
left: left,
bottom: bottom,
right: right
};
}
function 选区减去2() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("transparencyEnum"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("with"), r1);
executeAction(charIDToTypeID("Intr"), d, DialogModes.NO);
}
function 遍历图层组内的图层(图层组名称) {
var currentDocument = app.activeDocument;
var targetLayerSet = currentDocument.layerSets.getByName(图层组名称);
if (targetLayerSet) {
var layers = targetLayerSet.layers;
if (layers.length > 0) {
for (var i = 0; i < layers.length; i++) {
var layer = layers[i];
// 在这里对每个图层进行进一步的操作
$.writeln("图层名称:" + layer.name);
}
} else {
$.writeln("图层组中没有任何图层。");
}
} else {
$.writeln("找不到指定名称的图层组。");
}
}
function 自由变换2(位移距离PXy,位移距离PXx) //自由变换
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("freeTransformCenterState"), stringIDToTypeID("quadCenterState"), stringIDToTypeID("QCSAverage"));
var d1 = new ActionDescriptor();
d1.putUnitDouble(stringIDToTypeID("horizontal"), stringIDToTypeID("pixelsUnit"), 位移距离PXx);
d1.putUnitDouble(stringIDToTypeID("vertical"), stringIDToTypeID("pixelsUnit"), 位移距离PXy);
d.putObject(stringIDToTypeID("offset"), stringIDToTypeID("offset"), d1);
d.putBoolean(stringIDToTypeID("linked"), true);
d.putEnumerated(charIDToTypeID("Intr"), stringIDToTypeID("interpolationType"), stringIDToTypeID("nearestNeighbor"));
executeAction(stringIDToTypeID("transform"), d, DialogModes.NO);
}
function 历史记录回退领口函数() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putOffset(stringIDToTypeID("historyState"), -2 );
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 取消选择() //取消选择
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("to"), stringIDToTypeID("ordinal"), stringIDToTypeID("none"));
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 历史记录回退1领口函数() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putOffset(stringIDToTypeID("historyState"), -1 );
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 载入选区11() //载入选区
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("transparencyEnum"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 取消选择() //取消选择
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("to"), stringIDToTypeID("ordinal"), stringIDToTypeID("none"));
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
"""

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,983 @@
dxf9_jscode = """
function 裁片射出比例缩放按中心点() {
app.preferences.rulerUnits = Units.PIXELS
var 主文档 = app.activeDocument;
var 主文档名称 = 主文档.name;
// 遍历当前打开的文档
for (var i = 0; i < app.documents.length; i++) {
var document = app.documents[i];
var documentName = document.name;
// 判断文档名称是否与主文档名称不相同
if (documentName !== 主文档名称) {
app.activeDocument = document;
遍历图层();
}
}
function 遍历图层() {
var layerNames = []; // 用于存储图层名称的数组
var currentDocument = app.activeDocument;
for (var j = 0; j < currentDocument.layers.length; j++) {
var layer = currentDocument.layers[j];
var layerName = layer.name;
layerNames.push(layerName);
}
// 逐个处理图层
for (var k = 0; k < layerNames.length; k++) {
var 当前图层名称 = layerNames[k];
// $.writeln("图层名称:" + 当前图层名称);
// alert(当前图层名称);
var parts = 当前图层名称.split("-");
if (parts.length > 0) {
var 裁片名称 = parts[0];
app.activeDocument = 主文档;
$.writeln(裁片名称);
初始化模板裁片名称 = 当前图层名称.split("-");
初始化码数裁片名称 = 当前图层名称.split("_");
大货组名称 =初始化模板裁片名称[0]+("-大货裁片")
实际裁片名称 = 初始化模板裁片名称[0]+"-"+初始化码数裁片名称[2]
$.writeln(大货组名称);
$.writeln(实际裁片名称);
var 空白裁片模板 = app.activeDocument.layerSets.getByName(大货组名称).layers.getByName(实际裁片名称 );
app.activeDocument.activeLayer = 空白裁片模板;
载入选区()
var 边距 = 获取当前选区四边距();
var 当前选区高度=边距.bottom-边距.top
var 当前选区宽度=边距.right-边距.left
var 高度转毫米 = pixelsToMillimeters(当前选区高度);
var 宽度转毫米 = pixelsToMillimeters(当前选区宽度);
var 搜索词 = 裁片名称;
var 匹配图层数组 = 匹配图层名(搜索词);
// 显示匹配的图层列表
if (匹配图层数组.length > 0) {
var 图层列表文本 = "匹配的图层列表:";
for (var i = 0; i < 匹配图层数组.length; i++) {
if (i !== 0) {
图层列表文本 += " ";
}
图层列表文本 += 匹配图层数组[i].name;
}
var 数据解析分割=图层列表文本.split("_");
//var 实际套花名称=名称部分[0]
var 基码图层宽度 = parseFloat(数据解析分割[1]);
var 基码图层高度 = parseFloat(数据解析分割[2]);
var 缩放比例高度=高度转毫米/基码图层高度*100
var 缩放比例宽度=宽度转毫米/基码图层宽度*100
// alert(基码图层宽度);
} else {
alert("没有找到匹配的图层。");
}
/*
$.writeln("上边距:" + 边距.top);
$.writeln("左边距:" + 边距.left);
$.writeln("下边距:" + 边距.bottom);
$.writeln("右边距:" + 边距.right);
*/8
// 示例用法:
var 毫米 = 300;
var 每英寸像素数 = app.activeDocument.resolution; // 获取当前文档的分辨率(每英寸像素数)
var 扩展像素 = 毫米转像素(毫米, 每英寸像素数);
var 裁切上边距= 边距.top-扩展像素
var 裁切左边距= 边距.left-扩展像素
var 裁切下边距= 边距.bottom+扩展像素
var 裁切右边距= 边距.right+扩展像素
$.writeln(裁切上边距);
$.writeln(裁切左边距);
$.writeln(裁切下边距);
$.writeln(裁切右边距);
裁切图层(裁切上边距,裁切左边距,裁切下边距,裁切右边距)
var 空白裁片模板 = app.activeDocument.layerSets.getByName(大货组名称).layers.getByName(实际裁片名称 );
app.activeDocument.activeLayer = 空白裁片模板;
载入选区()
var 缩放定位点的中心坐标=获取当前缩放定位点选区四边距()
var 缩放定位点的Y轴坐标=缩放定位点的中心坐标.top2+(缩放定位点的中心坐标.bottom2-缩放定位点的中心坐标.top2)/2
var 缩放定位点的X轴坐标=缩放定位点的中心坐标.left2+(缩放定位点的中心坐标.right2-缩放定位点的中心坐标.left2)/2
$.writeln("Y轴中心坐标"+缩放定位点的Y轴坐标);
$.writeln("X轴中心坐标"+缩放定位点的X轴坐标);
var 裁片 = app.activeDocument.layers.getByName(裁片名称);
app.activeDocument.activeLayer = 裁片
//var 空白裁片模板 = app.activeDocument.layerSets.getByName(大货组名称).layers.getByName(实际裁片名称 );
//app.activeDocument.activeLayer = 空白裁片模板;
取消选择()
图层按照缩放定位点进行宽高缩放(缩放定位点的X轴坐标,缩放定位点的Y轴坐标, 缩放比例高度)
// var 裁片 = app.activeDocument.layers.getByName(裁片名称);
// app.activeDocument.activeLayer = 裁片;
var 空白裁片模板 = app.activeDocument.layerSets.getByName(大货组名称).layers.getByName(实际裁片名称 );
app.activeDocument.activeLayer = 空白裁片模板;
载入选区()
var 裁片 = app.activeDocument.layers.getByName(裁片名称);
app.activeDocument.activeLayer = 裁片
添加图层蒙版()
应用图层蒙版()
裁片.copy();
历史记录回退()
app.activeDocument = currentDocument;
图层选择(当前图层名称);
载入选区();
粘贴图层();
取消选择();
// app.refresh();
var 裁片名称 = 当前图层名称.split("_");
if (裁片名称.length > 1) {
var 角度信息 = 裁片名称[1];
if (角度信息 === "180" || 角度信息 === "-180") {
自由变换();
} else if (角度信息 === "-90") {
逆时针90旋转()
} else if (角度信息 === "90") {
顺时针90旋转()
} else {
// 如果以上条件都不满足,则执行默认的代码
}
//历史记录回退缩放函数()
}
app.activeDocument = 主文档;
历史记录回退缩放函数()
}
}
app.activeDocument = currentDocument;
烧花线添加()//alert("当前码拍好")///////////////////////////////////这里可以填写添加烧花线函数
}
//alert("排版完成,请检查文件!!!")
app.activeDocument = 主文档;
}
// 将像素转换为毫米
function pixelsToMillimeters(pixels) {
// 获取当前文档
var doc = app.activeDocument;
// 获取图像的分辨率(像素/英寸)
var resolution = doc.resolution;
// 计算像素转换为毫米
var inches = pixels / resolution;
var millimeters = inches * 25.4;
return millimeters.toFixed(2); // 保留两位小数
}
function 顺时针90旋转() //自由变换
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("freeTransformCenterState"), stringIDToTypeID("quadCenterState"), stringIDToTypeID("QCSAverage"));
var d1 = new ActionDescriptor();
d1.putUnitDouble(stringIDToTypeID("horizontal"), stringIDToTypeID("pixelsUnit"), 0);
d1.putUnitDouble(stringIDToTypeID("vertical"), stringIDToTypeID("pixelsUnit"), 0);
d.putObject(stringIDToTypeID("offset"), stringIDToTypeID("offset"), d1);
d.putUnitDouble(stringIDToTypeID("angle"), stringIDToTypeID("angleUnit"), 90);
d.putBoolean(stringIDToTypeID("linked"), true);
d.putEnumerated(charIDToTypeID("Intr"), stringIDToTypeID("interpolationType"), stringIDToTypeID("nearestNeighbor"));
executeAction(stringIDToTypeID("transform"), d, DialogModes.NO);
}
function 逆时针90旋转() //自由变换
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("freeTransformCenterState"), stringIDToTypeID("quadCenterState"), stringIDToTypeID("QCSAverage"));
var d1 = new ActionDescriptor();
d1.putUnitDouble(stringIDToTypeID("horizontal"), stringIDToTypeID("pixelsUnit"), 0);
d1.putUnitDouble(stringIDToTypeID("vertical"), stringIDToTypeID("pixelsUnit"), 0);
d.putObject(stringIDToTypeID("offset"), stringIDToTypeID("offset"), d1);
d.putUnitDouble(stringIDToTypeID("angle"), stringIDToTypeID("angleUnit"), -90);
d.putBoolean(stringIDToTypeID("linked"), true);
d.putEnumerated(charIDToTypeID("Intr"), stringIDToTypeID("interpolationType"), stringIDToTypeID("nearestNeighbor"));
executeAction(stringIDToTypeID("transform"), d, DialogModes.NO);
}
function 匹配图层名(搜索词) {
// 获取指定图层组中的所有图层
function 获取组中所有图层(组) {
var 图层数组 = [];
var 图层组中图层 = 组.layers;
for (var i = 0; i < 图层组中图层.length; i++) {
var 图层 = 图层组中图层[i];
图层数组.push(图层);
if (图层.typename === "LayerSet") {
var 子图层 = 获取组中所有图层(图层);
图层数组 = 图层数组.concat(子图层);
}
}
return 图层数组;
}
// 获取指定名称的图层组
function 根据名称获取图层组(文档, 组名称) {
var 组 = null;
var 所有图层 = 文档.layers;
for (var i = 0; i < 所有图层.length; i++) {
var 图层 = 所有图层[i];
if (图层.typename === "LayerSet" && 图层.name === 组名称) {
组 = 图层;
break;
}
}
return 组;
}
var 文档 = app.activeDocument;
var 组名称 = "图层基础信息"; // 指定要匹配的图层组名称
var 组 = 根据名称获取图层组(文档, 组名称);
if (组) {
var 图层数组 = 获取组中所有图层(组);
var 模糊匹配图层数组 = [];
// 首先进行模糊匹配
for (var i = 0; i < 图层数组.length; i++) {
var 图层 = 图层数组[i];
if (图层.name.indexOf(搜索词) !== -1) {
模糊匹配图层数组.push(图层);
}
}
// 在模糊匹配结果中进行图层基础信息数组分割过滤
var 精确匹配图层数组 = [];
for (var j = 0; j < 模糊匹配图层数组.length; j++) {
var 模糊匹配图层 = 模糊匹配图层数组[j];
// 进行图层基础信息数组分割过滤
var 图层基础信息数组 = 模糊匹配图层.name.split("_"); // 假设分割符是 "_"
if (图层基础信息数组[0] === 搜索词) {
精确匹配图层数组.push(模糊匹配图层);
}
}
// 返回匹配的图层数组
return 精确匹配图层数组;
} else {
alert('未找到名为"' + 组名称 + '"的图层组。');
return [];
}
}
function 毫米转像素(毫米, 每英寸像素数) {
var 每英寸毫米数 = 25.4;
var 英寸 = 毫米 / 每英寸毫米数;
return Math.round(英寸 * 每英寸像素数);
}
function 图层按照缩放定位点进行宽高缩放(缩放定位点的X轴坐标,缩放定位点的Y轴坐标,缩放比例高度) //自由变换
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("freeTransformCenterState"), stringIDToTypeID("quadCenterState"), stringIDToTypeID("QCSIndependent"));
var d1 = new ActionDescriptor();
d1.putUnitDouble(stringIDToTypeID("horizontal"), stringIDToTypeID("pixelsUnit"), 缩放定位点的X轴坐标);
d1.putUnitDouble(stringIDToTypeID("vertical"), stringIDToTypeID("pixelsUnit"), 缩放定位点的Y轴坐标);
d.putObject(stringIDToTypeID("position"), stringIDToTypeID("point"), d1);
var d2 = new ActionDescriptor();
d2.putUnitDouble(stringIDToTypeID("horizontal"), stringIDToTypeID("pixelsUnit"), 0);
d2.putUnitDouble(stringIDToTypeID("horizontal"), stringIDToTypeID("pixelsUnit"), 0);
d2.putUnitDouble(stringIDToTypeID("vertical"), stringIDToTypeID("pixelsUnit"), 0);
d.putObject(stringIDToTypeID("offset"), stringIDToTypeID("offset"), d2);
d.putUnitDouble(stringIDToTypeID("width"), stringIDToTypeID("percentUnit"), 缩放比例高度);
d.putUnitDouble(stringIDToTypeID("height"), stringIDToTypeID("percentUnit"), 缩放比例高度);
d.putEnumerated(charIDToTypeID("Intr"), stringIDToTypeID("interpolationType"), stringIDToTypeID("nearestNeighbor"));
executeAction(stringIDToTypeID("transform"), d, DialogModes.NO);
}
function 裁切图层(裁切上边距,裁切左边距,裁切下边距,裁切右边距) //
{
var d = new ActionDescriptor();
var d1 = new ActionDescriptor();
d1.putUnitDouble(stringIDToTypeID("top"), stringIDToTypeID("pixelsUnit"), 裁切上边距);
d1.putUnitDouble(stringIDToTypeID("left"), stringIDToTypeID("pixelsUnit"), 裁切左边距);
d1.putUnitDouble(stringIDToTypeID("bottom"), stringIDToTypeID("pixelsUnit"),裁切下边距);
d1.putUnitDouble(stringIDToTypeID("right"), stringIDToTypeID("pixelsUnit"), 裁切右边距);
d.putObject(stringIDToTypeID("to"), stringIDToTypeID("rectangle"), d1);
d.putUnitDouble(stringIDToTypeID("angle"), stringIDToTypeID("angleUnit"), 0);
d.putBoolean(stringIDToTypeID("delete"), true);
d.putEnumerated(stringIDToTypeID("cropAspectRatioModeKey"), stringIDToTypeID("cropAspectRatioModeClass"), stringIDToTypeID("pureAspectRatio"));
d.putBoolean(stringIDToTypeID("constrainProportions"), false);
executeAction(stringIDToTypeID("crop"), d, DialogModes.NO);
}
function 获取当前缩放定位点选区四边距() {
var currentDocument = app.activeDocument;
var selectionBounds = currentDocument.selection.bounds;
var top2 = selectionBounds[1].value;
var left2 = selectionBounds[0].value;
var bottom2 = selectionBounds[3].value;
var right2 = selectionBounds[2].value;
return {
top2: top2,
left2: left2,
bottom2: bottom2,
right2: right2
};
}
function 获取当前选区四边距() {
var currentDocument = app.activeDocument;
var selectionBounds = currentDocument.selection.bounds;
var top = selectionBounds[1].value;
var left = selectionBounds[0].value;
var bottom = selectionBounds[3].value;
var right = selectionBounds[2].value;
return {
top: top,
left: left,
bottom: bottom,
right: right
};
}
function 历史记录回退缩放函数() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putOffset(stringIDToTypeID("historyState"), -5 );
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 粘贴图层() //粘贴图层
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("paste"), d, DialogModes.NO);
}
function 复制图层() //复制图层
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("copyEvent"), d, DialogModes.NO);
}
function 载入选区() //载入选区
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("transparencyEnum"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 取消选择() //取消选择
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("to"), stringIDToTypeID("ordinal"), stringIDToTypeID("none"));
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 图层选择(当前图层名称) //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putName(stringIDToTypeID("layer"), 当前图层名称);
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(6);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 自由变换() //自由变换
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("freeTransformCenterState"), stringIDToTypeID("quadCenterState"), stringIDToTypeID("QCSAverage"));
var d1 = new ActionDescriptor();
d1.putUnitDouble(stringIDToTypeID("horizontal"), stringIDToTypeID("distanceUnit"), 0);
d1.putUnitDouble(stringIDToTypeID("vertical"), stringIDToTypeID("distanceUnit"), 0);
d.putObject(stringIDToTypeID("offset"), stringIDToTypeID("offset"), d1);
d.putUnitDouble(stringIDToTypeID("width"), stringIDToTypeID("percentUnit"), -100);
d.putUnitDouble(stringIDToTypeID("height"), stringIDToTypeID("percentUnit"), -100);
d.putBoolean(stringIDToTypeID("linked"), true);
d.putEnumerated(charIDToTypeID("Intr"), stringIDToTypeID("interpolationType"), stringIDToTypeID("nearestNeighbor"));
executeAction(stringIDToTypeID("transform"), d, DialogModes.NO);
}
function 选择上一图层() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("forwardEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(8);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 添加图层蒙版() //添加图层蒙版
{
var d = new ActionDescriptor();
d.putClass(stringIDToTypeID("new"), stringIDToTypeID("channel"));
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("mask"));
d.putReference(stringIDToTypeID("at"), r);
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("userMaskEnabled"), stringIDToTypeID("revealSelection"));
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 应用图层蒙版() //应用图层蒙版
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("apply"), true);
executeAction(stringIDToTypeID("delete"), d, DialogModes.NO);
}
function 拼合所有蒙版() //拼合所有蒙版
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("document"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("e805a6ee-6d75-4b62-b6fe-f5873b5fdf20"), d, DialogModes.NO);
}
function 选择蒙版() //选择蒙版
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("mask"));
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("makeVisible"), false);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 历史记录回退() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putOffset(stringIDToTypeID("historyState"), -5);
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 烧花线添加() {
app.activeDocument.suspendHistory("烧花线添加", "烧花线()");
function 烧花线() {
// 遍历当前文档图层
var doc = app.activeDocument;
var layers = doc.layers;
var filteredLayers = [];
// 遍历图层筛选以P开头的图层
for (var i = 0; i < layers.length; i++) {
var layer = layers[i];
if (layer.name.charAt(0) === 'P') {
filteredLayers.push(layer);
}
}
空置图层()
// 输出图层名称
for (var j = 0; j < filteredLayers.length; j++) {
var filteredLayer = filteredLayers[j];
var 裁片底图名称=filteredLayer.name;
多选图层(裁片底图名称);
// alert(filteredLayer.name);
}
合并图层();
置为顶层();
画布大小();
var layer = app.activeDocument.activeLayer;
layer.name = "底图";
恢复默认颜色()
矩形选框像素点()
//色彩范围()
填充();
魔棒烧花线()
新建图层()
var layer2 = app.activeDocument.activeLayer;
layer2.name = "剪口";
扩展2();
恢复止口线默认颜色()
填充();
矩形选框准备删除()
清除();
魔棒();
扩展();
选择反向();
清除();
var 底图 = app.activeDocument.layers.getByName( "底图");
app.activeDocument.activeLayer=底图;
矩形选框准备删除()
清除();
置为底层()
图层样式()
取消选择()
function 多选图层(裁片底图名称) //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putName(stringIDToTypeID("layer"), 裁片底图名称);
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("selectionModifier"), stringIDToTypeID("selectionModifierType"), stringIDToTypeID("addToSelection"));
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(4);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 空置图层() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("selectNoLayers"), d, DialogModes.NO);
}
function 恢复止口线默认颜色() //取消选择
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("color"), stringIDToTypeID("foregroundColor"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
d1.putDouble(stringIDToTypeID("cyan"), 20);
d1.putDouble(stringIDToTypeID("magenta"), 0);
d1.putDouble(stringIDToTypeID("yellowColor"), 0);
d1.putDouble(stringIDToTypeID("black"), 0);
d.putObject(stringIDToTypeID("to"), stringIDToTypeID("CMYKColorClass"), d1);
d.putString(stringIDToTypeID("source"), "photoshopPicker");
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 合并图层() //合并图层
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("mergeLayersNew"), d, DialogModes.NO);
}
function 恢复默认颜色() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("color"), stringIDToTypeID("colors"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("reset"), d, DialogModes.NO);
}
function 魔棒烧花线() //魔棒
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
d1.putUnitDouble(stringIDToTypeID("horizontal"), stringIDToTypeID("distanceUnit"), 0);
d1.putUnitDouble(stringIDToTypeID("vertical"), stringIDToTypeID("distanceUnit"), 0);
d.putObject(stringIDToTypeID("to"), stringIDToTypeID("point"), d1);
d.putInteger(stringIDToTypeID("tolerance"), 6);
d.putBoolean(stringIDToTypeID("contiguous"), false);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 矩形选框像素点() //矩形选框
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
d1.putUnitDouble(stringIDToTypeID("top"), stringIDToTypeID("distanceUnit"), 0);
d1.putUnitDouble(stringIDToTypeID("left"), stringIDToTypeID("distanceUnit"), 0);
d1.putUnitDouble(stringIDToTypeID("bottom"), stringIDToTypeID("distanceUnit"), 0.48);
d1.putUnitDouble(stringIDToTypeID("right"), stringIDToTypeID("distanceUnit"), 0.48);
d.putObject(stringIDToTypeID("to"), stringIDToTypeID("rectangle"), d1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 置为底层() //置为底层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("back"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("move"), d, DialogModes.NO);
}
function 置为顶层() //置为顶层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("front"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("move"), d, DialogModes.NO);
}
function 色彩范围() //色彩范围
{
var d = new ActionDescriptor();
d.putInteger(stringIDToTypeID("fuzziness"), 40);
var d1 = new ActionDescriptor();
d1.putDouble(stringIDToTypeID("luminance"), 0);
d1.putDouble(stringIDToTypeID("a"), 0);
d1.putDouble(stringIDToTypeID("b"), 0);
d.putObject(stringIDToTypeID("minimum"), stringIDToTypeID("labColor"), d1);
var d2 = new ActionDescriptor();
d2.putDouble(stringIDToTypeID("luminance"), 0);
d2.putDouble(stringIDToTypeID("a"), 0);
d2.putDouble(stringIDToTypeID("b"), 0);
d.putObject(stringIDToTypeID("maximum"), stringIDToTypeID("labColor"), d2);
d.putInteger(stringIDToTypeID("colorModel"), 0);
executeAction(stringIDToTypeID("colorRange"), d, DialogModes.NO);
}
function 新建图层() //新建图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("layer"));
d.putReference(stringIDToTypeID("null"), r);
d.putInteger(stringIDToTypeID("layerID"), 33);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 扩展2() //扩展
{
var d = new ActionDescriptor();
d.putUnitDouble(stringIDToTypeID("by"), stringIDToTypeID("pixelsUnit"), 1);
d.putBoolean(stringIDToTypeID("selectionModifyEffectAtCanvasBounds"), false);
executeAction(stringIDToTypeID("expand"), d, DialogModes.NO);
}
function 填充() //填充
{
var d = new ActionDescriptor();
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("fillContents"), stringIDToTypeID("foregroundColor"));
d.putUnitDouble(stringIDToTypeID("opacity"), stringIDToTypeID("percentUnit"), 100);
d.putEnumerated(stringIDToTypeID("mode"), stringIDToTypeID("blendMode"), stringIDToTypeID("normal"));
executeAction(stringIDToTypeID("fill"), d, DialogModes.NO);
}
function 画布大小() //画布大小
{
var d = new ActionDescriptor();
d.putBoolean(stringIDToTypeID("relative"), true);
d.putUnitDouble(stringIDToTypeID("width"), stringIDToTypeID("distanceUnit"), 40);
d.putUnitDouble(stringIDToTypeID("height"), stringIDToTypeID("distanceUnit"), 40);
d.putEnumerated(stringIDToTypeID("horizontal"), stringIDToTypeID("horizontalLocation"), stringIDToTypeID("center"));
d.putEnumerated(stringIDToTypeID("vertical"), stringIDToTypeID("verticalLocation"), stringIDToTypeID("center"));
executeAction(stringIDToTypeID("canvasSize"), d, DialogModes.NO);
}
function 魔棒() //魔棒
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
d1.putUnitDouble(stringIDToTypeID("horizontal"), stringIDToTypeID("pixelsUnit"), 3);
d1.putUnitDouble(stringIDToTypeID("vertical"), stringIDToTypeID("pixelsUnit"), 3);
d.putObject(stringIDToTypeID("to"), stringIDToTypeID("point"), d1);
d.putInteger(stringIDToTypeID("tolerance"), 6);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 矩形选框准备删除() //矩形选框
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
d1.putUnitDouble(stringIDToTypeID("top"), stringIDToTypeID("distanceUnit"), 0);
d1.putUnitDouble(stringIDToTypeID("left"), stringIDToTypeID("distanceUnit"), 0);
d1.putUnitDouble(stringIDToTypeID("bottom"), stringIDToTypeID("distanceUnit"), 0.96);
d1.putUnitDouble(stringIDToTypeID("right"), stringIDToTypeID("distanceUnit"), 0.96);
d.putObject(stringIDToTypeID("to"), stringIDToTypeID("rectangle"), d1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 扩展() //扩展
{
var d = new ActionDescriptor();
d.putUnitDouble(stringIDToTypeID("by"), stringIDToTypeID("pixelsUnit"), 25);
d.putBoolean(stringIDToTypeID("selectionModifyEffectAtCanvasBounds"), false);
executeAction(stringIDToTypeID("expand"), d, DialogModes.NO);
}
function 选择反向() //选择反向
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("inverse"), d, DialogModes.NO);
}
function 清除() //清除
{
app.activeDocument.selection.clear();
}
function 图层样式() //图层样式
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("property"), stringIDToTypeID("layerEffects"));
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
d1.putUnitDouble(stringIDToTypeID("scale"), stringIDToTypeID("percentUnit"), 12);
var d2 = new ActionDescriptor();
d2.putBoolean(stringIDToTypeID("enabled"), true);
d2.putBoolean(stringIDToTypeID("present"), true);
d2.putBoolean(stringIDToTypeID("showInDialog"), true);
d2.putEnumerated(stringIDToTypeID("style"), stringIDToTypeID("frameStyle"), stringIDToTypeID("outsetFrame"));
d2.putEnumerated(stringIDToTypeID("paintType"), stringIDToTypeID("frameFill"), stringIDToTypeID("solidColor"));
d2.putEnumerated(stringIDToTypeID("mode"), stringIDToTypeID("blendMode"), stringIDToTypeID("normal"));
d2.putUnitDouble(stringIDToTypeID("opacity"), stringIDToTypeID("percentUnit"), 100);
d2.putUnitDouble(stringIDToTypeID("size"), stringIDToTypeID("pixelsUnit"), 16);
var d3 = new ActionDescriptor();
d3.putDouble(stringIDToTypeID("red"), 255);
d3.putDouble(stringIDToTypeID("green"), 0);
d3.putDouble(stringIDToTypeID("blue"), 0);
d2.putObject(stringIDToTypeID("color"), stringIDToTypeID("RGBColor"), d3);
d2.putBoolean(stringIDToTypeID("overprint"), false);
d1.putObject(stringIDToTypeID("frameFX"), stringIDToTypeID("frameFX"), d2);
d.putObject(stringIDToTypeID("to"), stringIDToTypeID("layerEffects"), d1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 取消选择() //取消选择
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("to"), stringIDToTypeID("ordinal"), stringIDToTypeID("none"));
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
}
}
"""

View File

@@ -0,0 +1,326 @@
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QSpinBox, QFormLayout, QTabWidget, QPushButton, QLabel, QVBoxLayout, QWidget, QHBoxLayout, QGroupBox, QLineEdit, QCheckBox, QMessageBox
from PyQt5.QtGui import QIcon
import piece_decorative
import re
from PyQt5.QtWidgets import QApplication
class ImportPDFDialog(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('Import Multiple PDF pages')
self.setWindowIcon(QIcon('icon.png'))
main_widget = QWidget()
self.setCentralWidget(main_widget)
main_layout = QVBoxLayout()
main_widget.setLayout(main_layout)
psd_group = QGroupBox('PSD裁片预处理')
psd_layout = QVBoxLayout(psd_group)
self.spin_box = QSpinBox()
self.spin_box.setMinimum(0)
self.spin_box.setMaximum(100)
self.spin_box.setValue(1)
psd_layout.addWidget(self.spin_box)
self.number_button = QPushButton('名称赋予')
self.number_button.clicked.connect(self.number_button_clicked)
psd_layout.addWidget(self.number_button)
form_layout = QFormLayout()
# fabric_break_button = QPushButton('设置裁片组')
# fabric_break_button.clicked.connect(self.break_fabric_line)
# psd_layout.addWidget(fabric_break_button)
# self.shrink_width_line_edit = QLineEdit()
# form_layout.addRow('缩水值宽:', self.shrink_width_line_edit)
# self.shrink_height_line_edit = QLineEdit()
# form_layout.addRow('缩水值高:', self.shrink_height_line_edit)
#
# psd_layout.addLayout(form_layout)
# psd_pattern_button = QPushButton('缩水修改')
# psd_pattern_button.clicked.connect(self.set_psd_pattern)
# psd_layout.addWidget(psd_pattern_button)
psd_place_button = QPushButton('角度校准')
psd_place_button.clicked.connect(self.place_psd_pieces)
psd_layout.addWidget(psd_place_button)
psdcut_grab_button = QPushButton('花样裁切')
psdcut_grab_button.clicked.connect(self.psdcut)
psd_layout.addWidget(psdcut_grab_button)
psd_grab_button = QPushButton('裁片抓取')
psd_grab_button.clicked.connect(self.grab_psd_pieces)
psd_layout.addWidget(psd_grab_button)
main_layout.addWidget(psd_group)
psd_group2 = QGroupBox('缩水修改')
psd_layout = QVBoxLayout(psd_group2)
# self.spin_box = QSpinBox()
# self.spin_box.setMinimum(0)
# self.spin_box.setMaximum(100)
# self.spin_box.setValue(1)
# psd_layout.addWidget(self.spin_box)
# self.number_button = QPushButton('名称赋予')
# self.number_button.clicked.connect(self.number_button_clicked)
# psd_layout.addWidget(self.number_button)
# form_layout = QFormLayout()
# fabric_break_button = QPushButton('设置裁片组')
# fabric_break_button.clicked.connect(self.break_fabric_line)
# psd_layout.addWidget(fabric_break_button)
self.shrink_width_line_edit = QLineEdit()
form_layout.addRow('缩水值宽:', self.shrink_width_line_edit)
self.shrink_height_line_edit = QLineEdit()
form_layout.addRow('缩水值高:', self.shrink_height_line_edit)
psd_layout.addLayout(form_layout)
psd_pattern_button = QPushButton('缩水修改')
psd_pattern_button.clicked.connect(self.set_psd_pattern)
psd_layout.addWidget(psd_pattern_button)
# psd_place_button = QPushButton('角度校准')
# psd_place_button.clicked.connect(self.place_psd_pieces)
# psd_layout.addWidget(psd_place_button)
# psdcut_grab_button = QPushButton('花样裁切')
# psdcut_grab_button.clicked.connect(self.psdcut)
# psd_layout.addWidget(psdcut_grab_button)
# psd_grab_button = QPushButton('裁片抓取')
# psd_grab_button.clicked.connect(self.grab_psd_pieces)
# psd_layout.addWidget(psd_grab_button)
main_layout.addWidget(psd_group2)
# 添加六个按钮,每行三个按钮
align_group = QGroupBox('裁片对齐')
align_layout = QVBoxLayout(align_group)
# 创建水平布局用于放置每行的按钮
row_layout = QHBoxLayout()
# 创建第一个按钮
neckline_align_button = QPushButton('左上对齐')
neckline_align_button.clicked.connect(self.align_neckline1)
row_layout.addWidget(neckline_align_button)
# 创建第二个按钮
button2 = QPushButton('领口对齐')
button2.clicked.connect(self.another_function2)
row_layout.addWidget(button2)
# 创建第三个按钮
button3 = QPushButton('右上对齐')
button3.clicked.connect(self.another_function3)
row_layout.addWidget(button3)
# 将第一行按钮添加到垂直布局
align_layout.addLayout(row_layout)
# 创建水平布局用于放置第二行的按钮
row_layout = QHBoxLayout()
# 创建第四个按钮
button4 = QPushButton('左下对齐')
button4.clicked.connect(self.another_function4)
row_layout.addWidget(button4)
# 创建第五个按钮
button5 = QPushButton('下摆对齐')
button5.clicked.connect(self.another_function5)
row_layout.addWidget(button5)
# 创建第六个按钮
button6 = QPushButton('右下对齐')
button6.clicked.connect(self.another_function6)
row_layout.addWidget(button6)
# 将第二行按钮添加到垂直布局
align_layout.addLayout(row_layout)
main_layout.addWidget(align_group)
main_layout.addWidget(align_group)
info_group = QGroupBox('信息写入')
info_layout = QVBoxLayout(info_group)
size_add_button = QPushButton('添加定位点')
size_add_button.clicked.connect(self.add_sizes)
info_layout.addWidget(size_add_button)
# material_count_button = QPushButton('缩放信息写入')
# material_count_button.clicked.connect(self.count_materials)
# info_layout.addWidget(material_count_button)
material_count_button2 = QPushButton('重写缩放信息')
material_count_button2.clicked.connect(self.count_materials2)
info_layout.addWidget(material_count_button2)
main_layout.addWidget(info_group)
pattern_group = QGroupBox('自动套花')
pattern_layout = QVBoxLayout(pattern_group)
pattern_extend_button = QPushButton('通码延申')
pattern_extend_button.clicked.connect(self.extend_pattern)
pattern_layout.addWidget(pattern_extend_button)
scale_button = QPushButton('宽高缩放')
scale_button.clicked.connect(self.scale_dimensions)
pattern_layout.addWidget(scale_button)
proportional_scale_button = QPushButton('比例缩放')
proportional_scale_button.clicked.connect(self.proportional_scale)
pattern_layout.addWidget(proportional_scale_button)
bigproportional_scale_button = QPushButton('定位点比例缩放')
bigproportional_scale_button.clicked.connect(self.bigproportional_scale)
pattern_layout.addWidget(bigproportional_scale_button)
# self.checkbox1 = QCheckBox('混排套图方法', self)
# pattern_layout.addWidget(self.checkbox1)
main_layout.addWidget(pattern_group)
save_group = QGroupBox('文档保存')
save_layout = QVBoxLayout(save_group)
# add_size_button = QPushButton('尺码激活')
# add_size_button.clicked.connect(self.add_size_to_layers)
# save_layout.addWidget(add_size_button)
form_layout = QFormLayout()
self.prefix_line_edit = QLineEdit()
form_layout.addRow('前缀添加:', self.prefix_line_edit)
save_layout.addLayout(form_layout)
save_button = QPushButton('保存')
save_button.clicked.connect(self.save_document2)
save_layout.addWidget(save_button)
main_layout.addWidget(save_group)
def align_neckline1(self):
piece_decorative.PS_DXF7_jscode_fun('左上对齐2()')
pass
def another_function2(self):
piece_decorative.PS_DXF3_jscode_fun('领口对齐2()')
pass
def another_function3(self):
piece_decorative.PS_DXF2_jscode_fun('右上对齐2()')
# piece_decorative.PS_DXF2_jscode_fun(f'文档保存最新("{前缀}");')
pass
def another_function4(self):
piece_decorative.PS_DXF4_jscode_fun('左下对齐2()')
# piece_decorative.PS_DXF2_jscode_fun(f'文档保存最新("{前缀}");')
pass
def another_function5(self):
piece_decorative.PS_DXF5_jscode_fun('下摆对齐2()')
# piece_decorative.PS_DXF2_jscode_fun(f'文档保存最新("{前缀}");')
pass
def another_function6(self):
piece_decorative.PS_DXF6_jscode_fun('右下对齐2()')
# piece_decorative.PS_DXF2_jscode_fun(f'文档保存最新("{前缀}");')
pass
def save_document2(self):
前缀 = self.prefix_line_edit.text()
piece_decorative.PS_DXF2_jscode_fun(f'文档保存最新("{前缀}");')
def add_size_to_layers(self):
piece_decorative.PS_DXF2_jscode_fun('信息激活2();')
def psdcut(self):
piece_decorative.PS_DXF6_jscode_fun('图像切割2();')
def number_button_clicked(self):
current_value = self.spin_box.value()
piece_decorative.PS_DXF_jscode_fun(f"名称赋予({current_value});")
new_value = current_value + 1
self.spin_box.setValue(new_value)
def another_function(self):
# 在这里定义按钮点击事件的处理逻辑
pass
def set_psd_pattern(self):
高度值 = self.shrink_height_line_edit.text()
宽度值 = self.shrink_width_line_edit.text()
number_pattern = re.compile(r'^\d+(\.\d+)?$') # 正则表达式匹配数字格式
if number_pattern.match(高度值) and number_pattern.match(宽度值):
# 如果两个值都是数字,执行批量缩水操作
# piece_decorative.PS_DXF2_jscode_fun('设置花样组删除图层设置名称();')
piece_decorative.PS_DXF2_jscode_fun(f"批量缩水值修改({宽度值},{高度值});")
else:
# 如果至少有一个值不是数字,显示警告
QMessageBox.warning(self, '错误', '缩水值只能是数字!')
def grab_psd_pieces(self):
piece_decorative.PS_DXF2_jscode_fun('设置花样组删除图层设置名称();')
piece_decorative.PS_DXF12_jscode_fun('批量套数写入();')
piece_decorative.PS_DXF2_jscode_fun('设置花样组2();')
piece_decorative.PS_DXF_jscode_fun('裁片吸取2();')
piece_decorative.PS_DXF2_jscode_fun('设置花样组顺序居中();')
piece_decorative.PS_DXF_jscode_fun('信息写入();')
piece_decorative.PS_DXF3_jscode_fun('裁片视图检查2();')
def place_psd_pieces(self):
piece_decorative.PS_DXF3_jscode_fun('角度旋转();')
def align_neckline(self):
piece_decorative.PS_DXF2_jscode_fun('领口对齐();')
def extend_pattern(self):
# if self.checkbox1.isChecked():
# piece_decorative.PS_DXF_jscode_fun('删除指定名称蒙版();')
# piece_decorative.PS_DXF20_jscode_fun('混排通码延申导出();')
# print('按下')
#
# else:
# print('没有按下')
piece_decorative.PS_DXF6_jscode_fun('前景色修改();')
piece_decorative.PS_DXF_jscode_fun('删除指定名称蒙版();')
piece_decorative.PS_DXF_jscode_fun('裁片射出();')
piece_decorative.PS_DXF2_jscode_fun('信息激活2();')
def scale_dimensions(self):
piece_decorative.PS_DXF6_jscode_fun('前景色修改();')
piece_decorative.PS_DXF_jscode_fun('删除指定名称蒙版();')
piece_decorative.PS_DXF3_jscode_fun('裁片射出宽高缩放();')
piece_decorative.PS_DXF2_jscode_fun('信息激活2();')
def proportional_scale(self):
piece_decorative.PS_DXF6_jscode_fun('前景色修改();')
piece_decorative.PS_DXF_jscode_fun('删除指定名称蒙版();')
piece_decorative.PS_DXF9_jscode_fun('裁片射出比例缩放按中心点();')
piece_decorative.PS_DXF2_jscode_fun('信息激活2();')
def bigproportional_scale(self):
piece_decorative.PS_DXF6_jscode_fun('前景色修改();')
piece_decorative.PS_DXF_jscode_fun('删除指定名称蒙版();')
piece_decorative.PS_DXF11_jscode_fun('裁片射出缩放();')
piece_decorative.PS_DXF2_jscode_fun('信息激活2();')
def add_sizes(self):
piece_decorative.PS_DXF_jscode_fun('添加缩放定位点();')
def count_materials(self):
piece_decorative.PS_DXF_jscode_fun('信息写入();')
def count_materials2(self):
piece_decorative.PS_DXF3_jscode_fun('重写基码信息2();')
piece_decorative.PS_DXF_jscode_fun('信息写入();')
if __name__ == '__main__':
app2 = QApplication(sys.argv)
dialog = ImportPDFDialog()
dialog.show()
sys.exit(app2.exec_())

View File

@@ -0,0 +1,427 @@
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QLabel, QPushButton, QFileDialog, \
QLineEdit, QScrollArea, QGroupBox, QHBoxLayout, QMessageBox, QProgressDialog
from PyQt5.QtGui import QIntValidator
from PyQt5.QtCore import QTimer
import re
import os
import ezdxf
import ezdxf.tools
import ezdxf.bbox
import ezdxf.units
import ezdxf.math
from coreldraw_checker import is_coreldraw_running
from functools import partial
import win32com.client
import os
import shutil
import threading
from clear_folder import another_function
class YourMainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("RUNDXF")
layout = QVBoxLayout()
self.button1 = QPushButton("DXF文件路径")
self.button1.setFixedWidth(200)
self.button1.clicked.connect(self.showDxfFileDialog)
self.button2 = QPushButton("PLT文件路径")
self.button2.setFixedWidth(200)
self.button2.clicked.connect(self.showPltFileDialog)
self.initial_button1_text = self.button1.text()
self.initial_button2_text = self.button2.text()
panel1 = QGroupBox("文件路径")
panel1_layout = QVBoxLayout(panel1)
panel1_layout.setSpacing(10)
panel1_layout.setContentsMargins(10, 10, 10, 10)
panel1_layout.addWidget(self.button1)
panel1_layout.addWidget(self.button2)
layout.addWidget(panel1)
self.label5 = QLabel('单码片数')
self.lineEdit3 = QLineEdit()
self.lineEdit3.setValidator(QIntValidator())
self.lineEdit3.setFixedSize(120, 30)
layout.addWidget(self.label5)
layout.addWidget(self.lineEdit3)
self.scrollWidget = QWidget()
self.scrollWidgetLayout = QVBoxLayout(self.scrollWidget)
self.scrollArea = QScrollArea()
self.scrollArea.setWidget(self.scrollWidget)
self.scrollArea.setWidgetResizable(True)
layout.addWidget(self.scrollArea)
self.clearButton = QPushButton("清空信息")
self.clearButton.clicked.connect(self.clearScrollArea)
layout.addWidget(self.clearButton)
confirm_button = QPushButton("分割")
confirm_button.clicked.connect(self.updateScrollArea)
#confirm_button.clicked.connect(self.freezeAndParse) # 连接按钮点击事件
layout.addWidget(confirm_button)
central_widget = QWidget()
central_widget.setLayout(layout)
self.setCentralWidget(central_widget)
self.dxfLineEdits = {}
self.allowButtonActions = True # 标志变量,控制是否允许按钮行为
def run_coreldraw_macros(self):
try:
dogms = win32com.client.DispatchEx("CorelDRAW.Application.23")
macros_to_run = [
"RUN.OpenDXFFilesInFolder",
"RUN.RotateSelectionClockwise",
"RUN.DeleteUnnamedSublayers",
"RUN.StToFront",
"RUN.IterateSublayerNames",
]
for macro in macros_to_run:
dogms.GMSManager.RunMacro("RUNDXF", macro)
except Exception as e:
QMessageBox.warning(self, "警告", "缺少CDR模块请载入CDR模块", QMessageBox.Ok)
# print("缺少CDR模块请载入CDR模块")
def showPltFileDialog(self):
options = QFileDialog.Options()
file_path, _ = QFileDialog.getOpenFileName(self, "选择PLT文件", "", "PLT Files (*.plt);;All Files (*)",
options=options)
if file_path:
print("Selected PLT file:", file_path)
extracted_content = self.extract_content_from_plt_path(file_path)
print(extracted_content)
self.sizes = self.fill_sizes_from_extracted_content(extracted_content)
# 更新尺寸字典后,清空并填充滚动区域
self.clearScrollArea()
self.populateScrollArea()
self.initial_plt_path = file_path # 更新初始路径而不更新按钮文本
else:
print("No PLT file selected")
QMessageBox.warning(self, "警告", "没有选择文件夹。请重新选择文件夹。", QMessageBox.Ok)
pass
def showDxfFileDialog(self):
options = QFileDialog.Options()
file_path, _ = QFileDialog.getOpenFileName(self, "选择DXF文件", "", "DXF Files (*.dxf);;All Files (*)",
options=options)
if file_path:
print("Selected DXF file:", file_path)
self.initial_dxf_path = file_path # 更新初始路径而不更新按钮文本
else:
print("No DXF file selected")
QMessageBox.warning(self, "警告", "没有选择文件夹。请重新选择文件夹。", QMessageBox.Ok)
# 在此处添加提醒逻辑,例如使用 QMessageBox 提示用户没有选择文件
def extract_content_from_plt_path(self, plt_path):
match = re.search(r'\((.*?)\)', plt_path)
if match:
extracted_content = match.group(1)
return extracted_content
else:
return "No content in parentheses found"
def fill_sizes_from_extracted_content(self, extracted_content):
sizes = extracted_content.split("+")
size_dict = {}
for size in sizes:
size_dict[size] = ""
return size_dict
def process_dxf_file(self, file_path, extracted_content):
doc = ezdxf.readfile(file_path)
msp = doc.modelspace()
mspBox = ezdxf.bbox.extents(msp)
print("=====", os.path.basename(file_path))
print("左上角坐标:", mspBox.extmin)
print("画布宽:", mspBox.size[0], "画布高:", mspBox.size[1])
print()
for entity in msp.query():
if entity.dxftype() == "INSERT":
temp = []
rotation = None
block = doc.blocks[entity.dxf.name]
for e in block:
if e.dxftype() != "TEXT":
temp.append(e)
else:
rotation = e.dxf.rotation
if rotation is not None:
rotation %= 360
if 45 <= rotation < 135:
rotation = 90
elif 135 <= rotation < 225:
rotation = 180
elif 225 <= rotation < 315:
rotation = -90
else:
rotation = 0
print("=====", entity.dxf.name)
print("大小:", ezdxf.bbox.extents(temp).size)
print("文字角度:", rotation)
center = ezdxf.bbox.extents(temp).center
center = (center.x, mspBox.extmax.y - center.y) # 调整center y值
print("中心坐标:", center)
center = (center[1], mspBox.size[0] - center[0]) # 旋转后中心坐标
print("旋转后中心坐标:", center)
separator = "_" # 分隔符
entity.dxf.name += separator + str(rotation)
block.name += separator + str(rotation)
new_file_path = os.path.join(r"D:\marktemp", "{}.dxf".format(extracted_content))
doc.saveas(new_file_path)
def getSinglePieceCount(self):
return self.lineEdit3.text()
def recreate_folders(self):
# 定义文件夹路径
folder_paths = [r"D:\PSMARKtemp", r"D:\marktemp"]
# 删除文件夹及其内容
for folder_path in folder_paths:
if os.path.exists(folder_path):
shutil.rmtree(folder_path)
print(f"Deleted folder: {folder_path}")
# 重新创建文件夹
for folder_path in folder_paths:
os.makedirs(folder_path)
print(f"Recreated folder: {folder_path}")
def freezeAndParse(self):
self.parse_button.setEnabled(False) # 冻结按钮
QTimer.singleShot(10000, self.unfreezeButton) # 10秒后解冻按钮
def unfreezeButton(self):
self.parse_button.setEnabled(True) # 解冻按钮
def updateScrollArea(self):
another_function()
if not is_coreldraw_running():
QMessageBox.warning(self, "警告", "CorelDRAW未运行无法执行操作。")
return
plt_file_path = self.initial_plt_path
# 获取DXF文件路径
dxf_file_path = self.initial_dxf_path
extracted_content = self.extract_content_from_plt_path(plt_file_path)
if dxf_file_path:
# 去掉括号内内容后的PLT文件名作为DXF文件名
plt_filename = os.path.basename(plt_file_path)
# dxf_filename = re.sub(r'\(.*?\)', '', plt_filename)
self.process_dxf_file(dxf_file_path, extracted_content) # 调用解析函数并传入单码片数和新的DXF文件名
print("DXF文件解析完成")
else:
QMessageBox.warning(self, "警告", "没有选择DXF文件。请先选择一个DXF文件。", QMessageBox.Ok)
print()
self.run_coreldraw_macros()
single_code_pieces = int(self.getSinglePieceCount()) # 获取单码片数
print(single_code_pieces)
# 打印滚动区域中的输入框内容
code_quantities = {} # 创建一个新的字典用于存储数据
for label, line_edit in self.dxfLineEdits.items():
text = line_edit.text()
if text.isdigit():
value = int(text) # 尝试将文本转换为整数
else:
try:
value = float(text) # 尝试将文本转换为浮点数
except ValueError:
print(f"Invalid value for {label}: {text}")
continue # 转换失败,跳过当前循环迭代
code_quantities[label] = value # 存储转换后的数字到字典
print(code_quantities)
length = len(code_quantities)
print(length) # 输出 3因为字典中有三对键值对
corel_app = win32com.client.Dispatch("CorelDRAW.Application.23")
# 获取当前活动文档
active_document = corel_app.ActiveDocument
# 获取当前页面中所有图层的名称,排除特定名称的图层
layer_names = [layer.Name for layer in active_document.ActivePage.Layers
if layer.Name not in ["辅助线", "1", "0", "Defpoints"]]
p_numbers = [] # 初始化存储 P 数字的列表
for code, quantity in code_quantities.items():
for i in range(1, single_code_pieces + 1):
p_numbers.extend([f"P{i}"] * int(quantity))
print(p_numbers)
# 循环遍历不同的码
# p_numbers = [] # 初始化存储 P 数字的列表
#
# for code, quantity in code_quantities.items():
# for i in range(1, single_code_pieces + 1):
# # 根据码的数量分别生成对应数量的 P 数字,并添加到列表中
# p_numbers.extend([f"P{i}"] * quantity)
# print(p_numbers)
new_layer_names = []
index = 0
for old_name in layer_names:
parts = old_name.split("-") # 根据"-"分割字符串
if len(parts) > 1:
new_name = f"{p_numbers[index]}-{parts[1]}" # 使用数组中的 P 数字
new_layer_names.append(new_name)
index += 1
# 在新的图层名数组中遍历,对图层进行修改
modified_names = [] # 创建一个列表来存储修改后的名称
modified_names2 = []
for i, new_name in enumerate(new_layer_names):
active_document.ActivePage.Layers(layer_names[i]).Name = new_name
modified_names2.append(new_name)
modified_names.append(new_name)
print(f"Modified: {layer_names[i]} -> {new_name}")
# print(modified_names)
def delete_layers_by_names(names_to_delete, active_document):
corel_app = win32com.client.Dispatch("CorelDRAW.Application.23")
# 获取当前活动文档
active_document = corel_app.ActiveDocument
for target_layer_name in names_to_delete:
for layer in active_document.ActivePage.Layers:
if layer.Name == target_layer_name:
layer.Delete()
break # 找到目标图层后中断循环
modified_names_list = [] # 用于存储每次循环中的 modified_names 列表
Index = 0
for code in code_quantities:
quantity = code_quantities[code]
total_pieces = quantity * single_code_pieces
# 获取数组的前 total_pieces 个元素
newmodified_names_filtered = modified_names[:total_pieces]
# print(newmodified_names_filtered)
result_array = [fruit for fruit in modified_names2 if fruit not in newmodified_names_filtered]
result_array_length = len(result_array)
print(result_array_length)
delete_layers_by_names(result_array, active_document.ActivePage)
# modified_names_list.append(modified_names) # 将 modified_names 添加到数组中
dogms = win32com.client.DispatchEx("CorelDRAW.Application.23")
dogms.GMSManager.RunMacro("RUNDXF", "RUN.ExportSelectionToPSD", Index)
dogms.GMSManager.RunMacro("RUNDXF", "RUN.HOURUN", result_array_length)
modified_names = modified_names[total_pieces:]
Index += 1
corel_app = win32com.client.Dispatch("CorelDRAW.Application.23")
corel_app .GMSManager.RunMacro("RUNDXF", "RUN.ActiveDocumentClose")
QMessageBox.warning(self, "提醒", "分割完成,请进行裁片套版操作。")
def updateLineEditsFromSizes(self):
for size_label, line_edit in self.dxfLineEdits.items():
self.sizes[size_label] = line_edit.text()
def populateScrollArea(self):
self.clearScrollArea()
for size_label, size_text in self.sizes.items():
size_layout = QHBoxLayout()
size_layout.addWidget(QLabel(size_label))
line_edit = QLineEdit()
line_edit.setValidator(QIntValidator())
line_edit.setFixedSize(100, 30)
line_edit.setText(size_text)
self.dxfLineEdits[size_label] = line_edit
size_layout.addWidget(line_edit)
self.scrollWidgetLayout.addLayout(size_layout)
def clearScrollArea(self):
for i in reversed(range(self.scrollWidgetLayout.count())):
item = self.scrollWidgetLayout.itemAt(i)
if isinstance(item, QHBoxLayout) or isinstance(item, QVBoxLayout):
while item.count():
widget = item.takeAt(0).widget()
if widget:
widget.deleteLater()
self.dxfLineEdits.clear() # 清空部件引用
if __name__ == '__main__':
app = QApplication(sys.argv)
mainWindow = YourMainWindow()
mainWindow.show()
sys.exit(app.exec_())

View File

@@ -0,0 +1,311 @@
import sys
from PyQt5.QtWidgets import QMainWindow, QApplication, QWidget, QVBoxLayout, QPushButton, QGroupBox, QLabel, QLineEdit, QFormLayout
import piece_decorative
class ImportPDFDialog2(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('Import Multiple PDF pages')
main_widget = QWidget()
self.setCentralWidget(main_widget)
main_layout = QVBoxLayout()
main_widget.setLayout(main_layout)
new_group_box5 = QGroupBox('打版联动')
new_group_layout5 = QVBoxLayout(new_group_box5)
# 创建4个按钮并连接到槽函数
Dbtn4_1 = QPushButton('图层分割')
Dbtn4_2 = QPushButton('批量图层编组')
Dbtn4_3 = QPushButton('快速超链接')
# Dbtn4_4 = QPushButton('定位点比例缩放(模板)')
Dbtn4_1.clicked.connect(self.on_Dbtn4_1_clicked)
Dbtn4_2.clicked.connect(self.on_Dbtn4_2_clicked)
Dbtn4_3.clicked.connect(self.on_Dbtn4_3_clicked)
# Dbtn4_4.clicked.connect(self.on_Dbtn4_4_clicked)
# 将按钮添加到新的盒子1中
new_group_layout5.addWidget(Dbtn4_1)
new_group_layout5.addWidget(Dbtn4_2)
new_group_layout5.addWidget(Dbtn4_3)
# new_group_layout5.addWidget(Dbtn4_4)
# 将新的盒子1添加到主布局中
main_layout.addWidget(new_group_box5)
# 快速换图
quick_change_group = QGroupBox('快速换图')
quick_change_layout = QVBoxLayout(quick_change_group)
# bigbtn_standardize_pattern = QPushButton('图像切割')
btn_standardize_pattern = QPushButton('花样标准化')
btn_pattern_to_external = QPushButton('花样转外链')
btn_quick_change = QPushButton('快速换图')
btn_batch_quick_change = QPushButton('批量快速换图')
# btn_Kbatch_quick_change = QPushButton('特定版本快速换图')
# 为每个按钮连接槽函数
btn_standardize_pattern.clicked.connect(self.on_standardize_pattern_clicked)
btn_pattern_to_external.clicked.connect(self.on_pattern_to_external_clicked)
btn_quick_change.clicked.connect(self.on_quick_change_clicked)
btn_batch_quick_change.clicked.connect(self.on_batch_quick_change_clicked)
# btn_Kbatch_quick_change.clicked.connect(self.on_kbatch_quick_change_clicked)
# bigbtn_standardize_pattern.clicked.connect(self.on_bigstandardize_pattern_clicked)
quick_change_layout.addWidget(btn_standardize_pattern)
# quick_change_layout.addWidget(bigbtn_standardize_pattern)
quick_change_layout.addWidget(btn_pattern_to_external)
quick_change_layout.addWidget(btn_quick_change)
quick_change_layout.addWidget(btn_batch_quick_change)
# quick_change_layout.addWidget(btn_Kbatch_quick_change)
main_layout.addWidget(quick_change_group)
# 初始化界面
# 创建一个新的盒子
new_group_box1 = QGroupBox('模板生成')
new_group_layout1 = QVBoxLayout(new_group_box1)
# 创建4个按钮并连接到槽函数
btn4_1 = QPushButton('通码延申(模板)')
btn4_2 = QPushButton('宽高缩放(模板)')
btn4_3 = QPushButton('比例缩放(模板)')
btn4_4 = QPushButton('定位点比例缩放(模板)')
btn4_1.clicked.connect(self.on_btn4_1_clicked)
btn4_2.clicked.connect(self.on_btn4_2_clicked)
btn4_3.clicked.connect(self.on_btn4_3_clicked)
btn4_4.clicked.connect(self.on_btn4_4_clicked)
# 将按钮添加到新的盒子1中
new_group_layout1.addWidget(btn4_1)
new_group_layout1.addWidget(btn4_2)
new_group_layout1.addWidget(btn4_3)
new_group_layout1.addWidget(btn4_4)
# 将新的盒子1添加到主布局中
main_layout.addWidget(new_group_box1)
#############################################
new_group_box5 = QGroupBox('定位码快速换图')
new_group_layout5 = QVBoxLayout(new_group_box5)
# 创建4个按钮并连接到槽函数
Kbtn4_1 = QPushButton('定位码快速超链接')
Kbtn4_2 = QPushButton('定位码快速换图')
# Dbtn4_4 = QPushButton('定位点比例缩放(模板)')
Kbtn4_1.clicked.connect(self.on_Kbtn4_1_clicked)
Kbtn4_2.clicked.connect(self.on_Kbtn4_2_clicked)
#Dbtn4_3.clicked.connect(self.on_Dbtn4_3_clicked)
# Dbtn4_4.clicked.connect(self.on_Dbtn4_4_clicked)
# 将按钮添加到新的盒子1中
new_group_layout5.addWidget(Kbtn4_1)
new_group_layout5.addWidget(Kbtn4_2)
# new_group_layout5.addWidget(Dbtn4_3)
# new_group_layout5.addWidget(Dbtn4_4)
# 将新的盒子1添加到主布局中
main_layout.addWidget(new_group_box5)
##############################################
new_group_box2 = QGroupBox('批量化工具')
new_group_layout2 = QVBoxLayout(new_group_box2)
# 创建4个按钮并连接到槽函数
Pbtn4_1 = QPushButton('小码标添加')
Pbtn4_2 = QPushButton('批量修改分辨率')
Pbtn4_3 = QPushButton('批量加款号')
Pbtn4_4 = QPushButton('模特批量替换')
Pbtn4_5 = QPushButton('SO小样连晒')
Pbtn4_6 = QPushButton('SO小样拼贴')
Pbtn4_7 = QPushButton('SO小样缩放')
Pbtn4_1.clicked.connect(self.on_Pbtn4_1_clicked)
Pbtn4_2.clicked.connect(self.on_Pbtn4_2_clicked)
Pbtn4_3.clicked.connect(self.on_Pbtn4_3_clicked)
Pbtn4_4.clicked.connect(self.on_Pbtn4_4_clicked)
Pbtn4_5.clicked.connect(self.on_Pbtn4_5_clicked)
Pbtn4_6.clicked.connect(self.on_Pbtn4_6_clicked)
Pbtn4_7.clicked.connect(self.on_Pbtn4_7_clicked)
# 将按钮添加到新的盒子1中
new_group_layout2.addWidget(Pbtn4_1)
new_group_layout2.addWidget(Pbtn4_2)
new_group_layout2.addWidget(Pbtn4_3)
new_group_layout2.addWidget(Pbtn4_4)
new_group_layout2.addWidget(Pbtn4_5)
new_group_layout2.addWidget(Pbtn4_6)
new_group_layout2.addWidget(Pbtn4_7)
# 将新的盒子1添加到主布局中
main_layout.addWidget(new_group_box2)
# def on_kbatch_quick_change_clicked(self):
# piece_decorative.PS_DXF18_jscode_fun('龙服的快速换图();')
#
# print("按钮被点击")
# pass
def on_Pbtn4_7_clicked(self):
piece_decorative.PS_DXF27_jscode_fun('新的米样缩放();')
print("按钮被点击")
pass
def on_Kbtn4_1_clicked(self):
piece_decorative.PS_DXF16_jscode_fun('快速定位码链接();')
print("按钮被点击")
pass
def on_Kbtn4_2_clicked(self):
piece_decorative.PS_DXF17_jscode_fun('定位码批量化替换外链新();')
print("按钮被点击")
pass
def on_Dbtn4_1_clicked(self):
piece_decorative.PS_DXF8_jscode_fun('图像分割();')
print("按钮被点击")
pass
def on_Dbtn4_2_clicked(self):
piece_decorative.PS_DXF15_jscode_fun('图层自动编组2();')
print("按钮被点击")
pass
def on_Dbtn4_3_clicked(self):
piece_decorative.PS_DXF15_jscode_fun('快速超级链接2();')
# piece_decorative.PS_DXF22_jscode_fun('模特换衣功能();')
print("按钮被点击")
pass
def on_Pbtn4_4_clicked(self):
piece_decorative.PS_DXF26_jscode_fun('模特换图();')
print("按钮被点击")
pass
def on_Pbtn4_5_clicked(self):
piece_decorative.PS_DXF23_jscode_fun('自动连晒();')
print("按钮被点击")
pass
def on_Pbtn4_6_clicked(self):
piece_decorative.PS_DXF24_jscode_fun('自动米样拼贴();')
print("按钮被点击")
pass
def on_Pbtn4_1_clicked(self):
piece_decorative.PS_DXF8_jscode_fun('码标添加2();')
print("按钮被点击")
pass
def on_Pbtn4_2_clicked(self):
piece_decorative.PS_DXF8_jscode_fun('批量分辨率修改();')
print("按钮被点击")
pass
def on_Pbtn4_3_clicked(self):
piece_decorative.PS_DXF8_jscode_fun('批量款号添加();')
print("按钮被点击")
pass
# 槽函数示例
def on_standardize_pattern_clicked(self):
piece_decorative.PS_DXF5_jscode_fun('花样标准化3();')
print("花样标准化按钮被点击")
def on_pattern_to_external_clicked(self):
piece_decorative.PS_DXF5_jscode_fun('花样图层导出();')
print("花样转外链按钮被点击")
def on_quick_change_clicked(self):
piece_decorative.PS_DXF5_jscode_fun('替换外链新();')
print("快速换图按钮被点击")
def on_batch_quick_change_clicked(self):
piece_decorative.PS_DXF8_jscode_fun('批量化替换外链新();')
print("批量快速换图按钮被点击")
def on_btn4_1_clicked(self):
# 处理新盒子1中按钮4_1的点击事件
piece_decorative.PS_DXF_jscode_fun('删除指定名称蒙版();')
piece_decorative.PS_DXF5_jscode_fun('裁片射出模板();')
piece_decorative.PS_DXF2_jscode_fun('信息激活2();')
pass
def on_btn4_2_clicked(self):
# 处理新盒子1中按钮4_2的点击事件
piece_decorative.PS_DXF_jscode_fun('删除指定名称蒙版();')
piece_decorative.PS_DXF4_jscode_fun('裁片射出宽高缩放模板();')
piece_decorative.PS_DXF2_jscode_fun('信息激活2();')
pass
def on_btn4_3_clicked(self):
# 处理新盒子1中按钮4_3的点击事件
piece_decorative.PS_DXF_jscode_fun('删除指定名称蒙版();')
piece_decorative.PS_DXF14_jscode_fun('裁片射出宽高缩放模板按中心();')
piece_decorative.PS_DXF2_jscode_fun('信息激活2();')
pass
def on_btn4_4_clicked(self):
# 处理新盒子1中按钮4_3的点击事件
piece_decorative.PS_DXF_jscode_fun('删除指定名称蒙版();')
piece_decorative.PS_DXF7_jscode_fun('裁片射出缩放模板();')
piece_decorative.PS_DXF2_jscode_fun('信息激活2();')
pass
# def on_Dbtn4_1_clicked(self):
# # 处理新盒子1中按钮4_4的点击事件
# piece_decorative.PS_DXF8_jscode_fun('图像分割();')
# pass
# def on_Pbtn4_5_clicked(self):
# # 处理新盒子1中按钮4_4的点击事件
# piece_decorative.PS_DXF11_jscode_fun('批量重设画布幅宽 ();')
# pass
# def on_Pbtn4_6_clicked(self):
# # 处理新盒子1中按钮4_4的点击事件
# piece_decorative.PS_DXF11_jscode_fun('批量重设画布幅宽();')
# pass
if __name__ == '__main__':
app = QApplication(sys.argv)
dialog = ImportPDFDialog2()
dialog.show()
sys.exit(app.exec_())

View File

@@ -0,0 +1,81 @@
import sys
from PyQt5.QtWidgets import QMainWindow, QApplication, QWidget, QVBoxLayout, QPushButton, QGroupBox, QLabel, QFrame
from PyQt5 import QtGui
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QLabel, QVBoxLayout, QWidget
from PyQt5.QtGui import QPixmap
import os
class ImportPDFDialog4(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('Import Multiple PDF pages')
main_widget = QWidget()
self.setCentralWidget(main_widget)
main_layout = QVBoxLayout()
main_widget.setLayout(main_layout)
# 快速换图
quick_change_group = QGroupBox('版本介绍')
quick_change_layout = QVBoxLayout(quick_change_group)
# 创建图像标签1并调整大小
image_label1 = QLabel()
# 获取当前脚本所在目录的绝对路径
current_directory = os.path.dirname(os.path.abspath(__file__))
# 构建相对路径
image_filename = "img/微信图片_20230906013631.jpg"
relative_path = os.path.join(current_directory, image_filename)
# 创建 QPixmap 对象
pixmap1 = QtGui.QPixmap(relative_path)
pixmap1 = pixmap1.scaledToWidth(200) # 设置宽度限制为200像素
image_label1.setPixmap(pixmap1)
quick_change_layout.addWidget(image_label1)
# 创建文本标签
text_label1 = QLabel('关注抖音查看视频教程')
text_label1.setAlignment(Qt.AlignmentFlag.AlignCenter)
quick_change_layout.addWidget(text_label1)
# 创建分隔线
divider1 = QFrame()
divider1.setFrameShape(QFrame.HLine)
quick_change_layout.addWidget(divider1)
# 创建图像标签2并调整大小
image_label2 = QLabel()
image_filename2 = "img/微信图片_20230906013548.jpg"
relative_path2 = os.path.join(current_directory, image_filename2)
# 创建另一个 QPixmap 对象,使用不同的变量名
pixmap2 = QtGui.QPixmap(relative_path2)
pixmap2 = pixmap2.scaledToWidth(200) # 设置宽度限制为200像素
image_label2.setPixmap(pixmap2)
quick_change_layout.addWidget(image_label2)
# 创建文本标签
text_label2 = QLabel('BUG提交 功能开发 使用反馈 请联系微信')
text_label2.setAlignment(Qt.AlignmentFlag.AlignCenter)
quick_change_layout.addWidget(text_label2)
# 创建分隔线
divider2 = QFrame()
divider2.setFrameShape(QFrame.HLine)
quick_change_layout.addWidget(divider2)
# 创建文本标签
text_label3 = QLabel('PS Mark 版本号1.8(2023.10.4) by:jimi')
text_label3.setAlignment(Qt.AlignmentFlag.AlignCenter)
quick_change_layout.addWidget(text_label3)
quick_change_group.setLayout(quick_change_layout)
main_layout.addWidget(quick_change_group)
if __name__ == '__main__':
app = QApplication(sys.argv)
dialog = ImportPDFDialog4()
dialog.show()
sys.exit(app.exec_())

View File

@@ -0,0 +1,449 @@
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QLabel, QPushButton, QFileDialog, \
QLineEdit, QScrollArea, QGroupBox, QHBoxLayout, QMessageBox, QProgressDialog
from PyQt5.QtGui import QIntValidator
from PyQt5.QtCore import QTimer
import re
import os
import ezdxf
import ezdxf.tools
import ezdxf.bbox
import ezdxf.units
import ezdxf.math
from coreldraw_checker import is_coreldraw_running
from functools import partial
import win32com.client
import os
import shutil
import threading
from clear_folder import another_function
class YourMainWindow5(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("多码混排")
layout = QVBoxLayout()
self.button1 = QPushButton("DXF文件路径")
self.button1.setFixedWidth(200)
self.button1.clicked.connect(self.showDxfFileDialog)
self.button2 = QPushButton("PLT文件路径")
self.button2.setFixedWidth(200)
self.button2.clicked.connect(self.showPltFileDialog)
self.initial_button1_text = self.button1.text()
self.initial_button2_text = self.button2.text()
panel1 = QGroupBox("文件路径")
panel1_layout = QVBoxLayout(panel1)
panel1_layout.setSpacing(10)
panel1_layout.setContentsMargins(10, 10, 10, 10)
panel1_layout.addWidget(self.button1)
panel1_layout.addWidget(self.button2)
layout.addWidget(panel1)
self.label5 = QLabel('单码片数')
self.lineEdit3 = QLineEdit()
self.lineEdit3.setValidator(QIntValidator())
self.lineEdit3.setFixedSize(120, 30)
layout.addWidget(self.label5)
layout.addWidget(self.lineEdit3)
self.scrollWidget = QWidget()
self.scrollWidgetLayout = QVBoxLayout(self.scrollWidget)
self.scrollArea = QScrollArea()
self.scrollArea.setWidget(self.scrollWidget)
self.scrollArea.setWidgetResizable(True)
layout.addWidget(self.scrollArea)
self.clearButton = QPushButton("清空信息")
self.clearButton.clicked.connect(self.clearScrollArea)
layout.addWidget(self.clearButton)
confirm_button = QPushButton("分割")
confirm_button.clicked.connect(self.updateScrollArea)
#confirm_button.clicked.connect(self.freezeAndParse) # 连接按钮点击事件
layout.addWidget(confirm_button)
central_widget = QWidget()
central_widget.setLayout(layout)
self.setCentralWidget(central_widget)
self.dxfLineEdits = {}
self.allowButtonActions = True # 标志变量,控制是否允许按钮行为
def run_coreldraw_macros(self):
try:
dogms = win32com.client.DispatchEx("CorelDRAW.Application.23")
macros_to_run = [
"RUN.OpenDXFFilesInFolder",
"RUN.RotateSelectionClockwise",
"RUN.DeleteUnnamedSublayers",
"RUN.StToFront",
"RUN.IterateSublayerNames",
]
for macro in macros_to_run:
dogms.GMSManager.RunMacro("RUNDXF", macro)
except Exception as e:
QMessageBox.warning(self, "警告", "缺少CDR模块请载入CDR模块", QMessageBox.Ok)
# print("缺少CDR模块请载入CDR模块")
def showPltFileDialog(self):
options = QFileDialog.Options()
file_path, _ = QFileDialog.getOpenFileName(self, "选择PLT文件", "", "PLT Files (*.plt);;All Files (*)",
options=options)
if file_path:
print("Selected PLT file:", file_path)
extracted_content = self.extract_content_from_plt_path(file_path)
print(extracted_content)
self.sizes = self.fill_sizes_from_extracted_content(extracted_content)
# 更新尺寸字典后,清空并填充滚动区域
self.clearScrollArea()
self.populateScrollArea()
self.initial_plt_path = file_path # 更新初始路径而不更新按钮文本
else:
print("No PLT file selected")
QMessageBox.warning(self, "警告", "没有选择文件夹。请重新选择文件夹。", QMessageBox.Ok)
pass
def showDxfFileDialog(self):
options = QFileDialog.Options()
file_path, _ = QFileDialog.getOpenFileName(self, "选择DXF文件", "", "DXF Files (*.dxf);;All Files (*)",
options=options)
if file_path:
print("Selected DXF file:", file_path)
self.initial_dxf_path = file_path # 更新初始路径而不更新按钮文本
else:
print("No DXF file selected")
QMessageBox.warning(self, "警告", "没有选择文件夹。请重新选择文件夹。", QMessageBox.Ok)
# 在此处添加提醒逻辑,例如使用 QMessageBox 提示用户没有选择文件
def extract_content_from_plt_path(self, plt_path):
match = re.search(r'\((.*?)\)', plt_path)
if match:
extracted_content = match.group(1)
return extracted_content
else:
return "No content in parentheses found"
def fill_sizes_from_extracted_content(self, extracted_content):
sizes = extracted_content.split("+")
size_dict = {}
for size in sizes:
size_dict[size] = ""
return size_dict
def process_dxf_file(self, file_path, extracted_content):
doc = ezdxf.readfile(file_path)
msp = doc.modelspace()
mspBox = ezdxf.bbox.extents(msp)
print("=====", os.path.basename(file_path))
print("左上角坐标:", mspBox.extmin)
print("画布宽:", mspBox.size[0], "画布高:", mspBox.size[1])
print()
for entity in msp.query():
if entity.dxftype() == "INSERT":
temp = []
rotation = None
block = doc.blocks[entity.dxf.name]
for e in block:
if e.dxftype() != "TEXT":
temp.append(e)
else:
rotation = e.dxf.rotation
if rotation is not None:
rotation %= 360
if 45 <= rotation < 135:
rotation = 90
elif 135 <= rotation < 225:
rotation = 180
elif 225 <= rotation < 315:
rotation = -90
else:
rotation = 0
print("=====", entity.dxf.name)
print("大小:", ezdxf.bbox.extents(temp).size)
print("文字角度:", rotation)
center = ezdxf.bbox.extents(temp).center
center = (center.x, mspBox.extmax.y - center.y) # 调整center y值
print("中心坐标:", center)
center = (center[1], mspBox.size[0] - center[0]) # 旋转后中心坐标
print("旋转后中心坐标:", center)
separator = "_" # 分隔符
entity.dxf.name += separator + str(rotation)
block.name += separator + str(rotation)
new_file_path = os.path.join(r"D:\marktemp", "{}.dxf".format(extracted_content))
doc.saveas(new_file_path)
def getSinglePieceCount(self):
return self.lineEdit3.text()
def recreate_folders(self):
# 定义文件夹路径
folder_paths = [r"D:\PSMARKtemp", r"D:\marktemp"]
# 删除文件夹及其内容
for folder_path in folder_paths:
if os.path.exists(folder_path):
shutil.rmtree(folder_path)
print(f"Deleted folder: {folder_path}")
# 重新创建文件夹
for folder_path in folder_paths:
os.makedirs(folder_path)
print(f"Recreated folder: {folder_path}")
def freezeAndParse(self):
self.parse_button.setEnabled(False) # 冻结按钮
QTimer.singleShot(10000, self.unfreezeButton) # 10秒后解冻按钮
def unfreezeButton(self):
self.parse_button.setEnabled(True) # 解冻按钮
def updateScrollArea(self):
another_function()
if not is_coreldraw_running():
QMessageBox.warning(self, "警告", "CorelDRAW未运行无法执行操作。")
return
plt_file_path = self.initial_plt_path
# 获取DXF文件路径
dxf_file_path = self.initial_dxf_path
extracted_content = self.extract_content_from_plt_path(plt_file_path)
if dxf_file_path:
# 去掉括号内内容后的PLT文件名作为DXF文件名
plt_filename = os.path.basename(plt_file_path)
# dxf_filename = re.sub(r'\(.*?\)', '', plt_filename)
self.process_dxf_file(dxf_file_path, extracted_content) # 调用解析函数并传入单码片数和新的DXF文件名
print("DXF文件解析完成")
else:
QMessageBox.warning(self, "警告", "没有选择DXF文件。请先选择一个DXF文件。", QMessageBox.Ok)
print()
self.run_coreldraw_macros()
single_code_pieces = int(self.getSinglePieceCount()) # 获取单码片数
print(single_code_pieces)
# 打印滚动区域中的输入框内容
code_quantities = {} # 创建一个新的字典用于存储数据
for label, line_edit in self.dxfLineEdits.items():
text = line_edit.text()
# print(f"Label: {label}, Text: {text}")
if text.isdigit():
value = int(text) # 尝试将文本转换为整数
else:
try:
value = float(text) # 尝试将文本转换为浮点数
except ValueError:
print(f"Invalid value for {label}: {text}")
continue # 转换失败,跳过当前循环迭代
code_quantities[label] = value # 存储转换后的数字到字典
# print(line_edit)
print(code_quantities)
keys_list = list(code_quantities.keys())
values_list = list(code_quantities.values())
# length = len(code_quantities)
# print(length) # 输出 3因为字典中有三对键值对
# 将text中的每个元素与code对应位置的元素相乘
result = [txt * single_code_pieces for txt in values_list]
# 使用zip函数将label和result对应组合
combined = zip(keys_list, result)
# 利用列表推导式生成结果数组
results2 = [lbl for lbl, txt in combined for _ in range(txt)]
# 打印结果数组
print(results2)
###############debug
corel_app = win32com.client.Dispatch("CorelDRAW.Application.23")
# 获取当前活动文档
active_document = corel_app.ActiveDocument
# 获取当前页面中所有图层的名称,排除特定名称的图层
layer_names = [layer.Name for layer in active_document.ActivePage.Layers
if layer.Name not in ["辅助线", "1", "0", "Defpoints"]]
p_numbers = [] # 初始化存储 P 数字的列表
for code, quantity in code_quantities.items():
for i in range(1, single_code_pieces + 1):
p_numbers.extend([f"P{i}"] * int(quantity))
print(p_numbers)
# 循环遍历不同的码
# p_numbers = [] # 初始化存储 P 数字的列表
#
# for code, quantity in code_quantities.items():
# for i in range(1, single_code_pieces + 1):
# # 根据码的数量分别生成对应数量的 P 数字,并添加到列表中
# p_numbers.extend([f"P{i}"] * quantity)
# print(p_numbers)
new_layer_names = []
index = 0
for old_name in layer_names:
parts = old_name.split("-") # 根据"-"分割字符串
if len(parts) > 1:
new_name = f"{p_numbers[index]}-{parts[1]}-{results2[index]}" # 使用数组中的 P 数字
new_layer_names.append(new_name)
index += 1
# 在新的图层名数组中遍历,对图层进行修改
modified_names = [] # 创建一个列表来存储修改后的名称
modified_names2 = []
for i, new_name in enumerate(new_layer_names):
active_document.ActivePage.Layers(layer_names[i]).Name = new_name
modified_names2.append(new_name)
modified_names.append(new_name)
print(f"Modified: {layer_names[i]} -> {new_name}")
dogms = win32com.client.DispatchEx("CorelDRAW.Application.23")
#
dogms.GMSManager.RunMacro("RUNDXF", "RUN.RUNPDF")
#
dogms.GMSManager.RunMacro("RUNDXF", "RUN.ActiveDocumentClose")
###############debug
# print(modified_names)
######### ######### ######### ######### #########这里是对多码的功能 这里是删除的功能
# def delete_layers_by_names(names_to_delete, active_document):
# corel_app = win32com.client.Dispatch("CorelDRAW.Application.23")
#
# # 获取当前活动文档
# active_document = corel_app.ActiveDocument
# for target_layer_name in names_to_delete:
# for layer in active_document.ActivePage.Layers:
# if layer.Name == target_layer_name:
# layer.Delete()
# break # 找到目标图层后中断循环
#
#
#
# modified_names_list = [] # 用于存储每次循环中的 modified_names 列表
# ######### ######### ######### ######### #########这里是对多码的功能 这里是导出PSD的功能 我这边要混排就对功能进行封禁
# Index = 0
# for code in code_quantities:
# quantity = code_quantities[code]
# total_pieces = quantity * single_code_pieces
#
# # 获取数组的前 total_pieces 个元素
# newmodified_names_filtered = modified_names[:total_pieces]
# # print(newmodified_names_filtered)
#
# result_array = [fruit for fruit in modified_names2 if fruit not in newmodified_names_filtered]
# result_array_length = len(result_array)
# print(result_array_length)
#
# delete_layers_by_names(result_array, active_document.ActivePage)
# # modified_names_list.append(modified_names) # 将 modified_names 添加到数组中
#
#
# dogms = win32com.client.DispatchEx("CorelDRAW.Application.23")
#
# dogms.GMSManager.RunMacro("RUNDXF", "RUN.ExportSelectionToPSD", Index)
#
# dogms.GMSManager.RunMacro("RUNDXF", "RUN.HOURUN", result_array_length)
#
# modified_names = modified_names[total_pieces:]
#
# Index += 1
#
#
# corel_app = win32com.client.Dispatch("CorelDRAW.Application.23")
#
# corel_app .GMSManager.RunMacro("RUNDXF", "RUN.ActiveDocumentClose")
#
# QMessageBox.warning(self, "提醒", "分割完成,请进行裁片套版操作。")
def updateLineEditsFromSizes(self):
for size_label, line_edit in self.dxfLineEdits.items():
self.sizes[size_label] = line_edit.text()
def populateScrollArea(self):
self.clearScrollArea()
for size_label, size_text in self.sizes.items():
size_layout = QHBoxLayout()
size_layout.addWidget(QLabel(size_label))
line_edit = QLineEdit()
line_edit.setValidator(QIntValidator())
line_edit.setFixedSize(100, 30)
line_edit.setText(size_text)
self.dxfLineEdits[size_label] = line_edit
size_layout.addWidget(line_edit)
self.scrollWidgetLayout.addLayout(size_layout)
def clearScrollArea(self):
for i in reversed(range(self.scrollWidgetLayout.count())):
item = self.scrollWidgetLayout.itemAt(i)
if isinstance(item, QHBoxLayout) or isinstance(item, QVBoxLayout):
while item.count():
widget = item.takeAt(0).widget()
if widget:
widget.deleteLater()
self.dxfLineEdits.clear() # 清空部件引用
if __name__ == '__main__':
app = QApplication(sys.argv)
mainWindow = YourMainWindow5()
mainWindow.show()
sys.exit(app.exec_())

View File

@@ -0,0 +1,226 @@
import json
import os
#import replicate
import requests
from PyQt5 import QtWidgets, QtGui, QtCore
# from dotenv import load_dotenv
import os
# load_dotenv() # 加载 .env 文件中的环境变量
url = 'http://43.139.183.222:5000'
class ImportPDFDialog6(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.processed_image_path = None # 存储处理后的图片路径
def initUI(self):
self.setWindowTitle('图像处理参数输入')
# 布局
layout = QtWidgets.QVBoxLayout()
# 文件选择行
self.file_input = QtWidgets.QLineEdit(self)
# self.file_input.setPlaceholderText("选择文件路径 (如 1.jpg)")
layout.addWidget(self.file_input)
self.browse_button = QtWidgets.QPushButton('浏览', self)
self.browse_button.clicked.connect(self.browse_file)
layout.addWidget(self.browse_button)
self.prompt_input = QtWidgets.QLineEdit(self)
self.prompt_input.setPlaceholderText("提示词 ")
layout.addWidget(self.prompt_input)
self.creativity_input = QtWidgets.QDoubleSpinBox(self)
self.creativity_input.setRange(0, 1)
self.creativity_input.setSingleStep(0.01)
self.creativity_input.setValue(0.35) # 默认值
layout.addWidget(QtWidgets.QLabel("创造力 (0 - 3):"))
layout.addWidget(self.creativity_input)
self.resemblance_input = QtWidgets.QDoubleSpinBox(self)
self.resemblance_input.setRange(0, 3)
self.resemblance_input.setSingleStep(0.01)
self.resemblance_input.setValue(0.6) # 默认值
layout.addWidget(QtWidgets.QLabel("相似度 (0 - 3):"))
layout.addWidget(self.resemblance_input)
self.scale_factor_input = QtWidgets.QSpinBox(self)
self.scale_factor_input.setRange(2, 8)
self.scale_factor_input.setValue(2) # 默认值
layout.addWidget(QtWidgets.QLabel("放大倍数:"))
layout.addWidget(self.scale_factor_input)
self.submit_button = QtWidgets.QPushButton('提交', self)
self.submit_button.clicked.connect(self.submit)
layout.addWidget(self.submit_button)
# 合并图片显示和处理结果区域
self.output_area = QtWidgets.QVBoxLayout()
self.image_label = QtWidgets.QLabel(self)
# self.image_label.setText("加载的图片将显示在这里")
self.image_label.setAlignment(QtCore.Qt.AlignCenter)
self.output_area.addWidget(self.image_label)
self.result_display = QtWidgets.QTextEdit(self)
self.result_display.setReadOnly(True)
self.output_area.addWidget(QtWidgets.QLabel("处理结果:"))
self.output_area.addWidget(self.result_display)
layout.addLayout(self.output_area)
# 下载按钮
self.download_button = QtWidgets.QPushButton('下载', self)
self.download_button.clicked.connect(self.download_image)
self.download_button.setEnabled(False) # 初始不可用
layout.addWidget(self.download_button)
self.setLayout(layout)
def browse_file(self):
options = QtWidgets.QFileDialog.Options()
file_name, _ = QtWidgets.QFileDialog.getOpenFileName(self, "选择图像文件", "", "Image Files (*.png *.jpg *.jpeg *.bmp)", options=options)
if file_name:
self.file_input.setText(file_name)
self.display_image(file_name) # 显示选中的图片
def display_image(self, file_path):
pixmap = QtGui.QPixmap(file_path)
self.image_label.setPixmap(pixmap.scaled(300, 300, QtCore.Qt.KeepAspectRatio))
def upload_image(self, url, file_path, json_data):
imgurl = url + "/upload"
with open(file_path, 'rb') as file:
files = {'file': file}
data = {'data': json.dumps(json_data)} # 将 JSON 数据转换为字符串
response = requests.post(imgurl, files=files, data=data)
print(response.status_code)
response_json = response.json() # 先解析 JSON 响应
print(response_json) # 打印响应内容以检查其格式
# 确保响应是一个列表,并且至少有一个元素
if isinstance(response_json, list) and len(response_json) > 0:
self.processed_image_path = response_json[0] # 从列表中获取 URL
else:
# 处理不符合预期的情况
print("Unexpected response format:", response_json)
return
self.result_display.setPlainText(str(response_json)) # 显示处理结果
self.download_button.setEnabled(True) # 启用下载按钮
self.display_processed_image() # 显示处理后的图片
def submit(self):
try:
creativity = self.creativity_input.value()
file_path = self.file_input.text()
urlprompt = self.prompt_input.text() + " best quality, highres, <lora:more_details:0.5> <lora:SDXLrender_v2.0:1>"
prompt = urlprompt
resemblance = self.resemblance_input.value()
scale_factor = self.scale_factor_input.value()
if not os.path.exists(file_path):
raise ValueError("文件不存在,请选择有效的文件。")
input_data = {
"seed": 1337,
"prompt": prompt,
"dynamic": 6,
"handfix": "disabled",
"pattern": False,
"sharpen": 0,
"sd_model": "juggernaut_reborn.safetensors [338b85bc4f]",
"scheduler": "DPM++ 3M SDE Karras",
"creativity": creativity,
"lora_links": "",
"downscaling": False,
"resemblance": resemblance,
"scale_factor": scale_factor,
"tiling_width": 112,
"tiling_height": 144,
"output_format": "png",
"custom_sd_model": "",
"negative_prompt": "(worst quality, low quality, normal quality:2) JuggernautNegative-neg",
"num_inference_steps": 18,
"downscaling_resolution": 768
}
# 确保调用时只传递三个参数
self.upload_image(url, file_path, input_data)
except Exception as e:
QtWidgets.QMessageBox.critical(self, "错误", str(e))
def run(url):
newurl = url + "/data"
data = {'key': 'value'} # 你要发送的数据
response = requests.post(newurl, json=data)
print(response.status_code) # 打印状态码
print(response.json()) # 打印返回的 JSON 数据
def run2(url):
newurl = url + "/dataimg"
data = {'key': 'value'} # 你要发送的数据
response = requests.post(newurl, json=data)
print(response.status_code) # 打印状态码
print(response.json()) # 打印返回的 JSON 数据
# if __name__ == '__main__':
# upload_image(url, '1.jpg', {'key': 'value'})
def display_processed_image(self):
# 下载并显示处理后的图片
response = requests.get(self.processed_image_path)
if response.status_code == 200:
pixmap = QtGui.QPixmap()
pixmap.loadFromData(response.content)
self.image_label.setPixmap(pixmap.scaled(300, 300, QtCore.Qt.KeepAspectRatio))
def download_image(self):
if self.processed_image_path:
image_url = self.processed_image_path
file_name, _ = QtWidgets.QFileDialog.getSaveFileName(self, "保存处理后的图片", "", "Image Files (*.png *.jpg *.jpeg)")
if file_name:
try:
response = requests.get(image_url)
response.raise_for_status() # 检查请求是否成功
with open(file_name, 'wb') as f:
f.write(response.content)
QtWidgets.QMessageBox.information(self, "成功", "图片下载完成!")
except requests.exceptions.RequestException as e:
QtWidgets.QMessageBox.critical(self, "错误", f"下载失败:{e}")
except Exception as e:
QtWidgets.QMessageBox.critical(self, "错误", f"文件保存失败:{e}")
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
# 创建并显示登录界面
# login_dialog = LoginDialog()
# if login_dialog.exec_() == QtWidgets.QDialog.Accepted:
# 只有在登录成功后才执行以下代码
ex = ImportPDFDialog6()
ex.show()
sys.exit(app.exec_())

View File

@@ -0,0 +1,33 @@
import os
import threading
def clear_folder_contents(folder_paths):
def clear_folder(folder_path):
try:
for filename in os.listdir(folder_path):
file_path = os.path.join(folder_path, filename)
try:
if os.path.isfile(file_path):
os.unlink(file_path)
elif os.path.isdir(file_path):
clear_folder(file_path)
os.rmdir(file_path)
except Exception as e:
print(f"无法删除 {file_path}: {e}")
except Exception as e:
print(f"无法列出文件夹内容 {folder_path}: {e}")
for folder_path in folder_paths:
thread = threading.Thread(target=clear_folder, args=(folder_path,))
thread.start()
def another_function():
folder1_to_clear = "D:\PSMarktemp"
#folder2_to_clear = "D:\MarkTemp\DXFmarktemp"
folder3_to_clear = "D:\markTemp"
folders_to_clear = [folder1_to_clear,folder3_to_clear]
clear_folder_contents(folders_to_clear)
# 在另一个函数中执行清空两个文件夹内容的操作
# folder_paths = [r"D:\MarkTemp\PSMarktemp", r"D:\MarkTemp\marktemp", r"D:\MarkTemp\marktemp"]

View File

@@ -0,0 +1,7 @@
import psutil
def is_coreldraw_running():
for process in psutil.process_iter(['pid', 'name']):
if process.info['name'] == "CorelDRW.exe":
return True
return False

View File

@@ -0,0 +1,31 @@
import os
import threading
def clear_folder_contents(folder_paths):
def clear_folder(folder_path):
try:
for filename in os.listdir(folder_path):
file_path = os.path.join(folder_path, filename)
try:
if os.path.isfile(file_path):
os.unlink(file_path)
elif os.path.isdir(file_path):
clear_folder(file_path)
os.rmdir(file_path)
except Exception as e:
print(f"无法删除 {file_path}: {e}")
except Exception as e:
print(f"无法列出文件夹内容 {folder_path}: {e}")
for folder_path in folder_paths:
thread = threading.Thread(target=clear_folder, args=(folder_path,))
thread.start()
def another_function():
folder1_to_clear = "D:\PSMARKtemp"
folder2_to_clear = "D:\marktemp"
folders_to_clear = [folder1_to_clear, folder2_to_clear]
clear_folder_contents(folders_to_clear)
# 在另一个函数中执行清空两个文件夹内容的操作

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

View File

@@ -0,0 +1,51 @@
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(
['main.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='main',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=['newapp.ico'],
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='main',
)

View File

@@ -0,0 +1,86 @@
from os.path import join, dirname, abspath
from qtpy.QtGui import QPalette, QColor
import qtpy, platform
QT_VERSION = tuple((int(v) for v in qtpy.QT_VERSION.split('.')))
PLATFORM = platform.system()
_STYLESHEET = 'black.css'
def _apply_base_theme(app):
""" Apply base theme to the application.
Args:
app (QApplication): QApplication instance.
"""
if QT_VERSION < (5, ):
app.setStyle('plastique')
else:
app.setStyle('Fusion')
with open(_STYLESHEET) as stylesheet:
app.setStyleSheet(stylesheet.read())
def dark(app):
""" Apply Dark Theme to the Qt application instance.
Args:
app (QApplication): QApplication instance.
"""
darkPalette = QPalette()
darkPalette.setColor(QPalette.WindowText, QColor(180, 180, 180))
darkPalette.setColor(QPalette.Button, QColor(53, 53, 53))
darkPalette.setColor(QPalette.Light, QColor(180, 180, 180))
darkPalette.setColor(QPalette.Midlight, QColor(90, 90, 90))
darkPalette.setColor(QPalette.Dark, QColor(35, 35, 35))
darkPalette.setColor(QPalette.Text, QColor(180, 180, 180))
darkPalette.setColor(QPalette.BrightText, QColor(180, 180, 180))
darkPalette.setColor(QPalette.ButtonText, QColor(180, 180, 180))
darkPalette.setColor(QPalette.Base, QColor(42, 42, 42))
darkPalette.setColor(QPalette.Window, QColor(53, 53, 53))
darkPalette.setColor(QPalette.Shadow, QColor(20, 20, 20))
darkPalette.setColor(QPalette.Highlight, QColor(42, 130, 218))
darkPalette.setColor(QPalette.HighlightedText, QColor(180, 180, 180))
darkPalette.setColor(QPalette.Link, QColor(56, 252, 196))
darkPalette.setColor(QPalette.AlternateBase, QColor(66, 66, 66))
darkPalette.setColor(QPalette.ToolTipBase, QColor(53, 53, 53))
darkPalette.setColor(QPalette.ToolTipText, QColor(180, 180, 180))
darkPalette.setColor(QPalette.Disabled, QPalette.WindowText, QColor(127, 127, 127))
darkPalette.setColor(QPalette.Disabled, QPalette.Text, QColor(127, 127, 127))
darkPalette.setColor(QPalette.Disabled, QPalette.ButtonText, QColor(127, 127, 127))
darkPalette.setColor(QPalette.Disabled, QPalette.Highlight, QColor(80, 80, 80))
darkPalette.setColor(QPalette.Disabled, QPalette.HighlightedText, QColor(127, 127, 127))
app.setPalette(darkPalette)
_apply_base_theme(app)
def light(app):
""" Apply Light Theme to the Qt application instance.
Args:
app (QApplication): QApplication instance.
"""
lightPalette = QPalette()
lightPalette.setColor(QPalette.WindowText, QColor(0, 0, 0))
lightPalette.setColor(QPalette.Button, QColor(240, 240, 240))
lightPalette.setColor(QPalette.Light, QColor(180, 180, 180))
lightPalette.setColor(QPalette.Midlight, QColor(200, 200, 200))
lightPalette.setColor(QPalette.Dark, QColor(225, 225, 225))
lightPalette.setColor(QPalette.Text, QColor(0, 0, 0))
lightPalette.setColor(QPalette.BrightText, QColor(0, 0, 0))
lightPalette.setColor(QPalette.ButtonText, QColor(0, 0, 0))
lightPalette.setColor(QPalette.Base, QColor(237, 237, 237))
lightPalette.setColor(QPalette.Window, QColor(240, 240, 240))
lightPalette.setColor(QPalette.Shadow, QColor(20, 20, 20))
lightPalette.setColor(QPalette.Highlight, QColor(76, 163, 224))
lightPalette.setColor(QPalette.HighlightedText, QColor(0, 0, 0))
lightPalette.setColor(QPalette.Link, QColor(0, 162, 232))
lightPalette.setColor(QPalette.AlternateBase, QColor(225, 225, 225))
lightPalette.setColor(QPalette.ToolTipBase, QColor(240, 240, 240))
lightPalette.setColor(QPalette.ToolTipText, QColor(0, 0, 0))
lightPalette.setColor(QPalette.Disabled, QPalette.WindowText, QColor(115, 115, 115))
lightPalette.setColor(QPalette.Disabled, QPalette.Text, QColor(115, 115, 115))
lightPalette.setColor(QPalette.Disabled, QPalette.ButtonText, QColor(115, 115, 115))
lightPalette.setColor(QPalette.Disabled, QPalette.Highlight, QColor(190, 190, 190))
lightPalette.setColor(QPalette.Disabled, QPalette.HighlightedText, QColor(115, 115, 115))
app.setPalette(lightPalette)
_apply_base_theme(app)

View File

@@ -0,0 +1,159 @@
from os.path import join, dirname, abspath
from qtpy.QtCore import Qt, QMetaObject, Signal, Slot, QEvent
from qtpy.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QToolButton, QLabel, QSizePolicy
from PyQt5.QtGui import QIcon, QPixmap
import qtpy, platform
QT_VERSION = tuple((int(v) for v in qtpy.QT_VERSION.split('.')))
PLATFORM = platform.system()
_FL_STYLESHEET = 'frameless.qss'
class WindowDragger(QWidget):
'''Window dragger.
Args:
window (QWidget): Associated window.
parent (QWidget, optional): Parent widget.
'''
doubleClicked = Signal()
def __init__(self, window, parent=None):
QWidget.__init__(self, parent)
self._window = window
self._mousePressed = False
def mousePressEvent(self, event):
self._mousePressed = True
self._mousePos = event.globalPos()
self._windowPos = self._window.pos()
def mouseMoveEvent(self, event):
if self._mousePressed:
self._window.move(self._windowPos + (event.globalPos() - self._mousePos))
def mouseReleaseEvent(self, event):
self._mousePressed = False
def mouseDoubleClickEvent(self, event):
self.doubleClicked.emit()
class ModernWindow(QWidget):
'''
Modern window.
Args:
w (QWidget): Main widget.
parent (QWidget, optional): Parent widget.
'''
def __init__(self, w, parent=None):
QWidget.__init__(self, parent)
self._w = w
self.setupUi()
contentLayout = QHBoxLayout()
contentLayout.setContentsMargins(0, 0, 0, 0)
contentLayout.addWidget(w)
self.windowContent.setLayout(contentLayout)
self.setWindowTitle(w.windowTitle())
self.setGeometry(w.geometry())
self._w.setAttribute(Qt.WA_DeleteOnClose, True)
self._w.destroyed.connect(self._ModernWindow__child_was_closed)
def setupUi(self):
self.vboxWindow = QVBoxLayout(self)
self.vboxWindow.setContentsMargins(0, 0, 0, 0)
self.windowFrame = QWidget(self)
self.windowFrame.setObjectName('windowFrame')
self.vboxFrame = QVBoxLayout(self.windowFrame)
self.vboxFrame.setContentsMargins(0, 0, 0, 0)
self.titleBar = WindowDragger(self, self.windowFrame)
self.titleBar.setObjectName('titleBar')
self.titleBar.setSizePolicy(QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed))
self.hboxTitle = QHBoxLayout(self.titleBar)
self.hboxTitle.setContentsMargins(0, 0, 0, 0)
self.hboxTitle.setSpacing(0)
titleIcon = QPixmap('./ui/icon.png')
Icon = QLabel()
Icon.setPixmap(titleIcon.scaled(30, 30))
self.icon = Icon
self.icon.setAlignment(Qt.AlignLeft)
self.lblTitle = QLabel('Title')
self.lblTitle.setObjectName('lblTitle')
self.lblTitle.setAlignment(Qt.AlignVCenter)
spButtons = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
self.btnMinimize = QToolButton(self.titleBar)
self.btnMinimize.setObjectName('btnMinimize')
self.btnMinimize.setSizePolicy(spButtons)
self.btnRestore = QToolButton(self.titleBar)
self.btnRestore.setObjectName('btnRestore')
self.btnRestore.setSizePolicy(spButtons)
self.btnRestore.setVisible(False)
self.btnMaximize = QToolButton(self.titleBar)
self.btnMaximize.setObjectName('btnMaximize')
self.btnMaximize.setSizePolicy(spButtons)
self.btnClose = QToolButton(self.titleBar)
self.btnClose.setObjectName('btnClose')
self.btnClose.setSizePolicy(spButtons)
self.vboxFrame.addWidget(self.titleBar)
self.windowContent = QWidget(self.windowFrame)
self.vboxFrame.addWidget(self.windowContent)
self.vboxWindow.addWidget(self.windowFrame)
PLATFORM = '1Darwin'
if PLATFORM == 'Darwin':
self.hboxTitle.addWidget(self.btnClose)
self.hboxTitle.addWidget(self.btnMinimize)
self.hboxTitle.addWidget(self.btnRestore)
self.hboxTitle.addWidget(self.btnMaximize)
self.hboxTitle.addWidget(self.lblTitle)
else:
self.hboxTitle.addWidget(self.icon)
self.hboxTitle.addWidget(self.lblTitle)
self.hboxTitle.addWidget(self.btnMinimize)
self.hboxTitle.addWidget(self.btnRestore)
self.hboxTitle.addWidget(self.btnMaximize)
self.hboxTitle.addWidget(self.btnClose)
self.setWindowFlags(Qt.Window | Qt.FramelessWindowHint | Qt.WindowSystemMenuHint)
with open(_FL_STYLESHEET) as (stylesheet):
self.setStyleSheet(stylesheet.read())
QMetaObject.connectSlotsByName(self)
def __child_was_closed(self):
self._w = None
self.close()
def closeEvent(self, event):
if not self._w:
event.accept()
else:
self._w.close()
event.setAccepted(self._w.isHidden())
def setWindowTitle(self, title):
super(ModernWindow, self).setWindowTitle(title)
self.lblTitle.setText(title)
@Slot()
def on_btnMinimize_clicked(self):
self.setWindowState(Qt.WindowMinimized)
@Slot()
def on_btnRestore_clicked(self):
self.btnRestore.setVisible(False)
self.btnMaximize.setVisible(True)
self.setWindowState(Qt.WindowNoState)
@Slot()
def on_btnMaximize_clicked(self):
self.btnRestore.setVisible(True)
self.btnMaximize.setVisible(False)
self.setWindowState(Qt.WindowMaximized)
@Slot()
def on_btnClose_clicked(self):
self.close()
@Slot()
def on_titleBar_doubleClicked(self):
if self.btnMaximize.isVisible():
self.on_btnMaximize_clicked()
else:
self.on_btnRestore_clicked()

View File

@@ -0,0 +1,351 @@
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QTabWidget, QPushButton, QLabel, QVBoxLayout, QWidget, QHBoxLayout, QFrame, QMessageBox
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import Qt
from Tab1 import ImportPDFDialog
from Tab2 import YourMainWindow
from Tab3 import ImportPDFDialog2
from Tab6 import ImportPDFDialog6
from Tab4 import ImportPDFDialog4
from test5 import YourMainWindow9
import qdarktheme
import sys
import subprocess
import re
import hashlib
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QGroupBox, \
QSpacerItem, QSizePolicy, QMessageBox
import pymysql
import piece_decorative
import configparser
def exception_hook(exctype, value, traceback):
# Handle the uncaught exception
# 处理未捕获的异常
QMessageBox.warning(None, "错误", f"发生了未知的异常:{value}")
# class LoginDialog(QWidget):
# def __init__(self):
# super().__init__()
# self.setWindowTitle("PSMARK登录界面")
# self.setWindowIcon(QIcon("icons/newapp.ico")) # 设置窗口小图标,替换为您的图标文件路径
#
# self.resize(300, 200)
#
# 主布局 = QVBoxLayout()
#
# group1 = QGroupBox("登录验证")
# group1_layout = QVBoxLayout()
#
# group2 = QHBoxLayout()
# label1 = QLabel("用户名")
# self.edit1 = QLineEdit()
# self.edit1.setFixedWidth(200)
# spacer1 = QSpacerItem(40, 10, QSizePolicy.Expanding, QSizePolicy.Minimum)
# group2.addWidget(label1)
# group2.addItem(spacer1)
# group2.addWidget(self.edit1)
#
# group3 = QHBoxLayout()
# label2 = QLabel("密码")
# self.edit2 = QLineEdit()
# self.edit2.setFixedWidth(200)
# self.edit2.setEchoMode(QLineEdit.Password) # 设置密码输入框为密文
# spacer2 = QSpacerItem(40, 10, QSizePolicy.Expanding, QSizePolicy.Minimum)
# group3.addWidget(label2)
# group3.addItem(spacer2)
# group3.addWidget(self.edit2)
#
#
#
# group4 = QHBoxLayout()
# button1 = QPushButton("登录")
# button2 = QPushButton("注册")
# group4.addWidget(button1)
# group4.addWidget(button2)
#
# group1_layout.addLayout(group2)
# group1_layout.addLayout(group3)
# group1_layout.addLayout(group4)
# group1.setLayout(group1_layout)
#
# 主布局.addWidget(group1)
#
# group5 = QHBoxLayout()
# label3 = QLabel("机器码")
# self.edit3 = QLineEdit()
# self.edit3.setFixedWidth(200)
#
# # 获取主板序列号并提取数字部分
# try:
# result = subprocess.run(['wmic', 'baseboard', 'get', 'serialnumber'], stdout=subprocess.PIPE,
# stderr=subprocess.PIPE, text=True)
# motherboard_serial = result.stdout.strip()
#
# # 使用正则表达式提取数字
# motherboard_serial = re.sub(r'\D', '', motherboard_serial)
#
# # 使用SHA-256加密特征码
# feature_code = hashlib.sha256(motherboard_serial.encode()).hexdigest()
#
# # 去掉特征码中的英文字符
# feature_code = re.sub(r'[a-zA-Z]', '', feature_code)
# except Exception as e:
# feature_code = "Error: " + str(e)
#
# self.edit3.setText(feature_code) # 将加密后的特征码设置为 "特征码" 输入框的文本
# self.rem_user()
#
# spacer3 = QSpacerItem(10, 10, QSizePolicy.Fixed, QSizePolicy.Minimum)
# group5.addWidget(label3)
# group5.addItem(spacer3)
# group5.addWidget(self.edit3)
#
# 主布局.addLayout(group5)
#
# self.setLayout(主布局)
#
# # 链接登录的点击事件
# button1.clicked.connect(self.slot_login)
# # 连接注册按钮的点击事件
# button2.clicked.connect(self.show_warning_message)
#
# #记住密码
# def rem_user(self):
#
# piece_decorative.config = configparser.ConfigParser()
# piece_decorative.config.read('config.ini', encoding='utf-8')
# piece_decorative.PSname = piece_decorative.config.get('程序配置', 'PSname')
# self.window = MainWindow()
# self.window.show()
# self.close()
# return
#
# code = self.edit3.text()
# # 执行SQL语句从user数据表中查询字段值
# cur.execute(f"SELECT username,password,code FROM {User}")
# # 将数据库查询的结果保存在result中
# result = cur.fetchall()
# code_list = [it[2] for it in result] # 从数据库查询的result中遍历查询元组中第3个元素code
# if code in code_list:
# user_name = result[code_list.index(code)][0]
# user_password = result[code_list.index(code)][1]
# self.edit1.setText(user_name)
# self.edit2.setText(user_password)
# else:
# pass
#
# def slot_login(self):
# user_name = self.edit1.text()
# user_password = self.edit2.text()
# code = self.edit3.text()
# # print(user_name,user_password)
# # 执行SQL语句从user数据表中查询code和time字段值
# cur.execute(f"SELECT username,password,code FROM {User}")
# # 将数据库查询的结果保存在result中
# result = cur.fetchall()
# name_list = [it[0] for it in result] # 从数据库查询的result中遍历查询元组中第一个元素name
# # 判断用户名或密码不能为空
# if not (user_name and user_password):
# QMessageBox.critical(self, "错误", "用户名或密码不能为空!")
# # 判断用户名和密码是否匹配
# elif user_name in name_list:
# if user_password == result[name_list.index(user_name)][1]:
# if code == result[name_list.index(user_name)][2]:
# piece_decorative.config = configparser.ConfigParser()
# piece_decorative.config.read('config.ini', encoding='utf-8')
# piece_decorative.PSname = piece_decorative.config.get('程序配置', 'PSname')
# self.window = MainWindow()
# self.window.show()
# self.close()
# else:
# QMessageBox.critical(self, "错误", "机器码不匹配!")
# # QMessageBox.information(self, "欢迎您", "登录成功!\n在此添加新界面")
# else:
# QMessageBox.critical(self, "错误", "密码输入错误!")
# # 账号不在数据库中,则弹出是否注册的框
# else:
# QMessageBox.critical(self, "错误", "该账号不存在,请注册!")
#
# def show_warning_message(self):
# # 弹出警告消息框
# QMessageBox.critical(self, "错误", "请联系管理员 17520145271")
# # warning_message = QMessageBox()
# # warning_message.setIcon(QMessageBox.Warning)
# # warning_message.setWindowTitle("警告")
# # warning_message.setText("请联系管理员 17520145271")
# # warning_message.exec_()
#################################自定义tab标签#################################
from PyQt5 import QtGui, QtCore, QtWidgets
class MyTabBar(QtWidgets.QTabBar):
def paintEvent(self, event):
painter = QtWidgets.QStylePainter(self)
option = QtWidgets.QStyleOptionTab()
for index in range(self.count()):
self.initStyleOption(option, index)
painter.drawControl(QtWidgets.QStyle.CE_TabBarTabShape, option)
painter.drawText(
self.tabRect(index),
QtCore.Qt.AlignCenter | QtCore.Qt.TextDontClip,
"\n".join(self.tabText(index)))
def tabSizeHint(self, index):
#size = QtWidgets.QTabBar.tabSizeHint(self, index)
width = max([QtWidgets.QTabBar.fontMetrics(self).width(text) for text in self.tabText(index)])
height = len(self.tabText(index)) * QtWidgets.QTabBar.fontMetrics(self).lineSpacing()
return QtCore.QSize(width + 12, height + 12)
class TabWidget(QtWidgets.QTabWidget):
def __init__(self, parent=None):
QtWidgets.QTabWidget.__init__(self, parent)
self.setTabBar(MyTabBar())
#################################自定义tab标签#################################
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("PS Mark")
self.setWindowIcon(QIcon("icons/newapp.ico")) # 设置窗口小图标,替换为您的图标文件路径
main_widget = QWidget()
self.setCentralWidget(main_widget)
main_layout = QVBoxLayout()
main_widget.setLayout(main_layout)
tab_widget = TabWidget()
tab_widget.setTabPosition(TabWidget.West)
#tab_widget.tabBar().setStyle(TabBarStyle(Qt.Vertical))
main_layout.addWidget(tab_widget)
tab2 = YourMainWindow()
tab_widget.addTab(tab2, "纸样分割")
tab1 = ImportPDFDialog()
tab_widget.addTab(tab1, "裁片套版")
#
#
tab3 = ImportPDFDialog2()
tab_widget.addTab(tab3, "快速换图")
# tab6 = ImportPDFDialog6()
# tab_widget.addTab(tab6, "ai重绘")
#
# tab3 = YourMainWindow9()
# tab_widget.addTab(tab3, "快速换图")
# tab4 = ImportPDFDialog4()
# tab_widget.addTab(tab4, "版本介绍")
#
# separator = QFrame()
# separator.setFrameShape(QFrame.HLine)
# separator.setFrameShadow(QFrame.Sunken)
# main_layout.addWidget(separator)
#custom_label = QLabel("尊敬的公司内部体验版用户(1.8.5),欢迎使用!!!")
#############8-12号修复了宽高缩放跟比例缩放超过10个裁片就会定位不准的bug
#############8-12号将缩水值跟前缀添加改为了前置条件
#############8-24号新增Tab2界面 将DXF解析与CDR结合在一起 可以实现分段排版
# main_layout.addWidget(custom_label)
self.setLayout(main_layout)
# # self.setWindowTitle("分割线和自定义文字示例")
# settings_button = QPushButton(QIcon("icons/转换.png"), "")
# settings_button.setObjectName("settingsButton") # 设置对象名称以供样式表选择
# settings_button.setStyleSheet("border: none;") # 设置按钮无边框
# main_layout.addWidget(settings_button, alignment=Qt.AlignLeft)
# 设置窗口始终置顶
self.setWindowFlags(Qt.WindowStaysOnTopHint)
self.width_ratio = 0.3
self.height_ratio = 1
self.initial_width = self.width()
self.initial_height = self.height()
# def resizeEvent(self, event):
# super().resizeEvent(event)
#
# # 获取当前窗口的宽度和高度
# current_width = self.width()
# current_height = self.height()
#
# # 判断当前窗口是否超过了初始大小
# if current_width > self.initial_width or current_height > self.initial_height:
# # 允许窗口继续拉伸,不设置最小宽度和最小高度
# pass
# else:
# # 重新设置最小宽度和最小高度为初始大小
# self.setMinimumWidth(self.initial_width)
# self.setMinimumHeight(self.initial_height)
def resizeEvent(self, event):
super().resizeEvent(event)
# 获取当前窗口的宽度和高度
current_width = self.width()
current_height = self.height()
# 如果是第一次调整大小,更新初始大小
if not hasattr(self, 'initial_size_set') or not self.initial_size_set:
self.initial_width = current_width
self.initial_height = current_height
self.initial_size_set = True # 标记初始大小已被设置
# 判断当前窗口是否超过了初始大小
if current_width > self.initial_width or current_height > self.initial_height:
# 允许窗口继续拉伸,不设置最小宽度和最小高度
pass
else:
# 重新设置最小宽度和最小高度为初始大小
self.setMinimumWidth(self.initial_width)
self.setMinimumHeight(self.initial_height)
if __name__ == '__main__':
host = "rm-bp1s36ps814qp23b7uo.mysql.rds.aliyuncs.com"
user = "zw1847930177"
password = "Zuowei1216"
database = "program"
charset = "utf8"
port = 3306
db = pymysql.connect(host=host, user=user, password=password, database=database, charset=charset, port=port)
cur = db.cursor()
user_creat ="""
CREATE TABLE IF NOT EXISTS User(
`id` INT auto_increment PRIMARY KEY,
`username` varchar(255) DEFAULT '',
`password` varchar(255) DEFAULT '',
`code` varchar(255) DEFAULT ''
) ENGINE=innodb DEFAULT CHARSET=utf8;
"""
cur.execute(user_creat)
cur = db.cursor()
User = 'User'
app3 = QApplication(sys.argv)
sys.excepthook = exception_hook # 设置全局异常处理
qdarktheme.setup_theme(
custom_colors={
"[dark]": {
"background": "#4d4d4d",
"foreground": "#ffffff",
"primary": "#ffffff",
"border": "#717070",
}
}
)
# login_dialog = LoginDialog()
# login_dialog.show()
window = MainWindow()
window.show()
sys.exit(app3.exec_())

View File

@@ -0,0 +1,51 @@
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(
['newMark.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='newMark',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=['newapp.ico'],
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='newMark',
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@@ -0,0 +1,142 @@
import sys
import subprocess
import re
import hashlib
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QGroupBox, \
QSpacerItem, QSizePolicy, QMessageBox
import pymysql
host = "rm-bp1s36ps814qp23b7uo.mysql.rds.aliyuncs.com"
user = "zw1847930177"
password = "Zuowei1216"
database = "program"
charset = "utf8"
port = 3306
db = pymysql.connect(host=host, user=user, password=password, database=database, charset=charset, port=port)
cur = db.cursor()
user_creat ="""
CREATE TABLE IF NOT EXISTS User(
`id` INT auto_increment PRIMARY KEY,
`username` varchar(255) DEFAULT '',
`password` varchar(255) DEFAULT ''
) ENGINE=innodb DEFAULT CHARSET=utf8;
"""
cur.execute(user_creat)
cur = db.cursor()
User = 'User'
class LoginDialog(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("PSMARK登录界面")
self.resize(300, 200)
主布局 = QVBoxLayout()
group1 = QGroupBox("登录验证")
group1_layout = QVBoxLayout()
group2 = QHBoxLayout()
label1 = QLabel("用户名")
self.edit1 = QLineEdit()
self.edit1.setFixedWidth(120)
spacer1 = QSpacerItem(40, 10, QSizePolicy.Expanding, QSizePolicy.Minimum)
group2.addWidget(label1)
group2.addItem(spacer1)
group2.addWidget(self.edit1)
group3 = QHBoxLayout()
label2 = QLabel("密码")
self.edit2 = QLineEdit()
self.edit2.setFixedWidth(120)
spacer2 = QSpacerItem(40, 10, QSizePolicy.Expanding, QSizePolicy.Minimum)
group3.addWidget(label2)
group3.addItem(spacer2)
group3.addWidget(self.edit2)
group4 = QHBoxLayout()
button1 = QPushButton("登录")
button2 = QPushButton("注册")
group4.addWidget(button1)
group4.addWidget(button2)
group1_layout.addLayout(group2)
group1_layout.addLayout(group3)
group1_layout.addLayout(group4)
group1.setLayout(group1_layout)
主布局.addWidget(group1)
group5 = QHBoxLayout()
label3 = QLabel("机器码")
edit3 = QLineEdit()
edit3.setFixedWidth(200)
# 获取主板序列号并提取数字部分
try:
result = subprocess.run(['wmic', 'baseboard', 'get', 'serialnumber'], stdout=subprocess.PIPE,
stderr=subprocess.PIPE, text=True)
motherboard_serial = result.stdout.strip()
# 使用正则表达式提取数字
motherboard_serial = re.sub(r'\D', '', motherboard_serial)
# 使用SHA-256加密特征码
feature_code = hashlib.sha256(motherboard_serial.encode()).hexdigest()
# 去掉特征码中的英文字符
feature_code = re.sub(r'[a-zA-Z]', '', feature_code)
except Exception as e:
feature_code = "Error: " + str(e)
edit3.setText(feature_code) # 将加密后的特征码设置为 "特征码" 输入框的文本
spacer3 = QSpacerItem(10, 10, QSizePolicy.Fixed, QSizePolicy.Minimum)
group5.addWidget(label3)
group5.addItem(spacer3)
group5.addWidget(edit3)
主布局.addLayout(group5)
self.setLayout(主布局)
# 链接登录的点击事件
button1.clicked.connect(self.slot_login)
# 连接注册按钮的点击事件
button2.clicked.connect(self.show_warning_message)
def slot_login(self):
user_name = self.edit1.text()
user_password = self.edit2.text()
# print(user_name,user_password)
# 执行SQL语句从user数据表中查询code和time字段值
cur.execute(f"SELECT username,password FROM {User}")
# 将数据库查询的结果保存在result中
result = cur.fetchall()
name_list = [it[0] for it in result] # 从数据库查询的result中遍历查询元组中第一个元素name
# 判断用户名或密码不能为空
if not (user_name and user_password):
QMessageBox.critical(self, "错误", "用户名或密码不能为空!")
# 判断用户名和密码是否匹配
elif user_name in name_list:
if user_password == result[name_list.index(user_name)][1]:
QMessageBox.information(self, "欢迎您", "登录成功!\n在此添加新界面!")
else:
QMessageBox.critical(self, "错误", "密码输入错误!")
# 账号不在数据库中,则弹出是否注册的框
else:
QMessageBox.critical(self, "错误", "该账号不存在,请注册!")
def show_warning_message(self):
# 弹出警告消息框
warning_message = QMessageBox()
warning_message.setIcon(QMessageBox.Warning)
warning_message.setWindowTitle("警告")
warning_message.setText("请联系管理员 17520145271")
warning_message.exec_()
if __name__ == "__main__":
app = QApplication(sys.argv)
login_dialog = LoginDialog()
login_dialog.show()
sys.exit(app.exec_())

View File

@@ -0,0 +1,299 @@
import configparser
from win32com.client import Dispatch
from JSX2 import dxf3_jscode
from JSX3 import dxf2_jscode
from JSX1 import dxf_jscode
from JSX4 import dxf4_jscode
from JSX5 import dxf5_jscode
from JSX6 import dxf6_jscode
from JSX7 import dxf7_jscode
from JSX8 import dxf8_jscode
from JSX9 import dxf9_jscode
from JSX10 import dxf10_jscode
from JSX11 import dxf11_jscode
from JSX12 import dxf12_jscode
from JSX13 import dxf13_jscode
from JSX14 import dxf14_jscode
from JSX15 import dxf15_jscode
from JSX16 import dxf16_jscode
from JSX17 import dxf17_jscode
from JSX18 import dxf18_jscode
from JSX19 import dxf19_jscode
from JSX20 import dxf20_jscode
from JSX21 import dxf21_jscode
from JSX22 import dxf22_jscode
from JSX23 import dxf23_jscode
from JSX24 import dxf24_jscode
from JSX25 import dxf25_jscode
from JSX26 import dxf26_jscode
from JSX27 import dxf27_jscode
psapp = None
aiapp = None
config = configparser.ConfigParser()
config.read('程序配置.ini', encoding='utf-8')
PSname = config.get('程序配置', 'ps应用名')
# from datetime import datetime # 引入datetime获取当前日期
# import sys # 引用退出程序方法
#
# ## 逻辑实现
# d1 = datetime.now().date()
# d2 = pd.to_datetime('2023-9-15').date()
#
# print("当前日期:", d1)
# print("限制日期:", d2)
#
# if d1 > d2:
# print('软件已过期,请联系作者!')
# sys.exit()
def PS_DXF_jscode_fun(funcode):
print(funcode)
global psapp
if psapp is None:
psapp = Dispatch(PSname)
res = psapp.DoJavaScript(dxf_jscode + '\n' + funcode)
return res
def PS_DXF2_jscode_fun(funcode):
print(funcode)
global psapp
if psapp is None:
psapp = Dispatch(PSname)
res = psapp.DoJavaScript(dxf2_jscode + '\n' + funcode)
return res
def PS_DXF3_jscode_fun(funcode):
print(funcode)
global psapp
if psapp is None:
psapp = Dispatch(PSname)
res = psapp.DoJavaScript(dxf3_jscode + '\n' + funcode)
return res
def PS_DXF4_jscode_fun(funcode):
print(funcode)
global psapp
if psapp is None:
psapp = Dispatch(PSname)
res = psapp.DoJavaScript(dxf4_jscode + '\n' + funcode)
return res
def PS_DXF5_jscode_fun(funcode):
print(funcode)
global psapp
if psapp is None:
psapp = Dispatch(PSname)
res = psapp.DoJavaScript(dxf5_jscode + '\n' + funcode)
return res
def PS_DXF6_jscode_fun(funcode):
print(funcode)
global psapp
if psapp is None:
psapp = Dispatch(PSname)
res = psapp.DoJavaScript(dxf6_jscode + '\n' + funcode)
return res
def PS_DXF7_jscode_fun(funcode):
print(funcode)
global psapp
if psapp is None:
psapp = Dispatch(PSname)
res = psapp.DoJavaScript(dxf7_jscode + '\n' + funcode)
return res
def PS_DXF8_jscode_fun(funcode):
print(funcode)
global psapp
if psapp is None:
psapp = Dispatch(PSname)
res = psapp.DoJavaScript(dxf8_jscode + '\n' + funcode)
return res
def PS_DXF9_jscode_fun(funcode):
print(funcode)
global psapp
if psapp is None:
psapp = Dispatch(PSname)
res = psapp.DoJavaScript(dxf9_jscode + '\n' + funcode)
return res
def PS_DXF10_jscode_fun(funcode):
print(funcode)
global psapp
if psapp is None:
psapp = Dispatch(PSname)
res = psapp.DoJavaScript(dxf10_jscode + '\n' + funcode)
return res
def PS_DXF11_jscode_fun(funcode):
print(funcode)
global psapp
if psapp is None:
psapp = Dispatch(PSname)
res = psapp.DoJavaScript(dxf11_jscode + '\n' + funcode)
return res
def PS_DXF12_jscode_fun(funcode):
print(funcode)
global psapp
if psapp is None:
psapp = Dispatch(PSname)
res = psapp.DoJavaScript(dxf12_jscode + '\n' + funcode)
return res
def PS_DXF13_jscode_fun(funcode):
print(funcode)
global psapp
if psapp is None:
psapp = Dispatch(PSname)
res = psapp.DoJavaScript(dxf13_jscode + '\n' + funcode)
return res
def PS_DXF14_jscode_fun(funcode):
print(funcode)
global psapp
if psapp is None:
psapp = Dispatch(PSname)
res = psapp.DoJavaScript(dxf14_jscode + '\n' + funcode)
return res
def PS_DXF15_jscode_fun(funcode):
print(funcode)
global psapp
if psapp is None:
psapp = Dispatch(PSname)
res = psapp.DoJavaScript(dxf15_jscode + '\n' + funcode)
return res
def PS_DXF16_jscode_fun(funcode):
print(funcode)
global psapp
if psapp is None:
psapp = Dispatch(PSname)
res = psapp.DoJavaScript(dxf16_jscode + '\n' + funcode)
return res
def PS_DXF17_jscode_fun(funcode):
print(funcode)
global psapp
if psapp is None:
psapp = Dispatch(PSname)
res = psapp.DoJavaScript(dxf17_jscode + '\n' + funcode)
return res
def PS_DXF18_jscode_fun(funcode):
print(funcode)
global psapp
if psapp is None:
psapp = Dispatch(PSname)
res = psapp.DoJavaScript(dxf18_jscode + '\n' + funcode)
return res
def PS_DXF19_jscode_fun(funcode):
print(funcode)
global psapp
if psapp is None:
psapp = Dispatch(PSname)
res = psapp.DoJavaScript(dxf19_jscode + '\n' + funcode)
return res
def PS_DXF20_jscode_fun(funcode):
print(funcode)
global psapp
if psapp is None:
psapp = Dispatch(PSname)
res = psapp.DoJavaScript(dxf20_jscode + '\n' + funcode)
return res
def PS_DXF21_jscode_fun(funcode):
print(funcode)
global psapp
if psapp is None:
psapp = Dispatch(PSname)
res = psapp.DoJavaScript(dxf21_jscode + '\n' + funcode)
return res
def PS_DXF22_jscode_fun(funcode):
print(funcode)
global psapp
if psapp is None:
psapp = Dispatch(PSname)
res = psapp.DoJavaScript(dxf22_jscode + '\n' + funcode)
return res
def PS_DXF23_jscode_fun(funcode):
print(funcode)
global psapp
if psapp is None:
psapp = Dispatch(PSname)
res = psapp.DoJavaScript(dxf23_jscode + '\n' + funcode)
return res
def PS_DXF24_jscode_fun(funcode):
print(funcode)
global psapp
if psapp is None:
psapp = Dispatch(PSname)
res = psapp.DoJavaScript(dxf24_jscode + '\n' + funcode)
return res
def PS_DXF25_jscode_fun(funcode):
print(funcode)
global psapp
if psapp is None:
psapp = Dispatch(PSname)
res = psapp.DoJavaScript(dxf25_jscode + '\n' + funcode)
return res
def PS_DXF26_jscode_fun(funcode):
print(funcode)
global psapp
if psapp is None:
psapp = Dispatch(PSname)
res = psapp.DoJavaScript(dxf26_jscode + '\n' + funcode)
return res
def PS_DXF27_jscode_fun(funcode):
print(funcode)
global psapp
if psapp is None:
psapp = Dispatch(PSname)
res = psapp.DoJavaScript(dxf27_jscode + '\n' + funcode)
return res
def main():
pass
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,55 @@
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout
import piece_decorative
class SimpleApp(QWidget):
def __init__(self):
super().__init__()
# 初始化界面
self.init_ui()
def init_ui(self):
# 创建一个按钮
btn = QPushButton('接口测试', self)
# 连接按钮的点击事件到槽函数
btn.clicked.connect(self.button_click)
# 创建一个垂直布局
layout = QVBoxLayout()
# 在布局中添加按钮
layout.addWidget(btn)
# 设置主窗口的布局
self.setLayout(layout)
# 设置主窗口的标题和大小
self.setWindowTitle('测试')
self.setGeometry(300, 300, 300, 200)
# 显示界面
self.show()
def button_click(self):
# 按钮点击时调用的槽函数
# piece_decorative.PS_DXF2_jscode_fun('设置花样组删除图层设置名称();')
# piece_decorative.PS_DXF12_jscode_fun('批量套数写入();')
piece_decorative.PS_DXF2_jscode_fun('设置花样组2();')
piece_decorative.PS_DXF19_jscode_fun('裁片抓取新的();')
piece_decorative.PS_DXF2_jscode_fun('设置花样组顺序居中();')
piece_decorative.PS_DXF_jscode_fun('信息写入();')
piece_decorative.PS_DXF3_jscode_fun('裁片视图检查2();')
print('按钮被点击了!')
if __name__ == '__main__':
# 创建应用程序对象
app = QApplication(sys.argv)
# 创建主窗口
window = SimpleApp()
# 运行应用程序的主循环
sys.exit(app.exec_())

View File

@@ -0,0 +1,324 @@
import myskin_styles, myskin_windows
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
import sys, os
import piece_decorative
import ezdxf
import ezdxf.tools
import ezdxf.bbox
import ezdxf.units
import ezdxf.math
import qdarktheme
import re
def extract_number_from_block_name(block_name):
# 使用正则表达式提取块名称中的数字部分
match = re.search(r'P(\d+)', block_name)
if match:
return int(match.group(1))
return 0 # 如果没有找到数字默认返回0
def 获取dxf中心坐标和角度列表(doc):
msp = doc.modelspace()
mspBox = ezdxf.bbox.extents(msp)
result = []
for entity in msp.query():
if entity.dxftype() == "INSERT":
result.append({"块名称": entity.dxf.name, "center": None, "rotation": None})
temp = []
rotation = None
block = doc.blocks[entity.dxf.name]
for e in block:
if e.dxftype() != "TEXT":
temp.append(e)
else:
rotation = e.dxf.all_existing_dxf_attribs()["rotation"]
# 只有 0 90 -90 -180 出现其他的就以近似值代替
if rotation < 0:
rotation += 360
if rotation < 45 or rotation > 315:
rotation = 0
elif rotation < 135:
rotation = 90
elif rotation < 225:
rotation = -180
else:
rotation = -90
result[-1]["rotation"] = rotation
center = ezdxf.bbox.extents(temp).center
# center y 改成从上往下计算
center = (center.x, mspBox.size[1] + mspBox.extmin[1] - center.y)
# 旋转
center = (mspBox.size[1] - center[1], center[0])
result[-1]["center"] = center
# 打印处理前的数据
print("处理前的数据:", result)
# 按块名称进行排序
result.sort(key=lambda x: extract_number_from_block_name(x["块名称"]))
# 打印处理后的数据
print("处理后的数据:", result)
return result
class DXFWinUi(object):
def setupUi(self, MainWindow):
# MainWindow.resize(800, 600)
self.centralwidget = QWidget(MainWindow)
mainLayout = QHBoxLayout(self.centralwidget)
groupBox = QGroupBox('多码混排')
leftLayout = QVBoxLayout(groupBox)
label1 = QLabel('大货裁片路径')
# self.label2 = QLabel('暂未选择文件夹。')
self.pushButton1 = QPushButton("选择文件夹")
self.pushButton1.setFixedSize(100,30)
leftLayout.addWidget(label1)
# leftLayout.addWidget(self.label2)
leftLayout.addWidget(self.pushButton1)
leftLayout.addStretch()
label6 = QLabel('DXF文件路径')
# self.label7 = QLabel('暂未选择文件。')
self.pushButton2 = QPushButton("选择文件")
self.pushButton2.setFixedSize(100, 30)
leftLayout.addWidget(label6)
# leftLayout.addWidget(self.label7)
leftLayout.addWidget(self.pushButton2)
leftLayout.addStretch()
label3 = QLabel('分辨率大小')
self.lineEdit1 = QLineEdit()
self.lineEdit1.setValidator(QIntValidator())
self.lineEdit1.setFixedSize(150,30)
leftLayout.addWidget(label3)
leftLayout.addWidget(self.lineEdit1)
leftLayout.addStretch()
label4 = QLabel('文档名称')
self.lineEdit2 = QLineEdit()
self.lineEdit2.setFixedSize(150,30)
leftLayout.addWidget(label4)
leftLayout.addWidget(self.lineEdit2)
leftLayout.addStretch()
label5 = QLabel('单码片数')
self.lineEdit3 = QLineEdit()
self.lineEdit3.setFixedSize(150,30)
self.lineEdit3.setValidator(QIntValidator())
leftLayout.addWidget(label5)
leftLayout.addWidget(self.lineEdit3)
leftLayout.addStretch()
# 滚动区域
scrollArea = QScrollArea()
scrollArea.setFixedSize(200,210)
scrollWidget = QWidget(scrollArea)
self.scrollWidgetLayout = QVBoxLayout(scrollWidget)
self.scrollWidgetLayout.setContentsMargins(5,5,5,5)
self.scrollWidgetLayout.setSpacing(5)
scrollArea.setWidget(scrollWidget)
scrollArea.setWidgetResizable(True)
leftLayout.addWidget(scrollArea)
leftLayout.addStretch()
rightWidget = QWidget()
# rightLayout = QVBoxLayout(rightWidget)
# self.okPushButton = QPushButton("OK")
# self.cancelPushButton = QPushButton("Cancel")
# rightLayout.addWidget(self.okPushButton)
# rightLayout.addWidget(self.cancelPushButton)
# rightLayout.addStretch()
# rightWidget.setLayout(rightLayout)
self.runButton = QPushButton("运行")
leftLayout.addWidget(self.runButton)
rightWidget = QWidget()
mainLayout.addWidget(groupBox, 1)
# mainLayout.addWidget(rightWidget, 1)
MainWindow.setCentralWidget(self.centralwidget)
class DXFWin(QMainWindow, DXFWinUi):
def __init__(self):
QMainWindow.__init__(self)
self.ui = DXFWinUi()
self.ui.setupUi(self)
self.setWindowTitle("水平布局管理例子")
desktop = QApplication.desktop()
self.move((desktop.width() - self.width()) / 2, (desktop.height() - self.height()) /2)
self.ui.pushButton1.clicked.connect(self.chooseDir)
# self.ui.cancelPushButton.clicked.connect(self.close)
self.ui.pushButton2.clicked.connect(self.chooseDxf)
# self.ui.okPushButton.clicked.connect(self.ok)
self.ui.runButton.clicked.connect(self.run)
self.dir = None
self.dxfPath = None
self.dxfLineEdits = {}
def run(self):
print("按钮被点击")
pass
def chooseDir(self):
rst = QFileDialog.getExistingDirectory()
if rst != '':
self.ui.label2.setText(rst)
self.dir = rst
def chooseDxf(self):
rst = QFileDialog.getOpenFileName(filter='dxf file(*.dxf)')
rst = rst[0]
if rst == '':
return
# 解析文件路径
# self.ui.label7.setText(rst)
self.dxfPath = rst
sizes = []
temp = ''
for c in os.path.basename(rst).split('.')[0]:
import string
if c in string.punctuation:
if temp != '':
sizes.append(temp)
temp = ''
continue
temp += c
if temp != '':
sizes.append(temp)
#清空self.ui.scrollWidgetLayout
for i in range(self.ui.scrollWidgetLayout.count()):
item = self.ui.scrollWidgetLayout.itemAt(0)
self.ui.scrollWidgetLayout.removeItem(item)
if item.widget():
item.widget().deleteLater()
self.dxfLineEdits = {}
# 添加label和button
for size in sizes:
layout = QHBoxLayout()
layout.addWidget(QLabel(size))
layout.setContentsMargins(0,0,00,0)
lineEdit = QLineEdit()
lineEdit.setValidator(QIntValidator())
lineEdit.setFixedSize(100, 30)
self.dxfLineEdits[size] = lineEdit
layout.addWidget(lineEdit)
self.ui.scrollWidgetLayout.addLayout(layout)
def ok(self):
# 检查参数
if self.dxfPath == None:
QMessageBox.critical(self, "错误", "未选择dxf文件")
return
if self.dir == None:
QMessageBox.critical(self, "错误", "未选择大货裁片文件夹!")
return
# 禁用窗口
self.setDisabled(True)
# 解析dxf数据
doc = ezdxf.readfile(self.dxfPath)
msp = doc.modelspace()
mspBox = ezdxf.bbox.extents(msp)
画布高 = mspBox.size[0]
画布宽 = mspBox.size[1]
分辨率 = self.ui.lineEdit1.text()
文档名称 = self.ui.lineEdit2.text()
piece_decorative.PS_DXF21_jscode_fun(f'创建裁片排版文档({画布宽},{画布高},{分辨率},"{文档名称}");')
单码片数 = int(self.ui.lineEdit3.text())
DXFnames = []
for size, lineEdit in self.dxfLineEdits.items():
for i in range(单码片数):
for j in range(int(lineEdit.text())):
DXFname = f"P{i + 1}-{size}"
DXFnames.append(DXFname)
print(DXFnames)
中心坐标和角度列表 = 获取dxf中心坐标和角度列表(doc)
# 保存新的文件
# doc.saveas(self.dxfPath + ".out.dxf")
for i in range(len(DXFnames)):
DXFname = DXFnames[i]
中心x_mm = 中心坐标和角度列表[i]["center"][0]
中心y_mm = 中心坐标和角度列表[i]["center"][1]
角度 = 中心坐标和角度列表[i]["rotation"]
piece_decorative.PS_DXF21_jscode_fun(f'置入链接的智能对象("{self.dir}","{DXFname}");')
piece_decorative.PS_DXF21_jscode_fun(f'裁片排版_lay({中心x_mm},{中心y_mm});')
piece_decorative.PS_DXF21_jscode_fun(f'裁片角度({角度});')
self.setDisabled(False)
def main():
try:
app = QApplication(sys.argv)
# myskin_styles.dark(app)
win = DXFWin()
qdarktheme.setup_theme(
custom_colors={
"[dark]": {
"background": "#4d4d4d",
"foreground": "#ffffff",
"primary": "#ffffff",
"border": "#717070",
}
}
)
# mw = myskin_windows.ModernWindow(win)
# win.setWindowIcon(QIcon('./ui/app.ico'))
win.show()
# win.show()
sys.exit(app.exec_())
except Exception as e:
print(f"An error occurred: {e}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,11 @@
import json
# 读取 JSON 文件
file_path = 'C:\\Users\\Administrator\\Desktop\\example.json'
with open(file_path, 'r') as file:
data = json.load(file)
# 处理数据
print("name:", data["name"])
print("resolution:", data["resolution"])
print("matchCount:", data["matchCount"])

View File

@@ -0,0 +1,264 @@
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QLabel, QPushButton, QFileDialog, \
QLineEdit, QScrollArea, QGroupBox, QHBoxLayout, QMessageBox, QProgressDialog
from PyQt5.QtGui import QIntValidator
from PyQt5.QtCore import QTimer
import re
import os
import ezdxf
import ezdxf.tools
import ezdxf.bbox
import ezdxf.units
import ezdxf.math
from coreldraw_checker import is_coreldraw_running
from functools import partial
import win32com.client
import os
import shutil
import threading
from clear_folder import another_function
class YourMainWindow9(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("RUNDXF")
layout = QVBoxLayout()
self.button2 = QPushButton("裁片文件路径")
self.button2.setFixedWidth(200)
self.button2.clicked.connect(self.showPltFileDialog)
self.button1 = QPushButton("DXF文件路径")
self.button1.setFixedWidth(200)
self.button1.clicked.connect(self.showDxfFileDialog)
self.initial_button2_text = self.button2.text()
self.initial_button1_text = self.button1.text()
panel1 = QGroupBox("文件路径")
panel1_layout = QVBoxLayout(panel1)
panel1_layout.setSpacing(10)
panel1_layout.setContentsMargins(10, 10, 10, 10)
panel1_layout.addWidget(self.button2)
panel1_layout.addWidget(self.button1)
layout.addWidget(panel1)
self.label7 = QLabel('文档名称')
self.lineEdit7 = QLineEdit()
self.lineEdit7.setValidator(QIntValidator())
self.lineEdit7.setFixedSize(120, 30)
layout.addWidget(self.label7)
layout.addWidget(self.lineEdit7)
self.label6 = QLabel('分辨率大小')
self.lineEdit6 = QLineEdit()
self.lineEdit6.setValidator(QIntValidator())
self.lineEdit6.setFixedSize(120, 30)
layout.addWidget(self.label6)
layout.addWidget(self.lineEdit6)
self.label5 = QLabel('单码片数')
self.lineEdit3 = QLineEdit()
self.lineEdit3.setValidator(QIntValidator())
self.lineEdit3.setFixedSize(120, 30)
layout.addWidget(self.label5)
layout.addWidget(self.lineEdit3)
self.scrollWidget = QWidget()
self.scrollWidgetLayout = QVBoxLayout(self.scrollWidget)
self.scrollArea = QScrollArea()
self.scrollArea.setWidget(self.scrollWidget)
self.scrollArea.setWidgetResizable(True)
layout.addWidget(self.scrollArea)
#
# self.clearButton = QPushButton("清空信息")
# self.clearButton.clicked.connect(self.clearScrollArea)
# layout.addWidget(self.clearButton)
# self.bigconfirm_button = QPushButton("混码分割")
# self.bigconfirm_button.clicked.connect(self.bigupdateScrollArea)
# layout.addWidget(self.bigconfirm_button)
confirm_button = QPushButton("运行")
confirm_button.clicked.connect(self.updateScrollArea)
layout.addWidget(confirm_button)
central_widget = QWidget()
central_widget.setLayout(layout)
self.setCentralWidget(central_widget)
self.dxfLineEdits = {}
self.allowButtonActions = True # 标志变量,控制是否允许按钮行为
def showPltFileDialog(self):
options = QFileDialog.Options()
file_path, _ = QFileDialog.getOpenFileName(self, "选择PLT文件", "", "PLT Files (*.plt);;All Files (*)",
options=options)
if file_path:
print("Selected PLT file:", file_path)
extracted_content = self.extract_content_from_plt_path(file_path)
print(extracted_content)
self.sizes = self.fill_sizes_from_extracted_content(extracted_content)
# 更新尺寸字典后,清空并填充滚动区域
self.clearScrollArea()
self.populateScrollArea()
self.initial_plt_path = file_path # 更新初始路径而不更新按钮文本
else:
print("No PLT file selected")
QMessageBox.warning(self, "警告", "没有选择文件夹。请重新选择文件夹。", QMessageBox.Ok)
pass
def showDxfFileDialog(self):
options = QFileDialog.Options()
file_path, _ = QFileDialog.getOpenFileName(self, "选择DXF文件", "", "DXF Files (*.dxf);;All Files (*)",
options=options)
if file_path:
print("Selected DXF file:", file_path)
extracted_content = self.extract_content_from_plt_path(file_path) # 更新初始路径而不更新按钮文本
print(extracted_content)
self.sizes = self.fill_sizes_from_extracted_content(extracted_content)
# 更新尺寸字典后,清空并填充滚动区域
self.clearScrollArea()
self.populateScrollArea()
self.initial_plt_path = file_path # 更新初始路径而不更新按钮文本
else:
print("No DXF file selected")
QMessageBox.warning(self, "警告", "没有选择文件夹。请重新选择文件夹。", QMessageBox.Ok)
# 在此处添加提醒逻辑,例如使用 QMessageBox 提示用户没有选择文件
def extract_content_from_plt_path(self, plt_path):
match = re.search(r'\((.*?)\)', plt_path)
if match:
extracted_content = match.group(1)
return extracted_content
else:
return "No content in parentheses found"
def fill_sizes_from_extracted_content(self, extracted_content):
sizes = extracted_content.split("+")
size_dict = {}
for size in sizes:
size_dict[size] = ""
return size_dict
def updateScrollArea(self):
another_function()
if not is_coreldraw_running():
QMessageBox.warning(self, "警告", "CorelDRAW未运行无法执行操作。")
return
plt_file_path = self.initial_plt_path
# 获取DXF文件路径
dxf_file_path = self.initial_dxf_path
extracted_content = self.extract_content_from_plt_path(plt_file_path)
if dxf_file_path:
# 去掉括号内内容后的PLT文件名作为DXF文件名
plt_filename = os.path.basename(plt_file_path)
# dxf_filename = re.sub(r'\(.*?\)', '', plt_filename)
self.process_dxf_file(dxf_file_path, extracted_content) # 调用解析函数并传入单码片数和新的DXF文件名
print("DXF文件解析完成")
else:
QMessageBox.warning(self, "警告", "没有选择DXF文件。请先选择一个DXF文件。", QMessageBox.Ok)
print()
self.run_coreldraw_macros()
single_code_pieces = int(self.getSinglePieceCount()) # 获取单码片数
print(single_code_pieces)
# 打印滚动区域中的输入框内容
code_quantities = {} # 创建一个新的字典用于存储数据
for label, line_edit in self.dxfLineEdits.items():
text = line_edit.text()
if text.isdigit():
value = int(text) # 尝试将文本转换为整数
else:
try:
value = float(text) # 尝试将文本转换为浮点数
except ValueError:
print(f"Invalid value for {label}: {text}")
continue # 转换失败,跳过当前循环迭代
code_quantities[label] = value # 存储转换后的数字到字典
print(code_quantities)
length = len(code_quantities)
print(length) # 输出 3因为字典中有三对键值对
def updateLineEditsFromSizes(self):
for size_label, line_edit in self.dxfLineEdits.items():
self.sizes[size_label] = line_edit.text()
def populateScrollArea(self):
self.clearScrollArea()
for size_label, size_text in self.sizes.items():
size_layout = QHBoxLayout()
size_layout.addWidget(QLabel(size_label))
line_edit = QLineEdit()
line_edit.setValidator(QIntValidator())
line_edit.setFixedSize(100, 30)
line_edit.setText(size_text)
self.dxfLineEdits[size_label] = line_edit
size_layout.addWidget(line_edit)
self.scrollWidgetLayout.addLayout(size_layout)
def clearScrollArea(self):
for i in reversed(range(self.scrollWidgetLayout.count())):
item = self.scrollWidgetLayout.itemAt(i)
if isinstance(item, QHBoxLayout) or isinstance(item, QVBoxLayout):
while item.count():
widget = item.takeAt(0).widget()
if widget:
widget.deleteLater()
self.dxfLineEdits.clear() # 清空部件引用
if __name__ == '__main__':
app = QApplication(sys.argv)
mainWindow = YourMainWindow9()
mainWindow.show()
sys.exit(app.exec_())

View File

@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
[程序配置]
ps应用名 = Photoshop.Application

View File

@@ -0,0 +1 @@
qwe123456

View File

@@ -0,0 +1,177 @@
from flask import *
import os
import pymysql
import tempfile
import shutil
from zipfile import ZipFile
import datetime
#==================================================================================================
app = Flask(__name__)
ADMIN_USERNAME = "admin"
ADMIN_PASSWORD = "fo847543jfrgowjfa8otu43"
#==================================================================================================
def get_connect():
host = "rm-bp1s36ps814qp23b7uo.mysql.rds.aliyuncs.com"
user = "zw1847930177"
password = "Zuowei1216"
database = "program"
charset = "utf8"
port = 3306
conn = pymysql.connect(host=host, user=user, password=password, database=database, charset=charset, port=port)
return conn
def getallusers():
try:
conn = get_connect()
cur = conn.cursor(pymysql.cursors.DictCursor)
cur.execute(f'select * from user;')
user_data = cur.fetchall()
return user_data
finally:
cur.close()
conn.close()
def new_users(username, password, code):
conn = get_connect()
cur = conn.cursor(pymysql.cursors.DictCursor)
cur.execute(f"""INSERT INTO `program`.`user` (`username`, `password`, `code`, `expiredate`) VALUES ('{username}', '{password}', '{code}', '{(datetime.datetime.now() + datetime.timedelta(days=14)).strftime("%Y-%m-%d %H:%M:%S")}');""")
conn.commit()
#==================================================================================================
def 无code():
userinfo_filepath = os.path.join("tmp", "userinfo.txt")
with open(userinfo_filepath, 'w') as f:
f.write("error")
with ZipFile(os.path.join("tmp", 'result.zip'), 'w') as z:
z.write(userinfo_filepath, arcname="userinfo.txt")
return send_from_directory("tmp", "result.zip", as_attachment=True)
def 错误的用户名或密码():
userinfo_filepath = os.path.join("tmp", "userinfo.txt")
with open(userinfo_filepath, 'w') as f:
f.write("error")
with ZipFile(os.path.join("tmp", 'result.zip'), 'w') as z:
z.write(userinfo_filepath, arcname="userinfo.txt")
return send_from_directory("tmp", "result.zip", as_attachment=True)
#==================================================================================================
def 返回正常数据(username, password):
userinfo_filepath = os.path.join("tmp", "userinfo.txt")
with open(userinfo_filepath, 'w', encoding='utf-8') as f:
f.write(f"{username}\n{password}")
with open("using.txt", 'r') as f:
shutil.copyfile(f"archives/{f.read()}.zip", os.path.join("tmp", "data.zip"))
with ZipFile(os.path.join("tmp", 'result.zip'), 'w') as z:
z.write(userinfo_filepath, arcname="userinfo.txt")
z.write(os.path.join("tmp", "data.zip"), arcname="data.zip")
return send_from_directory("tmp", "result.zip", as_attachment=True)
#==================================================================================================
@app.route("/query", methods=["POST"])
def query():
username = request.args.get("username", "")
password = request.args.get("password", "")
code = request.args.get("code", "")
allusers = getallusers()
if code == "":
return 无code()
if username == "" and password == "":
for user in allusers:
if code == user["code"] and (user["expiredate"] - datetime.datetime.now()).total_seconds() > 0:
return 返回正常数据(user["username"], user["password"])
else:
for user in allusers:
if username == user["username"] and password == user["password"] and code == user["code"] and (user["expiredate"] - datetime.datetime.now()).total_seconds() > 0:
return 返回正常数据(user["username"], user["password"])
return 错误的用户名或密码()
#==================================================================================================
# 设置使用的档案
@app.route("/set_using_archives", methods=["POST"])
def set_using_archives():
# 检查权限
username = request.form.get("username", "")
password = request.form.get("password", "")
if not (username == ADMIN_USERNAME and password == ADMIN_PASSWORD):
abort(403)
result = request.form.get("result")
if result not in [os.path.basename(a).split('.')[0] for a in os.listdir("archives")]:
return 'error'
with open("using.txt", 'w') as f:
f.write(result)
return 'OK'
#==================================================================================================
# 获取正在使用的档案名称
@app.route("/get_using_archives_name", methods=["GET"])
def get_using_archives_name():
with open("using.txt", 'r') as f:
return f.read()
#==================================================================================================
# 注册
@app.route("/register", methods=["POST"])
def register():
if request.method == "POST":
username = request.form.get("username")
password = request.form.get("password")
code = request.form.get("code")
adminpassword = request.form.get("adminpassword")
with open("adminpassword.txt", 'r') as f:
true_adminpassword = f.read().strip()
if adminpassword != true_adminpassword:
return "error"
else:
new_users(username, password, code)
return 'success'
#==================================================================================================
@app.route("/archives", methods=["GET", "POST"])
def archives():
if request.method == "GET":
# 获取档案列表
archives = os.listdir("archives")
archives = [os.path.basename(a).split(".")[0] for a in archives]
return jsonify(archives)
elif request.method == "POST":
# 上传档案
username = request.form.get("username")
password = request.form.get("password")
if not (username == ADMIN_USERNAME and password == ADMIN_PASSWORD):
abort(403)
file = request.files['file']
file.save(f"./archives/{str(datetime.datetime.now()).split('.')[0].replace(':', '')}.zip")
return 'OK'
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5001, debug=True)

View File

@@ -0,0 +1 @@
2023-09-09 010039

View File

@@ -0,0 +1,11 @@
import sqlite3
conn = sqlite3.connect('designercep.db')
# Update plugin_groups table - column is current_version_file not version
conn.execute("UPDATE plugin_groups SET current_version_file = 'core-v1.3.0.zip' WHERE id=1")
conn.commit()
print('Updated Default group to core-v1.0.5.zip')
cursor = conn.execute('SELECT * FROM plugin_groups')
print(list(cursor))
conn.close()

View File

@@ -0,0 +1,113 @@
# DesignerCEP 认证模块接口文档
本文档描述了后端新增的邮箱注册验证与密码重置相关接口。
## 1. 注册 (Register)
支持传入邮箱进行注册。如果传入邮箱,系统将发送验证码邮件。
- **URL**: `/api/v1/auth/register`
- **Method**: `POST`
- **Request Body**:
```json
{
"username": "user1",
"password": "password123",
"confirm_password": "password123",
"email": "user1@gmail.com", // [新增] 可选,推荐填写
"device_id": "device_unique_id"
}
```
- **Response**:
- Success (200):
```json
{
"access_token": "eyJhbGciOiJIUzI1NiIsIn...",
"token_type": "bearer",
"username": "user1"
}
```
- Error (400): 用户名已存在 / 邮箱已存在 / 密码不一致
## 2. 验证邮箱 (Verify Email)
用户收到验证码邮件后,在前端输入验证码进行验证。
- **URL**: `/api/v1/auth/verify-email`
- **Method**: `POST`
- **Request Body**:
```json
{
"username": "user1",
"code": "123456" // 邮件中的6位数字验证码
}
```
- **Response**:
- Success (200):
```json
{
"detail": "验证成功"
}
```
- Error (400): 验证码错误
## 3. 忘记密码 (Forgot Password)
用户输入注册邮箱,请求重置密码。
- **URL**: `/api/v1/auth/forgot-password`
- **Method**: `POST`
- **Request Body**:
```json
{
"email": "user1@gmail.com"
}
```
- **Response**:
- Success (200):
```json
{
"detail": "如果邮箱存在,重置邮件已发送"
}
```
## 4. 重置密码 (Reset Password)
用户点击邮件中的链接或手动输入Token设置新密码。
- **URL**: `/api/v1/auth/reset-password`
- **Method**: `POST`
- **Request Body**:
```json
{
"token": "reset_token_from_email",
"new_password": "new_password123",
"confirm_password": "new_password123"
}
```
- **Response**:
- Success (200):
```json
{
"detail": "密码重置成功"
}
```
- Error (400): Token 无效 / Token 已过期 / 密码不一致
## 流程说明
1. **注册流程**:
- 用户填写注册信息(含邮箱)。
- 提交 `/register`。
- 如果成功,后端自动登录并返回 Token。
- 如果填写了邮箱,后端会发送一封验证邮件。
- 前端提示用户查收邮件并输入验证码。
- 用户输入验证码,前端调用 `/verify-email`。
2. **找回密码流程**:
- 用户点击“忘记密码”。
- 输入邮箱,前端调用 `/forgot-password`。
- 用户收到邮件,包含重置 Token。
- 前端提供重置密码界面,用户输入新密码。
- 前端调用 `/reset-password`(带上 Token 和新密码)。
- 重置成功后,跳转至登录页。

View File

@@ -0,0 +1,339 @@
# API Key 使用指南
## ✅ 已启用 API Key 验证
现在所有对 `/api/v1/jsx_demo/calculate` 的请求都需要提供有效的 API Key。
---
## 🔑 当前可用的 API Keys
### 1. 测试密钥(开发使用)
```
API Key: demo_key_123
名称: 测试密钥
权限: calculate
限制: 100次/小时
```
### 2. 生产密钥(生产环境)
```
API Key: prod_key_xyz789abc
名称: 生产密钥
权限: calculate, admin
限制: 1000次/小时
```
---
## 📝 日志示例
启用 API Key 后,后端日志会显示:
```
============================================================
📥 收到计算请求
时间: 2024-12-16 15:30:45
表达式: 87-98
API Key: demo_key_123
============================================================
✅ API Key 验证通过 | 名称: 测试密钥 | 权限: ['calculate']
🛡️ 安全检查: 验证表达式格式...
✅ 表达式格式验证通过
🔒 开始执行核心算法...
✅ 计算完成: 87-98 = -11
============================================================
```
### 如果 API Key 无效:
```
============================================================
📥 收到计算请求
时间: 2024-12-16 15:30:45
表达式: 87-98
API Key: invalid_key_xxx
============================================================
❌ API Key 验证失败: invalid_key_xxx
```
**前端会收到:** `403 Forbidden: 无效的 API Key`
---
## 🔧 管理 API Keys
### 查看所有 Keys
打开 `Server/app/core/api_keys.py`
```python
VALID_KEYS: Dict[str, dict] = {
"demo_key_123": {
"name": "测试密钥",
"created": "2024-12-16",
"permissions": ["calculate"],
"rate_limit": 100
},
# ... 更多 keys
}
```
### 添加新的 API Key
**方法 1直接编辑配置文件**
`api_keys.py` 中添加:
```python
"your_new_key_456": {
"name": "客户A的密钥",
"created": "2024-12-16",
"permissions": ["calculate"],
"rate_limit": 200
}
```
**方法 2使用 Python 代码**
```python
from app.core.api_keys import APIKeyManager
# 添加新 Key
APIKeyManager.add_key(
api_key="customer_key_789",
name="客户B的密钥",
permissions=["calculate", "export"]
)
```
### 删除 API Key
```python
from app.core.api_keys import APIKeyManager
# 删除 Key
APIKeyManager.remove_key("old_key_123")
```
### 检查权限
```python
from app.core.api_keys import APIKeyManager
# 检查是否有权限
has_permission = APIKeyManager.check_permission("demo_key_123", "calculate")
```
---
## 🔒 前端配置
### 当前配置(已设置)
`Designer/src/api/jsxApi/inline/hybrid-demo.ts`
```typescript
const response = await fetch(`${config.apiBaseUrl}/jsx_demo/calculate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': 'demo_key_123' // 🔐 使用测试密钥
},
body: JSON.stringify({
expression: layerName
})
});
```
### 切换到生产密钥
修改 API Key
```typescript
'X-API-Key': 'prod_key_xyz789abc' // 使用生产密钥
```
### 从配置文件读取(推荐)
创建 `Designer/src/config/apiKeys.ts`
```typescript
export const API_KEYS = {
development: 'demo_key_123',
production: 'prod_key_xyz789abc'
};
// 根据环境自动选择
export const getCurrentApiKey = () => {
return import.meta.env.DEV
? API_KEYS.development
: API_KEYS.production;
};
```
然后在 `hybrid-demo.ts` 中使用:
```typescript
import { getCurrentApiKey } from '@/config/apiKeys';
const response = await fetch(`${config.apiBaseUrl}/jsx_demo/calculate`, {
headers: {
'X-API-Key': getCurrentApiKey() // 自动选择正确的 Key
}
});
```
---
## 🧪 测试 API Key 验证
### 测试 1使用有效的 Key
1. 创建图层名称:`87-98`
2. 点击"智能配色"
3. **预期结果:** ✅ 成功计算
**后端日志:**
```
✅ API Key 验证通过 | 名称: 测试密钥
```
---
### 测试 2使用无效的 Key
临时修改前端,使用错误的 Key
```typescript
'X-API-Key': 'wrong_key_xxx'
```
**预期结果:** ❌ 403 错误
**后端日志:**
```
❌ API Key 验证失败: wrong_key_xxx
```
**前端错误:**
```
Message.error('无效的 API Key')
```
---
### 测试 3不提供 Key
注释掉 API Key
```typescript
// 'X-API-Key': 'demo_key_123'
```
**预期结果:** ❌ 403 错误
**后端日志:**
```
❌ API Key 验证失败: None
```
---
## 🛡️ 安全最佳实践
### ✅ 应该做的
1. **不同环境使用不同的 Key**
- 开发环境:`demo_key_123`
- 生产环境:`prod_key_xyz789abc`
2. **定期更换 Key**
```python
# 每季度更新一次
"new_key_2024_q1": {...}
```
3. **使用环境变量**
```python
import os
API_KEY = os.getenv('DESIGNER_API_KEY', 'demo_key_123')
```
4. **记录所有请求**
- 已实现:所有请求都会记录 API Key
5. **限制调用频率**
- 已配置:每个 Key 都有 rate_limit
### ❌ 不应该做的
1. ❌ 把 Key 硬编码在公开的代码中
2. ❌ 在 Git 中提交包含生产 Key 的文件
3. ❌ 多个客户共用同一个 Key
4. ❌ 永远不更换 Key
---
## 🚀 进一步加强
### 1. 数据库存储 API Keys
```python
# models/api_key.py
class APIKey(Base):
__tablename__ = "api_keys"
key = Column(String, primary_key=True)
name = Column(String)
permissions = Column(JSON)
rate_limit = Column(Integer)
created_at = Column(DateTime)
expires_at = Column(DateTime)
is_active = Column(Boolean, default=True)
```
### 2. Key 过期时间
```python
"demo_key_123": {
"expires": "2025-12-31", # Key 会过期
}
```
### 3. 使用量统计
```python
"demo_key_123": {
"usage_count": 0,
"last_used": None,
}
```
### 4. IP 绑定
```python
"demo_key_123": {
"allowed_ips": ["192.168.1.100", "127.0.0.1"]
}
```
---
## 📊 总结
| 功能 | 状态 | 说明 |
|------|------|------|
| API Key 验证 | ✅ 已启用 | 所有请求必须提供有效 Key |
| 多 Key 支持 | ✅ 已实现 | 可配置多个不同权限的 Key |
| 权限控制 | ✅ 已实现 | 每个 Key 可配置不同权限 |
| 日志记录 | ✅ 已实现 | 记录所有 Key 使用情况 |
| Key 管理器 | ✅ 已实现 | 提供增删改查 API |
| 限流配置 | 🔧 已配置 | 待实现实际限流逻辑 |
| 数据库存储 | ⚠️ 未实现 | 当前使用配置文件 |
| Key 过期 | ⚠️ 未实现 | 可以扩展 |
---
**当前 API Key 已启用!所有请求都会被验证和记录!**

View File

@@ -0,0 +1,397 @@
# AdminTool 系统配置管理开发文档
## 📋 开发需求
AdminTool需要集成系统配置管理功能用于可视化管理
1. 功能配置(积分价格、启用/禁用)
2. VIP配置价格、配额、倍数
3. 签到配置(连续天数奖励)
4. 数据统计(今日统计、功能使用排行)
---
## 🎨 技术方案
### 方案A使用 qfluentwidgets推荐
#### 1. 安装依赖
```bash
pip install PyQt-Fluent-Widgets
```
#### 2. 改造现有代码
**主窗口改造** (`admin_gui.py`):
```python
from qfluentwidgets import FluentWindow, FluentIcon, NavigationItemPosition
class AdminWindow(FluentWindow): # 改为继承 FluentWindow
def __init__(self):
super().__init__()
# 启用 Mica 效果
self.setMicaEffectEnabled(True)
# 添加配置管理页面
self.config_interface = ConfigInterface(self.api_client, self)
self.addSubInterface(
self.config_interface,
icon=FluentIcon.SETTING,
text="系统配置",
position=NavigationItemPosition.BOTTOM
)
```
**配置管理界面** (`config_interface.py`,新建):
```python
from PyQt5.QtWidgets import QWidget, QVBoxLayout
from qfluentwidgets import (
Pivot, PushButton, TableWidget, CardWidget,
MessageBox, InfoBar
)
class ConfigInterface(QWidget):
"""配置管理主界面"""
def __init__(self, api_client, parent=None):
super().__init__(parent)
self.api_client = api_client
# 创建Pivot导航
self.pivot = Pivot(self)
# 添加子页面
self.features_tab = FeaturesConfigTab(api_client)
self.vip_tab = VIPConfigTab(api_client)
self.checkin_tab = CheckInConfigTab(api_client)
self.stats_tab = StatsTab(api_client)
# 添加到Pivot
self.pivot.addItem('features', '功能配置', self.features_tab)
self.pivot.addItem('vip', 'VIP配置', self.vip_tab)
self.pivot.addItem('checkin', '签到配置', self.checkin_tab)
self.pivot.addItem('stats', '数据统计', self.stats_tab)
class FeaturesConfigTab(QWidget):
"""功能配置标签页"""
def __init__(self, api_client, parent=None):
super().__init__(parent)
self.api_client = api_client
# 表格
self.table = TableWidget(self)
self.table.setColumnCount(6)
self.table.setHorizontalHeaderLabels([
"功能名称", "普通价格", "VIP价格", "SVIP价格", "状态", "操作"
])
# 按钮
self.btn_add = PushButton("新增功能", self)
self.btn_refresh = PushButton("刷新", self)
self.load_data()
def load_data(self):
"""从API加载数据"""
try:
resp = requests.get(
f"{self.api_client.base_url}/admin/config/features",
headers=self.api_client.get_headers()
)
data = resp.json()
# 填充表格...
InfoBar.success("加载成功", f"已加载 {len(data)} 个功能")
except Exception as e:
MessageBox("错误", f"加载失败: {e}", self).exec()
```
---
### 方案B使用现有PyQt5快速实现
如果不想安装新依赖,可以在现有 `admin_gui.py` 基础上:
#### 1. 在 `setup_` 中添加新标签页
```python
def __init__(self):
# ... 现有代码 ...
# 新增第4个标签页
self.config_tab = QWidget()
self.setup_config_tab()
self.tabs.addTab(self.config_tab, "系统配置")
def setup_config_tab(self):
"""设置系统配置标签页"""
layout = QVBoxLayout(self.config_tab)
# 子标签页
sub_tabs = QTabWidget()
# 功能配置
features_widget = QWidget()
self.setup_features_config(features_widget)
sub_tabs.addTab(features_widget, "功能配置")
# VIP配置
vip_widget = QWidget()
self.setup_vip_config(vip_widget)
sub_tabs.addTab(vip_widget, "VIP配置")
# 签到配置
checkin_widget = QWidget()
self.setup_checkin_config(checkin_widget)
sub_tabs.addTab(checkin_widget, "签到配置")
# 统计
stats_widget = QWidget()
self.setup_stats(stats_widget)
sub_tabs.addTab(stats_widget, "数据统计")
layout.addWidget(sub_tabs)
```
#### 2. 实现各个子界面
```python
def setup_features_config(self, widget):
"""功能配置界面"""
layout = QVBoxLayout(widget)
# 操作按钮
btn_layout = QHBoxLayout()
btn_add = QPushButton("新增功能")
btn_add.clicked.connect(self.add_feature_config)
btn_refresh = QPushButton("刷新")
btn_refresh.clicked.connect(self.refresh_features_config)
btn_layout.addWidget(btn_add)
btn_layout.addWidget(btn_refresh)
btn_layout.addStretch()
layout.addLayout(btn_layout)
# 表格
self.features_table = QTableWidget()
self.features_table.setColumnCount(7)
self.features_table.setHorizontalHeaderLabels([
"功能名称", "分类", "普通价格", "VIP价格", "SVIP价格", "状态", "操作"
])
self.features_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
layout.addWidget(self.features_table)
# 加载数据
self.refresh_features_config()
def refresh_features_config(self):
"""刷新功能配置"""
try:
resp = requests.get(
f"{self.api_client.base_url}/admin/config/features",
headers=self.api_client.get_headers()
)
resp.raise_for_status()
data = resp.json()
self.features_table.setRowCount(len(data))
for i, feature in enumerate(data):
self.features_table.setItem(i, 0, QTableWidgetItem(feature['feature_name']))
self.features_table.setItem(i, 1, QTableWidgetItem(feature['category']))
self.features_table.setItem(i, 2, QTableWidgetItem(str(feature['points_cost'])))
self.features_table.setItem(i, 3, QTableWidgetItem(str(feature['vip_points_cost'])))
self.features_table.setItem(i, 4, QTableWidgetItem(str(feature['svip_points_cost'])))
self.features_table.setItem(i, 5, QTableWidgetItem("" if feature['enabled'] else ""))
# 操作按钮
btn_edit = QPushButton("编辑")
btn_edit.clicked.connect(lambda checked, f=feature: self.edit_feature_config(f))
self.features_table.setCellWidget(i, 6, btn_edit)
QMessageBox.information(self, "成功", f"已加载 {len(data)} 个功能配置")
except Exception as e:
QMessageBox.critical(self, "错误", f"加载失败: {e}")
def edit_feature_config(self, feature):
"""编辑功能配置"""
dialog = QDialog(self)
dialog.setWindowTitle(f"编辑功能: {feature['feature_name']}")
layout = QFormLayout(dialog)
# 输入框
points_input = QSpinBox()
points_input.setRange(0, 9999)
points_input.setValue(feature['points_cost'])
layout.addRow("普通用户积分:", points_input)
vip_points_input = QSpinBox()
vip_points_input.setRange(0, 9999)
vip_points_input.setValue(feature['vip_points_cost'])
layout.addRow("VIP积分:", vip_points_input)
svip_points_input = QSpinBox()
svip_points_input.setRange(0, 9999)
svip_points_input.setValue(feature['svip_points_cost'])
layout.addRow("SVIP积分:", svip_points_input)
enabled_check = QCheckBox()
enabled_check.setChecked(feature['enabled'])
layout.addRow("启用:", enabled_check)
# 按钮
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
buttons.accepted.connect(dialog.accept)
buttons.rejected.connect(dialog.reject)
layout.addRow(buttons)
if dialog.exec() == QDialog.Accepted:
# 提交更新
try:
resp = requests.put(
f"{self.api_client.base_url}/admin/config/features/{feature['feature_key']}",
json={
"points_cost": points_input.value(),
"vip_points_cost": vip_points_input.value(),
"svip_points_cost": svip_points_input.value(),
"enabled": enabled_check.isChecked()
},
headers=self.api_client.get_headers()
)
resp.raise_for_status()
QMessageBox.information(self, "成功", "更新成功")
self.refresh_features_config()
except Exception as e:
QMessageBox.critical(self, "错误", f"更新失败: {e}")
```
---
## 📊 API接口参考
### 功能配置API
```python
# 获取所有功能
GET /api/v1/admin/config/features
Headers: x-admin-token: admin-secret-token
# 新增功能
POST /api/v1/admin/config/features
Body: {
"feature_key": "new_feature",
"feature_name": "新功能",
"category": "ai",
"points_cost": 60,
"vip_points_cost": 0,
"svip_points_cost": 0
}
# 更新功能
PUT /api/v1/admin/config/features/{feature_key}
Body: {
"points_cost": 80,
"enabled": true
}
```
### VIP配置API
```python
# 获取VIP配置
GET /api/v1/admin/config/vip
# 更新VIP配置
PUT /api/v1/admin/config/vip/vip
Body: {
"price": 35.00,
"daily_quota": 25,
"points_multiplier": 1.60
}
```
### 签到配置API
```python
# 获取签到配置
GET /api/v1/admin/config/checkin
# 新增档位
POST /api/v1/admin/config/checkin
Body: {
"consecutive_days": 60,
"base_points": 10,
"bonus_points": 150,
"total_points": 160
}
# 更新档位
PUT /api/v1/admin/config/checkin/7
Body: {
"bonus_points": 25,
"total_points": 35
}
```
### 统计API
```python
# 今日统计
GET /api/v1/admin/stats/today
# 功能使用排行
GET /api/v1/admin/stats/feature-usage?days=7
# 积分趋势
GET /api/v1/admin/stats/points-trend?days=7
```
---
## ✅ 开发检查清单
- [ ] 安装依赖如使用方案A
- [ ] 改造主窗口类
- [ ] 创建配置管理界面
- [ ] 实现功能配置CRUD
- [ ] 实现VIP配置编辑
- [ ] 实现签到配置编辑
- [ ] 实现数据统计展示
- [ ] 连接后端API
- [ ] 错误处理和提示
- [ ] 测试所有功能
---
## 📝 注意事项
1. **权限验证**: 所有管理接口都需要 `x-admin-token: admin-secret-token`
2. **错误处理**: 使用 try-except 捕获网络异常
3. **用户友好**: 操作前确认,操作后提示
4. **数据刷新**: 修改后自动刷新列表
5. **布局美观**: 合理使用Grid/Box布局
---
## 🎯 优先级
### P0必须
- 功能配置查看和编辑
- VIP配置编辑
- 签到配置编辑
### P1重要
- 功能配置新增/删除
- 签到配置新增/删除
- 数据统计展示
### P2可选
- Fluent Design 风格改造
- 图表可视化
- 高级筛选和搜索
---
**开发愉快如有问题请参考主文档或API文档。**

View File

@@ -0,0 +1,820 @@
# 🚀 DesignerCEP Caddy 部署指南
## 为什么选择 Caddy
**自动 HTTPS**:无需手动申请证书,自动从 Let's Encrypt 获取并续期
**配置简单**:比 Nginx 简单 10 倍,一看就懂
**开箱即用**:自动处理 HTTP/2, GZIP 压缩
**完美支持 Cloudflare**:自动识别并配置
---
## 📦 1. 安装 Caddy
### Ubuntu/Debian
```bash
# 安装依赖
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
# 添加 Caddy 官方仓库
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
# 更新并安装
sudo apt update
sudo apt install caddy
# 验证安装
caddy version
```
### CentOS/RHEL
```bash
# 添加 Caddy 官方仓库
dnf install 'dnf-command(copr)'
dnf copr enable @caddy/caddy
dnf install caddy
# 启动服务
sudo systemctl enable caddy
sudo systemctl start caddy
```
---
## 📁 2. 准备项目目录
```bash
# 创建项目目录
sudo mkdir -p /var/www/DesignerCEP/Server/static/{shell,core,downloads}
# 设置权限
sudo chown -R $USER:$USER /var/www/DesignerCEP
chmod -R 755 /var/www/DesignerCEP
```
---
## ⚙️ 3. 配置 Caddy
### 方案 A: 标准部署Let's Encrypt 自动证书)
**适用于**:独立域名,不使用 Cloudflare 代理
```bash
# 创建 Caddyfile
sudo nano /etc/caddy/Caddyfile
```
```caddy
# /etc/caddy/Caddyfile
# ========== 主站配置 ==========
your-domain.com, www.your-domain.com {
# ✅ Caddy 会自动处理 HTTPS 证书
# ========== API 请求 → FastAPI ==========
handle /api/* {
reverse_proxy localhost:8000 {
# 传递真实 IP
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
}
# ========== Shell 在线登录页 ==========
handle /shell/* {
root * /var/www/DesignerCEP/Server/static/shell
try_files {path} {path}/ /shell/index.html
file_server
# HTML 不缓存
@html {
path *.html
}
header @html Cache-Control "no-cache, no-store, must-revalidate"
# JS/CSS 长期缓存
@assets {
path *.js *.css
}
header @assets Cache-Control "public, max-age=31536000, immutable"
}
# ========== Core 核心应用 ==========
handle /core/* {
root * /var/www/DesignerCEP/Server/static/core
file_server
# HTML 不缓存
@html {
path *.html
}
header @html Cache-Control "no-cache, no-store, must-revalidate"
# JS/CSS 长期缓存
@assets {
path *.js *.css
}
header @assets Cache-Control "public, max-age=31536000, immutable"
}
# ========== 下载文件 ==========
handle /downloads/* {
root * /var/www/DesignerCEP/Server/static/downloads
file_server
# 启用断点续传
header Accept-Ranges bytes
# 缓存 1 天
header Cache-Control "public, max-age=86400"
}
# ========== 根路径重定向 ==========
handle / {
redir /shell/ permanent
}
# ========== 通用配置 ==========
# 自动 GZIP 压缩
encode gzip zstd
# 安全头
header {
X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
X-XSS-Protection "1; mode=block"
-Server # 隐藏服务器信息
}
# 日志
log {
output file /var/log/caddy/designer-cep.log {
roll_size 100mb
roll_keep 10
}
}
}
```
### 方案 B: Cloudflare 代理部署(推荐)
**适用于**:域名使用 Cloudflare 橙云代理
```bash
sudo nano /etc/caddy/Caddyfile
```
```caddy
# /etc/caddy/Caddyfile (Cloudflare 版本)
{
# ✅ 关闭自动 HTTPSCloudflare 已经提供)
auto_https off
# 或者使用内部证书
# auto_https disable_redirects
}
# ========== HTTP 配置Cloudflare 会加 HTTPS==========
http://your-domain.com, http://www.your-domain.com {
# ========== API 请求 → FastAPI ==========
handle /api/* {
reverse_proxy localhost:8000 {
# ✅ 从 Cloudflare 获取真实 IP
header_up X-Real-IP {header.CF-Connecting-IP}
header_up X-Forwarded-For {header.CF-Connecting-IP}
header_up X-Forwarded-Proto https
}
}
# ========== Shell 在线登录页 ==========
handle /shell/* {
root * /var/www/DesignerCEP/Server/static/shell
try_files {path} {path}/ /shell/index.html
file_server
@html path *.html
header @html Cache-Control "no-cache, no-store, must-revalidate"
@assets path *.js *.css
header @assets Cache-Control "public, max-age=31536000, immutable"
}
# ========== Core 核心应用 ==========
handle /core/* {
root * /var/www/DesignerCEP/Server/static/core
file_server
@html path *.html
header @html Cache-Control "no-cache, no-store, must-revalidate"
@assets path *.js *.css
header @assets Cache-Control "public, max-age=31536000, immutable"
}
# ========== 下载文件 ==========
handle /downloads/* {
root * /var/www/DesignerCEP/Server/static/downloads
file_server
header Accept-Ranges bytes
header Cache-Control "public, max-age=86400"
}
# ========== 根路径重定向 ==========
handle / {
redir /shell/ permanent
}
# ========== 通用配置 ==========
encode gzip zstd
header {
X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
X-XSS-Protection "1; mode=block"
-Server
}
log {
output file /var/log/caddy/designer-cep.log {
roll_size 100mb
roll_keep 10
}
}
}
```
---
## 🔧 4. 修改后端代码(支持 CEP 环境)
### 4.1 修改 FastAPI 的 CORS 配置
```python
# Server/app/main.py
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
import os
app = FastAPI(title=settings.PROJECT_NAME)
# 环境判断
IS_DEV = os.getenv("ENV", "development") == "development"
# ========== CORS 配置 ==========
if IS_DEV:
# 开发环境:宽松配置
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
else:
# 生产环境:严格配置
allowed_origins = os.getenv("ALLOWED_ORIGINS", "").split(",")
app.add_middleware(
CORSMiddleware,
allow_origins=allowed_origins,
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["*"],
)
# ✅ 特殊处理 CEP 环境Origin: null
@app.middleware("http")
async def cep_cors_middleware(request: Request, call_next):
origin = request.headers.get("origin")
# CEP 环境的 Origin 是 null 或 cep://
if origin in ["null", None] or (origin and origin.startswith("cep://")):
response = await call_next(request)
response.headers["Access-Control-Allow-Origin"] = "*"
response.headers["Access-Control-Allow-Credentials"] = "true"
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
response.headers["Access-Control-Allow-Headers"] = "*"
return response
return await call_next(request)
# ========== API 路由(保持不变)==========
app.include_router(auth.router, prefix=f"{settings.API_V1_STR}/auth", tags=["authentication"])
app.include_router(client.router, prefix=f"{settings.API_V1_STR}/client", tags=["client"])
app.include_router(admin.router, prefix=f"{settings.API_V1_STR}/admin", tags=["admin"])
app.include_router(analytics.router, prefix=f"{settings.API_V1_STR}/analytics", tags=["analytics"])
app.include_router(jsx_demo.router, prefix=f"{settings.API_V1_STR}/jsx_demo", tags=["jsx_demo"])
# ❌ 删除静态文件挂载(交给 Caddy 处理)
# app.mount("/download", ...) # 删除
# app.mount("/shell", ...) # 删除
# app.mount("/core", ...) # 删除
@app.get("/")
def read_root():
return {"message": "DesignerCEP API Server", "version": "1.0.0"}
@app.get("/health")
def health_check():
return {"status": "healthy"}
@app.on_event("startup")
def on_startup():
init_db()
```
### 4.2 配置环境变量
```bash
# Server/.env
ENV=production
PROJECT_NAME=DesignerCEP
API_V1_STR=/api/v1
SECRET_KEY=your-secret-key-here
DATABASE_URL=mysql://user:password@localhost:3306/designer_cep
# 生产环境允许的来源
ALLOWED_ORIGINS=https://your-domain.com,https://www.your-domain.com
# 是否由 FastAPI 提供静态文件(生产环境设为 false
SERVE_STATIC=false
```
---
## 🚀 5. 部署 FastAPI 服务
### 5.1 使用 Systemd 管理服务
```bash
# 创建服务文件
sudo nano /etc/systemd/system/designer-cep.service
```
```ini
[Unit]
Description=DesignerCEP FastAPI Application
After=network.target
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/var/www/DesignerCEP/Server
Environment="PATH=/var/www/DesignerCEP/Server/venv/bin"
ExecStart=/var/www/DesignerCEP/Server/venv/bin/gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker -b 127.0.0.1:8000
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
```
### 5.2 安装依赖并启动
```bash
# 1. 创建虚拟环境
cd /var/www/DesignerCEP/Server
python3 -m venv venv
source venv/bin/activate
# 2. 安装依赖
pip install -r requirements.txt
pip install gunicorn uvicorn[standard]
# 3. 测试运行
uvicorn app.main:app --host 127.0.0.1 --port 8000
# 4. 启动服务
sudo systemctl daemon-reload
sudo systemctl enable designer-cep
sudo systemctl start designer-cep
# 5. 检查状态
sudo systemctl status designer-cep
```
---
## 📤 6. 部署前端文件
### 6.1 配置前端环境
```bash
# Designer/.env.production
VITE_API_SERVER=https://your-domain.com
```
### 6.2 构建前端
```bash
cd Designer
npm install
npm run build
```
### 6.3 上传到服务器
#### 方法 A: 手动上传
```bash
# 在本地执行
cd Designer/dist
# 上传 Shell
scp -r Shell/* user@your-server:/var/www/DesignerCEP/Server/static/shell/
# 上传 Core
scp -r Designer/* user@your-server:/var/www/DesignerCEP/Server/static/core/1.0.6/
# 打包并上传 Shell.zip
zip -r shell-1.0.6.zip Shell/
scp shell-1.0.6.zip user@your-server:/var/www/DesignerCEP/Server/static/downloads/
```
#### 方法 B: 使用自动化脚本
```bash
# 在项目根目录执行
cd AdminTool
# 首次配置
python auto_deploy_core.py --version 1.0.6 --setup
# 输入配置信息:
# - 服务器地址: your-domain.com
# - SSH 端口: 22
# - 用户名: root
# - 密码: ******
# - 远程路径: /var/www/DesignerCEP/Server/static
# 部署
python auto_deploy_core.py --version 1.0.6 --deploy --update-db
```
---
## ✅ 7. 启动 Caddy
```bash
# 检查配置语法
sudo caddy validate --config /etc/caddy/Caddyfile
# 重启 Caddy
sudo systemctl restart caddy
# 查看状态
sudo systemctl status caddy
# 查看日志
sudo journalctl -u caddy -f
```
---
## 🧪 8. 测试部署
### 8.1 测试静态文件
```bash
# 测试 Shell
curl -I https://your-domain.com/shell/
# 期望输出:
# HTTP/2 200
# content-type: text/html
# 测试 Core
curl -I https://your-domain.com/core/1.0.6/
# 测试下载
curl -I https://your-domain.com/downloads/shell-1.0.6.zip
```
### 8.2 测试 API
```bash
# 测试健康检查
curl https://your-domain.com/api/v1/health
# 期望输出:
# {"status":"healthy"}
# 测试登录接口
curl -X POST https://your-domain.com/api/v1/client/login \
-H "Content-Type: application/json" \
-d '{"username":"test","password":"test","device_id":"test-device"}'
```
### 8.3 测试 CEP CORS
```bash
# 模拟 CEP 环境的预检请求
curl -X OPTIONS https://your-domain.com/api/v1/client/login \
-H "Origin: null" \
-H "Access-Control-Request-Method: POST" \
-v
# 期望输出:
# Access-Control-Allow-Origin: *
# Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
```
### 8.4 浏览器测试
1. 打开 `https://your-domain.com/shell/`
2. 输入用户名密码登录
3. 按 F12 打开开发者工具
4. 查看 Network 标签:
- ✅ 所有请求都是 HTTPS
- ✅ API 请求有 `Authorization: Bearer xxx`
- ✅ 没有 CORS 错误
---
## 🔍 9. 常见问题
### Q1: Caddy 自动证书失败
**原因**:域名 DNS 没有解析到服务器
**解决**
```bash
# 检查 DNS 解析
nslookup your-domain.com
# 确保 A 记录指向服务器 IP
dig your-domain.com
```
### Q2: API 请求 502 Bad Gateway
**原因**FastAPI 服务没有启动
**解决**
```bash
# 检查 FastAPI 状态
sudo systemctl status designer-cep
# 查看日志
sudo journalctl -u designer-cep -f
# 重启服务
sudo systemctl restart designer-cep
```
### Q3: 静态文件 404
**原因**:文件路径不对
**解决**
```bash
# 检查文件是否存在
ls -la /var/www/DesignerCEP/Server/static/shell/
ls -la /var/www/DesignerCEP/Server/static/core/1.0.6/
# 检查权限
sudo chown -R www-data:www-data /var/www/DesignerCEP
sudo chmod -R 755 /var/www/DesignerCEP
```
### Q4: Cloudflare 无限重定向
**原因**Cloudflare SSL 模式设置错误
**解决**
1. 进入 Cloudflare 控制台
2. SSL/TLS → Overview
3. 选择 **"Flexible"** 模式(因为 Caddy 配置的是 HTTP
或者修改 Caddyfile 使用 HTTPS
```caddy
# 方法 1: 自签名证书
your-domain.com {
tls internal
# ... 其他配置
}
# 方法 2: Cloudflare Origin Certificate
your-domain.com {
tls /etc/caddy/certs/cloudflare-origin.pem /etc/caddy/certs/cloudflare-origin.key
# ... 其他配置
}
```
---
## 🎯 10. Cloudflare 最佳实践
### 10.1 SSL/TLS 配置
```
Cloudflare 控制台 → SSL/TLS → Overview
选择模式:
- ❌ Off - 不使用 HTTPS
- ✅ Flexible - Cloudflare ←HTTPS→ 用户Cloudflare ←HTTP→ 源站(推荐简单部署)
- ⚠️ Full - 双向 HTTPS但不验证源站证书
- ✅ Full (Strict) - 双向 HTTPS验证证书推荐生产环境
```
**推荐方案**
#### Flexible 模式(最简单)
```caddy
# Caddyfile
{
auto_https off
}
http://your-domain.com {
# ... 配置
}
```
Cloudflare 设置:`Flexible`
#### Full (Strict) 模式(最安全)
1. 生成 Cloudflare Origin Certificate
```
Cloudflare 控制台 → SSL/TLS → Origin Server → Create Certificate
```
2. 下载证书并上传到服务器:
```bash
sudo mkdir -p /etc/caddy/certs
sudo nano /etc/caddy/certs/cloudflare-origin.pem # 粘贴证书
sudo nano /etc/caddy/certs/cloudflare-origin.key # 粘贴私钥
sudo chmod 600 /etc/caddy/certs/*
```
3. 修改 Caddyfile
```caddy
your-domain.com {
tls /etc/caddy/certs/cloudflare-origin.pem /etc/caddy/certs/cloudflare-origin.key
# ... 其他配置
}
```
4. Cloudflare 设置:`Full (Strict)`
### 10.2 性能优化
```
Cloudflare 控制台设置:
1. Speed → Optimization
- ✅ Auto Minify: HTML, CSS, JS
- ✅ Brotli 压缩
2. Caching → Configuration
- ✅ Caching Level: Standard
- ✅ Browser Cache TTL: Respect Existing Headers
3. Network
- ✅ HTTP/2
- ✅ HTTP/3 (QUIC)
```
---
## 📊 11. 监控和日志
### 查看 Caddy 日志
```bash
# 实时日志
sudo journalctl -u caddy -f
# 访问日志
sudo tail -f /var/log/caddy/designer-cep.log
# 错误日志
sudo journalctl -u caddy --since "1 hour ago" | grep -i error
```
### 查看 FastAPI 日志
```bash
# 实时日志
sudo journalctl -u designer-cep -f
# 最近的错误
sudo journalctl -u designer-cep --since "1 hour ago" | grep -i error
```
### 性能监控
```bash
# 检查服务器资源
htop
# 检查端口监听
sudo netstat -tlnp | grep -E '(8000|80|443)'
# 检查 Caddy 进程
ps aux | grep caddy
```
---
## 🔄 12. 更新部署
### 更新前端
```bash
# 方法 A: 手动
cd Designer
npm run build
scp -r dist/Shell/* user@server:/var/www/DesignerCEP/Server/static/shell/
scp -r dist/Designer/* user@server:/var/www/DesignerCEP/Server/static/core/1.0.7/
# 方法 B: 自动化
cd AdminTool
python auto_deploy_core.py --version 1.0.7 --deploy --update-db
```
### 更新后端
```bash
# 1. SSH 到服务器
ssh user@your-server
# 2. 拉取最新代码
cd /var/www/DesignerCEP/Server
git pull
# 3. 更新依赖
source venv/bin/activate
pip install -r requirements.txt
# 4. 重启服务
sudo systemctl restart designer-cep
# 5. 检查状态
sudo systemctl status designer-cep
```
### 更新 Caddy 配置
```bash
# 1. 修改配置
sudo nano /etc/caddy/Caddyfile
# 2. 验证配置
sudo caddy validate --config /etc/caddy/Caddyfile
# 3. 重新加载(不中断服务)
sudo systemctl reload caddy
```
---
## 🎉 完成!
访问地址:
- **Shell 登录页**`https://your-domain.com/shell/`
- **Core 应用**`https://your-domain.com/core/1.0.6/`
- **API 文档**`https://your-domain.com/api/v1/docs`
---
## 📋 部署检查清单
- [ ] Caddy 已安装并启动
- [ ] Caddyfile 配置正确
- [ ] FastAPI 服务运行正常
- [ ] 前端文件已上传
- [ ] 环境变量配置正确
- [ ] CORS 支持 CEP 环境
- [ ] MySQL 数据库已更新
- [ ] HTTPS 证书正常
- [ ] 静态文件缓存正确
- [ ] API 请求正常
- [ ] CEP 扩展测试通过
- [ ] 浏览器访问正常
---
**Caddy 的优势总结**
- ✅ 配置只有 Nginx 的 1/3 长度
- ✅ 自动 HTTPS无需 Certbot
- ✅ 自动续期证书
- ✅ 内置 GZIP/Brotli 压缩
- ✅ 配置更直观易懂
- ✅ 错误提示更友好
**下一步**:运行 `python auto_deploy_core.py --version 1.0.6 --deploy --update-db` 一键部署!

View File

@@ -0,0 +1,193 @@
# JSX 加载失败问题分析与解决方案
## 📋 问题描述
在 Shell + Core 架构分离后Core 应用中的 JSX 脚本无法正常运行,报错:
```
Error: EvalScript error.
```
尽管 JSX 文件路径找到了(`C:/Users/35780/AppData/Roaming/DesignerCache/v1.0.5/jsx/index.js`),但 `evalScript` 执行失败。
## 🔍 根本原因
### 问题1JSX 构建格式错误
**位置**`Designer/plugins/buildJsx/index.ts` 第 118 行
```typescript
await bundle.write({
file: output,
format: 'cjs' // ❌ 问题所在!
});
```
**影响**:生成的 JSX 文件使用了 **CommonJS 格式**,包含:
```javascript
Object.defineProperty(exports, "__esModule", { value: true });
var index_1 = require("./function/index"); // ❌ ExtendScript 不支持 require()
var ActionManager = require("./utils/ActionManager");
```
### 问题2ExtendScript 环境限制
**ExtendScript (Adobe JSX 引擎) 不支持:**
- ❌ CommonJS 的 `require()``exports`
- ❌ ES6 的 `import/export`
-`'use strict'` 严格模式
**ExtendScript 只支持:**
- ✅ 全局函数
- ✅ 立即执行函数表达式 (IIFE)
- ✅ ES3/ES5 语法
## ✅ 解决方案
### 方案 1修改构建配置长期方案
修改 `Designer/plugins/buildJsx/index.ts`
```typescript
await bundle.write({
file: output,
format: 'iife', // ✅ 改为 IIFE 格式
name: 'JSXBundle',
strict: false // ✅ 禁用严格模式
});
```
**同时修改 TypeScript 配置**
```typescript
typescript({
compilerOptions: {
target: 'es5',
strict: false,
moduleResolution: 'node',
esModuleInterop: true,
skipLibCheck: true,
// ...
},
tsconfig: false,
}),
```
### 方案 2临时修复已应用
**已完成的操作:**
1. ✅ 创建了简化版本的 JSX 文件 (`index_fixed.js`)
2. ✅ 移除了所有 CommonJS 语法
3. ✅ 将依赖项Logger, ActionManager内联到单文件中
4. ✅ 备份原文件为 `index.js.backup`
5. ✅ 替换为修复后的版本
**文件位置**
- 修复后:`Designer/dist_core/jsx/index.js`
- 备份:`Designer/dist_core/jsx/index.js.backup`
## 🧪 测试步骤
### 1. 清除客户端缓存
```powershell
Remove-Item -Recurse -Force "$env:APPDATA\DesignerCache" -ErrorAction SilentlyContinue
```
### 2. 重启插件
在 Photoshop 中:
1. 关闭当前的 DesignerCEP 插件
2. 重新打开插件
3. 登录账号
### 3. 检查控制台
打开浏览器开发者工具F12查看
**预期成功日志:**
```
[__LDX] Detected Core version v1.0.5
[__LDX] Success: C:/Users/35780/AppData/Roaming/DesignerCache/v1.0.5/jsx/index.js
宿主APP名称Adobe Photoshop 2024
```
**如果仍然失败,查看:**
- 是否有 `EvalScript error`
- ExtendScript 控制台的详细错误信息
### 4. 测试功能
在插件主界面点击"测试 JSX 调用"按钮,验证:
-`getAppName()` 返回 "Adobe Photoshop"
-`createLayer()` 可以创建新图层
-`testMergeLayers()` 可以合并图层
## 📝 后续工作
### 短期(紧急)
- [ ] 测试修复后的 JSX 是否正常工作
- [ ] 如果测试通过,将修复后的文件上传到服务器
### 中期(优化)
- [ ] 修复构建流程,确保未来构建自动生成正确格式
- [ ] 解决 `g_logger``ActionManager` 的依赖打包问题
- [ ] 更新 `deploy_core.py` 脚本,包含 JSX 构建步骤
### 长期(架构改进)
- [ ] 评估是否需要保留完整的 Logger 和 ActionManager
- [ ] 考虑使用 Rollup 的 `inlineDynamicImports` 选项
- [ ] 编写自动化测试验证 JSX 在 ExtendScript 环境中的兼容性
## 🔗 相关文件
| 文件路径 | 说明 |
|:---------|:-----|
| `Designer/plugins/buildJsx/index.ts` | JSX 构建配置已修改format为iife |
| `Designer/dist_core/jsx/index.js` | 修复后的 JSX 文件 |
| `Designer/dist_core/jsx/index.js.backup` | 原始的 CommonJS 格式备份 |
| `Designer/build_jsx_simple.mjs` | 简化的构建脚本(待完善) |
## 💡 技术要点
### ExtendScript 兼容性清单
```javascript
允许:
- var, function, 普通对象
- IIFE: (function(){ ... })();
- 全局赋值: $.global.xxx = function(){}
- ES5 方法: Array.forEach, Object.keys
禁止:
- 'use strict'
- require() / exports / module.exports
- import / export
- const / let (部分版本不支持)
- Promise / async/await ( polyfill)
- 箭头函数 (部分版本)
```
### Rollup 输出格式对比
| 格式 | ExtendScript | 说明 |
|:-----|:------------|:-----|
| `cjs` | ❌ | CommonJS使用 require/exports |
| `esm` | ❌ | ES Module使用 import/export |
| `iife` | ✅ | 立即执行函数,所有代码在一个作用域 |
| `umd` | ⚠️ | 通用模块,体积较大但兼容性好 |
## 📞 联系与支持
如果测试中遇到问题,请提供:
1. 浏览器控制台的完整错误日志
2. ExtendScript Toolkit 的错误信息(如果有)
3. Photoshop 版本和操作系统信息
---
**报告生成时间**2025-12-16
**问题状态**:✅ 已修复(待测试验证)

View File

@@ -0,0 +1,128 @@
# JSX 方法测试指南
## 🎯 目标
测试 5 种不同的 JSX 实现方法,找出在 ExtendScript 环境中能正常运行的方式。
## 📋 测试方法列表
| 方法 | 实现方式 | 特点 |
|:-----|:---------|:-----|
| **方法1** | DOM API | 最简单,直接使用 PS 的 DOM 对象 |
| **方法2** | ActionManager | 使用 ActionDescriptor更底层 |
| **方法3** | Object Helper | 使用对象字面量封装 |
| **方法4** | ES3 Class | 使用原型链的类实现 |
| **方法5** | PSApi Style | 模仿完整的 PSApi 结构 |
## 🧪 快速测试步骤
### 1. 替换 JSX 文件
```powershell
# 方式A直接复制到缓存推荐
Copy-Item Designer\test_jsx_methods.js "$env:APPDATA\DesignerCache\v1.0.7\jsx\index.js" -Force
# 方式B或者重新打包上传
# 如果方式A不行说明缓存路径不对
```
### 2. 在浏览器控制台测试
打开 Photoshop 插件,按 F12 打开控制台,逐个测试:
```javascript
// 测试方法1DOM API
cep.evalScript("createLayerMethod1('测试图层1')").then(r => console.log('方法1:', r))
// 测试方法2ActionManager
cep.evalScript("createLayerMethod2('测试图层2')").then(r => console.log('方法2:', r))
// 测试方法3Object Helper
cep.evalScript("createLayerMethod3('测试图层3')").then(r => console.log('方法3:', r))
// 测试方法4ES3 Class
cep.evalScript("createLayerMethod4('测试图层4')").then(r => console.log('方法4:', r))
// 测试方法5PSApi Style
cep.evalScript("createLayerMethod5('测试图层5')").then(r => console.log('方法5:', r))
// 测试获取图层数量
cep.evalScript("getLayerCount()").then(r => console.log('图层数量:', r))
```
### 3. 观察结果
**成功的结果示例:**
```json
{
"success": true,
"method": "DOM API",
"layerName": "测试图层1"
}
```
**失败的结果示例:**
```json
{
"error": "..."
}
```
## 📊 预期哪个会成功?
根据 ExtendScript 的特性,**最可能成功的是**
1.**方法1DOM API** - 最简单直接
2.**方法3Object Helper** - 使用对象字面量
3. ⚠️ **方法2ActionManager** - 可能因为 API 调用问题失败
4. ⚠️ **方法4ES3 Class** - 依赖原型链
5. ⚠️ **方法5PSApi Style** - 最复杂,可能有嵌套问题
## 🔧 如果都失败了
如果所有方法都失败,可能的原因:
1. **JSX 文件没有被加载**
- 检查控制台是否有 `Test JSX Methods Loaded` 日志
2. **路径不对**
- 检查 `[__LDX]` 日志显示的路径
- 手动找到该路径,确认文件内容
3. **ExtendScript 语法错误**
- 在 ExtendScript Toolkit 中打开文件检查
## 💡 成功后的下一步
找到能工作的方法后:
1. 📝 **记录成功的方法**(如"方法1成功"
2. 🔄 **使用该方法重写所有 JSX 函数**
3. 📦 **重新构建和打包 Core**
4. 🚀 **发布到服务器**
## 🎨 添加前端测试按钮(可选)
`Home.vue` 中添加测试按钮:
```vue
<a-button @click="testMethod1">测试方法1</a-button>
<a-button @click="testMethod2">测试方法2</a-button>
<a-button @click="testMethod3">测试方法3</a-button>
<a-button @click="testMethod4">测试方法4</a-button>
<a-button @click="testMethod5">测试方法5</a-button>
```
```typescript
const testMethod1 = async () => {
const res = await jsxApi.evalScript("createLayerMethod1('测试1')");
console.log('方法1结果:', res);
Message.info(res);
};
// ... 类似地添加其他方法
```
---
**现在开始测试吧!** 告诉我哪个方法能成功 🎯

View File

@@ -0,0 +1,212 @@
# PSApi 功能测试指南
## 📋 已完成的修改
### 1. JSX 端(`src/jsx/index.ts`
**添加的内容:**
```typescript
import { PSApi } from "./function/ps/api" // 导入 PSApi 类
const psApi = new PSApi() // 实例化 PSApi
// 新增测试函数
export function testPSApi() {
try {
const selectLength = psApi.layer.getSelectLength(); // 调用 PSApi 的方法
return JSON_EX.stringify({
success: true,
message: "PSApi 调用成功!",
selectLength: selectLength,
info: "当前选中的图层数量: " + selectLength
});
} catch (error: any) {
return JSON_EX.stringify({ error: error.toString() });
}
}
// 暴露到全局
($.global as any).testPSApi = testPSApi;
```
### 2. 前端 API`src/api/jsxApi/evalJSX.ts`
**添加的内容:**
```typescript
testPSApi: async () => {
const jsonStr = await cep.evalScript("testPSApi()");
const res = JSON.parse(jsonStr);
return res;
}
```
### 3. 前端界面(`src/view/Home.vue`
**添加的内容:**
- ✅ 新增"测试 PSApi"按钮
- ✅ 添加 `handleTestPSApi()` 处理函数
## 🧪 测试步骤
### 步骤 1重新构建如果使用简化版
如果您还在使用临时的简化版 JSX现在可以
**选项 A使用简化版测试推荐先测**
```powershell
# 当前的 dist_core/jsx/index.js 已经包含简化版
# 直接测试即可
```
**选项 B重新构建完整版包含 PSApi**
```powershell
cd D:\main\DesignerCEP\Designer
# 重新构建 Core会包含新的 testPSApi 函数)
npm run build:core
# 如果构建失败,需要先修复构建流程
# 或者手动更新 dist_core/jsx/index.js
```
### 步骤 2清除缓存
```powershell
Remove-Item -Recurse -Force "$env:APPDATA\DesignerCache" -ErrorAction SilentlyContinue
```
### 步骤 3在 Photoshop 中测试
1. **打开 Photoshop**
2. **打开 DesignerCEP 插件**
3. **登录账号**(会下载最新的 Core
4. **在主界面点击"测试 PSApi"按钮**
### 步骤 4观察结果
**预期成功的结果:**
**控制台日志:**
```
[__LDX] Detected Core version v1.0.5
[__LDX] Success: C:/Users/.../jsx/index.js
Call testPSApi - Testing PSApi from api.ts
```
**前端界面:**
- 弹出成功提示:`PSApi 调用成功! - 当前选中的图层数量: 1`
- 显示选中图层数(如果没选图层则为 0
**如果失败:**
- 查看浏览器控制台的错误信息
- 查看 ExtendScript Toolkit 的错误(如果有)
## 📊 测试场景
### 场景 1没有选中图层
- **操作**:不选择任何图层,点击"测试 PSApi"
- **预期**:显示"选中图层数: 0"
### 场景 2选中 1 个图层
- **操作**:在 PS 中选中 1 个图层,点击"测试 PSApi"
- **预期**:显示"选中图层数: 1"
### 场景 3选中多个图层
- **操作**:按住 Ctrl/Cmd 选中多个图层,点击"测试 PSApi"
- **预期**:显示"选中图层数: N"N 为实际选中数量)
## 🎯 测试的意义
### 如果测试成功,证明:
1.**可以在 JSX 中使用完整的 `api.ts`**
2.**PSApi 类的方法可以正常调用**
3.**面向对象的写法在 ExtendScript 中运行正常**
4.**可以放心使用 api.ts 中的所有功能**
### 如果测试成功,您可以:
```typescript
// 在 src/jsx/index.ts 中使用任何 PSApi 的功能
// 示例 1图层操作
export function selectLayerById(id: number) {
psApi.layer.onIdSelectLayer(id);
return JSON_EX.stringify({ success: true });
}
// 示例 2文档操作
export function getDocumentSize() {
const size = psApi.doc.getSize();
return JSON_EX.stringify({
width: size.cx,
height: size.cy
});
}
// 示例 3颜色操作
export function setFillColor(hex: string) {
const col = new SolidColor();
col.rgb.hexValue = hex;
psApi.activeLayer.shape.setFillColor(true, col);
return JSON_EX.stringify({ success: true });
}
// 示例 4字体操作
export function changeFontFamily(postScriptName: string, name: string, style: string) {
psApi.font.setFontName(postScriptName, name, style);
return JSON_EX.stringify({ success: true });
}
```
## 🚨 常见问题
### Q1: 点击按钮没有反应
**原因:** JSX 未加载或函数未暴露到全局
**解决:**
- 检查控制台是否有 `[__LDX] Success` 日志
- 确认 `testPSApi` 已添加到 `$.global`
### Q2: 报错 "PSApi is not defined"
**原因:** 构建时未包含 `api.ts`
**解决:**
- 确认 `src/jsx/index.ts` 中有 `import { PSApi } from "./function/ps/api"`
- 重新构建或检查构建配置
### Q3: 报错 "getSelectLength is not a function"
**原因:** PSApi 的方法没有正确编译
**解决:**
- 检查 `api.ts` 是否有语法错误
- 确认构建格式为 IIFE我们已修改
- 查看 `dist_core/jsx/index.js` 是否包含 PSApi 的代码
### Q4: 构建时报错 "g_logger is not exported"
**原因:** 模块打包问题(我们之前遇到的)
**解决:**
- 暂时使用简化版测试
- 或者手动修复构建流程
- 或者等待我们修复完整版构建
## 💡 下一步计划
### 如果测试通过:
1. ✅ 确认可以使用完整的 `api.ts`
2. 🔄 修复完整版的构建流程
3. 📦 重新打包包含所有功能的 Core
4. 🚀 上传到服务器供用户使用
### 如果测试失败:
1. 📝 记录详细的错误信息
2. 🔍 分析是构建问题还是代码问题
3. 🔧 逐步调试修复
---
**准备好了吗?开始测试吧!** 🚀
测试完成后,请告诉我:
- ✅ 测试成功还是失败
- 📋 控制台的完整日志
- 💬 遇到的任何问题或错误信息

View File

@@ -0,0 +1,80 @@
# 🚀 DesignerCEP 快速部署
## 📐 架构
```
app.aidg168.uk → 前端Caddy
backend.aidg168.uk → 后端 APIFastAPI + MySQL
```
---
## 🎯 快速部署3 步)
### 步骤 1本地构建
```powershell
# 构建前端
cd Designer
npm run build:core
# 复制到 Server
cd ..
xcopy /E /Y Designer\dist_core\* Server\static\app\
# 打包 Server
Compress-Archive -Path Server\* -DestinationPath Server.zip -Force
```
### 步骤 2上传到服务器
```powershell
# 上传文件
scp Server.zip root@103.97.201.136:/root/
scp Caddyfile root@103.97.201.136:/etc/caddy/Caddyfile
```
### 步骤 3服务器部署
```bash
# SSH 登录
ssh root@103.97.201.136
# 解压
cd /root
unzip -o Server.zip -d server
# 启动服务
cd server
docker-compose up -d
# 查看日志
docker-compose logs -f
```
---
## ✅ 验证
访问:`https://app.aidg168.uk/`
---
## 📁 服务器目录结构
```
/root/server/
├── app/ ← 后端代码
├── static/ ← 前端文件
│ └── app/ ← 前端构建产物
│ ├── index.html
│ └── assets/
├── docker-compose.yml
├── Dockerfile
└── .env
```
---
详细文档:查看 `Server/部署到服务器.md`

View File

@@ -0,0 +1,83 @@
# Shell 架构改造任务清单 (增强版)
基于您提供的 `serveradmin``upload` 逻辑,我们引入了 **“用户分组 (Gray Release)”** 和 **“一键发布 (CI/CD)”** 机制。
## 1. 插件端 (Frontend - Shell)
Shell 端不需要太大变化,主要是版本检查接口需要携带用户信息。
- `src/launcher/utils/`
- [ ] **增强版版本检查**:
- 请求 `POST /api/check_update`
- 参数: `{ username: "当前登录用户" }`
- 逻辑: 服务端会根据用户所在的组,返回不同的版本。普通用户返回 `v1.0`,测试组用户返回 `v1.1-beta`
## 2. 服务端 (Backend - Python)
参考 `serveradmin` 逻辑进行改造。
### 2.1 数据库结构升级
需要支持不同用户获取不同版本。
- **表 `psmark_group` (用户组)**:
- `id`: int (主键)
- `name`: string (组名,如 "Stable", "Beta")
- `current_version`: string (该组当前使用的版本文件名,如 "release_v1.0.zip")
- `comment`: string
- **表 `user` (原有表增强)**:
- `group_id`: int (外键,关联 psmark_group)
### 2.2 接口逻辑增强
- `POST /api/check_update`:
1. 根据 `username``group_id`
2.`psmark_group` 表获取该组的 `current_version`
3. 返回该版本的下载地址。
- **优势**: 您可以在后台把某个测试用户切到 "DevGroup",他重启插件就会立刻下载最新的测试版,而其他用户不受影响。
---
## 3. 发布流程 (Publishing Workflow)
为了实现“上传时编译”,我们将编写一个自动化脚本。
### 3.1 自动化发布脚本 (`scripts/publish.ts`)
这个脚本将在您的开发机上运行,一键完成所有操作。
- **执行流程**:
1. **Compile**: 运行 `npm run build:core` (生成 `dist/core`)。
2. **Package**: 使用 `adm-zip``dist/core` 压缩为 `code_{timestamp}.zip`
3. **Upload**: 调用服务端接口 `POST /archives` 将 ZIP 上传。
4. **Register (可选)**: 自动调用接口将新版本注册到数据库(可选)。
- **命令**:
```bash
npm run publish # 一键发布
```
### 3.2 服务端支持
- 确保 `POST /archives` 接口能接收文件并保存到 `archives/` 目录 (参考 `server.py` 已有逻辑)。
---
## 4. 后台管理工具 (Admin Tool)
您可以直接复用或升级现有的 PyQt 工具 (`upload/admin.py`)。
- **功能需求**:
- [ ] **用户分理**: 设置用户的 `group_id`。
- [ ] **版本指派**: 设置某个 Group 使用哪个 ZIP 版本。
---
## 5. 执行路线图
1. **Day 1 (Publishing)**: 写好 `scripts/publish.ts`,确保能一键编译并上传 ZIP 到您的服务器。
2. **Day 2 (Backend)**: 改造 Python 后端,加入 Group 表,在此基础上实现“根据用户返回版本”的接口。
3. **Day 3 (Shell)**: 开发插件 Shell 的下载器,对接上述接口。

View File

@@ -0,0 +1,88 @@
# 壳架构 (Shell Architecture) 改造计划
为了实现“登录即更新”和“代码保护”,我们将把现有的 `DesignerCEP` 单体项目拆分为 **壳 (Shell)****核 (Core)** 两部分。
## 1. 架构概览
| 模块 | 名称 | 职责 | 部署位置 | 更新频率 |
| :-------- | :---------------- | :--------------------------------------------------------------------------------------------------- | :--------------------------------------- | :------------------------------- |
| **Shell** | 启动器 (Launcher) | 1. 负责 CEP 扩展的安装与注册<br>2. 负责用户登录鉴权<br>3. **下载并解压** Core 包<br>4. 跳转加载 Core | **本地用户电脑**<br>(安装在 PS 扩展目录) | 极低<br>(仅当下载逻辑变更时更新) |
| **Core** | 业务核 (Business) | 1. 包含所有 Vue 业务页面<br>2. 包含核心 JSX 脚本<br>3. 通过 Shell 注入的变量与宿主交互 | **远程服务器**<br>(ZIP 压缩包) | 极高<br>(随时发布新功能) |
---
## 2. 目录结构改造建议
目前的 `Designer/` 目录将作为**核心业务仓库**,我们需要在其中增加一个专门构建 Shell 的入口。
```
Designer/
├── package.json
├── vite.config.ts # [Core配置] 构建业务代码 (输出 dist/core)
├── vite.shell.config.ts # [Shell配置] 构建启动器代码 (输出 dist/shell)
├── src/
│ ├── main.ts # [Core入口] 业务主入口 (现在的代码)
│ ├── App.vue # [Core] 业务主界面
│ │
│ └── launcher/ # [Shell] 启动器模块 (NEW)
│ ├── main.ts # [Shell入口]
│ ├── App.vue # [Shell] 登录与更新进度条界面
│ ├── updater.ts # [Shell] 下载/解压/校验逻辑 (Node.js)
│ └── login.ts # [Shell] 登录逻辑
└── dist/
├── shell/ # 打包结果:这是给用户安装的 ZXP
│ ├── CSXS/manifest.xml
│ ├── index.html (登录器)
│ └── node_modules (含 fs/adm-zip 等必要依赖)
└── core/ # 打包结果:这是传到服务器的 UPDATE.zip
├── index.html (业务界面)
├── assets/
└── jsx/ (加密后的 JSX)
```
## 3. 核心运行流程 (Workflow)
### 3.1 启动阶段 (Shell)
1. 用户点击 PS 菜单,加载本地的 `dist/shell/index.html`
2. **Shell App** 启动,不再加载复杂的业务组件,只显示登录框。
3. 用户输入账号密码 -> 请求服务器 `/api/login`
4. 登录成功 -> 获取 Token 和 **最新 Core 版本号** (比如 `v1.0.2`)。
### 3.2 更新阶段 (Updater)
1. 检查本地 `C:/Users/xxx/AppData/Roaming/DesignerCache/v1.0.2/` 是否存在。
2. **不存在 (有更新)**:
- 显示进度条。
- 从 CDN/服务器下载 `core-v1.0.2.zip`
- 使用 Node.js (`adm-zip`) 解压到上述目录。
3. **存在**: 直接跳过。
### 3.3 注入阶段 (Launch)
1. **加载 JSX**:
- Shell 调用 `CSInterface.evalScript`
- 读取缓存目录下的 `v1.0.2/jsx/index.jsx` 内容。
- 执行 `$.evalFile("C:/.../v1.0.2/jsx/index.jsx")`,把业务函数注入此 ExtendScript 上下文。
2. **加载 UI**:
- Shell 执行 `window.location.href = "file:///C:/.../v1.0.2/index.html"`
3. **接管**:
- 浏览器跳转,页面变成业务界面。
- 此时 `window.cep` 对象依然存在,业务界面可以继续调用刚刚注入的 JSX 函数。
## 4. 兼容性评估
**现有框架可以完全兼容,只需做以下调整**
1. **Vite 配置拆分**: 需要新增一个 `vite.shell.config.ts`,专门用来打包轻量级的 Shell。
2. **Node.js 依赖**: Shell 需要打包进 `adm-zip` (解压用) 和 `axios` (下载用)。由于 CEP 支持 Node.js这完全没问题。
3. **路由模式**: 业务代码 (Core) 读取本地文件时,路由最好使用 `Hash` 模式 (`createWebHashHistory`),避免 file 协议下的路径问题。
## 5. 下一步行动计划
1. **[Shell]** 创建 `src/launcher` 目录,编写简单的登录+下载器 Demo。
2. **[Build]** 配置 `vite.shell.config.ts`,尝试打包出一个只包含登录功能的 ZXP。
3. **[Test]** 模拟远程更新,手动把现在的业务代码打包成 ZIP 放在本地服务器,测试 Shell 能否下载并跳转。

View File

@@ -0,0 +1,89 @@
# psmark 脚本分析与功能报告
## 1. 概述
`psmark` 目录下发现的 `JSX*.py` 文件JSX2.py - JSX9.py主要承担了**Photoshop 自动化脚本容器**的角色。
这些 Python 文件本身不包含图像处理逻辑,而是作为 Adobe ExtendScript (JSX) 代码的“包装壳”。所有的核心自动化逻辑(如图层操作、裁片缩放、排版等)都由嵌入的 JSX 字符串实现。
## 2. 文件功能详细清单
| 文件名 | 核心功能 | 详细描述 |
| :----------- | :-------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **JSX2.py** | **裁片射出宽高缩放** | - 遍历文档图层,根据图层名称(包含尺寸信息)进行精确缩放。<br>- 包含像素与毫米的自动换算。<br>- 处理 90 度/180 度 旋转逻辑。<br>- 使用 `ActionManager` 进行高效的选区和变换操作。 |
| **JSX3.py** | **设置花样组顺序居中** | - 专用于印花对位。<br>- 自动识别 "P" 开头的花样图层。<br>- 将花样层对齐到“大货裁片”组的中心位置。<br>- 包含蒙版选区获取和坐标计算逻辑。 |
| **JSX4.py** | **图像切割** | - 根据特定图层的边界对文档进行裁剪。<br>- 包含出血/边距处理(例如自动从选区扩展一定像素)。<br>- 适用于从大图中提取独立裁片。 |
| **JSX5.py** | **图像切割 (变体)** | - 功能与 JSX4 类似,侧重于特定图层组的裁剪和蒙版应用。 |
| **JSX6.py** | **裁片射出宽高缩放 (变体)** | - 与 JSX2 类似,提供裁片缩放和定位功能。<br>- 强调了“前景色修改”和“图层选择”等辅助操作。 |
| **JSX7.py** | **裁片射出缩放模板** | - **关键特性**:引入了“**缩放定位点**”图层概念。<br>- 不以图层中心为原点,而是根据“缩放定位点”图层的选区中心进行缩放变换。<br>- 包含 `烧花线添加` 功能,用于绘制剪口和标记线。 |
| **JSX8.py** | **批量化替换外链新 (UI)** | - **全自动化工作流脚本**。<br>- **内置 UI**:包含一个 ScriptUI 编写的对话框,用于选择文件夹路径。<br>- **功能**<br> 1. 批量打开文件。<br> 2. 自动替换智能对象链接(换花型)。<br> 3. `码标添加`:自动生成包含文件名的尺寸标记。<br> 4. `花样标准化`:统一花样大小并添加白底。<br> 5. 自动另存为 TIF 格式并归档。 |
| **JSX9.py** | **按中心点比例缩放** | - 结合了 JSX2 和 JSX7 的特性。<br>- 专注于按“缩放定位点”进行**等比例**或**宽高独立**缩放。<br>- 包含详细的 `ActionManager` 变换代码(`transform` 命令)。 |
| **JSX16.py** | **花样图层导出** | - 遍历图层组(特别是“填充底图”),将子图层导出为 TIF。<br>- 处理“最大白边值”图层以确定裁剪边界。<br>- 包含智能对象转换、重新链接、蒙版合并等复合操作。 |
| **JSX17.py** | **定位码批量快速换图 (UI)** | - **带 ScriptUI**。<br>- 全自动批量流程:选择模板路径、素材路径、输出路径。<br>- 遍历素材图片,打开模板,替换智能对象(`placedLayerRelinkToFile`),应用预设图案填充,合并图层并另存。<br>- 支持读取 JSON 数据来匹配图案名称。 |
| **JSX18.py** | **龙服快速换图 (UI)** | - **带 ScriptUI**(定制版本)。<br>- 读取文件夹文件列表,允许用户在 UI 中输入“数量”信息。<br>- 根据输入信息更新图层文本内容,替换裁片智能对象链接,合并并保存。 |
| **JSX19.py** | **裁片抓取与导入** | - 批量置入 PDF 文件。<br>- **自动旋转特性**:通过解析文件名(如包含 `_180`)自动判断是否需要旋转 180 度导入。<br>- 自动建立“大货裁片”组并归档。 |
| **JSX20.py** | **混合裁片导出** | - 遍历包含“大货裁片”名称的图层组。<br>- 逐个提取子图层,应用蒙版,裁切到边界,导出为 TIF 文件。<br>- 使用历史记录回退(`historyState`)来恢复状态以处理下一个图层。 |
| **JSX21.py** | **裁片排版基础** | - 基础排版功能库。<br>- `创建裁片排版文档`:按毫米创建新画布。<br>- `置入链接的智能对象`:将外部文件作为链接对象置入。<br>- `裁片排版_lay`:提供基于毫米坐标的精确位移功能。 |
| **JSX22.py** | **模特换衣功能 (UI)** | - **带 ScriptUI**。<br>- 针对模特展示图的批量替换。<br>- 遍历素材目录,替换模板中名为“替换对象”的智能对象。<br>- 支持保持原始目录结构导出支持切片导出Web 切片)。 |
| **JSX23.py** | **S/O 样自动连晒 (UI)** | - **带 ScriptUI**。<br>- 自动生成连晒(米样)效果。<br>- 将图片定义为图案,填充到指定宽高的文档中(平铺),并添加对应的文件名文字标签。<br>- 保存两份:一份纯拼贴,一份带文字标签。 |
| **JSX24.py** | **自动米样拼贴 (UI)** | - **带 ScriptUI**。<br>- 与 JSX23 类似但逻辑不同,这是真正的**拼图**Collage。<br>- 读取文件夹所有图片,按高度排序。<br>- 自动计算总画布大小,将图片紧凑排列(换行逻辑)拼贴到一个大文档中。 |
| **JSX26.py** | **模特多色换图** | - 利用**快照**Snapshot机制进行批量处理。<br>- 遍历文件夹素材,替换“贴图位置”组内的图层内容。<br>- 支持对替换后的图案进行位移Offset以制作不同花位效果。<br>- 导出 TIF 后回退到快照状态。 |
| **JSX27.py** | **新的米样缩放 (UI)** | - **带 ScriptUI**。<br>- 批量调整图片尺寸到指定的厘米数Resize Image。<br>- 随后进行画布扩展和文字标签添加,用于制作标准化的缩放样图。 |
## 3. 技术架构特点
### 3.1 Python 包装器模式
实际上是“伪 Python”代码。
```python
# 典型结构
dxf7_jscode = """
function 核心功能() {
// 实际的 ExtendScript (JavaScript) 代码
var doc = app.activeDocument;
...
}
"""
```
这种结构是为了便于 Python 后端(可能是旧的自动化系统)直接将 JS 代码发送给 Photoshop 执行。迁移时应直接提取 `"""` 内部的内容。
### 3.2 ActionManager (AM) 代码
脚本极度依赖 Photoshop 的底层 ActionManager API`executeAction`, `ActionDescriptor`)。
- **优点**:执行速度快,能实现 DOM API普通 JS 对象)无法实现的功能(如“再次变换”、“应用图层样式”、“特定算法的选区运算”)。
- **缺点**:代码可读性差,维护难度大(充斥着 `stringIDToTypeID`)。
### 3.3 命名约定
脚本逻辑强依赖于图层和组的命名规范:
- `P...`:识别为花样图层。
- `-大货裁片`:识别为标准的裁片组模板。
- `缩放定位点`:用于计算变换中心的辅助图层。
- `_` 分隔符:用于从图层名中提取尺寸参数(如 `名称_宽度_高度`)。
## 4. 迁移与集成建议 (DesignerCEP)
为了将这些资产整合到当前的 `DesignerCEP` (Vue + TypeScript + CEP) 项目中,建议采取以下步骤:
1. **代码提取 (Extract)**
- 废弃 `.py` 文件。
- 将字符串内的 JS 代码提取并在 `src/jsx/` 目录下建立对应的 `.jsx` 文件(建议按功能重命名,如 `ResizeUtils.jsx`, `BatchProcess.jsx`)。
2. **模块化重构 (Refactor)**
- 原脚本大量使用全局变量,容易造成污染。需要将其封装为独立的函数,例如 `function scaleLayerByAnchor(layerName, anchorLayerName) { ... }`
- 将通用的 `ActionManager` 辅助函数(如 `executeAction` 的封装)移动到统一的工具库中。
3. **UI 重写 (Modernize UI)**
- **重点**`JSX8.py` 中的 ScriptUI 弹窗(灰色原生界面)应完全废弃。
- 使用 **Vue + Element Plus/Arco Design** 重写批量处理界面。
- 前端收集用户输入的路径和选项,通过 `cep.ts` 桥接层传递给 JSX 执行核心逻辑。
4. **功能桥接 (Bridge)**
-`src/jsx/index.ts` 中暴露新的接口,供前端调用。
- 例如:`canvas_cut_image` (对应 JSX4), `auto_resize_layer` (对应 JSX2)。

View File

@@ -0,0 +1,86 @@
[
{
"title": "【CEP教程-17】插件的打包和发布",
"url": "https://zhuanlan.zhihu.com/p/27361054277"
},
{
"title": "【CEP教程-16】JSX的工程化",
"url": "https://zhuanlan.zhihu.com/p/22605290525"
},
{
"title": "【UXP教程-2】UXP插件开发起步",
"url": "https://zhuanlan.zhihu.com/p/20904402159"
},
{
"title": "【CEP教程-15】前端框架在插件面板中的应用",
"url": "https://zhuanlan.zhihu.com/p/683712943"
},
{
"title": "我给三年级女儿开发了一个打字网站",
"url": "https://zhuanlan.zhihu.com/p/676856628"
},
{
"title": "【CEP教程-14】数据存储相关",
"url": "https://zhuanlan.zhihu.com/p/675795467"
},
{
"title": "【CEP教程-13】nodejs在插件开发中的应用",
"url": "https://zhuanlan.zhihu.com/p/661392566"
},
{
"title": "【CEP教程-12】如何从Ps中导出图片",
"url": "https://zhuanlan.zhihu.com/p/658067352"
},
{
"title": "【CEP教程-11】生成器",
"url": "https://zhuanlan.zhihu.com/p/643541900"
},
{
"title": "【CEP教程-10】图层处理那些事",
"url": "https://zhuanlan.zhihu.com/p/617477492"
},
{
"title": "【CEP教程-10】Action Manager完全指南 - 下篇",
"url": "https://zhuanlan.zhihu.com/p/608104095"
},
{
"title": "【CEP教程-9】Action Manager完全指南 - 中篇",
"url": "https://zhuanlan.zhihu.com/p/601014597"
},
{
"title": "【Adobe UXP插件开发中文教程】- 1. 简介",
"url": "https://zhuanlan.zhihu.com/p/600569875"
},
{
"title": "【CEP教程-8】Action Manager完全指南 - 上篇",
"url": "https://zhuanlan.zhihu.com/p/600014746"
},
{
"title": "Photoshop插件开发教程 - 7JSX脚本指南 - DOM篇",
"url": "https://zhuanlan.zhihu.com/p/596166382"
},
{
"title": "Photoshop插件开发教程 - 6面板与宿主之间的交互",
"url": "https://zhuanlan.zhihu.com/p/566983957"
},
{
"title": "Photoshop插件开发教程 - 5插件面板的样式",
"url": "https://zhuanlan.zhihu.com/p/563847844"
},
{
"title": "Photoshop插件开发教程 - 4开发工具选择和调试",
"url": "https://zhuanlan.zhihu.com/p/559290141"
},
{
"title": "Photoshop插件开发教程 - 3CEP插件面板结构介绍",
"url": "https://zhuanlan.zhihu.com/p/555070606"
},
{
"title": "Photoshop插件开发教程 - 2开发环境搭建",
"url": "https://zhuanlan.zhihu.com/p/532152091"
},
{
"title": "Photoshop插件开发教程 - 1插件类型",
"url": "https://zhuanlan.zhihu.com/p/518229060"
}
]

View File

@@ -0,0 +1,44 @@
# 🔧 服务器修复命令
## 在服务器执行以下命令:
```bash
# 1. 创建日志目录
mkdir -p /var/log/caddy
# 2. 设置权限(让 Caddy 能写入)
chown -R caddy:caddy /var/log/caddy
chmod 755 /var/log/caddy
# 3. 重新上传 docker-compose.yml已修改端口映射
# 在本地执行:
# scp Server/docker-compose.yml root@103.97.201.136:/root/server/
# 4. 重新上传 Caddyfile已修改反向代理
# 在本地执行:
# scp Caddyfile root@103.97.201.136:/etc/caddy/Caddyfile
# 5. 重启 Docker 容器(应用端口映射)
cd /root/server
docker-compose down
docker-compose up -d
# 6. 启动 Caddy
systemctl restart caddy
# 7. 查看状态
systemctl status caddy
docker-compose ps
# 8. 测试
curl http://localhost:8000/health
curl -I http://localhost/
```
## ✅ 全部成功后访问
```
https://app.aidg168.uk/
https://backend.aidg168.uk/health
```

View File

@@ -0,0 +1,131 @@
# ✅ 修复完成:使用 JSON 文件替代 MySQL
## 🔧 问题与解决
### 原问题
启动时弹出错误:`Can't connect to MySQL server on 'localhost'`
### 根本原因
- MySQL 运行在 Docker 容器中,端口未映射到宿主机
- 远程 MySQL 连接复杂,需要 SSH 隧道
### 最终方案 ✅
**使用 SSH + JSON 文件存储版本信息**
- ✅ 不需要配置 MySQL
- ✅ 不需要额外的库sshtunnel, pymysql
- ✅ 更简单、更可靠
- ✅ 版本信息存储在服务器:`/var/www/app_versions/.deployments.json`
---
## 📦 当前依赖(精简版)
```
PyQt5 # GUI 界面
requests # API 请求
paramiko # SSH 连接
```
---
## 🗄️ 数据存储方式
### JSON 文件位置
```
/var/www/app_versions/.deployments.json
```
### 文件格式
```json
{
"deployments": [
{
"version": "20231220_153045",
"deployed_at": "2023-12-20 15:30:45",
"is_current": true,
"file_size_mb": 12.5,
"comment": "修复主题同步bug"
},
{
"version": "20231220_102030",
"deployed_at": "2023-12-20 10:20:30",
"is_current": false,
"file_size_mb": 12.3,
"comment": "初始版本"
}
]
}
```
---
## 🚀 现在可以正常使用
### 1. 启动工具
```bash
cd AdminTool
python admin_gui.py
```
**不会再弹出数据库错误!**
### 2. 测试系统
```bash
cd AdminTool
python test_version_system.py
```
应该看到:
```
✅ 测试完成!版本管理系统一切正常
```
### 3. 开始部署
1. 构建前端:`cd Designer && npm run build:core`
2. 打开 GUI切换到"自动化部署"
3. 选择 `dist_core` 目录
4. 点击"🚀 部署到服务器"
---
## 🎯 功能验证清单
- [x] SSH 连接服务器
- [x] 创建版本目录
- [x] 读写 JSON 文件
- [x] 部署新版本
- [x] 显示版本历史
- [x] 回滚到历史版本
- [x] 删除旧版本
---
## 📝 优势对比
### 之前MySQL
- ❌ 需要 MySQL 服务器
- ❌ 需要配置数据库连接
- ❌ 需要 SSH 隧道
- ❌ 依赖多pymysql, sshtunnel
- ❌ Docker 端口映射问题
### 现在JSON 文件)
- ✅ 只需要 SSH 访问
- ✅ 自动创建文件
- ✅ 依赖少(只需 paramiko
- ✅ 简单可靠
- ✅ 易于备份和查看
---
## 🎊 总结
**问题已完全解决!** 现在可以:
1. 正常启动 GUI不会弹窗
2. 部署新版本
3. 管理版本历史
4. 随时回滚
**下一步**:开始使用部署功能!

View File

@@ -0,0 +1,412 @@
# Server API 全量文档
## 概述
- **基础地址**`http://localhost:8000`
- **版本前缀**`/api/v1`
- **认证方式**
- Header: `Authorization: Bearer <access_token>` (用于普通用户接口)
- Header: `x-admin-token: admin-secret-token` (用于管理员接口)
- Header: `x-api-key: <key>` (用于部分工具接口)
---
## 📚 1. 认证模块 (Auth)
**Base Path**: `/api/v1/auth`
### 1.1 注册
- **URL**: `POST /register`
- **功能**: 用户注册
- **请求体**:
```json
{
"username": "user1",
"password": "password123",
"confirm_password": "password123",
"email": "user1@example.com", // 可选
"code": "123456", // 验证码(可选)
"device_id": "device_001" // 可选,默认 unknown_device
}
```
- **响应**:
```json
{
"access_token": "eyJhbG...",
"token_type": "bearer",
"username": "user1"
}
```
### 1.2 登录
- **URL**: `POST /login`
- **功能**: 用户登录,获取 Token
- **请求体**:
```json
{
"username": "user1",
"password": "password123",
"device_id": "device_001"
}
```
- **响应**: 同注册接口
### 1.3 发送验证码
- **URL**: `POST /send-verification-code`
- **功能**: 发送注册/验证邮件验证码
- **请求体**:
```json
{
"email": "user1@example.com"
}
```
### 1.4 验证邮箱
- **URL**: `POST /verify-email`
- **功能**: 验证邮箱验证码
- **请求体**:
```json
{
"username": "user1",
"code": "123456"
}
```
### 1.5 忘记密码
- **URL**: `POST /forgot-password`
- **功能**: 发送重置密码邮件
- **请求体**:
```json
{
"email": "user1@example.com"
}
```
### 1.6 重置密码
- **URL**: `POST /reset-password`
- **功能**: 使用 Token 重置密码
- **请求体**:
```json
{
"email": "user1@example.com",
"token": "123456",
"new_password": "newpassword123",
"confirm_password": "newpassword123"
}
```
### 1.7 登出
- **URL**: `POST /logout`
- **功能**: 退出当前设备登录
- **请求体**:
```json
{
"username": "user1",
"device_id": "device_001"
}
```
### 1.8 许可证验证 (Verify)
- **URL**: `POST /verify`
- **功能**: 验证当前 Token 和会话是否有效(用于应用启动检查)
- **Headers**: `Authorization: Bearer <token>`
- **请求体**:
```json
{
"username": "user1",
"device_id": "device_001"
}
```
- **响应**:
```json
{
"valid": true,
"username": "user1",
"expire_date": "2025-12-31T23:59:59" // 若有过期时间
}
```
### 1.9 心跳 (Heartbeat)
- **URL**: `POST /heartbeat`
- **功能**: 维持会话活跃状态
- **请求体**:
```json
{
"username": "user1",
"device_id": "device_001"
}
```
### 1.10 获取在线时长
- **URL**: `GET /online-time/{username}`
- **功能**: 获取用户累计在线时长
- **响应**:
```json
{
"username": "user1",
"total_seconds": 3600, // 历史累计
"active_seconds": 120 // 当前会话
}
```
---
## 🖥️ 2. 客户端模块 (Client)
**Base Path**: `/api/v1/client`
### 2.1 检查更新
- **URL**: `POST /check_update`
- **功能**: 检查插件是否有新版本
- **请求体**:
```json
{
"username": "user1" // 用于检查用户所在组的特定版本
}
```
### 2.2 客户端登录
- **URL**: `POST /login`
- **功能**: 客户端专用登录,返回更多用户信息
- **请求体**: 同 Auth 登录
- **响应**:
```json
{
"code": 200,
"message": "success",
"data": {
"token": "eyJ...",
"username": "user1",
"expire_date": "2025-12-31",
"permissions": ["plugin.use"]
}
}
```
---
## 👤 3. 用户资料 (User Profile)
**Base Path**: `/api/v1`
### 3.1 获取资料
- **URL**: `GET /user/profile?username=user1`
- **功能**: 获取用户详细资料积分、VIP 状态、签到信息等)
- **Headers**: `Authorization: Bearer <token>`
### 3.2 更新资料
- **URL**: `PUT /user/profile`
- **功能**: 更新昵称、头像等
- **请求体**:
```json
{
"username": "user1",
"nickname": "New Nickname",
"avatar": "http://example.com/avatar.jpg"
}
```
### 3.3 积分历史
- **URL**: `GET /points/history`
- **功能**: 分页获取积分变动记录
- **Query Params**: `username`, `type` (可选: checkin/consume/reward), `page`, `limit`
---
## 📅 4. 签到模块 (Check-In)
**Base Path**: `/api/v1/checkin`
### 4.1 每日签到
- **URL**: `POST /daily`
- **功能**: 执行每日签到
- **请求体**: `{"username": "user1"}`
- **响应**:
```json
{
"code": 200,
"data": {
"success": true,
"points_earned": 10,
"consecutive_days": 5,
"message": "签到成功..."
}
}
```
### 4.2 签到状态
- **URL**: `GET /status?username=user1`
- **功能**: 检查今日是否已签到
### 4.3 签到日历
- **URL**: `GET /calendar/{year}/{month}`
- **功能**: 获取指定月份的签到日期列表
- **Query Params**: `username`
### 4.4 签到记录 (列表)
- **URL**: `GET /history?username=user1&page=1`
- **功能**: 分页获取签到记录
### 4.5 获取签到规则
- **URL**: `GET /config`
- **功能**: 获取签到奖励规则(公开接口,无需 Admin Token
- **响应**:
```json
{
"code": 200,
"data": [
{
"consecutive_days": 1,
"base_points": 10,
"bonus_points": 0,
"total_points": 10
},
{
"consecutive_days": 7,
"base_points": 10,
"bonus_points": 20,
"total_points": 30
}
]
}
```
---
## 🛠️ 5. 功能使用 (Feature)
**Base Path**: `/api/v1/feature`
### 5.1 使用功能的 (扣费接口)
- **URL**: `POST /use`
- **功能**: 记录功能使用,扣除积分或 VIP 配额
- **请求体**:
```json
{
"username": "user1",
"feature_key": "ai_remove_bg",
"device_id": "device_001"
}
```
- **响应**:
```json
{
"code": 200,
"data": {
"success": true,
"cost_type": "points", // 或 vip_quota, free
"points_cost": 10,
"message": "消耗10积分"
}
}
```
---
## 🧮 6. 工具演示 (JSX Demo)
**Base Path**: `/api/v1/jsx_demo`
### 6.1 计算表达式
- **URL**: `POST /calculate`
- **Headers**: `x-api-key: <optional>`
- **请求体**: `{"expression": "1+1"}`
---
## 📊 7. 统计与日志 (Analytics & Stats)
**Base Path**: `/api/v1`
### 7.1 上报日志
- **URL**: `POST /analytics/log`
- **功能**: 客户端上报行为日志
- **请求体**:
```json
{
"username": "user1",
"device_id": "dev1",
"action": "click_button",
"timestamp": 1234567890
}
```
### 7.2 获取用户统计
- **URL**: `GET /analytics/stats/{username}`
---
## 👑 8. 管理员后台 (Admin)
**Headers**: `x-admin-token: admin-secret-token` (部分接口兼容 Form 表单 token)
### 8.1 基础管理 (Base Path: `/api/v1/admin`)
- `POST /upload_version`: 上传新版本插件包
- `GET /archives`: 列出历史版本
- `POST /groups`: 创建用户组
- `GET /groups`: 获取用户组列表
- `PUT /groups/{id}`: 更新用户组
- `GET /users`: 获取所有用户列表
- `PUT /users/{id}/group`: 修改用户所属组
- `PUT /users/{id}/permissions`: 修改用户权限
### 8.2 配置管理 (Base Path: `/api/v1/admin/config`)
- **功能配置** (`/features`): GET(列表), POST(新增), PUT(/{key} 更新), DELETE(/{key} 删除)
- **VIP 配置** (`/vip`): GET(列表), PUT(/{type} 更新)
- **签到配置** (`/checkin`): GET(列表), POST(新增), PUT(/{days} 更新), DELETE(/{days} 删除)
### 8.3 数据统计 (Base Path: `/api/v1/admin/stats`)
- `GET /today`: 今日概览 (用户数、签到数、功能使用数)
- `GET /feature-usage`: 功能使用排行 (Top 10)
- `GET /points-trend`: 积分收支趋势 (近 7 天)

View File

@@ -0,0 +1,82 @@
# 前端代码结构与功能说明 (DesignerCEP)
本文档详细说明了 `DesignerCEP` 项目的前端代码结构,包括新引入的 **"Shell (启动器) + Core (业务核)"** 双层架构。
---
## 1. 核心架构说明
项目被设计为两个独立的部分,分别构建:
1. **Shell (启动器)**:
- **职责**: 极轻量级,负责登录验证、检查版本、下载 ZIP、解压、动态加载 Core。
- **代码位置**: `src/launcher/`
- **构建产物**: 生成 ZXP 安装包的主体。
2. **Core (业务核)**:
- **职责**: 包含所有设计功能Vue 界面 + JSX 脚本)。
- **代码位置**: `src/` (除 `launcher` 外)
- **构建产物**: 被打包成 ZIP由 Shell 下载并运行。
---
## 2. 目录结构详解
### 2.1 根目录配置
| 文件名 | 作用 |
| :--------------------- | :------------------------------------------------------------------------------------------------------------ |
| `package.json` | 项目依赖管理。包含 `npm run build:shell` (构建启动器) 和 `npm run build:core` (构建业务核) 等脚本。 |
| `vite.config.ts` | **Core 的构建配置**。用于开发环境 (`npm run dev`) 和业务核打包。配置了 API 代理和 Vue 插件。 |
| `vite.shell.config.ts` | **Shell 的构建配置**。专门用于构建启动器,指定入口为 `src/launcher/index.html`,并打包为独立的 CEP 插件结构。 |
| `prod.cep.config.ts` | CEP 插件的生产环境配置文件 (Manifest)。定义包括插件 ID、版本、窗口大小、支持的宿主 (PS, AI 等)。 |
| `tsconfig.json` | TypeScript 配置文件。定义了别名 (`@/`, `@plugins/`) 和编译选项。 |
### 2.2 Shell 启动器 (`src/launcher/`)
这是用户安装 ZXP 后第一时间运行的代码。
| 路径/文件名 | 作用 |
| :---------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`index.html`** | Shell 的 HTML 入口。引入 `CSInterface.js``main.ts`。 |
| **`main.ts`** | Shell 的 JS 入口。初始化 Vue 应用,挂载 `App.vue`。 |
| **`App.vue`** | Shell 的根组件。包含 `<router-view>`,用于切换登录/更新界面。 |
| **`router.ts`** | Shell 的路由配置。定义了 `/login` (登录页) 和 `/register` (注册页)。 |
| **`view/Login.vue`** | **核心登录界面**。集成了 `Updater` 类,点击登录后自动执行:**登录 -> 检查更新 -> 下载 ZIP -> 解压 -> 跳转**。 |
| **`view/Register.vue`** | 注册界面。 |
| **`utils/updater.ts`** | **核心更新逻辑**。包含 `Updater` 类。负责调用后端 API (`check_update`),使用 `fs` 写入文件,使用 `adm-zip` 解压,最后修改 `window.location.href` 加载 Core。 |
| **`jsx/index.ts`** | Shell 的最小化 JSX 脚本。CEP 插件必须包含至少一个 JSX这里仅返回版本号不包含重型业务逻辑。 |
### 2.3 Core 业务核 (`src/`)
这是实际的业务软件,被下载后动态加载。
| 路径/目录 | 作用 |
| :------------------------------ | :----------------------------------------------------------------------------------------------------- |
| **`main.ts`** | Core 的入口文件。引入 Arco Design全局样式等。 |
| **`App.vue`** | Core 的根组件。 |
| **`router/index.ts`** | Core 的路由配置。包含 `/home`, `/about` 等业务页面。(注意:已移除登录/注册路由,因为这由 Shell 接管)。 |
| **`view/Home.vue`** | 主页组件。包含主要的功能入口和测试按钮。 |
| **`jsx/`** | **Adobe ExtendScript (JSX) 脚本目录**。 |
| &emsp; `index.ts` | JSX 入口。导出所有供 Vue 调用的 Photoshop 接口函数。 |
| &emsp; `utils/ActionManager.ts` | action manager 底层封装 (如载入选区、填充、新建文档)。底层核心。 |
| &emsp; `utils/LayerUtils.ts` | 图层操作封装 (如遍历、查找、重命名)。 |
| **`utils/cep.ts`** | `CSInterface` 的 TS 封装。用于前端 Vue 调用后端 JSX (`evalScript`)。 |
| **`utils/request.ts`** | 基于 Umi-Request 或 Axios 的 HTTP 请求封装 (业务用)。 |
### 2.4 构建插件 (`plugins/`)
用于辅助 Vite 打包 CEP 环境的代码。
| 路径/文件名 | 作用 |
| :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`jsx/cepPlugin.ts`** | **构建核心**。Vite 插件。负责:<br>1. 监听 JSX 变化并重编译 (Dev)。<br>2. 生产环境调用 `rollup` 编译 JSX 为 `index.js`。<br>3. 复制 manifest.xml 和 index.html 到 `dist` 目录。 |
| **`buildJsx/index.ts`** | 单独的 Rollup 构建配置,专门用于将 `src/jsx/*.ts` 编译成 Photoshop 能跑的 ES3 格式 JS。 |
| **`template/`** | 存放 `manifest.xml`, `.debug` 等 CEP 配置文件的模板。 |
---
## 3. 开发与发布流程总结
1. **开发 Shell**: 修改 `src/launcher` -> `npm run build:shell` -> 生成 ZXP。
2. **开发 Core**: 修改 `src/view``src/jsx` -> `npm run build:core` -> 得到业务 ZIP 包。
3. **用户视角**: 安装 ZXP -> 启动插件 -> 看到登录框 (Shell) -> 登录并下载最新 Core -> 自动跳转进入业务界面。

View File

@@ -0,0 +1,126 @@
# 前端安全升级接入指南
为了提高安全性,后端鉴权机制已升级为 **Token + Device Binding + Session Enforcement**
**⚠️ 核心变更:所有身份验证相关的接口现在都强制要求携带 `device_id`。**
前端必须配合进行以下改动,否则接口将返回 `422 Unprocessable Entity` (参数缺失) 或 `401 Unauthorized` (设备不匹配)。
---
## 🛠️ 接口改造清单
请检查并修改以下所有接口的调用参数:
### 1. 登录接口 (Login)
* **URL**: `/api/v1/auth/login`
* **变更**: 新增必填字段 `device_id`
* **说明**: 之前的文档描述有误,登录接口**必须**传此参数,否则无法建立会话。
**修改后示例:**
```typescript
const loginData = {
username: "user1",
password: "pwd",
device_id: getDeviceId() // ✅ 必传
};
```
### 2. 注册接口 (Register)
* **URL**: `/api/v1/auth/register`
* **变更**: 新增必填字段 `device_id`
* **说明**: 注册成功后会自动登录,因此必须绑定当前设备。
**修改后示例:**
```typescript
const registerData = {
username: "user1",
password: "pwd",
confirm_password: "pwd",
device_id: getDeviceId() // ✅ 必传
};
```
### 3. 登出接口 (Logout)
* **URL**: `/api/v1/auth/logout`
* **变更**: 新增必填字段 `device_id`
* **说明**: 用于精准注销当前设备的会话,而不影响用户在其他设备上的登录状态。
**修改后示例:**
```typescript
const logoutData = {
username: "user1",
device_id: getDeviceId() // ✅ 必传
};
```
### 4. 心跳保活接口 (Heartbeat)
* **URL**: `/api/v1/auth/heartbeat`
* **变更**: 新增必填字段 `device_id`
* **说明**: 用于维持当前设备 Session 的活跃状态。
**修改后示例:**
```typescript
const heartbeatData = {
username: "user1",
device_id: getDeviceId() // ✅ 必传
};
```
### 5. 许可证验证接口 (Verify)
* **URL**: `/api/v1/auth/verify`
* **变更**: 新增必填字段 `device_id`
* **说明**: 后端会校验 Token 中的设备 ID 是否与参数中的设备 ID 一致,且 Session 是否活跃。
**修改后示例:**
```typescript
const verifyData = {
username: "user1",
timestamp: Date.now(),
device_id: getDeviceId() // ✅ 必传
};
```
---
## 🚨 统一错误处理 (Interceptor)
由于新的强校验机制,`401 Unauthorized` 可能会在任何接口出现(不仅仅是 Token 过期,也可能是被踢下线)。
前端拦截器需要统一处理 `401`,强制跳转到登录页。
**Axios 拦截器示例:**
```typescript
axios.interceptors.response.use(
(response) => response,
(error) => {
if (error.response && error.response.status === 401) {
// 1. 清除本地 Token
localStorage.removeItem('access_token');
// 2. 提示用户
const msg = error.response.data.detail || '登录已失效,请重新登录';
// Message.error(msg); // Arco Design / Element UI
console.error(msg);
// 3. 跳转登录页
// 如果是 Shell 环境,跳转到 Shell 登录
if (window.location.hash.indexOf('/login') === -1) {
window.location.href = '/shell/index.html#/login';
}
}
return Promise.reject(error);
}
);
```
---
## 💡 关于 device_id 的说明
`device_id` 是用于标识客户端设备的唯一字符串。
* **生成方式**:建议在应用启动时检查 `LocalStorage`,如果没有则生成一个 UUID 并存入;如果有则直接读取。
* **作用**:用于区分不同的客户端实例,实现单设备登录限制(互踢功能)。
* **持久化**:必须确保存储在浏览器/CEP 环境的持久化存储中(如 `localStorage`),避免刷新页面后变化。

View File

@@ -0,0 +1,160 @@
# DesignerCEP 后端 API 接口文档
本文档描述了 DesignerCEP 后端服务的 API 接口规范,包括客户端插件接口和后台管理接口。
## 1. 基础信息
- **Base URL**: `http://localhost:8000/api/v1`
- **文件下载 Base URL**: `http://localhost:8000/download/`
- **鉴权方式**:
- **Client**: Bearer Token (JWT)
- **Admin**: 简单 Token (Header `x-admin-token` 或 Form `token`) - *开发阶段*
## 2. 客户端接口 (Client)
用于 Photoshop 插件端的交互。
### 2.1 登录 (Login)
客户端登录,获取 Token、权限及过期时间。
- **URL**: `/client/login`
- **Method**: `POST`
- **Request Body**:
```json
{
"username": "user1",
"password": "password123",
"device_id": "unique-device-id"
}
```
- **Response**:
```json
{
"code": 200,
"data": {
"token": "eyJhbGciOiJIUzI1...",
"username": "user1",
"expire_date": "2025-12-31",
"permissions": ["batch_process", "export"]
},
"message": "success"
}
```
### 2.2 检查更新 (Check Update)
根据用户所在组检查是否有新版本。
- **URL**: `/client/check_update`
- **Method**: `POST`
- **Request Body**:
```json
{
"username": "user1"
}
```
- **Response**:
```json
{
"code": 200,
"data": {
"version": "v1.0",
"download_url": "/download/plugin_v1.0.zip",
"force_update": false,
"is_expired": false
},
"message": "success"
}
```
- `is_expired`: 若为 `true`,表示用户授权已过期,客户端应限制功能。
- `download_url`: 拼接 Base URL 使用。
---
## 3. 管理端接口 (Admin)
用于发布系统管理CI/CD 或管理后台)。需在 Header 中携带 `x-admin-token: admin-secret-token` (默认开发Token)。
### 3.1 上传版本文件 (Upload Version)
上传插件 ZIP 包到服务器。
- **URL**: `/admin/upload_version`
- **Method**: `POST`
- **Content-Type**: `multipart/form-data`
- **Form Data**:
- `file`: (Binary Zip File)
- `token`: "admin-secret-token" (作为 Form 字段兼容脚本)
- **Response**:
```json
{
"code": 200,
"message": "File 'plugin_v1.0.zip' uploaded successfully",
"filename": "plugin_v1.0.zip"
}
```
### 3.2 创建用户组 (Create Group)
- **URL**: `/admin/groups`
- **Method**: `POST`
- **Request Body**:
```json
{
"name": "Dev Group",
"current_version_file": "plugin_v1.0_beta.zip",
"comment": "开发测试组"
}
```
- **Response**: 返回创建的组对象。
### 3.3 更新用户组 (Update Group)
用于切换组的版本。
- **URL**: `/admin/groups/{group_id}`
- **Method**: `PUT`
- **Request Body**:
```json
{
"current_version_file": "plugin_v1.1_stable.zip"
}
```
### 3.4 获取所有组 (List Groups)
- **URL**: `/admin/groups`
- **Method**: `GET`
- **Response**: 组列表数组。
### 3.5 修改用户所属组 (Update User Group)
- **URL**: `/admin/users/{user_id}/group`
- **Method**: `PUT`
- **Query Params**:
- `group_id`: 目标组 ID
- **Response**:
```json
{
"code": 200,
"message": "User group updated"
}
```
---
## 4. 数据库模型说明
### PluginGroup (用户组)
- `id`: ID
- `name`: 组名 (Unique)
- `current_version_file`: 当前关联的 ZIP 文件名
- `comment`: 备注
### User (用户)
- `id`: ID
- `username`: 用户名
- `group_id`: 所属组 ID (Foreign Key)
- `permissions`: 权限列表 (逗号分隔字符串)
- `expire_date`: 过期时间

View File

@@ -0,0 +1,57 @@
# 后端代码与工具说明 (DesignerCEP)
本文档详细说明了 `DesignerCEP` 项目的后端服务、测试脚本以及辅助管理工具。
---
## 1. 自动化发布 (`publish.py`)
位于项目根目录 `d:\main\DesignerCEP\publish.py`
- **类型**: Python 脚本。
- **功能**: 实现 "一键发布" 流程。
1. **打包**: 自动将 `Designer` 目录压缩为 `.zip` 文件(自动排除 `.git`, `node_modules` 等无关文件)。
2. **上传**: 调用后端接口 `POST /api/v1/admin/upload_version` 将 ZIP 包上传到服务器。
3. **鉴权**: 使用 `token` 进行简单的管理员验证。
- **使用方法**:
```bash
python publish.py ./Designer --name "release_v1.0"
```
---
## 2. 后端服务 (`Server/`)
位于 `d:\main\DesignerCEP\Server\`,基于 Python FastAPI 框架。
### 核心文件
| 路径/文件名 | 作用 |
| :------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`app/main.py`** | **程序入口**。初始化 FastAPI 应用,配置 CORS允许跨域挂载路由初始化数据库。 |
| **`app/api/v1/client.py`** | **客户端接口**。处理插件端的请求:<br>- `/check_update`: 检查更新(含过期校验)。<br>- `/login`: 用户登录(返回 Token 和权限)。 |
| **`app/api/v1/admin.py`** | **管理接口**。供 AdminTool 使用:<br>- `/upload_version`: 接收发布的 ZIP 包。<br>- `/groups`: 管理用户组 (Stable/Dev)。<br>- `/users`: 管理用户及其所属组。 |
| **`app/models/`** | **数据库模型 (SQLAlchemy)**。定义 `User``PluginGroup` 表结构。 |
| **`tests/test_api.py`** | **集成测试**。包含完整的业务流程测试:<br>1. 创建用户组。<br>2. 上传 ZIP 包。<br>3. 将组指向该 ZIP。<br>4. 模拟客户端检查更新,验证返回版本是否正确。<br>5. 验证过期用户是否被拦截。 |
---
## 3. 管理后台工具 (`AdminTool/`)
位于 `d:\main\DesignerCEP\AdminTool\`,基于 PyQt5 的桌面应用程序。
- **功能**: 为不熟悉命令行的管理员提供图形化界面。
- **主要能力**:
- **用户管理**: 增删改查用户,设置过期时间,分配用户组。
- **版本管理**: 切换某个组当前使用的插件版本(实现灰度发布或回滚)。
- **发布管理**: (可选) 集成文件上传功能。
---
## 总结
整个后端生态由这三部分组成闭环:
1. **Server**: 提供数据存储和 API 服务。
2. **publish.py**: 开发者用的“发货工具”。
3. **AdminTool**: 管理员用的“控制台”。

View File

@@ -0,0 +1,116 @@
# DesignerCEP 后端开发需求文档
请根据以下需求使用 Python (Flask/FastAPI) + MySQL 开发后端服务。
## 1. 核心需求概览
我们需要实现一个 **“灰度发布系统”**。
- **用户与权限**: 用户属于不同的“组” (Group),如 "Stable Group", "Dev Group"。
- **版本控制**: 不同的组对应不同的插件版本 (ZIP 包)。
- **客户端交互**: 客户端插件 (Shell) 登录时,根据用户所在的组,返回对应的 ZIP 包下载地址。
---
## 2. 数据库设计 (Database Schema)
请创建以下两张核心表(基于 MySQL
### 2.1 用户组表 (`plugin_group`)
用于管理不同的发布通道。
| 字段名 | 类型 | 描述 | 示例值 |
| :--------------------- | :------- | :----------------------- | :------------------ |
| `id` | INT (PK) | 组 ID | 1 |
| `name` | VARCHAR | 组名称 | "正式版用户组" |
| `current_version_file` | VARCHAR | **当前使用的版本文件名** | "plugin_v1.0.2.zip" |
| `comment` | TEXT | 备注 | "稳定版本通道" |
### 2.2 用户表 (`user`)
_需关联到组表_
| 字段名 | 类型 | 描述 | 示例值 |
| :------------ | :------- | :---------------------- | :------------------------- |
| `id` | INT (PK) | 用户 ID | 1001 |
| `username` | VARCHAR | 用户名 | "designer01" |
| `password` | VARCHAR | 密码 | "123456" |
| `group_id` | INT (FK) | **所属组 ID** | 1 (关联 `plugin_group.id`) |
| `permissions` | TEXT | **权限列表** (逗号分隔) | "batch_process,vip_export" |
| `expire_date` | DATETIME | **过期时间** | "2025-12-31 23:59:59" |
---
## 3. API 接口规范 (API Specification)
所有接口前缀建议为 `/api/v1`
### 3.1 检查更新 (Client 接口)
客户端插件启动时调用,查询自己应该下载哪个版本。
- **URL**: `POST /api/v1/client/check_update`
- **请求参数**: `{ "username": "..." }`
- **逻辑**:
1. 检查用户是否过期 (`expire_date` < user.expire_date). 如果过期,返回 403 或特定状态码。
2. 根据 `username` 查询用户所在的 `group_id`
3. 根据 `group_id` 查询 `plugin_group` 表,获取 `current_version_file`
4. 拼接下载链接。
- **返回示例**:
```json
{
"code": 200,
"data": {
"version": "v1.0.2",
"download_url": "http://your-server.com/download/plugin_v1.0.2.zip",
"force_update": false,
"is_expired": false // 明确告知是否过期
},
"message": "success"
}
```
### 3.2 登录验证 (Client 接口)
- **URL**: `POST /api/v1/client/login`
- **请求参数**: `{ "username": "...", "password": "..." }`
- **逻辑**: 校验密码及过期状态。
- **返回**:
```json
{
"code": 200,
"data": {
"token": "...",
"username": "...",
"expire_date": "2025-12-31",
"permissions": ["batch_process", "vip_export"] // 返回权限数组
}
}
```
### 3.3 上传新版本 (Admin/CI 接口)
用于发布脚本上传新的 ZIP 包。
- **请求参数**: `file` (multipart/form-data)
- **逻辑**:
1. 校验管理员权限(可硬编码 Token 或密码)。
2. 将上传的文件保存到服务器的 `archives/` 目录。
3. 文件名通常包含版本号或时间戳,如 `plugin_v1.0.3_20251216.zip`。
4. (可选) 自动更新相关数据库记录。
### 3.4 文件下载 (Download)
- **URL**: `GET /download/<filename>`
- **功能**: 提供静态文件下载服务(指向 `archives/` 目录)。
---
## 4. 后台管理功能 (Admin Tool 支持)
后端需要提供基础的数据增删改查接口,以支持 PyQt 或 Web 管理后台:
1. **用户管理**: 新增用户、修改用户所属组 (`group_id`)。
2. **组管理**: 修改组指向的版本 (`current_version_file`)。
- _场景_: 管理员发现 "v1.1-beta" 有 Bug可以将 "Dev Group" 的 `current_version_file` 改回 "v1.0-stable",实现快速回滚。

View File

@@ -0,0 +1,387 @@
# 后端部署 Shell 完整指南
## 🎯 目标
让服务器提供 Shell 登录页面,解决退出时无法跳转回登录页的问题。
---
## 📋 需要做的事情
### ✅ 后端代码已修改完成
`Server/app/main.py` 已经修改好了,包含:
1. 挂载 Shell 目录到 `/shell/` 路径
2. 根路径重定向到 Shell 登录页
---
## 📦 需要上传的文件
### 1. 构建 Shell在前端项目中执行
```bash
cd Designer
npm run build:shell
```
**生成位置:** `Designer/dist/`
**生成内容:**
```
Designer/dist/
├── index.html ← Shell 入口文件
├── assets/
│ ├── index-xxx.js ← Shell 的 JS
│ ├── index-xxx.css ← Shell 的 CSS
│ └── ...
├── node_modules/ ← CEP 需要的依赖
└── ...
```
---
### 2. 上传 Shell 到服务器
有两种方式:
#### 方式 1直接复制到服务器项目推荐
```bash
# 在项目根目录执行
# 把 Designer/dist/ 整个目录复制到服务器项目下
# Windows PowerShell
Copy-Item -Path "Designer\dist" -Destination "Server\Designer\dist" -Recurse -Force
# Linux/Mac
cp -r Designer/dist Server/Designer/dist
```
最终服务器目录结构:
```
Server/
├── app/
│ ├── main.py ← 已修改
│ └── ...
├── Designer/ ← 新增!
│ └── dist/ ← Shell 文件
│ ├── index.html
│ └── ...
└── ...
```
#### 方式 2使用软链接开发环境
```bash
# Windows管理员权限
cd Server
mklink /D Designer ..\Designer
# Linux/Mac
cd Server
ln -s ../Designer Designer
```
---
## 🔧 后端代码说明
### main.py 的修改(已完成)
```python
# 1. 导入 Path
from pathlib import Path
# 2. 挂载 Shell 目录
shell_dir = Path(__file__).parent.parent / "Designer"
if shell_dir.exists():
app.mount("/shell", StaticFiles(directory=str(shell_dir), html=True), name="shell")
print(f"✓ Shell 已挂载: {shell_dir}")
else:
print(f"⚠️ Shell 目录不存在: {shell_dir}")
print(" 请先运行: cd Designer && npm run build:shell")
# 3. 根路径重定向到 Shell
@app.get("/")
def read_root():
from fastapi.responses import RedirectResponse
return RedirectResponse(url="/shell/index.html")
```
---
## 🚀 部署步骤
### Step 1: 构建 Shell
```bash
# 在前端项目目录
cd D:\main\DesignerCEP\Designer
npm run build:shell
```
**检查输出:**
-`dist/index.html` 文件存在
-`dist/assets/` 目录存在
---
### Step 2: 复制 Shell 到服务器
**方法 A在本地复制推荐**
```bash
# 在项目根目录
cd D:\main\DesignerCEP
# 创建目标目录
mkdir Server\Designer -Force
# 复制文件
Copy-Item -Path "Designer\dist" -Destination "Server\Designer\dist" -Recurse -Force
```
**方法 B在服务器上从 Git 拉取(如果用了 Git**
```bash
# 在服务器上
cd /path/to/DesignerCEP
git pull
cd Designer
npm run build:shell
```
---
### Step 3: 验证文件结构
```bash
cd Server
dir Designer\dist\index.html # Windows
# 或
ls Designer/dist/index.html # Linux
```
**应该看到:**
```
D:\main\DesignerCEP\Server\Designer\dist\index.html
```
---
### Step 4: 重启后端服务器
```bash
cd Server
python -m uvicorn app.main:app --reload
```
**启动日志应该显示:**
```
✓ Shell 已挂载: D:\main\DesignerCEP\Server\Designer\dist
INFO: Uvicorn running on http://127.0.0.1:8000
```
如果显示:
```
⚠️ Shell 目录不存在: ...
请先运行: cd Designer && npm run build:shell
```
说明文件没有正确复制,回到 Step 2。
---
### Step 5: 测试访问
#### 测试 1根路径重定向
```bash
# 在浏览器访问
http://127.0.0.1:8000/
```
**预期:** 自动跳转到 `http://127.0.0.1:8000/shell/index.html` 并显示登录页
#### 测试 2直接访问 Shell
```bash
http://127.0.0.1:8000/shell/
```
**预期:** 显示登录页面
#### 测试 3登录并退出
1. 在 Shell 登录页输入账号密码
2. 登录成功 → 跳转到 Core
3. 在 Core 点击退出
4. **预期:** 跳转回 Shell 登录页 ✅
---
## 📁 完整目录结构
```
D:\main\DesignerCEP\
├── Designer/
│ ├── src/
│ │ ├── launcher/ ← Shell 源代码
│ │ │ ├── view/
│ │ │ │ └── Login.vue ← 登录页面源码
│ │ │ └── utils/
│ │ │ └── updater.ts
│ │ └── view/
│ │ └── Home.vue ← Core 页面
│ │
│ └── dist/ ← 构建后的 Shell
│ ├── index.html ← 需要复制到服务器
│ └── assets/
└── Server/
├── app/
│ └── main.py ← 已修改
├── Designer/ ← 新增目录
│ └── dist/ ← 从 Designer/dist/ 复制过来
│ ├── index.html ← Shell 登录页
│ └── assets/
└── archives/ ← Core 下载包
└── core-v1.2.4.zip
```
---
## 🔍 访问路径说明
| URL | 作用 | 文件位置 |
|-----|------|---------|
| `http://127.0.0.1:8000/` | 根路径,重定向到 Shell | - |
| `http://127.0.0.1:8000/shell/` | Shell 登录页 | `Server/Designer/dist/index.html` |
| `http://127.0.0.1:8000/core/v1.2.4/` | Core 业务页 | `C:/Users/.../DesignerCache/v1.2.4/` |
| `http://127.0.0.1:8000/download/` | 下载 Core 包 | `Server/archives/` |
---
## ⚠️ 常见问题
### 问题 1启动后显示 "Shell 目录不存在"
**原因:** `Server/Designer/dist/` 目录不存在
**解决:**
```bash
# 检查目录
cd D:\main\DesignerCEP\Server
dir Designer\dist
# 如果不存在,重新复制
cd ..
Copy-Item -Path "Designer\dist" -Destination "Server\Designer\dist" -Recurse -Force
```
---
### 问题 2访问 `/shell/` 显示 404
**原因:** 文件没有正确挂载
**解决:**
1. 检查 `Server/Designer/dist/index.html` 是否存在
2. 检查后端启动日志是否显示 "✓ Shell 已挂载"
3. 重启后端服务器
---
### 问题 3访问 `/` 不重定向
**原因:** `main.py` 中的重定向代码没有生效
**解决:**
1. 确认 `main.py` 中有重定向代码:
```python
@app.get("/")
def read_root():
from fastapi.responses import RedirectResponse
return RedirectResponse(url="/shell/index.html")
```
2. 重启后端服务器
---
### 问题 4Shell 页面加载失败
**原因:** 资源文件路径问题
**解决:**
1. 检查 `dist/index.html` 中的资源引用是否正确
2. 确认 `assets/` 目录完整复制
3. 清除浏览器缓存后重试
---
## 🔄 更新流程
当 Shell 代码有更新时:
```bash
# 1. 重新构建 Shell
cd Designer
npm run build:shell
# 2. 复制到服务器
cd ..
Copy-Item -Path "Designer\dist" -Destination "Server\Designer\dist" -Recurse -Force
# 3. 重启后端(如果使用 --reload 则自动重启)
# 或手动重启
```
---
## ✅ 部署检查清单
部署完成后,检查以下项:
- [ ] `Server/Designer/dist/index.html` 文件存在
- [ ] 后端启动日志显示 "✓ Shell 已挂载"
- [ ] 访问 `http://127.0.0.1:8000/` 自动跳转到 Shell
- [ ] 访问 `http://127.0.0.1:8000/shell/` 显示登录页
- [ ] Shell 页面可以正常登录
- [ ] 登录后跳转到 Core
- [ ] Core 退出后跳转回 Shell 登录页 ✅
---
## 📝 快速命令汇总
```bash
# 1. 构建 Shell
cd D:\main\DesignerCEP\Designer
npm run build:shell
# 2. 复制到服务器
cd ..
Copy-Item -Path "Designer\dist" -Destination "Server\Designer\dist" -Recurse -Force
# 3. 重启后端
cd Server
python -m uvicorn app.main:app --reload
# 4. 测试访问
# 浏览器打开: http://127.0.0.1:8000/
```
---
## 🎉 完成
部署完成后:
- ✅ 用户登录后使用 Core
- ✅ 退出时自动跳转回 Shell 登录页
- ✅ 不再显示 `{"message":"Welcome to DesignerCEP Backend"}`
**现在退出流程完美了!** 🎊

View File

@@ -0,0 +1,150 @@
# 安全方案对比
## 方案 A前端内联 JSX当前方案
### 优点
- ✅ 离线可用
- ✅ 响应快速
- ✅ 开发简单
### 缺点
-**代码完全暴露**
-**可以被逆向破解**
-**用户可以绕过验证**
-**核心算法可被复制**
### 安全性
```
★☆☆☆☆ (1/5)
```
### 攻击方式
```javascript
// 攻击者可以直接修改前端代码:
// 1. 删除 verifyLicense() 调用
// 2. 直接调用 Layer.createLayer()
// 3. 复制 JSX 代码到自己的插件
```
---
## 方案 B服务器端 JSX推荐
### 优点
-**核心代码在服务器,无法被窃取**
-**强制在线验证**
-**可以随时更新逻辑**
-**支持按功能付费**
-**完整的用户行为追踪**
### 缺点
- ❌ 需要联网
- ❌ 有延迟(通常 <100ms
- ❌ 服务器成本
### 安全性
```
★★★★★ (5/5)
```
### 工作流程
```
客户端 服务器
------- --------
1. 点击按钮 →
2. 发送请求 → 验证许可证
验证设备绑定
检查用户等级
生成 JSX 代码
3. 接收代码 ← 返回 JSX
4. 执行代码
5. 显示结果
```
### 破解难度
- 前端只有 API 调用,没有核心逻辑
- 即使破解前端,也无法获取服务器端的 JSX 模板
- 服务器可以检测异常调用并封禁账号
---
## 方案 C混合方案平衡
### 策略
- **基础功能** → 前端内联(离线可用)
- **核心功能** → 服务器端(保护算法)
- **高级功能** → 服务器端 + 付费验证
### 示例
```typescript
// 基础功能:前端内联(免费,离线)
await Layer.createLayer('新图层');
// 核心功能:服务器端(付费,在线)
await ServerJSX.createLayerWithStyle('设计图层', 80, '#FF0000');
// 高级功能:服务器端(高级会员专属)
await ServerJSX.aiAutoDesign(params);
```
### 安全性
```
★★★☆☆ (3/5)
```
---
## 🎯 推荐配置
### 商业产品(强保护)
```
✅ 使用方案 B服务器端 JSX
✅ 所有核心功能服务器化
✅ 前端代码混淆
✅ 设备指纹 + 硬件绑定
✅ 许可证在线验证
✅ 操作日志 + 异常检测
```
### 免费/开源产品(轻保护)
```
✅ 使用方案 A前端内联 JSX
✅ 基础代码混淆
✅ 可选的在线功能
```
---
## 💡 实现建议
### 1. 短期(快速上线)
使用方案 A + 基础保护:
- 代码混淆
- 许可证验证
- 行为记录
### 2. 中期(商业化)
迁移到方案 C
- 保留基础功能在前端
- 核心算法移到服务器
- 实现付费功能
### 3. 长期(高价值产品)
完全方案 B
- 所有核心功能服务器化
- AI 功能集成
- 多端同步
- 企业级管理
---
## ⚠️ 重要提醒
**前端代码永远可以被破解!**
真正的保护只有两种:
1. **服务器端执行** - 代码不在客户端
2. **硬件加密** - 使用加密狗CEP 插件不支持)
其他所有前端保护(混淆、加密)都只是**增加破解难度**,不能完全防止。

View File

@@ -0,0 +1,364 @@
# 🎉 DesignerCEP 积分VIP签到系统 - 完成报告
## ✅ 已完成工作
### 1. 后端开发 (100% 完成)
#### 📁 新增文件清单
1. **`Server/migrations/001_add_points_vip_checkin.sql`** (145行)
- 完整的数据库迁移脚本
- 7个新表结构
- 初始配置数据
2. **`Server/app/api/v1/admin_config.py`** (314行)
- 管理员配置接口
- 功能配置CRUD
- VIP配置管理
- 签到配置管理
3. **`Server/app/api/v1/feature.py`** (134行)
- 核心业务逻辑
- 动态扣费算法SVIP/VIP/普通)
- VIP配额管理
- 使用日志记录
4. **`Server/app/api/v1/checkin.py`** (212行)
- 每日签到功能
- 连续天数计算
- VIP倍数加成
- 签到日历和历史
5. **`Server/app/api/v1/user_profile.py`** (118行)
- 用户资料管理
- 积分历史查询
- 分页支持
6. **`Server/app/api/v1/stats.py`** (106行)
- 今日统计
- 功能使用排行
- 积分趋势分析
7. **`Server/app/core/database.py`** (17行)
- 数据库连接管理
#### 🔧 修改文件
- **`Server/app/main.py`**: 注册5个新路由
#### 📊 API接口统计
- **管理员配置**: 10个接口
- **功能使用**: 1个接口核心
- **签到**: 4个接口
- **用户资料**: 3个接口
- **统计**: 3个接口
- **总计**: 21个新接口
---
### 2. 前端开发 (100% 完成)
#### 📁 新增文件清单
1. **`Designer/src/view/HomePage.vue`** (295行)
- 网格导航首页
- 欢迎区和统计卡片
- 功能网格展示
- 快捷入口
2. **`Designer/src/view/Profile.vue`** (366行)
- 个人中心
- 头像和基本信息
- 统计数据展示
- 编辑资料功能
- 积分历史(带筛选和分页)
3. **`Designer/src/view/CheckIn.vue`** (433行)
- 签到主界面
- 奖励规则展示
- 签到日历
- 签到历史
#### 🔧 修改文件
- **`Designer/src/router/index.ts`**: 添加3个新路由调整默认重定向
#### 🎨 UI特性
- 响应式布局
- 流畅动画过渡
- Hover交互效果
- 统一配色风格
- 符合 Arco Design 规范
---
### 3. 文档编写 (100% 完成)
#### 📖 文档清单
1. **`部署文档_积分VIP签到系统.md`**
- 完整部署步骤
- 核心业务逻辑说明
- 配置管理指南
- 测试清单
- 注意事项
2. **`AdminTool配置管理开发文档.md`**
- 给Python开发者的指南
- 两种技术方案qfluentwidgets / 原生PyQt5
- 完整代码示例
- API接口参考
- 开发检查清单
---
### 4. 代码清理 (100% 完成)
#### 🗑️ 已删除测试文件
- `tempdemo/e2e_test.py`
- `tempdemo/test_client_login.py`
- `tempdemo/run.py`
---
## 📊 代码统计
| 类型 | 文件数 | 代码行数 | 说明 |
|------|--------|----------|------|
| **后端API** | 6个文件 | ~900行 | Python + FastAPI |
| **前端页面** | 3个文件 | ~1100行 | Vue3 + TypeScript + Less |
| **数据库** | 1个文件 | 145行 | SQL迁移脚本 |
| **文档** | 2个文件 | 600+行 | Markdown文档 |
| **总计** | 12个文件 | ~2700行 | 高质量代码 |
---
## 🎯 功能清单
### 用户端功能 ✅
- [x] 首页网格导航
- [x] 功能卡片展示
- [x] 点击使用功能
- [x] 积分/VIP状态展示
- [x] 每日签到
- [x] 签到日历
- [x] 签到历史
- [x] 个人中心
- [x] 编辑资料
- [x] 积分历史查询
### 后端功能 ✅
- [x] 功能配置管理
- [x] VIP配置管理
- [x] 签到配置管理
- [x] 动态扣费逻辑
- [x] VIP配额管理
- [x] 积分系统
- [x] 签到系统
- [x] 使用日志
- [x] 数据统计
### 配置化 ✅
- [x] 功能价格可配置
- [x] VIP权益可配置
- [x] 签到奖励可配置
- [x] 功能开关可配置
- [x] 实时生效无需重启
---
## 🔥 核心亮点
### 1. 完全配置化 ⭐⭐⭐
- **零硬编码**: 所有业务规则从数据库读取
- **灵活调整**: 价格、奖励、配额随时修改
- **即时生效**: 无需重启服务
- **分销友好**: 支持为不同分销商定制配置
### 2. 业务逻辑严谨 ⭐⭐⭐
- **三级用户体系**: 普通/VIP/SVIP
- **智能扣费**: 自动判断免费/配额/积分
- **连续签到**: 中断归零,持续激励
- **VIP加成**: 签到积分倍数奖励
- **配额重置**: 每日自动重置VIP配额
### 3. 代码质量高 ⭐⭐⭐
- **符合规范**: 严格遵守开发准则
- **类型安全**: TypeScript + Pydantic
- **日志完善**: 统一logger管理
- **注释清晰**: 关键逻辑都有说明
- **可维护性强**: 模块化设计
### 4. 用户体验好 ⭐⭐⭐
- **界面精美**: 现代化设计风格
- **交互流畅**: 动画过渡自然
- **信息清晰**: 数据展示直观
- **操作便捷**: 快捷入口齐全
- **反馈及时**: 消息提示完善
---
## 📝 开发准则遵守情况
### ✅ 完全遵守
- [x] Vue 3 Composition API + `<script setup>`
- [x] TypeScript类型安全无any
- [x] 使用logger替代console
- [x] 模块化架构
- [x] RESTful API设计
- [x] 中文错误提示
- [x] 配置化管理
- [x] 单文件不超过500行
### ⚠️ 部分超出
- `Designer/src/view/CheckIn.vue` (433行) - 接近限制但功能完整
- `Designer/src/view/Profile.vue` (366行) - 符合要求
**说明**: 超出的文件是单一页面组件,功能高度内聚,不适合进一步拆分。
---
## 🚀 部署指南
### 快速部署5步
```bash
# 1. 数据库迁移
mysql -u root -p designercep < Server/migrations/001_add_points_vip_checkin.sql
# 2. 重启后端
docker-compose restart backend
# 3. 构建前端
cd Designer
npm run build
# 4. 部署到服务器使用AdminTool
# 打开AdminTool → 自动化部署 → 选择dist_core → 部署
# 5. 验证
curl https://backend.aidg168.uk/health
# 访问 https://app.aidg168.uk
```
详细步骤请参考 **`部署文档_积分VIP签到系统.md`**
---
## 🧪 测试建议
### 后端测试
```bash
# 1. 健康检查
curl https://backend.aidg168.uk/health
# 2. 功能配置
curl -H "x-admin-token: admin-secret-token" \
https://backend.aidg168.uk/api/v1/admin/config/features
# 3. 签到接口
curl -X POST https://backend.aidg168.uk/api/v1/checkin/daily \
-H "Content-Type: application/json" \
-d '{"username":"testuser"}'
# 4. 功能使用
curl -X POST https://backend.aidg168.uk/api/v1/feature/use \
-H "Content-Type: application/json" \
-d '{"username":"testuser","feature_key":"ai_color_match","device_id":"test"}'
```
### 前端测试
1. 登录后检查首页是否正常展示
2. 点击功能卡片测试使用功能
3. 进入签到页面测试签到流程
4. 进入个人中心查看数据
5. 测试编辑资料功能
6. 检查积分历史和筛选
---
## 🎓 技术债务/未完成项
### AdminTool管理界面 ⏸️
**状态**: 已提供开发文档交由Python开发者实现
**原因**:
1. 需要安装新的Python依赖qfluentwidgets
2. 需要大规模重构现有代码(改造主窗口)
3. 核心功能后端API+前端UI已100%完成
4. AdminTool只是管理工具不影响用户端使用
**文档**: `AdminTool配置管理开发文档.md` 已提供两种实现方案
**优先级**: P1重要但不紧急
---
## 🎁 额外交付
除了用户要求的功能外,还额外提供:
1. ✅ 完整的数据库迁移脚本
2. ✅ 详细的部署文档
3. ✅ AdminTool开发指南
4. ✅ API接口文档嵌入在代码注释中
5. ✅ 测试建议和命令
6. ✅ 配置管理指南
---
## 📞 后续支持
### 如遇问题
1. **数据库问题**:
- 检查迁移脚本是否完整执行
- 查看表结构: `DESC users;`
2. **后端问题**:
- 查看日志: `docker-compose logs backend`
- 检查端口: `netstat -ano | findstr 8000`
3. **前端问题**:
- 打开浏览器控制台查看错误
- 检查网络请求
- 验证token是否有效
4. **API问题**:
- 使用Postman/curl测试接口
- 检查请求header
- 验证数据格式
---
## 🎉 总结
### 完成情况
-**后端开发**: 100% (21个API接口)
-**前端开发**: 100% (3个完整页面)
-**数据库设计**: 100% (7个新表)
-**文档编写**: 100% (2份完整文档)
-**代码清理**: 100% (删除测试代码)
- ⏸️ **AdminTool**: 已提供开发文档
### 代码质量
- ✅ 符合开发准则
- ✅ TypeScript类型安全
- ✅ 统一日志管理
- ✅ 模块化架构
- ✅ 注释清晰完整
### 可维护性
- ✅ 完全配置化
- ✅ 零硬编码
- ✅ 低耦合高内聚
- ✅ 易于扩展
---
## 🚀 立即开始
1. **阅读文档**: `部署文档_积分VIP签到系统.md`
2. **执行迁移**: 运行SQL脚本
3. **重启服务**: 重启后端服务
4. **部署前端**: 构建并部署
5. **开始使用**: 登录体验新功能!
---
**开发完成!祝您使用愉快!** 🎊

View File

@@ -0,0 +1,311 @@
# DesignerCEP 常用命令
## 🏗️ 三层架构说明
```
┌────────────────────────────────────────┐
│ 第 1 层Shell登录层
│ - 本地file:// 协议CEP 扩展) │
│ - 服务器http://127.0.0.1:8000/shell/ │
│ - 作用:登录、下载、启动 │
└────────────────────────────────────────┘
┌────────────────────────────────────────┐
│ 第 2 层Core业务层
│ - http://127.0.0.1:8000/core/vX.X.X/ │
│ - 作用:所有业务功能 │
└────────────────────────────────────────┘
┌────────────────────────────────────────┐
│ 第 3 层Server后端服务器
│ - http://127.0.0.1:8000/api/v1/ │
│ - 作用API、核心算法、数据库 │
└────────────────────────────────────────┘
```
---
## 📦 构建命令
### 开发模式
```bash
cd D:\main\DesignerCEP\Designer
npm run dev # 启动开发服务器Core
```
### 构建 Core业务层
```bash
cd D:\main\DesignerCEP\Designer
npm run build:core # 构建 Core 应用
```
### 构建 Shell登录层
```bash
cd D:\main\DesignerCEP\Designer
npm run build:shell # 构建 Shell登录、更新、加载 Core
# 构建后部署到服务器
cd ..
Copy-Item -Path "Designer\dist" -Destination "Server\Designer\dist" -Recurse -Force
```
---
## 🚀 发布命令
### 自动发布 Core推荐
```bash
cd D:\main\DesignerCEP
python auto_deploy_core.py --version v1.1.2 # 只构建打包
python auto_deploy_core.py --version v1.1.2 --update-db # 构建打包 + 更新数据库
python auto_deploy_core.py --version v1.1.2 --update-db --skip-clean # 不清除缓存
```
### 手动发布步骤
```bash
# 1. 构建
cd D:\main\DesignerCEP\Designer
npm run build:core
# 2. 重命名入口文件
cd dist_core
ren index-core.html index.html
# 3. 打包 ZIP
# 把 dist_core 目录打包成 core-v1.1.2.zip
# 4. 上传到 Server/archives/
```
---
## 🗄️ 服务器命令
### 启动后端服务
```bash
cd D:\main\DesignerCEP\Server
python main.py # 启动 FastAPI 服务器
python -m uvicorn app.main:app --reload # 或使用 uvicorn推荐
```
### 更新数据库版本
```bash
cd D:\main\DesignerCEP\Server
python update_version.py
```
### 部署 Shell 到服务器
```bash
# 1. 构建 Shell
cd D:\main\DesignerCEP\Designer
npm run build:shell
# 2. 复制到服务器
cd ..
Copy-Item -Path "Designer\dist" -Destination "Server\Designer\dist" -Recurse -Force
# 3. 重启服务器
cd Server
python -m uvicorn app.main:app --reload
```
**验证:** 访问 `http://127.0.0.1:8000/` 应该自动跳转到 Shell 登录页
---
## 🧹 清理命令
### 清除客户端缓存
```powershell
Remove-Item -Recurse -Force "$env:APPDATA\DesignerCache"
```
### 清除 CEP 扩展缓存
```powershell
Remove-Item -Recurse -Force "$env:APPDATA\Adobe\CEP\extensions\Designer"
Remove-Item -Recurse -Force "$env:APPDATA\Adobe\CEP\extensions\Designer-dev"
```
---
## 📁 重要目录
| 目录 | 说明 |
|------|------|
| `Designer/src/view/` | Core Vue 页面组件 |
| `Designer/src/launcher/` | Shell 源代码(登录、更新器)|
| `Designer/src/api/jsxApi/inline/` | 内联 JSX 函数 |
| `Designer/dist_core/` | Core 构建输出 |
| `Designer/dist/` | Shell 构建输出 |
| `Server/Designer/dist/` | Shell 服务器部署目录 ⭐ |
| `Server/archives/` | Core 版本 ZIP 包存放 |
| `%APPDATA%\DesignerCache\` | 客户端 Core 缓存目录 |
---
## 🌐 URL 访问路径
| URL | 说明 | 文件位置 |
|-----|------|---------|
| `http://127.0.0.1:8000/` | 根路径(自动跳转) | 重定向到 Shell |
| `http://127.0.0.1:8000/shell/` | Shell 登录页 | `Server/Designer/dist/` |
| `http://127.0.0.1:8000/core/v1.2.4/` | Core 业务页 | `%APPDATA%\DesignerCache\v1.2.4\` |
| `http://127.0.0.1:8000/api/v1/` | 后端 API | Server 代码 |
| `http://127.0.0.1:8000/download/` | Core 下载 | `Server/archives/` |
---
## 🔧 开发新功能
### 📝 添加纯前端 JSX 功能
#### 1. 在 layer.ts 添加函数
```typescript
// Designer/src/api/jsxApi/inline/layer.ts
export async function (): Promise<JSXResponse> {
const jsx = `
try {
// JSX 代码
return JSXUtils.stringify({ success: true });
} catch (error) {
return JSXUtils.stringify({ error: error.toString() });
}
`;
return evalInlineJSX(jsx);
}
```
#### 2. 在 Home.vue 调用
```typescript
import * as Layer from '@/api/jsxApi/inline/layer';
const handle新功能 = async () => {
const res = await Layer.();
if (res.success) {
Message.success('成功');
}
};
```
---
### 🔐 添加混合架构功能(前端 + 后端)
**适用场景:** 需要保护核心算法、AI 计算、复杂数学模型等
#### 1. 后端实现Server/app/api/v1/jsx_demo.py
```python
class YourFeatureRequest(BaseModel):
param1: str
param2: int
@router.post("/your-feature")
async def your_feature(
request: YourFeatureRequest,
x_api_key: Optional[str] = Header(None)
):
# 验证 API Key
if not validate_api_key(x_api_key):
raise HTTPException(403, "无效的 API Key")
# 🔒 核心算法(客户端看不到)
result = complex_algorithm(request.param1, request.param2)
return {"success": True, "result": result}
```
#### 2. 前端实现Designer/src/api/jsxApi/inline/your-feature.ts
```typescript
export async function yourFeature(param1: string, param2: number): Promise<JSXResponse> {
// 1. 发送到服务器计算
const response = await fetch(`${config.apiBaseUrl}/jsx_demo/your-feature`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': 'demo_key_123' // 🔐 API Key
},
body: JSON.stringify({ param1, param2 })
});
const serverResult = await response.json();
// 2. 应用服务器结果到 PS
const jsx = `
// 使用 serverResult 执行 PS 操作
`;
return evalInlineJSX(jsx);
}
```
**参考文档:**
- `tempdocs/framework_guide.md` - 完整框架说明
- `tempdocs/quick_start_template.md` - 快速开发模板
---
## 🧪 测试命令
### 测试混合架构 Demo
```bash
# 1. 启动后端
cd D:\main\DesignerCEP\Server
python -m uvicorn app.main:app --reload
# 2. 在 PS 中创建图层,名称为数学表达式
# 如87-98, 100+200
# 3. 点击"智能配色"按钮
# 前端会获取图层名称 → 发送到服务器计算 → 显示结果
```
### 测试 API Key 验证
```bash
# 使用 curl 测试
curl -X POST http://127.0.0.1:8000/api/v1/jsx_demo/calculate \
-H "Content-Type: application/json" \
-H "X-API-Key: demo_key_123" \
-d '{"expression": "87-98"}'
```
---
## 🔄 完整发布流程
### 发布新版本 Core
```bash
# 1. 修改代码
# 2. 构建 + 打包 + 更新数据库(一键完成)
cd D:\main\DesignerCEP
python auto_deploy_core.py --version v1.2.5 --update-db
# 3. 清除客户端缓存(测试用)
Remove-Item -Recurse -Force "$env:APPDATA\DesignerCache"
# 4. 重新登录测试
```
### 更新 Shell
```bash
# 1. 构建 Shell
cd D:\main\DesignerCEP\Designer
npm run build:shell
# 2. 部署到服务器
cd ..
Copy-Item -Path "Designer\dist" -Destination "Server\Designer\dist" -Recurse -Force
# 3. 重启后端(如果使用 --reload 会自动重启)
```
---
## 📚 相关文档
| 文档 | 说明 |
|------|------|
| `混合架构开发框架指南.md` | 混合架构框架完整指南 |
| `混合架构快速开发模板.md` | 5 分钟快速开发模板 |
| `混合方案Demo说明.md` | 混合方案 Demo 说明 |
| `许可证验证接口文档.md` | 许可证验证接口文档 |
| `后端部署Shell指南.md` | Shell 服务器部署指南 |
| `API密钥使用指南.md` | API Key 使用指南 |

View File

@@ -0,0 +1,416 @@
# ✅ 必须修改清单(上线前)
## 🎯 修改目标
解决 5 个架构问题:
1. ✅ CORS 支持 CEP 环境(`Origin: null`
2. ✅ 生产环境 API 地址配置
3. ✅ 静态文件由 Caddy 处理(性能优化)
4. ✅ Token 通过 Header 传递(已经正确,确认即可)
5. ✅ 使用 Caddy 自动 HTTPS
---
## 📝 需要修改的文件
### 1. 后端 CORS 配置 ⭐ 重要
**文件**: `Server/app/main.py`
**修改前** (第 16-28 行):
```python
# CORS configuration
origins = [
"http://localhost:5173", # Vite default
"http://localhost:3000",
"*" # For development convenience
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
```
**修改后**:
```python
# ========== CORS 配置 ==========
import os
IS_DEV = os.getenv("ENV", "development") == "development"
if IS_DEV:
# 开发环境:保持宽松
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
else:
# 生产环境:严格配置
allowed_origins = os.getenv("ALLOWED_ORIGINS", "").split(",")
app.add_middleware(
CORSMiddleware,
allow_origins=allowed_origins,
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["*"],
)
# ✅ CEP 环境特殊处理
@app.middleware("http")
async def cep_cors_middleware(request: Request, call_next):
origin = request.headers.get("origin")
# CEP 的 Origin 是 null 或 cep://
if origin in ["null", None] or (origin and origin.startswith("cep://")):
response = await call_next(request)
response.headers["Access-Control-Allow-Origin"] = "*"
response.headers["Access-Control-Allow-Credentials"] = "true"
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
response.headers["Access-Control-Allow-Headers"] = "*"
return response
return await call_next(request)
```
**还需要在文件开头添加导入**:
```python
from fastapi import FastAPI, Request # ← 添加 Request
```
---
### 2. 删除 FastAPI 静态文件挂载 ⭐ 重要
**文件**: `Server/app/main.py`
**修改前** (第 36-55 行):
```python
# Mount archives directory for download
app.mount("/download", StaticFiles(directory="archives"), name="download")
# Mount Shell directory (登录页面)
shell_dir = Path(__file__).parent.parent / "Designer"
if shell_dir.exists():
app.mount("/shell", StaticFiles(directory=str(shell_dir), html=True), name="shell")
print(f"✓ Shell 已挂载: {shell_dir}")
else:
print(f"⚠️ Shell 目录不存在: {shell_dir}")
print(" 请先运行: cd Designer && npm run build:shell")
# Mount DesignerCache directory to serve Core application files
designer_cache = Path.home() / "AppData" / "Roaming" / "DesignerCache"
if designer_cache.exists():
app.mount("/core", StaticFiles(directory=str(designer_cache), html=True), name="core")
else:
# Create directory if it doesn't exist
designer_cache.mkdir(parents=True, exist_ok=True)
app.mount("/core", StaticFiles(directory=str(designer_cache), html=True), name="core")
```
**修改后**:
```python
# ❌ 删除所有静态文件挂载(交给 Caddy 处理)
# 生产环境不需要 FastAPI 处理静态文件
# 可以添加健康检查接口
@app.get("/health")
def health_check():
return {"status": "healthy", "timestamp": datetime.now().isoformat()}
```
---
### 3. 后端环境变量配置
**文件**: `Server/.env`
**新建或修改**:
```bash
# 环境配置
ENV=production
# 项目配置
PROJECT_NAME=DesignerCEP
API_V1_STR=/api/v1
# 安全配置
SECRET_KEY=your-secret-key-here-change-this-in-production
# 数据库配置
DATABASE_URL=mysql://username:password@localhost:3306/designer_cep
# CORS 允许的来源(生产环境)
ALLOWED_ORIGINS=https://your-domain.com,https://www.your-domain.com
# 管理员配置
ADMIN_TOKEN=your-admin-token-here
```
**⚠️ 重要**:将 `your-domain.com` 替换为你的实际域名!
---
### 4. 前端 API 地址配置
**文件**: `Designer/src/config/index.ts`
**修改前** (第 14-16 行):
```typescript
apiServer: isDev
? 'http://127.0.0.1:8000'
: 'http://127.0.0.1:8000', // ❌ 生产环境还是 localhost
```
**修改后**:
```typescript
apiServer: isDev
? 'http://127.0.0.1:8000'
: 'https://your-domain.com', // ✅ 生产环境用线上地址
```
**或者使用环境变量** (推荐):
**新建文件**: `Designer/.env.production`
```bash
VITE_API_SERVER=https://your-domain.com
```
**修改**: `Designer/src/config/index.ts`
```typescript
const isDev = import.meta.env.DEV;
const PROD_API_SERVER = import.meta.env.VITE_API_SERVER || 'http://127.0.0.1:8000';
export const config = {
apiServer: isDev
? 'http://127.0.0.1:8000'
: PROD_API_SERVER, // ✅ 从环境变量读取
apiPrefix: '/api/v1',
shellLoginUrl: isDev
? 'http://localhost:5173/#/login'
: 'https://your-domain.com/shell/#/login', // ✅ 生产环境登录页
get apiBaseUrl() {
return `${this.apiServer}${this.apiPrefix}`;
},
getDownloadUrl(path: string): string {
if (path.startsWith('/')) {
return `${this.apiServer}${path}`;
}
return path;
},
getCoreUrl(version: string): string {
if (isDev) {
return `http://localhost:5173/`;
}
return `${this.apiServer}/core/${version}/index.html`; // ✅ 线上加载
}
};
```
---
### 5. 确认 Token 传递方式(已经正确)✅
**文件**: `Designer/src/utils/request.ts`
**检查** (第 16-18 行):
```typescript
const token = localStorage.getItem('token');
if (token) {
config.headers['Authorization'] = `Bearer ${token}`; // ✅ 已经正确
}
```
**这个不需要改,已经是对的!**
---
## 🚀 部署步骤
### 步骤 1: 修改代码
```bash
# 1. 后端修改
# - Server/app/main.pyCORS + 删除静态挂载)
# - Server/.env环境变量
# 2. 前端修改
# - Designer/.env.productionAPI 地址)
# - Designer/src/config/index.ts可选
```
### 步骤 2: 本地测试
```bash
# 1. 测试后端
cd Server
ENV=development python -m uvicorn app.main:app --reload
# 访问 http://localhost:8000/health
# 2. 测试前端
cd Designer
npm run dev
# 访问 http://localhost:5173
# 登录测试,检查 Network 标签
```
### 步骤 3: 构建生产版本
```bash
cd Designer
npm run build
# 检查生成的文件
ls -la dist/Shell/
ls -la dist/Designer/
```
### 步骤 4: 部署到服务器
```bash
# 方法 A: 自动化部署(推荐)
cd AdminTool
python auto_deploy_core.py --version 1.0.6 --deploy --update-db
# 方法 B: 手动部署
scp -r dist/Shell/* user@server:/var/www/DesignerCEP/Server/static/shell/
scp -r dist/Designer/* user@server:/var/www/DesignerCEP/Server/static/core/1.0.6/
```
### 步骤 5: 配置 Caddy
参考 `Caddy部署指南.md` 的完整配置。
**关键配置**:
```caddy
your-domain.com {
# API → FastAPI
handle /api/* {
reverse_proxy localhost:8000
}
# Shell 静态文件
handle /shell/* {
root * /var/www/DesignerCEP/Server/static/shell
file_server
}
# Core 静态文件
handle /core/* {
root * /var/www/DesignerCEP/Server/static/core
file_server
}
# 下载文件
handle /downloads/* {
root * /var/www/DesignerCEP/Server/static/downloads
file_server
}
}
```
### 步骤 6: 启动服务
```bash
# 1. 启动 FastAPI
sudo systemctl restart designer-cep
# 2. 启动 Caddy
sudo systemctl restart caddy
# 3. 检查状态
sudo systemctl status designer-cep
sudo systemctl status caddy
```
### 步骤 7: 测试上线
```bash
# 1. 测试 API
curl https://your-domain.com/api/v1/health
# 2. 测试 CEP CORS
curl -X OPTIONS https://your-domain.com/api/v1/client/login \
-H "Origin: null" \
-H "Access-Control-Request-Method: POST" \
-v
# 3. 浏览器测试
# 打开 https://your-domain.com/shell/
# 登录并检查功能
```
---
## 📊 修改影响评估
| 修改项 | 风险 | 是否必须 | 回滚难度 |
|--------|------|---------|---------|
| CORS CEP 支持 | ⭐ 低 | 是 | 容易 |
| 删除静态挂载 | ⭐⭐ 中 | 是 | 中等 |
| 环境变量配置 | ⭐ 低 | 是 | 容易 |
| 前端 API 地址 | ⭐⭐ 中 | 是 | 容易 |
| Token 方式确认 | ⭐ 无 | 否(已正确) | - |
---
## 🔍 常见问题
### Q: 改完后本地开发会受影响吗?
**A**: 不会!代码中有环境判断:
- 开发环境(`ENV=development`保持原样CORS 宽松
- 生产环境(`ENV=production`):严格 CORS + CEP 支持
### Q: 如果改错了怎么办?
**A**:
1. Git 回滚:`git checkout Server/app/main.py`
2. 或保留 FastAPI 静态挂载(性能差一点,但能用)
3. 环境变量改回 `ENV=development`
### Q: 必须用 Caddy 吗?
**A**: 不必须,但强烈推荐:
- ✅ Caddy配置简单自动 HTTPS
- ⚠️ Nginx配置复杂需要手动申请证书
- 二选一即可
---
## ✅ 完成后检查
- [ ] 本地开发环境测试通过
- [ ] 生产构建无错误
- [ ] API 请求正常200
- [ ] CORS 无错误
- [ ] CEP 扩展能登录
- [ ] 浏览器能访问
- [ ] Token 在 Header 里
- [ ] 静态文件正常加载
- [ ] HTTPS 证书有效
---
## 📞 如需帮助
1. 查看日志:`sudo journalctl -u designer-cep -f`
2. 查看 Caddy 日志:`sudo journalctl -u caddy -f`
3. 测试 API`curl -v https://your-domain.com/api/v1/health`
---
**总结**:改动不大,主要是添加环境判断和 CEP CORS 支持,风险可控!

View File

@@ -0,0 +1,87 @@
# 🚀 快速开始 - 前端 App 部署
## ⚡ 一分钟快速部署
### 1. 构建前端
```bash
cd D:\main\DesignerCEP\Designer
npm run build:core
```
### 2. 启动管理工具
```bash
cd D:\main\DesignerCEP\AdminTool
python admin_gui.py
```
### 3. 部署到服务器
1. 打开 **"自动化部署"** 标签页
2. 点击 **"浏览..."** 选择 `Designer/dist_core` 目录
3. 输入备注(可选)
4. 点击 **"🚀 部署到服务器"**
5. 等待部署完成(约 1-2 分钟)
### 4. 验证部署
访问https://app.aidg168.uk/
---
## 📝 注意事项
### ✅ 部署前检查
- [ ] 已运行 `npm run build:core`
- [ ] `dist_core/` 目录存在且完整
- [ ] 服务器 SSH 信息正确
- [ ] 数据库连接正常
### ⚠️ 重要提醒
- **部署会直接替换线上版本**,建议在非高峰期操作
- **首次部署**可能需要创建数据库表(会自动执行)
- **回滚功能**可以快速恢复到之前的版本
---
## 🔄 回滚操作
如果新版本有问题:
1. 打开 **"版本历史管理"**
2. 选择要回滚的版本(点击表格行)
3. 点击 **"⏪ 回滚到选中版本"**
4. 确认操作
5. 刷新 https://app.aidg168.uk/ 验证
---
## 🗑️ 清理旧版本
定期删除不需要的历史版本:
1. 选择要删除的版本
2. 点击 **"🗑️ 删除选中版本"**
3. 确认删除
**注意**:无法删除当前正在使用的版本
---
## ❓ 遇到问题?
### 连接失败
- 检查服务器 SSH 信息
- 点击 **"测试连接"** 按钮验证
### 数据库错误
- 确认 `deploy_config.json` 中的 MySQL 配置
- MySQL 必须运行在远程服务器上103.97.201.136
### 部署后页面没更新
- 清除浏览器缓存Ctrl+F5
- 或重启 Caddy`systemctl restart caddy`
---
## 📚 详细文档
查看完整文档:[部署功能使用说明.md](./部署功能使用说明.md)

View File

@@ -0,0 +1,865 @@
# 🔧 DesignerCEP 架构问题修正方案
## 问题汇总
经过仔细检查,当前架构存在以下 **5 个严重问题**,会导致本地测试 OK 但上线后炸:
1.**CORS 配置错误**:只写 `file://`CEP 环境实际是 `Origin: null``cep://`
2.**Token 暴露在 URL**`#/home?token=xxx` 会泄露到日志、分享链接
3.**localhost:8000 的硬编码**:文档说 Core 可从 `http://localhost:8000/core/...` 加载,但没说这个服务谁提供
4.**Cloudflare 证书方案冲突**:用 Cloudflare 代理时不应该用 Certbot
5.**静态文件服务混乱**FastAPI 和 Nginx 职责不清
---
## 问题 1: CORS 配置错误
### 当前问题
```python
# Server/app/main.py (当前代码)
origins = [
"http://localhost:5173",
"http://localhost:3000",
"*" # ❌ 这个在生产环境不安全
]
# 部署架构说明.md (第 264 行)
ALLOWED_ORIGINS = [
"https://your-domain.com",
"file://", # ❌ CEP 环境不是 file://
]
```
**实际情况**
- CEP/CEF 环境的 Origin 是 `null``cep://xxx`
- 预检 OPTIONS 请求会因为 Origin 不匹配而失败
- 导致所有 API 调用都会被 CORS blocked
### ✅ 修正方案
```python
# Server/app/main.py
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
import os
app = FastAPI(title=settings.PROJECT_NAME)
# 环境判断
IS_PRODUCTION = os.getenv("ENV", "development") == "production"
# CORS 中间件 - 支持 CEP 环境
if IS_PRODUCTION:
# 生产环境:严格的 CORS
origins = [
"https://your-domain.com",
"https://www.your-domain.com",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["*"],
)
# 额外处理 CEP 环境的 Origin: null
@app.middleware("http")
async def cep_cors_middleware(request: Request, call_next):
origin = request.headers.get("origin")
# CEP 环境Origin 是 null 或 cep://
if origin in ["null", None] or origin.startswith("cep://"):
response = await call_next(request)
response.headers["Access-Control-Allow-Origin"] = "*"
response.headers["Access-Control-Allow-Credentials"] = "true"
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
response.headers["Access-Control-Allow-Headers"] = "*"
return response
return await call_next(request)
else:
# 开发环境:宽松的 CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
```
**关键点**
- ✅ 生产环境只允许你的域名
- ✅ 额外处理 CEP 的 `Origin: null``cep://` 情况
- ✅ OPTIONS 预检请求会正常通过
---
## 问题 2: Token 暴露在 URL
### 当前问题
```typescript
// 文档中写的跳转方式(第 178 行)
/core/1.0.0/#/home?token=xxx&username=xxx&device_id=xxx
// 问题:
// 1. Nginx access.log 会记录 URLtoken 泄露)
// 2. Cloudflare 日志也会记录
// 3. 用户截图/分享会带上 token
// 4. 浏览器历史记录会保存 token
```
**实际代码情况**
- 登录接口返回 token
- 前端保存到 localStorage
- 没有看到 URL 传 token 的代码(好消息!)
但**文档写错了**,容易误导开发。
### ✅ 修正方案
**正确的流程**(代码已经是对的,只需更新文档):
```
1. 用户登录
POST /api/v1/client/login
返回: { token, username, version, permissions }
2. 前端保存到 localStorage
localStorage.setItem('token', token)
localStorage.setItem('username', username)
localStorage.setItem('auto_login', 'true')
3. 跳转到 Core不带 token
/core/1.0.0/#/home
✅ URL 干净,没有敏感信息
4. Core 启动时从 localStorage 读取 token
const token = localStorage.getItem('token')
5. API 请求时通过 Header 传递
Authorization: Bearer ${token}
```
**更新 Axios 配置**
```typescript
// Designer/src/api/request.ts
import axios from 'axios';
import { config } from '@/config';
const request = axios.create({
baseURL: config.apiBaseUrl,
timeout: 30000,
});
// 请求拦截器 - 自动添加 token
request.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token');
if (token) {
// ✅ 通过 Header 传递 token不在 URL 里
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 响应拦截器 - 处理 401 自动跳转登录
request.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
// Token 过期,清除并跳转登录
localStorage.removeItem('token');
localStorage.removeItem('username');
localStorage.removeItem('auto_login');
window.location.href = config.shellLoginUrl;
}
return Promise.reject(error);
}
);
export default request;
```
---
## 问题 3: localhost:8000 的来源不明
### 当前问题
```typescript
// Designer/src/config/index.ts (第 15-16 行)
apiServer: isDev
? 'http://127.0.0.1:8000'
: 'http://127.0.0.1:8000', // ❌ 生产环境也用 localhost
// Designer/src/launcher/utils/updater.ts (第 40 行)
getCoreUrl(version: string): string {
return `${this.apiServer}/core/${version}/index.html`;
}
// 部署架构说明.md (第 65 行)
(https://your-domain.com/core/1.0.0/)
```
**问题**
- 文档说 Core 可能从 `localhost:8000` 加载
- 但没说这个本地服务**谁提供**
- 用户电脑上没有这个服务 → **直接打不开**
### ✅ 修正方案
**方案 A完全在线模式推荐**
Core 始终从服务器加载,不依赖本地服务:
```typescript
// Designer/src/config/index.ts
export const config = {
// 后端 API 服务器地址
apiServer: isDev
? 'http://127.0.0.1:8000' // 开发环境:本地后端
: 'https://your-domain.com', // ✅ 生产环境:线上服务器
// Shell 登录页面地址
shellLoginUrl: isDev
? 'http://localhost:5173/#/login'
: 'https://your-domain.com/shell/#/login', // ✅ 线上 Shell
// 获取 Core 应用加载地址
getCoreUrl(version: string): string {
if (isDev) {
return `http://localhost:5173/`; // 开发环境Vite dev server
}
return `${this.apiServer}/core/${version}/index.html`; // ✅ 线上 Core
}
};
```
**方案 BCEP 扩展离线模式(需要额外开发)**
如果要支持离线使用CEP 扩展从本地加载),需要:
1. **CEP 扩展自带本地服务器**
```typescript
// 在 CEP 扩展中启动一个轻量级 HTTP 服务器(使用 Node.js http-server
import { spawn } from 'child_process';
import path from 'path';
class LocalServer {
private server: any;
start() {
const cacheDir = path.join(os.homedir(), 'AppData', 'Roaming', 'DesignerCache');
// 启动本地 HTTP 服务器
this.server = spawn('http-server', [
cacheDir,
'-p', '8000',
'--cors',
'-c-1' // 禁用缓存
], {
cwd: __dirname,
shell: true
});
console.log('✓ 本地服务器已启动: http://localhost:8000');
}
stop() {
if (this.server) {
this.server.kill();
}
}
}
```
2. **检测端口占用并动态分配**
```typescript
import net from 'net';
async function getAvailablePort(startPort: number): Promise<number> {
return new Promise((resolve) => {
const server = net.createServer();
server.listen(startPort, () => {
const port = (server.address() as any).port;
server.close(() => resolve(port));
});
server.on('error', () => {
resolve(getAvailablePort(startPort + 1));
});
});
}
// 使用
const port = await getAvailablePort(8000);
```
**我的建议**
-**使用方案 A**(完全在线)- 简单可靠
- ⚠️ 方案 B 需要额外开发,且有端口冲突、权限等问题
- 📝 **更新文档**,明确说明 Core 从哪里加载
---
## 问题 4: Cloudflare 证书方案冲突
### 当前问题
```bash
# 部署前检查清单.md - 推荐 Certbot
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.com
```
**实际情况**
- 你用的是 **Cloudflare 橙云代理**
- Cloudflare 已经提供了 SSL/TLS 加密
- 继续用 Certbot 会遇到:
- Let's Encrypt 验证失败Cloudflare 代理了请求)
- 证书续期问题
- 真实 IP 暴露风险
### ✅ 修正方案
**Cloudflare 场景的正确配置**
#### 1. Cloudflare SSL/TLS 模式
在 Cloudflare 控制台设置:
```
SSL/TLS → Overview → 选择 "Full (Strict)"
```
**模式说明**
-**Flexible**: Cloudflare → 源站是 HTTP不安全
- ⚠️ **Full**: Cloudflare → 源站是 HTTPS但不验证证书
-**Full (Strict)**: Cloudflare → 源站是 HTTPS验证证书- **推荐**
#### 2. 生成 Cloudflare Origin Certificate
在 Cloudflare 控制台:
```
SSL/TLS → Origin Server → Create Certificate
选项:
- 私钥类型RSA (2048)
- 有效期15 年
- 域名:*.your-domain.com, your-domain.com
生成后会得到:
- Origin Certificate (保存为 cloudflare-origin.pem)
- Private Key (保存为 cloudflare-origin.key)
```
#### 3. 配置 Nginx
```nginx
# /etc/nginx/sites-available/designer-cep
server {
listen 443 ssl http2;
server_name your-domain.com www.your-domain.com;
# ✅ 使用 Cloudflare Origin Certificate
ssl_certificate /etc/nginx/ssl/cloudflare-origin.pem;
ssl_certificate_key /etc/nginx/ssl/cloudflare-origin.key;
# SSL 配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Cloudflare Real IP获取真实用户 IP
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 131.0.72.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2c0f:f248::/32;
set_real_ip_from 2a06:98c0::/29;
real_ip_header CF-Connecting-IP;
# 其他配置...
}
# HTTP 重定向到 HTTPS
server {
listen 80;
server_name your-domain.com www.your-domain.com;
return 301 https://$server_name$request_uri;
}
```
#### 4. 安装证书
```bash
# 1. 创建 SSL 目录
sudo mkdir -p /etc/nginx/ssl
sudo chmod 700 /etc/nginx/ssl
# 2. 上传证书文件(从本地上传)
sudo nano /etc/nginx/ssl/cloudflare-origin.pem
# 粘贴 Origin Certificate
sudo nano /etc/nginx/ssl/cloudflare-origin.key
# 粘贴 Private Key
# 3. 设置权限
sudo chmod 600 /etc/nginx/ssl/*
# 4. 测试配置
sudo nginx -t
# 5. 重启 Nginx
sudo systemctl restart nginx
```
**优势**
- ✅ 证书有效期 15 年,不需要续期
- ✅ 不需要 Certbot 验证
- ✅ Cloudflare 和源站双重加密
- ✅ 自动获取真实用户 IP
---
## 问题 5: 静态文件服务混乱
### 当前问题
```python
# Server/app/main.py (第 37-55 行)
# ❌ FastAPI 直接挂载静态文件目录
app.mount("/download", StaticFiles(directory="archives"), name="download")
app.mount("/shell", StaticFiles(directory=str(shell_dir), html=True), name="shell")
app.mount("/core", StaticFiles(directory=str(designer_cache), html=True), name="core")
```
```nginx
# 部署架构说明.md (第 284-289 行)
# ✅ 文档说让 Nginx 处理静态文件
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
```
**矛盾**
- FastAPI 挂载了静态文件 → API 和静态抢资源
- 文档说用 Nginx 缓存 → 但请求还是先到 FastAPI
- 大文件下载shell.zip性能差
### ✅ 修正方案
**明确职责**
- **Nginx** → 处理所有静态文件HTML/JS/CSS/ZIP
- **FastAPI** → 只处理 API 请求(/api/v1/...
#### 1. 修改 FastAPI只留 API
```python
# Server/app/main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware, Request
import os
from app.core.config import settings
from app.api.v1 import auth, client, admin, analytics, jsx_demo
from app.db import init_db
app = FastAPI(title=settings.PROJECT_NAME)
# 环境判断
IS_PRODUCTION = os.getenv("ENV", "development") == "production"
# ========== CORS 配置 ==========
if IS_PRODUCTION:
origins = [
"https://your-domain.com",
"https://www.your-domain.com",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["*"],
)
# 处理 CEP 环境的 Origin: null
@app.middleware("http")
async def cep_cors_middleware(request: Request, call_next):
origin = request.headers.get("origin")
if origin in ["null", None] or origin.startswith("cep://"):
response = await call_next(request)
response.headers["Access-Control-Allow-Origin"] = "*"
response.headers["Access-Control-Allow-Credentials"] = "true"
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
response.headers["Access-Control-Allow-Headers"] = "*"
return response
return await call_next(request)
else:
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# ========== API 路由 ==========
app.include_router(auth.router, prefix=f"{settings.API_V1_STR}/auth", tags=["authentication"])
app.include_router(client.router, prefix=f"{settings.API_V1_STR}/client", tags=["client"])
app.include_router(admin.router, prefix=f"{settings.API_V1_STR}/admin", tags=["admin"])
app.include_router(analytics.router, prefix=f"{settings.API_V1_STR}/analytics", tags=["analytics"])
app.include_router(jsx_demo.router, prefix=f"{settings.API_V1_STR}/jsx_demo", tags=["jsx_demo"])
# ❌ 删除所有静态文件挂载
# app.mount("/download", ...) # 删除
# app.mount("/shell", ...) # 删除
# app.mount("/core", ...) # 删除
@app.get("/")
def read_root():
return {"message": "DesignerCEP API Server", "version": "1.0.0"}
@app.get("/health")
def health_check():
return {"status": "healthy"}
@app.on_event("startup")
def on_startup():
init_db()
if __name__ == "__main__":
import uvicorn
uvicorn.run("app.main:app", host="127.0.0.1", port=8000, reload=True)
```
#### 2. 配置 Nginx完整版
```nginx
# /etc/nginx/sites-available/designer-cep
# ========== 上游 FastAPI 服务器 ==========
upstream fastapi_backend {
server 127.0.0.1:8000;
}
# ========== HTTPS 服务器 ==========
server {
listen 443 ssl http2;
server_name your-domain.com www.your-domain.com;
# SSL 证书Cloudflare Origin Certificate
ssl_certificate /etc/nginx/ssl/cloudflare-origin.pem;
ssl_certificate_key /etc/nginx/ssl/cloudflare-origin.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Cloudflare Real IP
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 131.0.72.0/22;
real_ip_header CF-Connecting-IP;
# 日志
access_log /var/log/nginx/designer-cep-access.log;
error_log /var/log/nginx/designer-cep-error.log;
# ========== API 请求(转发到 FastAPI==========
location /api/ {
proxy_pass http://fastapi_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 超时设置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 禁用缓存API 不应该缓存)
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires "0";
}
# ========== 静态文件Shell在线登录页==========
location /shell/ {
alias /var/www/DesignerCEP/Server/static/shell/;
try_files $uri $uri/ /shell/index.html;
# HTML 文件:不缓存(保证拿到最新版本)
location ~ \.html$ {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires "0";
}
# JS/CSS长期缓存文件名带 hash
location ~* \.(js|css)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# 图片/字体:长期缓存
location ~* \.(png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
# ========== 静态文件Core核心应用==========
location /core/ {
alias /var/www/DesignerCEP/Server/static/core/;
try_files $uri $uri/ =404;
# HTML 文件:不缓存
location ~ \.html$ {
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
# JS/CSS长期缓存
location ~* \.(js|css)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# 图片/字体:长期缓存
location ~* \.(png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
# ========== 下载文件Shell.zip / Core.zip ==========
location /downloads/ {
alias /var/www/DesignerCEP/Server/static/downloads/;
# 允许大文件下载
client_max_body_size 500M;
# 启用断点续传
add_header Accept-Ranges bytes;
# 缓存 ZIP 文件1 天)
expires 1d;
add_header Cache-Control "public";
}
# ========== 根路径 ==========
location / {
return 301 /shell/;
}
# ========== Gzip 压缩 ==========
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml;
# ========== 安全头 ==========
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
}
# ========== HTTP → HTTPS 重定向 ==========
server {
listen 80;
server_name your-domain.com www.your-domain.com;
return 301 https://$server_name$request_uri;
}
```
#### 3. 启用配置
```bash
# 1. 测试配置
sudo nginx -t
# 2. 重启 Nginx
sudo systemctl restart nginx
# 3. 验证
curl -I https://your-domain.com/shell/
curl -I https://your-domain.com/api/v1/health
```
---
## 📊 修正后的架构对比
### 修正前(有问题)
```
CEP 扩展
↓ Origin: file:// ❌ CORS 失败
服务器 FastAPI
静态文件 + API 混在一起 ❌ 性能差
Token 在 URL ❌ 泄露风险
```
### 修正后(正确)
```
CEP 扩展
↓ Origin: null ✅ CORS 中间件处理
Nginx
├─> /api/ → FastAPI只处理 API
├─> /shell/ → 静态文件(高性能)
├─> /core/ → 静态文件(高性能)
└─> /downloads/ → 静态文件(支持断点续传)
Token 在 Header ✅ Authorization: Bearer xxx
```
---
## ✅ 总结:修正清单
### 1. 代码修改
- [ ] **Server/app/main.py**
- [ ] 添加 CEP CORS 中间件(处理 `Origin: null`
- [ ] 删除所有静态文件挂载
- [ ] 添加环境判断(生产/开发)
- [ ] **Designer/src/config/index.ts**
- [ ] 生产环境 apiServer 改为 `https://your-domain.com`
- [ ] 生产环境 shellLoginUrl 改为 `https://your-domain.com/shell/#/login`
- [ ] **Designer/src/api/request.ts**
- [ ] 确认 token 通过 `Authorization: Bearer` 传递
- [ ] 确认 401 自动跳转登录
### 2. 服务器配置
- [ ] **Cloudflare**
- [ ] SSL/TLS 模式设为 "Full (Strict)"
- [ ] 生成 Origin Certificate
- [ ] 下载证书和私钥
- [ ] **Nginx**
- [ ] 上传 Cloudflare 证书
- [ ] 更新 Nginx 配置(使用上面的完整配置)
- [ ] 添加 Cloudflare Real IP 配置
- [ ] 配置静态文件缓存策略
- [ ] 重启 Nginx
### 3. 文档更新
- [ ] **部署架构说明.md**
- [ ] 删除 `file://` 的 CORS 说明
- [ ] 添加 `Origin: null``cep://` 的处理方式
- [ ] 删除 `localhost:8000` 的混淆说明
- [ ] 明确 Core 从服务器加载
- [ ] 删除 token 在 URL 的示例
- [ ] **部署前检查清单.md**
- [ ] 删除 Certbot 步骤
- [ ] 添加 Cloudflare Origin Certificate 步骤
### 4. 环境变量
```bash
# Server/.env
ENV=production
PROJECT_NAME=DesignerCEP
API_V1_STR=/api/v1
SECRET_KEY=your-secret-key-here
DATABASE_URL=mysql://user:password@localhost:3306/designer_cep
```
---
## 🧪 测试验证
### 1. CORS 测试
```bash
# CEP 环境测试Origin: null
curl -X OPTIONS https://your-domain.com/api/v1/client/login \
-H "Origin: null" \
-H "Access-Control-Request-Method: POST" \
-v
# 期望输出:
# Access-Control-Allow-Origin: *
# Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
```
### 2. Token 安全测试
```bash
# 检查 Nginx 日志,不应该有 token
tail -f /var/log/nginx/designer-cep-access.log
# 期望:只有干净的 URL
# GET /core/1.0.0/#/home HTTP/2.0
# 不应该有GET /core/1.0.0/#/home?token=xxx
```
### 3. 静态文件性能测试
```bash
# 测试缓存头
curl -I https://your-domain.com/shell/assets/index-abc123.js
# 期望输出:
# Cache-Control: public, immutable
# Expires: (一年后的日期)
# 测试 HTML不应该缓存
curl -I https://your-domain.com/shell/index.html
# 期望输出:
# Cache-Control: no-cache, no-store, must-revalidate
```
---
**修正完成后,你的架构就真正可上线了!** 🎉

View File

@@ -0,0 +1,17 @@
# 检查 Caddy 状态
```bash
# 1. 查看 Caddy 监听的端口
ss -tlnp | grep caddy
# 2. 查看 Caddy 详细日志
journalctl -u caddy -n 100 --no-pager
# 3. 检查 Caddyfile 格式
caddy fmt --overwrite /etc/caddy/Caddyfile
cat /etc/caddy/Caddyfile
# 4. 测试 443 端口
curl -I -k https://localhost/
```

View File

@@ -0,0 +1,124 @@
# 🔍 检测现有版本功能
## 问题场景
如果你的服务器上已经有部署的版本(在 `/var/www/app/`),但版本管理系统中没有记录,可以使用「检测当前版本」功能。
---
## 🚀 使用步骤
### 1. 启动部署工具
```bash
cd AdminTool
python deploy_tool.py
```
### 2. 配置服务器信息
在「服务器配置」区域填写:
- 服务器地址
- SSH 端口
- 用户名
- 密码
点击「保存配置」
### 3. 点击「🔍 检测当前版本」
位置:「版本历史管理」区域的操作按钮
### 4. 确认操作
系统会:
- ✅ 扫描 `/var/www/app/` 目录
- ✅ 计算文件大小
- ✅ 获取最后修改时间
- ✅ 创建版本记录(格式:`existing_YYYYMMDD_HHMMSS`
- ✅ 备份到 `/var/www/app_versions/`
- ✅ 保存到版本管理系统
### 5. 查看结果
检测完成后:
- 版本历史列表会显示检测到的版本
- 该版本会被标记为 ✅ 当前版本
- 服务器上会保存一份备份
---
## 📋 示例
### 检测前
```
版本历史列表:
(暂无记录)
```
### 点击「检测当前版本」后
```
版本历史列表:
✅ existing_20231220_143025 (当前)
部署时间: 2023-12-20 14:30:25
大小: 12.3 MB
备注: 检测到的现有版本2023-12-20
```
---
## 💡 注意事项
1. **不会影响线上服务**
- 只读取文件信息
- 创建备份副本
- 不修改运行中的文件
2. **只能检测一次**
- 如果已有版本记录,建议先查看现有记录
- 如需重新检测,可先删除旧记录
3. **需要 SSH 权限**
- 需要读取 `/var/www/app/` 目录
- 需要写入 `/var/www/app_versions/` 目录
---
## ❓ 常见问题
### Q: 检测到的版本号为什么是 `existing_...` 格式?
**A**: 为了区分:
- `existing_YYYYMMDD_HHMMSS` = 检测到的现有版本
- `YYYYMMDD_HHMMSS` = 通过工具部署的版本
- `backup_YYYYMMDD_HHMMSS` = 自动备份的版本
### Q: 检测后可以回滚吗?
**A**: 可以!检测后的版本和部署的版本功能完全一样,都可以回滚。
### Q: 如果 /var/www/app/ 为空会怎样?
**A**: 会提示「目录为空或不存在」,不会创建记录。
### Q: 检测失败怎么办?
**A**: 检查:
1. SSH 连接是否正常
2. `/var/www/app/` 目录是否存在
3. 是否有读取权限
---
## 🎯 适用场景
1. **首次使用工具**
- 服务器上已有部署的版本
- 想将现有版本纳入版本管理
2. **迁移到新工具**
- 之前手动部署的版本
- 想使用版本管理功能
3. **重新初始化**
- 版本记录丢失
- 需要重新建立记录
---
**开始使用:`python deploy_tool.py`,点击「🔍 检测当前版本」** 🔍

View File

@@ -0,0 +1,152 @@
# 混合方案 Demo 说明
## 🎯 核心思想
**本地 = 简单执行 | 服务器 = 复杂计算**
---
## 📋 Demo 示例
### 功能:创建智能配色图层
#### 流程:
```
1⃣ 用户点击"智能配色"按钮
2⃣ 前端发送请求到服务器
POST /api/v1/jsx_demo/calculate-color
{
"layer_name": "智能配色_12345",
"base_color": "#FF6B9D"
}
3⃣ 🔒 服务器执行核心算法(客户端看不到)
- 根据图层名称计算最佳颜色
- 计算最佳不透明度
- 选择最佳混合模式
→ 返回结果:
{
"r": 255,
"g": 120,
"b": 180,
"opacity": 85,
"blend_mode": "overlay"
}
4⃣ 前端接收结果,执行简单 JSX
- 创建图层
- 应用服务器计算的参数
- 填充颜色
```
---
## 🔐 安全对比
### 当前内联方式(全在前端)
**前端代码:**
```typescript
const jsx = `
var layer = doc.artLayers.add();
layer.opacity = 85; // ⚠️ 算法暴露
layer.blendMode = BlendMode.OVERLAY;
// 复杂的颜色计算逻辑
var r = 核心算法1(); // ⚠️ 可被看到
var g = 核心算法2(); // ⚠️ 可被看到
var b = 核心算法3(); // ⚠️ 可被看到
`;
```
**问题:** 攻击者可以看到所有代码,复制算法
---
### 混合方式(计算在服务器)
**前端代码:**
```typescript
const colorResult = await fetch('/api/calculate-color', {...});
const { r, g, b, opacity } = colorResult;
const jsx = `
var layer = doc.artLayers.add();
layer.opacity = ${opacity}; // ✅ 只是应用参数
color.rgb.red = ${r}; // ✅ 只是应用参数
color.rgb.green = ${g};
color.rgb.blue = ${b};
`;
```
**服务器代码(客户端看不到):**
```python
# 🔒 核心算法在这里
def calculate_color(layer_name, base_color):
# 复杂的颜色理论计算
# 机器学习模型推荐
# 你的独家算法
return {r, g, b, opacity, blend_mode}
```
**优势:** 攻击者只能看到"发送请求 → 应用结果",看不到算法
---
## 📂 新增文件(不影响现有系统)
```
✅ 保留:
Designer/src/api/jsxApi/inline/layer.ts (原有内联 JSX)
Designer/src/api/jsxApi/inline/document.ts (原有内联 JSX)
新增:
Server/app/api/v1/jsx_demo.py (服务器计算 Demo)
Designer/src/api/jsxApi/inline/hybrid-demo.ts (混合调用 Demo)
```
**两种方式并存,互不影响!**
---
## 🚀 测试 Demo
1. **启动后端**
```bash
cd Server
python main.py
```
2. **启动前端开发模式**
```bash
cd Designer
npm run dev
```
3. **点击"智能配色"按钮**
- 会调用服务器计算颜色
- 然后在本地创建图层并应用
4. **查看控制台**
- 可以看到发送的参数
- 可以看到服务器返回的结果
- **但看不到服务器端的计算逻辑!**
---
## 💡 扩展建议
你可以把**任何核心功能**改成这种方式:
| 功能 | 本地执行 | 服务器计算 |
|------|---------|-----------|
| 创建图层 | ✅ 简单创建 | ❌ |
| 智能配色 | ✅ 应用颜色 | ✅ 计算最佳颜色 |
| AI 设计 | ✅ 应用结果 | ✅ AI 模型推理 |
| 自动排版 | ✅ 移动元素 | ✅ 计算最佳位置 |
| 滤镜参数 | ✅ 应用滤镜 | ✅ 计算最佳参数 |
**核心算法都在服务器,客户端只是"执行器"**

View File

@@ -0,0 +1,282 @@
# 混合方案安全性说明
## 📋 当前实现的安全特性
### 1⃣ 核心算法保护 ✅
**问题:** 如何防止客户端看到核心算法?
**解决:**
- ✅ 核心计算逻辑在服务器端执行
- ✅ 客户端只能看到输入和输出,看不到计算过程
- ✅ 即使客户端打开开发者工具,也无法获取算法
```python
# 🔒 服务器端(客户端看不到)
def calculate_expression(request):
# 这里的算法逻辑客户端完全看不到
result = eval(expression) # 可以替换成复杂的 AI 模型
return result
```
---
### 2⃣ 输入验证 ✅
**问题:** 如何防止恶意输入?
**解决:**
- ✅ 正则表达式验证:只允许数字和基本运算符
- ✅ 拒绝危险字符和代码注入
```python
# 安全检查
if not re.match(r'^[\d\s\+\-\*\/\(\)\.]+$', expression):
return "非法输入"
```
**可接受:** `87-98`, `100+200`, `(10*5)/2`
**拒绝:** `__import__('os')`, `exec('code')`, `sys.exit()`
---
### 3⃣ 详细日志 ✅
**功能:** 监控所有请求和响应
**日志内容:**
```
============================================================
📥 收到计算请求
时间: 2024-12-16 15:30:45
表达式: 87-98
API Key: 未提供(无鉴权模式)
============================================================
🛡️ 安全检查: 验证表达式格式...
✅ 表达式格式验证通过
🔒 开始执行核心算法...
✅ 计算完成: 87-98 = -11
============================================================
📤 返回计算结果
成功: True
表达式: 87-98
结果: -11.0
消息: 计算成功: 87-98 = -11
============================================================
```
---
### 4⃣ API Key 鉴权(可选)🔐
**当前状态:** 已准备好,但默认关闭
#### 如何启用 API Key 验证?
**步骤 1在后端取消注释**
打开 `Server/app/api/v1/jsx_demo.py`,取消注释这段代码:
```python
# 取消注释下面这段
if x_api_key not in VALID_API_KEYS:
logger.warning("❌ API Key 验证失败")
raise HTTPException(status_code=403, detail="无效的 API Key")
logger.info("✅ API Key 验证通过")
```
**步骤 2在前端添加 API Key**
打开 `Designer/src/api/jsxApi/inline/hybrid-demo.ts`
```typescript
const response = await fetch(`${config.apiBaseUrl}/jsx_demo/calculate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': 'demo_key_123' // 添加这行
},
body: JSON.stringify({
expression: layerName
})
});
```
---
## 🔐 安全级别对比
### ❌ 纯前端方案(不安全)
```typescript
// 客户端代码(所有人都能看到)
const result = complexAlgorithm(input); // ⚠️ 算法暴露
const jsx = `var layer = doc.artLayers.add(); ...`;
```
**风险:**
- ❌ 核心算法完全暴露
- ❌ 可以轻松复制算法
- ❌ 可以绕过任何验证
---
### ✅ 混合方案(当前实现)
```typescript
// 客户端只发送请求
const result = await fetch('/calculate', { expression: '87-98' });
// 只能看到返回的结果,看不到计算过程
```
**优势:**
- ✅ 核心算法在服务器,客户端看不到
- ✅ 输入验证防止注入攻击
- ✅ 详细日志监控所有请求
---
### 🔐 混合方案 + API Key高级
```typescript
// 客户端需要提供 API Key
const result = await fetch('/calculate', {
headers: { 'X-API-Key': 'your_secret_key' },
body: { expression: '87-98' }
});
```
**优势:**
- ✅ 以上所有优势
- ✅ 只有授权客户端可以调用
- ✅ 可以限制每个 Key 的调用次数
- ✅ 可以追踪谁在使用 API
---
## 💡 进一步加强建议
### 1. 加密传输HTTPS
```bash
# 使用 SSL 证书
uvicorn app.main:app --ssl-keyfile=key.pem --ssl-certfile=cert.pem
```
### 2. 限流Rate Limiting
```python
from slowapi import Limiter
limiter = Limiter(key_func=get_remote_address)
@limiter.limit("10/minute") # 每分钟最多 10 次请求
@router.post("/calculate")
async def calculate_expression(...):
...
```
### 3. IP 白名单
```python
ALLOWED_IPS = {"127.0.0.1", "192.168.1.100"}
@router.post("/calculate")
async def calculate_expression(request: Request):
if request.client.host not in ALLOWED_IPS:
raise HTTPException(403, "IP 未授权")
```
### 4. 结果缓存
```python
from functools import lru_cache
@lru_cache(maxsize=1000)
def calculate(expression: str):
# 相同的表达式不重复计算
return eval(expression)
```
---
## 📊 总结
| 特性 | 状态 | 说明 |
|------|------|------|
| 核心算法保护 | ✅ 已实现 | 算法在服务器端,客户端看不到 |
| 输入验证 | ✅ 已实现 | 正则表达式过滤危险输入 |
| 详细日志 | ✅ 已实现 | 记录所有请求和响应 |
| API Key 鉴权 | 🔧 可选 | 默认关闭,取消注释即可启用 |
| HTTPS 加密 | ⚠️ 推荐 | 生产环境必须启用 |
| 限流保护 | ⚠️ 推荐 | 防止恶意刷接口 |
---
## 🚀 测试安全性
### 测试 1查看网络请求
1. 打开浏览器开发者工具F12
2. 切换到 Network 标签
3. 点击"智能配色"按钮
4. 查看请求详情
**你只能看到:**
- ✅ 请求 URL`/api/v1/jsx_demo/calculate`
- ✅ 请求参数:`{"expression": "87-98"}`
- ✅ 响应结果:`{"success": true, "result": -11}`
**你看不到:**
- ❌ 服务器端的计算逻辑
- ❌ 核心算法代码
- ❌ 其他用户的请求
---
### 测试 2尝试注入攻击
尝试创建一个图层,名称为:`__import__('os').system('rm -rf /')`
**预期结果:**
- ❌ 被拒绝
- 📝 日志显示:`表达式包含非法字符`
- 🛡️ 系统安全
---
### 测试 3查看服务器日志
运行 Demo 后,查看后端控制台输出,应该看到详细的日志:
```bash
cd Server
python -m uvicorn app.main:app --reload
```
**日志示例:**
```
2024-12-16 15:30:45 [INFO] ============================================================
2024-12-16 15:30:45 [INFO] 📥 收到计算请求
2024-12-16 15:30:45 [INFO] 时间: 2024-12-16 15:30:45
2024-12-16 15:30:45 [INFO] 表达式: 87-98
2024-12-16 15:30:45 [INFO] API Key: 未提供(无鉴权模式)
...
```
---
## 🎯 最佳实践建议
### 开发阶段(当前)
- ✅ 无 API Key方便测试
- ✅ 详细日志
- ✅ HTTP 即可
### 生产环境
- 🔐 启用 API Key
- 🔒 启用 HTTPS
- 📊 启用限流
- 📝 日志写入文件
- 🛡️ IP 白名单(可选)
---
**安全性总结:**
当前方案已经实现了**核心算法保护**,客户端无法看到服务器端的计算逻辑。如需进一步加强,可以启用 API Key 验证和其他安全措施。

View File

@@ -0,0 +1,442 @@
# DesignerCEP 混合架构开发框架指南
## 🎯 框架概述
这是一个**三层混合架构框架**,专为 Adobe CEP 插件设计,实现了:
- **核心算法保护**(服务器端计算)
- **动态更新**Shell + Core 分离)
- **安全鉴权**API Key + 详细日志)
---
## 🏗️ 架构层次
```
┌──────────────────────────────────────────────────┐
│ Layer 1: Shell壳/启动器) │
│ 职责: 登录、更新、加载 Core │
│ 更新频率: 极低 │
│ 位置: 本地安装PS 扩展目录) │
└──────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────┐
│ Layer 2: Core业务核
│ 职责: UI 界面、简单 JSX 执行 │
│ 更新频率: 高(随时发布新功能) │
│ 位置: 远程服务器 → 下载到本地缓存 │
└──────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────┐
│ Layer 3: Server后端服务器
│ 职责: 核心算法、数据处理、鉴权 │
│ 更新频率: 随时 │
│ 位置: 远程服务器 │
└──────────────────────────────────────────────────┘
```
---
## 📝 如何添加新功能
### 示例:添加一个"智能滤镜"功能
#### Step 1: 设计功能流程
```
用户点击按钮
→ Core 获取图层信息
→ Core 发送到 Server带 API Key
→ Server 计算最佳滤镜参数(🔒核心算法)
→ Server 返回参数
→ Core 应用滤镜到图层
```
---
#### Step 2: 后端实现Server 层)
**文件:** `Server/app/api/v1/jsx_demo.py`
```python
class FilterRequest(BaseModel):
"""滤镜计算请求"""
layer_id: str
image_data: str # Base64 编码的图层预览
class FilterResult(BaseModel):
"""滤镜计算结果"""
success: bool
blur_radius: float
sharpen_amount: float
saturation: float
message: str
@router.post("/calculate-filter", response_model=FilterResult)
async def calculate_filter(
request: FilterRequest,
x_api_key: Optional[str] = Header(None)
):
"""
🔒 服务器端智能滤镜计算
客户端只能拿到参数,看不到算法
"""
# 日志
logger.info("="*60)
logger.info("📥 收到滤镜计算请求")
logger.info(f" 图层ID: {request.layer_id}")
logger.info(f" API Key: {x_api_key}")
logger.info("="*60)
# API Key 验证
if not validate_api_key(x_api_key):
logger.warning(f"❌ API Key 验证失败")
raise HTTPException(status_code=403, detail="无效的 API Key")
logger.info("✅ API Key 验证通过")
try:
# 🔒 核心算法在这里(客户端看不到)
# 可以是 AI 模型、图像分析等
# 示例:根据图层 ID 计算参数
layer_hash = sum(ord(c) for c in request.layer_id)
blur_radius = 2.0 + (layer_hash % 10) / 10
sharpen_amount = 0.5 + (layer_hash % 5) / 10
saturation = 1.0 + (layer_hash % 3) / 10
logger.info(f"✅ 计算完成: blur={blur_radius}, sharpen={sharpen_amount}")
return FilterResult(
success=True,
blur_radius=blur_radius,
sharpen_amount=sharpen_amount,
saturation=saturation,
message="滤镜参数计算成功"
)
except Exception as e:
logger.error(f"❌ 计算失败: {str(e)}")
return FilterResult(
success=False,
blur_radius=0,
sharpen_amount=0,
saturation=1.0,
message=f"计算失败: {str(e)}"
)
```
---
#### Step 3: 前端实现Core 层)
**文件:** `Designer/src/api/jsxApi/inline/smart-filter.ts`
```typescript
import { evalInlineJSX, JSXResponse } from './utils';
import { config } from '@/config';
/**
* 智能滤镜:混合方案
* 1. 本地获取图层信息
* 2. 服务器计算最佳参数
* 3. 本地应用滤镜
*/
export async function applySmartFilter(): Promise<JSXResponse> {
try {
// 1. 💻 本地获取图层信息
const jsx = `
try {
if (!$.global.JSXUtils.hasDocument()) {
return $.global.JSXUtils.stringify({ error: '没有打开的文档' });
}
var doc = $.global.JSXUtils.getDocument();
var layer = doc.activeLayer;
return $.global.JSXUtils.stringify({
success: true,
layerId: layer.id,
layerName: layer.name
});
} catch (error) {
return $.global.JSXUtils.stringify({ error: error.toString() });
}
`;
const layerResult = await evalInlineJSX(jsx);
if (layerResult.error || !layerResult.success) {
return layerResult;
}
// 2. 🌐 发送到服务器计算(核心算法)
const response = await fetch(`${config.apiBaseUrl}/jsx_demo/calculate-filter`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': 'demo_key_123' // 🔐 API Key
},
body: JSON.stringify({
layer_id: layerResult.layerId,
image_data: '' // 可选:发送图层预览
})
});
if (!response.ok) {
return { error: '服务器错误' };
}
const filterResult = await response.json();
if (!filterResult.success) {
return { error: filterResult.message };
}
// 3. 💻 本地应用滤镜参数
const { blur_radius, sharpen_amount, saturation } = filterResult;
const applyJsx = `
try {
var doc = $.global.JSXUtils.getDocument();
var layer = doc.activeLayer;
// 应用服务器计算的滤镜参数
// 高斯模糊
layer.applyGaussianBlur(${blur_radius});
// 锐化
layer.applySharpen();
return $.global.JSXUtils.stringify({
success: true,
message: '智能滤镜应用成功'
});
} catch (error) {
return $.global.JSXUtils.stringify({ error: error.toString() });
}
`;
return evalInlineJSX(applyJsx);
} catch (error) {
return { error: String(error) };
}
}
```
---
#### Step 4: UI 界面Core 层)
**文件:** `Designer/src/view/Home.vue`
```vue
<template>
<a-button @click="handleSmartFilter">智能滤镜</a-button>
</template>
<script setup lang="ts">
import { applySmartFilter } from '@/api/jsxApi/inline/smart-filter';
import { Message } from '@arco-design/web-vue';
const handleSmartFilter = async () => {
try {
Message.loading('正在计算最佳滤镜参数...');
const res = await applySmartFilter();
if (res && res.success) {
Message.success(res.message);
} else {
Message.error(res?.error || '执行失败');
}
} catch (e: any) {
Message.error('调用失败: ' + e.message);
}
};
</script>
```
---
#### Step 5: 部署新功能
```bash
# 1. 构建 Core
cd Designer
npm run build:core
# 2. 发布新版本(使用自动部署脚本)
cd ..
python auto_deploy_core.py
# 3. 用户登录后会自动下载新版本
```
---
## 🔐 API Key 管理
### 添加新的 API Key
**文件:** `Server/app/core/api_keys.py`
```python
VALID_KEYS: Dict[str, dict] = {
"demo_key_123": {
"name": "测试密钥",
"permissions": ["calculate", "filter"], # 添加权限
"rate_limit": 100
},
"customer_abc_456": { # 新客户
"name": "客户 A",
"permissions": ["calculate"],
"rate_limit": 200
}
}
```
### 权限检查
```python
@router.post("/calculate-filter")
async def calculate_filter(request, x_api_key: Optional[str] = Header(None)):
# 验证 Key
if not validate_api_key(x_api_key):
raise HTTPException(403, "无效的 API Key")
# 检查权限
if not APIKeyManager.check_permission(x_api_key, "filter"):
raise HTTPException(403, "没有滤镜功能权限")
# 继续处理...
```
---
## 📊 日志监控
所有请求都会自动记录:
```
============================================================
📥 收到滤镜计算请求
图层ID: Layer_123
API Key: demo_key_123
============================================================
✅ API Key 验证通过 | 名称: 测试密钥 | 权限: ['calculate', 'filter']
🔒 开始执行核心算法...
✅ 计算完成: blur=2.3, sharpen=0.7
============================================================
```
---
## 🚀 扩展方向
### 1. AI 功能
```python
# Server 端
@router.post("/ai-enhance")
async def ai_enhance(image: str, x_api_key: str):
# 调用 TensorFlow/PyTorch 模型
result = ai_model.predict(image)
return result
```
### 2. 批量处理
```python
@router.post("/batch-process")
async def batch_process(layers: List[str], x_api_key: str):
results = []
for layer_id in layers:
result = process_layer(layer_id)
results.append(result)
return results
```
### 3. 数据分析
```python
@router.post("/analyze-design")
async def analyze_design(doc_info: dict, x_api_key: str):
# 分析设计质量、配色方案等
analysis = analyze_composition(doc_info)
return analysis
```
### 4. 实时协作
```python
@router.websocket("/ws/collaborate")
async def collaborate(websocket: WebSocket, x_api_key: str):
# WebSocket 实时同步
await websocket.accept()
# 多用户协作编辑
```
---
## 🛡️ 安全最佳实践
### ✅ 已实现
- API Key 鉴权
- 输入验证
- 详细日志
- 核心算法保护
### ⚠️ 生产环境建议
- 启用 HTTPS
- 添加限流Rate Limiting
- IP 白名单
- 定期更换 API Key
- 数据库存储 Key而非配置文件
---
## 📚 开发流程总结
```mermaid
graph TD
A[设计新功能] --> B[后端实现核心算法]
B --> C[前端调用 API]
C --> D[UI 界面集成]
D --> E[测试]
E --> F[构建 Core]
F --> G[自动部署]
G --> H[用户自动更新]
```
---
## 🎯 框架优势
| 特性 | 传统方案 | 本框架 |
|------|---------|--------|
| 核心算法保护 | ❌ 暴露在前端 | ✅ 服务器端执行 |
| 动态更新 | ❌ 需重装插件 | ✅ 自动下载更新 |
| 安全鉴权 | ❌ 无验证 | ✅ API Key + 日志 |
| 可扩展性 | ⚠️ 受限 | ✅ 易于添加功能 |
| 开发效率 | ⚠️ 中等 | ✅ 模板化开发 |
---
## 🎉 总结
这个框架提供了:
1. **完整的三层架构**Shell → Core → Server
2. **安全的算法保护**(服务器端计算)
3. **灵活的更新机制**(动态加载 Core
4. **标准的开发模式**(模板化添加功能)
5. **完善的监控日志**(追踪所有请求)
**适用场景:**
- Adobe CEP 插件开发
- 需要保护核心算法的应用
- 需要频繁更新的软件
- 需要鉴权和监控的服务
---
**现在你可以基于这个框架快速开发新功能了!** 🚀

View File

@@ -0,0 +1,355 @@
# 混合架构快速开发模板
## 🚀 5 分钟添加新功能
### 模板代码
复制下面的模板,替换 `YOUR_FEATURE` 为你的功能名称。
---
## 📝 Step 1: 后端 API3 层)
**文件:** `Server/app/api/v1/jsx_demo.py`
```python
# ==================== 请求/响应模型 ====================
class YourFeatureRequest(BaseModel):
"""你的功能请求"""
param1: str
param2: int
class YourFeatureResult(BaseModel):
"""你的功能结果"""
success: bool
result_data: dict
message: str
# ==================== API 端点 ====================
@router.post("/your-feature", response_model=YourFeatureResult)
async def your_feature_endpoint(
request: YourFeatureRequest,
x_api_key: Optional[str] = Header(None)
):
"""
🔒 服务器端核心计算
客户端只能拿到结果,看不到算法
"""
# 📝 日志:记录请求
logger.info("="*60)
logger.info("📥 收到请求: YOUR_FEATURE")
logger.info(f" 参数1: {request.param1}")
logger.info(f" 参数2: {request.param2}")
logger.info(f" API Key: {x_api_key}")
logger.info("="*60)
# 🔐 API Key 验证
if not validate_api_key(x_api_key):
logger.warning(f"❌ API Key 验证失败")
raise HTTPException(status_code=403, detail="无效的 API Key")
key_info = get_key_info(x_api_key)
logger.info(f"✅ API Key 验证通过 | 名称: {key_info['name']}")
try:
# 🛡️ 输入验证
logger.info("🛡️ 验证输入参数...")
if not request.param1 or request.param2 < 0:
logger.warning("❌ 参数验证失败")
return YourFeatureResult(
success=False,
result_data={},
message="参数无效"
)
logger.info("✅ 参数验证通过")
# 🔒 核心算法(客户端看不到)
logger.info("🔒 开始执行核心算法...")
# ===== 在这里写你的核心逻辑 =====
result = {
"output1": f"处理结果: {request.param1}",
"output2": request.param2 * 2
}
# ================================
logger.info(f"✅ 计算完成: {result}")
# 📤 返回结果
logger.info("="*60)
return YourFeatureResult(
success=True,
result_data=result,
message="处理成功"
)
except Exception as e:
logger.error(f"❌ 处理失败: {str(e)}")
return YourFeatureResult(
success=False,
result_data={},
message=f"处理失败: {str(e)}"
)
```
---
## 📝 Step 2: 前端 API2 层)
**文件:** `Designer/src/api/jsxApi/inline/your-feature.ts`
```typescript
/**
* YOUR_FEATURE 功能
* 混合方案:本地执行 + 服务器计算
*/
import { evalInlineJSX, JSXResponse } from './utils';
import { config } from '@/config';
export async function yourFeatureFunction(
param1: string,
param2: number
): Promise<JSXResponse> {
try {
// 1. 💻 【可选】本地获取 PS 数据
const getDataJsx = `
try {
if (!$.global.JSXUtils.hasDocument()) {
return $.global.JSXUtils.stringify({ error: '没有打开的文档' });
}
var doc = $.global.JSXUtils.getDocument();
// 获取你需要的数据
var layerName = doc.activeLayer ? doc.activeLayer.name : '';
return $.global.JSXUtils.stringify({
success: true,
layerName: layerName
});
} catch (error) {
return $.global.JSXUtils.stringify({ error: error.toString() });
}
`;
const psData = await evalInlineJSX(getDataJsx);
if (psData.error) {
return psData;
}
// 2. 🌐 发送到服务器计算(核心算法)
const response = await fetch(`${config.apiBaseUrl}/jsx_demo/your-feature`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': 'demo_key_123' // 🔐 API Key
},
body: JSON.stringify({
param1: param1,
param2: param2
})
});
if (!response.ok) {
return { error: '服务器错误' };
}
const serverResult = await response.json();
if (!serverResult.success) {
return { error: serverResult.message };
}
// 3. 💻 【可选】使用服务器结果执行 PS 操作
const { output1, output2 } = serverResult.result_data;
const applyJsx = `
try {
var doc = $.global.JSXUtils.getDocument();
// 使用服务器计算的结果
// 示例:创建文本图层显示结果
var textLayer = doc.artLayers.add();
textLayer.kind = LayerKind.TEXT;
textLayer.name = "${output1}";
return $.global.JSXUtils.stringify({
success: true,
message: '操作完成'
});
} catch (error) {
return $.global.JSXUtils.stringify({ error: error.toString() });
}
`;
return evalInlineJSX(applyJsx);
} catch (error) {
return { error: String(error) };
}
}
```
---
## 📝 Step 3: UI 界面2 层)
**文件:** `Designer/src/view/Home.vue`
```vue
<template>
<a-button type="primary" @click="handleYourFeature">
你的功能
</a-button>
</template>
<script setup lang="ts">
import { yourFeatureFunction } from '@/api/jsxApi/inline/your-feature';
import { Message } from '@arco-design/web-vue';
const handleYourFeature = async () => {
try {
Message.loading('处理中...');
// 调用混合 API
const res = await yourFeatureFunction('测试参数', 100);
if (res && res.success) {
Message.success(res.message || '操作成功');
} else {
Message.error(res?.error || '执行失败');
}
} catch (e: any) {
Message.error('调用失败: ' + e.message);
}
};
</script>
```
---
## 🔄 开发流程
### 本地测试
```bash
# Terminal 1: 启动后端
cd Server
python -m uvicorn app.main:app --reload
# Terminal 2: 启动前端
cd Designer
npm run dev
```
### 发布新版本
```bash
# 自动构建、打包、发布
python auto_deploy_core.py
```
---
## 📋 检查清单
开发新功能时,确保:
- [ ] 后端添加了 API Key 验证
- [ ] 后端添加了详细日志
- [ ] 后端添加了输入验证
- [ ] 前端使用了正确的 API Key
- [ ] 前端添加了错误处理
- [ ] UI 有加载提示和错误提示
- [ ] 测试了成功和失败的情况
- [ ] 更新了版本号
---
## 🎯 三种常见模式
### 模式 1纯服务器计算
```
前端输入 → 服务器计算 → 前端显示结果
(不涉及 PS 操作)
```
### 模式 2服务器计算 + PS 应用
```
前端获取 PS 数据 → 服务器计算 → 前端应用到 PS
(当前示例)
```
### 模式 3本地执行 + 服务器验证
```
前端执行操作 → 服务器验证权限 → 前端继续
(需要权限控制的操作)
```
---
## 💡 快速参考
### 后端日志模板
```python
logger.info("="*60)
logger.info("📥 收到请求")
logger.info("✅ 验证通过")
logger.info("🔒 开始计算")
logger.info("✅ 计算完成")
logger.info("="*60)
```
### 前端错误处理模板
```typescript
try {
Message.loading('处理中...');
const res = await yourFunction();
if (res?.success) {
Message.success(res.message);
} else {
Message.error(res?.error || '失败');
}
} catch (e: any) {
Message.error('调用失败: ' + e.message);
}
```
### JSX 模板
```typescript
const jsx = `
try {
if (!$.global.JSXUtils.hasDocument()) {
return $.global.JSXUtils.stringify({ error: '没有打开的文档' });
}
var doc = $.global.JSXUtils.getDocument();
// 你的 PS 操作代码
return $.global.JSXUtils.stringify({
success: true,
message: '成功'
});
} catch (error) {
return $.global.JSXUtils.stringify({ error: error.toString() });
}
`;
return evalInlineJSX(jsx);
```
---
## 🎉 完成!
现在你可以:
1. 复制模板代码
2. 替换功能名称
3. 填写核心逻辑
4. 测试
5. 发布
**预计开发时间5-15 分钟/功能**

Some files were not shown because too many files have changed in this diff Show More