feat: 增加拟人鼠标轨迹选择,Token 允许留空 (closes #12)

This commit is contained in:
foxhui
2026-01-24 03:24:49 +08:00
Unverified
parent f7bcddc91b
commit a85b731ce1
43 changed files with 210 additions and 63 deletions
-1
View File
@@ -1 +0,0 @@
import{c as i,I as u}from"./index-BgSomuu7.js";var l={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M928 140H96c-17.7 0-32 14.3-32 32v496c0 17.7 14.3 32 32 32h380v112H304c-8.8 0-16 7.2-16 16v48c0 4.4 3.6 8 8 8h432c4.4 0 8-3.6 8-8v-48c0-8.8-7.2-16-16-16H548V700h380c17.7 0 32-14.3 32-32V172c0-17.7-14.3-32-32-32zm-40 488H136V212h752v416z"}}]},name:"desktop",theme:"outlined"};function c(r){for(var t=1;t<arguments.length;t++){var e=arguments[t]!=null?Object(arguments[t]):{},n=Object.keys(e);typeof Object.getOwnPropertySymbols=="function"&&(n=n.concat(Object.getOwnPropertySymbols(e).filter(function(a){return Object.getOwnPropertyDescriptor(e,a).enumerable}))),n.forEach(function(a){s(r,a,e[a])})}return r}function s(r,t,e){return t in r?Object.defineProperty(r,t,{value:e,enumerable:!0,configurable:!0,writable:!0}):r[t]=e,r}var o=function(t,e){var n=c({},t,e.attrs);return i(u,c({},n,{icon:l}),null)};o.displayName="DesktopOutlined";o.inheritAttrs=!1;export{o as D};
+1
View File
@@ -0,0 +1 @@
import{c as i,I as u}from"./index.js";var l={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M928 140H96c-17.7 0-32 14.3-32 32v496c0 17.7 14.3 32 32 32h380v112H304c-8.8 0-16 7.2-16 16v48c0 4.4 3.6 8 8 8h432c4.4 0 8-3.6 8-8v-48c0-8.8-7.2-16-16-16H548V700h380c17.7 0 32-14.3 32-32V172c0-17.7-14.3-32-32-32zm-40 488H136V212h752v416z"}}]},name:"desktop",theme:"outlined"};function c(r){for(var t=1;t<arguments.length;t++){var e=arguments[t]!=null?Object(arguments[t]):{},n=Object.keys(e);typeof Object.getOwnPropertySymbols=="function"&&(n=n.concat(Object.getOwnPropertySymbols(e).filter(function(a){return Object.getOwnPropertyDescriptor(e,a).enumerable}))),n.forEach(function(a){s(r,a,e[a])})}return r}function s(r,t,e){return t in r?Object.defineProperty(r,t,{value:e,enumerable:!0,configurable:!0,writable:!0}):r[t]=e,r}var o=function(t,e){var n=c({},t,e.attrs);return i(u,c({},n,{icon:l}),null)};o.displayName="DesktopOutlined";o.inheritAttrs=!1;export{o as D};
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
View File
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
import{_ as b,k as w,l as k,o as c,b as C,d as S,w as n,c as o,g as e,f as a,h as i}from"./index-BgSomuu7.js";const B={style:{"margin-bottom":"8px"}},T={style:{"margin-bottom":"8px"}},z={style:{"margin-bottom":"8px"}},U={style:{display:"flex","justify-content":"flex-end","margin-top":"24px"}},M={style:{"margin-bottom":"8px"}},j={style:{"margin-bottom":"8px"}},q={style:{display:"flex","justify-content":"flex-end","margin-top":"24px"}},L={__name:"server",setup(N){const d=w(),s=k({port:5173,authToken:"",keepaliveMode:"comment",queueBuffer:2,imageLimit:5});c(async()=>{await d.fetchServerConfig(),Object.assign(s,d.serverConfig)});const m=async()=>{await d.saveServerConfig(s)};return(V,t)=>{const p=a("a-input-number"),r=a("a-col"),y=a("a-input-password"),u=a("a-select-option"),g=a("a-select"),f=a("a-row"),v=a("a-button"),x=a("a-card"),_=a("a-layout");return S(),C(_,{style:{background:"transparent"}},{default:n(()=>[o(x,{title:"服务器设置",bordered:!1,style:{width:"100%"}},{default:n(()=>[o(f,{gutter:[16,16]},{default:n(()=>[o(r,{xs:24,md:12},{default:n(()=>[e("div",B,[t[5]||(t[5]=e("div",{style:{"font-weight":"600","margin-bottom":"4px"}},"监听端口",-1)),t[6]||(t[6]=e("div",{style:{"font-size":"12px",color:"#8c8c8c","margin-bottom":"8px"}}," 设置服务器监听的端口号,默认为 5173 ",-1)),o(p,{value:s.port,"onUpdate:value":t[0]||(t[0]=l=>s.port=l),min:1,max:65535,placeholder:"请输入端口号",style:{width:"100%"}},null,8,["value"])])]),_:1}),o(r,{xs:24,md:12},{default:n(()=>[e("div",T,[t[7]||(t[7]=e("div",{style:{"font-weight":"600","margin-bottom":"4px"}},"鉴权 Token",-1)),t[8]||(t[8]=e("div",{style:{"font-size":"12px",color:"#8c8c8c","margin-bottom":"8px"}}," 用于 API 请求鉴权的密钥,留空则不启用鉴权 ",-1)),o(y,{value:s.authToken,"onUpdate:value":t[1]||(t[1]=l=>s.authToken=l),placeholder:"请输入 Token",type:"password"},null,8,["value"])])]),_:1}),o(r,{xs:24,md:12},{default:n(()=>[e("div",z,[t[11]||(t[11]=e("div",{style:{"font-weight":"600","margin-bottom":"4px"}},"心跳包类型",-1)),t[12]||(t[12]=e("div",{style:{"font-size":"12px",color:"#8c8c8c","margin-bottom":"8px"}}," 选择 SSE 流式响应的心跳包格式 ",-1)),o(g,{value:s.keepaliveMode,"onUpdate:value":t[2]||(t[2]=l=>s.keepaliveMode=l),style:{width:"100%"},placeholder:"请选择心跳包类型"},{default:n(()=>[o(u,{value:"comment"},{default:n(()=>[...t[9]||(t[9]=[i("Comment - 注释格式",-1)])]),_:1}),o(u,{value:"content"},{default:n(()=>[...t[10]||(t[10]=[i("Content - 内容格式",-1)])]),_:1})]),_:1},8,["value"])])]),_:1})]),_:1}),e("div",U,[o(v,{type:"primary",onClick:m},{default:n(()=>[...t[13]||(t[13]=[i(" 保存设置 ",-1)])]),_:1})])]),_:1}),o(x,{title:"队列设置",bordered:!1,style:{width:"100%","margin-top":"10px"}},{default:n(()=>[o(f,{gutter:[16,16]},{default:n(()=>[o(r,{xs:24,md:12},{default:n(()=>[e("div",M,[t[14]||(t[14]=e("div",{style:{"font-weight":"600","margin-bottom":"4px"}},"队列缓冲区大小",-1)),t[15]||(t[15]=e("div",{style:{"font-size":"12px",color:"#8c8c8c","margin-bottom":"8px"}},[i(" 非流式请求的额外排队数(设为 0 则不限制非流式请求数量)"),e("br"),i(" 实际队列上限 = Workers数量 + 缓冲区大小 ")],-1)),o(p,{value:s.queueBuffer,"onUpdate:value":t[3]||(t[3]=l=>s.queueBuffer=l),min:0,max:100,placeholder:"默认为 2",style:{width:"100%"}},null,8,["value"])])]),_:1}),o(r,{xs:24,md:12},{default:n(()=>[e("div",j,[t[16]||(t[16]=e("div",{style:{"font-weight":"600","margin-bottom":"4px"}},"图片数量上限",-1)),t[17]||(t[17]=e("div",{style:{"font-size":"12px",color:"#8c8c8c","margin-bottom":"8px"}},[i(" 单次请求最多支持的图片附件数量"),e("br"),i(" 网页最多支持10个附件,超出会被丢弃 ")],-1)),o(p,{value:s.imageLimit,"onUpdate:value":t[4]||(t[4]=l=>s.imageLimit=l),min:1,max:10,placeholder:"默认为 5",style:{width:"100%"}},null,8,["value"])])]),_:1})]),_:1}),e("div",q,[o(v,{type:"primary",onClick:m},{default:n(()=>[...t[18]||(t[18]=[i(" 保存设置 ",-1)])]),_:1})])]),_:1})]),_:1})}}},A=b(L,[["__scopeId","data-v-bd32923f"]]);export{A as default};
-1
View File
@@ -1 +0,0 @@
.ant-input-number[data-v-bd32923f]{width:100%}
+1
View File
@@ -0,0 +1 @@
.ant-input-number[data-v-5b571484]{width:100%}
+1
View File
@@ -0,0 +1 @@
import{_ as k,k as w,l as c,o as T,b as C,d as S,w as a,c as o,g as e,f as l,h as i,m as B,M}from"./index.js";const U={style:{"margin-bottom":"8px"}},z={style:{"margin-bottom":"8px"}},I={style:{"margin-bottom":"8px"}},j={style:{display:"flex","justify-content":"flex-end","margin-top":"24px"}},q={style:{"margin-bottom":"8px"}},L={style:{"margin-bottom":"8px"}},N={style:{display:"flex","justify-content":"flex-end","margin-top":"24px"}},V={__name:"server",setup(A){const d=w(),n=c({port:5173,authToken:"",keepaliveMode:"comment",queueBuffer:2,imageLimit:5});T(async()=>{await d.fetchServerConfig(),Object.assign(n,d.serverConfig)});const m=async()=>{await d.saveServerConfig(n)},u=async()=>{if(n.authToken&&n.authToken.length>0&&n.authToken.length<10){B.error("鉴权 Token 如果设置则必须至少 10 个字符,或留空");return}if(!n.authToken){M.confirm({title:"安全警告",content:"您正在将鉴权 Token 留空,这意味着 API 和 WebUI 将无需认证即可访问。请勿在公网环境中使用此配置!确定要继续吗?",okText:"确定留空",okType:"danger",cancelText:"取消",onOk:m});return}await m()};return(O,t)=>{const p=l("a-input-number"),r=l("a-col"),y=l("a-input-password"),f=l("a-select-option"),_=l("a-select"),v=l("a-row"),x=l("a-button"),g=l("a-card"),b=l("a-layout");return S(),C(b,{style:{background:"transparent"}},{default:a(()=>[o(g,{title:"服务器设置",bordered:!1,style:{width:"100%"}},{default:a(()=>[o(v,{gutter:[16,16]},{default:a(()=>[o(r,{xs:24,md:12},{default:a(()=>[e("div",U,[t[5]||(t[5]=e("div",{style:{"font-weight":"600","margin-bottom":"4px"}},"监听端口",-1)),t[6]||(t[6]=e("div",{style:{"font-size":"12px",color:"#8c8c8c","margin-bottom":"8px"}}," 设置服务器监听的端口号,默认为 5173 ",-1)),o(p,{value:n.port,"onUpdate:value":t[0]||(t[0]=s=>n.port=s),min:1,max:65535,placeholder:"请输入端口号",style:{width:"100%"}},null,8,["value"])])]),_:1}),o(r,{xs:24,md:12},{default:a(()=>[e("div",z,[t[7]||(t[7]=e("div",{style:{"font-weight":"600","margin-bottom":"4px"}},"鉴权 Token",-1)),t[8]||(t[8]=e("div",{style:{"font-size":"12px",color:"#8c8c8c","margin-bottom":"8px"}}," 用于 API 请求鉴权的密钥,留空则不启用鉴权 ",-1)),o(y,{value:n.authToken,"onUpdate:value":t[1]||(t[1]=s=>n.authToken=s),placeholder:"请输入 Token",type:"password"},null,8,["value"])])]),_:1}),o(r,{xs:24,md:12},{default:a(()=>[e("div",I,[t[11]||(t[11]=e("div",{style:{"font-weight":"600","margin-bottom":"4px"}},"心跳包类型",-1)),t[12]||(t[12]=e("div",{style:{"font-size":"12px",color:"#8c8c8c","margin-bottom":"8px"}}," 选择 SSE 流式响应的心跳包格式 ",-1)),o(_,{value:n.keepaliveMode,"onUpdate:value":t[2]||(t[2]=s=>n.keepaliveMode=s),style:{width:"100%"},placeholder:"请选择心跳包类型"},{default:a(()=>[o(f,{value:"comment"},{default:a(()=>[...t[9]||(t[9]=[i("Comment - 注释格式",-1)])]),_:1}),o(f,{value:"content"},{default:a(()=>[...t[10]||(t[10]=[i("Content - 内容格式",-1)])]),_:1})]),_:1},8,["value"])])]),_:1})]),_:1}),e("div",j,[o(x,{type:"primary",onClick:u},{default:a(()=>[...t[13]||(t[13]=[i(" 保存设置 ",-1)])]),_:1})])]),_:1}),o(g,{title:"队列设置",bordered:!1,style:{width:"100%","margin-top":"10px"}},{default:a(()=>[o(v,{gutter:[16,16]},{default:a(()=>[o(r,{xs:24,md:12},{default:a(()=>[e("div",q,[t[14]||(t[14]=e("div",{style:{"font-weight":"600","margin-bottom":"4px"}},"队列缓冲区大小",-1)),t[15]||(t[15]=e("div",{style:{"font-size":"12px",color:"#8c8c8c","margin-bottom":"8px"}},[i(" 非流式请求的额外排队数(设为 0 则不限制非流式请求数量)"),e("br"),i(" 实际队列上限 = Workers数量 + 缓冲区大小 ")],-1)),o(p,{value:n.queueBuffer,"onUpdate:value":t[3]||(t[3]=s=>n.queueBuffer=s),min:0,max:100,placeholder:"默认为 2",style:{width:"100%"}},null,8,["value"])])]),_:1}),o(r,{xs:24,md:12},{default:a(()=>[e("div",L,[t[16]||(t[16]=e("div",{style:{"font-weight":"600","margin-bottom":"4px"}},"图片数量上限",-1)),t[17]||(t[17]=e("div",{style:{"font-size":"12px",color:"#8c8c8c","margin-bottom":"8px"}},[i(" 单次请求最多支持的图片附件数量"),e("br"),i(" 网页最多支持10个附件,超出会被丢弃 ")],-1)),o(p,{value:n.imageLimit,"onUpdate:value":t[4]||(t[4]=s=>n.imageLimit=s),min:1,max:10,placeholder:"默认为 5",style:{width:"100%"}},null,8,["value"])])]),_:1})]),_:1}),e("div",N,[o(x,{type:"primary",onClick:u},{default:a(()=>[...t[18]||(t[18]=[i(" 保存设置 ",-1)])]),_:1})])]),_:1})]),_:1})}}},W=k(V,[["__scopeId","data-v-5b571484"]]);export{W as default};
-1
View File
@@ -1 +0,0 @@
import{y as i,k as a,v as r}from"./index-BgSomuu7.js";const u=i("system",{state:()=>({status:"",version:"1.0.0",systemVersion:"",uptime:0,cpuUsage:0,memoryUsage:{total:0,used:0,free:0},safeMode:{enabled:!1,reason:null},stats:{totalRequests:0,successRate:0,activeWorkers:0,totalWorkers:0,avgResponseTime:0,success:0,failed:0}}),actions:{async fetchStatus(){const t=a();try{const e=await fetch("/admin/status",{headers:t.getHeaders()});if(e.ok){const s=await e.json();this.$patch(s)}}catch(e){console.error("Failed to fetch system status:",e)}},async fetchStats(){const t=a();try{const e=await fetch("/admin/stats",{headers:t.getHeaders()});if(e.ok){const s=await e.json();this.stats=s}}catch(e){console.error("Failed to fetch stats:",e)}},async restartService(t={}){const e=a(),{loginMode:s,workerName:n}=t;try{const o=await(await fetch("/admin/restart",{method:"POST",headers:{...e.getHeaders(),"Content-Type":"application/json"},body:JSON.stringify({loginMode:s,workerName:n})})).json();return o.success?(r.success(o.message||"服务重启中..."),!0):(r.error("重启失败"),!1)}catch{return r.error("重启请求失败"),!1}},async stopService(){const t=a();try{const s=await(await fetch("/admin/stop",{method:"POST",headers:t.getHeaders()})).json();return s.success?(r.success(s.message||"服务停止中..."),!0):(r.error("停止失败"),!1)}catch{return r.error("停止请求失败"),!1}}}});export{u};
+1
View File
@@ -0,0 +1 @@
import{y as i,k as a,m as r}from"./index.js";const u=i("system",{state:()=>({status:"",version:"1.0.0",systemVersion:"",uptime:0,cpuUsage:0,memoryUsage:{total:0,used:0,free:0},safeMode:{enabled:!1,reason:null},stats:{totalRequests:0,successRate:0,activeWorkers:0,totalWorkers:0,avgResponseTime:0,success:0,failed:0}}),actions:{async fetchStatus(){const t=a();try{const e=await fetch("/admin/status",{headers:t.getHeaders()});if(e.ok){const s=await e.json();this.$patch(s)}}catch(e){console.error("Failed to fetch system status:",e)}},async fetchStats(){const t=a();try{const e=await fetch("/admin/stats",{headers:t.getHeaders()});if(e.ok){const s=await e.json();this.stats=s}}catch(e){console.error("Failed to fetch stats:",e)}},async restartService(t={}){const e=a(),{loginMode:s,workerName:n}=t;try{const o=await(await fetch("/admin/restart",{method:"POST",headers:{...e.getHeaders(),"Content-Type":"application/json"},body:JSON.stringify({loginMode:s,workerName:n})})).json();return o.success?(r.success(o.message||"服务重启中..."),!0):(r.error("重启失败"),!1)}catch{return r.error("重启请求失败"),!1}},async stopService(){const t=a();try{const s=await(await fetch("/admin/stop",{method:"POST",headers:t.getHeaders()})).json();return s.success?(r.success(s.message||"服务停止中..."),!0):(r.error("停止失败"),!1)}catch{return r.error("停止请求失败"),!1}}}});export{u};
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -6,8 +6,8 @@
<link rel="icon" type="image/png" href="/favicon.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WebAI2API</title>
<script type="module" crossorigin src="/assets/index-BgSomuu7.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BVr8U7Bl.css">
<script type="module" crossorigin src="/assets/index.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index.css">
</head>
<body>
+31 -7
View File
@@ -9,6 +9,7 @@ const formData = reactive({
path: '',
headless: false,
fission: true,
humanizeCursor: false, // false | true | 'camou'
// CSS 性能优化
cssAnimation: false,
cssFilter: false,
@@ -29,6 +30,8 @@ onMounted(async () => {
formData.path = cfg.path || '';
formData.headless = cfg.headless || false;
formData.fission = cfg.fission !== false; // 默认 true
// humanizeCursor: false=禁用, true=ghost-cursor, 'camou'=Camoufox内置
formData.humanizeCursor = cfg.humanizeCursor ?? false;
// CSS 性能优化
if (cfg.cssInject) {
@@ -59,6 +62,7 @@ const handleSave = async () => {
font: formData.cssFont
},
fission: formData.fission,
humanizeCursor: formData.humanizeCursor,
proxy: {
enable: formData.proxyEnable,
type: formData.proxyType,
@@ -120,6 +124,27 @@ const handleSave = async () => {
</span>
</div>
</a-col>
<!-- 拟人鼠标轨迹 -->
<a-col :xs="24" :md="24">
<div style="margin-bottom: 8px;">
<div style="font-weight: 600; margin-bottom: 4px;">拟人鼠标轨迹模式</div>
<div style="font-size: 12px; color: #8c8c8c; margin-bottom: 8px;">
控制鼠标点击的拟人化程度影响性能和反爬检测风险
</div>
<a-segmented v-model:value="formData.humanizeCursor" block :options="[
{ label: '禁用 (性能最佳)', value: false },
{ label: 'Ghost-Cursor (更拟人)', value: true },
{ label: 'Camoufox内置 (平衡)', value: 'camou' }
]" />
<div style="font-size: 11px; color: #8c8c8c; margin-top: 6px;">
<span v-if="formData.humanizeCursor === false">使用 Playwright 原生点击性能最好但可能被检测为自动化</span>
<span v-else-if="formData.humanizeCursor === true">使用项目优化的 ghost-cursor
模拟人类鼠标轨迹如不会点击正中心性能稍差</span>
<span v-else>使用 Camoufox 内置的 humanize 功能性能与拟人化的平衡选择</span>
</div>
</div>
</a-col>
</a-row>
<!-- 全局代理设置折叠面板 -->
@@ -196,12 +221,8 @@ const handleSave = async () => {
<!-- CSS 性能优化 -->
<a-collapse-panel key="cssInject" header="CSS 性能优化注入">
<a-alert
message="⚡ 适用于无 GPU 的服务器环境,通过禁用网页特效来降低 CPU 压力"
type="info"
show-icon
style="margin-bottom: 16px;"
/>
<a-alert message="⚡ 适用于无 GPU 的服务器环境,通过禁用网页特效来降低 CPU 压力" type="info" show-icon
style="margin-bottom: 16px;" />
<!-- 禁用动画 -->
<div style="margin-bottom: 16px; padding: 12px; background: #fafafa; border-radius: 6px;">
@@ -211,7 +232,10 @@ const handleSave = async () => {
<div style="font-size: 12px; color: #8c8c8c;">
移除 transition animation显著降低 CPU 持续占用
</div>
<a-tag color="green" style="margin-top: 6px;">风险</a-tag>
<a-tag color="green" style="margin-top: 6px;">风险</a-tag>
<span style="font-size: 11px; color: #389e0d; margin-left: 8px;">
几乎不影响浏览器指纹但可能导致部分网页布局异常
</span>
</div>
<a-switch v-model:checked="formData.cssAnimation" />
</div>
+28 -2
View File
@@ -1,6 +1,7 @@
<script setup>
import { onMounted, reactive } from 'vue';
import { useSettingsStore } from '@/stores/settings';
import { Modal, message } from 'ant-design-vue';
const settingsStore = useSettingsStore();
@@ -18,10 +19,35 @@ onMounted(async () => {
Object.assign(formData, settingsStore.serverConfig);
});
// 保存设置
const handleSave = async () => {
// 实际保存逻辑
const doSave = async () => {
await settingsStore.saveServerConfig(formData);
};
// 保存设置 (带校验和确认弹窗)
const handleSave = async () => {
// 前端校验:Token 长度在 1-9 之间时提示
if (formData.authToken && formData.authToken.length > 0 && formData.authToken.length < 10) {
message.error('鉴权 Token 如果设置则必须至少 10 个字符,或留空');
return;
}
// Token 留空时弹出确认框
if (!formData.authToken) {
Modal.confirm({
title: '安全警告',
content: '您正在将鉴权 Token 留空,这意味着 API 和 WebUI 将无需认证即可访问。请勿在公网环境中使用此配置!确定要继续吗?',
okText: '确定留空',
okType: 'danger',
cancelText: '取消',
onOk: doSave
});
return;
}
// 正常保存
await doSave();
};
</script>
<template>
+3 -1
View File
@@ -61,9 +61,11 @@ export const useSettingsStore = defineStore('settings', {
return { success: true, data };
} else {
console.error('Request failed:', res.status, data);
// 后端返回格式: { error: { message: "..." } } 或 { message: "..." }
const errorMessage = data.error?.message || data.message || `请求未成功: ${res.status} ${res.statusText}`;
Modal.error({
title: '保存失败',
content: data.message || `请求未成功: ${res.status} ${res.statusText}`,
content: errorMessage,
okText: '好的'
});
return { success: false, data };
+9
View File
@@ -4,6 +4,15 @@ import path from 'path'
export default defineConfig({
plugins: [vue()],
build: {
rollupOptions: {
output: {
entryFileNames: 'assets/[name].js',
chunkFileNames: 'assets/[name].js',
assetFileNames: 'assets/[name].[ext]'
}
}
},
resolve: {
alias: {
'@': path.resolve(__dirname, './src')