20251222
56
%APPDATA%/Adobe/CEP/extensions/AdminPanel/CSXS/manifest.xml
Normal 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>
|
||||||
|
|
||||||
146
%APPDATA%/Adobe/CEP/extensions/AdminPanel/index.html
Normal 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>
|
||||||
|
|
||||||
41
%APPDATA%/Adobe/CEP/extensions/AdminPanel/检查并启用CEP.bat
Normal 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
@@ -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;
|
||||||
|
|
||||||
49
AdminPanel/dev.cep.config.ts
Normal 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
@@ -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
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
0
AdminPanel/plugins/README.md
Normal file
6
AdminPanel/plugins/global.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
__adobe_cep__: any;
|
||||||
|
}
|
||||||
|
}
|
||||||
4
AdminPanel/plugins/index.ts
Normal 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"
|
||||||
98
AdminPanel/plugins/jsx/cepPlugin.ts
Normal 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({
|
||||||
|
// 方案 B:CEP 插件打开后直接跳转到服务器 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()
|
||||||
|
}
|
||||||
181
AdminPanel/plugins/jsx/copyCepToDev.ts
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
AdminPanel/plugins/jsx/createConfig.ts
Normal 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);
|
||||||
|
}
|
||||||
3
AdminPanel/plugins/jsx/template/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
## 目录说明
|
||||||
|
- cep 插件壳模板文件
|
||||||
|
- cep.config.json 配置文件
|
||||||
65
AdminPanel/plugins/jsx/template/cep.config.ts
Normal 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;
|
||||||
21
AdminPanel/plugins/jsx/template/cep/CSXS/manifest.xml
Normal 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>
|
||||||
|
Before Width: | Height: | Size: 475 B After Width: | Height: | Size: 475 B |
|
Before Width: | Height: | Size: 846 B After Width: | Height: | Size: 846 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 475 B After Width: | Height: | Size: 475 B |
|
Before Width: | Height: | Size: 846 B After Width: | Height: | Size: 846 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
16
AdminPanel/plugins/jsx/template/cep/index.html
Normal 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>
|
||||||
15
AdminPanel/plugins/jsx/template/debug.ts
Normal 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>
|
||||||
|
`
|
||||||
|
}
|
||||||
15
AdminPanel/plugins/jsx/template/html.ts
Normal 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>`
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|
||||||
|
`
|
||||||
|
}
|
||||||
42
AdminPanel/plugins/jsx/types.ts
Normal 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
|
||||||
|
}
|
||||||
18
AdminPanel/plugins/jsx/utils/cepDir.ts
Normal 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
|
||||||
|
}
|
||||||
25
AdminPanel/plugins/jsx/utils/fs.ts
Normal 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'
|
||||||
|
}
|
||||||
1
AdminPanel/plugins/utils/README.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
## 提供工具
|
||||||
99
AdminPanel/plugins/utils/cep/cep-types.d.ts
vendored
Normal 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) => {};
|
||||||
|
}
|
||||||
36
AdminPanel/plugins/utils/cep/cepPath.ts
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
701
AdminPanel/plugins/utils/cep/cep_engine_extensions.js
Normal 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();
|
||||||
|
};
|
||||||
|
|
||||||
|
})();
|
||||||
788
AdminPanel/plugins/utils/cep/csinterface.d.ts
vendored
Normal 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;
|
||||||
|
}
|
||||||
1263
AdminPanel/plugins/utils/cep/csinterface.js
Normal file
30
AdminPanel/plugins/utils/cep/csinterfaceEx.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
3
AdminPanel/plugins/utils/cep/es-types/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export type Scripts = {
|
||||||
|
[key: string]: (a: any, ...ags: any) => any;
|
||||||
|
};
|
||||||
64
AdminPanel/plugins/utils/cep/index.ts
Normal 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"
|
||||||
87
AdminPanel/plugins/utils/cep/node.ts
Normal 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
@@ -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;
|
||||||
620
AdminPanel/plugins/utils/cep/vulcan.js
Normal 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;
|
||||||
65
AdminPanel/plugins/utils/dom/cursor.js
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
313
AdminPanel/plugins/utils/dom/dom.js
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
113
AdminPanel/plugins/utils/dom/index.js
Normal 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);
|
||||||
|
}
|
||||||
64
AdminPanel/plugins/utils/drag/dom.js
Normal 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;
|
||||||
|
}
|
||||||
174
AdminPanel/plugins/utils/drag/drag.js
Normal 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 || {})
|
||||||
|
}
|
||||||
|
}
|
||||||
203
AdminPanel/plugins/utils/drag/dragFileToFolder.js
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
68
AdminPanel/plugins/utils/drag/dragFileToImage.js
Normal 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);//转换失败
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
230
AdminPanel/plugins/utils/drag/dragFolder.js
Normal 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;
|
||||||
189
AdminPanel/plugins/utils/drag/dragtagToimage.ts
Normal 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;
|
||||||
1
AdminPanel/plugins/utils/json/.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.js linguist-detectable=false
|
||||||
3
AdminPanel/plugins/utils/path.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export function join(...arr: string[]) {
|
||||||
|
return arr.join('/')
|
||||||
|
}
|
||||||
97
AdminPanel/plugins/utils/utils/aeft.ts
Normal 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 [];
|
||||||
|
};
|
||||||
253
AdminPanel/plugins/utils/utils/bolt.ts
Normal 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);
|
||||||
|
}
|
||||||
|
};
|
||||||
28
AdminPanel/plugins/utils/utils/cep.ts
Normal 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);
|
||||||
|
};
|
||||||
175
AdminPanel/plugins/utils/utils/ppro.ts
Normal 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",
|
||||||
|
];
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
35
AdminPanel/src/App.vue
Normal 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>
|
||||||
|
|
||||||
40
AdminPanel/src/hooks/useTheme.ts
Normal 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
@@ -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 已启动')
|
||||||
|
|
||||||
22
AdminPanel/src/router/index.ts
Normal 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
|
||||||
|
|
||||||
130
AdminPanel/src/style/base/flex.css
Normal 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;
|
||||||
|
}
|
||||||
228
AdminPanel/src/style/base/flex.less
Normal 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;
|
||||||
|
}
|
||||||
1605
AdminPanel/src/style/base/layout.less
Normal file
14
AdminPanel/src/style/base/myReset.css
Normal 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
@@ -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;
|
||||||
|
}
|
||||||
37
AdminPanel/src/style/global.less
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
2
AdminPanel/src/style/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
// 全局样式入口
|
||||||
|
import './global.less'
|
||||||
3
AdminPanel/src/style/theme/common.less
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
@appPadding: 0px 16px;
|
||||||
|
@appBgColor: #202020;
|
||||||
|
@appTextColor: #fff;
|
||||||
23
AdminPanel/src/style/theme/theme.ts
Normal 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
@@ -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();
|
||||||
7
AdminPanel/src/utils/cep/fs.ts
Normal 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')
|
||||||
9
AdminPanel/src/utils/cep/path.ts
Normal 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')
|
||||||
|
|
||||||
17
AdminPanel/src/utils/cep/tool.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
181
AdminPanel/src/utils/logger.ts
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
146
AdminPanel/src/utils/theme.ts
Normal 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);
|
||||||
|
}
|
||||||
|
};
|
||||||
132
AdminPanel/src/view/IframePage.vue
Normal 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
@@ -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
@@ -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" }]
|
||||||
|
}
|
||||||
9
AdminPanel/tsconfig.node.json
Normal 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
@@ -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')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
56
AdminPanel/接收PS主题示例.js
Normal 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' }, '*');
|
||||||
|
|
||||||
@@ -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.zip(CEP 扩展下载)...")
|
|
||||||
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()
|
|
||||||
|
|
||||||
461
AdminTool/config_interface.py
Normal 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}")
|
||||||
@@ -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
@@ -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())
|
||||||
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
PyQt5
|
PyQt5
|
||||||
requests
|
requests
|
||||||
paramiko
|
paramiko
|
||||||
pymysql
|
PyQt-Fluent-Widgets
|
||||||
colorama
|
|
||||||
|
|||||||
127
AdminTool/test_db_connection.py
Normal 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
@@ -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()
|
||||||
113
AdminTool/test_version_system.py
Normal 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
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||