一键启动停止项目

This commit is contained in:
tianmo committed 2026-09-02 10:24:48 +08:00
1 parent 96ee180564
commit 88cdcdfb02
11 files changed
+233 -8

No files matched your search

+1
View File
@@ -15,6 +15,7 @@ vite.config.d.ts
.vite/ .vite/
.cache/ .cache/
coverage/ coverage/
/.run/
# Logs # Logs
*.log *.log
npm-debug.log* npm-debug.log*
@@ -1 +1 @@
.stage-wrap[data-v-646aa9bd]{position:relative;width:100%;height:100%;overflow:hidden}.stage-holder[data-v-646aa9bd]{width:100%;height:100%;cursor:grab;touch-action:none;background-color:#0e1219;outline:none}.stage-holder.grid[data-v-646aa9bd]{background:conic-gradient(#141a26 90deg,transparent 90deg 180deg,#141a26 180deg 270deg,transparent 270deg);background-size:28px 28px}.stage-holder[data-v-646aa9bd]:active{cursor:grabbing}.topbar[data-v-646aa9bd]{position:absolute;top:10px;left:10px;right:10px;display:flex;align-items:center;gap:10px;padding:8px 12px;background:#141420d9;border-radius:10px;border:1px solid rgba(255,255,255,.08);font-size:12px;color:#aab;z-index:5;flex-wrap:wrap}.topbar input[type=range][data-v-646aa9bd]{background:#1a1a28;border:1px solid #2e2e44;color:#dde;border-radius:5px}.tool-group[data-v-646aa9bd]{display:flex;gap:6px;margin-left:6px;padding-left:12px;border-left:1px solid #2a2a3c}.tool-btn[data-v-646aa9bd]{width:30px;height:26px;background:#1e1e2e;border:1px solid #2e2e44;border-radius:6px;color:#aab;font-size:14px;cursor:pointer;display:flex;align-items:center;justify-content:center}.tool-btn[data-v-646aa9bd]:hover{background:#2b3a5c;color:#fff}.tool-btn.on[data-v-646aa9bd]{background:#3a5a8c;color:#fff;border-color:#5a7ab8}.background-menu[data-v-646aa9bd]{position:relative;flex:0 0 auto}.background-btn[data-v-646aa9bd]{height:28px;padding:0 11px;border:1px solid #3a4963;border-radius:6px;background:#202a3b;color:#cbd7ed;font-size:12px;cursor:pointer}.background-btn[data-v-646aa9bd]:hover,.background-btn.on[data-v-646aa9bd]{border-color:#7774ff;background:#343b63;color:#fff}.background-panel[data-v-646aa9bd]{position:absolute;top:calc(100% + 9px);right:0;width:248px;box-sizing:border-box;padding:10px;border:1px solid #343b50;border-radius:9px;background:#141420fa;color:#aeb8cc;box-shadow:0 8px 24px #0000007a;z-index:12}.background-upload[data-v-646aa9bd]{width:100%;height:30px;border:1px solid #5652b5;border-radius:6px;background:#4540a0;color:#fff;cursor:pointer}.background-upload[data-v-646aa9bd]:hover{border-color:#8982ff;background:#554dc2}.background-clear[data-v-646aa9bd]{width:100%;height:28px;margin-top:6px;border:1px solid #70404a;border-radius:6px;background:#512a33;color:#efb5bd;cursor:pointer}.background-clear[data-v-646aa9bd]:hover:not(:disabled){border-color:#b65b6a;background:#6a303b;color:#fff}.background-clear[data-v-646aa9bd]:disabled{opacity:.42;cursor:not-allowed}.background-file-input[data-v-646aa9bd]{display:none}.background-file-name[data-v-646aa9bd]{margin:7px 2px 9px;overflow:hidden;color:#737f98;font-size:10px;text-overflow:ellipsis;white-space:nowrap}.background-position-row[data-v-646aa9bd]{display:grid;grid-template-columns:1fr 1fr;gap:8px}.background-position-row label[data-v-646aa9bd]{display:flex;align-items:center;gap:5px;color:#8995ad;font-size:11px}.background-position-row input[data-v-646aa9bd]{width:0;min-width:0;flex:1;box-sizing:border-box;height:28px;padding:0 6px;border:1px solid #343e55;border-radius:5px;background:#101522;color:#e1e6f2}.background-opacity-row[data-v-646aa9bd]{display:grid;grid-template-columns:46px 1fr 34px;align-items:center;gap:7px;margin-top:10px;font-size:11px}.background-opacity-row input[data-v-646aa9bd]{min-width:0;accent-color:#675de0}.background-opacity-row b[data-v-646aa9bd]{color:#aeb8cc;font-size:10px;text-align:right;font-variant-numeric:tabular-nums}.help-btn[data-v-646aa9bd]{height:28px;margin-left:auto;padding:0 11px;flex:0 0 auto;border:1px solid #3a4963;border-radius:6px;background:#202a3b;color:#cbd7ed;font-size:12px;font-weight:700;cursor:pointer}.help-btn[data-v-646aa9bd]:hover,.help-btn.on[data-v-646aa9bd]{border-color:#7774ff;background:#343b63;color:#fff}.zoom-pct[data-v-646aa9bd]{margin-left:0;font-variant-numeric:tabular-nums;color:#9aa;min-width:44px;text-align:center}.help-overlay[data-v-646aa9bd]{position:absolute;top:0;right:0;bottom:0;left:0;z-index:40;display:flex;align-items:center;justify-content:center;padding:72px 24px 28px;box-sizing:border-box;background:#05080fb8;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px)}.help-dialog[data-v-646aa9bd]{width:min(820px,100%);max-height:100%;display:flex;flex-direction:column;overflow:hidden;border:1px solid #3a4864;border-radius:14px;background:#141a27;color:#b8c3d8;box-shadow:0 22px 70px #0000009e}.help-head[data-v-646aa9bd]{display:flex;align-items:flex-start;justify-content:space-between;gap:18px;padding:18px 20px 15px;border-bottom:1px solid #2b354a;background:linear-gradient(135deg,#202942,#181e2d)}.help-head h2[data-v-646aa9bd]{margin:0;color:#eef2ff;font-size:19px}.help-head p[data-v-646aa9bd]{margin:5px 0 0;color:#7786a3;font-size:11px}.help-close[data-v-646aa9bd]{width:28px;height:28px;flex:0 0 28px;border:1px solid #43516b;border-radius:6px;background:#273249;color:#ccd7ed;cursor:pointer}.help-close[data-v-646aa9bd]:hover{border-color:#7774ff;background:#3b4670;color:#fff}.help-body[data-v-646aa9bd]{min-height:0;overflowLine truncated .stage-wrap[data-v-1b609e97]{position:relative;width:100%;height:100%;overflow:hidden}.stage-holder[data-v-1b609e97]{width:100%;height:100%;cursor:grab;touch-action:none;background-color:#0e1219;outline:none}.stage-holder.grid[data-v-1b609e97]{background:conic-gradient(#141a26 90deg,transparent 90deg 180deg,#141a26 180deg 270deg,transparent 270deg);background-size:28px 28px}.stage-holder[data-v-1b609e97]:active{cursor:grabbing}.topbar[data-v-1b609e97]{position:absolute;top:10px;left:10px;right:10px;display:flex;align-items:center;gap:10px;padding:8px 12px;background:#141420d9;border-radius:10px;border:1px solid rgba(255,255,255,.08);font-size:12px;color:#aab;z-index:5;flex-wrap:wrap}.topbar input[type=range][data-v-1b609e97]{background:#1a1a28;border:1px solid #2e2e44;color:#dde;border-radius:5px}.tool-group[data-v-1b609e97]{display:flex;gap:6px;margin-left:6px;padding-left:12px;border-left:1px solid #2a2a3c}.tool-btn[data-v-1b609e97]{width:30px;height:26px;background:#1e1e2e;border:1px solid #2e2e44;border-radius:6px;color:#aab;font-size:14px;cursor:pointer;display:flex;align-items:center;justify-content:center}.tool-btn[data-v-1b609e97]:hover{background:#2b3a5c;color:#fff}.tool-btn.on[data-v-1b609e97]{background:#3a5a8c;color:#fff;border-color:#5a7ab8}.background-menu[data-v-1b609e97]{position:relative;flex:0 0 auto}.background-btn[data-v-1b609e97]{height:28px;padding:0 11px;border:1px solid #3a4963;border-radius:6px;background:#202a3b;color:#cbd7ed;font-size:12px;cursor:pointer}.background-btn[data-v-1b609e97]:hover,.background-btn.on[data-v-1b609e97]{border-color:#7774ff;background:#343b63;color:#fff}.background-panel[data-v-1b609e97]{position:absolute;top:calc(100% + 9px);right:0;width:248px;box-sizing:border-box;padding:10px;border:1px solid #343b50;border-radius:9px;background:#141420fa;color:#aeb8cc;box-shadow:0 8px 24px #0000007a;z-index:12}.background-upload[data-v-1b609e97]{width:100%;height:30px;border:1px solid #5652b5;border-radius:6px;background:#4540a0;color:#fff;cursor:pointer}.background-upload[data-v-1b609e97]:hover{border-color:#8982ff;background:#554dc2}.background-clear[data-v-1b609e97]{width:100%;height:28px;margin-top:6px;border:1px solid #70404a;border-radius:6px;background:#512a33;color:#efb5bd;cursor:pointer}.background-clear[data-v-1b609e97]:hover:not(:disabled){border-color:#b65b6a;background:#6a303b;color:#fff}.background-clear[data-v-1b609e97]:disabled{opacity:.42;cursor:not-allowed}.background-file-input[data-v-1b609e97]{display:none}.background-file-name[data-v-1b609e97]{margin:7px 2px 9px;overflow:hidden;color:#737f98;font-size:10px;text-overflow:ellipsis;white-space:nowrap}.background-position-row[data-v-1b609e97]{display:grid;grid-template-columns:1fr 1fr;gap:8px}.background-position-row label[data-v-1b609e97]{display:flex;align-items:center;gap:5px;color:#8995ad;font-size:11px}.background-position-row input[data-v-1b609e97]{width:0;min-width:0;flex:1;box-sizing:border-box;height:28px;padding:0 6px;border:1px solid #343e55;border-radius:5px;background:#101522;color:#e1e6f2}.background-opacity-row[data-v-1b609e97]{display:grid;grid-template-columns:46px 1fr 34px;align-items:center;gap:7px;margin-top:10px;font-size:11px}.background-opacity-row input[data-v-1b609e97]{min-width:0;accent-color:#675de0}.background-opacity-row b[data-v-1b609e97]{color:#aeb8cc;font-size:10px;text-align:right;font-variant-numeric:tabular-nums}.help-btn[data-v-1b609e97]{height:28px;margin-left:auto;padding:0 11px;flex:0 0 auto;border:1px solid #3a4963;border-radius:6px;background:#202a3b;color:#cbd7ed;font-size:12px;font-weight:700;cursor:pointer}.help-btn[data-v-1b609e97]:hover,.help-btn.on[data-v-1b609e97]{border-color:#7774ff;background:#343b63;color:#fff}.zoom-pct[data-v-1b609e97]{margin-left:0;font-variant-numeric:tabular-nums;color:#9aa;min-width:44px;text-align:center}.help-overlay[data-v-1b609e97]{position:absolute;top:0;right:0;bottom:0;left:0;z-index:40;display:flex;align-items:center;justify-content:center;padding:72px 24px 28px;box-sizing:border-box;background:#05080fb8;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px)}.help-dialog[data-v-1b609e97]{width:min(820px,100%);max-height:100%;display:flex;flex-direction:column;overflow:hidden;border:1px solid #3a4864;border-radius:14px;background:#141a27;color:#b8c3d8;box-shadow:0 22px 70px #0000009e}.help-head[data-v-1b609e97]{display:flex;align-items:flex-start;justify-content:space-between;gap:18px;padding:18px 20px 15px;border-bottom:1px solid #2b354a;background:linear-gradient(135deg,#202942,#181e2d)}.help-head h2[data-v-1b609e97]{margin:0;color:#eef2ff;font-size:19px}.help-head p[data-v-1b609e97]{margin:5px 0 0;color:#7786a3;font-size:11px}.help-close[data-v-1b609e97]{width:28px;height:28px;flex:0 0 28px;border:1px solid #43516b;border-radius:6px;background:#273249;color:#ccd7ed;cursor:pointer}.help-close[data-v-1b609e97]:hover{border-color:#7774ff;background:#3b4670;color:#fff}.help-body[data-v-1b609e97]{min-height:0;overflowLine truncated
@@ -1165,7 +1165,7 @@ void main(void)\r
font-weight: ${t.weight}; font-weight: ${t.weight};
font-style: ${t.style}; font-style: ${t.style};
font-display: ${t.display}; font-display: ${t.display};
}`,this._stylesheet)}get stylesheet(){return this._stylesheet}set stylesheet(e){this._stylesheet!==e&&(this._stylesheet=e,this.styleID++)}normalizeColor(e){return Array.isArray(e)&&(e=o_(e)),typeof e=="number"?r_(e):e}dropShadowToCSS(){let e=this.normalizeColor(this.dropShadowColor);const t=this.dropShadowAlpha,s=Math.round(Math.cos(this.dropShadowAngle)*this.dropShadowDistance),n=Math.round(Math.sin(this.dropShadowAngle)*this.dropShadowDistance);e.startsWith("#")&&t<1&&(e+=(t*255|0).toString(16).padStart(2,"0"));const r=`${s}px ${n}px`;return this.dropShadowBlur>0?`text-shadow: ${r} ${this.dropShadowBlur}px ${e}`:`text-shadow: ${r} ${e}`}reset(){Object.assign(this,qr.defaultOptions)}onBeforeDraw(){const{fontsDirty:e}=this;return this.fontsDirty=!1,this.isSafari&&this._fonts.length>0&&e?new Promise(t=>setTimeout(t,100)):Promise.resolve()}get isSafari(){const{userAgent:e}=Ve.ADAPTER.getNavigator();return/^((?!chrome|android).)*safari/i.test(e)}set fillGradientStops(e){console.warn("[HTMLTextStyle] fillGradientStops is not supported by HTMLText")}get fillGradientStops(){return super.fillGradientStops}set fillGradientType(e){console.warn("[HTMLTextStyle] fillGradientType is not supported by HTMLText")}get fillGradientType(){return super.fillGradientType}set miterLimit(e){console.warn("[HTMLTextStyle] miterLimit is not supported by HTMLText")}get miterLimit(){return super.miterLimit}set trim(e){console.warn("[HTMLTextStyle] trim is not supported by HTMLText")}get trim(){return super.trim}set textBaseline(e){console.warn("[HTMLTextStyle] textBaseline is not supported by HTMLText")}get textBaseline(){return super.textBaseline}set leading(e){console.warn("[HTMLTextStyle] leading is not supported by HTMLText")}get leading(){return super.leading}set lineJoin(e){console.warn("[HTMLTextStyle] lineJoin is not supported by HTMLText")}get lineJoin(){return super.lineJoin}};_d.availableFonts={},_d.defaultOptions={align:"left",breakWords:!1,dropShadow:!1,dropShadowAlpha:1,dropShadowAngle:Math.PI/6,dropShadowBlur:0,dropShadowColor:"black",dropShadowDistance:5,fill:"black",fontFamily:"Arial",fontSize:26,fontStyle:"normal",fontVariant:"normal",fontWeight:"normal",letterSpacing:0,lineHeight:0,padding:0,stroke:"black",strokeThickness:0,whiteSpace:"normal",wordWrap:!1,wordWrapWidth:100};let xu=_d;const Ja=class Kr extends Qn{constructor(e="",t={}){super(et.EMPTY),this._text=null,this._style=null,this._autoResolution=!0,this.localStyleID=-1,this.dirty=!1,this._updateID=0,this.ownsStyle=!1;const s=new Image,n=et.from(s,{scaleMode:Ve.SCALE_MODE,resourceOptions:{autoLoad:!1}});n.orig=new lt,n.trim=new lt,this.texture=n;const r="http://www.w3.org/2000/svg",o="http://www.w3.org/1999/xhtml",a=document.createElementNS(r,"svg"),h=document.createElementNS(r,"foreignObject"),l=document.createElementNS(o,"div"),c=document.createElementNS(o,"style");h.setAttribute("width","10000"),h.setAttribute("height","10000"),h.style.overflow="hidden",a.appendChild(h),this.maxWidth=Kr.defaultMaxWidth,this.maxHeight=Kr.defaultMaxHeight,this._domElement=l,this._styleElement=c,this._svgRoot=a,this._foreignObject=h,this._foreignObject.appendChild(c),this._foreignObject.appendChild(l),this._image=s,this._loadImage=new Image,this._autoResolution=Kr.defaultAutoResolution,this._resolution=Kr.defaultResolution??Ve.RESOLUTION,this.text=e,this.style=t}measureText(e){var c,u;const{text:t,style:s,resolution:n}=Object.assign({text:this._text,style:this._style,resolution:this._resolution},e);Object.assign(this._domElement,{innerHTML:t,style:s.toCSS(n)}),this._styleElement.textContent=s.toGlobalCSS(),document.body.appendChild(this._svgRoot);const r=this._domElement.getBoundingClientRect();this._svgRoot.remove();const{width:o,height:a}=r;(o>this.maxWidth||a>this.maxHeight)&&console.warn("[HTMLText] Large expanse of text, increase HTMLText.maxWidth or HTMLText.maxHeight property.");const h=Math.min(this.maxWidth,Math.ceil(o)),l=Math.min(this.maxHeight,Math.ceil(a));return this._svgRoot.setAttribute("width",h.toString()),this._svgRoot.setAttribute("height",l.toString()),t!==this._text&&(this._domElement.innerHTML=this._text),s!==this._style&&(Object.assign(this._domElement,{style:(c=this._style)==null?void 0:c.toCSS(n)}),this._styleElement.textContent=(u=this._style)==null?void 0:u.toGlobalCSS()),{width:h+s.padding*2,height:l+s.padding*2}}async updateText(e=!0){const{style:t,_image:s,_loadImage:n}=this;if(this.localStyleID!==t.styleID&&(this.dirty=!0,this.localStyleID=t.styleID),!this.dirty&&e)return;const{width:r,height:o}=this.measureText();s.width=n.width=Math.ceil(Math.max(1,r)),s.height=n.height=Math.ceil(Math.max(1,o)),this._updateID++;const a=this._updateID;await new Promise(h=>{n.onload=async()=>{if(a<this._updateID){h();return}await t.onBeforeDraw(),s.src=n.src,n.onload=null,n.src="",this.updateTexture(),h()};const l=new XMLSerializer().serializeToString(this._svgRoot);n.src=`data:image/svg+xml;charset=utf8,${encodeURIComponent(l)}`})}get source(){ret Line truncated }`,this._stylesheet)}get stylesheet(){return this._stylesheet}set stylesheet(e){this._stylesheet!==e&&(this._stylesheet=e,this.styleID++)}normalizeColor(e){return Array.isArray(e)&&(e=o_(e)),typeof e=="number"?r_(e):e}dropShadowToCSS(){let e=this.normalizeColor(this.dropShadowColor);const t=this.dropShadowAlpha,s=Math.round(Math.cos(this.dropShadowAngle)*this.dropShadowDistance),n=Math.round(Math.sin(this.dropShadowAngle)*this.dropShadowDistance);e.startsWith("#")&&t<1&&(e+=(t*255|0).toString(16).padStart(2,"0"));const r=`${s}px ${n}px`;return this.dropShadowBlur>0?`text-shadow: ${r} ${this.dropShadowBlur}px ${e}`:`text-shadow: ${r} ${e}`}reset(){Object.assign(this,qr.defaultOptions)}onBeforeDraw(){const{fontsDirty:e}=this;return this.fontsDirty=!1,this.isSafari&&this._fonts.length>0&&e?new Promise(t=>setTimeout(t,100)):Promise.resolve()}get isSafari(){const{userAgent:e}=Ve.ADAPTER.getNavigator();return/^((?!chrome|android).)*safari/i.test(e)}set fillGradientStops(e){console.warn("[HTMLTextStyle] fillGradientStops is not supported by HTMLText")}get fillGradientStops(){return super.fillGradientStops}set fillGradientType(e){console.warn("[HTMLTextStyle] fillGradientType is not supported by HTMLText")}get fillGradientType(){return super.fillGradientType}set miterLimit(e){console.warn("[HTMLTextStyle] miterLimit is not supported by HTMLText")}get miterLimit(){return super.miterLimit}set trim(e){console.warn("[HTMLTextStyle] trim is not supported by HTMLText")}get trim(){return super.trim}set textBaseline(e){console.warn("[HTMLTextStyle] textBaseline is not supported by HTMLText")}get textBaseline(){return super.textBaseline}set leading(e){console.warn("[HTMLTextStyle] leading is not supported by HTMLText")}get leading(){return super.leading}set lineJoin(e){console.warn("[HTMLTextStyle] lineJoin is not supported by HTMLText")}get lineJoin(){return super.lineJoin}};_d.availableFonts={},_d.defaultOptions={align:"left",breakWords:!1,dropShadow:!1,dropShadowAlpha:1,dropShadowAngle:Math.PI/6,dropShadowBlur:0,dropShadowColor:"black",dropShadowDistance:5,fill:"black",fontFamily:"Arial",fontSize:26,fontStyle:"normal",fontVariant:"normal",fontWeight:"normal",letterSpacing:0,lineHeight:0,padding:0,stroke:"black",strokeThickness:0,whiteSpace:"normal",wordWrap:!1,wordWrapWidth:100};let xu=_d;const Ja=class Kr extends Qn{constructor(e="",t={}){super(et.EMPTY),this._text=null,this._style=null,this._autoResolution=!0,this.localStyleID=-1,this.dirty=!1,this._updateID=0,this.ownsStyle=!1;const s=new Image,n=et.from(s,{scaleMode:Ve.SCALE_MODE,resourceOptions:{autoLoad:!1}});n.orig=new lt,n.trim=new lt,this.texture=n;const r="http://www.w3.org/2000/svg",o="http://www.w3.org/1999/xhtml",a=document.createElementNS(r,"svg"),h=document.createElementNS(r,"foreignObject"),l=document.createElementNS(o,"div"),c=document.createElementNS(o,"style");h.setAttribute("width","10000"),h.setAttribute("height","10000"),h.style.overflow="hidden",a.appendChild(h),this.maxWidth=Kr.defaultMaxWidth,this.maxHeight=Kr.defaultMaxHeight,this._domElement=l,this._styleElement=c,this._svgRoot=a,this._foreignObject=h,this._foreignObject.appendChild(c),this._foreignObject.appendChild(l),this._image=s,this._loadImage=new Image,this._autoResolution=Kr.defaultAutoResolution,this._resolution=Kr.defaultResolution??Ve.RESOLUTION,this.text=e,this.style=t}measureText(e){var c,u;const{text:t,style:s,resolution:n}=Object.assign({text:this._text,style:this._style,resolution:this._resolution},e);Object.assign(this._domElement,{innerHTML:t,style:s.toCSS(n)}),this._styleElement.textContent=s.toGlobalCSS(),document.body.appendChild(this._svgRoot);const r=this._domElement.getBoundingClientRect();this._svgRoot.remove();const{width:o,height:a}=r;(o>this.maxWidth||a>this.maxHeight)&&console.warn("[HTMLText] Large expanse of text, increase HTMLText.maxWidth or HTMLText.maxHeight property.");const h=Math.min(this.maxWidth,Math.ceil(o)),l=Math.min(this.maxHeight,Math.ceil(a));return this._svgRoot.setAttribute("width",h.toString()),this._svgRoot.setAttribute("height",l.toString()),t!==this._text&&(this._domElement.innerHTML=this._text),s!==this._style&&(Object.assign(this._domElement,{style:(c=this._style)==null?void 0:c.toCSS(n)}),this._styleElement.textContent=(u=this._style)==null?void 0:u.toGlobalCSS()),{width:h+s.padding*2,height:l+s.padding*2}}async updateText(e=!0){const{style:t,_image:s,_loadImage:n}=this;if(this.localStyleID!==t.styleID&&(this.dirty=!0,this.localStyleID=t.styleID),!this.dirty&&e)return;const{width:r,height:o}=this.measureText();s.width=n.width=Math.ceil(Math.max(1,r)),s.height=n.height=Math.ceil(Math.max(1,o)),this._updateID++;const a=this._updateID;await new Promise(h=>{n.onload=async()=>{if(a<this._updateID){h();return}await t.onBeforeDraw(),s.src=n.src,n.onload=null,n.src="",this.updateTexture(),h()};const l=new XMLSerializer().serializeToString(this._svgRoot);n.src=`data:image/svg+xml;charset=utf8,${encodeURIComponent(l)}`})}get source(){ret Line truncated
attribute vec2 aVertexPosition; attribute vec2 aVertexPosition;
attribute vec2 aTextureCoord; attribute vec2 aTextureCoord;
@@ -1235,4 +1235,4 @@ void main(void){
gl_FragColor.a = color.a * vColor.a; gl_FragColor.a = color.a * vColor.a;
gl_FragColor.rgb = ((color.a - 1.0) * vDarkColor.a + 1.0 - color.rgb) * vDarkColor.rgb + color.rgb * vColor.rgb; gl_FragColor.rgb = ((color.a - 1.0) * vDarkColor.a + 1.0 - color.rgb) * vDarkColor.rgb + color.rgb * vColor.rgb;
} }
`;class ox extends Bn{constructor(e){super(e),this.shaderGenerator=new hv(DA,NA),this.geometryClass=LA,this.vertexSize=7}packInterleavedGeometry(e,t,s,n,r){const{uint32View:o,float32View:a}=t,h=n/this.vertexSize,l=e.uvs,c=e.indices,u=e.vertexData,f=e._texture.baseTexture._batchLocation,d=Math.min(e.worldAlpha,1),m=At.shared.setValue(e._tintRGB).toPremultiplied(d,!0),g=At.shared.setValue(e._darkTintRGB).premultiply(d,!0).toPremultiplied(1,!1);for(let b=0;b<u.length;b+=2)a[n++]=u[b],a[n++]=u[b+1],a[n++]=l[b],a[n++]=l[b+1],o[n++]=m,o[n++]=g,a[n++]=f;for(let b=0;b<c.length;b++)s[r++]=h+c[b]}}A(ox,"extension",{name:"darkTintBatch",type:Oe.RendererPlugin});He.add(ox);class UA{constructor(){A(this,"container",new gi);A(this,"entries",new Map)}sync(e,t,s){var n;for(const[r,o]of this.entries)e.some(a=>a.id===r)||(this.container.removeChild(o.root),o.root.destroy({children:!0}),this.entries.delete(r));for(const r of e){const o=EE(r.id);let a=this.entries.get(r.id);if(a&&a.assetVersion!==r.assetVersion&&!o&&(this.container.removeChild(a.root),a.root.destroy({children:!0}),this.entries.delete(r.id),a=void 0),(!a||a.assetVersion!==r.assetVersion)&&o){a&&(this.container.removeChild(a.root),a.root.destroy({children:!0}));const f=new gi,d=new Rd({skeletonData:o.skeletonData,autoUpdate:!1,darkTint:!1});o.skeletonData.defaultSkin&&d.skeleton.setSkin(o.skeletonData.defaultSkin),d.skeleton.setSlotsToSetupPose(),f.addChild(d),this.container.addChild(f),a={root:f,spine:d,assetVersion:r.assetVersion,animation:""},this.entries.set(r.id,a)}if(!a)continue;const h=a.root,l=a.spine,c=r.visible!==!1&&r.enabled;h.visible=c,h.renderable=c,h.position.set(r.x,-r.y),h.rotation=-r.rotation*Math.PI/180,h.scale.set(r.scaleX,r.scaleY),a.animation!==r.selectedAnimation&&(a.animation=r.selectedAnimation,l.skeleton.setToSetupPose(),l.state.clearTracks(),a.animation&&l.state.setAnimation(0,a.animation,!1));const u=l.state.getCurrent(0);if(u){const f=((n=r.animations.find(m=>m.name===r.selectedAnimation))==null?void 0:n.duration)||0,d=Math.max(0,t/Math.max(1,s)-r.timeOffset);u.trackTime=f>0?Math.min(d,f):d}l.update(0)}}getBoneWorldTransform(e,t){const s=this.entries.get(e),n=s==null?void 0:s.spine.skeleton.findBone(t);if(!s||!n||!n.active)return null;const r=s.root,o=Math.cos(r.rotation),a=Math.sin(r.rotation),h=o*r.scale.x,l=-a*r.scale.y,c=a*r.scale.x,u=o*r.scale.y,f=h*n.a+l*n.c,d=h*n.b+l*n.d,m=c*n.a+u*n.c,g=c*n.b+u*n.d,b=r.position.x+h*n.worldX+l*n.worldY,_=r.position.y+c*n.worldX+u*n.worldY;return{x:b,y:-_,rotation:-Math.atan2(m,f)*180/Math.PI,scaleX:Math.max(1e-4,Math.hypot(f,m)),scaleY:Math.max(1e-4,Math.hypot(d,g))}}destroy(){for(const e of this.entries.values())e.root.removeFromParent(),e.root.destroy({children:!0});this.entries.clear(),this.container.removeFromParent(),this.container.destroy({children:!0})}}function Yn(i,e,t){const s=Number(i[e]);return Number.isFinite(s)?s:t}function ki(i,e,t,s){i[e]==null&&(i[e]=Yn(i,t,s))}function pf(i,e,t){if(typeof i.curve!="number")return;const s=Yn(i,"time",0),r=Yn(e,"time",0)-s,o=i.curve,a=Yn(i,"c2",0),h=Yn(i,"c3",1),l=Yn(i,"c4",1);i.curve=t.flatMap(([c,u])=>[s+r*o,c+(u-c)*a,s+r*h,c+(u-c)*l]),delete i.c2,delete i.c3,delete i.c4}function Wn(i,e){if(Array.isArray(i))for(let t=0;t<i.length-1;t++){const s=i[t],n=i[t+1];pf(s,n,e.map(({key:r,fallback:o})=>[Yn(s,r,o),Yn(n,r,o)]))}}function VA(i){if(Array.isArray(i))for(let e=0;e<i.length-1;e++)pf(i[e],i[e+1],[[0,1]])}function Yr(i,e){const t=typeof i=="string"?i.replace(/^#/,""):"",s=t.length===6?`${t}ff`:t.padEnd(8,"f").slice(0,8),n=[0,2,4,6].map(r=>Number.parseInt(s.slice(r,r+2),16)/255);return e?n:n.slice(0,3)}function dg(i,e){if(Array.isArray(i))for(let t=0;t<i.length-1;t++){const s=i[t],n=i[t+1],r=e?[...Yr(s.light,!0),...Yr(s.dark,!1)]:Yr(s.color,!0),o=e?[...Yr(n.light,!0),...Yr(n.dark,!1)]:Yr(n.color,!0);pf(s,n,r.map((a,h)=>[a,o[h]]))}}function XA(i){for(const e of i.bones||[])e.inherit==null&&e.transform!=null&&(e.inherit=e.transform);for(const e of i.transform||[])ki(e,"mixRotate","rotateMix",1),ki(e,"mixX","translateMix",1),e.mixY==null&&(e.mixY=e.mixX),ki(e,"mixScaleX","scaleMix",1),e.mixScaleY==null&&(e.mixScaleY=e.mixScaleX),ki(e,"mixShearY","shearMix",1);for(const e of i.path||[])ki(e,"mixRotate","rotateMix",1),ki(e,"mixX","translateMix",1),e.mixY==null&&(e.mixY=e.mixX)}function YA(i){for(const e of i.skins||[])for(const t of Object.values(e.attachments||{}))for(const s of Object.values(t||{}))s.parent!=null&&s.timelines==null&&s.deform!=null&&(s.timelines=s.deform)}function $A(i){for(const e of Object.values(i.slots||{}))e.color&&!e.rgba&&(e.rgba=e.color,delete e.color),e.twoColor&&!e.rgba2&&(e.rgba2=e.twoColor,delete e.twoColor),dg(e.rgba,!1),dg(e.rgba2,!0)}function GA(i){for(const e of Object.values(i.bones||{})){for(const t of e.rotate||[])ki(t,"value","angle",0);Wn(e.rotate,[{key:"value",fallback:0}]),Wn(e.translate,[{key:"x",fallback:0},{key:"y",fallback:0}]),Wn(e.scale,[{key:"x",fallback:1},{key:"y",fallback:1}]),Wn(e.shear,[{key:"x",fa Line truncated `;class ox extends Bn{constructor(e){super(e),this.shaderGenerator=new hv(DA,NA),this.geometryClass=LA,this.vertexSize=7}packInterleavedGeometry(e,t,s,n,r){const{uint32View:o,float32View:a}=t,h=n/this.vertexSize,l=e.uvs,c=e.indices,u=e.vertexData,f=e._texture.baseTexture._batchLocation,d=Math.min(e.worldAlpha,1),m=At.shared.setValue(e._tintRGB).toPremultiplied(d,!0),g=At.shared.setValue(e._darkTintRGB).premultiply(d,!0).toPremultiplied(1,!1);for(let b=0;b<u.length;b+=2)a[n++]=u[b],a[n++]=u[b+1],a[n++]=l[b],a[n++]=l[b+1],o[n++]=m,o[n++]=g,a[n++]=f;for(let b=0;b<c.length;b++)s[r++]=h+c[b]}}A(ox,"extension",{name:"darkTintBatch",type:Oe.RendererPlugin});He.add(ox);class UA{constructor(){A(this,"container",new gi);A(this,"entries",new Map)}sync(e,t,s){var n;for(const[r,o]of this.entries)e.some(a=>a.id===r)||(this.container.removeChild(o.root),o.root.destroy({children:!0}),this.entries.delete(r));for(const r of e){const o=EE(r.id);let a=this.entries.get(r.id);if(a&&a.assetVersion!==r.assetVersion&&!o&&(this.container.removeChild(a.root),a.root.destroy({children:!0}),this.entries.delete(r.id),a=void 0),(!a||a.assetVersion!==r.assetVersion)&&o){a&&(this.container.removeChild(a.root),a.root.destroy({children:!0}));const f=new gi,d=new Rd({skeletonData:o.skeletonData,autoUpdate:!1,darkTint:!1});o.skeletonData.defaultSkin&&d.skeleton.setSkin(o.skeletonData.defaultSkin),d.skeleton.setSlotsToSetupPose(),f.addChild(d),this.container.addChild(f),a={root:f,spine:d,assetVersion:r.assetVersion,animation:""},this.entries.set(r.id,a)}if(!a)continue;const h=a.root,l=a.spine,c=r.visible!==!1&&r.enabled;h.visible=c,h.renderable=c,h.position.set(r.x,-r.y),h.rotation=-r.rotation*Math.PI/180,h.scale.set(r.scaleX,r.scaleY),a.animation!==r.selectedAnimation&&(a.animation=r.selectedAnimation,l.skeleton.setToSetupPose(),l.state.clearTracks(),a.animation&&l.state.setAnimation(0,a.animation,!1));const u=l.state.getCurrent(0);if(u){const f=((n=r.animations.find(m=>m.name===r.selectedAnimation))==null?void 0:n.duration)||0,d=Math.max(0,t/Math.max(1,s)-r.timeOffset);u.trackTime=f>0?Math.min(d,f):d}l.update(0)}}getBoneWorldTransform(e,t){const s=this.entries.get(e),n=s==null?void 0:s.spine.skeleton.findBone(t);if(!s||!n||!n.active)return null;const r=s.root,o=Math.cos(r.rotation),a=Math.sin(r.rotation),h=o*r.scale.x,l=-a*r.scale.y,c=a*r.scale.x,u=o*r.scale.y,f=h*n.a+l*n.c,d=h*n.b+l*n.d,m=c*n.a+u*n.c,g=c*n.b+u*n.d,b=r.position.x+h*n.worldX+l*n.worldY,_=r.position.y+c*n.worldX+u*n.worldY;return{x:b,y:-_,rotation:-Math.atan2(m,f)*180/Math.PI,scaleX:Math.max(1e-4,Math.hypot(f,m)),scaleY:Math.max(1e-4,Math.hypot(d,g))}}destroy(){for(const e of this.entries.values())e.root.removeFromParent(),e.root.destroy({children:!0});this.entries.clear(),this.container.removeFromParent(),this.container.destroy({children:!0})}}function Yn(i,e,t){const s=Number(i[e]);return Number.isFinite(s)?s:t}function ki(i,e,t,s){i[e]==null&&(i[e]=Yn(i,t,s))}function pf(i,e,t){if(typeof i.curve!="number")return;const s=Yn(i,"time",0),r=Yn(e,"time",0)-s,o=i.curve,a=Yn(i,"c2",0),h=Yn(i,"c3",1),l=Yn(i,"c4",1);i.curve=t.flatMap(([c,u])=>[s+r*o,c+(u-c)*a,s+r*h,c+(u-c)*l]),delete i.c2,delete i.c3,delete i.c4}function Wn(i,e){if(Array.isArray(i))for(let t=0;t<i.length-1;t++){const s=i[t],n=i[t+1];pf(s,n,e.map(({key:r,fallback:o})=>[Yn(s,r,o),Yn(n,r,o)]))}}function VA(i){if(Array.isArray(i))for(let e=0;e<i.length-1;e++)pf(i[e],i[e+1],[[0,1]])}function Yr(i,e){const t=typeof i=="string"?i.replace(/^#/,""):"",s=t.length===6?`${t}ff`:t.padEnd(8,"f").slice(0,8),n=[0,2,4,6].map(r=>Number.parseInt(s.slice(r,r+2),16)/255);return e?n:n.slice(0,3)}function dg(i,e){if(Array.isArray(i))for(let t=0;t<i.length-1;t++){const s=i[t],n=i[t+1],r=e?[...Yr(s.light,!0),...Yr(s.dark,!1)]:Yr(s.color,!0),o=e?[...Yr(n.light,!0),...Yr(n.dark,!1)]:Yr(n.color,!0);pf(s,n,r.map((a,h)=>[a,o[h]]))}}function XA(i){for(const e of i.bones||[])e.inherit==null&&e.transform!=null&&(e.inherit=e.transform);for(const e of i.transform||[])ki(e,"mixRotate","rotateMix",1),ki(e,"mixX","translateMix",1),e.mixY==null&&(e.mixY=e.mixX),ki(e,"mixScaleX","scaleMix",1),e.mixScaleY==null&&(e.mixScaleY=e.mixScaleX),ki(e,"mixShearY","shearMix",1);for(const e of i.path||[])ki(e,"mixRotate","rotateMix",1),ki(e,"mixX","translateMix",1),e.mixY==null&&(e.mixY=e.mixX)}function YA(i){for(const e of i.skins||[])for(const t of Object.values(e.attachments||{}))for(const s of Object.values(t||{}))s.parent!=null&&s.timelines==null&&s.deform!=null&&(s.timelines=s.deform)}function $A(i){for(const e of Object.values(i.slots||{}))e.color&&!e.rgba&&(e.rgba=e.color,delete e.color),e.twoColor&&!e.rgba2&&(e.rgba2=e.twoColor,delete e.twoColor),dg(e.rgba,!1),dg(e.rgba2,!0)}function GA(i){for(const e of Object.values(i.bones||{})){for(const t of e.rotate||[])ki(t,"value","angle",0);Wn(e.rotate,[{key:"value",fallback:0}]),Wn(e.translate,[{key:"x",fallback:0},{key:"y",fallback:0}]),Wn(e.scale,[{key:"x",fallback:1},{key:"y",fallback:1}]),Wn(e.shear,[{key:"x",fa Line truncated
+2 -2
View File
@@ -8,8 +8,8 @@
html, body { margin: 0; padding: 0; background: #111; color: #eee; font-family: system-ui, -apple-system, sans-serif; } html, body { margin: 0; padding: 0; background: #111; color: #eee; font-family: system-ui, -apple-system, sans-serif; }
#app { display: flex; flex-direction: column; height: 100vh; } #app { display: flex; flex-direction: column; height: 100vh; }
</style> </style>
<script type="module" crossorigin src="/assets/index-DujVfZi9.js"></script> <script type="module" crossorigin src="/assets/index-CnGkEkb-.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-nUMfzhFe.css"> <link rel="stylesheet" crossorigin href="/assets/index-CSwdbBmn.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
+1 -1
View File
@@ -132,7 +132,7 @@ export const useParticleStore = defineStore('particle', {
// 与 Spine 编辑器默认时间基准对齐:1 秒 = 30 帧。 // 与 Spine 编辑器默认时间基准对齐:1 秒 = 30 帧。
timeline: { playing: true, loop: true, frame: 0, totalFrames: 60, fps: 30, recorded: false, animation: 'animation', animations: ['animation'] } as TimelineState, timeline: { playing: true, loop: true, frame: 0, totalFrames: 60, fps: 30, recorded: false, animation: 'animation', animations: ['animation'] } as TimelineState,
settings: { settings: {
parameterPanelOnRight: false, parameterPanelOnRight: true,
tickEnabled: false, tickEnabled: false,
axisFontSize: 12, axisFontSize: 12,
tickWidth: 1, tickWidth: 1,
+1 -1
View File
@@ -1,7 +1,7 @@
<template> <template>
<div class="panel"> <div class="panel">
<div class="panel-header"> <div class="panel-header">
<span>Spine粒子编辑器</span> <span>Spine粒子编辑器-Yeomai</span>
</div> </div>
<div class="panel-scroll"> <div class="panel-scroll">
+1 -1
View File
@@ -209,7 +209,7 @@ const canvasFocused = ref(false)
function toggleTool(t: '' | 'translate' | 'rotate' | 'scale') { activeTool.value = activeTool.value === t ? '' : t } function toggleTool(t: '' | 'translate' | 'rotate' | 'scale') { activeTool.value = activeTool.value === t ? '' : t }
if (!(store.settings.undoLimit > 0)) store.settings.undoLimit = 20 if (!(store.settings.undoLimit > 0)) store.settings.undoLimit = 20
if (store.settings.parameterPanelOnRight == null) store.settings.parameterPanelOnRight = false if (store.settings.parameterPanelOnRight == null) store.settings.parameterPanelOnRight = true
if (!Number.isFinite(store.settings.backgroundX)) store.settings.backgroundX = 0 if (!Number.isFinite(store.settings.backgroundX)) store.settings.backgroundX = 0
if (!Number.isFinite(store.settings.backgroundY)) store.settings.backgroundY = 0 if (!Number.isFinite(store.settings.backgroundY)) store.settings.backgroundY = 0
if (!Number.isFinite(store.settings.backgroundOpacity)) store.settings.backgroundOpacity = 1 if (!Number.isFinite(store.settings.backgroundOpacity)) store.settings.backgroundOpacity = 1
+43
View File
@@ -0,0 +1,43 @@
@echo off
chcp 65001 >nul
setlocal
set "PROJECT_DIR=%~dp0"
set "RUN_DIR=%PROJECT_DIR%.run"
set "PID_FILE=%RUN_DIR%\spine-particle-windows.pid"
cd /d "%PROJECT_DIR%"
if not exist "%PID_FILE%" (
echo 没有发现由“启动项目-Windows.bat”启动的项目进程。
exit /b 0
)
set /p SERVER_PID=<"%PID_FILE%"
powershell -NoProfile -Command ^
"$process=Get-CimInstance Win32_Process -Filter ('ProcessId = ' + $env:SERVER_PID) -ErrorAction SilentlyContinue;" ^
"if ($null -eq $process) { exit 2 };" ^
"$expected=(Join-Path $env:PROJECT_DIR 'node_modules\vite\bin\vite.js');" ^
"if ($process.CommandLine -notlike ('*' + $expected + '*')) { exit 3 };" ^
"Stop-Process -Id ([int]$env:SERVER_PID) -Force; exit 0"
set "STOP_RESULT=%ERRORLEVEL%"
if "%STOP_RESULT%"=="3" (
echo PID 已被其他程序占用,为避免误关进程,本次未执行停止操作。
del /q "%PID_FILE%" >nul 2>&1
pause
exit /b 1
)
del /q "%PID_FILE%" >nul 2>&1
if "%STOP_RESULT%"=="2" (
echo 项目已经停止。
) else if "%STOP_RESULT%"=="0" (
echo 项目已停止。
) else (
echo 停止项目失败,请检查 PowerShell 权限。
pause
exit /b 1
)
exit /b 0
+46
View File
@@ -0,0 +1,46 @@
#!/bin/zsh
set -u
PROJECT_DIR="${0:A:h}"
RUN_DIR="$PROJECT_DIR/.run"
PID_FILE="$RUN_DIR/spine-particle.pid"
cd "$PROJECT_DIR" || exit 1
if [[ ! -f "$PID_FILE" ]]; then
echo "没有发现由“启动项目.command”启动的项目进程。"
exit 0
fi
SERVER_PID="$(tr -dc '0-9' < "$PID_FILE")"
if [[ -z "$SERVER_PID" ]] || ! kill -0 "$SERVER_PID" 2>/dev/null; then
rm -f "$PID_FILE"
echo "项目已经停止。"
exit 0
fi
SERVER_COMMAND="$(ps -p "$SERVER_PID" -o command= 2>/dev/null || true)"
if [[ "$SERVER_COMMAND" != *"$PROJECT_DIR/node_modules/vite/"* ]]; then
echo "PID 已被其他程序占用,为避免误关进程,本次未执行停止操作。"
echo "当前进程:$SERVER_COMMAND"
rm -f "$PID_FILE"
read "?按回车键关闭窗口..."
exit 1
fi
echo "正在停止 Spine粒子编辑器..."
kill "$SERVER_PID" 2>/dev/null || true
for _ in {1..20}; do
if ! kill -0 "$SERVER_PID" 2>/dev/null; then
rm -f "$PID_FILE"
echo "项目已停止。"
exit 0
fi
sleep 0.1
done
echo "普通停止未响应,正在强制结束该项目进程..."
kill -KILL "$SERVER_PID" 2>/dev/null || true
rm -f "$PID_FILE"
echo "项目已停止。"
+71
View File
@@ -0,0 +1,71 @@
@echo off
chcp 65001 >nul
setlocal
set "PROJECT_DIR=%~dp0"
set "RUN_DIR=%PROJECT_DIR%.run"
set "PID_FILE=%RUN_DIR%\spine-particle-windows.pid"
set "OUT_LOG=%RUN_DIR%\spine-particle-windows.log"
set "ERROR_LOG=%RUN_DIR%\spine-particle-windows-error.log"
set "PROJECT_URL=http://127.0.0.1:5173/"
if not exist "%RUN_DIR%" mkdir "%RUN_DIR%"
cd /d "%PROJECT_DIR%"
if exist "%PID_FILE%" (
set /p SERVER_PID=<"%PID_FILE%"
powershell -NoProfile -Command "if (Get-Process -Id $env:SERVER_PID -ErrorAction SilentlyContinue) { exit 0 } else { exit 1 }"
if not errorlevel 1 (
echo Spine粒子编辑器已经在运行,正在打开页面:
echo %PROJECT_URL%
start "" "%PROJECT_URL%"
exit /b 0
)
del /q "%PID_FILE%" >nul 2>&1
)
where npm >nul 2>&1
if errorlevel 1 (
echo 未找到 npm,请先安装 Node.js:
echo https://nodejs.org/
pause
exit /b 1
)
if not exist "%PROJECT_DIR%node_modules\vite\bin\vite.js" (
echo 首次运行,正在安装项目依赖...
call npm install
if errorlevel 1 (
echo 依赖安装失败,请检查网络或 npm 配置。
pause
exit /b 1
)
)
echo 正在启动 Spine粒子编辑器...
powershell -NoProfile -Command ^
"$node=(Get-Command node).Source; $vite=Join-Path $env:PROJECT_DIR 'node_modules\vite\bin\vite.js';" ^
"$arguments=@($vite,'--host','127.0.0.1','--port','5173','--strictPort');" ^
"$process=Start-Process -FilePath $node -ArgumentList $arguments -WorkingDirectory $env:PROJECT_DIR -RedirectStandardOutput $env:OUT_LOG -RedirectStandardError $env:ERROR_LOG -WindowStyle Hidden -PassThru;" ^
"Set-Content -Path $env:PID_FILE -Value $process.Id -Encoding ascii"
if errorlevel 1 (
echo 项目启动失败。
pause
exit /b 1
)
set /p SERVER_PID=<"%PID_FILE%"
timeout /t 2 /nobreak >nul
powershell -NoProfile -Command "if (Get-Process -Id $env:SERVER_PID -ErrorAction SilentlyContinue) { exit 0 } else { exit 1 }"
if errorlevel 1 (
echo 项目启动失败,错误日志如下:
if exist "%ERROR_LOG%" type "%ERROR_LOG%"
del /q "%PID_FILE%" >nul 2>&1
pause
exit /b 1
)
echo 启动成功:%PROJECT_URL%
start "" "%PROJECT_URL%"
exit /b 0
+64
View File
@@ -0,0 +1,64 @@
#!/bin/zsh
set -u
PROJECT_DIR="${0:A:h}"
RUN_DIR="$PROJECT_DIR/.run"
PID_FILE="$RUN_DIR/spine-particle.pid"
LOG_FILE="$RUN_DIR/spine-particle.log"
URL="http://127.0.0.1:5173/"
mkdir -p "$RUN_DIR"
cd "$PROJECT_DIR" || exit 1
if [[ -f "$PID_FILE" ]]; then
RUNNING_PID="$(tr -dc '0-9' < "$PID_FILE")"
if [[ -n "$RUNNING_PID" ]] && kill -0 "$RUNNING_PID" 2>/dev/null; then
echo "Spine粒子编辑器已经在运行,正在打开页面:"
echo "$URL"
open "$URL"
exit 0
fi
rm -f "$PID_FILE"
fi
if ! command -v npm >/dev/null 2>&1; then
echo "未找到 npm,请先安装 Node.js。"
echo "https://nodejs.org/"
read "?按回车键关闭窗口..."
exit 1
fi
if [[ ! -x "$PROJECT_DIR/node_modules/.bin/vite" ]]; then
echo "首次运行,正在安装项目依赖..."
npm install || {
echo "依赖安装失败,请检查网络或 npm 配置。"
read "?按回车键关闭窗口..."
exit 1
}
fi
echo "正在启动 Spine粒子编辑器..."
nohup "$PROJECT_DIR/node_modules/.bin/vite" --host 127.0.0.1 --port 5173 --strictPort > "$LOG_FILE" 2>&1 &
SERVER_PID=$!
echo "$SERVER_PID" > "$PID_FILE"
for _ in {1..60}; do
if ! kill -0 "$SERVER_PID" 2>/dev/null; then
echo "项目启动失败,日志如下:"
tail -n 30 "$LOG_FILE"
rm -f "$PID_FILE"
read "?按回车键关闭窗口..."
exit 1
fi
if curl -fsS "$URL" >/dev/null 2>&1; then
echo "启动成功:$URL"
open "$URL"
exit 0
fi
sleep 0.25
done
echo "项目启动超时,请查看日志:$LOG_FILE"
read "?按回车键关闭窗口..."
exit 1