This commit is contained in:
zuowei1216
2025-12-22 21:06:29 +08:00
parent 8ea58fe480
commit 1b19ff1b92
179 changed files with 21895 additions and 3774 deletions

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<ExtensionManifest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ExtensionBundleId="com.designer.adminpanel" ExtensionBundleVersion="1.0.0" Version="8.0">
<ExtensionList>
<Extension Id="com.designer.adminpanel" Version="1.0.0"/>
</ExtensionList>
<ExecutionEnvironment>
<HostList>
<!-- Photoshop CC 2017-2024+ -->
<Host Name="PHSP" Version="[18.0,99.9]"/>
<Host Name="PHXS" Version="[18.0,99.9]"/>
</HostList>
<LocaleList>
<Locale Code="All"/>
</LocaleList>
<RequiredRuntimeList>
<RequiredRuntime Name="CSXS" Version="8.0"/>
</RequiredRuntimeList>
</ExecutionEnvironment>
<DispatchInfoList>
<Extension Id="com.designer.adminpanel">
<DispatchInfo>
<Resources>
<MainPath>./index.html</MainPath>
<CEFCommandLine>
<Parameter>--enable-nodejs</Parameter>
<Parameter>--mixed-context</Parameter>
</CEFCommandLine>
</Resources>
<Lifecycle>
<AutoVisible>true</AutoVisible>
</Lifecycle>
<UI>
<Type>Panel</Type>
<Menu>Designer Admin Panel</Menu>
<Geometry>
<Size>
<Height>800</Height>
<Width>1200</Width>
</Size>
<MinSize>
<Height>400</Height>
<Width>600</Width>
</MinSize>
</Geometry>
</UI>
</DispatchInfo>
</Extension>
</DispatchInfoList>
</ExtensionManifest>

View File

@@ -0,0 +1,146 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Designer Admin Panel</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
overflow: hidden;
}
#frame {
width: 100%;
height: 100%;
border: 0;
display: block;
}
/* 加载动画 */
#loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
font-family: Arial, sans-serif;
color: #666;
}
.spinner {
border: 3px solid #f3f3f3;
border-top: 3px solid #3498db;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
#frame.loaded ~ #loading {
display: none;
}
</style>
</head>
<body>
<!-- 加载指示器 -->
<div id="loading">
<div class="spinner"></div>
<p>加载中...</p>
</div>
<!-- 主页面 iframe -->
<iframe
id="frame"
src="https://app.aidg168.uk"
allow="clipboard-read; clipboard-write"
></iframe>
<script>
const frame = document.getElementById('frame');
const loading = document.getElementById('loading');
let loadTimeout;
// iframe 加载完成
frame.addEventListener('load', function() {
clearTimeout(loadTimeout);
this.classList.add('loaded');
console.log('✅ 页面加载成功');
});
// iframe 加载错误
frame.addEventListener('error', function(e) {
clearTimeout(loadTimeout);
loading.innerHTML = '<div style="color: red; padding: 20px; text-align: center;">' +
'<h3>❌ 加载失败</h3>' +
'<p>无法连接到https://app.aidg168.uk</p>' +
'<p>请检查网络连接或更换 URL</p>' +
'<button onclick="location.reload()" style="padding: 10px 20px; cursor: pointer;">重试</button>' +
'</div>';
console.error('❌ iframe 加载错误:', e);
});
// 20秒超时处理
loadTimeout = setTimeout(function() {
if (!frame.classList.contains('loaded')) {
loading.innerHTML = '<div style="color: orange; padding: 20px; text-align: center;">' +
'<h3>⏱️ 加载超时</h3>' +
'<p>网页加载时间过长(超过 20 秒)</p>' +
'<p>当前 URL: https://app.aidg168.uk</p>' +
'<button onclick="location.reload()" style="padding: 10px 20px; cursor: pointer; margin: 5px;">重试</button>' +
'<button onclick="testUrl()" style="padding: 10px 20px; cursor: pointer; margin: 5px;">测试连接</button>' +
'</div>';
console.warn('⏱️ iframe 加载超时');
}
}, 20000);
// 测试 URL 是否可访问
function testUrl() {
loading.innerHTML = '<div style="padding: 20px; text-align: center;">' +
'<div class="spinner"></div>' +
'<p>正在测试连接...</p></div>';
fetch('https://app.aidg168.uk', { mode: 'no-cors' })
.then(() => {
alert('✅ 网站可以访问,正在重新加载...');
location.reload();
})
.catch(err => {
loading.innerHTML = '<div style="color: red; padding: 20px; text-align: center;">' +
'<h3>❌ 网站无法访问</h3>' +
'<p>错误: ' + err.message + '</p>' +
'<button onclick="location.reload()" style="padding: 10px 20px; cursor: pointer;">返回</button>' +
'</div>';
});
}
// 与 iframe 通信(可选)
window.addEventListener("message", function(e) {
if (e.origin === "https://app.aidg168.uk") {
console.log("📨 收到消息:", e.data);
}
});
function sendToIframe(data) {
frame.contentWindow.postMessage(data, "https://app.aidg168.uk");
}
// 调试信息
console.log('🚀 AdminPanel 已启动');
console.log('📍 加载 URL:', frame.src);
</script>
</body>
</html>

View File

@@ -0,0 +1,41 @@
@echo off
chcp 65001 >nul
echo ========================================
echo CEP 扩展调试模式检查工具
echo ========================================
echo.
echo [1] 检查当前 CEP 调试模式状态...
reg query "HKEY_CURRENT_USER\Software\Adobe\CSXS.8" /v PlayerDebugMode 2>nul
if %errorlevel% neq 0 (
echo ❌ 未找到 CSXS.8 调试模式配置
) else (
echo ✅ CSXS.8 配置已存在
)
echo.
echo [2] 启用 CEP 调试模式...
reg add "HKEY_CURRENT_USER\Software\Adobe\CSXS.8" /v PlayerDebugMode /t REG_SZ /d 1 /f >nul
reg add "HKEY_CURRENT_USER\Software\Adobe\CSXS.9" /v PlayerDebugMode /t REG_SZ /d 1 /f >nul
reg add "HKEY_CURRENT_USER\Software\Adobe\CSXS.10" /v PlayerDebugMode /t REG_SZ /d 1 /f >nul
reg add "HKEY_CURRENT_USER\Software\Adobe\CSXS.11" /v PlayerDebugMode /t REG_SZ /d 1 /f >nul
echo ✅ CEP 调试模式已启用CSXS 8/9/10/11
echo.
echo [3] 检查插件安装位置...
set "EXT_DIR=%APPDATA%\Adobe\CEP\extensions\AdminPanel"
if exist "%EXT_DIR%\" (
echo ✅ 插件已安装到: %EXT_DIR%
dir "%EXT_DIR%" /B
) else (
echo ❌ 插件未安装,请运行安装命令
)
echo.
echo ========================================
echo 操作完成!
echo 请 [完全关闭 Photoshop] 后重新打开
echo ========================================
pause

50
AdminPanel/cep.config.ts Normal file
View File

@@ -0,0 +1,50 @@
import { ICepConfig } from "./plugins";
const config: ICepConfig = {
"name": "AdminPanel",
"id": "com.designer.adminpanel",
"version": "1.0.0",
"extensionVersion": "6.1.0",
"requiredRuntimeVersion": "8.0",
"type": "Panel",
"parameters": [
"--enable-nodejs"
],
"panels": [
{
"name": "AdminPanel",
"displayName": "ps套版",
"main": "./index.html",
"width": 280,
"height": 600,
"minWidth": 280,
"minHeight": 600,
"maxWidth": 600,
"maxHeight": 4080
}
],
"hosts": [
{
"name": "PHSP",
"version": "[18.0,99.9]"
},
{
"name": "PHXS",
"version": "[18.0,99.9]"
}
],
"build": {
"jsxBin": false,
"country": "CN",
"province": "GD",
"org": "Designer",
"password": "",
"tsa": ""
},
"zxp": {
"jsxBin": false
}
}
export default config;

View File

@@ -0,0 +1,49 @@
import { ICepConfig } from "./plugins";
const config: ICepConfig = {
"name": "AdminPanel-dev",
"id": "com.designer.adminpanel.dev",
"version": "1.0.0",
"extensionVersion": "6.1.0",
"requiredRuntimeVersion": "8.0",
"type": "Panel",
"parameters": [
"--enable-nodejs"
],
"panels": [
{
"name": "AdminPanel-dev",
"displayName": "管理面板",
"main": "./index.html",
"width": 280,
"height": 600,
"minWidth": 280,
"minHeight": 600,
"maxWidth": 600,
"maxHeight": 4080
}
],
"hosts": [
{
"name": "PHSP",
"version": "[18.0,99.9]"
},
{
"name": "PHXS",
"version": "[18.0,99.9]"
}
],
"build": {
"jsxBin": false,
"country": "CN",
"province": "GD",
"org": "Designer",
"password": "",
"tsa": ""
},
"zxp": {
"jsxBin": false
}
}
export default config;

13
AdminPanel/index.html Normal file
View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Designer Admin Panel</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

34
AdminPanel/package.json Normal file
View File

@@ -0,0 +1,34 @@
{
"name": "AdminPanel",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@vueuse/core": "^10.4.1",
"axios": "^1.6.8",
"types-for-adobe": "^7.0.12",
"vue": "^3.2.47",
"vue-router": "^4.2.4"
},
"devDependencies": {
"@arco-design/web-vue": "^2.57.0",
"@types/node": "^20.3.2",
"@vitejs/plugin-legacy": "^4.0.5",
"@vitejs/plugin-vue": "^4.1.0",
"adm-zip": "^0.5.10",
"chokidar": "^3.5.3",
"less": "^4.1.3",
"open": "^11.0.0",
"sass": "^1.63.6",
"ts-node": "^10.9.2",
"typescript": "^5.1.5",
"vite": "^4.3.9",
"vite-tsconfig-paths": "^4.3.2",
"vue-tsc": "^1.4.2"
}
}

View File

6
AdminPanel/plugins/global.d.ts vendored Normal file
View File

@@ -0,0 +1,6 @@
declare global {
interface Window {
__adobe_cep__: any;
}
}

View File

@@ -0,0 +1,4 @@
export type { ICepConfig } from './jsx/types';
export { initJsx, evalJSX } from './utils/cep';
export { CEPPath } from './utils/cep/cepPath'
export { CSInterface } from "./utils/cep"

View File

@@ -0,0 +1,98 @@
import { copyCepToDev, copyCepToProd } from "./copyCepToDev"
import { createConfig } from "./createConfig"
// 注意:已移除 buildJsx 依赖,现在使用内联 JSX 方式
//@ts-ignore
import packageConfig from "../../package.json"
import defaultConfig from '../../cep.config';
import { ICepConfig } from "./types";
let g_config: any = ''
interface CepPluginOptions {
cepConfig?: ICepConfig;
jsxInput?: string;
jsxOutput?: string;
jsxIncludes?: string[];
}
export default function cepPlugin(options: CepPluginOptions = {}) {
const config = options.cepConfig || defaultConfig;
return {
name: 'transform-file',
async buildStart(viteConfig: any) {
// 已移除 JSX 构建逻辑,现在使用内联 JSX 方式
if (process.env.NODE_ENV === 'production') {
console.log('正式环境 - 使用内联 JSX');
}
},
transform(src: any, id: any) {
},
configResolved(viteConfig: any) {
g_config = viteConfig
if (viteConfig.isProduction)
return console.log('[cepPlugin]打包不处理');
// 已移除 JSX 监听构建,现在使用内联 JSX 方式
console.log('[cepPlugin] 使用内联 JSX无需构建外部文件');
const name = `${getProjectName(viteConfig.root)}-dev`
// 使用配置的端口strictPort: true 确保端口不会变)
const port = viteConfig.server?.port || 5180
const serverURL = `http://localhost:${port}/`
try {
const cepConfig = createConfig(viteConfig.root, {
name: name,
id: `com.${name}`,
version: packageConfig.version,
}, "dev")
copyCepToDev({
serverURL: serverURL,
name: name,
isBuild: false
}, cepConfig)
} catch (error) {
console.log('[CEP] 初始化失败');
console.log(error);
}
},
async closeBundle() {
if (!g_config.isProduction)
return console.log('[cepPlugin]开发环境不处理');
// await buildJsxByProd()
const name = `${getProjectName(g_config.root)}`
try {
// Use the provided config instead of recreating from package.json if possible,
// but createConfig merges simple props.
// Wait, createConfig uses hardcoded template logic.
// Let's passed detailed config if needed.
// For now, we trust config passed in options.
// Note: createConfig logic might need review if we want full Custom Config control.
// But typically it uses the 'config' object we imported/selected at top.
// Actually createConfig generates the manifest content string.
// We should pass our 'config' object to the copy/write logic.
console.log('[id]:' + config.id);
copyCepToProd({
// 方案 BCEP 插件打开后直接跳转到服务器 Shell 登录页
serverURL: 'https://aidg168.uk/shell/#/login',
name: name,
isBuild: true,
distDir: g_config.build.outDir
}, config)
} catch (error) {
console.log('[CEP] 初始化失败');
console.log(error);
}
console.log('[cepPlugins] 编译成功');
}
}
}
function getProjectName(root: string) {
if(root.endsWith("src/launcher")) return "Designer"; // Hack for launcher root? No, Vite root usually project root.
return root.split('/').pop()
}

View File

@@ -0,0 +1,181 @@
import fs from 'fs'
import path from 'path'
import { ICepConfig, IPanel } from './types';
import { joinDebug } from './template/debug';
import { copyFolderSync } from './utils/fs';
import { getAdobeCepDir } from './utils/cepDir';
import { joinManifest } from './template/manifest';
import { joinHtml } from './template/html';
import open from 'open';
/**
* 复制插件壳到ps插件目录
*/
export function copyCepToDev(options: Omit<IOptions, 'outCepPath' | 'inputCepTemp'>, cepConfig: ICepConfig) {
cepConfig.panels[0].displayName = `[dev]${cepConfig.panels[0].displayName}`
const cep = new CEP(options, cepConfig)
cep.write()
console.log(`[CEP] 插件壳已就绪`);
console.log(`[CEP] PS调试地址: http://localhost:7090`);
open('http://localhost:7090')
}
export function copyCepToProd(options: Omit<IOptions, 'outCepPath' | 'inputCepTemp'>, cepConfig: ICepConfig) {
cepConfig.panels[0].displayName = `${cepConfig.panels[0].displayName}`
const cep = new CEP(options, cepConfig)
cep.write()
}
type IOptions = {
name: string
serverURL: string
outCepPath: string
inputCepTemp: string
isBuild: boolean
distDir?: string // Added dynamic dist path
}
export class CEP {
constructor(private readonly options: Omit<IOptions, 'outCepPath' | 'inputCepTemp'>, private readonly cepConfig: ICepConfig) {
this.createDist()
}
private get cepInput() {
return path.join(__dirname, "./template/cep")
}
private get dist() {
if (this.options.distDir) {
if (path.isAbsolute(this.options.distDir)) return this.options.distDir;
return path.resolve(process.cwd(), this.options.distDir);
}
return path.join(__dirname, '../../dist')
}
private get cepOutput() {
return path.join(this.dist, this.options.name)
}
private get cepLink() {
return path.join(getAdobeCepDir(), this.options.name)
}
private get debug() {
return path.join(this.cepOutput, '.debug')
}
private createDist() {
if (!fs.existsSync(this.dist)) {
fs.mkdirSync(this.dist)
}
}
public write() {
this.copyFolder()
if (!this.options.isBuild)
this.writeHtml()
this.writeCSXS()
if (!this.options.isBuild)
this.writeDebug()
this.copyJson2()
// 创建符号链接或直接复制
if (!fs.existsSync(this.cepLink)) {
try {
fs.symlinkSync(this.cepOutput, this.cepLink, 'dir')
console.log('[CEP] 符号链接已创建')
} catch (error: any) {
// 权限不足时,改用复制
if (error.code === 'EPERM') {
console.warn('[CEP] 符号链接权限不足,改用复制方式')
this.copyToCepDir()
} else {
throw error
}
}
} else {
// 目录已存在,需要更新文件
this.copyToCepDir()
}
if (this.options.isBuild) {
this.copyBuildFiles()
}
console.log('[cepPlugin] 安装目录:', getAdobeCepDir());
}
private copyToCepDir() {
// 删除旧目录
if (fs.existsSync(this.cepLink)) {
fs.rmSync(this.cepLink, { recursive: true, force: true })
}
// 复制目录
copyFolderSync(this.cepOutput, this.cepLink)
console.log('[CEP] 已复制到 CEP 目录')
}
private copyFolder() {
copyFolderSync(this.cepInput, this.cepOutput)
}
private writeHtml() {
const outhtml = path.join(this.cepOutput, 'index.html')
let content = joinHtml(this.cepConfig.name, this.options.serverURL || 'http://localhost:5173/')
fs.writeFileSync(outhtml, content)
}
private writeCSXS() {
const info = joinManifest(this.cepConfig)
const output = path.join(this.cepOutput, 'CSXS/manifest.xml')
fs.writeFileSync(output, info)
}
private writeDebug() {
const info = joinDebug(this.cepConfig.id, this.cepConfig.hosts)
fs.writeFileSync(this.debug, info)
}
private copyJson2() {
const input = path.join(__dirname, '../utils/json')
const out = path.join(this.cepOutput, 'js')
copyFolderSync(input, out)
}
private copyBuildFiles() {
// 1. 复制 assets 目录
const assetsInput = path.join(this.dist, 'assets')
const assetsOut = path.join(this.cepOutput, 'assets')
if (fs.existsSync(assetsInput)) {
copyFolderSync(assetsInput, assetsOut)
console.log('[CEP] ✓ 已复制 assets 目录')
}
// 2. 复制 CSInterface.js
const csInterfaceSrc = path.join(this.dist, 'CSInterface.js')
const csInterfaceDst = path.join(this.cepOutput, 'CSInterface.js')
if (fs.existsSync(csInterfaceSrc)) {
fs.copyFileSync(csInterfaceSrc, csInterfaceDst)
console.log('[CEP] ✓ 已复制 CSInterface.js')
}
// 3. 复制并修正 HTML 路径
const builtHtmlPath = path.join(this.dist, 'src/launcher/index.html')
const targetHtmlPath = path.join(this.cepOutput, 'index.html')
if (fs.existsSync(builtHtmlPath)) {
// 读取 HTML 内容并修正路径
let htmlContent = fs.readFileSync(builtHtmlPath, 'utf-8')
// 修正路径:把 ../../ 替换成 ./
htmlContent = htmlContent.replace(/\.\.\/\.\.\//g, './')
// 确保 CSInterface.js 路径正确
htmlContent = htmlContent.replace(/src="\.\/CSInterface\.js"/g, 'src="CSInterface.js"')
fs.writeFileSync(targetHtmlPath, htmlContent)
console.log('[CEP] ✓ 已复制并修正 HTML 路径')
} else {
console.warn('[CEP] ✗ 未找到构建的 HTML:', builtHtmlPath)
}
}
}

View File

@@ -0,0 +1,40 @@
import fs from 'fs';
import path from 'path';
import defConfig from './template/cep.config';
import { ICepConfig } from './types';
const fileName = 'cep.config.ts';
/**
*
* @param root 创建cep.config.ts
*/
export function createConfig(root: string, config: Partial<ICepConfig>, env: 'dev' | 'prod') {
// 指定要检查的文件路径
const filePath = path.resolve(root, env + "." + fileName);
const temp: ICepConfig = Object.assign({}, defConfig, {
name: config.name,
id: config.id,
version: config.version,
});
// 判断文件是否存在
if (!fs.existsSync(filePath)) {
// 如果文件不存在,则创建文件
fs.writeFileSync(filePath, `import { ICepConfig } from "@/plugins";\n\nconst config: ICepConfig = ${JSON.stringify(temp, null, 4)}\n\nexport default config; `);
console.log(`[CEP] 添加配置文件 ${fileName} 成功!`);
} else {
console.log(`[CEP] 已有配置`);
return getFileConfig(filePath);
}
return temp;
}
function getFileConfig(configPath: string) {
let temp = fs.readFileSync(configPath, 'utf-8').split('ICepConfig = ')[1];
const temp2 = temp.split('}')
temp2.pop()
const str = temp2.join('}') + '}';
return JSON.parse(str);
}

View File

@@ -0,0 +1,3 @@
## 目录说明
- cep 插件壳模板文件
- cep.config.json 配置文件

View File

@@ -0,0 +1,65 @@
import { ICepConfig } from "@plugins";
const config: ICepConfig = {
name: "cepName",
id: "com.cepName",
version: "1.0.0",
extensionVersion: "6.1.0",
requiredRuntimeVersion: "9.0",
type: "Panel",
parameters: [
"--enable-nodejs",
],
panels: [
{
name: "cepName",
displayName: "panelName",
main: "./index.html",
width: 400,
height: 300,
minWidth: 400,
minHeight: 300,
maxWidth: 4000,
maxHeight: 3000,
}
],
hosts:[
{
name: "AEFT",
version: "[0.0,99.9]",
},
{
name: "PPRO",
version: "[0.0,99.9]",
},
{
name: "ILST",
version: "[0.0,99.9]",
},
{
name: "PHXS",
version: "[0.0,99.9]",
},
{
name: "FLPR",
version: "[0.0,99.9]",
},
],
build: {
jsxBin: false,
/**国家 */
country: "CN",
/**省份 */
province: "GD",
/**公司名称 */
org: "你的公司名称",
/**签名密码 */
password: "",
tsa: "",
},
zxp: {
jsxBin: false
}
}
export default config;

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<ExtensionManifest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ExtensionBundleId="com.temp.id" ExtensionBundleVersion="1.0" Version="6.0"> <!-- MAJOR-VERSION-UPDATE-MARKER -->
<ExtensionList>
<Extension Id="com.temp.id" Version="6.1.0"/>
</ExtensionList>
<ExecutionEnvironment>
<HostList>
<Host Name="PHXS" Version="[11.0,99.9]"/>
<Host Name="ILST" Version="[11.0,99.9]"/>
</HostList>
<LocaleList>
<Locale Code="All"/>
</LocaleList>
<RequiredRuntimeList>
<RequiredRuntime Name="CSXS" Version="7.0"/> <!-- MAJOR-VERSION-UPDATE-MARKER -->
</RequiredRuntimeList>
</ExecutionEnvironment>
<DispatchInfoList>
</DispatchInfoList>
</ExtensionManifest>

View File

Before

Width:  |  Height:  |  Size: 475 B

After

Width:  |  Height:  |  Size: 475 B

View File

Before

Width:  |  Height:  |  Size: 846 B

After

Width:  |  Height:  |  Size: 846 B

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 475 B

After

Width:  |  Height:  |  Size: 475 B

View File

Before

Width:  |  Height:  |  Size: 846 B

After

Width:  |  Height:  |  Size: 846 B

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{name}}</title>
<script>
window.location.href = "{{serverURL}}"
</script>
</head>
<body>
</body>
</html>

View File

@@ -0,0 +1,15 @@
export function joinDebug(id: string, hosts: { name: string }[]) {
let port = 7090
return `
<?xml version="1.0" encoding="UTF-8"?>
<ExtensionList>
<Extension Id="${id}">
<HostList>
${hosts
.map((host) => `<Host Name="${host.name}" Port="${port++}"/>`)
.join("\n")}
</HostList>
</Extension>
</ExtensionList>
`
}

View File

@@ -0,0 +1,15 @@
export function joinHtml(name:string,server:string){
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>${name}</title>
<script>
// 直接跳转到目标网址(带时间戳防止缓存)
window.location.href = "https://app.aidg168.uk/?_t=" + Date.now();
</script>
</head>
<body>
</body>
</html>`
}

View File

@@ -1,31 +1,33 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> import { ICepConfig } from "../types";
<ExtensionManifest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ExtensionBundleId="com.cep-super-edition.body" ExtensionBundleVersion="1.0" Version="6.0"> <!-- MAJOR-VERSION-UPDATE-MARKER -->
export function joinManifest(config: ICepConfig) {
const { id, version, extensionVersion, requiredRuntimeVersion, hosts, parameters, panels } = config
const mainId=`${id}`
const panel = panels[0]
return `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<ExtensionManifest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ExtensionBundleId="${id}.body" ExtensionBundleVersion="1.0" Version="6.0"> <!-- MAJOR-VERSION-UPDATE-MARKER -->
<ExtensionList> <ExtensionList>
<Extension Id="com.cep-super-edition" Version="6.1.0"/> <Extension Id="${mainId}" Version="${extensionVersion}"/>
</ExtensionList> </ExtensionList>
<ExecutionEnvironment> <ExecutionEnvironment>
<HostList> <HostList>
<Host Name="AEFT" Version="[0.0,99.9]"/> ${hosts.map(item => `<Host Name="${item.name}" Version="${item.version}"/>`).join("\n")}
<Host Name="PPRO" Version="[0.0,99.9]"/>
<Host Name="ILST" Version="[0.0,99.9]"/>
<Host Name="PHXS" Version="[0.0,99.9]"/>
<Host Name="FLPR" Version="[0.0,99.9]"/>
</HostList> </HostList>
<LocaleList> <LocaleList>
<Locale Code="All"/> <Locale Code="All"/>
</LocaleList> </LocaleList>
<RequiredRuntimeList> <RequiredRuntimeList>
<RequiredRuntime Name="CSXS" Version="8.0"/> <!-- MAJOR-VERSION-UPDATE-MARKER --> <RequiredRuntime Name="CSXS" Version="${requiredRuntimeVersion}"/> <!-- MAJOR-VERSION-UPDATE-MARKER -->
</RequiredRuntimeList> </RequiredRuntimeList>
</ExecutionEnvironment> </ExecutionEnvironment>
<DispatchInfoList> <DispatchInfoList>
<Extension Id="com.cep-super-edition"> <Extension Id="${mainId}">
<DispatchInfo> <DispatchInfo>
<Resources> <Resources>
<MainPath>./index.html</MainPath> <MainPath>./index.html</MainPath>
<!-- <ScriptPath>./jsx/core.jsx</ScriptPath> --> <!-- <ScriptPath>./jsx/core.jsx</ScriptPath> -->
<CEFCommandLine> <CEFCommandLine>
<Parameter>--enable-nodejs</Parameter> ${parameters.map(item => `<Parameter>${item}</Parameter>`).join("\n")}
</CEFCommandLine> </CEFCommandLine>
</Resources> </Resources>
<Lifecycle> <Lifecycle>
@@ -33,19 +35,19 @@
</Lifecycle> </Lifecycle>
<UI> <UI>
<Type>Panel</Type> <Type>Panel</Type>
<Menu></Menu> <Menu>${panel.displayName}</Menu>
<Geometry> <Geometry>
<Size> <Size>
<Height>600</Height> <Height>${panel.height}</Height>
<Width>250</Width> <Width>${panel.width}</Width>
</Size> </Size>
<MaxSize> <MaxSize>
<Height>3000</Height> <Height>${panel.maxHeight||panel.height}</Height>
<Width>4000</Width> <Width>${panel.maxWidth||panel.width}</Width>
</MaxSize> </MaxSize>
<MinSize> <MinSize>
<Height>600</Height> <Height>${panel.minHeight||panel.height}</Height>
<Width>250</Width> <Width>${panel.minWidth||panel.width}</Width>
</MinSize> </MinSize>
</Geometry> </Geometry>
<Icons> <Icons>
@@ -60,4 +62,5 @@
</DispatchInfoList> </DispatchInfoList>
</ExtensionManifest> </ExtensionManifest>
`
}

View File

@@ -0,0 +1,42 @@
export type ICepConfig = {
name: string
id: string
version: string
extensionVersion: string
requiredRuntimeVersion: string
type: "Panel"
parameters: IParameter[]
panels: IPanel[],
hosts?: {name:string,version:string}[]
build: {
jsxBin: boolean
/**国家 */
country: string,
/**省份 */
province: string,
/**公司名称 */
org: string
/**签名密码 */
password: string,
tsa: string,
},
zxp: {
jsxBin: boolean
}
}
type IParameter = "--enable-nodejs" | "--enable-media-stream" | "--enable-speech-input"
export type IPanel = {
/**入口index.html */
main:string
name: string
/**插件名称 */
displayName:string
width: number
height: number
minWidth?: number
minHeight?: number
maxWidth?: number
maxHeight?: number
}

View File

@@ -0,0 +1,18 @@
import { isMac } from "./fs"
import os from 'os'
import path from 'path'
function getUserDataPath() {
if (process.platform === 'darwin') {
return path.join(os.homedir(), 'Library', 'Application Support');
} else if (process.platform === 'win32') {
return path.join(process.env.APPDATA);
} else {
return path.join(os.homedir());
}
}
export function getAdobeCepDir() {
const mac = path.join(getUserDataPath(), 'Adobe/CEP/extensions')
const win = path.join(getUserDataPath(), 'Adobe/CEP/extensions')
return isMac() ? mac : win
}

View File

@@ -0,0 +1,25 @@
import fs from 'fs'
import path from 'path'
import os from 'os'
export const copyFolderSync = (from, to) => {
if (!fs.existsSync(to)) {
fs.mkdirSync(to);
}
fs.readdirSync(from).forEach((element) => {
const srcPath = path.join(from, element);
const destPath = path.join(to, element);
if (fs.lstatSync(srcPath).isFile()) {
fs.copyFileSync(srcPath, destPath);
} else {
copyFolderSync(srcPath, destPath);
}
});
};
export function isMac() {
return os.platform() === 'darwin'
}

View File

@@ -0,0 +1 @@
## 提供工具

View File

@@ -0,0 +1,99 @@
export declare interface cep_node {
global: any;
process: any;
buffer: any;
require: any;
}
export declare interface cep {
encoding: {
Base64: "Base64" | string;
UTF8: "UTF-8" | string;
convertion: {
utf8_to_b64: (...params: any) => {};
b64_to_utf8: (...params: any) => {};
binary_to_b64: (...params: any) => {};
b64_to_binary: (...params: any) => {};
ascii_to_b64: (...params: any) => {};
};
};
fs: {
ERR_CANT_READ: number;
ERR_CANT_WRITE: number;
ERR_FILE_EXISTS: number;
ERR_INVALID_PARAMS: number;
ERR_NOT_DIRECTORY: number;
ERR_NOT_FILE: number;
ERR_NOT_FOUND: number;
ERR_OUT_OF_SPACE: number;
ERR_UNKNOWN: number;
ERR_UNSUPPORTED_ENCODING: number;
NO_ERROR: number;
chmod: (...params: any) => {};
deleteFile: (...params: any) => {};
makedir: (...params: any) => {};
readFile: (...params: any) => {};
readdir: (...params: any) => {};
rename: (...params: any) => {};
showOpenDialog: (...params: any) => {};
showOpenDialogEx: (...params: any) => {};
showSaveDialogEx: (...params: any) => {};
stat: (...params: any) => {};
writeFile: (...params: any) => {};
};
process: {
ERR_EXCEED_MAX_NUM_PROCESS: number;
createProcess: (...params: any) => {};
getWorkingDirectory: (...params: any) => {};
isRunning: (...params: any) => {};
onquit: (...params: any) => {};
stderr: (...params: any) => {};
stdin: (...params: any) => {};
stdout: (...params: any) => {};
terminate: (...params: any) => {};
waitfor: (...params: any) => {};
};
util: {
DEPRECATED_API: number;
ERR_INVALID_URL: number;
openURLInDefaultBrowser: (...params: any) => {};
registerExtensionUnloadCallback: (...params: any) => {};
storeProxyCredentials: (...params: any) => {};
};
}
export interface __adobe_cep__ {
addEventListener: (...params: any) => {};
analyticsLogging: (...params: any) => {};
autoThemeColorChange: (...params: any) => {};
closeExtension: (...params: any) => {};
dispatchEvent: (...params: any) => {};
dumpInstallationInfo: (...params: any) => {};
evalScript: (...params: any) => {};
getCurrentApiVersion: (...params: any) => {};
getCurrentImsUserId: (...params: any) => {};
getExtensionId: (...params: any) => {};
getExtensions: (...params: any) => {};
getHostCapabilities: (...params: any) => {};
getHostEnvironment: (...params: any) => {};
getMonitorScaleFactor: (...params: any) => {};
getNetworkPreferences: (...params: any) => {};
getScaleFactor: (...params: any) => {};
getSystemPath: (...params: any) => {};
imsConnect: (...params: any) => {};
imsDisconnect: (...params: any) => {};
imsFetchAccessToken: (...params: any) => {};
imsFetchAccounts: (...params: any) => {};
imsSetProxyCredentials: (...params: any) => {};
initResourceBundle: (...params: any) => {};
invokeAsync: (...params: any) => {};
invokeSync: (...params: any) => {};
nglImsFetchAccessToken: (...params: any) => {};
nglImsFetchProfile: (...params: any) => {};
registerInvalidCertificateCallback: (...params: any) => {};
registerKeyEventsInterest: (...params: any) => {};
removeEventListener: (...params: any) => {};
requestOpenExtension: (...params: any) => {};
resizeContent: (...params: any) => {};
setScaleFactorChangedHandler: (...params: any) => {};
showAAM: (...params: any) => {};
}

View File

@@ -0,0 +1,36 @@
import CSInterface, { SystemPath } from "./csinterface"
export class CEPPath {
constructor() {
}
static getUserData(): string {
const cs = new CSInterface()
return cs.getSystemPath(SystemPath.USER_DATA)
}
static getCommonFiles() {
const cs = new CSInterface()
return cs.getSystemPath(SystemPath.COMMON_FILES)
}
static getMyDocuments(): string {
const cs = new CSInterface()
return cs.getSystemPath(SystemPath.MY_DOCUMENTS)
}
static getApplication(): string {
const cs = new CSInterface()
return cs.getSystemPath(SystemPath.APPLICATION)
}
static getExtension(): string {
const cs = new CSInterface()
return cs.getSystemPath(SystemPath.EXTENSION)
}
static getHostApplication(): string {
const cs = new CSInterface()
return cs.getSystemPath(SystemPath.HOST_APPLICATION)
}
}

View File

@@ -0,0 +1,701 @@
// FOR REFERENCE ONLY -- THIS FILE IS NOT BUNDLED
/**************************************************************************************************
*
* ADOBE SYSTEMS INCORPORATED
* Copyright 2020 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the
* terms of the Adobe license agreement accompanying it. If you have received this file from a
* source other than Adobe, then your use, modification, or distribution of it requires the prior
* written permission of Adobe.
*
**************************************************************************************************/
// This is the JavaScript code for bridging to native functionality
// See CEPEngine_extensions.cpp for implementation of native methods.
//
// Note: So far all native file i/o functions are synchronous, and aynchronous file i/o is TBD.
/** Version v11.0.0 */
/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, forin: true, maxerr: 50, regexp: true */
/*global define, native */
var cep;
if (!cep) {
cep = {};
}
if (!cep.fs) {
cep.fs = {};
}
if (!cep.process) {
cep.process = {};
}
if (!cep.encoding) {
cep.encoding = {};
}
if (!cep.util) {
cep.util = {};
}
(function () {
// Internal function to get the last error code.
native function GetLastError();
function getLastError() {
return GetLastError();
}
function getErrorResult(){
var result = {err: getLastError()};
return result;
}
// Error values. These MUST be in sync with the error values
// at the top of CEPEngine_extensions.cpp
/**
* @constant No error.
*/
cep.fs.NO_ERROR = 0;
/**
* @constant Unknown error occurred.
*/
cep.fs.ERR_UNKNOWN = 1;
/**
* @constant Invalid parameters passed to function.
*/
cep.fs.ERR_INVALID_PARAMS = 2;
/**
* @constant File or directory was not found.
*/
cep.fs.ERR_NOT_FOUND = 3;
/**
* @constant File or directory could not be read.
*/
cep.fs.ERR_CANT_READ = 4;
/**
* @constant An unsupported encoding value was specified.
*/
cep.fs.ERR_UNSUPPORTED_ENCODING = 5;
/**
* @constant File could not be written.
*/
cep.fs.ERR_CANT_WRITE = 6;
/**
* @constant Target directory is out of space. File could not be written.
*/
cep.fs.ERR_OUT_OF_SPACE = 7;
/**
* @constant Specified path does not point to a file.
*/
cep.fs.ERR_NOT_FILE = 8;
/**
* @constant Specified path does not point to a directory.
*/
cep.fs.ERR_NOT_DIRECTORY = 9;
/**
* @constant Specified file already exists.
*/
cep.fs.ERR_FILE_EXISTS = 10;
/**
* @constant The maximum number of processes has been exceeded.
*/
cep.process.ERR_EXCEED_MAX_NUM_PROCESS = 101;
/**
* @constant Invalid URL.
*/
cep.util.ERR_INVALID_URL = 201;
/**
* @constant deprecated API.
*/
cep.util.DEPRECATED_API = 202;
/**
* @constant UTF8 encoding type.
*/
cep.encoding.UTF8 = "UTF-8";
/**
* @constant Base64 encoding type.
*/
cep.encoding.Base64 = "Base64";
/**
* Displays the OS File Open dialog, allowing the user to select files or directories.
*
* @param allowMultipleSelection {boolean} When true, multiple files/folders can be selected.
* @param chooseDirectory {boolean} When true, only folders can be selected. When false, only
* files can be selected.
* @param title {string} Title of the open dialog.
* @param initialPath {string} Initial path to display in the dialog. Pass NULL or "" to
* display the last path chosen.
* @param fileTypes {Array.<string>} The file extensions (without the dot) for the types
* of files that can be selected. Ignored when chooseDirectory=true.
*
* @return An object with these properties:
* <ul><li>"data": An array of the full names of the selected files.</li>
* <li>"err": The status of the operation, one of
* <br>NO_ERROR
* <br>ERR_INVALID_PARAMS </li>
* </ul>
**/
native function ShowOpenDialog();
cep.fs.showOpenDialog = function (allowMultipleSelection, chooseDirectory, title, initialPath, fileTypes) {
var resultString = ShowOpenDialog(allowMultipleSelection, chooseDirectory,
title || 'Open', initialPath || '',
fileTypes ? fileTypes.join(' ') : '');
var result = {data: JSON.parse(resultString || '[]'), err: getLastError() };
return result;
};
/**
* Displays the OS File Open dialog, allowing the user to select files or directories.
*
* @param allowMultipleSelection {boolean} When true, multiple files/folders can be selected.
* @param chooseDirectory {boolean} When true, only folders can be selected. When false, only
* files can be selected.
* @param title {string} Title of the open dialog.
* @param initialPath {string} Initial path to display in the dialog. Pass NULL or "" to
* display the last path chosen.
* @param fileTypes {Array.<string>} The file extensions (without the dot) for the types
* of files that can be selected. Ignored when chooseDirectory=true.
* @param friendlyFilePrefix {string} String to put in front of the extensions
* of files that can be selected. Ignored when chooseDirectory=true. (win only)
* For example:
* fileTypes = ["gif", "jpg", "jpeg", "png", "bmp", "webp", "svg"];
* friendlyFilePrefix = "Images (*.gif;*.jpg;*.jpeg;*.png;*.bmp;*.webp;*.svg)";
* @param prompt {string} String for OK button (mac only, default is "Open" on mac, "Open" or "Select Folder" on win).
*
* @return An object with these properties:
* <ul><li>"data": An array of the full names of the selected files.</li>
* <li>"err": The status of the operation, one of
* <br>NO_ERROR
* <br>ERR_INVALID_PARAMS </li>
* </ul>
**/
native function ShowOpenDialogEx();
cep.fs.showOpenDialogEx = function (allowMultipleSelection, chooseDirectory, title, initialPath, fileTypes,
friendlyFilePrefix, prompt) {
var resultString = ShowOpenDialogEx(allowMultipleSelection, chooseDirectory,
title || 'Open', initialPath || '',
fileTypes ? fileTypes.join(' ') : '', friendlyFilePrefix || '',
prompt || '');
var result = {data: JSON.parse(resultString || '[]'), err: getLastError() };
return result;
};
/**
* Displays the OS File Save dialog, allowing the user to type in a file name.
*
* @param title {string} Title of the save dialog.
* @param initialPath {string} Initial path to display in the dialog. Pass NULL or "" to
* display the last path chosen.
* @param fileTypes {Array.<string>} The file extensions (without the dot) for the types
* of files that can be selected.
* @param defaultName {string} String to start with for the file name.
* @param friendlyFilePrefix {string} String to put in front of the extensions of files that can be selected. (win only)
* For example:
* fileTypes = ["gif", "jpg", "jpeg", "png", "bmp", "webp", "svg"];
* friendlyFilePrefix = "Images (*.gif;*.jpg;*.jpeg;*.png;*.bmp;*.webp;*.svg)";
* @param prompt {string} String for Save button (mac only, default is "Save" on mac and win).
* @param nameFieldLabel {string} String displayed in front of the file name text field (mac only, "File name:" on win).
*
* @return An object with these properties:
* <ul><li>"data": The file path selected to save at or "" if canceled</li>
* <li>"err": The status of the operation, one of
* <br>NO_ERROR
* <br>ERR_INVALID_PARAMS </li>
* </ul>
**/
native function ShowSaveDialogEx();
cep.fs.showSaveDialogEx = function (title, initialPath, fileTypes, defaultName, friendlyFilePrefix, prompt, nameFieldLabel) {
var resultString = ShowSaveDialogEx(title || '', initialPath || '',
fileTypes ? fileTypes.join(' ') : '', defaultName || '',
friendlyFilePrefix || '', prompt || '', nameFieldLabel || '');
var result = {data: resultString || '', err: getLastError() };
return result;
};
/**
* Reads the contents of a folder.
*
* @param path {string} The path of the folder to read.
*
* @return An object with these properties:
* <ul><li>"data": An array of the names of the contained files (excluding '.' and '..'.</li>
* <li>"err": The status of the operation, one of:
* <br>NO_ERROR
* <br>ERR_UNKNOWN
* <br>ERR_INVALID_PARAMS
* <br>ERR_NOT_FOUND
* <br>ERR_CANT_READ </li></ul>
**/
native function ReadDir();
cep.fs.readdir = function (path) {
var resultString = ReadDir(path);
var result = {data: JSON.parse(resultString || '[]'), err: getLastError() };
return result;
};
/**
* Creates a new folder.
*
* @param path {string} The path of the folder to create.
*
* @return An object with this property:
* <ul><li>"err": The status of the operation, one of:
* <br>NO_ERROR
* <br>ERR_UNKNOWN
* <br>ERR_INVALID_PARAMS</li></ul>
**/
native function MakeDir();
cep.fs.makedir = function (path) {
MakeDir(path);
return getErrorResult();
};
/**
* Renames a file or folder.
*
* @param oldPath {string} The old name of the file or folder.
* @param newPath {string} The new name of the file or folder.
*
* @return An object with this property:
* <ul><li>"err": The status of the operation, one of:
* <br>NO_ERROR
* <br>ERR_UNKNOWN
* <br>ERR_INVALID_PARAMS
* <br>ERR_NOT_FOUND
* <br>ERR_FILE_EXISTS </li></ul>
**/
native function Rename();
cep.fs.rename = function(oldPath, newPath) {
Rename(oldPath, newPath);
return getErrorResult();
};
/**
* Reports whether an item is a file or folder.
*
* @param path {string} The path of the file or folder.
*
* @return An object with these properties:
* <ul><li>"data": An object with properties
* <br>isFile (boolean)
* <br>isDirectory (boolean)
* <br>mtime (modification DateTime) </li>
* <li>"err": The status of the operation, one of
* <br>NO_ERROR
* <br>ERR_UNKNOWN
* <br>ERR_INVALID_PARAMS
* <br>ERR_NOT_FOUND </li>
* </ul>
**/
native function IsDirectory();
native function GetFileModificationTime();
cep.fs.stat = function (path) {
var isDir = IsDirectory(path);
var modtime = GetFileModificationTime(path);
var result = {
data: {
isFile: function () {
return !isDir;
},
isDirectory: function () {
return isDir;
},
mtime: modtime
},
err: getLastError()
};
return result;
};
/**
* Reads the entire contents of a file.
*
* @param path {string} The path of the file to read.
* @param encoding {string} The encoding of the contents of file, one of
* UTF8 (the default) or Base64.
*
* @return An object with these properties:
* <ul><li>"data": The file contents. </li>
* <li>"err": The status of the operation, one of
* <br>NO_ERROR
* <br>ERR_UNKNOWN
* <br>ERR_INVALID_PARAMS
* <br>ERR_NOT_FOUND
* <br>ERR_CANT_READ
* <br>ERR_UNSUPPORTED_ENCODING </li>
* </ul>
**/
native function ReadFile();
cep.fs.readFile = function (path, encoding) {
encoding = encoding ? encoding : cep.encoding.UTF8;
var contents = ReadFile(path, encoding);
var result = {data: contents, err: getLastError() };
return result;
};
/**
* Writes data to a file, replacing the file if it already exists.
*
* @param path {string} The path of the file to write.
* @param data {string} The data to write to the file.
* @param encoding {string} The encoding of the contents of file, one of
* UTF8 (the default) or Base64.
*
* @return An object with this property:
* <ul><li>"err": The status of the operation, one of:
* <br>NO_ERROR
* <br>ERR_UNKNOWN
* <br>ERR_INVALID_PARAMS
* <br>ERR_UNSUPPORTED_ENCODING
* <br>ERR_CANT_WRITE
* <br>ERR_OUT_OF_SPACE </li></ul>
**/
native function WriteFile();
cep.fs.writeFile = function (path, data, encoding) {
encoding = encoding ? encoding : cep.encoding.UTF8;
WriteFile(path, data, encoding);
return getErrorResult();
};
/**
* Sets permissions for a file or folder.
*
* @param path {string} The path of the file or folder.
* @param mode {number} The permissions in numeric format (for example, 0777).
*
* @return An object with this property:
* <ul><li>"err": The status of the operation, one of:
* <br>NO_ERROR
* <br>ERR_UNKNOWN
* <br>ERR_INVALID_PARAMS
* <br>ERR_CANT_WRITE </li></ul>
**/
native function SetPosixPermissions();
cep.fs.chmod = function (path, mode) {
SetPosixPermissions(path, mode);
return getErrorResult();
};
/**
* Deletes a file.
*
* @param path {string} The path of the file to delete.
*
* @return An object with this property:
* <ul><li>"err": The status of the operation, one of:
* <br>NO_ERROR
* <br>ERR_UNKNOWN
* <br>ERR_INVALID_PARAMS
* <br>ERR_NOT_FOUND
* <br>ERR_NOT_FILE </li></ul>
**/
native function DeleteFileOrDirectory();
native function IsDirectory();
cep.fs.deleteFile = function (path) {
if (IsDirectory(path)) {
var result = {err: cep.fs.ERR_NOT_FILE};
return result;
}
DeleteFileOrDirectory(path);
return getErrorResult();
};
/**
* Creates a process.
*
* @param arguments {list} The arguments to create process. The first one is the full path of the executable,
* followed by the arguments of the executable.
*
* @return An object with these properties:
* <ul><li>"data": The pid of the process, or -1 on error. </li>
* <li>"err": The status of the operation, one of
* <br>NO_ERROR
* <br>ERR_UNKNOWN
* <br>ERR_INVALID_PARAMS
* <br>ERR_EXCEED_MAX_NUM_PROCESS
* <br>ERR_NOT_FOUND
* <br>ERR_NOT_FILE</li>
* </ul>
**/
native function CreateProcess();
cep.process.createProcess = function () {
var args = Array.prototype.slice.call(arguments);
var pid = CreateProcess(args);
var result = {data: pid, err: getLastError()};
return result;
};
/**
* Registers a standard-output handler for a process.
*
* @param pid {int} The pid of the process.
* @param callback {function} The handler function for the standard output callback.
*
* @return An object with this property:
* <ul><li>"err": The status of the operation, one of:
* <br>NO_ERROR
* <br>ERR_UNKNOWN
* <br>ERR_INVALID_PARAMS
* <br>ERR_INVALID_PROCESS_ID </li></ul>
**/
native function SetupStdOutHandler();
cep.process.stdout = function (pid, callback) {
SetupStdOutHandler(pid, callback);
return getErrorResult();
};
/**
* Registers up a standard-error handler for a process.
*
* @param pid {int} The pid of the process.
* @param callback {function} The handler function for the standard error callback.
*
* @return An object with this property:
* <ul><li>"err": The status of the operation, one of:
* <br>NO_ERROR
* <br>ERR_UNKNOWN
* <br>ERR_INVALID_PARAMS
* <br>ERR_INVALID_PROCESS_ID </li></ul>
**/
native function SetupStdErrHandler();
cep.process.stderr = function (pid, callback) {
SetupStdErrHandler(pid, callback);
return getErrorResult();
};
/**
* Writes data to the standard input of a process.
*
* @param pid {int} The pid of the process
* @param data {string} The data to write.
*
* @return An object with this property:
* <ul><li>"err": The status of the operation, one of:
* <br>NO_ERROR
* <br>ERR_UNKNOWN
* <br>ERR_INVALID_PARAMS
* <br>ERR_INVALID_PROCESS_ID </li></ul>
**/
native function WriteStdIn();
cep.process.stdin = function (pid, data) {
WriteStdIn(pid, data);
return getErrorResult();
};
/**
* Retrieves the working directory of a process.
*
* @param pid {int} The pid of the process.
*
* @return An object with these properties:
* <ul><li>"data": The path of the working directory. </li>
* <li>"err": The status of the operation, one of
* <br>NO_ERROR
* <br>ERR_UNKNOWN
* <br>ERR_INVALID_PARAMS
* <br>ERR_INVALID_PROCESS_ID </li></ul>
**/
native function GetWorkingDirectory();
cep.process.getWorkingDirectory = function (pid) {
var wd = GetWorkingDirectory(pid);
var result = {data: wd, err: getLastError()};
return result;
};
/**
* Waits for a process to quit.
*
* @param pid {int} The pid of the process.
*
* @return An object with this property:
* <ul><li>"err": The status of the operation, one of:
* <br>NO_ERROR
* <br>ERR_UNKNOWN
* <br>ERR_INVALID_PARAMS
* <br>ERR_INVALID_PROCESS_ID </li></ul>
**/
native function WaitFor();
cep.process.waitfor = function (pid) {
WaitFor(pid);
return getErrorResult();
};
/**
* Registers a handler for the onquit callback of a process.
*
* @param pid {int} The pid of the process.
* @param callback {function} The handler function.
*
* @return An object with this property:
* <ul><li>"err": The status of the operation, one of:
* <br>NO_ERROR
* <br>ERR_UNKNOWN
* <br>ERR_INVALID_PARAMS
* <br>ERR_INVALID_PROCESS_ID </li></ul>
**/
native function OnQuit();
cep.process.onquit = function (pid, callback) {
OnQuit(pid, callback);
return getErrorResult();
};
/**
* Reports whether a process is currently running.
*
* @param pid {int} The pid of the process.
*
* @return An object with these properties:
* <ul><li>"data": True if the process is running, false otherwise. </li>
* <li>"err": The status of the operation, one of
* <br>NO_ERROR
* <br>ERR_UNKNOWN
* <br>ERR_INVALID_PARAMS
* <br>ERR_INVALID_PROCESS_ID </li></ul>
**/
native function IsRunning();
cep.process.isRunning = function (pid) {
var isRunning = IsRunning(pid);
var result = {data: isRunning, err: getLastError()};
return result;
};
/**
* Terminates a process.
*
* @param pid {int} The pid of the process
*
* @return An object with this property:
* <ul><li>"err": The status of the operation, one of:
* <br>NO_ERROR
* <br>ERR_UNKNOWN
* <br>ERR_INVALID_PARAMS
* <br>ERR_INVALID_PROCESS_ID </li></ul>
**/
native function Terminate();
cep.process.terminate = function (pid) {
Terminate(pid);
return getErrorResult();
};
/**
* Encoding conversions.
*
*/
cep.encoding.convertion =
{
utf8_to_b64: function(str) {
return window.btoa(unescape(encodeURIComponent(str)));
},
b64_to_utf8: function(base64str) {
// If a base64 string contains any whitespace character, DOM Exception 5 occurs during window.atob, please see
// http://stackoverflow.com/questions/14695988/dom-exception-5-invalid-character-error-on-valid-base64-image-string-in-javascri
base64str = base64str.replace(/\s/g, '');
return decodeURIComponent(escape(window.atob(base64str)));
},
binary_to_b64: function(binary) {
return window.btoa(binary);
},
b64_to_binary: function(base64str) {
return window.atob(base64str);
},
ascii_to_b64: function(ascii) {
return window.btoa(binary);
},
b64_to_ascii: function(base64str) {
return window.atob(base64str);
}
};
/**
* Opens a page in the default system browser.
*
* @param url {string} The URL of the page/file to open, or the email address.
* Must use HTTP/HTTPS/file/mailto. For example:
* "http://www.adobe.com"
* "https://github.com"
* "file:///C:/log.txt"
* "mailto:test@adobe.com"
*
* @return An object with this property:
* <ul><li>"err": The status of the operation, one of:
* <br>NO_ERROR
* <br>ERR_UNKNOWN
* <br>ERR_INVALID_PARAMS</li></ul>
**/
native function OpenURLInDefaultBrowser();
cep.util.openURLInDefaultBrowser = function (url) {
if (url && (url.indexOf("http://") === 0 ||
url.indexOf("https://") === 0 ||
url.indexOf("file://") === 0 ||
url.indexOf("mailto:") === 0)) {
OpenURLInDefaultBrowser(url);
return getErrorResult();
} else {
return { err : cep.util.ERR_INVALID_URL };
}
};
/**
* Registers a callback function for extension unload. If called more than once,
* the last callback that is successfully registered is used.
*
* @deprecated since version 6.0.0
*
* @param callback {function} The handler function.
*
* @return An object with this property:
* <ul><li>"err": The status of the operation, one of:
* <br>NO_ERROR
* <br>ERR_INVALID_PARAMS</li></ul>
**/
native function RegisterExtensionUnloadCallback();
cep.util.registerExtensionUnloadCallback = function (callback) {
return { err : cep.util.DEPRECATED_API };
};
/**
* Stores the user's proxy credentials
*
* @param username {string} proxy username
* @param password {string} proxy password
*
* @return An object with this property:
* <ul><li>"err": The status of the operation, one of
* <br>NO_ERROR
* <br>ERR_INVALID_PARAMS </li>
* </ul>
**/
native function StoreProxyCredentials();
cep.util.storeProxyCredentials = function (username, password) {
StoreProxyCredentials(username, password);
return getErrorResult();
};
})();

View File

@@ -0,0 +1,788 @@
/**
* Stores constants for the window types supported by the CSXS infrastructure.
*/
declare function CSXSWindowType(): void;
/**
* EvalScript error message
*/
declare var EvalScript_ErrMessage: any;
/**
* Version
Defines a version number with major, minor, micro, and special
components. The major, minor and micro values are numeric; the special
value can be any string.
* @param major - The major version component, a positive integer up to nine digits long.
* @param minor - The minor version component, a positive integer up to nine digits long.
* @param micro - The micro version component, a positive integer up to nine digits long.
* @param special - The special version component, an arbitrary string.
*/
declare class Version {
constructor(major: any, minor: any, micro: any, special: any);
/**
* The maximum value allowed for a numeric version component.
This reflects the maximum value allowed in PlugPlug and the manifest schema.
*/
static MAX_NUM: any;
}
/**
* VersionBound
Defines a boundary for a version range, which associates a \c Version object
with a flag for whether it is an inclusive or exclusive boundary.
* @param version - The \c #Version object.
* @param inclusive - True if this boundary is inclusive, false if it is exclusive.
*/
declare class VersionBound {
constructor(version: any, inclusive: any);
}
/**
* VersionRange
Defines a range of versions using a lower boundary and optional upper boundary.
* @param lowerBound - The \c #VersionBound object.
* @param upperBound - The \c #VersionBound object, or null for a range with no upper boundary.
*/
declare class VersionRange {
constructor(lowerBound: any, upperBound: any);
}
/**
* Runtime
Represents a runtime related to the CEP infrastructure.
Extensions can declare dependencies on particular
CEP runtime versions in the extension manifest.
* @param name - The runtime name.
* @param version - A \c #VersionRange object that defines a range of valid versions.
*/
declare class Runtime {
constructor(name: any, version: any);
}
/**
* Extension
Encapsulates a CEP-based extension to an Adobe application.
* @param id - The unique identifier of this extension.
* @param name - The localizable display name of this extension.
* @param mainPath - The path of the "index.html" file.
* @param basePath - The base path of this extension.
* @param windowType - The window type of the main window of this extension.
* Valid values are defined by \c #CSXSWindowType.
* @param width - The default width in pixels of the main window of this extension.
* @param height - The default height in pixels of the main window of this extension.
* @param minWidth - The minimum width in pixels of the main window of this extension.
* @param minHeight - The minimum height in pixels of the main window of this extension.
* @param maxWidth - The maximum width in pixels of the main window of this extension.
* @param maxHeight - The maximum height in pixels of the main window of this extension.
* @param defaultExtensionDataXml - The extension data contained in the default \c ExtensionDispatchInfo section of the extension manifest.
* @param specialExtensionDataXml - The extension data contained in the application-specific \c ExtensionDispatchInfo section of the extension manifest.
* @param requiredRuntimeList - An array of \c Runtime objects for runtimes required by this extension.
* @param isAutoVisible - True if this extension is visible on loading.
* @param isPluginExtension - True if this extension has been deployed in the Plugins folder of the host application.
*/
declare class Extension {
constructor(
id: any,
name: any,
mainPath: any,
basePath: any,
windowType: any,
width: any,
height: any,
minWidth: any,
minHeight: any,
maxWidth: any,
maxHeight: any,
defaultExtensionDataXml: any,
specialExtensionDataXml: any,
requiredRuntimeList: any,
isAutoVisible: any,
isPluginExtension: any
);
}
/**
* CSEvent
A standard JavaScript event, the base class for CEP events.
* @param type - The name of the event type.
* @param scope - The scope of event, can be "GLOBAL" or "APPLICATION".
* @param appId - The unique identifier of the application that generated the event.
* @param extensionId - The unique identifier of the extension that generated the event.
*/
declare class CSEvent {
public type: any
public scope: any
public appId: any
public extensionId: any
constructor(type?: any, scope?: any, appId?: any, extensionId?: any);
/**
* Event-specific data.
*/
data: any;
}
/**
* SystemPath
Stores operating-system-specific location constants for use in the
\c #CSInterface.getSystemPath() method.
*/
declare class SystemPath {
constructor();
/**
* The path to user data.
*/
static USER_DATA: any;
/**
* The path to common files for Adobe applications.
*/
static COMMON_FILES: any;
/**
* The path to the user's default document folder.
*/
static MY_DOCUMENTS: any;
static APPLICATION: any;
/**
* The path to current extension.
*/
static EXTENSION: any;
/**
* The path to hosting application's executable.
*/
static HOST_APPLICATION: any;
}
/**
* ColorType
Stores color-type constants.
*/
declare class ColorType {
constructor();
/**
* RGB color type.
*/
static RGB: any;
/**
* Gradient color type.
*/
static GRADIENT: any;
/**
* Null color type.
*/
static NONE: any;
}
/**
* RGBColor
Stores an RGB color with red, green, blue, and alpha values.
All values are in the range [0.0 to 255.0]. Invalid numeric values are
converted to numbers within this range.
* @param red - The red value, in the range [0.0 to 255.0].
* @param green - The green value, in the range [0.0 to 255.0].
* @param blue - The blue value, in the range [0.0 to 255.0].
* @param alpha - The alpha (transparency) value, in the range [0.0 to 255.0].
The default, 255.0, means that the color is fully opaque.
*/
declare class RGBColor {
constructor(red: any, green: any, blue: any, alpha: any);
}
/**
* Direction
A point value in which the y component is 0 and the x component
is positive or negative for a right or left direction,
or the x component is 0 and the y component is positive or negative for
an up or down direction.
* @param x - The horizontal component of the point.
* @param y - The vertical component of the point.
*/
declare class Direction {
constructor(x: any, y: any);
}
/**
* GradientStop
Stores gradient stop information.
* @param offset - The offset of the gradient stop, in the range [0.0 to 1.0].
* @param rgbColor - The color of the gradient at this point, an \c #RGBColor object.
*/
declare class GradientStop {
constructor(offset: any, rgbColor: any);
}
/**
* GradientColor
Stores gradient color information.
* @param type - The gradient type, must be "linear".
* @param direction - A \c #Direction object for the direction of the gradient
* (up, down, right, or left).
* @param numStops - The number of stops in the gradient.
* @param gradientStopList - An array of \c #GradientStop objects.
*/
declare class GradientColor {
constructor(type: any, direction: any, numStops: any, gradientStopList: any);
}
/**
* UIColor
Stores color information, including the type, anti-alias level, and specific color
values in a color object of an appropriate type.
* @param type - The color type, 1 for "rgb" and 2 for "gradient".
* The supplied color object must correspond to this type.
* @param antialiasLevel - The anti-alias level constant.
* @param color - A \c #RGBColor or \c #GradientColor object containing specific color information.
*/
declare class UIColor {
constructor(type: any, antialiasLevel: any, color: any);
}
/**
* AppSkinInfo
Stores window-skin properties, such as color and font. All color parameter values are \c #UIColor objects except that systemHighlightColor is \c #RGBColor object.
* @param baseFontFamily - The base font family of the application.
* @param baseFontSize - The base font size of the application.
* @param appBarBackgroundColor - The application bar background color.
* @param panelBackgroundColor - The background color of the extension panel.
* @param appBarBackgroundColorSRGB - The application bar background color, as sRGB.
* @param panelBackgroundColorSRGB - The background color of the extension panel, as sRGB.
* @param systemHighlightColor - The highlight color of the extension panel, if provided by the host application. Otherwise, the operating-system highlight color.
*/
declare class AppSkinInfo {
constructor(
baseFontFamily: any,
baseFontSize: any,
appBarBackgroundColor: any,
panelBackgroundColor: any,
appBarBackgroundColorSRGB: any,
panelBackgroundColorSRGB: any,
systemHighlightColor: any
);
}
/**
* HostEnvironment
Stores information about the environment in which the extension is loaded.
* @param appName - The application's name.
* @param appVersion - The application's version.
* @param appLocale - The application's current license locale.
* @param appUILocale - The application's current UI locale.
* @param appId - The application's unique identifier.
* @param isAppOnline - True if the application is currently online.
* @param appSkinInfo - An \c #AppSkinInfo object containing the application's default color and font styles.
*/
declare class HostEnvironment {
constructor(
appName: any,
appVersion: any,
appLocale: any,
appUILocale: any,
appId: any,
isAppOnline: any,
appSkinInfo: any
);
}
/**
* HostCapabilities
Stores information about the host capabilities.
* @param EXTENDED_PANEL_MENU - True if the application supports panel menu.
* @param EXTENDED_PANEL_ICONS - True if the application supports panel icon.
* @param DELEGATE_APE_ENGINE - True if the application supports delegated APE engine.
* @param SUPPORT_HTML_EXTENSIONS - True if the application supports HTML extensions.
* @param DISABLE_FLASH_EXTENSIONS - True if the application disables FLASH extensions.
*/
declare class HostCapabilities {
constructor(
EXTENDED_PANEL_MENU: any,
EXTENDED_PANEL_ICONS: any,
DELEGATE_APE_ENGINE: any,
SUPPORT_HTML_EXTENSIONS: any,
DISABLE_FLASH_EXTENSIONS: any
);
}
/**
* ApiVersion
Stores current api version.
Since 4.2.0
* @param major - The major version
* @param minor - The minor version.
* @param micro - The micro version.
*/
declare class ApiVersion {
constructor(major: any, minor: any, micro: any);
}
/**
* MenuItemStatus
Stores flyout menu item status
Since 5.2.0
* @param menuItemLabel - The menu item label.
* @param enabled - True if user wants to enable the menu item.
* @param checked - True if user wants to check the menu item.
*/
declare class MenuItemStatus {
constructor(menuItemLabel: any, enabled: any, checked: any);
}
/**
* ContextMenuItemStatus
Stores the status of the context menu item.
Since 5.2.0
* @param menuItemID - The menu item id.
* @param enabled - True if user wants to enable the menu item.
* @param checked - True if user wants to check the menu item.
*/
declare class ContextMenuItemStatus {
constructor(menuItemID: any, enabled: any, checked: any);
}
/**
* CSInterface
This is the entry point to the CEP extensibility infrastructure.
Instantiate this object and use it to:
<ul>
<li>Access information about the host application in which an extension is running</li>
<li>Launch an extension</li>
<li>Register interest in event notifications, and dispatch events</li>
</ul>
*/
type RBGAColor = {
alpha: number;
green: number;
blue: number;
red: number;
};
export default class CSInterface {
constructor();
/**
* User can add this event listener to handle native application theme color changes.
Callback function gives extensions ability to fine-tune their theme color after the
global theme color has been changed.
The callback function should be like below:
* @example
* // event is a CSEvent object, but user can ignore it.
function OnAppThemeColorChanged(event)
{
// Should get a latest HostEnvironment object from application.
var skinInfo = JSON.parse(window.__adobe_cep__.getHostEnvironment()).appSkinInfo;
// Gets the style information such as color info from the skinInfo,
// and redraw all UI controls of your extension according to the style info.
}
*/
static THEME_COLOR_CHANGED_EVENT: any;
/**
* The host environment data object.
*/
hostEnvironment: {
appName: string;
appVersion: string;
appLocale: string;
appUILocale: string;
appId: string;
isAppOnline: boolean;
appSkinInfo: {
baseFontFamily: string;
baseFontSize: number;
appBarBackgroundColor: {
antialiasLevel: number;
type: number;
color: RBGAColor;
};
panelBackgroundColor: {
antialiasLevel: number;
type: number;
color: RBGAColor;
};
appBarBackgroundColorSRGB: {
antialiasLevel: number;
type: number;
color: RBGAColor;
};
panelBackgroundColorSRGB: {
antialiasLevel: number;
type: number;
color: RBGAColor;
};
systemHighlightColor: RBGAColor;
};
};
/**
* Retrieves information about the host environment in which the
extension is currently running.
* @returns A \c #HostEnvironment object.
*/
getHostEnvironment(): any;
/**
* Loads binary file created which is located at url asynchronously
* @example
* To create JS binary use command ./cep_compiler test.js test.bin
To load JS binary asyncronously
var CSLib = new CSInterface();
CSLib.loadBinAsync(url, function () { });
* @param urlName - url at which binary file is located. Local files should start with 'file://'
* @param callback - Optional. A callback function that returns after binary is loaded
*/
loadBinAsync(urlName: any, callback: any): void;
/**
* Loads binary file created synchronously
* @example
* To create JS binary use command ./cep_compiler test.js test.bin
To load JS binary syncronously
var CSLib = new CSInterface();
CSLib.loadBinSync(path);
* @param pathName - the local path at which binary file is located
*/
loadBinSync(pathName: any): void;
/**
* Closes this extension.
*/
closeExtension(): void;
/**
* Retrieves a path for which a constant is defined in the system.
* @param pathType - The path-type constant defined in \c #SystemPath ,
* @returns The platform-specific system path string.
*/
getSystemPath(pathType: any): any;
/**
* Evaluates a JavaScript script, which can use the JavaScript DOM
of the host application.
* @param script - The JavaScript script.
* @param callback - Optional. A callback function that receives the result of execution.
If execution fails, the callback function receives the error message \c EvalScript_ErrMessage.
*/
evalScript(script: any, callback: any): void;
/**
* Retrieves the unique identifier of the application.
in which the extension is currently running.
* @returns The unique ID string.
*/
getApplicationID(): any;
/**
* Retrieves host capability information for the application
in which the extension is currently running.
* @returns A \c #HostCapabilities object.
*/
getHostCapabilities(): any;
/**
* Triggers a CEP event programmatically. Yoy can use it to dispatch
an event of a predefined type, or of a type you have defined.
* @param event - A \c CSEvent object.
*/
dispatchEvent(event: any): void;
/**
* Registers an interest in a CEP event of a particular type, and
assigns an event handler.
The event infrastructure notifies your extension when events of this type occur,
passing the event object to the registered handler function.
* @param type - The name of the event type of interest.
* @param listener - The JavaScript handler function or method.
* @param obj - Optional, the object containing the handler method, if any.
Default is null.
*/
addEventListener(type: any, listener: Function, obj?: any): void;
/**
* Removes a registered event listener.
* @param type - The name of the event type of interest.
* @param listener - The JavaScript handler function or method that was registered.
* @param obj - Optional, the object containing the handler method, if any.
Default is null.
*/
removeEventListener(type: any, listener: any, obj: any): void;
/**
* Loads and launches another extension, or activates the extension if it is already loaded.
* @example
* To launch the extension "help" with ID "HLP" from this extension, call:
<code>requestOpenExtension("HLP", ""); </code>
* @param extensionId - The extension's unique identifier.
* @param startupParams - Not currently used, pass "".
*/
requestOpenExtension(extensionId: any, startupParams: any): void;
/**
* Retrieves the list of extensions currently loaded in the current host application.
The extension list is initialized once, and remains the same during the lifetime
of the CEP session.
* @param extensionIds - Optional, an array of unique identifiers for extensions of interest.
If omitted, retrieves data for all extensions.
* @returns Zero or more \c #Extension objects.
*/
getExtensions(extensionIds: any): any;
/**
* Retrieves network-related preferences.
* @returns A JavaScript object containing network preferences.
*/
getNetworkPreferences(): any;
/**
* Initializes the resource bundle for this extension with property values
for the current application and locale.
To support multiple locales, you must define a property file for each locale,
containing keyed display-string values for that locale.
See localization documentation for Extension Builder and related products.
Keys can be in the
form <code>key.value="localized string"</code>, for use in HTML text elements.
For example, in this input element, the localized \c key.value string is displayed
instead of the empty \c value string:
<code><input type="submit" value="" data-locale="key"/></code>
* @returns An object containing the resource bundle information.
*/
initResourceBundle(): any;
/**
* Writes installation information to a file.
* @returns The file path.
*/
dumpInstallationInfo(): any;
/**
* Retrieves version information for the current Operating System,
See http://www.useragentstring.com/pages/Chrome/ for Chrome \c navigator.userAgent values.
* @returns A string containing the OS version, or "unknown Operation System".
If user customizes the User Agent by setting CEF command parameter "--user-agent", only
"Mac OS X" or "Windows" will be returned.
*/
getOSInformation(): any;
/**
* Opens a page in the default system browser.
Since 4.2.0
* @param url - The URL of the page/file to open, or the email address.
Must use HTTP/HTTPS/file/mailto protocol. For example:
"http://www.adobe.com"
"https://github.com"
"file:///C:/log.txt"
"mailto:test@adobe.com"
* @returns One of these error codes:\n
<ul>\n
<li>NO_ERROR - 0</li>\n
<li>ERR_UNKNOWN - 1</li>\n
<li>ERR_INVALID_PARAMS - 2</li>\n
<li>ERR_INVALID_URL - 201</li>\n
</ul>\n
*/
openURLInDefaultBrowser(url: any): any;
/**
* Retrieves extension ID.
Since 4.2.0
* @returns extension ID.
*/
getExtensionID(): any;
/**
* Retrieves the scale factor of screen.
On Windows platform, the value of scale factor might be different from operating system's scale factor,
since host application may use its self-defined scale factor.
Since 4.2.0
* @returns One of the following float number.
<ul>\n
<li> -1.0 when error occurs </li>\n
<li> 1.0 means normal screen </li>\n
<li> >1.0 means HiDPI screen </li>\n
</ul>\n
*/
getScaleFactor(): any;
/**
* Set a handler to detect any changes of scale factor. This only works on Mac.
Since 4.2.0
* @param handler - The function to be called when scale factor is changed.
*/
setScaleFactorChangedHandler(handler: any): void;
/**
* Retrieves current API version.
Since 4.2.0
* @returns ApiVersion object.
*/
getCurrentApiVersion(): {
minor: string;
micro: string;
major: string;
};
/**
* Set panel flyout menu by an XML.
Since 5.2.0
Register a callback function for "com.adobe.csxs.events.flyoutMenuClicked" to get notified when a
menu item is clicked.
The "data" attribute of event is an object which contains "menuId" and "menuName" attributes.
Register callback functions for "com.adobe.csxs.events.flyoutMenuOpened" and "com.adobe.csxs.events.flyoutMenuClosed"
respectively to get notified when flyout menu is opened or closed.
* @param menu - A XML string which describes menu structure.
An example menu XML:
<Menu>
<MenuItem Id="menuItemId1" Label="TestExample1" Enabled="true" Checked="false"/>
<MenuItem Label="TestExample2">
<MenuItem Label="TestExample2-1" >
<MenuItem Label="TestExample2-1-1" Enabled="false" Checked="true"/>
</MenuItem>
<MenuItem Label="TestExample2-2" Enabled="true" Checked="true"/>
</MenuItem>
<MenuItem Label="---" />
<MenuItem Label="TestExample3" Enabled="false" Checked="false"/>
</Menu>
*/
setPanelFlyoutMenu(menu: any): void;
/**
* Updates a menu item in the extension window's flyout menu, by setting the enabled
and selection status.
Since 5.2.0
* @param menuItemLabel - The menu item label.
* @param enabled - True to enable the item, false to disable it (gray it out).
* @param checked - True to select the item, false to deselect it.
* @returns false when the host application does not support this functionality (HostCapabilities.EXTENDED_PANEL_MENU is false).
Fails silently if menu label is invalid.
*/
updatePanelMenuItem(menuItemLabel: any, enabled: any, checked: any): any;
/**
* An example menu XML:
<Menu>
<MenuItem Id="menuItemId1" Label="TestExample1" Enabled="true" Checkable="true" Checked="false" Icon="./image/small_16X16.png"/>
<MenuItem Id="menuItemId2" Label="TestExample2">
<MenuItem Id="menuItemId2-1" Label="TestExample2-1" >
<MenuItem Id="menuItemId2-1-1" Label="TestExample2-1-1" Enabled="false" Checkable="true" Checked="true"/>
</MenuItem>
<MenuItem Id="menuItemId2-2" Label="TestExample2-2" Enabled="true" Checkable="true" Checked="true"/>
</MenuItem>
<MenuItem Label="---" />
<MenuItem Id="menuItemId3" Label="TestExample3" Enabled="false" Checkable="true" Checked="false"/>
</Menu>
* @param menu - A XML string which describes menu structure.
* @param callback - The callback function which is called when a menu item is clicked. The only parameter is the returned ID of clicked menu item.
*/
setContextMenu(menu: any, callback: any): void;
/**
* An example menu JSON:
{
"menu": [
{
"id": "menuItemId1",
"label": "testExample1",
"enabled": true,
"checkable": true,
"checked": false,
"icon": "./image/small_16X16.png"
},
{
"id": "menuItemId2",
"label": "testExample2",
"menu": [
{
"id": "menuItemId2-1",
"label": "testExample2-1",
"menu": [
{
"id": "menuItemId2-1-1",
"label": "testExample2-1-1",
"enabled": false,
"checkable": true,
"checked": true
}
]
},
{
"id": "menuItemId2-2",
"label": "testExample2-2",
"enabled": true,
"checkable": true,
"checked": true
}
]
},
{
"label": "---"
},
{
"id": "menuItemId3",
"label": "testExample3",
"enabled": false,
"checkable": true,
"checked": false
}
]
}
* @param menu - A JSON string which describes menu structure.
* @param callback - The callback function which is called when a menu item is clicked. The only parameter is the returned ID of clicked menu item.
*/
setContextMenuByJSON(menu: any, callback: any): void;
/**
* Updates a context menu item by setting the enabled and selection status.
Since 5.2.0
* @param menuItemID - The menu item ID.
* @param enabled - True to enable the item, false to disable it (gray it out).
* @param checked - True to select the item, false to deselect it.
*/
updateContextMenuItem(menuItemID: any, enabled: any, checked: any): void;
/**
* Get the visibility status of an extension window.
Since 6.0.0
* @returns true if the extension window is visible; false if the extension window is hidden.
*/
isWindowVisible(): any;
/**
* Resize extension's content to the specified dimensions.
1. Works with modal and modeless extensions in all Adobe products.
2. Extension's manifest min/max size constraints apply and take precedence.
3. For panel extensions
3.1 This works in all Adobe products except:
* Premiere Pro
* Prelude
* After Effects
3.2 When the panel is in certain states (especially when being docked),
it will not change to the desired dimensions even when the
specified size satisfies min/max constraints.
Since 6.0.0
* @param width - The new width
* @param height - The new height
*/
resizeContent(width: any, height: any): void;
/**
* Register the invalid certificate callback for an extension.
This callback will be triggered when the extension tries to access the web site that contains the invalid certificate on the main frame.
But if the extension does not call this function and tries to access the web site containing the invalid certificate, a default error page will be shown.
Since 6.1.0
* @param callback - the callback function
*/
registerInvalidCertificateCallback(callback: any): void;
/**
* Register an interest in some key events to prevent them from being sent to the host application.
This function works with modeless extensions and panel extensions.
Generally all the key events will be sent to the host application for these two extensions if the current focused element
is not text input or dropdown,
If you want to intercept some key events and want them to be handled in the extension, please call this function
in advance to prevent them being sent to the host application.
Since 6.1.0
*/
registerKeyEventsInterest(keyEventsInterest: any): void;
/**
* Set the title of the extension window.
This function works with modal and modeless extensions in all Adobe products, and panel extensions in Photoshop, InDesign, InCopy, Illustrator, Flash Pro and Dreamweaver.
Since 6.1.0
* @param title - The window title.
*/
setWindowTitle(title: any): void;
/**
* Get the title of the extension window.
This function works with modal and modeless extensions in all Adobe products, and panel extensions in Photoshop, InDesign, InCopy, Illustrator, Flash Pro and Dreamweaver.
Since 6.1.0
* @returns The window title.
*/
getWindowTitle(): any;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,30 @@
import CSInterface, { CSEvent } from "./csinterface";
/**
* 扩展PS的CSInterface类
*/
export class CSInterfaceEx {
constructor() {
}
/**持久化运行 */
public persistent() {
var cs = new CSInterface();
var event1 = new CSEvent();
event1.type = "com.adobe.PhotoshopPersistent";
event1.scope = "APPLICATION";
event1.extensionId = cs.getExtensionID();
cs.dispatchEvent(event1);
}
/**取消持久化运行 */
public unPersisten() {
var cs = new CSInterface();
var event1 = new CSEvent();
event1.type = "com.adobe.PhotoshopUnPersistent";
event1.scope = "APPLICATION";
event1.extensionId = cs.getExtensionID();
cs.dispatchEvent(event1);
}
}

View File

@@ -0,0 +1,3 @@
export type Scripts = {
[key: string]: (a: any, ...ags: any) => any;
};

View File

@@ -0,0 +1,64 @@
import CSInterface from "./csinterface"
export function initJsx(path: string) {
console.log('[cep] init jsx', path);
const cs = new CSInterface()
cs.evalScript(`$.evalFile("${path}")`, (e) => {
if (e === 'undefined') return console.log('init json success');
console.warn('load jsx', e);
})
}
type JsxTypeof = typeof import('../../../src/jsx/index')
type MethodName = keyof JsxTypeof
export function evalJSX<T extends MethodName>(functionName: T, ...args: Parameters<JsxTypeof[T]>) {
return new Promise<ReturnType<JsxTypeof[T]>>((resolve, reject) => {
const formattedArgs = args
.map((arg) => {
console.log(JSON.stringify(arg));
return `${JSON.stringify(arg)}`;
})
.join(",");
const cs = new CSInterface()
cs.evalScript(
`try{
var res = ${functionName}(${formattedArgs});
JSON.stringify(res)
}catch(e){
alert(e);
e.fileName =new File(e.fileName).fsName;
JSON.stringify(e)
}`, (e) => {
console.log('evalJSX', e);
if (e === 'undefined') return resolve(null)
try {
let parsed = JSON.parse(e);
if (parsed.name === "ReferenceError") {
console.error("REFERENCE ERROR");
reject(parsed);
} else {
resolve(parsed);
}
} catch (error) {
reject(error)
}
})
})
}
export function evalFile(path: string) {
return new Promise((resolve, reject) => {
const cs = new CSInterface()
cs.evalScript(`$.evalFile("${path}")`, (e) => {
if (e === 'undefined') return resolve(null)
// alert(e)
console.warn('evalFileError: ' + path, e);
reject(e)
})
})
}
export { CSInterface }
export { CSInterfaceEx } from "./csinterfaceEx"

View File

@@ -0,0 +1,87 @@
// Abstracted built-in Node.js Modules
//@ts-ignore
export const crypto = (
typeof window.cep !== "undefined" ? require("crypto") : {}
) as typeof import("crypto");
export const assert = (
typeof window.cep !== "undefined" ? require("assert") : {}
) as typeof import("assert");
export const buffer = (
typeof window.cep !== "undefined" ? require("buffer") : {}
) as typeof import("buffer");
export const child_process = (
typeof window.cep !== "undefined" ? require("child_process") : {}
) as typeof import("child_process");
export const cluster = (
typeof window.cep !== "undefined" ? require("cluster") : {}
) as typeof import("cluster");
export const dgram = (
typeof window.cep !== "undefined" ? require("dgram") : {}
) as typeof import("dgram");
export const dns = (
typeof window.cep !== "undefined" ? require("dns") : {}
) as typeof import("dns");
export const domain = (
typeof window.cep !== "undefined" ? require("domain") : {}
) as typeof import("domain");
export const events = (
typeof window.cep !== "undefined" ? require("events") : {}
) as typeof import("events");
export const fs = (
typeof window.cep !== "undefined" ? require("fs") : {}
) as typeof import("fs");
export const http = (
typeof window.cep !== "undefined" ? require("http") : {}
) as typeof import("http");
export const https = (
typeof window.cep !== "undefined" ? require("https") : {}
) as typeof import("https");
export const net = (
typeof window.cep !== "undefined" ? require("net") : {}
) as typeof import("net");
export const os = (
typeof window.cep !== "undefined" ? require("os") : {}
) as typeof import("os");
export const path = (
typeof window.cep !== "undefined" ? require("path") : {}
) as typeof import("path");
export const punycode = (
typeof window.cep !== "undefined" ? require("punycode") : {}
) as typeof import("punycode");
export const querystring = (
typeof window.cep !== "undefined" ? require("querystring") : {}
) as typeof import("querystring");
export const readline = (
typeof window.cep !== "undefined" ? require("readline") : {}
) as typeof import("readline");
export const stream = (
typeof window.cep !== "undefined" ? require("stream") : {}
) as typeof import("stream");
export const string_decoder = (
typeof window.cep !== "undefined" ? require("string_decoder") : {}
) as typeof import("string_decoder");
export const timers = (
typeof window.cep !== "undefined" ? require("timers") : {}
) as typeof import("timers");
export const tls = (
typeof window.cep !== "undefined" ? require("tls") : {}
) as typeof import("tls");
export const tty = (
typeof window.cep !== "undefined" ? require("tty") : {}
) as typeof import("tty");
export const url = (
typeof window.cep !== "undefined" ? require("url") : {}
) as typeof import("url");
export const util = (
typeof window.cep !== "undefined" ? require("util") : {}
) as typeof import("util");
export const v8 = (
typeof window.cep !== "undefined" ? require("v8") : {}
) as typeof import("v8");
export const vm = (
typeof window.cep !== "undefined" ? require("vm") : {}
) as typeof import("vm");
export const zlib = (
typeof window.cep !== "undefined" ? require("zlib") : {}
) as typeof import("zlib");

265
AdminPanel/plugins/utils/cep/vulcan.d.ts vendored Normal file
View File

@@ -0,0 +1,265 @@
/**
* Vulcan
The singleton instance, <tt>VulcanInterface</tt>, provides an interface
to the Vulcan. Allows you to launch CC applications
and discover information about them.
*/
export default class Vulcan {
constructor();
/**
* Gets all available application SAPCode-Specifiers on the local machine.
*
* Vulcan Control New 6.x APIs, and Deprecating older Vulcan Control APIs.
* Changes : New getTargetSpecifiersEx returns productSAPCodeSpecifiers
*
* @return The array of all available application SAPCode-Specifiers.
*/
getTargetSpecifiersEx(): any;
/**
* Launches a CC application on the local machine, if it is not already running.
*
* Vulcan Control New 6.x APIs, and Deprecating older Vulcan Control APIs.
* Changes : New launchAppEx uses productSAPCodeSpecifiers
*
* @param productSAPCodeSpecifier The application specifier; for example "ILST-25.2.3", "ILST-25", "ILST-25.2.3-en_US" and "ILST. Refer to `Documentation/CEP 11.1 HTML Extension Cookbook.md#applications-integrated-with-cep` for product SAPCode.
* @param focus True to launch in foreground, or false to launch in the background.
* @param cmdLine Optional, command-line parameters to supply to the launch command.
* @return True if the app can be launched, false otherwise.
*/
launchAppEx(
productSAPCodeSpecifier: string,
focus: boolean,
cmdLine?: string,
): boolean;
/**
* Checks whether a CC application is running on the local machine.
*
* Vulcan Control New 6.x APIs, and Deprecating older Vulcan Control APIs.
* Changes : New isAppRunningEx uses productSAPCodeSpecifiers
*
* @param productSAPCodeSpecifier The application specifier; for example "ILST-25.2.3", "ILST-25", "ILST-25.2.3-en_US" and "ILST. Refer to `Documentation/CEP 11.1 HTML Extension Cookbook.md#applications-integrated-with-cep` for product SAPCode.
* @return True if the app is running, false otherwise.
*/
isAppRunningEx(productSAPCodeSpecifier: string): boolean;
/**
* Checks whether a CC application is installed on the local machine.
*
* Vulcan Control New 6.x APIs, and Deprecating older Vulcan Control APIs.
* Changes : New isAppInstalledEx uses productSAPCodeSpecifiers
*
* @param productSAPCodeSpecifier The application specifier; for example "ILST-25.2.3", "ILST-25", "ILST-25.2.3-en_US" and "ILST. Refer to `Documentation/CEP 11.1 HTML Extension Cookbook.md#applications-integrated-with-cep` for product SAPCode.
* @return True if the app is installed, false otherwise.
*/
isAppInstalledEx(productSAPCodeSpecifier: string): any;
/**s
* Retrieves the local install path of a CC application.
*
* Vulcan Control New 6.x APIs, and Deprecating older Vulcan Control APIs.
* Changes : New getAppPathEx uses productSAPCodeSpecifiers
*
* @param productSAPCodeSpecifier The application specifier; for example "ILST-25.2.3", "ILST-25", "ILST-25.2.3-en_US" and "ILST. Refer to `Documentation/CEP 11.1 HTML Extension Cookbook.md#applications-integrated-with-cep` for product SAPCode.
* @return The path string if the application is found, "" otherwise.
*/
getAppPathEx(): any;
// OLD FUNCTIONS
// OLD FUNCTIONS
// OLD FUNCTIONS
// OLD FUNCTIONS
/**
* Gets all available application specifiers on the local machine.
* @returns The array of all available application specifiers.
*/
getTargetSpecifiers(): any;
/**
* Launches a CC application on the local machine, if it is not already running.
* @param targetSpecifier - The application specifier; for example "indesign".
Note: In Windows 7 64-bit or Windows 8 64-bit system, some target applications (like Photoshop and Illustrator) have both 32-bit version
and 64-bit version. Therefore, we need to specify the version by this parameter with "photoshop-70.032" or "photoshop-70.064". If you
installed Photoshop 32-bit and 64-bit on one Windows 64-bit system and invoke this interface with parameter "photoshop-70.032", you may
receive wrong result.
The specifiers for Illustrator is "illustrator-17.032", "illustrator-17.064", "illustrator-17" and "illustrator".
In other platforms there is no such issue, so we can use "photoshop" or "photoshop-70" as specifier.
* @param focus - True to launch in foreground, or false to launch in the background.
* @param cmdLine - Optional, command-line parameters to supply to the launch command.
* @returns True if the app can be launched, false otherwise.
*/
launchApp(targetSpecifier: any, focus: any, cmdLine: any): any;
/**
* Checks whether a CC application is running on the local machine.
* @param targetSpecifier - The application specifier; for example "indesign".
Note: In Windows 7 64-bit or Windows 8 64-bit system, some target applications (like Photoshop and Illustrator) have both 32-bit version
and 64-bit version. Therefore, we need to specify the version by this parameter with "photoshop-70.032" or "photoshop-70.064". If you
installed Photoshop 32-bit and 64-bit on one Windows 64-bit system and invoke this interface with parameter "photoshop-70.032", you may
receive wrong result.
The specifiers for Illustrator is "illustrator-17.032", "illustrator-17.064", "illustrator-17" and "illustrator".
In other platforms there is no such issue, so we can use "photoshop" or "photoshop-70" as specifier.
* @returns True if the app is running, false otherwise.
*/
isAppRunning(targetSpecifier: any): any;
/**
* Checks whether a CC application is installed on the local machine.
* @param targetSpecifier - The application specifier; for example "indesign".
Note: In Windows 7 64-bit or Windows 8 64-bit system, some target applications (like Photoshop and Illustrator) have both 32-bit version
and 64-bit version. Therefore, we need to specify the version by this parameter with "photoshop-70.032" or "photoshop-70.064". If you
installed Photoshop 32-bit and 64-bit on one Windows 64-bit system and invoke this interface with parameter "photoshop-70.032", you may
receive wrong result.
The specifiers for Illustrator is "illustrator-17.032", "illustrator-17.064", "illustrator-17" and "illustrator".
In other platforms there is no such issue, so we can use "photoshop" or "photoshop-70" as specifier.
* @returns True if the app is installed, false otherwise.
*/
isAppInstalled(targetSpecifier: any): any;
/**
* Retrieves the local install path of a CC application.
* @param targetSpecifier - The application specifier; for example "indesign".
Note: In Windows 7 64-bit or Windows 8 64-bit system, some target applications (like Photoshop and Illustrator) have both 32-bit version
and 64-bit version. Therefore, we need to specify the version by this parameter with "photoshop-70.032" or "photoshop-70.064". If you
installed Photoshop 32-bit and 64-bit on one Windows 64-bit system and invoke this interface with parameter "photoshop-70.032", you may
receive wrong result.
The specifiers for Illustrator is "illustrator-17.032", "illustrator-17.064", "illustrator-17" and "illustrator".
In other platforms there is no such issue, so we can use "photoshop" or "photoshop-70" as specifier.
* @returns The path string if the application is found, "" otherwise.
*/
getAppPath(targetSpecifier: any): any;
/**
* Registers a message listener callback function for a Vulcan message.
* @param type - The message type.
* @param callback - The callback function that handles the message.
Takes one argument, the message object.
* @param obj - Optional, the object containing the callback method, if any.
Default is null.
*/
addMessageListener(type: any, callback: any, obj: any): void;
/**
* Removes a registered message listener callback function for a Vulcan message.
* @param type - The message type.
* @param callback - The callback function that was registered.
Takes one argument, the message object.
* @param obj - Optional, the object containing the callback method, if any.
Default is null.
*/
removeMessageListener(type: any, callback: any, obj: any): void;
/**
* Dispatches a Vulcan message.
* @param vulcanMessage - The message object.
*/
dispatchMessage(vulcanMessage: any): void;
/**
* Retrieves the message payload of a Vulcan message for the registered message listener callback function.
* @param vulcanMessage - The message object.
* @returns A string containing the message payload.
*/
getPayload(vulcanMessage: any): any;
/**
* Gets all available endpoints of the running Vulcan-enabled applications.
Since 7.0.0
* @returns The array of all available endpoints.
An example endpoint string:
<endPoint>
<appId>PHXS</appId>
<appVersion>16.1.0</appVersion>
</endPoint>
*/
getEndPoints(): any;
/**
* Gets the endpoint for itself.
Since 7.0.0
* @returns The endpoint string for itself.
*/
getSelfEndPoint(): any;
}
/**
* Singleton instance of Vulcan
*/
declare var VulcanInterface: any;
/**
* VulcanMessage
Message type for sending messages between host applications.
A message of this type can be sent to the designated destination
when appId and appVersion are provided and valid. Otherwise,
the message is broadcast to all running Vulcan-enabled applications.
To send a message between extensions running within one
application, use the <code>CSEvent</code> type in CSInterface.js.
* @param type - The message type.
* @param appId - The peer appId.
* @param appVersion - The peer appVersion.
*/
export declare class VulcanMessage {
static TYPE_PREFIX: string;
static SCOPE_SUITE: string;
static DEFAULT_APP_ID: string;
static DEFAULT_APP_VERSION: string;
static DEFAULT_DATA: string;
static dataTemplate: string;
static payloadTemplate: string;
constructor(type: any, appId: any, appVersion: any);
/**
* Initializes this message instance.
* @param message - A \c message instance to use for initialization.
*/
initialize(message: any): void;
/**
* Retrieves the message data.
* @returns A data string in XML format.
*/
xmlData(): any;
/**
* Sets the message payload of this message.
* @param payload - A string containing the message payload.
*/
setPayload(payload: any): void;
/**
* Retrieves the message payload of this message.
* @returns A string containing the message payload.
*/
getPayload(): any;
/**
* Converts the properties of this instance to a string.
* @returns The string version of this instance.
*/
toString(): any;
}
/**
* Retrieves the content of an XML element.
* @param xmlStr - The XML string.
* @param key - The name of the tag.
* @returns The content of the tag, or the empty string
if such tag is not found or the tag has no content.
*/
declare function GetValueByKey(xmlStr: any, key: any): any;
/**
* Reports whether required parameters are valid.
* @returns True if all required parameters are valid,
false if any of the required parameters are invalid.
*/
declare function requiredParamsValid(): any;
/**
* Reports whether a string has a given prefix.
* @param str - The target string.
* @param prefix - The specific prefix string.
* @returns True if the string has the prefix, false if not.
*/
declare function strStartsWith(str: any, prefix: any): any;

View File

@@ -0,0 +1,620 @@
/**************************************************************************************************
*
* ADOBE SYSTEMS INCORPORATED
* Copyright 2020 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the
* terms of the Adobe license agreement accompanying it. If you have received this file from a
* source other than Adobe, then your use, modification, or distribution of it requires the prior
* written permission of Adobe.
*
**************************************************************************************************/
/** Vulcan - v11.2.0 */
/**
* @class Vulcan
*
* The singleton instance, <tt>VulcanInterface</tt>, provides an interface
* to the Vulcan. Allows you to launch CC applications
* and discover information about them.
*/
function Vulcan() {}
/**
* Gets all available application SAPCode-Specifiers on the local machine.
*
* Vulcan Control New 6.x APIs, and Deprecating older Vulcan Control APIs.
* Changes : New getTargetSpecifiersEx returns productSAPCodeSpecifiers
*
* @return The array of all available application SAPCode-Specifiers.
*/
Vulcan.prototype.getTargetSpecifiersEx = function () {
var params = {};
return JSON.parse(
window.__adobe_cep__.invokeSync(
"vulcanGetTargetSpecifiersEx",
JSON.stringify(params)
)
);
};
/**
* Launches a CC application on the local machine, if it is not already running.
*
* Vulcan Control New 6.x APIs, and Deprecating older Vulcan Control APIs.
* Changes : New launchAppEx uses productSAPCodeSpecifiers
*
* @param productSAPCodeSpecifier The application specifier; for example "ILST-25.2.3", "ILST-25", "ILST-25.2.3-en_US" and "ILST. Refer to `Documentation/CEP 11.1 HTML Extension Cookbook.md#applications-integrated-with-cep` for product SAPCode.
* @param focus True to launch in foreground, or false to launch in the background.
* @param cmdLine Optional, command-line parameters to supply to the launch command.
* @return True if the app can be launched, false otherwise.
*/
Vulcan.prototype.launchAppEx = function (
productSAPCodeSpecifier,
focus,
cmdLine
) {
if (!requiredParamsValid(productSAPCodeSpecifier)) {
return false;
}
var params = {};
params.productSAPCodeSpecifier = productSAPCodeSpecifier;
params.focus = focus ? "true" : "false";
params.cmdLine = requiredParamsValid(cmdLine) ? cmdLine : "";
return JSON.parse(
window.__adobe_cep__.invokeSync("vulcanLaunchAppEx", JSON.stringify(params))
).result;
};
/**
* Checks whether a CC application is running on the local machine.
*
* Vulcan Control New 6.x APIs, and Deprecating older Vulcan Control APIs.
* Changes : New isAppRunningEx uses productSAPCodeSpecifiers
*
* @param productSAPCodeSpecifier The application specifier; for example "ILST-25.2.3", "ILST-25", "ILST-25.2.3-en_US" and "ILST. Refer to `Documentation/CEP 11.1 HTML Extension Cookbook.md#applications-integrated-with-cep` for product SAPCode.
* @return True if the app is running, false otherwise.
*/
Vulcan.prototype.isAppRunningEx = function (productSAPCodeSpecifier) {
if (!requiredParamsValid(productSAPCodeSpecifier)) {
return false;
}
var params = {};
params.productSAPCodeSpecifier = productSAPCodeSpecifier;
return JSON.parse(
window.__adobe_cep__.invokeSync(
"vulcanIsAppRunningEx",
JSON.stringify(params)
)
).result;
};
/**
* Checks whether a CC application is installed on the local machine.
*
* Vulcan Control New 6.x APIs, and Deprecating older Vulcan Control APIs.
* Changes : New isAppInstalledEx uses productSAPCodeSpecifiers
*
* @param productSAPCodeSpecifier The application specifier; for example "ILST-25.2.3", "ILST-25", "ILST-25.2.3-en_US" and "ILST. Refer to `Documentation/CEP 11.1 HTML Extension Cookbook.md#applications-integrated-with-cep` for product SAPCode.
* @return True if the app is installed, false otherwise.
*/
Vulcan.prototype.isAppInstalledEx = function (productSAPCodeSpecifier) {
if (!requiredParamsValid(productSAPCodeSpecifier)) {
return false;
}
var params = {};
params.productSAPCodeSpecifier = productSAPCodeSpecifier;
return JSON.parse(
window.__adobe_cep__.invokeSync(
"vulcanIsAppInstalledEx",
JSON.stringify(params)
)
).result;
};
/**s
* Retrieves the local install path of a CC application.
*
* Vulcan Control New 6.x APIs, and Deprecating older Vulcan Control APIs.
* Changes : New getAppPathEx uses productSAPCodeSpecifiers
*
* @param productSAPCodeSpecifier The application specifier; for example "ILST-25.2.3", "ILST-25", "ILST-25.2.3-en_US" and "ILST. Refer to `Documentation/CEP 11.1 HTML Extension Cookbook.md#applications-integrated-with-cep` for product SAPCode.
* @return The path string if the application is found, "" otherwise.
*/
Vulcan.prototype.getAppPathEx = function (productSAPCodeSpecifier) {
if (!requiredParamsValid(productSAPCodeSpecifier)) {
return "";
}
var params = {};
params.productSAPCodeSpecifier = productSAPCodeSpecifier;
return JSON.parse(
window.__adobe_cep__.invokeSync(
"vulcanGetAppPathEx",
JSON.stringify(params)
)
).result;
};
/**
* DEPRECATED API:: use getTargetSpecifiersEx
* Gets all available application specifiers on the local machine.
*
* @return The array of all available application specifiers.
*/
Vulcan.prototype.getTargetSpecifiers = function () {
console.warn(
"WARNING! Function 'getTargetSpecifiers' has been deprecated, please use the new 'getTargetSpecifiersEx' function instead!"
);
var params = {};
return JSON.parse(
window.__adobe_cep__.invokeSync(
"vulcanGetTargetSpecifiers",
JSON.stringify(params)
)
);
};
/**
* DEPRECATED API:: use launchAppEx
* Launches a CC application on the local machine, if it is not already running.
*
* @param targetSpecifier The application specifier; for example "indesign".
*
* Note: In Windows 7 64-bit or Windows 8 64-bit system, some target applications (like Photoshop and Illustrator) have both 32-bit version
* and 64-bit version. Therefore, we need to specify the version by this parameter with "photoshop-70.032" or "photoshop-70.064". If you
* installed Photoshop 32-bit and 64-bit on one Windows 64-bit system and invoke this interface with parameter "photoshop-70.032", you may
* receive wrong result.
* The specifiers for Illustrator is "illustrator-17.032", "illustrator-17.064", "illustrator-17" and "illustrator".
*
* In other platforms there is no such issue, so we can use "photoshop" or "photoshop-70" as specifier.
* @param focus True to launch in foreground, or false to launch in the background.
* @param cmdLine Optional, command-line parameters to supply to the launch command.
* @return True if the app can be launched, false otherwise.
*/
Vulcan.prototype.launchApp = function (targetSpecifier, focus, cmdLine) {
console.warn(
"WARNING! Function 'launchApp' has been deprecated, please use the new 'launchAppEx' function instead!"
);
if (!requiredParamsValid(targetSpecifier)) {
return false;
}
var params = {};
params.targetSpecifier = targetSpecifier;
params.focus = focus ? "true" : "false";
params.cmdLine = requiredParamsValid(cmdLine) ? cmdLine : "";
return JSON.parse(
window.__adobe_cep__.invokeSync("vulcanLaunchApp", JSON.stringify(params))
).result;
};
/**
* DEPRECATED API:: use isAppRunningEx
* Checks whether a CC application is running on the local machine.
*
* @param targetSpecifier The application specifier; for example "indesign".
*
* Note: In Windows 7 64-bit or Windows 8 64-bit system, some target applications (like Photoshop and Illustrator) have both 32-bit version
* and 64-bit version. Therefore, we need to specify the version by this parameter with "photoshop-70.032" or "photoshop-70.064". If you
* installed Photoshop 32-bit and 64-bit on one Windows 64-bit system and invoke this interface with parameter "photoshop-70.032", you may
* receive wrong result.
* The specifiers for Illustrator is "illustrator-17.032", "illustrator-17.064", "illustrator-17" and "illustrator".
*
* In other platforms there is no such issue, so we can use "photoshop" or "photoshop-70" as specifier.
* @return True if the app is running, false otherwise.
*/
Vulcan.prototype.isAppRunning = function (targetSpecifier) {
console.warn(
"WARNING! Function 'isAppRunning' has been deprecated, please use the new 'isAppRunningEx' function instead!"
);
if (!requiredParamsValid(targetSpecifier)) {
return false;
}
var params = {};
params.targetSpecifier = targetSpecifier;
return JSON.parse(
window.__adobe_cep__.invokeSync(
"vulcanIsAppRunning",
JSON.stringify(params)
)
).result;
};
/**
* DEPRECATED API:: use isAppInstalledEx
* Checks whether a CC application is installed on the local machine.
*
* @param targetSpecifier The application specifier; for example "indesign".
*
* Note: In Windows 7 64-bit or Windows 8 64-bit system, some target applications (like Photoshop and Illustrator) have both 32-bit version
* and 64-bit version. Therefore, we need to specify the version by this parameter with "photoshop-70.032" or "photoshop-70.064". If you
* installed Photoshop 32-bit and 64-bit on one Windows 64-bit system and invoke this interface with parameter "photoshop-70.032", you may
* receive wrong result.
* The specifiers for Illustrator is "illustrator-17.032", "illustrator-17.064", "illustrator-17" and "illustrator".
*
* In other platforms there is no such issue, so we can use "photoshop" or "photoshop-70" as specifier.
* @return True if the app is installed, false otherwise.
*/
Vulcan.prototype.isAppInstalled = function (targetSpecifier) {
console.warn(
"WARNING! Function 'isAppInstalled' has been deprecated, please use the new 'isAppInstalledEx' function instead!"
);
if (!requiredParamsValid(targetSpecifier)) {
return false;
}
var params = {};
params.targetSpecifier = targetSpecifier;
return JSON.parse(
window.__adobe_cep__.invokeSync(
"vulcanIsAppInstalled",
JSON.stringify(params)
)
).result;
};
/**
* DEPRECATED API:: use getAppPathEx
* Retrieves the local install path of a CC application.
*
* @param targetSpecifier The application specifier; for example "indesign".
*
* Note: In Windows 7 64-bit or Windows 8 64-bit system, some target applications (like Photoshop and Illustrator) have both 32-bit version
* and 64-bit version. Therefore, we need to specify the version by this parameter with "photoshop-70.032" or "photoshop-70.064". If you
* installed Photoshop 32-bit and 64-bit on one Windows 64-bit system and invoke this interface with parameter "photoshop-70.032", you may
* receive wrong result.
* The specifiers for Illustrator is "illustrator-17.032", "illustrator-17.064", "illustrator-17" and "illustrator".
*
* In other platforms there is no such issue, so we can use "photoshop" or "photoshop-70" as specifier.
* @return The path string if the application is found, "" otherwise.
*/
Vulcan.prototype.getAppPath = function (targetSpecifier) {
console.warn(
"WARNING! Function 'getAppPath' has been deprecated, please use the new 'getAppPathEx' function instead!"
);
if (!requiredParamsValid(targetSpecifier)) {
return "";
}
var params = {};
params.targetSpecifier = targetSpecifier;
return JSON.parse(
window.__adobe_cep__.invokeSync("vulcanGetAppPath", JSON.stringify(params))
).result;
};
/**
* Registers a message listener callback function for a Vulcan message.
*
* @param type The message type.
* @param callback The callback function that handles the message.
* Takes one argument, the message object.
* @param obj Optional, the object containing the callback method, if any.
* Default is null.
*/
Vulcan.prototype.addMessageListener = function (type, callback, obj) {
if (
!requiredParamsValid(type, callback) ||
!strStartsWith(type, VulcanMessage.TYPE_PREFIX)
) {
return;
}
var params = {};
params.type = type;
window.__adobe_cep__.invokeAsync(
"vulcanAddMessageListener",
JSON.stringify(params),
callback,
obj
);
};
/**
* Removes a registered message listener callback function for a Vulcan message.
*
* @param type The message type.
* @param callback The callback function that was registered.
* Takes one argument, the message object.
* @param obj Optional, the object containing the callback method, if any.
* Default is null.
*/
Vulcan.prototype.removeMessageListener = function (type, callback, obj) {
if (
!requiredParamsValid(type, callback) ||
!strStartsWith(type, VulcanMessage.TYPE_PREFIX)
) {
return;
}
var params = {};
params.type = type;
window.__adobe_cep__.invokeAsync(
"vulcanRemoveMessageListener",
JSON.stringify(params),
callback,
obj
);
};
/**
* Dispatches a Vulcan message.
*
* @param vulcanMessage The message object.
*/
Vulcan.prototype.dispatchMessage = function (vulcanMessage) {
if (
!requiredParamsValid(vulcanMessage) ||
!strStartsWith(vulcanMessage.type, VulcanMessage.TYPE_PREFIX)
) {
return;
}
var params = {};
var message = new VulcanMessage(vulcanMessage.type);
message.initialize(vulcanMessage);
params.vulcanMessage = message;
window.__adobe_cep__.invokeSync(
"vulcanDispatchMessage",
JSON.stringify(params)
);
};
/**
* Retrieves the message payload of a Vulcan message for the registered message listener callback function.
*
* @param vulcanMessage The message object.
* @return A string containing the message payload.
*/
Vulcan.prototype.getPayload = function (vulcanMessage) {
if (
!requiredParamsValid(vulcanMessage) ||
!strStartsWith(vulcanMessage.type, VulcanMessage.TYPE_PREFIX)
) {
return null;
}
var message = new VulcanMessage(vulcanMessage.type);
message.initialize(vulcanMessage);
return message.getPayload();
};
/**
* Gets all available endpoints of the running Vulcan-enabled applications.
*
* Since 7.0.0
*
* @return The array of all available endpoints.
* An example endpoint string:
* <endPoint>
* <appId>PHXS</appId>
* <appVersion>16.1.0</appVersion>
* </endPoint>
*/
Vulcan.prototype.getEndPoints = function () {
var params = {};
return JSON.parse(
window.__adobe_cep__.invokeSync(
"vulcanGetEndPoints",
JSON.stringify(params)
)
);
};
/**
* Gets the endpoint for itself.
*
* Since 7.0.0
*
* @return The endpoint string for itself.
*/
Vulcan.prototype.getSelfEndPoint = function () {
var params = {};
return window.__adobe_cep__.invokeSync(
"vulcanGetSelfEndPoint",
JSON.stringify(params)
);
};
/** Singleton instance of Vulcan **/
var VulcanInterface = new Vulcan();
//--------------------------------- Vulcan Message ------------------------------
/**
* @class VulcanMessage
* Message type for sending messages between host applications.
* A message of this type can be sent to the designated destination
* when appId and appVersion are provided and valid. Otherwise,
* the message is broadcast to all running Vulcan-enabled applications.
*
* To send a message between extensions running within one
* application, use the <code>CSEvent</code> type in CSInterface.js.
*
* @param type The message type.
* @param appId The peer appId.
* @param appVersion The peer appVersion.
*
*/
function VulcanMessage(type, appId, appVersion) {
this.type = type;
this.scope = VulcanMessage.SCOPE_SUITE;
this.appId = requiredParamsValid(appId)
? appId
: VulcanMessage.DEFAULT_APP_ID;
this.appVersion = requiredParamsValid(appVersion)
? appVersion
: VulcanMessage.DEFAULT_APP_VERSION;
this.data = VulcanMessage.DEFAULT_DATA;
}
VulcanMessage.TYPE_PREFIX = "vulcan.SuiteMessage.";
VulcanMessage.SCOPE_SUITE = "GLOBAL";
VulcanMessage.DEFAULT_APP_ID = "UNKNOWN";
VulcanMessage.DEFAULT_APP_VERSION = "UNKNOWN";
VulcanMessage.DEFAULT_DATA = "<data><payload></payload></data>";
VulcanMessage.dataTemplate = "<data>{0}</data>";
VulcanMessage.payloadTemplate = "<payload>{0}</payload>";
/**
* Initializes this message instance.
*
* @param message A \c message instance to use for initialization.
*/
VulcanMessage.prototype.initialize = function (message) {
this.type = message.type;
this.scope = message.scope;
this.appId = message.appId;
this.appVersion = message.appVersion;
this.data = message.data;
};
/**
* Retrieves the message data.
*
* @return A data string in XML format.
*/
VulcanMessage.prototype.xmlData = function () {
if (this.data === undefined) {
var str = "";
str = String.format(VulcanMessage.payloadTemplate, str);
this.data = String.format(VulcanMessage.dataTemplate, str);
}
return this.data;
};
/**
* Sets the message payload of this message.
*
* @param payload A string containing the message payload.
*/
VulcanMessage.prototype.setPayload = function (payload) {
var str = cep.encoding.convertion.utf8_to_b64(payload);
str = String.format(VulcanMessage.payloadTemplate, str);
this.data = String.format(VulcanMessage.dataTemplate, str);
};
/**
* Retrieves the message payload of this message.
*
* @return A string containing the message payload.
*/
VulcanMessage.prototype.getPayload = function () {
var str = GetValueByKey(this.data, "payload");
if (str !== null) {
return cep.encoding.convertion.b64_to_utf8(str);
}
return null;
};
/**
* Converts the properties of this instance to a string.
*
* @return The string version of this instance.
*/
VulcanMessage.prototype.toString = function () {
var str = "type=" + this.type;
str += ", scope=" + this.scope;
str += ", appId=" + this.appId;
str += ", appVersion=" + this.appVersion;
str += ", data=" + this.xmlData();
return str;
};
//--------------------------------------- Util --------------------------------
/**
* Formats a string based on a template.
*
* @param src The format template.
*
* @return The formatted string
*/
String.format = function (src) {
if (arguments.length === 0) {
return null;
}
var args = Array.prototype.slice.call(arguments, 1);
return src.replace(/\{(\d+)\}/g, function (m, i) {
return args[i];
});
};
/**
* Retrieves the content of an XML element.
*
* @param xmlStr The XML string.
* @param key The name of the tag.
*
* @return The content of the tag, or the empty string
* if such tag is not found or the tag has no content.
*/
function GetValueByKey(xmlStr, key) {
if (window.DOMParser) {
var parser = new window.DOMParser();
try {
var xmlDoc = parser.parseFromString(xmlStr, "text/xml");
var node = xmlDoc.getElementsByTagName(key)[0];
if (node && node.childNodes[0]) {
return node.childNodes[0].nodeValue;
}
} catch (e) {
//log the error
}
}
return "";
}
/**
* Reports whether required parameters are valid.
*
* @return True if all required parameters are valid,
* false if any of the required parameters are invalid.
*/
function requiredParamsValid() {
for (var i = 0; i < arguments.length; i++) {
var argument = arguments[i];
if (argument === undefined || argument === null) {
return false;
}
}
return true;
}
/**
* Reports whether a string has a given prefix.
*
* @param str The target string.
* @param prefix The specific prefix string.
*
* @return True if the string has the prefix, false if not.
*/
function strStartsWith(str, prefix) {
if (typeof str != "string") {
return false;
}
return str.indexOf(prefix) === 0;
}
// Boilerplate Added Export
export { VulcanMessage };
export default Vulcan;

View File

@@ -0,0 +1,65 @@
/**
* 获取当前光标位置 返回表示位置的索引number以0开头
* @param ctrl
* @returns {number}
*/
export function getCursorPosition(element) {
var CaretPos = 0;
if (document.selection) {//支持IE
element.focus();
var Sel = document.selection.createRange();
Sel.moveStart('character', -element.value.length);
CaretPos = Sel.text.length;
}
else if (element.selectionStart || element.selectionStart == '0')//支持firefox
CaretPos = element.selectionStart;
return (CaretPos);
}
export function insertAtCursor(myField, myValue) {
//IE 浏览器
if (document.selection) {
myField.focus();
sel = document.selection.createRange();
sel.text = myValue;
sel.select();
}
//FireFox、Chrome等
else if (myField.selectionStart || myField.selectionStart == '0') {
var startPos = myField.selectionStart;
var endPos = myField.selectionEnd;
// 保存滚动条
var restoreTop = myField.scrollTop;
myField.value = myField.value.substring(0, startPos) + myValue + myField.value.substring(endPos, myField.value.length);
if (restoreTop > 0) {
myField.scrollTop = restoreTop;
}
myField.focus();
myField.selectionStart = startPos + myValue.length;
myField.selectionEnd = startPos + myValue.length;
} else {
myField.value += myValue;
myField.focus();
}
}

View File

@@ -0,0 +1,313 @@
/**
* 简单版对dom的封装
* 初始化一个myDom对象
* 通过类、id、以及dom元素创建
* 可以获取所有子节点、父节点、第一个子节点、最后一个子节点、上一个兄弟节点、下一个兄弟节点
* 判断元素类型
* 获取文本
* 获取元素的坐标、宽高
* 设置元素的坐标、宽高
* 设置样式
* 复制元素
* 返回真正的dom元素
* 获取所有父亲节点
* 获取指定类型的父节点
* 获取所有子节点
* 获取自定类型的子节点
*/
/**
* 获取dom元素
* @param {*} 参数 ,可以接受dom元素或者id(id必须带#号)
*/
export function $(el) {
//id选择器
if (typeof el == 'object') {
return new Dom(el);
} else if (typeof el == 'string') {
let dom = document.getElementById(el);
return dom ? new Dom(dom) : null;
} else {
console.warn(`Dom传入的字符串不符合要求${el}`);
}
return null;
}
export class Dom {
constructor(el) {
this._el = el;
}
/**
* 获取元素内的所有文本内容
*/
getTexts() {
let result = [];
const callback = (el) => {
let arr = [...el.childNodes];
arr.filter(item => item.nodeType == 3).forEach(item => result.push(item.nodeValue))
arr.filter(item => item.nodeType == 1).forEach(item => callback(item));//遍历所有元素的子节点
}
callback(this._el);
return result;
}
/**
* 获取标签内第一个文本,没有返回空
* @returns
*/
getText() {
let texts = this.getTexts();
return texts.length > 0 ? texts[0] : ''
}
/**
* 获取元素的高
*/
getHeight() {
return this._el.offsetHeight;
}
/**
* 获取元素的宽
*/
getWidth() {
return this._el.offsetWidth;
}
get left() {
return this.getLeft();
}
get top() {
return this.getTop();
}
get width() {
return this._el.offsetWidth;
}
get height() {
return this._el.offsetHeight;
}
get bottom() {
return this.top + this.height;
}
get right() {
return this.left + this.width;
}
/**
* 获取元素的left坐标
*/
getLeft() {
const callback = (e) => {
return e.offsetParent != null ? e.offsetLeft + callback(e.offsetParent) : e.offsetLeft
}
return callback(this._el)
}
/**
* 获取元素的顶点坐标
*/
getTop() {
const callback = (e) => {
return e.offsetParent != null ? e.offsetTop + callback(e.offsetParent) : e.offsetTop
}
return callback(this._el)
}
/**
* 获取坐标包含4个点以及宽高
* @returns
*/
getPosition() {
return {
left: this.getLeft(),
top: this.getTop(),
}
}
/**
* 设置样式直接合并css的样式对象
* @param {*} option
*/
setStyle(option) {
Object.assign(this._el.style, option);
}
addUnit(val, unit = 'px') {
// return unit ? val + unit : val;
return val + unit;
}
/**
* 设置坐标
* @param {*} val 左坐标位置
* @param {*} unit 默认单位为px如果不用则需要传递null
*/
setLeft(val, unit = 'px') {
this.setStyle({ left: this.addUnit(val, unit) })
}
/**
* 设置顶坐标
* @param {*} val
* @param {*} unit
*/
setTop(val, unit = 'px') {
this.setStyle({ top: this.addUnit(val, unit) })
}
/**
* 设置宽度
* @param {*} val
* @param {*} unit
*/
setWidth(val, unit = 'px') {
this.setStyle({ width: this.addUnit(val, unit) })
}
setHeight(val, unit = 'px') {
this.setStyle({ height: this.addUnit(val, unit) })
}
setSize(width, height, unit = 'px') {
this.setStyle({
width: this.addUnit(width, unit),
height: this.addUnit(height, unit),
})
}
/**
* 设置元素位置
* @param {*} left
* @param {*} top
* @param {*} unit 单位默认为px
*/
setPosition(left, top, unit = 'px') {
this.setStyle({
left: this.addUnit(left, unit),
top: this.addUnit(top, unit)
})
}
/**
* 克隆元素
* @param {*} deep
* @returns
*/
clone(deep = true) {
let el = this._el.cloneNode(deep);//克隆元素
return new Dom(el);
}
/**
* 移除元素
*/
remove() {
this._el.remove();
}
/**
* 返回原本的dom元素
* @returns
*/
getElement() {
return this._el;
}
/**
* 获取封装一层的子元素
*/
childNodes() {
return [...this._el.childNodes].map(item => new Dom(item))
}
parentNode() {
return new Dom(this._el.parentNode)
}
firstChild() {
return new Dom(this._el.firstChild);
}
lastChild() {
return new Dom(this._el.lastChild)
}
nextSibling() {
return new Dom(this._el.nextSibling)
}
previousSibling() {
return new Dom(this._el.previousSibling)
}
/**
* 是否是标签元素
* @returns
*/
isElement() {
return this._el.nodeType == 1;
}
/**
* 获取第一个指定标签的元素
* @param {*} name 标签名字
*/
firstParentByName(name) {
name = name.toUpperCase();//转大写
const callback = (el) => {
let parent = el.parentNode;
if (parent.nodeName == 'BODY')
return null;
if (parent.nodeName == name)
return new Dom(parent)
return callback(parent);
}
return callback(this._el);
}
firstChildByName(name) {
name = name.toUpperCase();//转大写
const callback = (el) => {
let childs = el.childNodes;
if (childs.length == 0)
return null;
for (let i = 0; i < childs.length; i++) {
const item = childs[i];
if (item.nodeName == name)
return new Dom(item)
}
for (let i = 0; i < childs.length; i++) {
const item = childs[i];
if (item.childNodes.length > 0)
return callback(item)
}
return null;
}
return callback(this._el);
}
getNodeName() {
return this._el.nodeName;
}
/**
* 全选
*/
select() {
// console.log(this._el);
this._el.focus();
this._el.select();
}
}

View File

@@ -0,0 +1,113 @@
/**
* 获取元素坐标
* @param {*} params
*/
export function getRefPosition(e) {
return { left: getLeft(e), top: getTop(e) };
}
export function getTop(e) {
var offset = e.offsetTop;
if (e.offsetParent != null) offset += getTop(e.offsetParent);
return offset;
}
export function getLeft(e) {
var offset = e.offsetLeft;
if (e.offsetParent != null) offset += getLeft(e.offsetParent);
return offset;
}
export function getHeight(id) {
let dom = document.getElementById(id);
return dom.offsetHeight;
}
export function getWidth(id) {
let dom = document.getElementById(id);
return dom.offsetWidth;
}
export function getClientHeight(id) {
let dom = document.getElementById(id);
return dom.clientHeight;
}
export function getClientWidth(id) {
let dom = document.getElementById(id);
return dom.clientWidth;
}
export function getWindowsHeight(param) {
return document.body.clientHeight
}
export function getWindowsWidth(param) {
return document.body.clientWidth
}
/**
* 教程菜单位置,保证菜单出现的位置不超出不可见区域
* @param {*} e
* @param {*} menuId
* @returns
*/
export function correctMenuPosition(e, menuId) {
let winHeight = getWindowsHeight();
let height = getHeight(menuId);
let winWidth = getWindowsWidth();
let windth = getWidth(menuId);
let top = (height + e.clientY) > winHeight ? winHeight - height : e.clientY;
let left = (windth + e.clientX) > winWidth ? winWidth - windth : e.clientX;
return { top, left: left + 5 }
}
/**
* 设置id的元素选择
* @param {*} id
*/
export function setInputSelect(id, time = 0) {
let count = 0;
const callbcak = () => {
let dom = document.getElementById(id);
if (!dom) {
count += 1;
if (count > 50) return
return setTimeout(callbcak, 50);
}
dom.select();
dom.focus()
}
time == 0 ? callbcak() : setTimeout(callbcak, 50);
}
export function setFoucs(id, time = 0) {
setTimeout(() => {
let dom = document.getElementById(id);
dom.focus()
}, time);
}
/**
* 获取dom元素位置
* @param {*} e
* @returns
*/
export function getPosition(e) {
var evt = e || event;
return { x: evt.clientX, y: evt.clientY }
}
/**
* 根据id获取元素的位置
* @param {*} elementId
* @returns
*/
export function getPostionById(elementId) {
var el = document.getElementById(elementId);
return getRefPosition(el);
}

View File

@@ -0,0 +1,64 @@
/**
* 在指定节点前插入节点
* @param {*} newElement
* @param {*} targentElement
*/
export function insertBefore(newElement, targentElement) {
targentElement.parentNode.insertBefore(newElement, targentElement)
}
/**
* 在指定节点后插入节点
* @param {*} newNode
* @param {*} referenceNode
*/
export function insertAfter(newNode, referenceNode) {
referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
}
/**
* 获取元素上坐标
* @param {*} e
* @returns
*/
export function getTop(e) {
var result = e.offsetTop;
if (e.offsetParent != null)
result += getTop(e.offsetParent);
return result;
}
/**
* 获取元素左坐标
* @param {*} e
* @returns
*/
export function getLeft(e) {
var result = e.offsetLeft;
if (e.offsetParent != null)
result += getLeft(e.offsetParent);
return result;
}
/**
* 获取dom元素位置
* @param {*} e
* @returns
*/
export function getPosition(e) {
var evt = e || event;
return { x: evt.clientX, y: evt.clientY }
}
/**
* 设置dom元素的位置 left top
* @param {*} dom
* @param {*} x
* @param {*} y
* @param {*} unit 后缀单位默认px
*/
export function setPosition(dom, left, top, unit = 'px') {
dom.style.left = left + unit;
dom.style.top = top + unit;
}

View File

@@ -0,0 +1,174 @@
//引入操作dom元素的
import { insertBefore, insertAfter, getTop, getLeft, getPosition, setPosition } from './dom.js'
class Drap {
static status = {
downIndex: null,
upIndex: null,
node: null,
transparentNode: null,
time: null,
callback: null,
styleWidth: null,
styleHeight: null,
}
/**
* 鼠标松开
* @returns
*/
static onMouseUp() {
Drap.status.time && clearTimeout(Drap.status.time);
let obj = Object.assign({}, Drap.status); //浅复制一份
Drap.resetStatus(); //重置
if (obj.downIndex != null && obj.upIndex != null && obj.downIndex != obj.upIndex) {
Drap.status.callback && Drap.status.callback(obj);
Drap.status.callback = null;
}
document.removeEventListener('mouseup', DragFolder.onMouseUp);
document.removeEventListener('mousemove', DragFolder.onMouseMove);
return false;
}
/**
* 鼠标移动
* @param {*} e
*/
static onMouseMove(e) {
let res = getPosition(e);
setPosition(Drap.status.node, res.x, res.y);
}
/**
* 重置状态
* @returns
*/
static resetStatus() {
if (Drap.status.downIndex == null)
return;
Drap.status.downIndex = null;
Drap.status.upIndex = null;
Drap.status.upIndex = null;
Drap.status.time = null;
if (Drap.status.node != null) {
Drap.status.node.style.width = Drap.status.styleWidth;
Drap.status.node.style.height = Drap.status.styleHeight;
Drap.status.node.style.position = "static";
}
Drap.status.transparentNode && Drap.status.transparentNode.remove();
Drap.status.transparentNode = null;
}
constructor(el, option = {}) {
let config = {
direction: 'x', //默认配置 左右方向
callback: null,
}
Object.assign(config, option);
this.option = config;
this.el = el;
this.downTime = 300;
this.init();
}
/**
* 初始化
*/
init() {
// console.log(this.option);
this.el.onmousedown = (e) => {
this.onMouseDown(e)
return false
}
this.el.onmousemove = (e) => {
this.onMouseMove(e);
}
}
/**
* 鼠标按下事件
* @param {*} e
*/
onMouseDown(e) {
document.addEventListener('mouseup', Drap.onMouseUp);
Drap.status.time = setTimeout(() => {
Drap.status.downIndex = this.option.index;
Drap.status.node = this.el;
Drap.status.transparentNode = this.el.cloneNode(true); //克隆节点
Drap.status.callback = this.option.callback;
console.log(this.el.offsetWidth);
Drap.status.styleWidth = Drap.status.node.style.width;
Drap.status.styleHeight = Drap.status.node.style.height;
Drap.status.node.style.width = this.el.offsetWidth + "px";
Drap.status.node.style.height = this.el.offsetHeight + "px"
//处理节点显示
Drap.status.node.style.position = "fixed";
Drap.status.transparentNode.style.cssText += ";opacity: 0;"
insertBefore(Drap.status.transparentNode, this.el);
document.addEventListener('mousemove', Drap.onMouseMove);
}, this.downTime);
}
/**
* 鼠标移动事件
* @param {*} e
* @returns
*/
onMouseMove(e) {
if (Drap.status.downIndex == null)
return false;
let left = getLeft(this.el) + this.el.offsetWidth / 2;
let top = getTop(this.el) + this.el.offsetHeight / 2;
let obj = getPosition(e);
let min = Drap.status.downIndex < this.option.index;
//左右方向进行拖拽
Drap.status.upIndex = this.option.index;
if (this.option.direction == 'x') {
if (obj.x < left) {
// console.log('左边');
insertBefore(Drap.status.transparentNode, this.el)
} else {
// console.log('右边');
insertAfter(Drap.status.transparentNode, this.el)
Drap.status.upIndex += min ? 0 : 1; //按下大于松开的下标+1
}
return;
}
//上下方向拖拽
if (obj.y < top) {
// console.log('上边');
insertBefore(Drap.status.transparentNode, this.el);
} else {
// console.log('下边');
insertAfter(Drap.status.transparentNode, this.el);
Drap.status.upIndex += min ? 0 : 1; //按下大于松开的下标+1
}
}
}
//导出vue指令用到的对象
export const drag = {
mounted(el, binding) {
new Drap(el, binding.value || {})
}
}

View File

@@ -0,0 +1,203 @@
/**
* 实现拖拽文件到文件夹
*/
import { getPosition, setPosition } from './dom.js'
import { ipcSend } from "@/api/electronApi/ipc.js"
let g_time = null;
let g_div = null;
let g_foldersElement = [];//所有文件夹的dom元素
let g_selectId = 'g_selectId';
let g_moveCssTest = '';//css样式
let g_callback = null;
/**
* 开启拖拽
* @param {Array} folderIds 所有文件夹的id数组需要通过获取文件夹单个的id开启拖移入移出事件
* @param {String} imgPath 预览图片的路径地址
* @param {Int} number 拖动的数量
* @param {Function} endCallback 回调函数返回null || id
*/
export function startDragFileToFolder(e, folderIds, imgPath, number, endCallback, readyCallback) {
const downTime = 200;
// document.addEventListener('mouseup', mouseUp, true);//绑定鼠标松开事件
g_time = setTimeout(() => {
// console.log('dragFileToFolder=>绑定事件');
// readyCallback && readyCallback();//准备
// createPreviewElement(imgPath, number);
// mouseMove(e)
ipcSend('ondragstart', imgPath)
// addFolderEvent(folderIds);
// g_callback = endCallback;
// document.addEventListener('mousemove', mouseMove, true);//绑定鼠标松开事件
}, downTime);
}
// export function startDragFileToFolder(e, folderIds, imgPath, number, endCallback, readyCallback) {
// const downTime = 200;
// document.addEventListener('mouseup', mouseUp, true);//绑定鼠标松开事件
// g_time = setTimeout(() => {
// console.log('dragFileToFolder=>绑定事件');
// readyCallback && readyCallback();//准备
// createPreviewElement(imgPath, number);
// // mouseMove(e)
// ipcSend('ondragstart',imgPath)
// addFolderEvent(folderIds);
// g_callback = endCallback;
// document.addEventListener('mousemove', mouseMove, true);//绑定鼠标松开事件
// }, downTime);
// }
/**
* 鼠标拖动的处理事件
* @param {*} params
*/
function mouseMove(e) {
console.log('dragFileToFolder=>拖动');
let res = getPosition(e);
setPosition(g_div, res.x + 4, res.y + 4);
}
/**
* 松开鼠标的处理事件
*/
function mouseUp() {
if (g_time)
clearTimeout(g_time);
clearPreviewElement();//清除节点
removeFolderEvent();//清除所有文件夹元素的事件
document.removeEventListener('mouseup', mouseUp, true);
document.removeEventListener('mousemove', mouseMove, true);
console.log('dragFileToFolder=>取消绑定');
if (g_selectId) {
console.log('最终id:', g_selectId);
if (g_selectId.style)
g_selectId.style.cssText = g_moveCssTest;
const idText = '#leftFolderItem';
if (g_selectId.id)
g_callback && g_callback(g_selectId.id.replace(idText, ''))
g_selectId = null;
}
g_callback = null;
}
/**
* 创建预览元素
*/
function createPreviewElement(imgPath, number) {
if (g_div != null)
return;
g_div = document.createElement('div');
let img = document.createElement('img');
img.src = imgPath;
g_div.appendChild(img);
Object.assign(img.style, {
maxWidth: '100px',
maxHeight: '100px',
border: '2px solid #2a98ff',
borderRadius: '4px'
})
Object.assign(g_div.style, {
position: 'fixed',
zIndex: 10011,
left: '-1000px',
})
g_div.appendChild(createTextElement(number));
document.body.appendChild(g_div);
}
/**
* 创建文件数量的样式
* @param {*} number
* @returns
*/
function createTextElement(number) {
let dom = document.createElement('div');
dom.innerHTML = number;
let style = {
width: '24px',
height: '24px',
lineHeight: '24px',
borderRadius: '100%',
background: "#D84A4A",
color: '#fff',
fontSize: '12px',
position: "absolute",
top: '-12px',
right: '-12px',
boxShadow: '0px 0px 10px rgba(0, 0, 0, 0.3)',
textAlign: 'center'
}
Object.assign(dom.style, style);
return dom;
}
/**
* 清除创建的元素
*/
function clearPreviewElement() {
if (g_div) {
g_div.remove();
g_div = null;
}
}
function mouseover(e) {
console.log('移入', e);
let el = e.target;
if (el.id == '')
el = el.parentNode;
g_selectId = el;
g_moveCssTest = el.style.cssText
el.style.border = '1px solid #2a98ff';
el.style.background = 'rgba(42,152,255,0.2)';
}
function mouseout(e) {
let el = e.target;
if (el.id == '') {
el = el.parentNode;
}
console.log('移出', el);
g_selectId = null;
// let el = e.target;
el.style.cssText = g_moveCssTest;
}
/**
* 给文件夹id添加关联事件
* @param {Array} ids 所有文件夹id
*/
function addFolderEvent(ids) {
const idText = '#leftFolderItem';
g_foldersElement = ids.map(id => document.getElementById(idText + id)).filter(el => el)
g_foldersElement.forEach(el => {
el.addEventListener('mouseover', mouseover);//鼠标移入
el.addEventListener('mouseout', mouseout);//鼠标移出事件
})
}
function removeFolderEvent(params) {
if (g_foldersElement.length > 0) {
g_foldersElement.forEach(el => {
el.removeEventListener('mouseover', mouseover);//鼠标移入
el.removeEventListener('mouseout', mouseout);//鼠标移出事件
})
g_foldersElement.splice(0, g_foldersElement.length);
}
}

View File

@@ -0,0 +1,68 @@
/**
* 拖拽文件时,创建一个临时的预览图
*/
import { nodeFs, getOsPrefix } from "@/utils/nodeFs"
import g_config from "@/api/config/index";
/**
* 通过canvas将svg转成png
* @param {*} path 路径
* @returns 返回base64字符串
*/
export function dragFile2png(path, number) {
// debugger
let outPath = g_config.path.dragPreview;
console.log(path, outPath);
return new Promise((resolve, reject) => {
var img = new Image();
img.src = getOsPrefix() + path;
img.onload = () => {
var canvas = document.createElement('canvas');
var c = canvas.getContext('2d');
let width, height;
let scale = img.width / 100;
if (img.width > img.height) {
width = 100;
height = img.height / scale;
} else {
scale = img.height / 100;
height = 100;
width = img.width / scale;
}
canvas.width = width + 20;
canvas.height = height + 10;
//canvas画图片
c.drawImage(img, 10, 10, width, height);
c.strokeStyle = "#2961d9";
c.lineWidth = 2;
c.strokeRect(10, 10, width, height);
if (number > 1) {
c.beginPath();
c.arc(width + 10, 10, 10, 0, Math.PI * 2, true);
c.closePath();
c.fillStyle = "rgba(255,0,0,1)";
c.fill();
c.fillStyle = "#fff";//文字的颜色
c.textAlign = 'center';//对齐方式
// c.font = '13px "微软雅黑"';
c.fillText(number, width + 10, 13);
}
//将图片添加到body中
// console.log('转png', canvas.toDataURL('image/png'));
let base64 = canvas.toDataURL('image/png')
let temp = nodeFs.sync.writeBase64(outPath, base64.split('base64,')[1]);
temp.err == 1 ? reject(temp.msg) : resolve({ outPath });
}
img.onerror = (err) => {
reject(err);//转换失败
}
})
}

View File

@@ -0,0 +1,230 @@
//引入操作dom元素的
import { insertBefore, getTop, getPosition, setPosition } from './dom.js'
export class DragFolder {
static status = {
downIndex: null,
upIndex: null,
node: null,
transparentNode: null,
moveNode: null,
moveCssTest: null,
time: null,
callback: null,
cssText: null,
position: null,
multipleChoice: null,//多选样式节点
}
/**
* 鼠标松开
* @returns
*/
static onMouseUp() {
DragFolder.status.time && clearTimeout(DragFolder.status.time);
if (DragFolder.status.moveNode != null) {
DragFolder.status.moveNode.style.cssText = DragFolder.status.moveCssTest;
DragFolder.status.moveNode = null;
}
// let obj = Object.assign({}, DragFolder.status); //浅复制一份
let downIndex = DragFolder.status.downIndex;
let upIndex = DragFolder.status.upIndex;
let position = DragFolder.status.position;
DragFolder.resetStatus(); //重置
console.log('拖拽松开');
if (downIndex != null && upIndex != null && downIndex != upIndex) {
DragFolder.status.callback && DragFolder.status.callback({ downIndex, upIndex, position });
DragFolder.status.callback = null;
}
document.removeEventListener('mouseup', DragFolder.onMouseUp);
document.removeEventListener('mousemove', DragFolder.onMouseMove);
return false;
}
/**
* 鼠标移动
* @param {*} e
*/
static onMouseMove(e) {
console.log('移动');
let res = getPosition(e);
setPosition(DragFolder.status.node, res.x + 4, res.y + 4);
}
/**
* 重置状态
* @returns
*/
static resetStatus() {
if (DragFolder.status.downIndex == null)
return;
DragFolder.status.downIndex = null;
DragFolder.status.upIndex = null;
DragFolder.status.upIndex = null;
DragFolder.status.time = null;
if (DragFolder.status.node != null) {
DragFolder.status.node.style.cssText = DragFolder.status.cssText;
DragFolder.status.node.style.position = "static";
}
if (DragFolder.status.transparentNode != null) {
DragFolder.status.transparentNode.remove();
DragFolder.status.transparentNode = null;
}
if (DragFolder.status.multipleChoice != null) {
DragFolder.status.multipleChoice.remove();
DragFolder.status.multipleChoice = null;
}
}
constructor(el, option = {}) {
let config = {
direction: 'x', //默认配置 左右方向
callback: null,
}
Object.assign(config, option);
this.option = config;
this.el = typeof el == 'string' ? document.getElementById(el) : el;
// console.log(this.el);
this.downTime = 300;
this.init();
}
/**
* 初始化
*/
init() {
// console.log(this.option);
// debugger
this.el.onmousedown = (e) => {
// debugger
this.onMouseDown(e)
// return false
}
this.el.onmousemove = (e) => {
this.onMouseMove(e);
}
// console.log('folder初始化', this.el,this.el.onmousedown);
}
/**
* 鼠标按下事件
* @param {*} e
*/
onMouseDown(e) {
document.addEventListener('mouseup', DragFolder.onMouseUp, true);
DragFolder.status.time = setTimeout(() => {
DragFolder.status.downIndex = this.option.index;
DragFolder.status.callback = this.option.callback;
if (this.option.downCallback) {
// debugger
let temp = this.option.downCallback();
let select = temp.indexOf(this.option.index) > -1 ? temp : [this.option.index];
console.log(this.option.index,temp,select)
//创建数量的提醒
if (select.length > 1) {
DragFolder.status.multipleChoice = document.createElement('div');
DragFolder.status.multipleChoice.innerHTML = select.length;
let style = {
width: '24px',
height: '24px',
lineHeight: '24px',
borderRadius: '100%',
background: "#D84A4A",
color: '#fff',
fontSize: '12px',
position: "absolute",
top: '-12px',
right: '-12px',
boxShadow: '0px 0px 10px rgba(0, 0, 0, 0.3)',
textAlign: 'center'
}
Object.assign(DragFolder.status.multipleChoice.style, style)
}
console.error('按下回调', select);
}
DragFolder.status.node = this.el;
DragFolder.status.transparentNode = this.el.cloneNode(true); //克隆节点
// DragFolder.status.node = this.el.cloneNode(true); //克隆节点
console.log(this.el.offsetWidth);
DragFolder.status.cssText = DragFolder.status.node.style.cssText;
DragFolder.status.node.style.width = this.el.offsetWidth + 4 + "px";
DragFolder.status.node.style.height = this.el.offsetHeight + 4 + "px"
//处理节点显示
DragFolder.status.node.style.position = "fixed";
DragFolder.status.node.style.background = "#2a98ff";
DragFolder.status.node.style.zIndex = "101";
DragFolder.status.transparentNode.style.cssText += ";border: 1px solid #797979"
DragFolder.status.multipleChoice && DragFolder.status.node.appendChild(DragFolder.status.multipleChoice)
insertBefore(DragFolder.status.transparentNode, this.el);
document.addEventListener('mousemove', DragFolder.onMouseMove);
}, this.downTime);
}
/**
* 鼠标移动事件
* @param {*} e
* @returns
*/
onMouseMove(e) {
if (DragFolder.status.downIndex == null)
return false;
if (DragFolder.status.moveNode != null) {
DragFolder.status.moveNode.style.cssText = DragFolder.status.moveCssTest;
DragFolder.status.moveNode = null;
}
DragFolder.status.moveNode = this.el;
DragFolder.status.moveCssTest = this.el.style.cssText;
// let elTop = getTop(this.el)
let elTop = this.el.getBoundingClientRect().top
let top = elTop + this.el.offsetHeight / 10 * 3;
let bottom = elTop + this.el.offsetHeight / 10 * 7;
let obj = getPosition(e);
//左右方向进行拖拽
DragFolder.status.upIndex = this.option.index;
//上下方向拖拽
if (obj.y < top) {
this.el.style.borderTop = '1px solid #2a98ff';
DragFolder.status.position = 'top'
} else if (obj.y > bottom) {
this.el.style.borderBottom = '1px solid #2a98ff'
DragFolder.status.position = 'bottom'
} else {
DragFolder.status.position = 'center'
this.el.style.border = '1px solid #2a98ff';
this.el.style.background = 'rgba(42,152,255,0.2)';
}
}
}
export default DragFolder;

View File

@@ -0,0 +1,189 @@
//引入操作dom元素的
import { insertBefore, getTop, getPosition, setPosition } from './dom.js'
interface IOption {
index?:string,
callback?:Function,
downCallback?: Function,
}
export class DragTag {
public el: HTMLElement
public option: IOption
public downTime: number
static status = {
node: null,
transparentNode: null,
moveNode: null,
moveCssTest: null,
time: null,
callback: null,
cssText: null,
position: null,
multipleChoice: null,//多选样式节点
option: null
}
constructor(el, option = {}) {
let config = {
direction: 'x', //默认配置 左右方向
callback: null,
}
Object.assign(config, option);
this.option = config;
this.el = typeof el == 'string' ? document.getElementById(el) : el;
// console.log(this.el);
this.downTime = 300;
this.init();
}
/**
* 鼠标松开
* @returns
*/
static onMouseUp() {
DragTag.status.time && clearTimeout(DragTag.status.time);
if (DragTag.status.moveNode != null) {
DragTag.status.moveNode.style.cssText = DragTag.status.moveCssTest;
DragTag.status.moveNode = null;
}
DragTag.resetStatus(); //重置
console.log('拖拽松开');
DragTag.status.callback && DragTag.status.callback();
document.removeEventListener('mouseup', DragTag.onMouseUp);
document.removeEventListener('mousemove', DragTag.onMouseMove);
return false;
}
/**
* 鼠标移动
* @param {*} e
*/
static onMouseMove(e) {
console.log('移动');
let res = getPosition(e);
setPosition(DragTag.status.node, res.x + 4, res.y + 4);
}
/**
* 重置状态
* @returns
*/
static resetStatus() {
DragTag.status.time = null;
if (DragTag.status.node != null) {
DragTag.status.node.style.cssText = DragTag.status.cssText;
DragTag.status.node.style.position = "static";
}
if (DragTag.status.transparentNode != null) {
DragTag.status.transparentNode.remove();
DragTag.status.transparentNode = null;
}
if (DragTag.status.multipleChoice != null) {
DragTag.status.multipleChoice.remove();
DragTag.status.multipleChoice = null;
}
}
/**
* 初始化
*/
init() {
// console.log('初始化',this.el)
this.el.onmousedown = (e) => {
this.onMouseDown(e)
}
this.el.onmousemove = (e) => {
this.onMouseMove(e);
}
}
/**
* 鼠标按下事件
* @param {*} e
*/
onMouseDown(e) {
e.preventDefault();
e.stopPropagation();
if (e.button != 0) return
console.log('按下', e.button)
document.addEventListener('mouseup', DragTag.onMouseUp);
DragTag.status.time = setTimeout(() => {
DragTag.status.callback = this.option.callback;
if (this.option.downCallback) {
// debugger
let temp = this.option.downCallback(this.option.index);
let select = temp.indexOf(this.option.index) > -1 ? temp : [this.option.index];
console.log(this.option.index, select)
//创建数量的提醒
if (select.length > 1) {
DragTag.status.multipleChoice = document.createElement('div');
DragTag.status.multipleChoice.innerHTML = select.length;
let style = {
width: '14px',
height: '14px',
lineHeight: '14px',
borderRadius: '100%',
background: "#6450FF",
color: '#fff',
fontSize: '10px',
position: "absolute",
top: '-10px',
right: '0px',
boxShadow: '0px 0px 10px rgba(0, 0, 0, 0.3)',
textAlign: 'center'
}
Object.assign(DragTag.status.multipleChoice.style, style)
}
// console.error('按下回调',select);
}
DragTag.status.node = this.el;
DragTag.status.transparentNode = this.el.cloneNode(true); //克隆节点
// DragTag.status.node = this.el.cloneNode(true); //克隆节点
DragTag.status.cssText = DragTag.status.node.style.cssText;
DragTag.status.node.style.width = this.el.offsetWidth + 4 + "px";
DragTag.status.node.style.height = this.el.offsetHeight + 4 + "px"
//处理节点显示
DragTag.status.node.style.position = "fixed";
DragTag.status.node.style.background = "#8070FF";
DragTag.status.node.style.zIndex = "101";
let res = getPosition(e);
setPosition(DragTag.status.node, res.x + 4, res.y + 4);
DragTag.status.multipleChoice && DragTag.status.node.appendChild(DragTag.status.multipleChoice)
insertBefore(DragTag.status.transparentNode, this.el);
document.addEventListener('mousemove', DragTag.onMouseMove);
}, this.downTime);
}
/**
* 鼠标移动事件
* @param {*} e
* @returns
*/
onMouseMove(e) {
}
}
export default DragTag;

View File

@@ -0,0 +1 @@
*.js linguist-detectable=false

View File

@@ -0,0 +1,3 @@
export function join(...arr: string[]) {
return arr.join('/')
}

View File

@@ -0,0 +1,97 @@
import { fs, path } from "../cep/node";
import { csi } from "./bolt";
const getLatestFile = (dir: string, suffix: string): string | null => {
const getModified = (filePath: string) =>
fs.statSync(filePath).mtime.valueOf();
let latestFile: string | null = null;
fs.readdirSync(dir)
.filter((file) => file.includes(suffix))
.map((file) => {
if (
latestFile === null ||
getModified(path.join(dir, file)) >
getModified(path.join(dir, latestFile))
) {
latestFile = file;
}
});
return latestFile;
};
export const getPrefsDir = (): string => {
const appVersion = csi.getHostEnvironment().appVersion;
const { platform, env } = window.cep_node.process;
const mainDir =
platform == "darwin"
? `${env.HOME}/Library/Preferences`
: env.APPDATA || "";
const prefsDir = path.join(
mainDir,
"Adobe",
"After Effects",
parseFloat(appVersion).toFixed(1).toString()
);
return prefsDir;
};
export const getOutputModules = (): string[] => {
const prefsDir = getPrefsDir();
const prefsSuffix = "indep-output.txt";
const outputPref = getLatestFile(prefsDir, prefsSuffix);
if (outputPref) {
const txt = fs.readFileSync(path.join(prefsDir, outputPref), {
encoding: "utf-8",
});
const matches = txt.match(
/\"Output Module Spec Strings Name .* = \".*.\"/g
);
if (matches) {
let outputModules: string[] = [];
matches.map((line) => {
const str = line.split("=").pop()?.trim().replace(/"/g, "");
if (str && !str.includes("_HIDDEN X-Factor")) {
outputModules.push(str);
}
});
return outputModules;
}
}
return [];
};
export const getRenderSettingsList = (): string[] => {
const prefsDir = getPrefsDir();
const prefsSuffix = "indep-render.txt";
const renderPref = getLatestFile(prefsDir, prefsSuffix);
if (renderPref) {
const txt = fs.readFileSync(path.join(prefsDir, renderPref), {
encoding: "utf-8",
});
const lines = txt.match(/[^\r\n]+/g);
if (lines) {
const firstLine = lines.findIndex((line) =>
line.includes("Render Settings List")
);
const lastLine = lines.findIndex((line) =>
line.includes("Still Frame RS Index")
);
const settingBlock = lines
.slice(firstLine, lastLine)
.join("")
.trim()
.replace(/^.*\=/g, "")
.replace(/\t/g, "")
.replace(/\\/g, "")
.replace(/\"\"/g, "");
let renderSettings: string[] = [];
settingBlock.match(/\".*?\"/g)?.map((str) => {
if (str && !str.includes("_HIDDEN X-Factor")) {
renderSettings.push(str.replace(/\"/g, ""));
}
});
return renderSettings;
}
}
return [];
};

View File

@@ -0,0 +1,253 @@
import CSInterface from "../cep/csinterface";
import Vulcan, { VulcanMessage } from "../cep/vulcan";
import { ns } from "../../../shared/shared";
import { fs } from "../cep/node";
export const csi = new CSInterface();
export const vulcan = new Vulcan();
// jsx utils
/**
* @function EvalES
* Evaluates a string in ExtendScript scoped to the project's namespace
* Optionally, pass true to the isGlobal param to avoid scoping
*
* @param script The script as a string to be evaluated
* @param isGlobal Optional. Defaults to false,
*
* @return String Result.
*/
export const evalES = (script: string, isGlobal = false): Promise<string> => {
return new Promise(function (resolve, reject) {
const pre = isGlobal
? ""
: `var host = typeof $ !== 'undefined' ? $ : window; host["${ns}"].`;
const fullString = pre + script;
csi.evalScript(
"try{" + fullString + "}catch(e){alert(e);}",
(res: string) => {
resolve(res);
}
);
});
};
import type { Scripts } from "@esTypes/index";
type ArgTypes<F extends Function> = F extends (...args: infer A) => any
? A
: never;
type ReturnType<F extends Function> = F extends (...args: infer A) => infer B
? B
: never;
/**
* @description End-to-end type-safe ExtendScript evaluation with error handling
* Call ExtendScript functions from CEP with type-safe parameters and return types.
* Any ExtendScript errors are captured and logged to the CEP console for tracing
*
* @param functionName The name of the function to be evaluated.
* @param args the list of arguments taken by the function.
*
* @return Promise resolving to function native return type.
*
* @example
* // CEP
* evalTS("myFunc", 60, 'test').then((res) => {
* console.log(res.word);
* });
*
* // ExtendScript
* export const myFunc = (num: number, word: string) => {
* return { num, word };
* }
*
*/
export const evalTS = <
Key extends string & keyof Scripts,
Func extends Function & Scripts[Key]
>(
functionName: Key,
...args: ArgTypes<Func>
): Promise<ReturnType<Func>> => {
return new Promise(function (resolve, reject) {
const formattedArgs = args
.map((arg) => {
console.log(JSON.stringify(arg));
return `${JSON.stringify(arg)}`;
})
.join(",");
csi.evalScript(
`try{
var host = typeof $ !== 'undefined' ? $ : window;
var res = host["${ns}"].${functionName}(${formattedArgs});
JSON.stringify(res);
}catch(e){
e.fileName = new File(e.fileName).fsName;
JSON.stringify(e);
}`,
(res: string) => {
try {
//@ts-ignore
if (res === "undefined") return resolve();
const parsed = JSON.parse(res);
if (parsed.name === "ReferenceError") {
console.error("REFERENCE ERROR");
reject(parsed);
} else {
resolve(parsed);
}
} catch (error) {
reject(res);
}
}
);
});
};
export const evalFile = (file: string) => {
return evalES(
"typeof $ !== 'undefined' ? $.evalFile(\"" +
file +
'") : fl.runScript(FLfile.platformPathToURI("' +
file +
'"));',
true
);
};
// js utils
export const initBolt = (log = true) => {
if (window.cep) {
const extRoot = csi.getSystemPath("extension");
const jsxSrc = `${extRoot}/jsx/index.js`;
const jsxBinSrc = `${extRoot}/jsx/index.jsxbin`;
if (fs.existsSync(jsxSrc)) {
if (log) console.log(jsxSrc);
evalFile(jsxSrc);
} else if (fs.existsSync(jsxBinSrc)) {
if (log) console.log(jsxBinSrc);
evalFile(jsxBinSrc);
}
}
};
export const posix = (str: string) => str.replace(/\\/g, "/");
export const openLinkInBrowser = (url: string) => {
if (window.cep) {
csi.openURLInDefaultBrowser(url);
} else {
location.href = url;
}
};
export const getAppBackgroundColor = () => {
const { green, blue, red } = JSON.parse(
window.__adobe_cep__.getHostEnvironment() as string
).appSkinInfo.panelBackgroundColor.color;
return {
rgb: {
r: red,
g: green,
b: blue,
},
hex: `#${red.toString(16)}${green.toString(16)}${blue.toString(16)}`,
};
};
export const subscribeBackgroundColor = (callback: (color: string) => void) => {
const getColor = () => {
const newColor = getAppBackgroundColor();
console.log("BG Color Updated: ", { rgb: newColor.rgb });
const { r, g, b } = newColor.rgb;
return `rgb(${r}, ${g}, ${b})`;
};
// get current color
callback(getColor());
// listen for changes
csi.addEventListener(
"com.adobe.csxs.events.ThemeColorChanged",
() => callback(getColor()),
{}
);
};
// vulcan
declare type IVulcanMessageObject = {
event: string;
callbackID?: string;
data?: string | null;
payload?: object;
};
export const vulcanSend = (id: string, msgObj: IVulcanMessageObject) => {
const msg = new VulcanMessage(VulcanMessage.TYPE_PREFIX + id, null, null);
const msgStr = JSON.stringify(msgObj);
msg.setPayload(msgStr);
vulcan.dispatchMessage(msg);
};
export const vulcanListen = (id: string, callback: Function) => {
vulcan.addMessageListener(
VulcanMessage.TYPE_PREFIX + id,
(res: any) => {
var msgStr = vulcan.getPayload(res);
const msgObj = JSON.parse(msgStr);
callback(msgObj);
},
null
);
};
export const isAppRunning = (targetSpecifier: string) => {
const { major, minor, micro } = csi.getCurrentApiVersion();
const version = parseFloat(`${major}.${minor}`);
if (version >= 11.2) {
return vulcan.isAppRunningEx(targetSpecifier.toUpperCase());
} else {
return vulcan.isAppRunning(targetSpecifier);
}
};
interface IOpenDialogResult {
data: string[];
}
export const selectFolder = (
dir: string,
msg: string,
callback: (res: string) => void
) => {
const result = window.cep.fs.showOpenDialog(
false,
true,
msg,
dir
) as IOpenDialogResult;
if (result.data?.length > 0) {
const folder = decodeURIComponent(result.data[0].replace("file://", ""));
callback(folder);
}
};
export const selectFile = (
dir: string,
msg: string,
callback: (res: string) => void
) => {
const result = window.cep.fs.showOpenDialog(
false,
false,
msg,
dir
) as IOpenDialogResult;
if (result.data?.length > 0) {
const folder = decodeURIComponent(result.data[0].replace("file://", ""));
callback(folder);
}
};

View File

@@ -0,0 +1,28 @@
import { csi } from "./bolt";
/**
* Register all possible keyboard shortcuts on Mac and Windows for you CEP Panel
* Warning: Note that certain keys will not work per OS regardless of registration
*/
export const keyRegisterOverride = () => {
const platform = navigator.platform.substring(0, 3);
let maxKey = 0;
if (platform === "Mac") maxKey = 126; // Mac Max Key Code
else if (platform === "Win") maxKey = 222; // HTML Max Key Code
let allKeys = [];
for (let k = 0; k <= maxKey; k++) {
for (let j = 0; j <= 15; j++) {
const guide = (j >>> 0).toString(2).padStart(4, "0");
allKeys.push({
keyCode: k,
ctrlKey: guide[0] === "1",
altKey: guide[1] === "1",
shiftKey: guide[2] === "1",
metaKey: guide[3] === "1",
});
}
}
const keyRes = csi.registerKeyEventsInterest(JSON.stringify(allKeys));
console.log("Key Events Registered Completed: " + keyRes);
};

View File

@@ -0,0 +1,175 @@
import { fs, os, path } from "../cep/node";
import { csi } from "./bolt";
const readDirSafe = (dir: string) =>
fs.existsSync(dir) ? fs.readdirSync(dir) : [];
export const getAllLuts = (): { creative: string[]; technical: string[] } => {
const isWin = os.platform() === "win32";
const appPath = path.dirname(csi.getSystemPath("hostApplication"));
const appLutsDir = path.join(
isWin ? appPath : path.dirname(appPath),
"Lumetri",
"LUTs"
);
const winLocal = path.join(
os.homedir(),
"AppData",
"Roaming",
"Adobe",
"Common",
"LUTs"
);
const winGlobal = path.join("C:", "Program Files", "Adobe", "Common", "LUTs");
const macLocal = path.join(
os.homedir(),
"Library",
"Application Support",
"Adobe",
"Common",
"LUTs"
);
const macGlobal = path.join(
"Library",
"Application Support",
"Adobe",
"Common",
"LUTs"
);
const appCreative = path.join(appLutsDir, "Creative");
const appTechnical = path.join(appLutsDir, "Technical");
const localCreative = isWin
? path.join(winLocal, "Creative")
: path.join(macLocal, "Creative");
const localTechnical = isWin
? path.join(winLocal, "Technical")
: path.join(macLocal, "Technical");
const globalCreative = isWin
? path.join(winGlobal, "Creative")
: path.join(macGlobal, "Creative");
const globalTechnical = isWin
? path.join(winGlobal, "Technical")
: path.join(macGlobal, "Technical");
const appCreativeLuts = readDirSafe(appCreative);
const appTechnicalLuts = readDirSafe(appTechnical);
const localCreativeLuts = readDirSafe(localCreative);
const localTechnicalLuts = readDirSafe(localTechnical);
const globalCreativeLuts = readDirSafe(globalCreative);
const globalTechnicalLuts = readDirSafe(globalTechnical);
const creative = [
...appCreativeLuts,
...localCreativeLuts,
...globalCreativeLuts,
]
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))
.map((lut) => path.basename(lut, path.extname(lut)));
const technical = [
...appTechnicalLuts,
...localTechnicalLuts,
...globalTechnicalLuts,
]
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))
.map((lut) => path.basename(lut, path.extname(lut)));
return { creative, technical };
};
export const allowedImportFiles: string[] = [
"264",
"3g2",
"3gp",
"3gpp",
"aac",
"aaf",
"ac3",
"ai",
"aif",
"aiff",
"ari",
"asf",
"asnd",
"asx",
"avc",
"avi",
"bmp",
"bwf",
"cin",
"cine",
"crm",
"dfxp",
"dib",
"dif",
"dng",
"dpx",
"dv",
"eps",
"exr",
"f4v",
"f4v",
"fli",
"gif",
"icb",
"ico",
"jfif",
"jpe",
"jpeg",
"jpg",
"m15",
"m1a",
"m1s",
"m1v",
"m2a",
"m2p",
"m2t",
"m2ts",
"m2v",
"m4a",
"m4v",
"m75",
"mcc",
"m0d",
"mov",
"mp2",
"mp3",
"mp4",
"mpa",
"mpe",
"mpeg",
"mpg",
"mpg4",
"mpm",
"mpv",
"mts",
"mxf",
"mxv",
"mxr",
"pct",
"pict",
"png",
"prt",
"ptl",
"qt",
"r3d",
"rle",
"rmf",
"scc",
"srt",
"stl",
"sxr",
"tga",
"tif",
"tiff",
"ts",
"vda",
"vob",
"vst",
"wav",
"wma",
"wmv",
"psd",
];

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

35
AdminPanel/src/App.vue Normal file
View File

@@ -0,0 +1,35 @@
<template>
<div class="app-container">
<router-view />
</div>
</template>
<script setup lang="ts">
import { useTheme } from '@/hooks/useTheme'
// 初始化主题
const { isDark } = useTheme()
</script>
<style>
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
/* 使用 PS 主题色 */
background-color: var(--ps-bg, #323232);
color: var(--ps-text, #e0e0e0);
}
.app-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
background-color: var(--ps-bg, #323232);
color: var(--ps-text, #e0e0e0);
}
</style>

View File

@@ -0,0 +1,40 @@
import { ref, onMounted, onUnmounted } from 'vue';
import { initThemeListener } from '@/utils/theme';
export function useTheme() {
const isDark = ref(true);
// 简易的判断当前是否是深色主题的逻辑
// 实际逻辑由 utils/theme.ts 中的 updateTheme 处理,它会更新 body 的 arco-theme 属性
const checkTheme = () => {
isDark.value = document.body.getAttribute('arco-theme') === 'dark';
};
onMounted(() => {
// 初始化并开始监听
initThemeListener();
// 初始检查
checkTheme();
// 监听 body 属性变化 (以便在 theme.ts 更新 body 属性时我们能感知到)
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes' && mutation.attributeName === 'arco-theme') {
checkTheme();
}
});
});
observer.observe(document.body, { attributes: true });
// Cleanup
onUnmounted(() => {
observer.disconnect();
});
});
return {
isDark
};
}

20
AdminPanel/src/main.ts Normal file
View File

@@ -0,0 +1,20 @@
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import ArcoVue from '@arco-design/web-vue'
import '@arco-design/web-vue/dist/arco.css'
import './style/index'
import { initThemeListener } from './utils/theme'
const app = createApp(App)
app.use(ArcoVue)
app.use(router)
app.mount('#app')
// 初始化 PS 主题监听(自动跟随 PS 主题变换颜色)
initThemeListener()
console.log('🎨 Designer Admin Panel 已启动')

View File

@@ -0,0 +1,22 @@
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
const routes: RouteRecordRaw[] = [
{
path: '/',
name: 'Home',
component: () => import('@/view/Home.vue')
},
{
path: '/iframe',
name: 'IframePage',
component: () => import('@/view/IframePage.vue')
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router

View File

@@ -0,0 +1,130 @@
/* flex 布局的定义
========================================================================== */
/* 基础样式 , 从左到右,不压缩子元素,超出则换行排列 */
.f-base {
display: flex;
flex-flow: row wrap;
align-content: flex-start;
}
/* 基础样式,垂直居中,从左到右排序 会压缩子元素*/
.f-baseX {
display: flex;
align-items: center;
}
/* 基础样式,从上到下排序元素 */
.f-baseY {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
align-items: center;
}
.f-baseLR {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.f-arrayX {
display: flex;
flex-direction: row;
}
/* 横向排列 */
.f-arrayY {
display: flex;
display: -webkit-flex;
flex-direction: column;
}
.f-alignNone {
display: flex;
display: -webkit-flex;
justify-content: space-between;
}
/* 上下左右居中 */
.f-center {
display: flex;
justify-content: center;
align-items: center;
}
/* 不压缩所有子元素 */
.f-allFull {
flex-flow: row wrap;
align-content: center;
}
/* 水平对齐方向 向左对齐 */
.f-levelL {
justify-content: flex-start;
}
/* 水平对齐方向 居中对齐 */
.f-levelC {
justify-content: center;
}
/* 水平对齐方向 向右对齐 */
.f-levelR {
justify-content: flex-end;
}
/* 控制垂直对齐方式 上对齐 */
.f-verticalT {
align-items: flex-start;
}
/* 控制垂直对齐方式 居中 */
.f-verticalC {
display: flex;
align-items: center;
}
/* 控制垂直对齐方式 下对齐 */
.f-verticalB {
display: flex;
align-items: flex-end;
}
/* 这个用在子元素,用于分配剩余空间 */
.f-space {
flex-grow: 1;
}
/* 不压缩子元素,既保留元素的完整大小,应用在元素身上 */
.f-full {
flex-shrink: 0;
}

View File

@@ -0,0 +1,228 @@
/* 基础样式 , 从左到右,不压缩子元素,超出不换行*/
.f-lr {
display: flex;
align-content: flex-start;
> * {
flex-shrink: 0;
}
}
/* 基础样式 , 从右到左,不压缩子元素,超出不换行*/
.f-rl {
display: flex;
align-content: flex-start;
flex-direction: row-reverse;
> * {
flex-shrink: 0;
}
}
/* 从左到右,上下居中,超出不换行*/
.f-lr-center {
display: flex;
align-items: center;
> * {
flex-shrink: 0;
}
}
/* 从右到左,上下居中,超出不换行*/
.f-rl-center {
display: flex;
align-items: center;
flex-direction: row-reverse;
> * {
flex-shrink: 0;
}
}
/* 基础样式 , 从左到右,不压缩子元素,超出则自动换行 */
.f-lr-wrap {
display: flex;
align-content: flex-start;
flex-flow: row wrap;
> * {
flex-shrink: 0;
}
}
/* 基础样式 , 从右到左,不压缩子元素,超出则自动换行 */
.f-rl-wrap {
display: flex;
align-content: flex-start;
flex-flow: row-reverse wrap;
> * {
flex-shrink: 0;
}
}
/*横向左右均匀分布 不压缩子元素 (元素间距离平均分配)*/
.f-lr-evenly {
display: flex;
flex-direction: row;
justify-content: space-evenly;
> * {
flex-shrink: 0;
}
}
/*横向左右均匀分布 从右到左 不压缩子元素 (元素间距离平均分配)*/
.f-rl-evenly {
display: flex;
flex-direction: row;
justify-content: space-evenly;
flex-direction: row-reverse;
> * {
flex-shrink: 0;
}
}
/*左中右分布 左到右 (第一个元素靠起点,最后一个元素靠终点,余下元素平均分配空间)*/
.f-lr-between {
display: flex;
flex-direction: row;
justify-content: space-between;
> * {
flex-shrink: 0;
}
}
/*左中右分布 右到左 (第一个元素靠起点,最后一个元素靠终点,余下元素平均分配空间)*/
.f-rl-between {
display: flex;
flex-direction: row;
justify-content: space-between;
flex-direction: row-reverse;
> * {
flex-shrink: 0;
}
}
/*纵向上下分布 不压缩子元素*/
.f-tb {
display: flex;
flex-flow: column nowrap;
> * {
flex-shrink: 0;
}
}
/*纵向上下分布 从下到上 不压缩子元素*/
.f-bt {
display: flex;
flex-flow: column-reverse nowrap;
> * {
flex-shrink: 0;
}
}
/*纵向上下分布 左右居中 不压缩子元素*/
.f-tb-center {
display: flex;
align-items: center;
flex-flow: column nowrap;
> * {
flex-shrink: 0;
}
}
/*纵向上下分布 从下到上 左右居中 不压缩子元素*/
.f-bt-center {
display: flex;
align-items: center;
flex-flow: column-reverse nowrap;
> * {
flex-shrink: 0;
}
}
// 纵向上下分布 ,不压缩子元素,超出则自动换行
.f-tb-wrap {
display: flex;
align-content: flex-start;
flex-flow: column wrap;
> * {
flex-shrink: 0;
}
}
// 纵向上下分布 ,从下到上,不压缩子元素,超出则自动换行
.f-bt-wrap {
display: flex;
align-content: flex-start;
flex-flow: column-reverse wrap;
> * {
flex-shrink: 0;
}
}
// 纵向上下均匀分布 不压缩子元素 (元素间距离平均分配)
.f-tb-evenly {
display: flex;
flex-flow: column nowrap;
justify-content: space-evenly;
> * {
flex-shrink: 0;
}
}
// 纵向上下均匀分布 不压缩子元素 (元素间距离平均分配)
.f-bt-evenly {
display: flex;
flex-flow: column-reverse nowrap;
justify-content: space-evenly;
> * {
flex-shrink: 0;
}
}
// 纵向上下分布 上到下  (第一个元素靠起点,最后一个元素靠终点,余下元素平均分配空间)
.f-tb-between {
display: flex;
flex-flow: column nowrap;
justify-content: space-between;
> * {
flex-shrink: 0;
}
}
// 纵向上下分布 上到下  (第一个元素靠起点,最后一个元素靠终点,余下元素平均分配空间)
.f-bt-between {
display: flex;
flex-flow: column-reverse nowrap;
justify-content: space-between;
> * {
flex-shrink: 0;
}
}
/*垂直水平居中*/
.f-center {
display: flex;
justify-content: center;
align-items: center;
> * {
flex-shrink: 0;
}
}
/* 元素在主交叉轴上的对齐方式——居中 */
.f-align-center {
align-items: center;
}
/*自动填充剩余空间*/
.f-space {
flex-grow: 1;
flex-shrink: 1;
}
.f-full {
flex-shrink: 0;
}
.f-shrink {
flex-shrink: 1;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,14 @@
* {
outline: none;
box-sizing: border-box;
margin: 0px;
padding: 0px;
}
svg,
img {
-webkit-user-select: none;
-moz-user-select: none;
-o-user-select: none;
user-select: none;
}

424
AdminPanel/src/style/base/normalize.css vendored Normal file
View File

@@ -0,0 +1,424 @@
/*! normalize.css v4.0.0 | MIT License | github.com/necolas/normalize.css */
/**
* 1. Change the default font family in all browsers (opinionated).
* 2. Prevent adjustments of font size after orientation changes in IE and iOS.
*/
html {
font-family: sans-serif; /* 1 */
-ms-text-size-adjust: 100%; /* 2 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/**
* Remove the margin in all browsers (opinionated).
*/
body {
margin: 0;
}
/* HTML5 display definitions
========================================================================== */
/**
* Add the correct display in IE 9-.
* 1. Add the correct display in Edge, IE, and Firefox.
* 2. Add the correct display in IE.
*/
article,
aside,
details, /* 1 */
figcaption,
figure,
footer,
header,
main, /* 2 */
menu,
nav,
section,
summary { /* 1 */
display: block;
}
/**
* Add the correct display in IE 9-.
*/
audio,
canvas,
progress,
video {
display: inline-block;
}
/**
* Add the correct display in iOS 4-7.
*/
audio:not([controls]) {
display: none;
height: 0;
}
/**
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
progress {
vertical-align: baseline;
}
/**
* Add the correct display in IE 10-.
* 1. Add the correct display in IE.
*/
template, /* 1 */
[hidden] {
display: none;
}
/* Links
========================================================================== */
/**
* Remove the gray background on active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* Remove the outline on focused links when they are also active or hovered
* in all browsers (opinionated).
*/
a:active,
a:hover {
outline-width: 0;
}
/* Text-level semantics
========================================================================== */
/**
* 1. Remove the bottom border in Firefox 39-.
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
text-decoration: underline dotted; /* 2 */
}
/**
* Prevent the duplicate application of `bolder` by the next rule in Safari 6.
*/
b,
strong {
font-weight: inherit;
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* Add the correct font style in Android 4.3-.
*/
dfn {
font-style: italic;
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/**
* Add the correct background and color in IE 9-.
*/
mark {
background-color: #ff0;
color: #000;
}
/**
* Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Embedded content
========================================================================== */
/**
* Remove the border on images inside links in IE 10-.
*/
img {
border-style: none;
}
/**
* Hide the overflow in IE.
*/
svg:not(:root) {
overflow: hidden;
}
/* Grouping content
========================================================================== */
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
pre,
samp {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/**
* Add the correct margin in IE 8.
*/
figure {
margin: 1em 40px;
}
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
*/
hr {
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
}
/* Forms
========================================================================== */
/**
* Change font properties to `inherit` in all browsers (opinionated).
*/
button,
input,
select,
textarea {
font: inherit;
}
/**
* Restore the font weight unset by the previous rule.
*/
optgroup {
font-weight: bold;
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
* 2. Show the overflow in Edge, Firefox, and IE.
*/
button,
input, /* 1 */
select { /* 2 */
overflow: visible;
}
/**
* Remove the margin in Safari.
* 1. Remove the margin in Firefox and Safari.
*/
button,
input,
select,
textarea { /* 1 */
margin: 0;
}
/**
* Remove the inheritence of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritence of text transform in Firefox.
*/
button,
select { /* 1 */
text-transform: none;
}
/**
* Change the cursor in all browsers (opinionated).
*/
button,
[type="button"],
[type="reset"],
[type="submit"] {
cursor: pointer;
}
/**
* Restore the default cursor to disabled elements unset by the previous rule.
*/
[disabled] {
cursor: default;
}
/**
* 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
* controls in Android 4.
* 2. Correct the inability to style clickable types in iOS.
*/
button,
html [type="button"], /* 1 */
[type="reset"],
[type="submit"] {
-webkit-appearance: button; /* 2 */
}
/**
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner,
input::-moz-focus-inner {
border: 0;
padding: 0;
}
/**
* Restore the focus styles unset by the previous rule.
*/
button:-moz-focusring,
input:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Change the border, margin, and padding in all browsers (opinionated).
*/
fieldset {
border: 1px solid #c0c0c0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
legend {
box-sizing: border-box; /* 1 */
color: inherit; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
}
/**
* Remove the default vertical scrollbar in IE.
*/
textarea {
overflow: auto;
}
/**
* 1. Add the correct box sizing in IE 10-.
* 2. Remove the padding in IE 10-.
*/
[type="checkbox"],
[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* Correct the odd appearance of search inputs in Chrome and Safari.
*/
[type="search"] {
-webkit-appearance: textfield;
}
/**
* Remove the inner padding and cancel buttons in Chrome on OS X and
* Safari on OS X.
*/
[type="search"]::-webkit-search-cancel-button,
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}

View File

@@ -0,0 +1,37 @@
// 全局样式
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
#app {
width: 100%;
height: 100%;
}
// 滚动条样式
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
&:hover {
background: #a1a1a1;
}
}

View File

@@ -0,0 +1,2 @@
// 全局样式入口
import './global.less'

View File

@@ -0,0 +1,3 @@
@appPadding: 0px 16px;
@appBgColor: #202020;
@appTextColor: #fff;

View File

@@ -0,0 +1,23 @@
import { logger } from '@/utils/logger';
/**
* 初始化 Arco Design 暗色主题
* 为 CEP 插件环境配置适配的暗色主题
*/
export function initTheme() {
// 设置 Arco Design 主题为暗色模式
document.body.setAttribute('arco-theme', 'dark');
// 如果需要自定义 Arco Design 的颜色变量,可以在这里设置
const root = document.documentElement;
// 设置主色调为蓝色(可根据需要调整)
root.style.setProperty('--primary-6', 'rgb(64, 128, 255)');
// 可选:设置背景色以适应 CEP 插件
root.style.setProperty('--color-bg-1', '#1a1a1a');
root.style.setProperty('--color-bg-2', '#252525');
root.style.setProperty('--color-bg-3', '#323232');
logger.log('✅ Arco Design 暗色主题已初始化');
}

116
AdminPanel/src/utils/cep.ts Normal file
View File

@@ -0,0 +1,116 @@
/// <reference types="types-for-adobe/Photoshop/2015.5"/>
// CSInterface/CEP 的最小化类型定义,避免 TS 编译报错
declare class CSInterface {
constructor();
getHostEnvironment(): any;
addEventListener(type: string, listener: any, obj?: any): void;
evalScript(script: string, callback?: (result: string) => void): void;
openURLInDefaultBrowser(url: string): void;
closeExtension(): void;
}
declare interface HostEnvironment {
appSkinInfo: any;
}
declare interface CSEvent {
type: string;
data: any;
}
/**
* cep.ts
* Adobe CSInterface 的封装类,提供类型安全和环境判断
*/
class CepWrapper {
private csInterface: CSInterface | null = null;
public inCEP: boolean = false;
constructor() {
// @ts-ignore
if (typeof CSInterface !== 'undefined') {
// @ts-ignore
this.csInterface = new CSInterface();
this.inCEP = true;
} else {
// 延迟导入 logger 以避免循环依赖
import('./logger').then(({ logger }) => {
logger.warn('未找到 CSInterface当前运行于浏览器模式。');
});
}
}
/**
* 获取原始 CSInterface 实例
*/
public getCSInterface(): CSInterface | null {
return this.csInterface;
}
/**
* 获取宿主环境信息 (如皮肤颜色、版本等)
*/
public getHostEnvironment(): HostEnvironment | null {
if (this.csInterface) {
return this.csInterface.getHostEnvironment();
}
return null;
}
/**
* 添加 CEP 事件监听
*/
public addEventListener(type: string, listener: (event: CSEvent) => void, obj?: any): void {
if (this.csInterface) {
this.csInterface.addEventListener(type, listener, obj);
}
}
/**
* 执行 ExtendScript (JSX) 脚本
* 返回 Promise 封装的结果
*/
public evalScript(script: string): Promise<string> {
return new Promise((resolve, reject) => {
if (!this.csInterface) {
// 浏览器模式下的模拟行为
import('./logger').then(({ logger }) => {
logger.log(`[Mock CEP] 执行脚本: ${script}`);
});
// 返回合法的 JSON 字符串以避免 Parse Error
resolve('{"success": true, "message": "MOCK_RESULT"}');
return;
}
this.csInterface.evalScript(script, (result: string) => {
if (result === 'EvalScript error.') {
reject(new Error(result));
} else {
resolve(result);
}
});
});
}
/**
* 在默认系统浏览器中打开 URL
*/
public openURLInDefaultBrowser(url: string): void {
if (this.csInterface) {
this.csInterface.openURLInDefaultBrowser(url);
} else {
window.open(url, '_blank');
}
}
/**
* 关闭扩展面板
*/
public closeExtension(): void {
this.csInterface?.closeExtension();
}
}
export const cep = new CepWrapper();

View File

@@ -0,0 +1,7 @@
import { isNodeJSEnabled } from "./tool"
/**
* cep_node 为ps内置的全局变量
*/
//@ts-ignore
export const fs = (isNodeJSEnabled() ? cep_node.require('fs') : {}) as typeof import('fs')

View File

@@ -0,0 +1,9 @@
import { isNodeJSEnabled } from "./tool"
console.error('isNodeJSEnabled()'+isNodeJSEnabled());
/**
* cep_node 为ps内置的全局变量
*/
//@ts-ignore
export const path = (isNodeJSEnabled() ? cep_node.require('path') : {}) as typeof import('path')

View File

@@ -0,0 +1,17 @@
/**是否开启了node模块 */
export function isNodeJSEnabled() {
//@ts-ignore
if (typeof (cep_node) !== 'undefined') {
//if require and process is available, it should be mixed context
//@ts-ignore
if ((typeof (cep_node.require) !== 'undefined') && (typeof (cep_node.process) !== 'undefined')) {
return true
}
else {
return false
}
}
else {
return false
}
}

View File

@@ -0,0 +1,181 @@
/**
* 全局日志管理工具
*
* 提供统一的日志管理,支持一键开启/关闭所有日志
*
* @example
* import { logger } from '@/utils/logger';
*
* // 使用日志
* logger.log('普通日志');
* logger.info('信息日志');
* logger.warn('警告日志');
* logger.error('错误日志');
* logger.debug('调试日志');
*
* // 控制日志开关
* logger.enable(); // 开启日志
* logger.disable(); // 关闭日志
*
* // 或者设置
* logger.setEnabled(true); // 开启
* logger.setEnabled(false); // 关闭
*/
type LogLevel = 'log' | 'info' | 'warn' | 'error' | 'debug';
class Logger {
private _enabled: boolean = false; // 默认关闭日志
/**
* 获取日志开启状态
*/
get enabled(): boolean {
return this._enabled;
}
/**
* 开启日志
*/
enable(): void {
this._enabled = true;
console.log('[Logger] 日志已开启');
}
/**
* 关闭日志
*/
disable(): void {
console.log('[Logger] 日志已关闭');
this._enabled = false;
}
/**
* 设置日志开启状态
*/
setEnabled(enabled: boolean): void {
if (enabled) {
this.enable();
} else {
this.disable();
}
}
/**
* 切换日志状态
*/
toggle(): void {
this.setEnabled(!this._enabled);
}
/**
* 普通日志
*/
log(...args: any[]): void {
if (this._enabled) {
console.log(...args);
}
}
/**
* 信息日志
*/
info(...args: any[]): void {
if (this._enabled) {
console.info(...args);
}
}
/**
* 警告日志
*/
warn(...args: any[]): void {
if (this._enabled) {
console.warn(...args);
}
}
/**
* 错误日志 - 错误日志始终显示,不受开关控制
* 如果需要控制错误日志,使用 errorSilent
*/
error(...args: any[]): void {
// 错误日志始终输出,便于调试问题
console.error(...args);
}
/**
* 可控制的错误日志
*/
errorSilent(...args: any[]): void {
if (this._enabled) {
console.error(...args);
}
}
/**
* 调试日志
*/
debug(...args: any[]): void {
if (this._enabled) {
console.debug(...args);
}
}
/**
* 分组日志开始
*/
group(...args: any[]): void {
if (this._enabled) {
console.group(...args);
}
}
/**
* 分组日志结束
*/
groupEnd(): void {
if (this._enabled) {
console.groupEnd();
}
}
/**
* 折叠分组日志开始
*/
groupCollapsed(...args: any[]): void {
if (this._enabled) {
console.groupCollapsed(...args);
}
}
/**
* 表格日志
*/
table(data: any, columns?: string[]): void {
if (this._enabled) {
console.table(data, columns);
}
}
/**
* 分隔线日志
*/
separator(char: string = '=', length: number = 60): void {
if (this._enabled) {
console.log(char.repeat(length));
}
}
}
// 导出单例
export const logger = new Logger();
// 默认导出
export default logger;
// 在开发环境下,可以通过 window.logger 访问
if (typeof window !== 'undefined') {
(window as any).logger = logger;
}

View File

@@ -0,0 +1,146 @@
/// <reference types="types-for-adobe/Photoshop/2015.5"/>
/**
* theme.ts
* 处理 Photoshop 主题颜色同步
* 支持两种模式:
* 1. 直接 CEP 模式CSInterface 可用)
* 2. iframe 模式(通过 postMessage 接收主题)
*/
import { cep } from './cep';
import { logger } from './logger';
interface Color {
red: number;
green: number;
blue: number;
alpha: number;
}
interface PSThemeMessage {
type: 'PS_THEME';
theme: {
bgColor: string;
isLight: boolean;
fontSize: number;
};
}
/**
* 将 Adobe 颜色对象转换为 CSS RGB 字符串
*/
function toHex(color: Color): string {
const r = Math.round(color.red).toString(16).padStart(2, '0');
const g = Math.round(color.green).toString(16).padStart(2, '0');
const b = Math.round(color.blue).toString(16).padStart(2, '0');
return `#${r}${g}${b}`;
}
/**
* 计算颜色的亮度 (0-255)
*/
function getBrightness(color: Color): number {
return (color.red * 299 + color.green * 587 + color.blue * 114) / 1000;
}
/**
* 应用主题到页面
*/
function applyTheme(bgColor: string, isLight: boolean, fontSize?: number) {
const root = document.documentElement;
let textColorHex, borderColorHex, iconColorHex;
if (isLight) {
document.body.removeAttribute('arco-theme');
textColorHex = '#222222';
borderColorHex = '#d0d0d0';
iconColorHex = '#333333';
} else {
document.body.setAttribute('arco-theme', 'dark');
textColorHex = '#dfdfdf';
borderColorHex = '#4a4a4a';
iconColorHex = '#f0f0f0';
}
// 设置 CSS 变量
root.style.setProperty('--ps-bg', bgColor);
root.style.setProperty('--ps-text', textColorHex);
root.style.setProperty('--ps-border', borderColorHex);
root.style.setProperty('--ps-icon', iconColorHex);
if (fontSize) {
root.style.setProperty('--ps-font-size', `${fontSize}px`);
}
// Arco Design 主题变量
root.style.setProperty('--color-bg-1', bgColor);
root.style.setProperty('--color-bg-2', bgColor);
root.style.setProperty('--color-bg-3', borderColorHex);
root.style.setProperty('--color-text-1', textColorHex);
root.style.setProperty('--color-border', borderColorHex);
// 应用到 body
document.body.style.backgroundColor = bgColor;
document.body.style.color = textColorHex;
logger.log(`[Theme] 已应用主题: ${isLight ? '浅色' : '深色'}, 背景: ${bgColor}`);
}
/**
* 从 CSInterface 直接获取并更新主题
*/
export const updateTheme = () => {
if (!cep.inCEP) {
// 浏览器模式 - 使用默认深色主题
applyTheme('#323232', false);
return;
}
const hostEnv = cep.getHostEnvironment();
if (!hostEnv) return;
const skinInfo = hostEnv.appSkinInfo;
const panelBgColor = skinInfo.panelBackgroundColor.color;
const bgColorHex = toHex(panelBgColor);
const brightness = getBrightness(panelBgColor);
const isLight = brightness > 128;
applyTheme(bgColorHex, isLight, skinInfo.baseFontSize);
};
/**
* 处理来自父窗口的主题消息iframe 模式)
*/
function handleThemeMessage(event: MessageEvent) {
const data = event.data as PSThemeMessage;
if (data && data.type === 'PS_THEME' && data.theme) {
logger.log('[Theme] 收到父窗口主题消息');
applyTheme(data.theme.bgColor, data.theme.isLight, data.theme.fontSize);
}
}
/**
* 初始化主题监听
*/
export const initThemeListener = () => {
// 检测是否在 iframe 中运行
const inIframe = window !== window.parent;
if (inIframe) {
// iframe 模式:监听来自父窗口的消息
logger.log('[Theme] iframe 模式 - 监听 postMessage');
window.addEventListener('message', handleThemeMessage);
// 请求父窗口发送主题
window.parent.postMessage({ type: 'REQUEST_THEME' }, '*');
} else if (cep.inCEP) {
// 直接 CEP 模式
logger.log('[Theme] CEP 模式 - 直接监听主题变化');
updateTheme();
cep.addEventListener('com.adobe.csxs.events.ThemeColorChanged', updateTheme);
} else {
// 纯浏览器模式
logger.log('[Theme] 浏览器模式 - 使用默认主题');
applyTheme('#323232', false);
}
};

View File

@@ -0,0 +1,132 @@
<template>
<div class="iframe-container">
<!-- 顶部工具栏 -->
<div class="toolbar">
<a-button size="small" @click="goBack">
<template #icon><icon-arrow-left /></template>
返回
</a-button>
<span class="url-display">{{ currentUrl }}</span>
<a-button size="small" @click="reload">
<template #icon><icon-refresh /></template>
刷新
</a-button>
<a-button size="small" @click="openExternal">
<template #icon><icon-export /></template>
外部打开
</a-button>
</div>
<!-- 加载指示器 -->
<div v-if="loading" class="loading-overlay">
<a-spin size="32" />
<p>加载中...</p>
</div>
<!-- iframe -->
<iframe
ref="frameRef"
:src="currentUrl"
class="main-frame"
@load="onFrameLoad"
@error="onFrameError"
/>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { IconArrowLeft, IconRefresh, IconExport } from '@arco-design/web-vue/es/icon'
const router = useRouter()
const frameRef = ref<HTMLIFrameElement | null>(null)
const loading = ref(true)
const currentUrl = ref(
// localStorage.getItem('adminPanelUrl') || 'https://app.aidg168.uk'
localStorage.getItem('adminPanelUrl') || 'http://localhost:5175/#/login'
)
function goBack() {
router.push('/')
}
function reload() {
loading.value = true
if (frameRef.value) {
frameRef.value.src = currentUrl.value
}
}
function openExternal() {
window.open(currentUrl.value, '_blank')
}
function onFrameLoad() {
loading.value = false
console.log('✅ iframe 加载完成')
}
function onFrameError(e: Event) {
loading.value = false
console.error('❌ iframe 加载失败', e)
}
onMounted(() => {
// 设置超时
setTimeout(() => {
if (loading.value) {
console.warn('⏱️ 加载超时')
}
}, 30000)
})
</script>
<style scoped>
.iframe-container {
display: flex;
flex-direction: column;
height: 100%;
background: var(--ps-bg, #1e1e1e);
}
.toolbar {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: var(--color-bg-2, #2d2d2d);
border-bottom: 1px solid var(--ps-border, #444);
}
.url-display {
flex: 1;
font-size: 12px;
color: var(--ps-text, #aaa);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.main-frame {
flex: 1;
width: 100%;
border: none;
}
.loading-overlay {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
color: var(--ps-text, white);
z-index: 10;
}
.loading-overlay p {
margin-top: 12px;
}
</style>

8
AdminPanel/src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1,8 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

22
AdminPanel/tsconfig.json Normal file
View File

@@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "Node",
"strict": true,
"jsx": "preserve",
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["ESNext", "DOM"],
"skipLibCheck": true,
"noEmit": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@@ -0,0 +1,9 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

46
AdminPanel/vite.config.ts Normal file
View File

@@ -0,0 +1,46 @@
import { defineConfig } from 'vite'
import tsconfigPaths from 'vite-tsconfig-paths'
import vue from '@vitejs/plugin-vue'
import cepPlugin from './plugins/jsx/cepPlugin'
import legacy from '@vitejs/plugin-legacy'
import path from 'path'
const isDev = process.env.NODE_ENV !== 'production'
export default defineConfig(({ command }) => {
const isBuild = command === 'build';
return {
server: {
port: 5180, // AdminPanel 专用端口
strictPort: true, // 强制使用此端口,如果被占用则报错
cors: true
},
plugins: [
legacy({
targets: ['chrome 41', 'not IE 11']
}),
!isBuild && cepPlugin(), // 开发时自动复制到 PS 扩展目录
vue(),
tsconfigPaths(),
],
base: './',
resolve: {
alias: {
"@/": new URL("./src/", import.meta.url).pathname,
'@plugins': path.resolve(__dirname, './plugins')
}
},
define: {
__BUILD_TIME__: Date.now(),
__DEV__: isDev,
__VERSION__: JSON.stringify(process.env.npm_package_version),
},
build: {
outDir: 'dist',
rollupOptions: {
input: path.resolve(__dirname, 'index.html')
}
}
}
})

View File

@@ -0,0 +1,56 @@
/**
* 在你的网站https://app.aidg168.uk中添加此代码
* 用于接收来自 Photoshop CEP 插件的主题信息
*/
// 监听来自 PS 插件的主题消息
window.addEventListener('message', function(event) {
// 安全检查(可选)
// if (event.origin !== 'expected-origin') return;
const data = event.data;
// 检查是否是 PS 主题消息
if (data && data.type === 'PS_THEME' && data.theme) {
const theme = data.theme;
console.log('📨 收到 PS 主题:', theme);
// theme.bgColor: 背景颜色 (例如 'rgb(50, 50, 50)')
// theme.isLight: 是否为浅色主题 (true/false)
// theme.fontSize: 字体大小 (数字)
// 应用主题到你的网站
applyPSTheme(theme);
}
});
// 应用 PS 主题到网站
function applyPSTheme(theme) {
const root = document.documentElement;
// 设置 CSS 变量
root.style.setProperty('--ps-bg-color', theme.bgColor);
root.style.setProperty('--ps-font-size', theme.fontSize + 'px');
// 根据亮度切换主题
if (theme.isLight) {
// 浅色主题
document.body.classList.remove('dark-theme');
document.body.classList.add('light-theme');
root.style.setProperty('--ps-text-color', '#222222');
} else {
// 深色主题
document.body.classList.remove('light-theme');
document.body.classList.add('dark-theme');
root.style.setProperty('--ps-text-color', '#dfdfdf');
}
// 直接应用到 body
document.body.style.backgroundColor = theme.bgColor;
console.log('✅ PS 主题已应用');
}
// 可选:主动请求主题(如果插件已经加载)
window.parent.postMessage({ type: 'REQUEST_THEME' }, '*');

File diff suppressed because it is too large Load Diff

View File

@@ -1,540 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Core 应用自动发布脚本
完全自动化构建、打包、上传服务器、更新数据库
使用方法:
python auto_deploy_core.py --version v1.0.6
python auto_deploy_core.py --version v1.0.6 --deploy # 部署到服务器
python auto_deploy_core.py --version v1.0.6 --deploy --update-db # 部署并更新数据库
"""
import os
import sys
import shutil
import subprocess
import argparse
import zipfile
import json
from pathlib import Path
from datetime import datetime
import paramiko
import pymysql
# ==================== 配置区域 ====================
PROJECT_ROOT = Path(__file__).parent.parent.absolute() # 上一级目录
DESIGNER_DIR = PROJECT_ROOT / "Designer"
DIST_CORE_DIR = DESIGNER_DIR / "dist" / "Designer" # Core 构建输出目录
DIST_SHELL_DIR = DESIGNER_DIR / "dist" / "Shell" # Shell 构建输出目录
SERVER_DIR = PROJECT_ROOT / "Server"
ARCHIVES_DIR = SERVER_DIR / "archives"
CONFIG_FILE = Path(__file__).parent / "deploy_config.json"
# 颜色输出Windows 兼容)
try:
import colorama
colorama.init()
GREEN = '\033[92m'
YELLOW = '\033[93m'
RED = '\033[91m'
BLUE = '\033[94m'
RESET = '\033[0m'
except ImportError:
GREEN = YELLOW = RED = BLUE = RESET = ''
# ==================== 工具函数 ====================
def print_step(step_num, total_steps, message):
"""打印步骤信息"""
print(f"\n{BLUE}{'='*60}{RESET}")
print(f"{GREEN}[步骤 {step_num}/{total_steps}] {message}{RESET}")
print(f"{BLUE}{'='*60}{RESET}\n")
def print_success(message):
"""打印成功信息"""
print(f"{GREEN}{message}{RESET}")
def print_warning(message):
"""打印警告信息"""
print(f"{YELLOW}{message}{RESET}")
def print_error(message):
"""打印错误信息"""
print(f"{RED}{message}{RESET}")
def run_command(command, cwd=None, shell=True, check=True):
"""运行命令并返回结果"""
print(f" 执行: {command}")
try:
result = subprocess.run(
command,
cwd=cwd,
shell=shell,
check=check,
capture_output=True,
text=True,
encoding='utf-8',
errors='replace'
)
if result.stdout:
print(f" 输出: {result.stdout.strip()}")
return result
except subprocess.CalledProcessError as e:
print_error(f"命令执行失败: {e}")
if e.stderr:
print(f" 错误: {e.stderr}")
if check:
sys.exit(1)
return None
# ==================== 发布步骤 ====================
def load_config():
"""加载部署配置"""
if not CONFIG_FILE.exists():
return None
try:
with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
print_warning(f"加载配置文件失败: {e}")
return None
def save_config(config):
"""保存部署配置"""
try:
with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
json.dump(config, f, ensure_ascii=False, indent=2)
print_success(f"配置已保存: {CONFIG_FILE}")
except Exception as e:
print_error(f"保存配置失败: {e}")
def step1_build_frontend():
"""步骤 1: 构建前端Shell + Core"""
print_step(1, 8, "构建前端Shell + Core")
# 检查 package.json
package_json = DESIGNER_DIR / "package.json"
if not package_json.exists():
print_error(f"未找到 package.json: {package_json}")
sys.exit(1)
# 运行构建命令
os.chdir(DESIGNER_DIR)
print(" 正在构建 Core...")
run_command("npm run build:core", cwd=DESIGNER_DIR)
print(" 正在构建 Shell...")
run_command("npm run build", cwd=DESIGNER_DIR)
# 验证输出目录
if not DIST_CORE_DIR.exists():
print_error(f"Core 构建输出目录不存在: {DIST_CORE_DIR}")
sys.exit(1)
if not DIST_SHELL_DIR.exists():
print_error(f"Shell 构建输出目录不存在: {DIST_SHELL_DIR}")
sys.exit(1)
print_success(f"构建完成")
print(f" Core: {DIST_CORE_DIR}")
print(f" Shell: {DIST_SHELL_DIR}")
def step2_package_shell_zip(version):
"""步骤 2: 打包 Shell 为 ZIP供 CEP 扩展下载)"""
print_step(2, 8, "打包 Shell 为 ZIP")
# 生成 ZIP 文件名
zip_filename = f"shell-{version}.zip"
zip_path = DESIGNER_DIR / "dist" / zip_filename
# 如果文件已存在,先删除
if zip_path.exists():
print_warning(f"ZIP 文件已存在,删除: {zip_path}")
zip_path.unlink()
# 创建 ZIP
print(f" 创建 ZIP: {zip_path}")
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
for root, dirs, files in os.walk(DIST_SHELL_DIR):
for file in files:
file_path = Path(root) / file
arcname = file_path.relative_to(DIST_SHELL_DIR)
zipf.write(file_path, arcname)
# 显示文件大小
size_mb = zip_path.stat().st_size / (1024 * 1024)
print_success(f"Shell 打包完成: {zip_path}")
print(f" 文件大小: {size_mb:.2f} MB")
return zip_path
def step3_upload_to_server(version, config):
"""步骤 3: 上传到服务器"""
print_step(3, 8, "上传到服务器")
if not config:
print_warning("未配置服务器信息,跳过上传")
return
# 连接 SSH
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
print(f" 连接服务器: {config['host']}")
ssh.connect(
hostname=config['host'],
port=int(config.get('port', 22)),
username=config['username'],
password=config.get('password', ''),
timeout=30
)
print_success("SSH 连接成功")
sftp = ssh.open_sftp()
remote_path = config['remote_path']
# 1. 创建远程目录
print(" 创建远程目录...")
commands = [
f"mkdir -p {remote_path}/shell",
f"mkdir -p {remote_path}/core/{version}",
f"mkdir -p {remote_path}/downloads"
]
for cmd in commands:
stdin, stdout, stderr = ssh.exec_command(cmd)
stdout.channel.recv_exit_status()
print_success("目录创建完成")
# 2. 上传 Shell在线登录页
print(" 上传 Shell在线登录页...")
upload_directory(sftp, DIST_SHELL_DIR, f"{remote_path}/shell")
print_success("Shell 上传完成")
# 3. 上传 Core
print(" 上传 Core核心应用...")
upload_directory(sftp, DIST_CORE_DIR, f"{remote_path}/core/{version}")
print_success("Core 上传完成")
# 4. 上传 Shell.zip
print(" 上传 Shell.zipCEP 扩展下载)...")
shell_zip = DESIGNER_DIR / "dist" / f"shell-{version}.zip"
if shell_zip.exists():
remote_zip = f"{remote_path}/downloads/shell-{version}.zip"
sftp.put(str(shell_zip), remote_zip)
print_success(f"Shell.zip 上传完成: {remote_zip}")
sftp.close()
ssh.close()
print_success("所有文件上传完成")
print(f"\n访问地址:")
print(f" Shell: https://{config['host']}/shell/")
print(f" Core: https://{config['host']}/core/{version}/")
print(f" 下载: https://{config['host']}/downloads/shell-{version}.zip")
except Exception as e:
print_error(f"上传失败: {e}")
raise
finally:
ssh.close()
def upload_directory(sftp, local_dir, remote_dir):
"""递归上传目录"""
if not os.path.exists(local_dir):
raise Exception(f"本地目录不存在: {local_dir}")
# 创建远程目录
try:
sftp.stat(remote_dir)
except IOError:
sftp.mkdir(remote_dir)
# 递归上传文件
for item in os.listdir(local_dir):
local_path = os.path.join(local_dir, item)
remote_path = remote_dir + '/' + item
if os.path.isfile(local_path):
print(f"{item}")
sftp.put(local_path, remote_path)
elif os.path.isdir(local_path):
upload_directory(sftp, local_path, remote_path)
def step4_update_mysql(version, config):
"""步骤 4: 更新 MySQL 数据库 (通过 SSH 执行 Docker 命令)"""
print_step(4, 8, "更新 MySQL 数据库")
# 注意:我们不再直接连接 MySQL而是通过 SSH 发送 docker exec 命令
# 这样用户不需要暴露 MySQL 端口,更安全
if not config:
print_warning("未配置服务器信息,跳过数据库更新")
return
try:
# 连接 SSH
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
print(f" 连接服务器: {config['host']}")
ssh.connect(
hostname=config['host'],
port=int(config.get('port', 22)),
username=config['username'],
password=config.get('password', ''),
timeout=30
)
print_success("SSH 连接成功")
# 构造 SQL 语句
# 更新 'default' 分组为最新版本 (更健壮,不依赖 ID=1)
sql = f"UPDATE plugin_groups SET current_version_file = 'core-v{version}.zip' WHERE name = 'default';"
# 构造 Docker 命令
# 假设容器名为 designercep_db用户 designer_user密码 DesignerPass123!,库名 designer_db
# 这些应该与 docker-compose.yml 保持一致
db_user = "designer_user"
db_pass = "DesignerPass123!"
db_name = "designer_db"
container_name = "designercep_db"
print(f" 执行远程 Docker SQL 命令...")
print(f" SQL: {sql}")
docker_cmd = f'docker exec -i {container_name} mysql -u{db_user} -p{db_pass} {db_name} -e "{sql}"'
stdin, stdout, stderr = ssh.exec_command(docker_cmd)
exit_status = stdout.channel.recv_exit_status()
if exit_status == 0:
print_success("数据库更新命令执行成功")
print(f" 输出: {stdout.read().decode().strip()}")
else:
print_error(f"数据库更新失败 (Exit code: {exit_status})")
print(f" 错误: {stderr.read().decode().strip()}")
raise Exception("Docker exec failed")
ssh.close()
except Exception as e:
print_error(f"数据库更新失败: {e}")
print_warning("您需要手动执行 SQL (进入容器执行):")
print(f" {sql}")
# 不抛出异常,以免打断流程,因为这步失败通常不影响文件上传
def step5_clean_cache():
"""步骤 5: 清除客户端缓存(测试用)"""
print_step(5, 8, "清除客户端缓存(可选)")
cache_dir = Path.home() / "AppData" / "Roaming" / "DesignerCache"
if not cache_dir.exists():
print_warning(f"缓存目录不存在: {cache_dir}")
return
try:
shutil.rmtree(cache_dir)
print_success(f"缓存已清除: {cache_dir}")
except Exception as e:
print_warning(f"清除缓存失败: {e}")
print(" 您可以手动删除缓存目录")
def step6_setup_config():
"""步骤 6: 配置服务器信息(首次运行)"""
print_step(6, 8, "配置服务器信息")
config = load_config()
if config:
print_warning("配置文件已存在,跳过配置")
print(f" 配置文件: {CONFIG_FILE}")
return config
print("请输入服务器配置信息:")
print()
config = {}
# SSH 配置
config['host'] = input(" 服务器地址: ").strip()
config['port'] = input(" SSH 端口 [22]: ").strip() or "22"
config['username'] = input(" SSH 用户名: ").strip()
config['password'] = input(" SSH 密码: ").strip()
config['remote_path'] = input(" 远程路径 [/var/www/DesignerCEP/Server/static]: ").strip() \
or "/var/www/DesignerCEP/Server/static"
print()
# MySQL 配置
use_mysql = input(" 是否配置 MySQL (y/n) [y]: ").strip().lower()
if use_mysql != 'n':
config['mysql'] = {}
config['mysql']['host'] = input(" MySQL 地址: ").strip()
config['mysql']['port'] = input(" MySQL 端口 [3306]: ").strip() or "3306"
config['mysql']['username'] = input(" MySQL 用户名: ").strip()
config['mysql']['password'] = input(" MySQL 密码: ").strip()
config['mysql']['database'] = input(" 数据库名: ").strip()
config['mysql']['table'] = input(" 表名 [plugin_groups]: ").strip() or "plugin_groups"
# 保存配置
save_config(config)
return config
def step7_summary(version, deployed):
"""步骤 7: 发布总结"""
print_step(7, 8, "发布总结")
print(f"""
{GREEN}{'='*60}
DesignerCEP 发布完成!
{'='*60}{RESET}
[发布信息]
版本号: {version}
构建时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
部署状态: {'已部署到服务器' if deployed else '仅本地构建'}
[构建产物]
Core: {DIST_CORE_DIR}
Shell: {DIST_SHELL_DIR}
Shell.zip: {DESIGNER_DIR / 'dist' / f'shell-{version}.zip'}
[后续步骤]
1. {'' if deployed else ''} 验证服务器文件
2. {'' if deployed else ''} 确认数据库版本
3. ○ 测试客户端更新功能
4. ○ 通知用户更新
[测试方法]
1. 删除客户端缓存(已自动执行)
2. 重启 Photoshop 插件
3. 登录账号,检查是否下载新版本
4. 验证功能是否正常
{GREEN}{'='*60}{RESET}
""")
# ==================== 主函数 ====================
def main():
parser = argparse.ArgumentParser(
description='DesignerCEP 自动发布脚本Shell + Core',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
示例:
# 仅构建(不部署)
python auto_deploy_core.py --version 1.0.6
# 构建并部署到服务器
python auto_deploy_core.py --version 1.0.6 --deploy
# 构建、部署并更新数据库
python auto_deploy_core.py --version 1.0.6 --deploy --update-db
# 首次运行,配置服务器信息
python auto_deploy_core.py --version 1.0.6 --setup
"""
)
parser.add_argument(
'--version', '-v',
required=True,
help='版本号,例如: 1.0.6(不要加 v'
)
parser.add_argument(
'--deploy', '-d',
action='store_true',
help='部署到服务器'
)
parser.add_argument(
'--update-db',
action='store_true',
help='自动更新 MySQL 数据库'
)
parser.add_argument(
'--setup',
action='store_true',
help='配置服务器信息(首次运行)'
)
parser.add_argument(
'--skip-clean',
action='store_true',
help='跳过清除缓存步骤'
)
args = parser.parse_args()
version = args.version
print(f"""
{BLUE}{'='*60}
DesignerCEP 自动发布脚本
{'='*60}{RESET}
[配置信息]
版本号: {version}
项目根目录: {PROJECT_ROOT}
Designer: {DESIGNER_DIR}
部署到服务器: {'' if args.deploy else ''}
更新数据库: {'' if args.update_db else ''}
清除缓存: {'' if args.skip_clean else ''}
{BLUE}{'='*60}{RESET}
""")
try:
# 加载配置
config = None
if args.deploy or args.setup:
config = load_config()
if not config or args.setup:
config = step6_setup_config()
# 执行发布步骤
step1_build_frontend()
step2_package_shell_zip(version)
if args.deploy:
if not config:
print_error("未找到配置文件,请先运行 --setup 配置服务器")
sys.exit(1)
step3_upload_to_server(version, config)
if args.update_db:
step4_update_mysql(version, config)
else:
print_step(4, 8, "更新数据库(已跳过)")
print_warning("数据库未自动更新,请手动执行:")
print(f" UPDATE plugin_groups SET current_version = '{version}';")
else:
print_step(3, 8, "上传到服务器(已跳过)")
print_step(4, 8, "更新数据库(已跳过)")
if not args.skip_clean:
step5_clean_cache()
else:
print_step(5, 8, "清除缓存(已跳过)")
step7_summary(version, args.deploy)
except KeyboardInterrupt:
print_error("\n用户中断")
sys.exit(1)
except Exception as e:
print_error(f"发布失败: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,461 @@
import requests
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QHeaderView, QTableWidgetItem, QFrame,
QGridLayout, QLabel
)
from PyQt5.QtCore import Qt
from qfluentwidgets import (
Pivot, PushButton, TableWidget, CardWidget,
MessageBox, InfoBar, LineEdit, SpinBox, CheckBox, ComboBox,
FluentIcon as FIF, SubtitleLabel, CaptionLabel,
ScrollArea, BodyLabel, PrimaryPushButton, MessageBoxBase, StrongBodyLabel
)
class ConfigDialog(MessageBoxBase):
def __init__(self, title, parent=None):
super().__init__(parent)
self.titleLabel = SubtitleLabel(title, self)
self.viewLayout.addWidget(self.titleLabel)
self.widget.setMinimumWidth(350)
self.yesButton.setText("确定")
self.cancelButton.setText("取消")
def add_row(self, label_text, widget):
self.viewLayout.addWidget(StrongBodyLabel(label_text, self))
self.viewLayout.addWidget(widget)
class ConfigInterface(QWidget):
"""配置管理主界面"""
def __init__(self, api_client, parent=None):
super().__init__(parent)
self.api_client = api_client
# 主布局
self.main_layout = QVBoxLayout(self)
self.main_layout.setContentsMargins(30, 30, 30, 30)
self.main_layout.setSpacing(20)
# 标题
self.title_label = SubtitleLabel("系统配置", self)
self.main_layout.addWidget(self.title_label)
# Pivot 导航
self.pivot = Pivot(self)
self.main_layout.addWidget(self.pivot)
# 堆叠窗口容器
self.stacked_widget = QWidget()
self.stacked_layout = QVBoxLayout(self.stacked_widget)
self.stacked_layout.setContentsMargins(0, 0, 0, 0)
self.main_layout.addWidget(self.stacked_widget)
# 子页面
self.features_tab = FeaturesConfigTab(api_client, self)
self.vip_tab = VIPConfigTab(api_client, self)
self.checkin_tab = CheckInConfigTab(api_client, self)
self.stats_tab = StatsTab(api_client, self)
# 添加子页面到 Pivot
self.add_sub_interface(self.features_tab, 'features', '功能配置')
self.add_sub_interface(self.vip_tab, 'vip', 'VIP配置')
self.add_sub_interface(self.checkin_tab, 'checkin', '签到配置')
self.add_sub_interface(self.stats_tab, 'stats', '数据统计')
# 初始化显示第一个
self.pivot.setCurrentItem(self.features_tab.objectName())
self.pivot.currentItemChanged.connect(self.on_pivot_changed)
# 初始加载
self.features_tab.setVisible(True)
self.vip_tab.setVisible(False)
self.checkin_tab.setVisible(False)
self.stats_tab.setVisible(False)
# 默认布局填充
self.stacked_layout.addWidget(self.features_tab)
self.stacked_layout.addWidget(self.vip_tab)
self.stacked_layout.addWidget(self.checkin_tab)
self.stacked_layout.addWidget(self.stats_tab)
def add_sub_interface(self, widget, object_name, text):
widget.setObjectName(object_name)
self.pivot.addItem(routeKey=object_name, text=text)
def on_pivot_changed(self, route_key):
self.features_tab.setVisible(route_key == 'features')
self.vip_tab.setVisible(route_key == 'vip')
self.checkin_tab.setVisible(route_key == 'checkin')
self.stats_tab.setVisible(route_key == 'stats')
# 刷新数据
if route_key == 'features':
self.features_tab.load_data()
elif route_key == 'vip':
self.vip_tab.load_data()
elif route_key == 'checkin':
self.checkin_tab.load_data()
elif route_key == 'stats':
self.stats_tab.load_data()
class FeaturesConfigTab(QWidget):
"""功能配置标签页"""
def __init__(self, api_client, parent=None):
super().__init__(parent)
self.api_client = api_client
self.init_ui()
def init_ui(self):
self.v_layout = QVBoxLayout(self)
# 工具栏
self.tool_layout = QHBoxLayout()
self.btn_refresh = PushButton(FIF.SYNC, "刷新", self)
self.btn_refresh.clicked.connect(self.load_data)
self.btn_add = PrimaryPushButton(FIF.ADD, "新增功能", self)
self.btn_add.clicked.connect(self.add_feature)
self.tool_layout.addWidget(self.btn_refresh)
self.tool_layout.addWidget(self.btn_add)
self.tool_layout.addStretch(1)
self.v_layout.addLayout(self.tool_layout)
# 表格
self.table = TableWidget(self)
self.table.setColumnCount(7)
self.table.setHorizontalHeaderLabels([
"Key", "功能名称", "分类", "普通价格", "VIP价格", "SVIP价格", "状态"
])
self.table.verticalHeader().hide()
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.table.setSelectionBehavior(TableWidget.SelectRows)
self.table.setEditTriggers(TableWidget.NoEditTriggers)
self.table.doubleClicked.connect(self.edit_feature)
self.v_layout.addWidget(self.table)
def load_mock_data(self):
"""加载模拟数据"""
data = [
(1, 10, 0, 10),
(2, 10, 5, 15),
(3, 10, 10, 20),
(7, 10, 50, 60),
]
self.table.setRowCount(len(data))
for i, (days, base, bonus, total) in enumerate(data):
self.table.setItem(i, 0, QTableWidgetItem(f"{days}"))
self.table.setItem(i, 1, QTableWidgetItem(str(base)))
self.table.setItem(i, 2, QTableWidgetItem(str(bonus)))
self.table.setItem(i, 3, QTableWidgetItem(str(total)))
def load_data(self):
if not self.api_client:
InfoBar.warning("未连接", "请先连接到后端服务器", parent=self)
return
try:
resp = requests.get(f"{self.api_client.base_url}/admin/config/features", headers=self.api_client.get_headers(), timeout=10)
resp.raise_for_status()
data = resp.json()
self.table.setRowCount(len(data))
for i, item in enumerate(data):
self.table.setItem(i, 0, QTableWidgetItem(item.get('feature_key', '')))
self.table.setItem(i, 1, QTableWidgetItem(item.get('feature_name', '')))
self.table.setItem(i, 2, QTableWidgetItem(item.get('category', '')))
self.table.setItem(i, 3, QTableWidgetItem(str(item.get('points_cost', 0))))
self.table.setItem(i, 4, QTableWidgetItem(str(item.get('vip_points_cost', 0))))
self.table.setItem(i, 5, QTableWidgetItem(str(item.get('svip_points_cost', 0))))
enabled = item.get('enabled', False)
status_item = QTableWidgetItem("启用" if enabled else "禁用")
if enabled:
status_item.setForeground(Qt.darkGreen)
else:
status_item.setForeground(Qt.red)
self.table.setItem(i, 6, status_item)
# Store full data
self.table.item(i, 0).setData(Qt.UserRole, item)
InfoBar.success("加载成功", f"已加载 {len(data)} 个功能配置", parent=self)
except Exception as e:
InfoBar.error("加载失败", f"无法加载功能配置: {str(e)}", parent=self)
print(f"Error loading config: {e}")
def add_feature(self):
dialog = ConfigDialog("新增功能", self)
key_edit = LineEdit()
name_edit = LineEdit()
category_edit = LineEdit()
points_spin = SpinBox()
points_spin.setRange(0, 9999)
vip_spin = SpinBox()
vip_spin.setRange(0, 9999)
svip_spin = SpinBox()
svip_spin.setRange(0, 9999)
dialog.add_row("Key (唯一标识):", key_edit)
dialog.add_row("功能名称:", name_edit)
dialog.add_row("分类:", category_edit)
dialog.add_row("积分消耗:", points_spin)
dialog.add_row("VIP消耗:", vip_spin)
dialog.add_row("SVIP消耗:", svip_spin)
if dialog.exec():
key = key_edit.text().strip()
name = name_edit.text().strip()
if not key or not name:
InfoBar.warning("输入错误", "Key和名称不能为空", parent=self)
return
payload = {
"feature_key": key,
"feature_name": name,
"category": category_edit.text().strip(),
"points_cost": points_spin.value(),
"vip_points_cost": vip_spin.value(),
"svip_points_cost": svip_spin.value(),
"enabled": True
}
try:
resp = requests.post(f"{self.api_client.base_url}/admin/config/features", json=payload, headers=self.api_client.get_headers(), timeout=10)
resp.raise_for_status()
InfoBar.success("成功", f"功能 '{name}' 已添加", parent=self)
self.load_data()
except Exception as e:
InfoBar.error("失败", f"添加失败: {str(e)}", parent=self)
def edit_feature(self):
row = self.table.currentRow()
if row < 0: return
item = self.table.item(row, 0).data(Qt.UserRole)
dialog = ConfigDialog(f"编辑功能: {item['feature_name']}", self)
points_spin = SpinBox()
points_spin.setRange(0, 9999)
points_spin.setValue(item.get('points_cost', 0))
vip_spin = SpinBox()
vip_spin.setRange(0, 9999)
vip_spin.setValue(item.get('vip_points_cost', 0))
svip_spin = SpinBox()
svip_spin.setRange(0, 9999)
svip_spin.setValue(item.get('svip_points_cost', 0))
enabled_check = CheckBox("启用")
enabled_check.setChecked(item.get('enabled', True))
dialog.add_row("积分消耗:", points_spin)
dialog.add_row("VIP消耗:", vip_spin)
dialog.add_row("SVIP消耗:", svip_spin)
dialog.viewLayout.addWidget(enabled_check)
if dialog.exec():
payload = {
"points_cost": points_spin.value(),
"vip_points_cost": vip_spin.value(),
"svip_points_cost": svip_spin.value(),
"enabled": enabled_check.isChecked()
}
try:
resp = requests.put(f"{self.api_client.base_url}/admin/config/features/{item['feature_key']}", json=payload, headers=self.api_client.get_headers(), timeout=10)
resp.raise_for_status()
InfoBar.success("成功", "配置已更新", parent=self)
self.load_data()
except Exception as e:
InfoBar.error("失败", f"更新失败: {str(e)}", parent=self)
class VIPConfigTab(QWidget):
"""VIP配置标签页"""
def __init__(self, api_client, parent=None):
super().__init__(parent)
self.api_client = api_client
self.v_layout = QVBoxLayout(self)
self.v_layout.setContentsMargins(0, 0, 0, 0)
self.card = CardWidget(self)
self.card_layout = QGridLayout(self.card)
self.card_layout.setContentsMargins(20, 20, 20, 20)
self.card_layout.setSpacing(20)
self.price_spin = SpinBox()
self.price_spin.setRange(0, 9999)
self.price_spin.setValue(35)
self.card_layout.addWidget(StrongBodyLabel("VIP 价格 (元/月):", self.card), 0, 0)
self.card_layout.addWidget(self.price_spin, 0, 1)
self.quota_spin = SpinBox()
self.quota_spin.setRange(0, 9999)
self.quota_spin.setValue(25)
self.card_layout.addWidget(StrongBodyLabel("每日配额 (次):", self.card), 1, 0)
self.card_layout.addWidget(self.quota_spin, 1, 1)
self.multiplier_spin = SpinBox()
self.multiplier_spin.setRange(100, 1000)
self.multiplier_spin.setValue(160)
self.card_layout.addWidget(StrongBodyLabel("积分倍数 (%):", self.card), 2, 0)
self.card_layout.addWidget(self.multiplier_spin, 2, 1)
self.btn_save = PrimaryPushButton(FIF.SAVE, "保存配置", self.card)
self.btn_save.clicked.connect(self.save_data)
self.card_layout.addWidget(self.btn_save, 3, 0, 1, 2)
self.v_layout.addWidget(self.card)
self.v_layout.addStretch(1)
def load_data(self):
if not self.api_client:
return
try:
resp = requests.get(f"{self.api_client.base_url}/admin/config/vip", headers=self.api_client.get_headers(), timeout=10)
resp.raise_for_status()
data = resp.json()
# 查找VIP配置
for item in data:
if item.get('vip_type') == 'vip':
self.price_spin.setValue(int(item.get('price', 35)))
self.quota_spin.setValue(int(item.get('daily_quota', 25)))
self.multiplier_spin.setValue(int(item.get('points_multiplier', 1.6) * 100))
break
except Exception as e:
print(f"Load VIP config error: {e}")
def save_data(self):
if not self.api_client:
InfoBar.warning("未连接", "请先连接到后端服务器", parent=self)
return
try:
payload = {
"price": self.price_spin.value(),
"daily_quota": self.quota_spin.value(),
"points_multiplier": self.multiplier_spin.value() / 100.0
}
resp = requests.put(f"{self.api_client.base_url}/admin/config/vip/vip", json=payload, headers=self.api_client.get_headers(), timeout=10)
resp.raise_for_status()
InfoBar.success("保存成功", "VIP配置已更新", parent=self)
except Exception as e:
InfoBar.error("保存失败", str(e), parent=self)
class CheckInConfigTab(QWidget):
"""签到配置标签页"""
def __init__(self, api_client, parent=None):
super().__init__(parent)
self.api_client = api_client
self.v_layout = QVBoxLayout(self)
self.btn_add = PrimaryPushButton(FIF.ADD, "新增签到规则", self)
self.v_layout.addWidget(self.btn_add)
self.table = TableWidget(self)
self.table.setColumnCount(4)
self.table.setHorizontalHeaderLabels(["连续天数", "基础积分", "额外奖励", "总计"])
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.v_layout.addWidget(self.table)
self.load_mock_data()
def load_mock_data(self):
"""加载模拟数据"""
data = [
(1, 10, 0, 10),
(2, 10, 5, 15),
(3, 10, 10, 20),
(7, 10, 50, 60),
]
self.table.setRowCount(len(data))
for i, (days, base, bonus, total) in enumerate(data):
self.table.setItem(i, 0, QTableWidgetItem(f"{days}"))
self.table.setItem(i, 1, QTableWidgetItem(str(base)))
self.table.setItem(i, 2, QTableWidgetItem(str(bonus)))
self.table.setItem(i, 3, QTableWidgetItem(str(total)))
def load_data(self):
if not self.api_client:
return
try:
resp = requests.get(f"{self.api_client.base_url}/admin/config/checkin", headers=self.api_client.get_headers(), timeout=10)
resp.raise_for_status()
data = resp.json()
self.table.setRowCount(len(data))
for i, item in enumerate(data):
self.table.setItem(i, 0, QTableWidgetItem(f"{item.get('consecutive_days', 0)}"))
self.table.setItem(i, 1, QTableWidgetItem(str(item.get('base_points', 0))))
self.table.setItem(i, 2, QTableWidgetItem(str(item.get('bonus_points', 0))))
self.table.setItem(i, 3, QTableWidgetItem(str(item.get('total_points', 0))))
# Store full data
self.table.item(i, 0).setData(Qt.UserRole, item)
InfoBar.success("加载成功", f"已加载 {len(data)} 个签到规则", parent=self)
except Exception as e:
InfoBar.error("加载失败", str(e), parent=self)
class StatsTab(QWidget):
"""数据统计标签页"""
def __init__(self, api_client, parent=None):
super().__init__(parent)
self.api_client = api_client
self.v_layout = QVBoxLayout(self)
self.card = CardWidget(self)
self.card_layout = QVBoxLayout(self.card)
self.card_layout.addWidget(SubtitleLabel("今日数据概览", self.card))
self.grid = QGridLayout()
self.grid.addWidget(BodyLabel("今日活跃用户:"), 0, 0)
self.lbl_active_users = StrongBodyLabel("-", self.card)
self.grid.addWidget(self.lbl_active_users, 0, 1)
self.grid.addWidget(BodyLabel("今日签到:"), 1, 0)
self.lbl_checkins = StrongBodyLabel("-", self.card)
self.grid.addWidget(self.lbl_checkins, 1, 1)
self.grid.addWidget(BodyLabel("今日积分消耗:"), 2, 0)
self.lbl_points = StrongBodyLabel("-", self.card)
self.grid.addWidget(self.lbl_points, 2, 1)
self.grid.addWidget(BodyLabel("今日功能调用:"), 3, 0)
self.lbl_feature_usage = StrongBodyLabel("-", self.card)
self.grid.addWidget(self.lbl_feature_usage, 3, 1)
self.card_layout.addLayout(self.grid)
# 刷新按钮
self.btn_refresh = PrimaryPushButton(FIF.SYNC, "刷新统计", self.card)
self.btn_refresh.clicked.connect(self.load_data)
self.card_layout.addWidget(self.btn_refresh)
self.v_layout.addWidget(self.card)
self.v_layout.addStretch(1)
def load_data(self):
if not self.api_client:
InfoBar.warning("未连接", "请先连接到后端服务器", parent=self)
return
try:
resp = requests.get(f"{self.api_client.base_url}/admin/stats/today", headers=self.api_client.get_headers(), timeout=10)
resp.raise_for_status()
data = resp.json()
self.lbl_active_users.setText(str(data.get('active_users', 0)))
self.lbl_checkins.setText(str(data.get('check_ins', 0)))
self.lbl_points.setText(str(data.get('points_consumed', 0)))
self.lbl_feature_usage.setText(str(data.get('feature_usage', 0)))
InfoBar.success("刷新成功", "统计数据已更新", parent=self)
except Exception as e:
InfoBar.error("加载失败", f"无法加载统计数据: {str(e)}", parent=self)
print(f"Load stats error: {e}")

View File

@@ -1,243 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Core 快速发布脚本
仅执行:构建 Core -> 上传 Core -> 更新数据库
跳过 Shell 的构建和上传
"""
import os
import sys
import shutil
import subprocess
import argparse
import zipfile
import json
from pathlib import Path
from datetime import datetime
import paramiko
# ==================== 配置区域 ====================
PROJECT_ROOT = Path(__file__).parent.parent.absolute()
DESIGNER_DIR = PROJECT_ROOT / "Designer"
DIST_CORE_DIR = DESIGNER_DIR / "dist_core" # Core 构建输出目录
CONFIG_FILE = Path(__file__).parent / "deploy_config.json"
# 颜色输出
try:
import colorama
colorama.init()
GREEN = '\033[92m'
YELLOW = '\033[93m'
RED = '\033[91m'
BLUE = '\033[94m'
RESET = '\033[0m'
except ImportError:
GREEN = YELLOW = RED = BLUE = RESET = ''
# ==================== 工具函数 ====================
def print_step(message):
print(f"\n{BLUE}{'='*60}{RESET}")
print(f"{GREEN}{message}{RESET}")
print(f"{BLUE}{'='*60}{RESET}\n")
def print_success(message):
print(f"{GREEN}{message}{RESET}")
def print_error(message):
print(f"{RED}{message}{RESET}")
def run_command(command, cwd=None):
print(f" 执行: {command}")
try:
result = subprocess.run(
command,
cwd=cwd,
shell=True,
check=True,
capture_output=True,
text=True,
encoding='utf-8',
errors='replace'
)
if result.stdout:
print(f" 输出: {result.stdout.strip()[:200]}...") # 只打印前200字符
return result
except subprocess.CalledProcessError as e:
print_error(f"命令执行失败: {e}")
if e.stderr:
print(f" 错误: {e.stderr}")
sys.exit(1)
def load_config():
if not CONFIG_FILE.exists():
print_error("未找到配置文件 deploy_config.json")
sys.exit(1)
with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
# ==================== 核心步骤 ====================
def step1_build_core():
print_step("步骤 1: 构建 Core")
# 检查目录
if not DESIGNER_DIR.exists():
print_error(f"Designer 目录不存在: {DESIGNER_DIR}")
sys.exit(1)
# 执行构建
print(" 正在构建 Core (npm run build:core)...")
os.chdir(DESIGNER_DIR)
run_command("npm run build:core", cwd=DESIGNER_DIR)
# 验证
if not DIST_CORE_DIR.exists():
print_error(f"Core 构建失败,输出目录不存在: {DIST_CORE_DIR}")
sys.exit(1)
print_success("Core 构建完成")
def step2_upload_core(version, config):
print_step("步骤 2: 上传 Core 到服务器")
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
print(f" 连接服务器: {config['host']}")
ssh.connect(
hostname=config['host'],
port=int(config.get('port', 22)),
username=config['username'],
password=config.get('password', ''),
timeout=30
)
print_success("SSH 连接成功")
sftp = ssh.open_sftp()
remote_base = config['remote_path']
remote_core_dir = f"{remote_base}/core/{version}"
# 创建远程目录
print(f" 创建远程目录: {remote_core_dir}")
ssh.exec_command(f"mkdir -p {remote_core_dir}")
# 递归上传
print(" 开始上传文件...")
upload_count = 0
for root, dirs, files in os.walk(DIST_CORE_DIR):
relative_root = Path(root).relative_to(DIST_CORE_DIR)
remote_root = f"{remote_core_dir}/{relative_root}".replace("\\", "/").rstrip("/")
if str(relative_root) == ".":
remote_root = remote_core_dir
# 确保子目录存在
try:
sftp.stat(remote_root)
except IOError:
sftp.mkdir(remote_root)
for file in files:
local_file = Path(root) / file
remote_file = f"{remote_root}/{file}"
sftp.put(str(local_file), remote_file)
upload_count += 1
if upload_count % 10 == 0:
print(f" 已上传 {upload_count} 个文件...", end='\r')
print(f"\n 共上传 {upload_count} 个文件")
print_success("Core 上传完成")
sftp.close()
ssh.close()
except Exception as e:
print_error(f"上传失败: {e}")
raise
def step3_update_db(version, config):
print_step("步骤 3: 更新数据库")
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
ssh.connect(
hostname=config['host'],
port=int(config.get('port', 22)),
username=config['username'],
password=config.get('password', ''),
timeout=30
)
# 构造 SQL
sql = f"UPDATE plugin_groups SET current_version_file = 'core-v{version}.zip' WHERE name = 'default';"
# 数据库配置
db_conf = config.get('mysql', {})
db_user = db_conf.get('username', 'designer_user')
db_pass = db_conf.get('password', 'DesignerPass123!')
db_name = db_conf.get('database', 'designer_db')
container_name = "designercep_db"
print(f" 更新 'default' 分组版本为: core-v{version}.zip")
docker_cmd = f'docker exec -i {container_name} mysql -u{db_user} -p{db_pass} {db_name} -e "{sql}"'
stdin, stdout, stderr = ssh.exec_command(docker_cmd)
exit_status = stdout.channel.recv_exit_status()
if exit_status == 0:
print_success("数据库更新成功")
else:
print_error(f"数据库更新失败: {stderr.read().decode().strip()}")
raise Exception("DB Update Failed")
ssh.close()
except Exception as e:
print_error(f"数据库操作失败: {e}")
raise
def step4_clean_cache():
print_step("步骤 4: 清除本地缓存")
cache_dir = Path.home() / "AppData" / "Roaming" / "DesignerCache"
if cache_dir.exists():
try:
shutil.rmtree(cache_dir)
print_success(f"已清除: {cache_dir}")
except Exception as e:
print_error(f"清除失败: {e}")
else:
print(" 无需清除")
# ==================== 主入口 ====================
def main():
parser = argparse.ArgumentParser(description='Core 快速发布脚本')
parser.add_argument('--version', '-v', required=True, help='版本号 (如 1.0.7)')
args = parser.parse_args()
version = args.version
print(f"{BLUE}开始发布 Core - 版本: {version}{RESET}")
try:
config = load_config()
step1_build_core()
step2_upload_core(version, config)
step3_update_db(version, config)
step4_clean_cache()
print_step("🎉 发布全部完成!")
print(f" 版本: {version}")
print(f" 时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
except Exception as e:
print_error(f"\n发布过程中止: {e}")
sys.exit(1)
if __name__ == "__main__":
main()

34
AdminTool/deploy_tool.py Normal file
View File

@@ -0,0 +1,34 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
快速启动部署工具(跳过组管理功能)
"""
import sys
from PyQt5.QtWidgets import QApplication
from admin_gui import AdminWindow
if __name__ == "__main__":
print("="*60)
print("🚀 启动部署工具...")
print("="*60)
print("\n💡 提示:如果看到后端 API 错误,可以忽略")
print(" 部署功能不依赖后端 API可以正常使用\n")
app = QApplication(sys.argv)
window = AdminWindow()
# 直接切换到部署标签页
# FluentWindow uses switchTo
window.switchTo(window.deploy_interface)
# 启用标签页(即使连接失败)
# FluentWindow handles navigation enabling differently, but generally it's enabled by default
# window.tabs.setEnabled(True) # No longer needed/available
window.show()
print("✅ 工具已启动")
print(" 请切换到「自动化部署」标签页开始使用\n")
sys.exit(app.exec())

View File

@@ -1,5 +1,4 @@
PyQt5 PyQt5
requests requests
paramiko paramiko
pymysql PyQt-Fluent-Widgets
colorama

View File

@@ -0,0 +1,127 @@
# -*- coding: utf-8 -*-
"""
测试数据库连接和 app_deployments 表(通过 SSH 隧道)
"""
import json
import pymysql
from datetime import datetime
import sys
import io
from sshtunnel import SSHTunnelForwarder
# 设置 stdout 为 UTF-8
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
def load_config():
with open('deploy_config.json', 'r', encoding='utf-8') as f:
return json.load(f)
def test_connection():
print("="*60)
print("测试 MySQL 数据库连接(通过 SSH 隧道)")
print("="*60)
config = load_config()
mysql_config = config['mysql']
print(f"\n📋 SSH 服务器:")
print(f" 地址: {config['host']}")
print(f" 端口: {config['port']}")
print(f" 用户: {config['username']}")
print(f"\n📋 MySQL 配置:")
print(f" 主机: {mysql_config['host']}")
print(f" 端口: {mysql_config['port']}")
print(f" 用户: {mysql_config['username']}")
print(f" 数据库: {mysql_config['database']}")
tunnel = None
try:
print("\n🔌 创建 SSH 隧道...")
tunnel = SSHTunnelForwarder(
(config['host'], int(config.get('port', 22))),
ssh_username=config['username'],
ssh_password=config['password'],
remote_bind_address=('127.0.0.1', int(mysql_config.get('port', 3306)))
)
tunnel.start()
print(f"✅ SSH 隧道已建立,本地端口: {tunnel.local_bind_port}")
print("\n🔌 连接 MySQL...")
conn = pymysql.connect(
host='127.0.0.1',
port=tunnel.local_bind_port,
user=mysql_config['username'],
password=mysql_config['password'],
database=mysql_config['database'],
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor
)
print("✅ MySQL 连接成功!")
# 测试创建表
print("\n📝 创建/检查 app_deployments 表...")
with conn.cursor() as cursor:
sql = """
CREATE TABLE IF NOT EXISTS app_deployments (
id INT PRIMARY KEY AUTO_INCREMENT,
version VARCHAR(50) UNIQUE NOT NULL,
deployed_at DATETIME NOT NULL,
is_current BOOLEAN DEFAULT FALSE,
file_size_mb DECIMAL(10, 2),
comment VARCHAR(500),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
"""
cursor.execute(sql)
conn.commit()
print("✅ 表已就绪")
# 查看表结构
print("\n📊 表结构:")
with conn.cursor() as cursor:
cursor.execute("DESCRIBE app_deployments")
for row in cursor.fetchall():
print(f" {row['Field']}: {row['Type']} {'(主键)' if row['Key'] == 'PRI' else ''}")
# 查看现有数据
print("\n📚 现有部署记录:")
with conn.cursor() as cursor:
cursor.execute("SELECT * FROM app_deployments ORDER BY deployed_at DESC")
records = cursor.fetchall()
if records:
for i, rec in enumerate(records, 1):
status = "✅ 当前" if rec['is_current'] else ""
print(f"\n [{i}] {rec['version']} {status}")
print(f" 部署时间: {rec['deployed_at']}")
print(f" 大小: {rec['file_size_mb']} MB")
print(f" 备注: {rec['comment'] or ''}")
else:
print(" (暂无记录)")
conn.close()
print("\n" + "="*60)
print("✅ 测试完成!数据库一切正常")
print("="*60)
except Exception as e:
print(f"\n❌ 测试失败: {e}")
print("\n请检查:")
print(" 1. SSH 服务器是否可访问")
print(" 2. deploy_config.json 配置是否正确")
print(" 3. 服务器上 MySQL 服务是否运行")
print(" 4. 数据库用户权限是否足够")
return False
finally:
if tunnel:
tunnel.stop()
print("\n🔌 SSH 隧道已关闭")
return True
if __name__ == "__main__":
test_connection()

24
AdminTool/test_launch.py Normal file
View File

@@ -0,0 +1,24 @@
import sys
from PyQt5.QtWidgets import QApplication
try:
from admin_gui import AdminWindow
print("Successfully imported AdminWindow")
except Exception as e:
print(f"Failed to import AdminWindow: {e}")
sys.exit(1)
def main():
app = QApplication(sys.argv)
try:
window = AdminWindow()
print("Successfully instantiated AdminWindow")
# Don't show or exec, just check if it crashes on init
# window.show()
except Exception as e:
print(f"Failed to instantiate AdminWindow: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,113 @@
# -*- coding: utf-8 -*-
"""
测试版本管理系统(使用 SSH + JSON 文件)
"""
import json
import sys
import io
import paramiko
# 设置 stdout 为 UTF-8
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
def load_config():
with open('deploy_config.json', 'r', encoding='utf-8') as f:
return json.load(f)
def test_connection():
print("="*60)
print("测试版本管理系统SSH + JSON 文件)")
print("="*60)
config = load_config()
print(f"\n📋 SSH 服务器:")
print(f" 地址: {config['host']}")
print(f" 端口: {config['port']}")
print(f" 用户: {config['username']}")
ssh = None
sftp = None
remote_file = '/var/www/app_versions/.deployments.json'
try:
print("\n🔌 连接 SSH...")
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(
hostname=config['host'],
port=int(config.get('port', 22)),
username=config['username'],
password=config['password'],
timeout=10
)
print("✅ SSH 连接成功!")
# 测试创建目录
print("\n📁 检查/创建版本目录...")
stdin, stdout, stderr = ssh.exec_command('mkdir -p /var/www/app_versions')
stdout.channel.recv_exit_status()
print("✅ 目录已就绪: /var/www/app_versions/")
# 测试读写 JSON 文件
print(f"\n📝 测试版本记录文件: {remote_file}")
sftp = ssh.open_sftp()
try:
# 尝试读取现有文件
with sftp.open(remote_file, 'r') as f:
content = f.read().decode('utf-8')
data = json.loads(content)
print(f"✅ 文件已存在,读取成功")
except IOError:
# 文件不存在,创建新文件
print(" 文件不存在,创建新文件...")
data = {'deployments': []}
with sftp.open(remote_file, 'w') as f:
f.write(json.dumps(data, indent=2, ensure_ascii=False).encode('utf-8'))
print("✅ 文件创建成功")
# 显示现有记录
print("\n📚 现有部署记录:")
if data['deployments']:
for i, rec in enumerate(sorted(data['deployments'], key=lambda x: x['deployed_at'], reverse=True), 1):
status = "✅ 当前" if rec.get('is_current') else ""
print(f"\n [{i}] {rec['version']} {status}")
print(f" 部署时间: {rec['deployed_at']}")
print(f" 大小: {rec.get('file_size_mb', '-')} MB")
print(f" 备注: {rec.get('comment') or ''}")
else:
print(" (暂无记录)")
# 测试写入
print("\n✏️ 测试写入...")
with sftp.open(remote_file, 'w') as f:
f.write(json.dumps(data, indent=2, ensure_ascii=False).encode('utf-8'))
print("✅ 写入成功")
print("\n" + "="*60)
print("✅ 测试完成!版本管理系统一切正常")
print("="*60)
print("\n💡 系统说明:")
print(" - 不需要 MySQL 数据库")
print(" - 版本信息存储在服务器的 JSON 文件中")
print(" - 文件位置: /var/www/app_versions/.deployments.json")
except Exception as e:
print(f"\n❌ 测试失败: {e}")
print("\n请检查:")
print(" 1. SSH 服务器是否可访问")
print(" 2. deploy_config.json 配置是否正确")
print(" 3. 服务器上是否有写入权限")
return False
finally:
if sftp:
sftp.close()
if ssh:
ssh.close()
return True
if __name__ == "__main__":
test_connection()

137
Caddyfile
View File

@@ -1,30 +1,25 @@
# DesignerCEP Caddy 配置文件 # ==================== DesignerCEP Caddy 配置 ====================
# #
# 部署架构 # 用途
# - app.aidg168.uk → 前端应用(登录 + 主功能 # - app.aidg168.uk → 前端应用(静态文件
# - backend.aidg168.uk → 后端 API # - backend.aidg168.uk → 后端 API(反向代理到 FastAPI
# #
# 使用方法: # 部署位置:/etc/caddy/Caddyfile
# 1. 将此文件上传到服务器 /etc/caddy/Caddyfile
# 2. sudo caddy validate --config /etc/caddy/Caddyfile
# 3. sudo systemctl restart caddy
{ {
# ==================== 全局配置 ====================
# 邮箱(用于 HTTPS 证书) # 邮箱(用于 HTTPS 证书)
# 如果使用 Cloudflare关闭自动 HTTPS email admin@aidg168.uk
# auto_https off
# 如果不使用 Cloudflare保持默认自动申请证书
# 使用 Cloudflare 橙云代理,关闭自动 HTTPS
auto_https off
}
# ==================== 前端应用 ==================== # ==================== 前端应用 ====================
http://app.aidg168.uk, https://app.aidg168.uk { http://app.aidg168.uk, https://app.aidg168.uk {
app.aidg168.uk { # 静态文件根目录(已修复路径)
# 静态文件根目录 root * /var/www/app
root * /var/www/DesignerCEP/Server/static/app
# SPA 路由支持(所有请求返回 index.html # SPA 路由支持(所有请求返回 index.html
# SPA 路由支持(重要!) try_files {path} /index.html
# 所有路由都返回 index.html让 Vue Router 处理
# 提供静态文件 # 提供静态文件
file_server file_server
@@ -32,100 +27,60 @@ app.aidg168.uk {
# ========== 缓存策略 ========== # ========== 缓存策略 ==========
# HTML 不缓存 # HTML 不缓存
# HTML 文件不缓存(确保更新即时生效) @html path *.html
@html { header @html Cache-Control "no-cache, no-store, must-revalidate"
path *.html
}
header @html {
Cache-Control "no-cache, no-store, must-revalidate"
Pragma "no-cache"
Expires "0"
}
# JS/CSS 长期缓存 # JS/CSS 长期缓存
# JS/CSS 长期缓存(文件名有 hash可以安全缓存 @assets path *.js *.css *.woff *.woff2
@assets { header @assets Cache-Control "public, max-age=31536000, immutable"
path *.js *.css *.woff *.woff2 *.ttf *.eot
}
header @assets {
Cache-Control "public, max-age=31536000, immutable"
}
# 图片缓存 # 图片缓存
@images path *.png *.jpg *.jpeg *.gif *.svg *.ico @images path *.png *.jpg *.jpeg *.gif *.svg *.ico
@images { header @images Cache-Control "public, max-age=2592000"
path *.png *.jpg *.jpeg *.gif *.svg *.ico *.webp
}
header @images {
Cache-Control "public, max-age=2592000"
}
# ========== 安全头 ========== # ========== 安全头 ==========
header { header {
# 允许 CEP 插件通过 iframe 嵌入 # 允许 CEP 插件通过 iframe 嵌入
# 允许在 iframe 中加载CEP 需要) # 注释掉 X-Frame-Options 以支持 Photoshop 插件
X-Frame-Options "SAMEORIGIN" # X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
# 防止 MIME 类型嗅探
X-XSS-Protection "1; mode=block" X-XSS-Protection "1; mode=block"
# XSS 保护
-Server -Server
# 隐藏服务器信息
} }
# ========== 压缩 ========== # ========== 压缩 ==========
# ========== 压缩 ==========
encode {
gzip 6
zstd
}
# ========== 日志 ==========
log {
output file /var/log/caddy/app.aidg168.uk.log {
roll_size 50mb
roll_keep 10
roll_keep_for 720h
}
format json
level INFO
}
}
# ==================== 后端 API ====================
backend.aidg168.uk {
# 反向代理到 FastAPI
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}
header_up X-Forwarded-Host {host}
# 超时设置
transport http {
dial_timeout 5s
response_header_timeout 30s
}
}
encode gzip encode gzip
# ========== 日志 ========== # ========== 日志 ==========
log { log {
output file /var/log/caddy/app.log { output file /var/log/caddy/app.log {
output file /var/log/caddy/backend.aidg168.uk.log { roll_size 50mb
roll_keep 5 roll_keep 5
roll_keep 10 }
roll_keep_for 720h
} }
format json
level INFO
} }
# ==================== 后端 API ==================== # ==================== 后端 API ====================
# ==================== 主域名重定向(可选)==================== http://backend.aidg168.uk, https://backend.aidg168.uk {
aidg168.uk, www.aidg168.uk { # 反向代理到 Docker 容器localhost:8000
# 重定向到应用 # Caddy 在宿主机运行,需要通过 localhost 访问 Docker 容器
reverse_proxy localhost:8000 {
header_up X-Real-IP {remote_host}
}
encode gzip
log {
output file /var/log/caddy/backend.log {
roll_size 50mb
roll_keep 5
}
}
}
# ==================== 主域名重定向 ====================
http://aidg168.uk, http://www.aidg168.uk, https://aidg168.uk, https://www.aidg168.uk {
redir https://app.aidg168.uk{uri} permanent
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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