功能升级
This commit is contained in:
+243
-1
@@ -1 +1,243 @@
|
||||
{}
|
||||
{
|
||||
"胜多负少": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": -1,
|
||||
"type": "KSampler",
|
||||
"pos": [
|
||||
980,
|
||||
197
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
262
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"label": "模型",
|
||||
"localized_name": "模型",
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"label": "正面条件",
|
||||
"localized_name": "正面条件",
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"label": "负面条件",
|
||||
"localized_name": "负面条件",
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"label": "Latent",
|
||||
"localized_name": "Latent图像",
|
||||
"name": "latent_image",
|
||||
"type": "LATENT",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "随机种",
|
||||
"name": "seed",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "seed"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "步数",
|
||||
"name": "steps",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "steps"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "CFG",
|
||||
"name": "cfg",
|
||||
"type": "FLOAT",
|
||||
"widget": {
|
||||
"name": "cfg"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "采样器",
|
||||
"name": "sampler_name",
|
||||
"type": "COMBO",
|
||||
"widget": {
|
||||
"name": "sampler_name"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "调度器",
|
||||
"name": "scheduler",
|
||||
"type": "COMBO",
|
||||
"widget": {
|
||||
"name": "scheduler"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "降噪",
|
||||
"name": "denoise",
|
||||
"type": "FLOAT",
|
||||
"widget": {
|
||||
"name": "denoise"
|
||||
},
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"label": "Latent",
|
||||
"localized_name": "Latent",
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"slot_index": 0,
|
||||
"links": []
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.56",
|
||||
"Node name for S&R": "KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
156680208700286,
|
||||
"randomize",
|
||||
20,
|
||||
8,
|
||||
"euler",
|
||||
"normal",
|
||||
1
|
||||
],
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"id": -1,
|
||||
"type": "VAEDecode",
|
||||
"pos": [
|
||||
1326,
|
||||
199
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
46
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"label": "Latent",
|
||||
"localized_name": "Latent",
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"label": "VAE",
|
||||
"localized_name": "vae",
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"label": "图像",
|
||||
"localized_name": "图像",
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"slot_index": 0,
|
||||
"links": []
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.56",
|
||||
"Node name for S&R": "VAEDecode"
|
||||
},
|
||||
"index": 1
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
null,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
4,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
null,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
6,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
null,
|
||||
0,
|
||||
0,
|
||||
2,
|
||||
7,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
null,
|
||||
0,
|
||||
0,
|
||||
3,
|
||||
5,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
19,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
null,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
4,
|
||||
"VAE"
|
||||
]
|
||||
],
|
||||
"external": [
|
||||
[
|
||||
1,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"widgets_values": [
|
||||
156680208700286,
|
||||
"randomize",
|
||||
20,
|
||||
8,
|
||||
"euler",
|
||||
"normal",
|
||||
1
|
||||
]
|
||||
}
|
||||
}
|
||||
+310
-285
@@ -1,29 +1,29 @@
|
||||
import { app } from "/scripts/app.js";
|
||||
import { ComfyWidgets } from "/scripts/widgets.js";
|
||||
import { GroupNodeConfig,GroupNodeHandler } from "/extensions/core/groupNode.js";
|
||||
import { $el,ComfyDialog } from "../../../scripts/ui.js";
|
||||
import { GroupNodeConfig, GroupNodeHandler } from "/extensions/core/groupNode.js";
|
||||
import { $el, ComfyDialog } from "../../../scripts/ui.js";
|
||||
import { api } from "../../../scripts/api.js";
|
||||
|
||||
// Displays input text on a node
|
||||
const CONVERTED_TYPE = "converted-widget";
|
||||
const type2paramType={
|
||||
"STRING": "text",
|
||||
"combo":"select",
|
||||
"number":"number",
|
||||
"toggle":"toggle",
|
||||
"BOOLEAN":"switch",
|
||||
"text":"text",
|
||||
"string":"text",
|
||||
"customtext":"customtext"
|
||||
const type2paramType = {
|
||||
"STRING": "text",
|
||||
"combo": "select",
|
||||
"number": "number",
|
||||
"toggle": "toggle",
|
||||
"BOOLEAN": "switch",
|
||||
"text": "text",
|
||||
"string": "text",
|
||||
"customtext": "customtext"
|
||||
}
|
||||
var update_comfyui_button = null;
|
||||
var switch_comfyui_button = null;
|
||||
var fetch_updates_button = null;
|
||||
var update_all_button = null;
|
||||
let share_option = 'all';
|
||||
let manager_instance =null;
|
||||
let manager_instance = null;
|
||||
$el("style", {
|
||||
textContent: `
|
||||
textContent: `
|
||||
|
||||
/*表单样式*/
|
||||
.lam_app_form {
|
||||
@@ -135,143 +135,155 @@ $el("style", {
|
||||
}
|
||||
|
||||
`,
|
||||
parent: document.body,
|
||||
parent: document.body,
|
||||
})
|
||||
let initData={}
|
||||
let initData = {}
|
||||
class ManagerMenuDialog extends ComfyDialog {
|
||||
|
||||
createControlsLeft() {
|
||||
let self = this;
|
||||
let authorIds=initData?.base?.authorIds || []
|
||||
let commands=initData?.commands||[]
|
||||
let authorEls=[]
|
||||
for(let id of authorIds){
|
||||
authorEls.push($el('ol',{style:{marginRight:'10px'}},[
|
||||
$el('span',{textContent:id}),
|
||||
$el('button',{textContent:'删除',dataset:{authorId:id},$:(el) => {
|
||||
el.onclick = () => {
|
||||
initData.base.authorIds=authorIds.filter(id=>id!=el.dataset.authorId)
|
||||
manager_instance.initElement();
|
||||
let self = this;
|
||||
let authorIds = initData?.base?.authorIds || []
|
||||
let commands = initData?.commands || []
|
||||
let authorEls = []
|
||||
for (let id of authorIds) {
|
||||
authorEls.push($el('ol', { style: { marginRight: '10px' } }, [
|
||||
$el('span', { textContent: id }),
|
||||
$el('button', {
|
||||
textContent: '删除', dataset: { authorId: id }, $: (el) => {
|
||||
el.onclick = () => {
|
||||
initData.base.authorIds = authorIds.filter(id => id != el.dataset.authorId)
|
||||
manager_instance.initElement();
|
||||
}
|
||||
}
|
||||
}}),
|
||||
}),
|
||||
]))
|
||||
}
|
||||
let commonEls=[]
|
||||
for(let command in commands){
|
||||
commonEls.push($el('ol',{style:{marginRight:'10px'}},[
|
||||
$el('span',{textContent:command}),
|
||||
$el('button',{textContent:'删除',dataset:{command:command},$:(el) => {
|
||||
el.onclick = () => {
|
||||
delete initData.commands[command]
|
||||
manager_instance.initElement();
|
||||
let commonEls = []
|
||||
for (let command in commands) {
|
||||
commonEls.push($el('ol', { style: { marginRight: '10px' } }, [
|
||||
$el('span', { textContent: command }),
|
||||
$el('button', {
|
||||
textContent: '删除', dataset: { command: command }, $: (el) => {
|
||||
el.onclick = () => {
|
||||
delete initData.commands[command]
|
||||
manager_instance.initElement();
|
||||
}
|
||||
}
|
||||
}}),
|
||||
}),
|
||||
]))
|
||||
}
|
||||
let from =$el("from", [
|
||||
$el("div.txtb", [
|
||||
$el("label", { textContent: "应用标题:" }),
|
||||
$el("input", {
|
||||
name: "appTitle",
|
||||
value: initData?.base?.appTitle,
|
||||
onchange: (e) => {
|
||||
initData.base.appTitle = e.target.value;
|
||||
},
|
||||
}),
|
||||
]),
|
||||
$el("div.txtb", [
|
||||
$el("label", { textContent: "应用logo:" }),
|
||||
$el("input", {
|
||||
name: "appLogo",
|
||||
value: initData?.base?.appLogo,
|
||||
onchange: (e) => {
|
||||
initData.base.appLogo = e.target.value;
|
||||
},
|
||||
}),
|
||||
]),
|
||||
$el("div.txtb", [
|
||||
$el("label", { textContent: "免费次数(0为无限):" }),
|
||||
$el("input", {
|
||||
name: "freeSize",
|
||||
value: initData?.base?.freeSize,
|
||||
onchange: (e) => {
|
||||
let value=e.target.value
|
||||
if(isNaN(value)){
|
||||
value=0
|
||||
}else{
|
||||
value=parseInt(value)
|
||||
}
|
||||
initData.base.freeSize = value;
|
||||
},
|
||||
}),
|
||||
]),
|
||||
$el("div.txtb", [
|
||||
$el("label", { textContent: "授权编号:" },[
|
||||
$el('button',{textContent:'添加',$:(el) => {
|
||||
let from = $el("from", [
|
||||
$el("div.txtb", [
|
||||
$el("label", { textContent: "应用标题:" }),
|
||||
$el("input", {
|
||||
name: "appTitle",
|
||||
value: initData?.base?.appTitle,
|
||||
onchange: (e) => {
|
||||
initData.base.appTitle = e.target.value;
|
||||
},
|
||||
}),
|
||||
]),
|
||||
$el("div.txtb", [
|
||||
$el("label", { textContent: "应用logo:" }),
|
||||
$el("input", {
|
||||
name: "appLogo",
|
||||
value: initData?.base?.appLogo,
|
||||
onchange: (e) => {
|
||||
initData.base.appLogo = e.target.value;
|
||||
},
|
||||
}),
|
||||
]),
|
||||
$el("div.txtb", [
|
||||
$el("label", { textContent: "免费次数(0为无限):" }),
|
||||
$el("input", {
|
||||
name: "freeSize",
|
||||
value: initData?.base?.freeSize,
|
||||
onchange: (e) => {
|
||||
let value = e.target.value
|
||||
if (isNaN(value)) {
|
||||
value = 0
|
||||
} else {
|
||||
value = parseInt(value)
|
||||
}
|
||||
initData.base.freeSize = value;
|
||||
},
|
||||
}),
|
||||
]),
|
||||
$el("div.txtb", [
|
||||
$el("label", { textContent: "授权编号:" }, [
|
||||
$el('button', {
|
||||
textContent: '添加', $: (el) => {
|
||||
el.onclick = () => {
|
||||
//随机字符串
|
||||
let authorId = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
||||
initData.base.authorIds.push(authorId)
|
||||
manager_instance.initElement();
|
||||
}
|
||||
}})
|
||||
]),
|
||||
$el("ul.scroll-container",authorEls)
|
||||
}
|
||||
})
|
||||
]),
|
||||
$el("div.txtb", [
|
||||
$el("label", { textContent: "应用管理:" }),
|
||||
$el("ul.scroll-container",commonEls)
|
||||
]),
|
||||
])
|
||||
return [$el('div.lam_app_form',[from,
|
||||
$el('span.btn',{textContent:'提交',$:(el) => {
|
||||
el.onclick = () => {
|
||||
updateInitData()
|
||||
$el("ul.scroll-container", authorEls)
|
||||
]),
|
||||
$el("div.txtb", [
|
||||
$el("label", { textContent: "应用管理:" }),
|
||||
$el("ul.scroll-container", commonEls)
|
||||
]),
|
||||
])
|
||||
return [$el('div.lam_app_form', [from,
|
||||
$el('span.btn', {
|
||||
textContent: '提交', $: (el) => {
|
||||
el.onclick = () => {
|
||||
updateInitData()
|
||||
}
|
||||
}
|
||||
}}),
|
||||
$el('span.btn',{textContent:'应用预览',onclick:()=>{
|
||||
openAppPage('default')
|
||||
}}),
|
||||
$el('span.btn',{textContent:'画板预览',onclick:()=>{
|
||||
openAppPage()
|
||||
}})
|
||||
}),
|
||||
$el('span.btn', {
|
||||
textContent: '应用预览', onclick: () => {
|
||||
openAppPage('default')
|
||||
}
|
||||
}),
|
||||
$el('span.btn', {
|
||||
textContent: '画板预览', onclick: () => {
|
||||
openAppPage()
|
||||
}
|
||||
})
|
||||
])];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
constructor() {
|
||||
super();
|
||||
const close_button = $el("button", { id: "cm-close-button", type: "button", textContent: "Close", onclick: () => this.close() });
|
||||
const content =
|
||||
$el("div.comfy-modal-content",
|
||||
[
|
||||
$el("tr.cm-title", {}, [
|
||||
$el("font", {size:6, color:"white"}, [`应用管理`])]
|
||||
),
|
||||
$el("br", {}, []),
|
||||
$el("div.cm-menu-container",
|
||||
[
|
||||
...this.createControlsLeft()
|
||||
]),
|
||||
const content =
|
||||
$el("div.comfy-modal-content",
|
||||
[
|
||||
$el("tr.cm-title", {}, [
|
||||
$el("font", { size: 6, color: "white" }, [`应用管理`])]
|
||||
),
|
||||
$el("br", {}, []),
|
||||
$el("div.cm-menu-container",
|
||||
[
|
||||
...this.createControlsLeft()
|
||||
]),
|
||||
|
||||
$el("br", {}, []),
|
||||
close_button,
|
||||
]
|
||||
);
|
||||
$el("br", {}, []),
|
||||
close_button,
|
||||
]
|
||||
);
|
||||
|
||||
content.style.width = '100%';
|
||||
content.style.height = '80%';
|
||||
this.element = $el("div.comfy-modal", { id:'cm-manager-dialog', parent: document.body,style:{maxWidth:'700px',height:'800px'} }, [ content ]);
|
||||
}
|
||||
|
||||
initElement(){
|
||||
this.element.children[0].children[2].innerHTML=''
|
||||
this.element.children[0].children[2].appendChild( ...this.createControlsLeft())
|
||||
content.style.width = '100%';
|
||||
content.style.height = '80%';
|
||||
this.element = $el("div.comfy-modal", { id: 'cm-manager-dialog', parent: document.body, style: { maxWidth: '700px', height: '800px' } }, [content]);
|
||||
}
|
||||
|
||||
show() {
|
||||
initElement() {
|
||||
this.element.children[0].children[2].innerHTML = ''
|
||||
this.element.children[0].children[2].appendChild(...this.createControlsLeft())
|
||||
}
|
||||
|
||||
show() {
|
||||
this.initElement()
|
||||
this.element.style.display = "block";
|
||||
}
|
||||
this.element.style.display = "block";
|
||||
}
|
||||
}
|
||||
async function openAppPage(type) {
|
||||
try {
|
||||
@@ -279,7 +291,7 @@ async function openAppPage(type) {
|
||||
if (resp.status === 200) {
|
||||
let data = await resp.json();
|
||||
//打开新标签页面
|
||||
window.open('/wechatauth/'+(type=='default'?'app':'index')+'?openId='+data.data);
|
||||
window.open('/wechatauth/' + (type == 'default' ? 'app' : 'index') + '?openId=' + data.data);
|
||||
return true;
|
||||
}
|
||||
throw new Error(resp.data.msg);
|
||||
@@ -288,14 +300,14 @@ async function openAppPage(type) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getAppInit(){
|
||||
async function getAppInit() {
|
||||
try {
|
||||
const resp = await api.fetchApi(`/lam/getInitData`);
|
||||
if (resp.status === 200) {
|
||||
let data = await resp.json();
|
||||
if(data.success){
|
||||
if (data.success) {
|
||||
console.log(data)
|
||||
initData=data.data
|
||||
initData = data.data
|
||||
manager_instance.show();
|
||||
}
|
||||
return true;
|
||||
@@ -308,11 +320,12 @@ async function getAppInit(){
|
||||
|
||||
async function updateInitData() {
|
||||
try {
|
||||
let data={appTitle:initData.base.appTitle,
|
||||
appLogo:initData.base.appLogo,
|
||||
freeSize:initData.base.freeSize,
|
||||
authorIds:initData.base.authorIds,
|
||||
commands:initData.commands
|
||||
let data = {
|
||||
appTitle: initData.base.appTitle,
|
||||
appLogo: initData.base.appLogo,
|
||||
freeSize: initData.base.freeSize,
|
||||
authorIds: initData.base.authorIds,
|
||||
commands: initData.commands
|
||||
}
|
||||
const response = await api.fetchApi("/lam/updateAppData", {
|
||||
method: "POST",
|
||||
@@ -333,8 +346,8 @@ async function updateInitData() {
|
||||
|
||||
async function setAppParams(data) {
|
||||
try {
|
||||
let workflow= await app.graphToPrompt();
|
||||
data['workflow']=workflow['output']
|
||||
let workflow = await app.graphToPrompt();
|
||||
data['workflow'] = workflow['output']
|
||||
const response = await api.fetchApi("/lam/setAppParams", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
@@ -351,61 +364,67 @@ async function setAppParams(data) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
function add_param(w,appNode){
|
||||
let typeEl=null;
|
||||
if(w.type=='number'||w.type=='slider'){
|
||||
typeEl=$el('td',[
|
||||
$el('select',{value:w.type,$:(el) =>{el.onchange=()=>{
|
||||
appNode.properties['paramList'].find(obj => obj.id === el.parentNode.parentNode.dataset.id).type=el.value
|
||||
}}},[
|
||||
$el('option',{selected:w.type=='number',textContent:'数值输入',value:'number'}),
|
||||
$el('option',{selected:w.type=='slider',textContent:'数值滑条',value:'slider'}),
|
||||
function add_param(w, appNode) {
|
||||
let typeEl = null;
|
||||
if (w.type == 'number' || w.type == 'slider') {
|
||||
typeEl = $el('td', [
|
||||
$el('select', {
|
||||
value: w.type, $: (el) => {
|
||||
el.onchange = () => {
|
||||
appNode.properties['paramList'].find(obj => obj.id === el.parentNode.parentNode.dataset.id).type = el.value
|
||||
}
|
||||
}
|
||||
}, [
|
||||
$el('option', { selected: w.type == 'number', textContent: '数值输入', value: 'number' }),
|
||||
$el('option', { selected: w.type == 'slider', textContent: '数值滑条', value: 'slider' }),
|
||||
])
|
||||
])
|
||||
}else{
|
||||
typeEl=$el('td',{textContent:w.type})
|
||||
} else {
|
||||
typeEl = $el('td', { textContent: w.type })
|
||||
}
|
||||
let maskEl=null;
|
||||
let maskd=appNode.properties['paramList'].find(obj => obj.id==w?.maskKey)
|
||||
if(w.type=='image'){
|
||||
maskEl=$el('td',[
|
||||
$el('span',{textContent:maskd?'已关联:'+maskd.zhName:'关联遮罩',$:(el) => {
|
||||
el.onclick = () => {
|
||||
let thIndex=appNode.properties['paramList'].findIndex(obj => obj.id==el.parentNode.parentNode.dataset.id)
|
||||
let imgs=appNode.properties['paramList'].filter(obj => obj.type === 'image'&&obj.id!=el.parentNode.parentNode.dataset.id)
|
||||
let maskKey=appNode.properties['paramList'][thIndex]?.maskKey
|
||||
if(imgs.length>0){
|
||||
if(!maskKey){
|
||||
appNode.properties['paramList'][thIndex]['maskKey']=imgs[0].id
|
||||
el.innerHTML='已关联:'+imgs[0].zhName
|
||||
}else{
|
||||
if(imgs.length==1){
|
||||
appNode.properties['paramList'][thIndex]['maskKey']=null
|
||||
el.innerHTML='关联遮罩'
|
||||
}else{
|
||||
let imgIndex=imgs.findIndex(obj => obj.id === maskKey)
|
||||
if(imgIndex==-1||imgIndex+1>=imgs.length){
|
||||
appNode.properties['paramList'][thIndex]['maskKey']=null
|
||||
el.innerHTML='关联遮罩'
|
||||
}else{
|
||||
appNode.properties['paramList'][thIndex]['maskKey']=imgs[imgIndex+1].id
|
||||
el.innerHTML='已关联:'+imgs[imgIndex+1].zhName
|
||||
let maskEl = null;
|
||||
let maskd = appNode.properties['paramList'].find(obj => obj.id == w?.maskKey)
|
||||
if (w.type == 'image') {
|
||||
maskEl = $el('td', [
|
||||
$el('span', {
|
||||
textContent: maskd ? '已关联:' + maskd.zhName : '关联遮罩', $: (el) => {
|
||||
el.onclick = () => {
|
||||
let thIndex = appNode.properties['paramList'].findIndex(obj => obj.id == el.parentNode.parentNode.dataset.id)
|
||||
let imgs = appNode.properties['paramList'].filter(obj => obj.type === 'image' && obj.id != el.parentNode.parentNode.dataset.id)
|
||||
let maskKey = appNode.properties['paramList'][thIndex]?.maskKey
|
||||
if (imgs.length > 0) {
|
||||
if (!maskKey) {
|
||||
appNode.properties['paramList'][thIndex]['maskKey'] = imgs[0].id
|
||||
el.innerHTML = '已关联:' + imgs[0].zhName
|
||||
} else {
|
||||
if (imgs.length == 1) {
|
||||
appNode.properties['paramList'][thIndex]['maskKey'] = null
|
||||
el.innerHTML = '关联遮罩'
|
||||
} else {
|
||||
let imgIndex = imgs.findIndex(obj => obj.id === maskKey)
|
||||
if (imgIndex == -1 || imgIndex + 1 >= imgs.length) {
|
||||
appNode.properties['paramList'][thIndex]['maskKey'] = null
|
||||
el.innerHTML = '关联遮罩'
|
||||
} else {
|
||||
appNode.properties['paramList'][thIndex]['maskKey'] = imgs[imgIndex + 1].id
|
||||
el.innerHTML = '已关联:' + imgs[imgIndex + 1].zhName
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
el.innerHTML = '关联遮罩'
|
||||
alert('请先添加遮罩图片参数')
|
||||
}
|
||||
}else{
|
||||
el.innerHTML='关联遮罩'
|
||||
alert('请先添加遮罩图片参数')
|
||||
|
||||
}
|
||||
|
||||
}}
|
||||
}
|
||||
})
|
||||
])
|
||||
}else{
|
||||
maskEl=$el('td')
|
||||
} else {
|
||||
maskEl = $el('td')
|
||||
}
|
||||
|
||||
return $el('tr',{
|
||||
|
||||
return $el('tr', {
|
||||
dataset: {
|
||||
id: w.id,
|
||||
name: w.name,
|
||||
@@ -413,68 +432,76 @@ function add_param(w,appNode){
|
||||
type: w.type,
|
||||
value: w.value
|
||||
}
|
||||
},[
|
||||
$el('td',{textContent:w.name}),
|
||||
$el('td',{},[
|
||||
$el('input',{name:w.name,value:w.zhName,$:(el) =>{el.onchange=()=>{
|
||||
appNode.properties['paramList'].find(obj => obj.id === el.parentNode.parentNode.dataset.id).zhName=el.value
|
||||
}}})
|
||||
}, [
|
||||
$el('td', { textContent: w.name }),
|
||||
$el('td', {}, [
|
||||
$el('input', {
|
||||
name: w.name, value: w.zhName, $: (el) => {
|
||||
el.onchange = () => {
|
||||
appNode.properties['paramList'].find(obj => obj.id === el.parentNode.parentNode.dataset.id).zhName = el.value
|
||||
}
|
||||
}
|
||||
})
|
||||
]),
|
||||
typeEl,
|
||||
maskEl,
|
||||
$el('td',{},[$el('button',{textContent:'删除',$:(el) => {
|
||||
el.onclick = () => {
|
||||
appNode.properties['paramList'].splice(appNode.properties['paramList'].findIndex(obj => obj.id === el.parentNode.parentNode.dataset.id),1)
|
||||
el.parentNode.parentNode.parentNode.removeChild(el.parentNode.parentNode)
|
||||
}}
|
||||
$el('td', {}, [$el('button', {
|
||||
textContent: '删除', $: (el) => {
|
||||
el.onclick = () => {
|
||||
appNode.properties['paramList'].splice(appNode.properties['paramList'].findIndex(obj => obj.id === el.parentNode.parentNode.dataset.id), 1)
|
||||
el.parentNode.parentNode.parentNode.removeChild(el.parentNode.parentNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
)]),
|
||||
])
|
||||
}
|
||||
app.registerExtension({
|
||||
name: "AppParams",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
var names=["AppParams"]
|
||||
if (names.indexOf(nodeData.name)>=0) {
|
||||
var names = ["AppParams"]
|
||||
if (names.indexOf(nodeData.name) >= 0) {
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function() {
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
const r = onNodeCreated?.apply(this, arguments);
|
||||
this.setProperty("paramList", [])
|
||||
const list = $el("table.lam_app_params_list",[
|
||||
$el('tr',[
|
||||
$el('td',{textContent:'参数名'}),
|
||||
$el('td',{textContent:'中文名'}),
|
||||
$el('td',{textContent:'组件类型'}),
|
||||
$el('td',{textContent:'绘制遮罩'}),
|
||||
$el('td',{textContent:'操作'}),
|
||||
const list = $el("table.lam_app_params_list", [
|
||||
$el('tr', [
|
||||
$el('td', { textContent: '参数名' }),
|
||||
$el('td', { textContent: '中文名' }),
|
||||
$el('td', { textContent: '组件类型' }),
|
||||
$el('td', { textContent: '绘制遮罩' }),
|
||||
$el('td', { textContent: '操作' }),
|
||||
])
|
||||
]);
|
||||
let params=this.addDOMWidget('styles',"list",$el('div.lam_app_param_div',[$el('button',{
|
||||
textContent:'应用预览',
|
||||
style:{},
|
||||
onclick:()=>{
|
||||
let params = this.addDOMWidget('styles', "list", $el('div.lam_app_param_div', [$el('button', {
|
||||
textContent: '应用预览',
|
||||
style: {},
|
||||
onclick: () => {
|
||||
let appType = this.widgets[this.widgets.findIndex(obj => obj.name === 'appType')];
|
||||
openAppPage(appType.value)
|
||||
}}),$el('button',{
|
||||
textContent:'参数应用',
|
||||
style:{},
|
||||
onclick:()=>{
|
||||
let paramList=this.properties['paramList']
|
||||
let appName = this.widgets[this.widgets.findIndex(obj => obj.name === 'appName')];
|
||||
let appType = this.widgets[this.widgets.findIndex(obj => obj.name === 'appType')];
|
||||
let appDesc = this.widgets[this.widgets.findIndex(obj => obj.name === 'appDesc')];
|
||||
|
||||
if(!appName.value){
|
||||
alert('请输入应用名称');
|
||||
return ;
|
||||
}
|
||||
if(paramList.length<=0){
|
||||
alert('请至少选择一个参数');
|
||||
return ;
|
||||
}
|
||||
let data={appName:appName.value,appType:appType.value,appDesc:appDesc.value,paramList:paramList}
|
||||
setAppParams(data)
|
||||
}}),list]));
|
||||
}
|
||||
}), $el('button', {
|
||||
textContent: '参数应用',
|
||||
style: {},
|
||||
onclick: () => {
|
||||
let paramList = this.properties['paramList']
|
||||
let appName = this.widgets[this.widgets.findIndex(obj => obj.name === 'appName')];
|
||||
let appType = this.widgets[this.widgets.findIndex(obj => obj.name === 'appType')];
|
||||
let appDesc = this.widgets[this.widgets.findIndex(obj => obj.name === 'appDesc')];
|
||||
|
||||
if (!appName.value) {
|
||||
alert('请输入应用名称');
|
||||
return;
|
||||
}
|
||||
if (paramList.length <= 0) {
|
||||
alert('请至少选择一个参数');
|
||||
return;
|
||||
}
|
||||
let data = { appName: appName.value, appType: appType.value, appDesc: appDesc.value, paramList: paramList }
|
||||
setAppParams(data)
|
||||
}
|
||||
}), list]));
|
||||
this.setSize([500, 600]);
|
||||
return r;
|
||||
};
|
||||
@@ -483,10 +510,10 @@ app.registerExtension({
|
||||
loadedGraphNode(node, _) {
|
||||
if (node.type === "AppParams") {
|
||||
node.properties['paramList'].forEach(el => {
|
||||
node.widgets[3].element.children[2].appendChild(add_param(el,node))
|
||||
node.widgets[3].element.children[2].appendChild(add_param(el, node))
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
async function addConvertToGroupOptions() {
|
||||
@@ -494,9 +521,9 @@ async function addConvertToGroupOptions() {
|
||||
const getNodeMenuOptions = LGraphCanvas.prototype.getNodeMenuOptions;
|
||||
LGraphCanvas.prototype.getNodeMenuOptions = function (node) {
|
||||
const options = getNodeMenuOptions.apply(this, arguments);
|
||||
if (!GroupNodeHandler.isGroupNode(node)&&node.type!='AppParams'&&node.widgets){
|
||||
if (!GroupNodeHandler.isGroupNode(node) && node.type != 'AppParams' && node.widgets) {
|
||||
let toInput = [];
|
||||
let wNames=node.inputs.filter(obj => obj.widget !== undefined && obj.link === null).map(obj => obj.name);
|
||||
let wNames = node.inputs.filter(obj => obj.widget !== undefined && obj.link === null).map(obj => obj.name);
|
||||
for (const w of node.widgets) {
|
||||
if (w.options?.forceInput || !wNames.includes(w.name)) {
|
||||
continue;
|
||||
@@ -506,72 +533,72 @@ async function addConvertToGroupOptions() {
|
||||
content: `添加 ${w.label} 到输入参数`,
|
||||
callback: async () => {
|
||||
console.log(w)
|
||||
let nodes=graph.computeExecutionOrder(false)
|
||||
let appNode=nodes[nodes.findIndex(obj => obj.type === 'AppParams')]
|
||||
if(!appNode){
|
||||
let nodes = graph.computeExecutionOrder(false)
|
||||
let appNode = nodes[nodes.findIndex(obj => obj.type === 'AppParams')]
|
||||
if (!appNode) {
|
||||
alert('请先添加AppParams节点');
|
||||
return;
|
||||
}
|
||||
let appTypeEl = appNode.widgets[appNode.widgets.findIndex(obj => obj.name === 'appType')];
|
||||
if(!w.type in type2paramType){
|
||||
if (!w.type in type2paramType) {
|
||||
return;
|
||||
}
|
||||
let paramData={id:'n'+node.id+'_'+w.name,keys:[''+node.id,'inputs',w.name],name:w.name,zhName:w.label,type:w.type,default:w.value,isRequired:false}
|
||||
if(w.type=='number'){
|
||||
paramData['min']=w.options.min
|
||||
paramData['max']=w.options.max
|
||||
paramData['step']=w.options.round
|
||||
}else if(w.type=='toggle'||w.type=='boolean'||w.type=='BOOLEAN'){
|
||||
paramData['type']='switch'
|
||||
paramData['onVal']=true
|
||||
paramData['offVal']=false
|
||||
}else if(w.type=='STRING'){
|
||||
paramData['type']='text'
|
||||
}else if(w.type=='combo'){
|
||||
paramData['type']='select'
|
||||
let values=w.options.values
|
||||
paramData['options']={}
|
||||
for(let i=0;i<values.length;i++){
|
||||
paramData['options'][values[i]]=values[i]
|
||||
let paramData = { id: 'n' + node.id + '_' + w.name, keys: ['' + node.id, 'inputs', w.name], name: w.name, zhName: w.label, type: w.type, default: w.value, isRequired: false }
|
||||
if (w.type == 'number') {
|
||||
paramData['min'] = w.options.min
|
||||
paramData['max'] = w.options.max
|
||||
paramData['step'] = w.options.round
|
||||
} else if (w.type == 'toggle' || w.type == 'boolean' || w.type == 'BOOLEAN') {
|
||||
paramData['type'] = 'switch'
|
||||
paramData['onVal'] = true
|
||||
paramData['offVal'] = false
|
||||
} else if (w.type == 'STRING') {
|
||||
paramData['type'] = 'text'
|
||||
} else if (w.type == 'combo') {
|
||||
paramData['type'] = 'select'
|
||||
let values = w.options.values
|
||||
paramData['options'] = {}
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
paramData['options'][values[i]] = values[i]
|
||||
}
|
||||
}
|
||||
if(node.type=="LamLoadPathImage"&&w.name=='image_path'){
|
||||
paramData['type']='image'
|
||||
paramData['default']=''
|
||||
}else if(node.type=="LamLoadImageBase64"&&w.name=='image'){
|
||||
paramData['type']='base64img'
|
||||
paramData['isRequired']=true
|
||||
}else if(w.name=='seed'){
|
||||
paramData['type']='seed'
|
||||
if(appTypeEl.value=='paint-board'){
|
||||
paramData['default']=-1
|
||||
if (node.type == "LamLoadPathImage" && w.name == 'image_path') {
|
||||
paramData['type'] = 'image'
|
||||
paramData['default'] = ''
|
||||
} else if (node.type == "LamLoadImageBase64" && w.name == 'image') {
|
||||
paramData['type'] = 'base64img'
|
||||
paramData['isRequired'] = true
|
||||
} else if (w.name == 'seed') {
|
||||
paramData['type'] = 'seed'
|
||||
if (appTypeEl.value == 'paint-board') {
|
||||
paramData['default'] = -1
|
||||
}
|
||||
}else if(w.name=='batch_size'){
|
||||
paramData['type']='number'
|
||||
}else if(w.name=='denoise'){
|
||||
paramData['type']='slider'
|
||||
if(appTypeEl.value=='paint-board'){
|
||||
paramData['min']=0
|
||||
paramData['max']=100
|
||||
paramData['step']=1
|
||||
paramData['original']=1
|
||||
} else if (w.name == 'batch_size') {
|
||||
paramData['type'] = 'number'
|
||||
} else if (w.name == 'denoise') {
|
||||
paramData['type'] = 'slider'
|
||||
if (appTypeEl.value == 'paint-board') {
|
||||
paramData['min'] = 0
|
||||
paramData['max'] = 100
|
||||
paramData['step'] = 1
|
||||
paramData['original'] = 1
|
||||
}
|
||||
paramData['default']=parseFloat(paramData['default'].toFixed(2))
|
||||
}
|
||||
paramData['default'] = parseFloat(paramData['default'].toFixed(2))
|
||||
}
|
||||
|
||||
let index=appNode.properties['paramList'].findIndex(obj => obj.id === paramData.id)
|
||||
if(index>=0){
|
||||
let index = appNode.properties['paramList'].findIndex(obj => obj.id === paramData.id)
|
||||
if (index >= 0) {
|
||||
return;
|
||||
}
|
||||
appNode.properties['paramList'].push(paramData)
|
||||
let tr=add_param(paramData,appNode)
|
||||
let tr = add_param(paramData, appNode)
|
||||
appNode.widgets[3].element.children[2].appendChild(tr)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
const index = options.findIndex((o) => o?.content === "Outputs") + 1 || options.length - 1;
|
||||
toInput.length>0&&options.splice(index + 1, null, {
|
||||
toInput.length > 0 && options.splice(index + 1, null, {
|
||||
content: `设置应用参数`,
|
||||
submenu: {
|
||||
options: toInput
|
||||
@@ -581,12 +608,12 @@ async function addConvertToGroupOptions() {
|
||||
return options;
|
||||
};
|
||||
|
||||
let cmGroup = new (await import("../../scripts/ui/components/buttonGroup.js")).ComfyButtonGroup(
|
||||
new(await import("../../scripts/ui/components/button.js")).ComfyButton({
|
||||
let cmGroup = new (await import("../../scripts/ui/components/buttonGroup.js")).ComfyButtonGroup(
|
||||
new (await import("../../scripts/ui/components/button.js")).ComfyButton({
|
||||
icon: "puzzle",
|
||||
action: () => {
|
||||
if(!manager_instance){
|
||||
manager_instance= new ManagerMenuDialog();
|
||||
if (!manager_instance) {
|
||||
manager_instance = new ManagerMenuDialog();
|
||||
}
|
||||
getAppInit()
|
||||
},
|
||||
@@ -596,7 +623,7 @@ async function addConvertToGroupOptions() {
|
||||
}).element
|
||||
);
|
||||
app.menu?.settingsGroup.element.before(cmGroup.element);
|
||||
|
||||
|
||||
}
|
||||
|
||||
const id = "Lam.AppParams";
|
||||
@@ -612,12 +639,10 @@ const ext = {
|
||||
// // Store this so we can mutate it later with group nodes
|
||||
// globalDefs = defs;
|
||||
// },
|
||||
nodeCreated(node) {
|
||||
if (!GroupNodeHandler.isGroupNode(node)) {
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
// nodeCreated(node) {
|
||||
// if (!GroupNodeHandler.isGroupNode(node)) {
|
||||
// }
|
||||
// },
|
||||
};
|
||||
|
||||
app.registerExtension(ext);
|
||||
@@ -0,0 +1,67 @@
|
||||
import { app } from "/scripts/app.js";
|
||||
|
||||
|
||||
function adaptWidgetsToConnection(node) {
|
||||
if (!node.outputs || node.outputs.length === 0) {
|
||||
return
|
||||
}
|
||||
console.log("到这里==================")
|
||||
}
|
||||
|
||||
function setupParameterNode(nodeType) {
|
||||
const onAdded = nodeType.prototype.onAdded
|
||||
nodeType.prototype.onAdded = function () {
|
||||
onAdded?.apply(this, arguments)
|
||||
adaptWidgetsToConnection(this)
|
||||
}
|
||||
|
||||
const onAfterGraphConfigured = nodeType.prototype.onAfterGraphConfigured
|
||||
nodeType.prototype.onAfterGraphConfigured = function () {
|
||||
onAfterGraphConfigured?.apply(this, arguments)
|
||||
adaptWidgetsToConnection(this)
|
||||
}
|
||||
|
||||
const onConnectOutput = nodeType.prototype.onConnectOutput
|
||||
nodeType.prototype.onConnectOutput = function (slot, type, input, target_node, target_slot) {
|
||||
if (!input.widget) {
|
||||
return false
|
||||
} else if (onConnectOutput) {
|
||||
result = onConnectOutput.apply(this, arguments)
|
||||
return result
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const onConnectionsChange = nodeType.prototype.onConnectionsChange
|
||||
nodeType.prototype.onConnectionsChange = function (_, index, connected) {
|
||||
if (!app.configuringGraph) {
|
||||
adaptWidgetsToConnection(this)
|
||||
}
|
||||
onConnectionsChange?.apply(this, arguments)
|
||||
}
|
||||
}
|
||||
|
||||
const id = "Lam.Switch";
|
||||
const ext = {
|
||||
name: id,
|
||||
setup() {
|
||||
//addConvertToGroupOptions();
|
||||
//添加按钮可以
|
||||
},
|
||||
async beforeConfigureGraph(graphData, missingNodeTypes) {
|
||||
//加载配置
|
||||
},
|
||||
beforeRegisterNodeDef(nodeType /*typeof LGraphNode*/, nodeData /*ComfyObjectInfo*/, app) {
|
||||
if (nodeData.name === "LamSwitchStart") {
|
||||
setupParameterNode(nodeType)
|
||||
}
|
||||
},
|
||||
addCustomNodeDefs(defs) {
|
||||
// 将其存储起来,以便稍后使用组节点对其进行修改
|
||||
},
|
||||
nodeCreated(node) {
|
||||
//节点创建
|
||||
},
|
||||
};
|
||||
|
||||
app.registerExtension(ext);
|
||||
+212
-946
File diff suppressed because it is too large
Load Diff
+581
-122
@@ -1,130 +1,589 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>简单画板</title>
|
||||
<!--<link rel="stylesheet" href="https://unpkg.com/element-plus@2.4.4/dist/index.css"/>-->
|
||||
<link rel="stylesheet" href="static/assets/index.css" />
|
||||
<!--<script src="https://unpkg.com/vue@3.3.12/dist/vue.global.js"></script>-->
|
||||
<script src="static/assets/vue.global.js"></script>
|
||||
<!--<script src="https://unpkg.com/vue-i18n@9.8.0/dist/vue-i18n.global.js"></script>-->
|
||||
<script src="static/assets/vue-i18n.global.js"></script>
|
||||
<!--<script src="https://unpkg.com/element-plus@2.4.4/dist/index.full.min.js"></script>-->
|
||||
<script src="static/assets/index.full.min.js"></script>
|
||||
<!--<script src="https://unpkg.com/@element-plus/icons-vue@2.3.1/dist/index.iife.min.js"></script>-->
|
||||
<script src="static/assets/index.iife.min.js"></script>
|
||||
<!--<script src="https://unpkg.com/clipboard@2.0.11/dist/clipboard.min.js"></script>-->
|
||||
<script src="static/assets/clipboard.min.js"></script>
|
||||
<!--<script src="https://unpkg.com/axios@1.6.2/dist/axios.min.js"></script>-->
|
||||
<script src="static/assets/axios.min.js"></script>
|
||||
<script src="static/assets/fabric.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" class="image-comparator">
|
||||
<section class="aigc-preview" >
|
||||
<el-carousel class="aigc-preview__img" :interval="5000" arrow="always" :autoplay="false">
|
||||
<el-carousel-item v-for="item in 4" :key="item">
|
||||
<el-image class="aigc-preview__img" src="../view?filename=ComfyUI_03170_.png&type=input&subfolder=" fit="contain" ></el-image>
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
</section>
|
||||
<!-- 核心标签代码 -->
|
||||
<div class="container" @mouseenter="startDrag" @mousemove="onDrag" @mouseup="endDrag" @mouseleave="endDrag">
|
||||
<div class="image-wrapper"
|
||||
:style="{ clipPath: `inset(0 ${100 - (dividerPosition / containerWidth * 100)}% 0 0)` }">
|
||||
<el-image src="https://fuss10.elemecdn.com/9/bb/e27858e973f5d7d3904835f46abbdjpeg.jpeg" class="image" fit="contain"/>
|
||||
</div>
|
||||
<div class="divider" :style="{ left: `${dividerPosition}px` }"></div>
|
||||
<div class="image-wrapper" :style="{ clipPath: `inset(0 0 0 ${(dividerPosition / containerWidth * 100)}%)` }">
|
||||
<el-image src="https://fuss10.elemecdn.com/d/e6/c4d93a3805b3ce3f323f7974e6f78jpeg.jpeg" class="image" fit="contain"/>
|
||||
</div>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>360°全景图播放器</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);
|
||||
color: white;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
header {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 10px;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.2rem;
|
||||
opacity: 0.9;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.player-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 70vh;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
#panorama {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #111;
|
||||
}
|
||||
|
||||
.loading {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 1.2rem;
|
||||
color: white;
|
||||
z-index: 10;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
padding: 15px 30px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 1.2rem;
|
||||
color: #ff6b6b;
|
||||
z-index: 10;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
padding: 15px 30px;
|
||||
border-radius: 8px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
margin-bottom: 30px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: none;
|
||||
color: white;
|
||||
padding: 12px 24px;
|
||||
border-radius: 50px;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
backdrop-filter: blur(5px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.control-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.control-btn:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.control-btn.active {
|
||||
background: rgba(253, 187, 45, 0.7);
|
||||
}
|
||||
|
||||
.instructions {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
max-width: 800px;
|
||||
margin: 0 auto 30px;
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.instructions h2 {
|
||||
margin-bottom: 15px;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.instructions ul {
|
||||
text-align: left;
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.instructions li {
|
||||
margin-bottom: 10px;
|
||||
padding-left: 25px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.instructions li:before {
|
||||
content: "•";
|
||||
color: #fdbb2d;
|
||||
font-size: 1.5rem;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: -5px;
|
||||
}
|
||||
|
||||
.image-selector {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.image-option {
|
||||
width: 120px;
|
||||
height: 80px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
opacity: 0.7;
|
||||
transition: all 0.3s ease;
|
||||
border: 2px solid transparent;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.image-option:hover {
|
||||
opacity: 0.9;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.image-option.active {
|
||||
opacity: 1;
|
||||
border-color: #fdbb2d;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.image-option img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.image-option .placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(45deg, #666, #999);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 20px;
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.player-container {
|
||||
height: 50vh;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
padding: 10px 20px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>360°全景图播放器</h1>
|
||||
<p class="subtitle">沉浸式体验全景视觉盛宴</p>
|
||||
</header>
|
||||
|
||||
<div class="player-container">
|
||||
<div id="panorama"></div>
|
||||
<div class="loading" id="loading">加载全景图...</div>
|
||||
<div class="error-message" id="errorMessage">图片加载失败,请尝试其他全景图</div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button class="control-btn" id="autoRotate">
|
||||
<span>自动旋转</span>
|
||||
</button>
|
||||
<button class="control-btn" id="fullscreen">
|
||||
<span>全屏模式</span>
|
||||
</button>
|
||||
<button class="control-btn" id="resetView">
|
||||
<span>重置视角</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="image-selector">
|
||||
<div class="image-option active" data-src="/api/view?filename=ComfyUI_00004_.png&subfolder=&type=output&rand=0.4623513290192227">
|
||||
<div class="placeholder">示例全景图1</div>
|
||||
</div>
|
||||
<div class="image-option" data-src="https://threejs.org/examples/textures/25258776a2_o.jpg">
|
||||
<div class="placeholder">示例全景图2</div>
|
||||
</div>
|
||||
<div class="image-option" data-src="https://threejs.org/examples/textures/3583869462_03c4660a5d_o.jpg">
|
||||
<div class="placeholder">示例全景图3</div>
|
||||
</div>
|
||||
<div class="image-option" data-src="https://threejs.org/examples/textures/landscape.png">
|
||||
<div class="placeholder">示例全景图4</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="instructions">
|
||||
<h2>使用说明</h2>
|
||||
<ul>
|
||||
<li>使用鼠标拖拽来旋转视角,探索全景环境</li>
|
||||
<li>使用鼠标滚轮来放大或缩小视图</li>
|
||||
<li>点击"自动旋转"按钮启用/禁用自动旋转</li>
|
||||
<li>点击"全屏模式"按钮进入全屏沉浸体验</li>
|
||||
<li>点击"重置视角"按钮回到初始视角</li>
|
||||
<li>从下方选择不同的全景图进行切换</li>
|
||||
<li><strong>注意:</strong>示例图片来自threejs.org,可能需要科学上网才能访问</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="instructions">
|
||||
<h2>本地图片使用说明</h2>
|
||||
<ul>
|
||||
<li>如需使用本地图片,请将图片文件放在项目目录中</li>
|
||||
<li>修改 data-src 属性为本地图片路径,例如:data-src="images/panorama1.jpg"</li>
|
||||
<li>确保图片是等距柱状投影格式(360°全景图)</li>
|
||||
<li>推荐图片尺寸:4096×2048 或 8192×4096</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>© 2023 全景图播放器 | 使用 Three.js 构建</p>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script>
|
||||
const app = Vue.createApp({
|
||||
data() {
|
||||
return {
|
||||
containerWidth: 500,
|
||||
dividerPosition: 250,
|
||||
dragging: false,
|
||||
containerRect: null,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
updateContainerWidth() {
|
||||
this.containerWidth = document.querySelector(".container").clientWidth;
|
||||
},
|
||||
startDrag(event) {
|
||||
this.dragging = true;
|
||||
this.containerRect = document.querySelector(".container").getBoundingClientRect();
|
||||
},
|
||||
onDrag(event) {
|
||||
if (this.dragging && this.containerRect) {
|
||||
let newPosition = event.clientX - this.containerRect.left;
|
||||
if (newPosition < 0) newPosition = 0;
|
||||
if (newPosition > this.containerRect.width)
|
||||
newPosition = this.containerRect.width;
|
||||
this.dividerPosition = newPosition;
|
||||
}
|
||||
},
|
||||
endDrag() {
|
||||
this.dragging = false;
|
||||
this.containerRect = null;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.updateContainerWidth();
|
||||
window.addEventListener("resize", this.updateContainerWidth);
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener("resize", this.updateContainerWidth);
|
||||
},
|
||||
});
|
||||
app.use(ElementPlus);
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.component(key, component);
|
||||
}
|
||||
app.mount("#app");
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.image-comparator {
|
||||
width: 90%;
|
||||
/*max-width: 600px;*/
|
||||
margin: 0 auto;
|
||||
<!-- 引入Three.js库 -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
overflow: hidden;
|
||||
}
|
||||
<script>
|
||||
// 全景图播放器类
|
||||
class PanoramaViewer {
|
||||
constructor(containerId) {
|
||||
this.container = document.getElementById(containerId);
|
||||
this.scene = null;
|
||||
this.camera = null;
|
||||
this.renderer = null;
|
||||
this.sphere = null;
|
||||
this.textureLoader = new THREE.TextureLoader();
|
||||
|
||||
.image-wrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
// 控制参数
|
||||
this.isUserInteracting = false;
|
||||
this.onPointerDownMouseX = 0;
|
||||
this.onPointerDownMouseY = 0;
|
||||
this.onPointerDownLon = 0;
|
||||
this.onPointerDownLat = 0;
|
||||
this.lon = 0;
|
||||
this.lat = 0;
|
||||
this.phi = 0;
|
||||
this.theta = 0;
|
||||
this.target = new THREE.Vector3();
|
||||
this.autoRotate = false;
|
||||
this.autoRotateSpeed = 0.3;
|
||||
|
||||
.image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
this.init();
|
||||
}
|
||||
|
||||
.divider {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 2px;
|
||||
background-color: black;
|
||||
cursor: ew-resize;
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</html>
|
||||
init() {
|
||||
// 创建场景
|
||||
this.scene = new THREE.Scene();
|
||||
|
||||
// 创建相机
|
||||
this.camera = new THREE.PerspectiveCamera(75, this.container.clientWidth / this.container.clientHeight, 0.1, 1000);
|
||||
|
||||
// 创建渲染器
|
||||
this.renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||
this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
|
||||
this.renderer.setPixelRatio(window.devicePixelRatio);
|
||||
this.container.appendChild(this.renderer.domElement);
|
||||
|
||||
// 创建球体几何
|
||||
const geometry = new THREE.SphereGeometry(500, 60, 40);
|
||||
geometry.scale(-1, 1, 1); // 反转球体,使纹理在内部
|
||||
|
||||
// 创建默认材质(等待图片加载)
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
color: 0x222222,
|
||||
side: THREE.DoubleSide
|
||||
});
|
||||
this.sphere = new THREE.Mesh(geometry, material);
|
||||
this.scene.add(this.sphere);
|
||||
|
||||
// 设置相机位置
|
||||
this.camera.position.set(0, 0, 0.1);
|
||||
|
||||
// 添加事件监听
|
||||
this.addEventListeners();
|
||||
|
||||
// 开始动画循环
|
||||
this.animate();
|
||||
|
||||
// 窗口大小变化处理
|
||||
window.addEventListener('resize', () => this.onWindowResize());
|
||||
}
|
||||
|
||||
loadPanorama(imageUrl) {
|
||||
document.getElementById('loading').style.display = 'block';
|
||||
document.getElementById('errorMessage').style.display = 'none';
|
||||
|
||||
// 设置超时时间
|
||||
const timeout = setTimeout(() => {
|
||||
this.showError('图片加载超时,请检查网络连接或尝试其他图片');
|
||||
}, 10000);
|
||||
|
||||
this.textureLoader.load(
|
||||
imageUrl,
|
||||
(texture) => {
|
||||
clearTimeout(timeout);
|
||||
texture.minFilter = THREE.LinearFilter;
|
||||
|
||||
// 创建新材质
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
map: texture,
|
||||
side: THREE.DoubleSide
|
||||
});
|
||||
this.sphere.material = material;
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
},
|
||||
(progress) => {
|
||||
// 加载进度
|
||||
const percent = Math.round((progress.loaded / progress.total) * 100);
|
||||
document.getElementById('loading').textContent = `加载中... ${percent}%`;
|
||||
},
|
||||
(error) => {
|
||||
clearTimeout(timeout);
|
||||
console.error('加载全景图失败:', error);
|
||||
this.showError('图片加载失败,请尝试其他全景图');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
showError(message) {
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
document.getElementById('errorMessage').textContent = message;
|
||||
document.getElementById('errorMessage').style.display = 'block';
|
||||
}
|
||||
|
||||
addEventListeners() {
|
||||
const domElement = this.renderer.domElement;
|
||||
|
||||
// 鼠标按下事件
|
||||
domElement.addEventListener('mousedown', (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
this.isUserInteracting = true;
|
||||
|
||||
this.onPointerDownMouseX = event.clientX;
|
||||
this.onPointerDownMouseY = event.clientY;
|
||||
|
||||
this.onPointerDownLon = this.lon;
|
||||
this.onPointerDownLat = this.lat;
|
||||
});
|
||||
|
||||
// 鼠标移动事件
|
||||
domElement.addEventListener('mousemove', (event) => {
|
||||
if (this.isUserInteracting) {
|
||||
this.lon = (this.onPointerDownMouseX - event.clientX) * 0.1 + this.onPointerDownLon;
|
||||
this.lat = (event.clientY - this.onPointerDownMouseY) * 0.1 + this.onPointerDownLat;
|
||||
}
|
||||
});
|
||||
|
||||
// 鼠标释放事件
|
||||
domElement.addEventListener('mouseup', () => {
|
||||
this.isUserInteracting = false;
|
||||
});
|
||||
|
||||
// 鼠标离开事件
|
||||
domElement.addEventListener('mouseleave', () => {
|
||||
this.isUserInteracting = false;
|
||||
});
|
||||
|
||||
// 鼠标滚轮事件(缩放)
|
||||
domElement.addEventListener('wheel', (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
// 调整相机FOV实现缩放效果
|
||||
this.camera.fov += event.deltaY * 0.05;
|
||||
this.camera.fov = THREE.MathUtils.clamp(this.camera.fov, 30, 110);
|
||||
this.camera.updateProjectionMatrix();
|
||||
});
|
||||
|
||||
// 触摸事件支持
|
||||
domElement.addEventListener('touchstart', (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (event.touches.length === 1) {
|
||||
this.isUserInteracting = true;
|
||||
|
||||
this.onPointerDownMouseX = event.touches[0].pageX;
|
||||
this.onPointerDownMouseY = event.touches[0].pageY;
|
||||
|
||||
this.onPointerDownLon = this.lon;
|
||||
this.onPointerDownLat = this.lat;
|
||||
}
|
||||
});
|
||||
|
||||
domElement.addEventListener('touchmove', (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (event.touches.length === 1 && this.isUserInteracting) {
|
||||
this.lon = (this.onPointerDownMouseX - event.touches[0].pageX) * 0.1 + this.onPointerDownLon;
|
||||
this.lat = (event.touches[0].pageY - this.onPointerDownMouseY) * 0.1 + this.onPointerDownLat;
|
||||
}
|
||||
});
|
||||
|
||||
domElement.addEventListener('touchend', () => {
|
||||
this.isUserInteracting = false;
|
||||
});
|
||||
}
|
||||
|
||||
onWindowResize() {
|
||||
this.camera.aspect = this.container.clientWidth / this.container.clientHeight;
|
||||
this.camera.updateProjectionMatrix();
|
||||
this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
|
||||
}
|
||||
|
||||
resetView() {
|
||||
this.lon = 0;
|
||||
this.lat = 0;
|
||||
this.camera.fov = 75;
|
||||
this.camera.updateProjectionMatrix();
|
||||
}
|
||||
|
||||
toggleAutoRotate() {
|
||||
this.autoRotate = !this.autoRotate;
|
||||
}
|
||||
|
||||
animate() {
|
||||
requestAnimationFrame(() => this.animate());
|
||||
|
||||
if (this.autoRotate && !this.isUserInteracting) {
|
||||
this.lon += this.autoRotateSpeed;
|
||||
}
|
||||
|
||||
this.lat = Math.max(-85, Math.min(85, this.lat));
|
||||
|
||||
this.phi = THREE.MathUtils.degToRad(90 - this.lat);
|
||||
this.theta = THREE.MathUtils.degToRad(this.lon);
|
||||
|
||||
this.target.x = 500 * Math.sin(this.phi) * Math.cos(this.theta);
|
||||
this.target.y = 500 * Math.cos(this.phi);
|
||||
this.target.z = 500 * Math.sin(this.phi) * Math.sin(this.theta);
|
||||
|
||||
this.camera.lookAt(this.target);
|
||||
|
||||
this.renderer.render(this.scene, this.camera);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化全景查看器
|
||||
let panoramaViewer;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// 创建全景查看器
|
||||
panoramaViewer = new PanoramaViewer('panorama');
|
||||
|
||||
// 加载第一个全景图
|
||||
const firstImage = document.querySelector('.image-option.active').getAttribute('data-src');
|
||||
panoramaViewer.loadPanorama(firstImage);
|
||||
|
||||
// 绑定控制按钮事件
|
||||
document.getElementById('autoRotate').addEventListener('click', function () {
|
||||
panoramaViewer.toggleAutoRotate();
|
||||
this.classList.toggle('active');
|
||||
this.innerHTML = panoramaViewer.autoRotate ?
|
||||
'<span>停止旋转</span>' : '<span>自动旋转</span>';
|
||||
});
|
||||
|
||||
document.getElementById('fullscreen').addEventListener('click', function () {
|
||||
const container = document.querySelector('.player-container');
|
||||
|
||||
if (!document.fullscreenElement) {
|
||||
container.requestFullscreen().catch(err => {
|
||||
console.error(`全屏请求错误: ${err.message}`);
|
||||
});
|
||||
this.innerHTML = '<span>退出全屏</span>';
|
||||
} else {
|
||||
document.exitFullscreen();
|
||||
this.innerHTML = '<span>全屏模式</span>';
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('resetView').addEventListener('click', function () {
|
||||
panoramaViewer.resetView();
|
||||
});
|
||||
|
||||
// 绑定全景图切换事件
|
||||
const imageOptions = document.querySelectorAll('.image-option');
|
||||
imageOptions.forEach(option => {
|
||||
option.addEventListener('click', function () {
|
||||
// 更新活动状态
|
||||
imageOptions.forEach(opt => opt.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
|
||||
// 切换全景图
|
||||
const imageUrl = this.getAttribute('data-src');
|
||||
panoramaViewer.loadPanorama(imageUrl);
|
||||
});
|
||||
});
|
||||
|
||||
// 全屏变化事件
|
||||
document.addEventListener('fullscreenchange', function () {
|
||||
const fullscreenBtn = document.getElementById('fullscreen');
|
||||
if (document.fullscreenElement) {
|
||||
fullscreenBtn.innerHTML = '<span>退出全屏</span>';
|
||||
} else {
|
||||
fullscreenBtn.innerHTML = '<span>全屏模式</span>';
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
+932
-1023
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,300 @@
|
||||
/* Extracted from pictureBook.html */
|
||||
|
||||
.aigc-layout {
|
||||
/* Add specific styles if needed, previously inline or default */
|
||||
}
|
||||
|
||||
.head-title-image {
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.contact-admin-btn {
|
||||
color: aliceblue;
|
||||
}
|
||||
|
||||
.qr-code-img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.role-remark-input,
|
||||
.narration-input,
|
||||
.prompt-input {
|
||||
background-color: #101a2d;
|
||||
}
|
||||
|
||||
.prompt-image {
|
||||
width: 70px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.preview-image {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mask-edit-btn {
|
||||
display: -webkit-box;
|
||||
}
|
||||
|
||||
.brush-slider {
|
||||
width: 40%;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.mask-btn {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.scroll-container {
|
||||
overflow-y: scroll;
|
||||
height: 78vh;
|
||||
}
|
||||
|
||||
.mid-text {
|
||||
font-weight: bold;
|
||||
font-size: medium;
|
||||
}
|
||||
|
||||
.el-message-box .box-title {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: large;
|
||||
color: aliceblue;
|
||||
}
|
||||
|
||||
.el-message-box .qrcode {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.el-message-box .el-input-group {
|
||||
box-shadow: 0 0 0 1px var(--el-input-border-color, var(--el-border-color)) inset;
|
||||
border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
|
||||
}
|
||||
|
||||
.el-message-box input {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.el-message-box .el-input-group__append {
|
||||
color: aliceblue;
|
||||
}
|
||||
|
||||
.el-message-box {
|
||||
margin-top: 4px;
|
||||
padding: 20px 16px;
|
||||
color: rgb(253 248 248 / 80%);
|
||||
background-color: #606266;
|
||||
border-radius: 24px;
|
||||
font-size: 13px;
|
||||
font-weight: lighter;
|
||||
border: 1px solid #606266;
|
||||
}
|
||||
|
||||
.head-title {
|
||||
color: aliceblue;
|
||||
font-size: x-large;
|
||||
height: 52px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.el-card__body,
|
||||
.el-card__footer {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.el-upload-dragger,
|
||||
.el-upload-dragger.is-dragover {
|
||||
padding: 0;
|
||||
background-color: rgba(255, 255, 255, .0);
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.image-view .el-upload--picture-card {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.el-upload-list--picture-card .el-upload-list__item,
|
||||
.el-upload--picture-card {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.el-menu--horizontal {
|
||||
--el-menu-horizontal-height: 100px;
|
||||
}
|
||||
|
||||
.el-form-item__label,
|
||||
.el-checkbox__label,
|
||||
.el-radio__label,
|
||||
.el-input__inner,
|
||||
.el-statistic__head,
|
||||
.el-statistic__number {
|
||||
color: rgba(255, 255, 255, .8);
|
||||
}
|
||||
|
||||
.el-input__wrapper {
|
||||
font-weight: lighter;
|
||||
position: relative;
|
||||
background-color: rgba(255, 255, 255, .05);
|
||||
color: rgba(255, 255, 255, .8);
|
||||
border-radius: 9px;
|
||||
font-size: 13px;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.el-textarea__inner {
|
||||
font-weight: lighter;
|
||||
position: relative;
|
||||
background-color: rgba(255, 255, 255, .05);
|
||||
color: rgba(255, 255, 255, .8);
|
||||
border-radius: 9px;
|
||||
font-size: 13px;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.el-card,
|
||||
.el-card__body,
|
||||
.el-card__header,
|
||||
.el-card__footer {
|
||||
color: rgba(255, 255, 255, .8);
|
||||
background-color: rgba(255, 255, 255, .0);
|
||||
border: 0px solid transparent;
|
||||
}
|
||||
|
||||
.el-input-group__append,
|
||||
.el-switch__core,
|
||||
.el-input-number__decrease,
|
||||
.el-input-number__increase,
|
||||
.el-upload--picture-card {
|
||||
background-color: rgba(255, 255, 255, .05);
|
||||
}
|
||||
|
||||
.el-textarea .el-input__count {
|
||||
color: rgba(255, 255, 255, .8);
|
||||
background-color: rgba(255, 255, 255, 0);
|
||||
}
|
||||
|
||||
.el-icon {
|
||||
color: rgba(255, 255, 255, .8);
|
||||
}
|
||||
|
||||
.el-popper.is-pure {
|
||||
padding: 13px 0;
|
||||
background: rgba(255, 255, 255, .1);
|
||||
color: #fff;
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
box-shadow: 0 4px 40px rgba(2, 10, 29, .4);
|
||||
border-radius: 9px;
|
||||
}
|
||||
|
||||
.el-select-dropdown__item {
|
||||
padding: 5px 20px 5px 12px;
|
||||
position: relative;
|
||||
background: transparent;
|
||||
color: #fff;
|
||||
display: block;
|
||||
min-height: 32px;
|
||||
padding: 5px 12px;
|
||||
color: rgba(250, 250, 250, 0.88);
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
line-height: 1.5714285714285714;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.el-select-dropdown__item.hover,
|
||||
.el-select-dropdown__item:hover {
|
||||
background: rgba(255, 255, 255, .05);
|
||||
}
|
||||
|
||||
.el-select-dropdown__item.selected {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.el-select-dropdown__item.selected:after {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
position: absolute;
|
||||
content: "";
|
||||
background: url(https://staticsns.cdn.bcebos.com/amis/2024-1/1704594047813/selected.png);
|
||||
background-size: 100% 100%;
|
||||
right: 8px;
|
||||
top: 8px;
|
||||
}
|
||||
|
||||
.el-button.is-disabled,
|
||||
.el-button.is-disabled:focus,
|
||||
.el-button.is-disabled:hover {
|
||||
position: relative;
|
||||
background: linear-gradient(to bottom right, rgb(229, 250, 87), rgb(64, 252, 67));
|
||||
font-size: 16px;
|
||||
font-weight: 900;
|
||||
text-align: center;
|
||||
line-height: 19.6px;
|
||||
border-radius: 48px;
|
||||
color: #000;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.el-carousel__container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Auth Dialog Styles */
|
||||
.auth-dialog-content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.auth-title {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: large;
|
||||
color: aliceblue;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.auth-qrcode {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.auth-input-container {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.auth-input {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.auth-msg {
|
||||
color: #ffb3b3;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.book-menu {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.book-content {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.story-content-item {
|
||||
z-index: 0;
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
.el-message-box {
|
||||
div.box-title {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: large;
|
||||
color: aliceblue;
|
||||
}
|
||||
|
||||
.qrcode {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.el-input-group {
|
||||
box-shadow: 0 0 0 1px var(--el-input-border-color, var(--el-border-color)) inset;
|
||||
border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
|
||||
}
|
||||
|
||||
input {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.el-input-group__append {
|
||||
color: aliceblue;
|
||||
}
|
||||
|
||||
margin-top: 4px;
|
||||
padding: 20px 16px;
|
||||
color: rgb(253 248 248 / 80%);
|
||||
background-color: #606266;
|
||||
border-radius: 24px;
|
||||
font-size: 13px;
|
||||
font-weight: lighter;
|
||||
border: 1px solid #606266;
|
||||
}
|
||||
|
||||
.head-title {
|
||||
color: aliceblue;
|
||||
font-size: x-large;
|
||||
height: 52px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.el-card__body,
|
||||
.el-card__footer {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.el-upload-dragger,
|
||||
.el-upload-dragger.is-dragover {
|
||||
padding: 0;
|
||||
background-color: rgba(255, 255, 255, .0);
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.image-view .el-upload--picture-card {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.el-upload-list--picture-card .el-upload-list__item,
|
||||
.el-upload--picture-card {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.el-menu--horizontal {
|
||||
--el-menu-horizontal-height: 100px;
|
||||
}
|
||||
|
||||
.el-form-item__label,
|
||||
.el-checkbox__label,
|
||||
.el-radio__label,
|
||||
.el-input__inner,
|
||||
.el-statistic__head,
|
||||
.el-statistic__number {
|
||||
color: rgba(255, 255, 255, .8);
|
||||
}
|
||||
|
||||
.el-input__wrapper {
|
||||
font-weight: lighter;
|
||||
position: relative;
|
||||
background-color: rgba(255, 255, 255, .05);
|
||||
color: rgba(255, 255, 255, .8);
|
||||
border-radius: 9px;
|
||||
font-size: 13px;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.el-textarea__inner {
|
||||
font-weight: lighter;
|
||||
position: relative;
|
||||
background-color: rgba(255, 255, 255, .05);
|
||||
color: rgba(255, 255, 255, .8);
|
||||
border-radius: 9px;
|
||||
font-size: 13px;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.el-card,
|
||||
.el-card__body,
|
||||
.el-card__header,
|
||||
.el-card__footer {
|
||||
color: rgba(255, 255, 255, .8);
|
||||
background-color: rgba(255, 255, 255, .0);
|
||||
border: 0px solid transparent;
|
||||
}
|
||||
|
||||
.el-input-group__append,
|
||||
.el-switch__core,
|
||||
.el-input-number__decrease,
|
||||
.el-input-number__increase,
|
||||
.el-upload--picture-card {
|
||||
background-color: rgba(255, 255, 255, .05);
|
||||
}
|
||||
|
||||
.el-textarea .el-input__count {
|
||||
color: rgba(255, 255, 255, .8);
|
||||
background-color: rgba(255, 255, 255, 0);
|
||||
}
|
||||
|
||||
.el-icon {
|
||||
color: rgba(255, 255, 255, .8);
|
||||
}
|
||||
|
||||
.el-popper.is-pure {
|
||||
padding: 13px 0;
|
||||
background: rgba(255, 255, 255, .1);
|
||||
color: #fff;
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
box-shadow: 0 4px 40px rgba(2, 10, 29, .4);
|
||||
border-radius: 9px;
|
||||
}
|
||||
|
||||
.el-select-dropdown__item {
|
||||
padding: 5px 20px 5px 12px;
|
||||
position: relative;
|
||||
background: transparent;
|
||||
color: #fff;
|
||||
display: block;
|
||||
min-height: 32px;
|
||||
padding: 5px 12px;
|
||||
color: rgba(250, 250, 250, 0.88);
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
line-height: 1.5714285714285714;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.el-select-dropdown__item.hover,
|
||||
.el-select-dropdown__item:hover {
|
||||
background: rgba(255, 255, 255, .05);
|
||||
}
|
||||
|
||||
.el-select-dropdown__item.selected {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.el-select-dropdown__item.selected:after {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
position: absolute;
|
||||
content: "";
|
||||
background: url(https://staticsns.cdn.bcebos.com/amis/2024-1/1704594047813/selected.png);
|
||||
background-size: 100% 100%;
|
||||
right: 8px;
|
||||
top: 8px;
|
||||
}
|
||||
|
||||
.el-button.is-disabled,
|
||||
.el-button.is-disabled:focus,
|
||||
.el-button.is-disabled:hover {
|
||||
position: relative;
|
||||
background: linear-gradient(to bottom right, rgb(229, 250, 87), rgb(64, 252, 67));
|
||||
font-size: 16px;
|
||||
font-weight: 900;
|
||||
text-align: center;
|
||||
line-height: 19.6px;
|
||||
border-radius: 48px;
|
||||
color: #000;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.el-carousel__container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* New classes extracted from app.html */
|
||||
.logo-image {
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.admin-contact-text {
|
||||
color: aliceblue;
|
||||
}
|
||||
|
||||
.qr-code-img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.preview-carousel {
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.full-size {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.text-white {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.scroll-container {
|
||||
overflow-y: scroll;
|
||||
height: 78vh;
|
||||
}
|
||||
|
||||
.w-100 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bold-medium-text {
|
||||
font-weight: bold;
|
||||
font-size: medium;
|
||||
}
|
||||
|
||||
.flex-box {
|
||||
display: -webkit-box;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.slider-custom {
|
||||
width: 40%;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.margin-h-10 {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.btn-gradient {
|
||||
background: linear-gradient(to bottom right, rgb(224 224 224), rgb(244 85 85));
|
||||
}
|
||||
|
||||
.full-height {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.auth-container {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.auth-input {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.auth-message {
|
||||
color: #ffb3b3;
|
||||
margin-top: 10px;
|
||||
}
|
||||
@@ -0,0 +1,607 @@
|
||||
function formatTime(seconds) {
|
||||
const days = Math.floor(seconds / (3600 * 24));
|
||||
seconds -= days * 3600 * 24;
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
seconds -= hours * 3600;
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
seconds -= minutes * 60;
|
||||
|
||||
const formattedDays = days > 0 ? days + ':' : '';
|
||||
const formattedHours = hours.toString().padStart(2, '0') + ':';
|
||||
const formattedMinutes = minutes.toString().padStart(2, '0') + ':';
|
||||
const formattedSeconds = seconds.toString().padStart(2, '0') + '';
|
||||
|
||||
return formattedDays + formattedHours + formattedMinutes + formattedSeconds;
|
||||
}
|
||||
|
||||
function getParams(url) {
|
||||
const regex = /[?&]([^=#]+)=([^&#]*)/g;
|
||||
const params = {};
|
||||
let match;
|
||||
while (match = regex.exec(url)) {
|
||||
params[decodeURIComponent(match[1])] = decodeURIComponent(match[2]);
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
let openId = window.APP_CONFIG.openId;
|
||||
|
||||
const app = Vue.createApp({
|
||||
data() {
|
||||
return {
|
||||
commandList: [],
|
||||
activeKey: '',
|
||||
paramList: [],
|
||||
brushSize: 20,
|
||||
canvas: null,
|
||||
dialogVisible: false,
|
||||
dialogImageUrl: '',
|
||||
dialogImageName: '',
|
||||
maskKey: '',
|
||||
historyList: [],
|
||||
queue_remaining: 0,
|
||||
page_number: 1,
|
||||
noMore: false,
|
||||
loading: false,
|
||||
disabled: false,
|
||||
isNotData: false,
|
||||
isConnect: false,
|
||||
isSuccess: false,
|
||||
authVisible: false,
|
||||
authCode: '',
|
||||
authMessage: '',
|
||||
form: {
|
||||
openId: '',
|
||||
command: '',
|
||||
status: "prepare",
|
||||
prompt_id: '',
|
||||
percenStatus: 'warning',
|
||||
percentage: 0,
|
||||
output: { images: [], texts: [] },
|
||||
nodeIds: [],
|
||||
doneNodeIds: []
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
if (openId) {
|
||||
this.initMain();
|
||||
} else {
|
||||
this.authUser();
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
loading(newVal, oldVal) {
|
||||
this.disabled = (newVal || this.noMore)
|
||||
},
|
||||
noMore(newVal, oldVal) {
|
||||
this.disabled = (newVal || this.loading)
|
||||
},
|
||||
},
|
||||
created() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
initMain() {
|
||||
this.loadData();
|
||||
document.getElementById("app").style.display = "block";
|
||||
this.initWebSocket();
|
||||
this.getHistorys();
|
||||
},
|
||||
authUser(msg = '') {
|
||||
this.authMessage = msg;
|
||||
this.authVisible = true;
|
||||
},
|
||||
submitAuth() {
|
||||
let that = this;
|
||||
if (this.authCode.length != 6) {
|
||||
this.authMessage = '验证码长度不正确!';
|
||||
return;
|
||||
}
|
||||
this.getUserOpenId(this.authCode);
|
||||
},
|
||||
isShowAdmin() {
|
||||
if ('pywebview' in window) {
|
||||
if (pywebview.api.isLogin()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
initWebSocket() {
|
||||
var that = this;
|
||||
var websocket = null;
|
||||
//var socketId = "ws-" + new Date().getTime();
|
||||
// 判断当前浏览器是否支持WebSocket, springboot是项目名
|
||||
if ('WebSocket' in window) {
|
||||
var host = window.location.host;
|
||||
websocket = new WebSocket("ws://" + host + '/ws?clientId=' + openId);
|
||||
} else {
|
||||
console.error("不支持WebSocket");
|
||||
}
|
||||
// 连接发生错误的回调方法
|
||||
websocket.onerror = function (e) {
|
||||
console.error("WebSocket连接发生错误");
|
||||
};
|
||||
// 连接成功建立的回调方法
|
||||
websocket.onopen = function () {
|
||||
// 获取所有在线用户
|
||||
console.log("WebSocket连接成功");
|
||||
that.isConnect = true;
|
||||
|
||||
}
|
||||
// 接收到消息的回调方法
|
||||
websocket.onmessage = async function (event) {
|
||||
if (event.data == null || event.data == "" || event.data instanceof Blob) {
|
||||
return;
|
||||
}
|
||||
var messageJson = eval("(" + event.data + ")");
|
||||
if (messageJson['type'] == 'crystools.monitor') {
|
||||
return
|
||||
}
|
||||
console.log('messageJson', messageJson)
|
||||
if (messageJson['type'] == "status") {
|
||||
that.queue_remaining = messageJson['data']['status']['exec_info']['queue_remaining'];
|
||||
} else if (messageJson['type'] == "execution_start") {
|
||||
if (that.form.prompt_id == messageJson['data']['prompt_id']) {
|
||||
that.form.percenStatus = 'success';
|
||||
}
|
||||
} else if (messageJson['type'] == "execution_cached") {
|
||||
if (that.form.prompt_id == messageJson['data']['prompt_id']) {
|
||||
that.form.doneNodeIds = messageJson['data']['nodes'];
|
||||
}
|
||||
} else if (messageJson['type'] == "executing") {
|
||||
if (that.form.prompt_id == messageJson['data']['prompt_id']) {
|
||||
let node = messageJson['data']['node'];
|
||||
if (that.form.executing) {
|
||||
that.form.doneNodeIds.push(that.form.executing);
|
||||
}
|
||||
if (!messageJson['data']['node']) {
|
||||
that.form.status = 'prepare';
|
||||
that.form.percenStatus = 'warning';
|
||||
that.form.percentage = 0;
|
||||
that.form.executing = '';
|
||||
that.form.doneNodeIds = [];
|
||||
that.form.nodeIds = [];
|
||||
if (that.isSuccess) {
|
||||
that.isSuccess = false
|
||||
setTimeout(() => {
|
||||
that.page_number = 1;
|
||||
that.getHistorys();
|
||||
}, 1000)
|
||||
}
|
||||
} else {
|
||||
that.form.executing = node;
|
||||
}
|
||||
}
|
||||
} else if (messageJson['type'] == 'progress') {
|
||||
if (that.form.prompt_id == messageJson['data']['prompt_id']) {
|
||||
let max = messageJson['data']['max'];
|
||||
let value = messageJson['data']['value'];
|
||||
let node = messageJson['data']['node'];
|
||||
let percentage = 0;
|
||||
if (that.form.executing == node) {
|
||||
percentage = parseFloat(value / max);
|
||||
}
|
||||
that.form.percentage = parseInt((that.form.doneNodeIds.length + percentage) / that.form.nodeIds.length * 100);
|
||||
}
|
||||
} else if (messageJson['type'] == 'execution_success') {
|
||||
if (that.form.prompt_id == messageJson['data']['prompt_id']) {
|
||||
that.isSuccess = true;
|
||||
}
|
||||
} else if (messageJson['type'] == 'executed') {
|
||||
if (that.form.prompt_id == messageJson['data']['prompt_id']) {
|
||||
let node_output = 'output' in messageJson['data'] ? messageJson['data']['output'] : {};
|
||||
let images = [];
|
||||
let texts = [];
|
||||
if ('outputImg' in node_output) {
|
||||
for (let img of node_output?.outputImg) {
|
||||
let url = '../view?filename=' + img.filename + '&type=' + img.type + '&subfolder=' + img.subfolder + '&rand=' + Math.random();
|
||||
images.push(url)
|
||||
}
|
||||
} else if ("wechat_text" in node_output) {
|
||||
texts.push(...node_output.wechat_text);
|
||||
}
|
||||
if (images.length > 0 || texts.length > 0) {
|
||||
that.form.output = { images: images, texts: texts };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 连接关闭的回调方法
|
||||
websocket.onclose = function () {
|
||||
console.error("WebSocket连接关闭");
|
||||
that.openCloseMsg();
|
||||
}
|
||||
},
|
||||
openCloseMsg() {
|
||||
var that = this;
|
||||
that.isConnect = false;
|
||||
ElementPlus.ElMessage({
|
||||
showClose: true,
|
||||
duration: 0,
|
||||
message: '服务器断开连接,关闭消息尝试重连',
|
||||
type: 'error',
|
||||
onClose: () => {
|
||||
that.initWebSocket();
|
||||
}
|
||||
})
|
||||
},
|
||||
loadData() {
|
||||
var that = this;
|
||||
axios.get('../wechatauth/getCommands')
|
||||
.then(function (response) {
|
||||
console.log(response);
|
||||
if (response.status == 200) {
|
||||
that.commandList = response.data;
|
||||
that.clickTab(Object.keys(response.data)[0])
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log(error);
|
||||
ElementPlus.ElMessage.error(error);
|
||||
})
|
||||
},
|
||||
clickTab(key) {
|
||||
this.activeKey = key;
|
||||
if (this.commandList[key]) {
|
||||
this.paramList = this.commandList[key].params;
|
||||
for (let paramKey in this.paramList) {
|
||||
this.paramList[paramKey]['value'] = this.paramList[paramKey]?.default;
|
||||
if (this.paramList[paramKey].type == 'image') {
|
||||
this.paramList[paramKey]['fileList'] = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
clearValue(param) {
|
||||
param.value = '';
|
||||
},
|
||||
refreshSeed(param) {
|
||||
param.value = Math.floor(Math.random() * 1000000000000000);
|
||||
},
|
||||
handlePictureCardPreview(uploadFile, maskKey) {
|
||||
this.dialogImageUrl = uploadFile.url;
|
||||
this.dialogImageName = uploadFile.name;
|
||||
this.dialogVisible = true;
|
||||
this.maskKey = maskKey;
|
||||
if (this.maskKey) {
|
||||
this.initCanvas(uploadFile.url);
|
||||
} else {
|
||||
this.canvas = null;
|
||||
}
|
||||
},
|
||||
handleRemove(uploadFile, paramKey) {
|
||||
this.paramList[paramKey]['value'] = '';
|
||||
},
|
||||
beforeAvatarUpload(rawFile) {
|
||||
// 判断文件格式不为图片的
|
||||
if (rawFile.type !== 'image/png' && rawFile.type !== 'image/jpg' && rawFile.type !== 'image/jpeg') {
|
||||
ElementPlus.ElMessage.error('请上传图片文件!');
|
||||
return false
|
||||
}
|
||||
// else if (rawFile.size / 1024 / 1024 > 2) {
|
||||
// ElementPlus.ElMessage.error('Avatar picture size can not exceed 2MB!')
|
||||
// return false
|
||||
// }
|
||||
return true
|
||||
},
|
||||
updateSuccess(data, paramKey) {
|
||||
this.paramList[paramKey]['value'] = data.subfolder ? (data.type + '/' + data.name) : (data.type + '/' + data.subfolder + '/' + data.name)
|
||||
},
|
||||
changeSize(value) {
|
||||
this.canvas.freeDrawingBrush.width = parseInt(value);
|
||||
},
|
||||
clearCanvas() {
|
||||
this.canvas.clear();
|
||||
this.initCanvas(this.dialogImageUrl);
|
||||
},
|
||||
saveCanvas() {
|
||||
let that = this;
|
||||
// 清除背景图
|
||||
that.canvas.backgroundImage = null;
|
||||
that.canvas.backgroundColor = 'rgba(0,0,0,0)'
|
||||
that.canvas.renderAll();
|
||||
that.canvas.lowerCanvasEl.toBlob(function (blob) {
|
||||
let formData = new FormData();
|
||||
formData.append("image", blob, 'mask_' + openId + that.dialogImageName);
|
||||
formData.append("overwrite", "true");
|
||||
formData.append("type", "input");
|
||||
axios.post('../upload/image', formData)
|
||||
.then(function (response) {
|
||||
let data = response.data;
|
||||
data['url'] = '../view?filename=' + data.name + '&type=input&subfolder=&rand=' + Math.random();
|
||||
that.paramList[that.maskKey]['fileList'] = [data];
|
||||
that.paramList[that.maskKey]['value'] = data.subfolder ? (data.type + '/' + data.name) : (data.type + '/' + data.subfolder + '/' + data.name);
|
||||
that.initCanvas(that.dialogImageUrl);
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log(error);
|
||||
ElementPlus.ElMessage.error(error);
|
||||
})
|
||||
}, "image/png");
|
||||
},
|
||||
initCanvas(backgroundUrl) {
|
||||
var that = this;
|
||||
fabric.Image.fromURL(backgroundUrl, (img) => {
|
||||
if (that.canvas == null) {
|
||||
that.canvas = new fabric.Canvas("canvas", {
|
||||
backgroundColor: "#FFFFFF",
|
||||
isDrawingMode: true,
|
||||
});
|
||||
let freeDrawingBrush = new fabric.PencilBrush(that.canvas);
|
||||
// Set initial brush settings
|
||||
freeDrawingBrush.width = that.brushSize;
|
||||
freeDrawingBrush.color = '#000000';
|
||||
// Enable free drawing mode on canvas
|
||||
that.canvas.isDrawingMode = true;
|
||||
that.canvas.freeDrawingBrush = freeDrawingBrush;
|
||||
}
|
||||
let parentWidth = document.querySelector(".mask_edit_btn").clientWidth;
|
||||
let bili = img.width / parentWidth;
|
||||
let imgHeight = img.height / bili;
|
||||
that.canvas.setWidth(parentWidth);
|
||||
that.canvas.setHeight(imgHeight);
|
||||
that.canvas.setBackgroundImage(img, that.canvas.renderAll.bind(that.canvas), {
|
||||
scaleX: that.canvas.width / img.width,
|
||||
scaleY: that.canvas.height / img.height,
|
||||
});
|
||||
that.canvas.renderAll();
|
||||
});
|
||||
},
|
||||
getUserOpenId(code) {
|
||||
let that = this;
|
||||
let formData = new FormData();
|
||||
formData.append("code", code);
|
||||
axios.post('../wechatauth/getUserOpenId', formData)
|
||||
.then(function (response) {
|
||||
if (response.data.success) {
|
||||
openId = response.data.openId;
|
||||
that.authVisible = false; // Close dialog
|
||||
that.initMain();
|
||||
if ('pywebview' in window) {
|
||||
pywebview.api.setOpenId(openId);
|
||||
}
|
||||
} else {
|
||||
that.authMessage = '授权码校验失败,请先扫码关注获取授权码!';
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log(error);
|
||||
that.authMessage = error.message || 'Unknown error';
|
||||
})
|
||||
},
|
||||
cancelTask() {
|
||||
let that = this;
|
||||
let formData = new FormData();
|
||||
formData.append("openId", openId);
|
||||
formData.append("prompt_id", that.form.prompt_id)
|
||||
axios.post('../wechatauth/cancelTask', formData)
|
||||
.then(function (response) {
|
||||
if (response.data.success) {
|
||||
that.form.prompt_id = '';
|
||||
that.form.nodeIds = [];
|
||||
that.form.status = 'prepare';
|
||||
that.form.percenStatus = 'warning';
|
||||
that.form.percentage = 0;
|
||||
} else {
|
||||
ElementPlus.ElMessage.error(response.data.msg);
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log(error);
|
||||
ElementPlus.ElMessage.error(error);
|
||||
})
|
||||
},
|
||||
startTask() {
|
||||
let that = this;
|
||||
if (that.form.status !== 'prepare') {
|
||||
ElementPlus.ElMessage.info('任务<' + that.form.command + '>正在运行,请勿重复操作!')
|
||||
return;
|
||||
}
|
||||
let formData = new FormData();
|
||||
formData.append("openId", openId);
|
||||
formData.append("command", this.activeKey);
|
||||
formData.append("status", "prepare");
|
||||
that.form.openId = openId;
|
||||
that.form.command = this.activeKey;
|
||||
that.form.status = "prepare";
|
||||
for (let paramKey in this.paramList) {
|
||||
if (this.paramList[paramKey]?.value) {
|
||||
formData.append(paramKey, this.paramList[paramKey]?.value);
|
||||
}
|
||||
}
|
||||
axios.post('../wechatauth/addTask', formData)
|
||||
.then(function (response) {
|
||||
console.log(response);
|
||||
if (response.data.success) {
|
||||
that.form.status = 'waiting';
|
||||
that.form.prompt_id = response.data.prompt_id;
|
||||
that.form.nodeIds = response.data.nodeIds;
|
||||
} else {
|
||||
ElementPlus.ElMessage.error(response.data.msg);
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log(error);
|
||||
ElementPlus.ElMessage.error(error);
|
||||
})
|
||||
},
|
||||
confirmSetParam(params) {
|
||||
let that = this;
|
||||
this.clickTab(params.command);
|
||||
for (let paramKey in this.paramList) {
|
||||
if (params[paramKey]) {
|
||||
if (typeof params[paramKey] == 'string' && params[paramKey] == 'undefined') {
|
||||
this.paramList[paramKey].value = '';
|
||||
} else if (this.paramList[paramKey].type == 'slider'
|
||||
|| this.paramList[paramKey].type == 'number') {
|
||||
this.paramList[paramKey].value = parseFloat(params[paramKey]);
|
||||
} else {
|
||||
if (this.paramList[paramKey].type == 'image') {
|
||||
let ps = params[paramKey].split('//');
|
||||
let url = '../view?filename=' + ps[1] + '&type=' + ps[0] + '&subfolder=&rand=' + Math.random();
|
||||
//获取文件名称
|
||||
this.paramList[paramKey].fileList = [{ url: url, name: ps[1] }];
|
||||
}
|
||||
this.paramList[paramKey].value = params[paramKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
async handleDownload(imgs, name) {
|
||||
let that = this;
|
||||
var zip = new JSZip();
|
||||
for (let img of imgs) {
|
||||
let fileName = img.filename;
|
||||
let { data } = await axios({
|
||||
method: 'get',
|
||||
url: img.url,
|
||||
responseType: 'blob',
|
||||
});
|
||||
zip.file(fileName, data);
|
||||
}
|
||||
zip.generateAsync({ type: 'blob' }).then(function (content) {
|
||||
let url = window.URL.createObjectURL(content);
|
||||
that.downloadFile(url, name);
|
||||
});
|
||||
},
|
||||
downloadFile(url, name) {
|
||||
let a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = name;
|
||||
a.click();
|
||||
},
|
||||
loadHistory() {
|
||||
let that = this;
|
||||
if (this.isNotData) {
|
||||
this.noMore = true;
|
||||
setTimeout(() => {
|
||||
this.noMore = false;
|
||||
}, 1000)
|
||||
return;
|
||||
}
|
||||
that.page_number += 1;
|
||||
that.getHistorys();
|
||||
},
|
||||
setRunTask(dataStr, prompt_id) {
|
||||
let that = this;
|
||||
let params = JSON.parse(dataStr)
|
||||
this.confirmSetParam(params)
|
||||
axios.get('../api/queue')
|
||||
.then(function (response) {
|
||||
let queue_pending = response.data.queue_pending;
|
||||
let queue_running = response.data.queue_running;
|
||||
for (let data of queue_running) {
|
||||
if (data[1] == prompt_id) {
|
||||
that.form.status = 'waiting';
|
||||
that.form.prompt_id = prompt_id;
|
||||
that.form.percenStatus = 'success';
|
||||
that.form.nodeIds = Object.keys(data[2]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (let data of queue_pending) {
|
||||
if (data[1] == prompt_id) {
|
||||
that.form.status = 'waiting';
|
||||
that.form.prompt_id = prompt_id;
|
||||
that.form.nodeIds = Object.keys(data[2]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
ElementPlus.ElMessage.error(error);
|
||||
})
|
||||
|
||||
},
|
||||
getHistorys() {
|
||||
let that = this;
|
||||
that.loading = true;
|
||||
axios.get('../wechatauth/getHistorys?openId=' + openId + '&page_number=' + that.page_number)
|
||||
.then(function (response) {
|
||||
console.log(response);
|
||||
if (that.page_number == 1) {
|
||||
that.isNotData = false;
|
||||
that.historyList = [];
|
||||
}
|
||||
let datas = response.data;
|
||||
if (datas.length > 0 && datas[0][5] == 'waiting') {
|
||||
that.setRunTask(datas[0][3], datas[0][4])
|
||||
}
|
||||
for (let res of datas) {
|
||||
let command = JSON.parse(res[3])
|
||||
if (res[5] == 'waiting') {
|
||||
continue
|
||||
} else if (res[5] == 'wcomplete') {
|
||||
command['status'] = 'wcomplete'
|
||||
let outputs = JSON.parse(res[8])
|
||||
let images = [];
|
||||
let texts = [];
|
||||
let files = [];
|
||||
for (let node_id in outputs) {
|
||||
let node_output = outputs[node_id]
|
||||
if (Object.keys(node_output).indexOf("outputImg") >= 0) {
|
||||
for (let img of node_output?.outputImg) {
|
||||
let url = '../view?filename=' + img.filename + '&type=' + img.type + '&subfolder=' + img.subfolder + '&rand=' + Math.random();
|
||||
images.push(url)
|
||||
img['url'] = url;
|
||||
files.push(img);
|
||||
}
|
||||
} else if (Object.keys(node_output).indexOf("wechat_text") >= 0) {
|
||||
texts.push(...node_output.wechat_text);
|
||||
}
|
||||
|
||||
}
|
||||
that.historyList.push({ 'prompt_id': res[4], 'command': command, 'images': images, 'files': files, 'status': res[5], 'texts': texts })
|
||||
}
|
||||
}
|
||||
if (datas.length < 10) {
|
||||
that.isNotData = true;
|
||||
}
|
||||
that.loading = false;
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
ElementPlus.ElMessage.error(error);
|
||||
})
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
// use ElementPlus and ElementPlusIconsVue
|
||||
app.use(ElementPlus)
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.component(key, component)
|
||||
}
|
||||
|
||||
// use VueI18n
|
||||
const browserLanguage = navigator.language || navigator.userLanguage; // zh-CN en-US
|
||||
const languageTag = browserLanguage.split('-')[0]; // zh en
|
||||
const i18n = VueI18n.createI18n({
|
||||
legacy: false, // you must set `false`, to use Composition API
|
||||
locale: languageTag, // set locale
|
||||
fallbackLocale: 'en', // set fallback locale
|
||||
messages: {},
|
||||
})
|
||||
app.use(i18n)
|
||||
function loadLocaleMessages(locale) {
|
||||
return axios.get(`static/locales/${locale}.json`).then(response => {
|
||||
return response.data;
|
||||
});
|
||||
}
|
||||
async function loadLocales() {
|
||||
const locales = ['en', 'zh'];
|
||||
await Promise.all(
|
||||
locales.map(async (locale) => {
|
||||
const messages = await loadLocaleMessages(locale);
|
||||
i18n.global.setLocaleMessage(locale, messages);
|
||||
})
|
||||
);
|
||||
}
|
||||
loadLocales().then(() => {
|
||||
app.mount('#app');
|
||||
});
|
||||
@@ -0,0 +1,150 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
import math
|
||||
import time
|
||||
import os
|
||||
import torch
|
||||
|
||||
def panorama_to_spherical(img):
|
||||
"""
|
||||
改进的正向变换函数,使用双线性插值
|
||||
"""
|
||||
rows, cols, c = img.shape
|
||||
# 优化:大幅提高中间图像分辨率
|
||||
# 原逻辑 R = cols / 2 / pi 导致垂直分辨率损失约70%
|
||||
# 新逻辑:直接使用原图高度作为半径,保证垂直细节不丢失
|
||||
R = rows
|
||||
D = R * 2
|
||||
cx = R
|
||||
cy = R
|
||||
|
||||
# 创建目标图像的坐标网格
|
||||
j_coords, i_coords = np.mgrid[0:D, 0:D]
|
||||
|
||||
# 计算极坐标参数
|
||||
r = np.sqrt((i_coords - cx)**2 + (j_coords - cy)**2)
|
||||
theta = np.arctan2(j_coords - cy, i_coords - cx)
|
||||
theta = np.where(theta < 0, theta + 2 * math.pi, theta) # 调整角度到[0, 2π]
|
||||
|
||||
# 计算源图像坐标
|
||||
xp = (theta / (2 * math.pi) * cols).astype(np.float32)
|
||||
yp = ((1 - r / R) * rows).astype(np.float32) # 修正yp计算
|
||||
|
||||
# 使用remap进行高质量变换
|
||||
valid_mask = r <= R
|
||||
|
||||
# 为了避免边缘伪影,可以不强制设为-1,而是让它自然插值(或者保持-1)
|
||||
# 这里保持原逻辑,但使用更高质量插值
|
||||
xp[~valid_mask] = -1
|
||||
yp[~valid_mask] = -1
|
||||
|
||||
# 使用 Lanczos 插值以获得更好的质量
|
||||
new_img = cv2.remap(img, xp, yp, cv2.INTER_LANCZOS4, borderMode=cv2.BORDER_CONSTANT, borderValue=0)
|
||||
|
||||
return new_img
|
||||
|
||||
# 更高质量的还原方案
|
||||
def spherical_to_panorama(transformed_img, original_rows, original_cols):
|
||||
"""
|
||||
使用向量化计算和Lanczos插值的高质量还原
|
||||
"""
|
||||
D, D_c, c = transformed_img.shape
|
||||
# 优化:根据中间图的实际尺寸计算半径,自适应不同分辨率
|
||||
R = D // 2
|
||||
cx = R
|
||||
cy = R
|
||||
|
||||
# 向量化生成网格
|
||||
x = np.arange(original_cols, dtype=np.float32)
|
||||
y = np.arange(original_rows, dtype=np.float32)
|
||||
X, Y = np.meshgrid(x, y)
|
||||
|
||||
# 计算极坐标参数
|
||||
# r = (1 - y / original_rows) * R
|
||||
r = (1.0 - Y / original_rows) * R
|
||||
# theta = x / original_cols * 2 * math.pi
|
||||
theta = X / original_cols * 2 * math.pi
|
||||
|
||||
# 极坐标转笛卡尔坐标
|
||||
# 必须转换为 float32 才能用于 cv2.remap
|
||||
map_x = (cx + r * np.cos(theta)).astype(np.float32)
|
||||
map_y = (cy + r * np.sin(theta)).astype(np.float32)
|
||||
|
||||
# 使用 Lanczos 插值(更高质量)
|
||||
restored_img = cv2.remap(transformed_img, map_x, map_y,
|
||||
interpolation=cv2.INTER_LANCZOS4,
|
||||
borderMode=cv2.BORDER_CONSTANT,
|
||||
borderValue=0)
|
||||
|
||||
return restored_img
|
||||
|
||||
class LamSpherical2Panorama:
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required":
|
||||
{
|
||||
"images": ("IMAGE", ),
|
||||
"width": ("INT", {"default": 4096, "min": 64, "max": 9999999}),
|
||||
"height": ("INT", {"default": 2048, "min": 64, "max": 9999999}),
|
||||
},
|
||||
}
|
||||
|
||||
CATEGORY = "lam"
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("图片",)
|
||||
FUNCTION = "spherical2Panorama"
|
||||
def spherical2Panorama(self, images,width,height):
|
||||
imaget_np=images.numpy()
|
||||
imgt_shape=imaget_np.shape
|
||||
prd_images=[]
|
||||
for i in range(imgt_shape[0]):
|
||||
imaget=np.uint8(imaget_np[i]*255)
|
||||
imaget = cv2.cvtColor(imaget, cv2.COLOR_BGR2RGB)
|
||||
transformed_img = spherical_to_panorama(imaget,height,width)
|
||||
|
||||
fin_color = cv2.cvtColor(transformed_img, cv2.COLOR_BGR2RGB)
|
||||
prd_images.append(torch.from_numpy(np.array(fin_color).astype(np.float32) / 255.0))
|
||||
|
||||
return (torch.stack(prd_images),)
|
||||
|
||||
|
||||
class LamPanorama2Spherical:
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required":
|
||||
{"images": ("IMAGE", ),
|
||||
},
|
||||
}
|
||||
|
||||
CATEGORY = "lam"
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("图片",)
|
||||
FUNCTION = "panorama2Spherical"
|
||||
def panorama2Spherical(self, images):
|
||||
imaget_np=images.numpy()
|
||||
imgt_shape=imaget_np.shape
|
||||
prd_images=[]
|
||||
for i in range(imgt_shape[0]):
|
||||
imaget=np.uint8(imaget_np[i]*255)
|
||||
imaget = cv2.cvtColor(imaget, cv2.COLOR_BGR2RGB)
|
||||
transformed_img = panorama_to_spherical(imaget)
|
||||
|
||||
fin_color = cv2.cvtColor(transformed_img, cv2.COLOR_BGR2RGB)
|
||||
prd_images.append(torch.from_numpy(np.array(fin_color).astype(np.float32) / 255.0))
|
||||
|
||||
return (torch.stack(prd_images),)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"LamPanorama2Spherical": LamPanorama2Spherical,
|
||||
"LamSpherical2Panorama": LamSpherical2Panorama
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"LamPanorama2Spherical": "全景图转球面图",
|
||||
"LamSpherical2Panorama": "球面图转全景图"
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
from .src.utils.uitls import AlwaysEqualProxy
|
||||
|
||||
class LamSwitchStart:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"base": (AlwaysEqualProxy("*"),),
|
||||
},
|
||||
"hidden": {
|
||||
"switchs": "STRING", #隐藏参数
|
||||
}
|
||||
}
|
||||
RETURN_TYPES = (AlwaysEqualProxy("*"),)
|
||||
|
||||
RETURN_NAMES = ('base',)
|
||||
|
||||
FUNCTION = "switch_start"
|
||||
|
||||
OUTPUT_NODE = False
|
||||
|
||||
CATEGORY = "lam"
|
||||
|
||||
def switch_start(self,base,switchs=''):
|
||||
return (base,)
|
||||
|
||||
class LamSwitchMiddle:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"base": (AlwaysEqualProxy("*"),),
|
||||
"new": (AlwaysEqualProxy("*"),{"lazy":True}),
|
||||
},
|
||||
"hidden": {
|
||||
"unique_id": "UNIQUE_ID", #节点编号
|
||||
"prompt": "PROMPT", #流程节点信息
|
||||
}
|
||||
}
|
||||
RETURN_TYPES = (AlwaysEqualProxy("*"),)
|
||||
|
||||
RETURN_NAMES = ('base',)
|
||||
|
||||
FUNCTION = "switch_middle"
|
||||
|
||||
OUTPUT_NODE = False
|
||||
|
||||
CATEGORY = "lam"
|
||||
|
||||
def check_lazy_status(self,base,new,unique_id,prompt):
|
||||
needed = []
|
||||
start_id = prompt[unique_id]['inputs']['base'][0]
|
||||
index = 0
|
||||
while prompt[start_id]['class_type']!='LamSwitchStart':
|
||||
start_id=prompt[start_id]['inputs']['base'][0]
|
||||
index+=1
|
||||
switchsStr = prompt[start_id]['inputs']['switchs']
|
||||
switchs = switchsStr.split(',')
|
||||
if switchs[index]=='1':
|
||||
needed.append('new')
|
||||
return needed
|
||||
|
||||
def switch_middle(self,base,new,unique_id,prompt):
|
||||
start_id = prompt[unique_id]['inputs']['base'][0]
|
||||
index = 0
|
||||
while prompt[start_id]['class_type']!='LamSwitchStart':
|
||||
start_id=prompt[start_id]['inputs']['base'][0]
|
||||
index+=1
|
||||
switchsStr = prompt[start_id]['inputs']['switchs']
|
||||
switchs = switchsStr.split(',')
|
||||
if switchs[index]=='1':
|
||||
return (new,)
|
||||
else:
|
||||
return (base,)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"LamSwitchStart": LamSwitchStart,
|
||||
"LamSwitchMiddle":LamSwitchMiddle
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"LamSwitchStart": "开关控制",
|
||||
"LamSwitchMiddle": "开关分段"
|
||||
}
|
||||
Reference in New Issue
Block a user