chavy.boxjs.html 124 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>BoxJs</title>
  5. <meta charset="utf-8" />
  6. <meta name="apple-mobile-web-app-capable" content="yes" />
  7. <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
  8. <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover" />
  9. <link rel="Bookmark" href="https://raw.githubusercontent.com/chavyleung/scripts/master/BOXJS.png" />
  10. <link rel="shortcut icon" href="https://raw.githubusercontent.com/chavyleung/scripts/master/BOXJS.png" />
  11. <link rel="apple-touch-icon" href="https://raw.githubusercontent.com/chavyleung/scripts/master/BOXJS.png" />
  12. <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet" />
  13. <link href="https://cdn.jsdelivr.net/npm/@mdi/[email protected]/css/materialdesignicons.min.css" rel="stylesheet" />
  14. <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.min.css" rel="stylesheet" />
  15. <script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
  16. <style>
  17. #BG {
  18. position: fixed;
  19. top: 0;
  20. left: 0;
  21. bottom: 0;
  22. right: 0;
  23. z-index: 0;
  24. background-position: center;
  25. background-size: cover;
  26. background-repeat: no-repeat;
  27. background-color: transparent;
  28. }
  29. @media (prefers-color-scheme: light) {
  30. body {
  31. background-color: #fff;
  32. }
  33. }
  34. @media (prefers-color-scheme: dark) {
  35. body {
  36. background-color: #121212;
  37. }
  38. }
  39. [v-cloak] {
  40. display: none;
  41. }
  42. .v-navigation-drawer {
  43. padding-top: constant(safe-area-inset-top) !important;
  44. padding-top: env(safe-area-inset-top) !important;
  45. }
  46. .v-bottom-sheet.v-dialog--fullscreen {
  47. padding-top: constant(safe-area-inset-top) !important;
  48. padding-top: env(safe-area-inset-top) !important;
  49. }
  50. .v-app-bar.safe {
  51. height: auto !important;
  52. padding-top: constant(safe-area-inset-top) !important;
  53. padding-top: env(safe-area-inset-top) !important;
  54. }
  55. .v-toolbar.safe {
  56. height: auto !important;
  57. padding-top: constant(safe-area-inset-top) !important;
  58. padding-top: env(safe-area-inset-top) !important;
  59. }
  60. .v-toolbar__content {
  61. padding-left: 12px !important;
  62. padding-right: 12px !important;
  63. }
  64. .v-main {
  65. margin-top: constant(safe-area-inset-top) !important;
  66. margin-top: env(safe-area-inset-top) !important;
  67. margin-bottom: constant(safe-area-inset-bottom) !important;
  68. margin-bottom: env(safe-area-inset-bottom) !important;
  69. }
  70. .v-main .container {
  71. height: 100%;
  72. }
  73. .v-bottom-navigation,
  74. .v-bottom-sheet {
  75. padding-bottom: constant(safe-area-inset-bottom);
  76. padding-bottom: env(safe-area-inset-bottom);
  77. }
  78. .v-bottom-navigation {
  79. box-sizing: content-box;
  80. }
  81. .v-bottom-navigation button {
  82. box-sizing: border-box;
  83. }
  84. .v-bottom-navigation button.v-btn:before {
  85. background-color: transparent;
  86. }
  87. .v-speed-dial {
  88. margin-bottom: calc(48px + constant(safe-area-inset-bottom));
  89. margin-bottom: calc(48px + env(safe-area-inset-bottom));
  90. }
  91. .container.container--fluid {
  92. padding-bottom: 68px;
  93. }
  94. .appicon {
  95. user-select: none;
  96. -webkit-user-select: none;
  97. }
  98. </style>
  99. </head>
  100. <body>
  101. <div id="BG"></div>
  102. <div id="app" v-cloak>
  103. <v-app v-if="box" :style="appViewStyle">
  104. <v-app-bar
  105. ref="appBar"
  106. v-bind="appBarBind"
  107. :class="!$refs.appBar || $refs.appBar.isActive ? 'safe' : undefined"
  108. :value="!isHidedSearchBar"
  109. v-touch="{ up: () => isHidedSearchBar = true }"
  110. >
  111. <!-- 搜索条 -->
  112. <v-autocomplete v-bind="ui.searchBar" :label="title" @click="ui.searchDialog.show = true" hide-no-data hide-details solo>
  113. <template #prepend-inner>
  114. <!-- 容器切换 Surge、QuanX、Loon -->
  115. <v-menu bottom left v-if="!isLoading && isMainView">
  116. <template #activator="{ on }">
  117. <v-btn v-on="on" icon class="ml-n3">
  118. <v-avatar size="26"><img :src="env.icons[iconEnvThemeIdx]" /></v-avatar>
  119. </v-btn>
  120. </template>
  121. <v-list>
  122. <v-list-item dense v-for="(env, envIdx) in envs" :key="env.id" @click="switchEnv(env.id)">
  123. <v-list-item-avatar size="26"><v-img :src="env.icon" /></v-list-item-avatar>
  124. <v-list-item-title>{{env.id}}</v-list-item-title>
  125. </v-list-item>
  126. </v-list>
  127. </v-menu>
  128. <!-- 返回按钮 -->
  129. <v-btn icon class="ml-n3" @click="back" v-else-if="!isLoading && !isMainView">
  130. <v-icon>mdi-chevron-left</v-icon>
  131. </v-btn>
  132. <v-btn icon class="ml-n3" v-show="isLoading" :loading="isLoading" color="primary"></v-btn>
  133. </template>
  134. <template #append>
  135. <v-btn icon class="mr-n3" @click="ui.naviDrawer.show = true">
  136. <v-avatar size="26"><v-icon>mdi-menu</v-icon></v-avatar>
  137. </v-btn>
  138. </template>
  139. </v-autocomplete>
  140. </v-app-bar>
  141. <v-dialog v-model="ui.searchDialog.show" fullscreen scrollable>
  142. <v-card class="align-self-start">
  143. <v-card-subtitle class="pa-0">
  144. <v-toolbar v-bind="searchBarBind" class="safe">
  145. <v-btn icon dark @click="ui.searchDialog.show = false">
  146. <v-icon>mdi-chevron-left</v-icon>
  147. </v-btn>
  148. <v-text-field ref="search" v-model="ui.searchBar.input" :label="title" autofocus hide-details solo></v-text-field>
  149. <v-btn icon @click="open(box.syscfgs.orz3.repo)">
  150. <v-avatar size="26"><img :src="box.syscfgs.orz3.icon" /></v-avatar>
  151. </v-btn>
  152. </v-toolbar>
  153. </v-card-subtitle>
  154. <v-card-text class="px-0">
  155. <v-list nav>
  156. <v-list-item
  157. v-for="(app, appIdx) in searchApps"
  158. :key="appIdx"
  159. @click="ui.searchDialog.show = false, switchAppView(app.id)"
  160. dense
  161. >
  162. <v-list-item-avatar class="elevation-3"><img :src="app.icon" /></v-list-item-avatar>
  163. <v-list-item-content>
  164. <v-list-item-title>{{`${app.name} (${app.id})`}}</v-list-item-title>
  165. <v-list-item-subtitle>{{app.repo}}</v-list-item-subtitle>
  166. <v-list-item-subtitle>{{app.author}}</v-list-item-subtitle>
  167. </v-list-item-content>
  168. <v-list-item-action>
  169. <v-btn icon @click.stop="ui.searchDialog.show = false, favApp(app.id)">
  170. <v-icon :color="app.favIconColor" v-text="app.favIcon" />
  171. </v-btn>
  172. </v-list-item-action>
  173. </v-list-item>
  174. </v-list>
  175. </v-card-text>
  176. </v-card>
  177. </v-dialog>
  178. <!-- 侧栏 -->
  179. <v-navigation-drawer app v-model="ui.naviDrawer.show" height="100%" temporary right disable-route-watcher>
  180. <v-list dense nav>
  181. <v-list-item dense>
  182. <v-list-item-avatar @click="open(box.syscfgs.boxjs.repo)" class="elevation-3">
  183. <v-img :src="box.syscfgs.boxjs.icon"></v-img>
  184. </v-list-item-avatar>
  185. <v-row justify="start" no-gutters>
  186. <v-col v-for="(c, cIdx) in ui.collaborators" cols="4" :key="c.id">
  187. <a>
  188. <v-avatar size="40" @click="open(c.repo)" class="elevation-3">
  189. <img :src="c.icon" />
  190. </v-avatar>
  191. </a>
  192. </v-col>
  193. </v-row>
  194. </v-list-item>
  195. <v-divider></v-divider>
  196. <v-list-item class="pt-1">
  197. <v-progress-linear :active="isLoading" height="1" absolute top indeterminate></v-progress-linear>
  198. <v-row justify="start" no-gutters>
  199. <v-col v-for="(c, cIdx) in ui.contributors" cols="2" :key="c.id">
  200. <v-tooltip bottom>
  201. <template v-slot:activator="{ on, attrs }">
  202. <a>
  203. <v-avatar v-on="on" class="ma-1 elevation-3" size="26" @click="open(c.repo)">
  204. <v-img :src="c.icon"></v-img>
  205. </v-avatar>
  206. </a>
  207. </template>
  208. <span>{{c.login}}</span>
  209. </v-tooltip>
  210. </v-col>
  211. </v-row>
  212. </v-list-item>
  213. <v-divider></v-divider>
  214. <v-list-item v-if="box.syscfgs.env === 'Surge'">
  215. <v-list-item-content>
  216. <v-select
  217. v-if="box.usercfgs.httpapis"
  218. hide-details
  219. v-model="box.usercfgs.httpapi"
  220. :items="box.usercfgs.httpapis.split(',')"
  221. @change="saveUserCfgs"
  222. label="HTTP-API (Surge)"
  223. >
  224. </v-select>
  225. <v-text-field
  226. v-else
  227. label="HTTP-API (Surge)"
  228. v-model="box.usercfgs.httpapi"
  229. hint="Surge http-api 地址."
  230. placeholder="[email protected]:6166"
  231. persistent-hint
  232. @change="saveUserCfgs"
  233. :rules="[(val)=> /.*?@.*?:[0-9]+/.test(val) || '格式错误: [email protected]:6166']"
  234. >
  235. </v-text-field>
  236. </v-list-item-content>
  237. </v-list-item>
  238. <v-list-item>
  239. <v-list-item-content>
  240. <v-select
  241. :items="[{text: 'English', value: 'en-US'}, {text: '简体中文', value: 'zh-CN'}]"
  242. hide-details
  243. label="Language"
  244. v-model="box.usercfgs.lang"
  245. >
  246. </v-select>
  247. </v-list-item-content>
  248. </v-list-item>
  249. <v-list-item>
  250. <v-list-item-content>
  251. <v-select
  252. :items="[{text: $t('prefs.appearances.auto'), value: 'auto'}, {text: $t('prefs.appearances.dark'), value: 'dark'}, {text: $t('prefs.appearances.light'), value: 'light'}]"
  253. :label="$t('prefs.appearance')"
  254. hide-details
  255. v-model="box.usercfgs.theme"
  256. >
  257. </v-select>
  258. </v-list-item-content>
  259. </v-list-item>
  260. <v-list-item class="mt-4" v-show="box.usercfgs.bgimgs">
  261. <v-list-item-content>
  262. <v-select
  263. :items="bgimgs"
  264. :label="$t('prefs.background')"
  265. @change="saveUserCfgs"
  266. hide-details
  267. item-text="name"
  268. item-value="url"
  269. v-model="box.usercfgs.bgimg"
  270. >
  271. </v-select>
  272. </v-list-item-content>
  273. </v-list-item>
  274. <v-list-item class="mt-4">
  275. <v-switch
  276. :hide-details="isDarkMode"
  277. :hint="$t('prefs.iconDesc')"
  278. :label="$t('prefs.icon')"
  279. :persistent-hint="true"
  280. @change="saveUserCfgs"
  281. class="mt-0"
  282. dense
  283. v-model="box.usercfgs.isTransparentIcons"
  284. >
  285. </v-switch>
  286. <v-spacer></v-spacer>
  287. <v-btn fab small text @click="open(box.syscfgs.orz3.repo)">
  288. <v-avatar size="32"><img :src="box.syscfgs.orz3.icon" /></v-avatar>
  289. </v-btn>
  290. </v-list-item>
  291. <v-list-item class="mt-4">
  292. <v-switch
  293. :hint="$t('prefs.bgModeDesc')"
  294. :label="$t('prefs.bgMode')"
  295. :persistent-hint="true"
  296. @change="saveUserCfgs"
  297. class="mt-0"
  298. dense
  299. v-model="isWallpaperMode"
  300. >
  301. </v-switch>
  302. <v-spacer></v-spacer>
  303. <v-btn fab small text>
  304. <v-avatar size="32"><v-icon>mdi-image</v-icon></v-avatar>
  305. </v-btn>
  306. </v-list-item>
  307. <v-list-item class="mt-4">
  308. <v-switch
  309. :hint="$t('prefs.hideTopBarDesc')"
  310. :label="$t('prefs.hideTopBar')"
  311. :persistent-hint="true"
  312. @change="saveUserCfgs"
  313. class="mt-0"
  314. dense
  315. v-model="box.usercfgs.isHidedSearchBar"
  316. >
  317. </v-switch>
  318. <v-spacer></v-spacer>
  319. <v-btn fab small text>
  320. <v-avatar size="32"><v-icon>mdi-dock-top</v-icon></v-avatar>
  321. </v-btn>
  322. </v-list-item>
  323. <v-list-item class="mt-4">
  324. <v-switch
  325. :hint="$t('prefs.autoTopBarDesc')"
  326. :label="$t('prefs.autoTopBar')"
  327. :persistent-hint="true"
  328. @change="saveUserCfgs"
  329. class="mt-0"
  330. dense
  331. v-model="isAutoSearchBar"
  332. >
  333. </v-switch>
  334. <v-spacer></v-spacer>
  335. <v-btn fab small text>
  336. <v-avatar size="32"><v-icon>mdi-format-align-top</v-icon></v-avatar>
  337. </v-btn>
  338. </v-list-item>
  339. <v-list-item class="mt-4">
  340. <v-switch
  341. :hint="$t('prefs.hideBottomBarDesc')"
  342. :label="$t('prefs.hideBottomBar')"
  343. :persistent-hint="true"
  344. @change="saveUserCfgs"
  345. class="mt-0"
  346. dense
  347. v-model="box.usercfgs.isHidedNaviBottom"
  348. >
  349. </v-switch>
  350. <v-spacer></v-spacer>
  351. <v-btn fab small text>
  352. <v-avatar size="32"><v-icon>mdi-dock-bottom</v-icon></v-avatar>
  353. </v-btn>
  354. </v-list-item>
  355. <v-list-item class="mt-4">
  356. <v-switch
  357. :hint="$t('prefs.autoBottomBarDesc')"
  358. :label="$t('prefs.autoBottomBar')"
  359. :persistent-hint="true"
  360. @change="saveUserCfgs"
  361. class="mt-0"
  362. dense
  363. v-model="isAutoNaviBottom"
  364. >
  365. </v-switch>
  366. <v-spacer></v-spacer>
  367. <v-btn fab small text>
  368. <v-avatar size="32"><v-icon>mdi-format-align-bottom</v-icon></v-avatar>
  369. </v-btn>
  370. </v-list-item>
  371. <!-- <v-list-item class="mt-4">
  372. <v-switch
  373. dense
  374. class="mt-0"
  375. label="透明主题"
  376. v-model="box.usercfgs.isTransparent"
  377. @change="saveUserCfgs"
  378. :persistent-hint="true"
  379. hint="使界面更多元素透明 (beta)"
  380. >
  381. </v-switch>
  382. <v-spacer></v-spacer>
  383. <v-btn fab small text>
  384. <v-avatar size="32"><v-icon>mdi-invert-colors</v-icon></v-avatar>
  385. </v-btn>
  386. </v-list-item> -->
  387. <v-list-item class="mt-4">
  388. <v-switch
  389. :hint="$t('prefs.muteModeDesc')"
  390. :label="$t('prefs.muteMode')"
  391. @change="saveUserCfgs"
  392. class="mt-0"
  393. dense
  394. persistent-hint
  395. v-model="box.usercfgs.isMute"
  396. ></v-switch>
  397. <v-spacer></v-spacer>
  398. <v-btn fab small text>
  399. <v-avatar size="32"><v-icon>mdi-volume-off</v-icon></v-avatar>
  400. </v-btn>
  401. </v-list-item>
  402. <v-list-item class="mt-4">
  403. <v-switch
  404. :hint="$t('prefs.hideHelpDesc')"
  405. :label="$t('prefs.hideHelp')"
  406. @change="saveUserCfgs"
  407. class="mt-0"
  408. dense
  409. persistent-hint
  410. v-model="box.usercfgs.isHideHelp"
  411. ></v-switch>
  412. <v-spacer></v-spacer>
  413. <v-btn fab small text @click="open(box.syscfgs.boxjs.repo)">
  414. <v-avatar size="32"><v-icon>mdi-help</v-icon></v-avatar>
  415. </v-btn>
  416. </v-list-item>
  417. <v-list-item class="mt-4">
  418. <v-switch
  419. :hint="$t('prefs.hideBoxJsDesc')"
  420. :label="$t('prefs.hideBoxJs')"
  421. @change="saveUserCfgs"
  422. class="mt-0"
  423. dense
  424. persistent-hint
  425. v-model="box.usercfgs.isHideBoxIcon"
  426. ></v-switch>
  427. <v-spacer></v-spacer>
  428. <v-btn fab small text @click="open(box.syscfgs.boxjs.repo)">
  429. <v-avatar size="32">
  430. <img :src="box.syscfgs.boxjs.icons[iconThemeIdx]" />
  431. </v-avatar>
  432. </v-btn>
  433. </v-list-item>
  434. <v-list-item class="mt-4">
  435. <v-switch
  436. :hint="$t('prefs.hideProfileTitleDesc')"
  437. :label="$t('prefs.hideProfileTitle')"
  438. @change="saveUserCfgs"
  439. class="mt-0"
  440. dense
  441. persistent-hint
  442. v-model="box.usercfgs.isHideMyTitle"
  443. ></v-switch>
  444. <v-spacer></v-spacer>
  445. <v-btn fab small text>
  446. <v-avatar v-if="box.usercfgs.icon" size="32">
  447. <img :src="box.usercfgs.icon" />
  448. </v-avatar>
  449. <v-icon v-else size="32">mdi-face-profile</v-icon>
  450. </v-btn>
  451. </v-list-item>
  452. <v-list-item class="mt-4">
  453. <v-switch
  454. :hint="$t('prefs.hideCoddingDesc')"
  455. :label="$t('prefs.hideCodding')"
  456. dense
  457. class="mt-0"
  458. persistent-hint
  459. v-model="box.usercfgs.isHideCoding"
  460. @change="saveUserCfgs"
  461. ></v-switch>
  462. <v-spacer></v-spacer>
  463. <v-btn fab small text>
  464. <v-avatar size="32"><v-icon>mdi-code-tags</v-icon></v-avatar>
  465. </v-btn>
  466. </v-list-item>
  467. <v-list-item class="mt-4">
  468. <v-switch
  469. :hint="$t('prefs.hideReloadDesc')"
  470. :label="$t('prefs.hideReload')"
  471. @change="saveUserCfgs"
  472. class="mt-0"
  473. dense
  474. persistent-hint
  475. v-model="box.usercfgs.isHideRefresh"
  476. ></v-switch>
  477. <v-spacer></v-spacer>
  478. <v-btn fab small text>
  479. <v-avatar size="32"><v-icon>mdi-refresh</v-icon></v-avatar>
  480. </v-btn>
  481. </v-list-item>
  482. <v-list-item class="mt-4">
  483. <v-switch
  484. :hint="$t('prefs.debugModeDesc')"
  485. :label="$t('prefs.debugMode')"
  486. @change="saveUserCfgs"
  487. class="mt-0"
  488. dense
  489. persistent-hint
  490. v-model="box.usercfgs.isDebugWeb"
  491. ></v-switch>
  492. <v-spacer></v-spacer>
  493. <v-btn fab small text>
  494. <v-avatar size="32"><v-icon>mdi-language-html5</v-icon></v-avatar>
  495. </v-btn>
  496. </v-list-item>
  497. <v-list-item v-if="box.usercfgs.isDebugWeb">
  498. <v-list-item-content>
  499. <v-text-field
  500. :hint="$t('prefs.debugPageDesc')"
  501. :label="$t('prefs.debugPage')"
  502. @change="saveUserCfgs"
  503. clearable
  504. persistent-hint
  505. placeholder="http://ip:port/boxjs.html"
  506. v-model="box.usercfgs.debugger_web"
  507. >
  508. </v-text-field>
  509. </v-list-item-content>
  510. </v-list-item>
  511. <v-list-item class="mt-4"></v-list-item>
  512. </v-list>
  513. </v-navigation-drawer>
  514. <!-- 主页 -->
  515. <v-main class="appBarBind.app ? 'safe' : ''" v-scroll="onScroll">
  516. <v-snackbar top app v-model="ui.snackbar.show" v-bind="ui.snackbar">{{ui.snackbar.msg}}</v-snackbar>
  517. <!-- 主页 -->
  518. <v-container
  519. fluid
  520. v-show="view === ''"
  521. v-touch="{
  522. up: () => {
  523. if (isWallpaperMode) {
  524. clearWallpaper()
  525. setWallpaper()
  526. }
  527. },
  528. down: () => {
  529. if (isWallpaperMode) {
  530. isWallpaperMode = !isWallpaperMode
  531. changeWallpaper()
  532. }
  533. }
  534. }"
  535. >
  536. <v-row no-gutters v-show="!isHidedAppIcons" class="align-self-start" id="appList">
  537. <v-col cols="3" md="2" v-for="(app, appIdx) in favApps" :key="app.id" class="d-flex justify-space-around">
  538. <div class="ma-2 appicon" @click="switchAppView(app.id)">
  539. <v-card v-if="isDarkMode" style="border-radius: 12px">
  540. <v-img style="border-radius: 12px" :aspect-ratio="1" width="54" height="54" contain v-ripple :src="app.icon"></v-img>
  541. </v-card>
  542. <v-img
  543. v-else
  544. style="border-radius: 12px"
  545. :aspect-ratio="1"
  546. width="54"
  547. height="54"
  548. contain
  549. v-ripple
  550. class="elevation-3"
  551. :src="app.icon"
  552. ></v-img>
  553. <p class="text-center ma-0">
  554. <span class="d-inline-block text-truncate font-weight-bold" :style="appIconFontStyle"> {{app.name}} </span>
  555. </p>
  556. </div>
  557. </v-col>
  558. </v-row>
  559. </v-container>
  560. <!-- 应用列表 -->
  561. <v-container fluid v-show="view === 'app' && !curapp">
  562. <!-- 收藏应用 -->
  563. <v-expansion-panels multiple class="mb-4" v-if="favApps.length > 0" v-model="box.usercfgs.favapppanel">
  564. <v-expansion-panel>
  565. <v-expansion-panel-header> {{ $t('apps.fav') }} ({{favApps.length}}) </v-expansion-panel-header>
  566. <v-expansion-panel-content>
  567. <v-list dense nav class="ma-n4">
  568. <template v-for="(app, appIdx) in favApps">
  569. <v-list-item dense @click="switchAppView(app.id)" :key="app.id">
  570. <v-list-item-avatar class="elevation-3"><v-img :src="app.icon" /></v-list-item-avatar>
  571. <v-list-item-content>
  572. <v-list-item-title>{{app.name}} ({{app.id}})</v-list-item-title>
  573. <v-list-item-subtitle>{{app.repo}}</v-list-item-subtitle>
  574. <v-list-item-subtitle>{{app.author}}</v-list-item-subtitle>
  575. </v-list-item-content>
  576. <v-list-item-action>
  577. <v-menu bottom left>
  578. <template #activator="{ on }">
  579. <v-btn icon v-on="on"><v-icon>mdi-dots-vertical</v-icon></v-btn>
  580. </template>
  581. <v-list>
  582. <v-list-item dense v-if="appIdx > 0" @click="moveFav(appIdx, -1)">
  583. <v-list-item-title>{{ $t('base.sort.up') }}</v-list-item-title>
  584. </v-list-item>
  585. <v-list-item dense v-if="appIdx + 1 < favApps.length" @click="moveFav(appIdx, 1)">
  586. <v-list-item-title>{{ $t('base.sort.dn') }}</v-list-item-title>
  587. </v-list-item>
  588. <v-divider v-if="favApps.length > 1"></v-divider>
  589. <v-list-item dense @click="favApp(app.id)">
  590. <v-list-item-title>{{ $t('apps.unStar') }}</v-list-item-title>
  591. </v-list-item>
  592. </v-list>
  593. </v-menu>
  594. </v-list-item-action>
  595. </v-list-item>
  596. <!-- <v-divider inset v-if="favApps.length !== appIdx + 1"></v-divider> -->
  597. </template>
  598. </v-list>
  599. </v-expansion-panel-content>
  600. </v-expansion-panel>
  601. </v-expansion-panels>
  602. <!-- 订阅应用 -->
  603. <v-expansion-panels multiple class="mb-4" v-if="appSubs.length > 0" v-model="box.usercfgs.subapppanel">
  604. <v-expansion-panel v-for="(sub, subIdx) in appSubs" :key="sub.id" v-if="!sub.isErr">
  605. <v-expansion-panel-header> {{sub.name}} ({{sub.apps.length}}) </v-expansion-panel-header>
  606. <v-expansion-panel-content>
  607. <v-list dense nav class="ma-n4">
  608. <template v-for="(app, appIdx) in sub.apps">
  609. <v-list-item dense @click="switchAppView(app.id)" :key="app.id">
  610. <v-list-item-avatar class="elevation-3"><v-img :src="app.icon" /></v-list-item-avatar>
  611. <v-list-item-content>
  612. <v-list-item-title>{{app.name}} ({{app.id}})</v-list-item-title>
  613. <v-list-item-subtitle>{{app.repo}}</v-list-item-subtitle>
  614. <v-list-item-subtitle>{{app.author}}</v-list-item-subtitle>
  615. </v-list-item-content>
  616. <v-list-item-action>
  617. <v-btn icon @click.stop="favApp(app.id)">
  618. <v-icon :color="app.favIconColor" v-text="app.favIcon" />
  619. </v-btn>
  620. </v-list-item-action>
  621. </v-list-item>
  622. <!-- <v-divider inset v-if="sub.apps.length !== appIdx + 1"></v-divider> -->
  623. </template>
  624. </v-list>
  625. </v-expansion-panel-content>
  626. </v-expansion-panel>
  627. </v-expansion-panels>
  628. <!-- 内置应用 -->
  629. <v-expansion-panels multiplev-if="sysApps.length > 0" v-model="box.usercfgs.sysapppanel">
  630. <v-expansion-panel>
  631. <v-expansion-panel-header> {{ $t('apps.sysApps') }} ({{sysApps.length}}) </v-expansion-panel-header>
  632. <v-expansion-panel-content>
  633. <v-list dense nav class="ma-n4">
  634. <template v-for="(app, appIdx) in sysApps">
  635. <v-list-item dense @click="switchAppView(app.id)" :key="app.id">
  636. <v-list-item-avatar class="elevation-3"><v-img :src="app.icon" /></v-list-item-avatar>
  637. <v-list-item-content>
  638. <v-list-item-title>{{app.name}} ({{app.id}})</v-list-item-title>
  639. <v-list-item-subtitle>{{app.repo}}</v-list-item-subtitle>
  640. <v-list-item-subtitle>{{app.author}}</v-list-item-subtitle>
  641. </v-list-item-content>
  642. <v-list-item-action>
  643. <v-btn icon @click.stop="favApp(app.id)">
  644. <v-icon :color="app.favIconColor" v-text="app.favIcon" />
  645. </v-btn>
  646. </v-list-item-action>
  647. </v-list-item>
  648. <!-- <v-divider inset v-if="sysApps.length !== appIdx + 1"></v-divider> -->
  649. </template>
  650. </v-list>
  651. </v-expansion-panel-content>
  652. </v-expansion-panel>
  653. </v-expansion-panels>
  654. </v-container>
  655. <!-- 订阅列表 -->
  656. <v-container fluid v-show="view === 'sub'">
  657. <template v-if="appSubs.length === 0">
  658. <v-btn block class="primary" @click="addAppSubDialog = true">{{ $t('subs.add') }}</v-btn>
  659. <v-btn block class="primary" @click="open('https://chavyleung.gitbook.io/boxjs/awesome/subscriptions')">
  660. <v-icon class="mr-2">mdi-cloud</v-icon>{{ $t('subs.moreSubs') }}
  661. </v-btn>
  662. </template>
  663. <v-card v-else>
  664. <v-list dense nav>
  665. <v-subheader inset dense>
  666. {{ $t('subs.appSubs') }} ({{appSubs.length}})
  667. <v-spacer></v-spacer>
  668. <v-btn icon @click="open('https://chavyleung.gitbook.io/boxjs/awesome/subscriptions')">
  669. <v-icon>mdi-cloud-circle</v-icon>
  670. </v-btn>
  671. <v-btn icon @click="reloadAppSub()">
  672. <v-icon>mdi-refresh-circle</v-icon>
  673. </v-btn>
  674. <v-btn icon>
  675. <v-icon color="primary" @click="addAppSubDialog = true">mdi-plus-circle</v-icon>
  676. </v-btn>
  677. </v-subheader>
  678. <template v-for="(sub, subIdx) in appSubs">
  679. <v-list-item dense two-line @click="reloadAppSub(sub)" :key="sub.id">
  680. <v-list-item-avatar v-if="sub.icon"><v-img :src="sub.icon" /></v-list-item-avatar>
  681. <v-list-item-avatar v-else color="primary"><v-icon dark>mdi-account</v-icon></v-list-item-avatar>
  682. <v-list-item-content>
  683. <v-list-item-title>
  684. {{sub.name}} ({{sub.apps.length}})
  685. <v-chip v-if="sub.isErr" color="pink" dark x-small class="ml-1 mb-1">{{ $t('subs.errData') }}</v-chip>
  686. </v-list-item-title>
  687. <v-list-item-subtitle>{{sub.repo ? sub.repo : sub.url}}</v-list-item-subtitle>
  688. <v-list-item-subtitle>{{sub.author ? sub.author : '@anonymous'}}</v-list-item-subtitle>
  689. <v-list-item-subtitle>
  690. {{ $t('subs.updated') }}: {{ timeago.format(sub.updateTime, timeagoLang.replace('-', '_')) }}
  691. </v-list-item-subtitle>
  692. </v-list-item-content>
  693. <v-list-item-action>
  694. <v-menu bottom left>
  695. <template #activator="{ on }">
  696. <v-btn icon v-on="on"><v-icon>mdi-dots-vertical</v-icon></v-btn>
  697. </template>
  698. <v-list dense>
  699. <template v-if="sub.onInstall">
  700. <v-list-item @click="openInstall(sub.raw.id)">
  701. <v-list-item-title>{{ $t('subs.install') }}</v-list-item-title>
  702. </v-list-item>
  703. <v-divider></v-divider>
  704. </template>
  705. <v-list-item @click="open(sub.repo)">
  706. <v-list-item-title>{{ $t('subs.repo') }}</v-list-item-title>
  707. </v-list-item>
  708. <v-list-item @click="copy(sub.url)">
  709. <v-list-item-title>{{ $t('base.cmd.cp') }}</v-list-item-title>
  710. </v-list-item>
  711. <v-list-item @click="share(sub.url)">
  712. <v-list-item-title>{{ $t('base.cmd.share') }}</v-list-item-title>
  713. </v-list-item>
  714. <v-divider></v-divider>
  715. <v-list-item v-if="subIdx > 0" @click="moveSub(subIdx, -1)">
  716. <v-list-item-title>{{ $t('base.sort.up') }}</v-list-item-title>
  717. </v-list-item>
  718. <v-list-item v-if="subIdx + 1 < appSubs.length" @click="moveSub(subIdx, 1)">
  719. <v-list-item-title>{{ $t('base.sort.dn') }}</v-list-item-title>
  720. </v-list-item>
  721. <v-divider></v-divider>
  722. <v-list-item @click="delSub(subIdx)">
  723. <v-list-item-title class="text-uppercase red--text">{{ $t('base.cmd.del') }}</v-list-item-title>
  724. </v-list-item>
  725. </v-list>
  726. </v-menu>
  727. </v-list-item-action>
  728. </v-list-item>
  729. <!-- <v-divider inset v-if="appSubs.length !== subIdx + 1"></v-divider> -->
  730. </template>
  731. </v-list>
  732. </v-card>
  733. <v-dialog v-model="addAppSubDialog" scrollable>
  734. <v-card>
  735. <v-card-title>{{ $t('subs.addDialog.title') }}</v-card-title>
  736. <v-divider></v-divider>
  737. <v-card-text>
  738. <v-textarea
  739. :label="$t('subs.addDialog.url')"
  740. :hint="$t('subs.addDialog.urlDesc')"
  741. autofocus
  742. clearable
  743. persistent-hint
  744. rows="3"
  745. v-model="ui.addAppSubDialog.url"
  746. ></v-textarea>
  747. </v-card-text>
  748. <v-divider></v-divider>
  749. <v-card-actions>
  750. <v-spacer></v-spacer>
  751. <v-btn text small color="grey" @click="addAppSubDialog = false"> {{$t('base.dialog.close')}} </v-btn>
  752. <v-btn text small color="primary" @click="addAppSub(ui.addAppSubDialog.url)" :loading="isLoading">
  753. {{$t('base.dialog.save')}}
  754. </v-btn>
  755. </v-card-actions>
  756. </v-card>
  757. </v-dialog>
  758. <v-dialog persistent v-model="ui.installConfirmDialog.show">
  759. <v-card>
  760. <v-card-title>{{ ui.installConfirmDialog.title }}</v-card-title>
  761. <v-card-text> {{ ui.installConfirmDialog.message }}</v-card-text>
  762. <v-card-actions>
  763. <v-spacer></v-spacer>
  764. <v-btn text small color="grey" @click="ui.installConfirmDialog.show = false"> {{$t('base.dialog.close')}} </v-btn>
  765. <v-btn text small color="primary" @click="install(ui.installConfirmDialog.url)" :loading="isLoading">
  766. {{$t('base.dialog.ok')}}
  767. </v-btn>
  768. </v-card-actions>
  769. </v-card>
  770. </v-dialog>
  771. </v-container>
  772. <!-- 我的 -->
  773. <v-container fluid v-show="view === 'my'">
  774. <v-card class="mx-auto">
  775. <v-card-title class="headline">
  776. {{box.usercfgs.name ? box.usercfgs.name : $t('profile.leaveName')}}
  777. <v-spacer></v-spacer>
  778. <v-dialog v-model="ui.editProfileDialog.show">
  779. <template #activator="{ on }">
  780. <v-btn icon v-on="on"><v-icon>mdi-cog-outline</v-icon></v-btn>
  781. </template>
  782. <v-card>
  783. <v-card-title>{{ $t('profile.editor.title') }}</v-card-title>
  784. <v-divider></v-divider>
  785. <v-card-text>
  786. <v-text-field
  787. :hint="$t('profile.editor.nameDesc')"
  788. :label="$t('profile.editor.name')"
  789. v-model="box.usercfgs.name"
  790. ></v-text-field>
  791. <v-text-field
  792. :hint="$t('profile.editor.avatarDesc')"
  793. :label="$t('profile.editor.avatar')"
  794. v-model="box.usercfgs.icon"
  795. ></v-text-field>
  796. </v-card-text>
  797. <v-divider></v-divider>
  798. <v-card-actions>
  799. <v-spacer></v-spacer>
  800. <v-btn text small color="grey" @click="ui.editProfileDialog.show = false">{{ $t('base.dialog.close') }}</v-btn>
  801. <v-btn text small color="primary" @click="ui.editProfileDialog.show = false" :loading="isLoading">
  802. {{ $t('base.dialog.save') }}
  803. </v-btn>
  804. </v-card-actions>
  805. </v-card>
  806. </v-dialog>
  807. </v-card-title>
  808. <v-divider class="mx-4"></v-divider>
  809. <v-card-text>
  810. <span class="subheading">{{ $t('profile.datas') }}</span>
  811. <v-chip-group>
  812. <v-chip small>{{ $t('profile.apps') }}: {{this.apps.length}}</v-chip>
  813. <v-chip small>{{ $t('profile.subs') }}: {{this.appSubs.length}}</v-chip>
  814. <v-chip small>{{ $t('profile.sessions') }}: {{this.sessions.length}}</v-chip>
  815. </v-chip-group>
  816. </v-card-text>
  817. <v-card-actions>
  818. <v-spacer></v-spacer>
  819. <v-btn small class="mr-2" @click="switchView('viewer')"> {{ $t('profile.dataviewer')}} </v-btn>
  820. <v-dialog v-model="ui.impGlobalBakDialog.show">
  821. <template #activator="{ on }">
  822. <v-btn small v-on="on">{{ $t('profile.imp') }}</v-btn>
  823. </template>
  824. <v-card>
  825. <v-card-title> {{ $t('profile.impDialog.title') }} </v-card-title>
  826. <v-divider></v-divider>
  827. <v-card-text>
  828. <v-textarea
  829. :hint="$t('profile.impDialog.impDataDesc')"
  830. :label="$t('profile.impDialog.impData')"
  831. autofocus
  832. clearable
  833. rows="3"
  834. v-model="ui.impGlobalBakDialog.impval"
  835. ></v-textarea>
  836. </v-card-text>
  837. <v-divider></v-divider>
  838. <v-card-actions>
  839. <v-spacer></v-spacer>
  840. <v-btn text small color="grey" text @click="ui.impGlobalBakDialog.show = false">{{ $t('base.dialog.close') }}</v-btn>
  841. <v-btn text small color="primary" text @click="impGlobalBak" :loading="isLoading">{{ $t('profile.imp') }}</v-btn>
  842. </v-card-actions>
  843. </v-card>
  844. </v-dialog>
  845. <v-btn small @click="saveGlobalBak">{{ $t('profile.bak') }}</v-btn>
  846. </v-card-actions>
  847. </v-card>
  848. <v-card class="mt-4" v-if="box.globalbaks">
  849. <template v-for="(bak, bakIdx) in box.globalbaks">
  850. <v-divider v-if="bakIdx>0"></v-divider>
  851. <v-list-item dense @click="switchBakView(bak.id)">
  852. <v-list-item-content>
  853. <v-list-item-title>{{bak.name}}</v-list-item-title>
  854. <v-list-item-subtitle>{{dayjs(bak.createTime).format('YYYY-MM-DD HH:mm:ss')}}</v-list-item-subtitle>
  855. <v-list-item-subtitle>
  856. <v-chip x-small class="mr-2" v-for="(tag, tagIdx) in bak.tags" :key="tagIdx">{{tag}}</v-chip>
  857. </v-list-item-subtitle>
  858. </v-list-item-content>
  859. <v-list-item-action>
  860. <v-btn icon><v-icon>mdi-chevron-right</v-icon></v-btn>
  861. </v-list-item-action>
  862. </v-list-item>
  863. </template>
  864. </v-card>
  865. </v-container>
  866. <!-- 数据查看 -->
  867. <v-container fluid v-show="view === 'viewer'">
  868. <v-card class="mb-4">
  869. <v-subheader>
  870. {{ $t('viewer.dataViewer') }}
  871. <v-spacer></v-spacer>
  872. <v-btn color="primary" small @click="copy(ui.viewer.key)"> {{ $t('base.cmd.cp') }} </v-btn>
  873. </v-subheader>
  874. <v-card-text>
  875. <v-text-field
  876. :hint="$t('viewer.dataKeyDesc')"
  877. :label="$t('viewer.dataKey')"
  878. persistent-hint
  879. placeholder="boxjs_host"
  880. v-model="ui.viewer.key"
  881. >
  882. </v-text-field>
  883. </v-card-text>
  884. <v-divider></v-divider>
  885. <v-card-actions>
  886. <!-- TODO 列出最近查询过的 key -->
  887. <v-spacer></v-spacer>
  888. <v-btn small text color="primary" @click="queryData">{{ $t('base.dialog.view') }}</v-btn>
  889. </v-card-actions>
  890. </v-card>
  891. <v-card class="mb-4">
  892. <v-subheader>
  893. {{ $t('viewer.dataEditor') }}
  894. <v-spacer></v-spacer>
  895. <v-btn color="primary" small @click="copy(ui.viewer.val)"> {{ $t('base.cmd.cp') }} </v-btn>
  896. </v-subheader>
  897. <v-card-text>
  898. <v-textarea v-model="ui.viewer.val" :row="3" :label="$t('viewer.dataVal')"> </v-textarea>
  899. </v-card-text>
  900. <v-divider></v-divider>
  901. <v-card-actions>
  902. <v-spacer></v-spacer>
  903. <v-btn small text color="primary" @click="saveData">{{ $t('base.dialog.save') }}</v-btn>
  904. </v-card-actions>
  905. </v-card>
  906. </v-container>
  907. <!-- 代码编辑 -->
  908. <v-container fluid v-show="view === 'coding'">
  909. <v-card rounded="0" flat style="width: inherit">
  910. <v-subheader>
  911. <h2>{{ $t('codding.title') }}</h2>
  912. <v-spacer></v-spacer>
  913. <v-btn icon @click="runTxtScript" :loading="isLoading">
  914. <v-icon color="primary">mdi-play-circle</v-icon>
  915. </v-btn>
  916. </v-subheader>
  917. </v-card>
  918. <div class="pa-0" id="container" style="width: inherit; height: inherit"></div>
  919. </v-container>
  920. <!-- 应用详情 -->
  921. <v-container fluid v-if="view === 'app' && !!curapp">
  922. <v-subheader>
  923. <h2 :style="appTitleStyle">{{curapp.name}}</h2>
  924. <v-spacer></v-spacer>
  925. <v-btn v-if="curapp.script" icon :loading="isLoading" @click="runRemoteScript(curapp.script, curapp.script_timeout)">
  926. <v-icon color="primary">mdi-play-circle</v-icon>
  927. </v-btn>
  928. </v-subheader>
  929. <v-card class="mb-4" v-if="curapp.desc || curapp.descs || curapp.desc_html || curapp.descs_html">
  930. <v-card-subtitle>
  931. <p v-if="curapp.desc" v-text="curapp.desc" class="text-pre-wrap"></p>
  932. <p
  933. v-for="(desc, descIdx) in curapp.descs"
  934. v-text="desc"
  935. :class="curapp.descs.length === descIdx + 1 ? 'text-pre-wrap' : 'mb-0 text-pre-wrap'"
  936. ></p>
  937. <p v-if="curapp.desc_html" v-html="curapp.desc_html"></p>
  938. <div v-for="(desc_html, desc_htmlIdx) in curapp.descs_html" v-html="desc_html"></div>
  939. </v-card-subtitle>
  940. </v-card>
  941. <v-card class="mb-4">
  942. <template v-if="curapp.scripts">
  943. <v-subheader> {{ $t('appDetail.scripts') }} ({{curapp.scripts.length}}) </v-subheader>
  944. <v-list dense>
  945. <v-list-item v-for="(script, scriptIdx) in curapp.scripts" :key="scriptIdx">
  946. <v-list-item-title> {{scriptIdx + 1}}. {{script.name}} </v-list-item-title>
  947. <v-btn icon :loading="isLoading" @click.stop="runRemoteScript(script.script, script.script_timeout)">
  948. <v-icon>mdi-play-circle</v-icon>
  949. </v-btn>
  950. </v-list-item>
  951. </v-list>
  952. </template>
  953. </v-card>
  954. <v-card class="mb-4">
  955. <template v-if="curapp.settings">
  956. <v-subheader> {{ $t('appDetail.settings') }} ({{curapp.settings.length}}) </v-subheader>
  957. <v-form class="pl-4 pr-4 pb-4">
  958. <template v-for="(setting, settingIdx) in curapp.settings">
  959. <v-slider
  960. v-model="setting.val"
  961. v-bind="setting"
  962. class="mt-4"
  963. dense
  964. persistent-hint
  965. :label="setting.name"
  966. :hint="setting.desc"
  967. thumb-label="always"
  968. v-if="setting.type === 'slider'"
  969. ></v-slider>
  970. <v-switch
  971. v-model="setting.val"
  972. class="mt-2"
  973. persistent-hint
  974. dense
  975. :label="setting.name"
  976. :hint="setting.desc"
  977. v-else-if="setting.type === 'boolean'"
  978. ></v-switch>
  979. <v-textarea
  980. v-model="setting.val"
  981. v-bind="setting"
  982. class="mt-4"
  983. :row="3"
  984. :label="setting.name"
  985. :hint="setting.desc"
  986. v-else-if="setting.type === 'textarea'"
  987. ></v-textarea>
  988. <v-radio-group
  989. v-model="setting.val"
  990. v-bind="setting"
  991. persistent-hint
  992. class="mt-0"
  993. :hint="setting.desc"
  994. v-else-if="setting.type === 'radios'"
  995. >
  996. <v-subheader class="mb-n4 pa-0">{{setting.name}}</v-subheader>
  997. <v-radio
  998. :class="itemIdx === 0 ? 'mt-2' : ''"
  999. v-for="(item, itemIdx) in setting.items"
  1000. :label="item.label"
  1001. :value="item.key"
  1002. :key="item.key"
  1003. ></v-radio>
  1004. </v-radio-group>
  1005. <template v-else-if="setting.type === 'checkboxes'">
  1006. <v-subheader class="mb-n8 pa-0">{{setting.name}}</v-subheader>
  1007. <v-item-group class="mt-4 pt-1">
  1008. <v-checkbox
  1009. v-model="setting.val"
  1010. class="mt-0"
  1011. persistent-hint
  1012. :hide-details="itemIdx + 1 !== setting.items.length"
  1013. :hint="setting.desc"
  1014. :label="item.label"
  1015. :value="item.key"
  1016. v-for="(item, itemIdx) in setting.items"
  1017. :key="item.key"
  1018. multiple
  1019. ></v-checkbox>
  1020. </v-item-group>
  1021. </template>
  1022. <template v-else-if="setting.type === 'colorpicker'">
  1023. <v-subheader class="mb-n2 pa-0">{{setting.name}}</v-subheader>
  1024. <v-color-picker
  1025. v-model="setting.val"
  1026. v-bind="setting"
  1027. class="mt-2 mb-4"
  1028. persistent-hint
  1029. :hint="setting.desc"
  1030. :hide-canvas="!setting.canvas"
  1031. :dot-size="30"
  1032. mode="hexa"
  1033. light
  1034. ></v-color-picker>
  1035. </template>
  1036. <div class="mt-4" v-else-if="setting.type === 'number'">
  1037. <v-text-field
  1038. v-model="setting.val"
  1039. v-bind="setting"
  1040. type="number"
  1041. :label="setting.name"
  1042. :hint="setting.desc"
  1043. ></v-text-field>
  1044. </div>
  1045. <div class="mt-4" v-else-if="setting.type === 'selects'">
  1046. <v-select
  1047. v-model="setting.val"
  1048. v-bind="setting"
  1049. persistent-hint
  1050. type="number"
  1051. item-text="label"
  1052. item-value="key"
  1053. :items="setting.items"
  1054. :label="setting.name"
  1055. :hint="setting.desc"
  1056. ></v-select>
  1057. </div>
  1058. <div class="mt-4" v-else>
  1059. <v-text-field v-model="setting.val" v-bind="setting" :label="setting.name" :hint="setting.desc"></v-text-field>
  1060. </div>
  1061. </template>
  1062. </v-form>
  1063. <v-divider></v-divider>
  1064. <v-card-actions>
  1065. <v-spacer></v-spacer>
  1066. <v-btn small text color="primary" @click="saveAppSettings">{{ $t('base.dialog.save') }}</v-btn>
  1067. </v-card-actions>
  1068. </template>
  1069. </v-card>
  1070. <v-card class="mx-auto" v-if="curapp.datas && curapp.datas.length > 0">
  1071. <v-subheader>
  1072. {{ $t('appDetail.curSession') }}
  1073. <a class="ml-2">{{curapp.curSession ? curapp.curSession.name : ''}}</a>
  1074. <v-spacer></v-spacer>
  1075. <v-menu bottom left>
  1076. <template #activator="{ on }">
  1077. <v-btn icon v-on="on"><v-icon>mdi-dots-vertical</v-icon></v-btn>
  1078. </template>
  1079. <v-list dense>
  1080. <v-list-item @click="copy(JSON.stringify(curapp))">
  1081. <v-list-item-title>{{ $t('base.cmd.cp') }}</v-list-item-title>
  1082. </v-list-item>
  1083. <v-dialog v-model="ui.impAppDatasDialog.show">
  1084. <template #activator="{ on }">
  1085. <v-list-item v-on="on">
  1086. <v-list-item-title>{{ $t('base.cmd.imp') }}</v-list-item-title>
  1087. </v-list-item>
  1088. </template>
  1089. <v-card>
  1090. <v-card-title> {{ $t('appDetail.impDialog.title') }} </v-card-title>
  1091. <v-divider></v-divider>
  1092. <v-card-text>
  1093. <v-textarea
  1094. v-model="ui.impAppDatasDialog.impval"
  1095. rows="3"
  1096. clearable
  1097. autofocus
  1098. :label="$t('appDetail.impDialog.data')"
  1099. :hint="$t('appDetail.impDialog.dataDesc')"
  1100. ></v-textarea>
  1101. </v-card-text>
  1102. <v-divider></v-divider>
  1103. <v-card-actions>
  1104. <v-spacer></v-spacer>
  1105. <v-btn text small color="grey" text @click="ui.impAppDatasDialog.show = false">
  1106. {{ $t('base.dialog.close') }}
  1107. </v-btn>
  1108. <v-btn text small color="primary" text @click="impAppDatas()" :loading="isLoading">
  1109. {{ $t('base.cmd.imp') }}
  1110. </v-btn>
  1111. </v-card-actions>
  1112. </v-card>
  1113. </v-dialog>
  1114. <v-list-item @click="copyData(curapp)">
  1115. <v-list-item-title>{{ $t('appDetail.copyDatas') }}</v-list-item-title>
  1116. </v-list-item>
  1117. <v-divider></v-divider>
  1118. <v-list-item @click="clearAppDatas()">
  1119. <v-list-item-title class="text-uppercase red--text">{{ $t('appDetail.clearDatas') }}</v-list-item-title>
  1120. </v-list-item>
  1121. </v-list>
  1122. </v-menu>
  1123. </v-subheader>
  1124. <v-list-item two-line dense v-for="(data, dataIdx) in curapp.datas" :key="dataIdx">
  1125. <v-list-item-content>
  1126. <v-list-item-title>{{data.key}}</v-list-item-title>
  1127. <v-list-item-subtitle>{{data.val ? data.val : $t('appDetail.noDatas')}}</v-list-item-subtitle>
  1128. </v-list-item-content>
  1129. <v-list-item-action>
  1130. <v-btn icon @click.stop="clearAppDatas(data.key)">
  1131. <v-icon color="grey">mdi-close</v-icon>
  1132. </v-btn>
  1133. </v-list-item-action>
  1134. </v-list-item>
  1135. <v-divider></v-divider>
  1136. <v-card-actions>
  1137. <v-spacer></v-spacer>
  1138. <v-btn small text color="primary" @click="saveAppSession">{{ $t('base.cmd.duplicate') }}</v-btn>
  1139. </v-card-actions>
  1140. </v-card>
  1141. <v-card :id="session.id" class="ml-10 mt-4" v-for="(session, sessionIdx) in curapp.sessions" :key="session.id">
  1142. <v-subheader>
  1143. <a v-if="curapp.curSession && curapp.curSession.id === session.id">#{{sessionIdx + 1}} {{session.name}}</a>
  1144. <template v-else>#{{sessionIdx + 1}} {{session.name}}</template>
  1145. <v-spacer></v-spacer>
  1146. <v-menu bottom left>
  1147. <template #activator="{ on }">
  1148. <v-btn icon v-on="on"><v-icon>mdi-dots-vertical</v-icon></v-btn>
  1149. </template>
  1150. <v-list dense>
  1151. <v-dialog v-model="ui.modSessionDialog.show">
  1152. <template #activator="{ on }">
  1153. <v-list-item v-on="on">
  1154. <v-list-item-title>{{ $t('base.cmd.mod') }}</v-list-item-title>
  1155. </v-list-item>
  1156. </template>
  1157. <v-card>
  1158. <v-card-title>{{ $t('appDetail.sessionEditor.title') }}</v-card-title>
  1159. <v-divider></v-divider>
  1160. <v-card-text>
  1161. <v-text-field class="mt-4" :label="$t('appDetail.sessionEditor.name')" v-model="session.name"></v-text-field>
  1162. <v-text-field
  1163. v-for="(data, dataIdx) in session.datas"
  1164. :key="dataIdx"
  1165. v-model="data.val"
  1166. :label="data.key"
  1167. ></v-text-field>
  1168. </v-card-text>
  1169. <v-divider></v-divider>
  1170. <v-card-actions>
  1171. <v-spacer></v-spacer>
  1172. <v-btn text small color="grey" text @click="ui.modSessionDialog.show = false"
  1173. >{{ $t('base.dialog.close') }}</v-btn
  1174. >
  1175. <v-btn text small color="primary" text @click="updateAppSession(session)" :loading="isLoading"
  1176. >{{ $t('base.dialog.save') }}</v-btn
  1177. >
  1178. </v-card-actions>
  1179. </v-card>
  1180. </v-dialog>
  1181. <v-divider></v-divider>
  1182. <v-list-item @click="delAppSession(session.id)">
  1183. <v-list-item-title>{{ $t('base.cmd.del') }}</v-list-item-title>
  1184. </v-list-item>
  1185. </v-list>
  1186. </v-menu>
  1187. </v-subheader>
  1188. <v-list-item two-line dense v-for="(data, dataIdx) in session.datas" :key="dataIdx">
  1189. <v-list-item-content>
  1190. <v-list-item-title>{{data.key}}</v-list-item-title>
  1191. <v-list-item-subtitle>{{data.val ? data.val : $t('appDetail.noDatas')}}</v-list-item-subtitle>
  1192. </v-list-item-content>
  1193. </v-list-item>
  1194. <v-divider></v-divider>
  1195. <v-card-actions>
  1196. <v-btn small text color="grey">{{dayjs(session.createTime).format('YYYY-MM-DD HH:mm:ss')}}</v-btn>
  1197. <v-spacer></v-spacer>
  1198. <v-btn small text color="primary" @click="useAppSession(session.id)">{{ $t('base.dialog.apply') }}</v-btn>
  1199. </v-card-actions>
  1200. </v-card>
  1201. </v-container>
  1202. <!-- 备份详情 -->
  1203. <v-container fluid v-else-if="view === 'bak' && !!curbak">
  1204. <v-subheader>
  1205. <h2 :style="appTitleStyle">{{curbak.name}}</h2>
  1206. <v-spacer></v-spacer>
  1207. <v-btn color="primary" small @click="revertGlobalBak"> {{ $t('base.cmd.recovery') }} </v-btn>
  1208. </v-subheader>
  1209. <v-card class="mb-4">
  1210. <v-subheader> {{ $t('bakDetail.title') }} </v-subheader>
  1211. <v-card-text>
  1212. <v-text-field :label="$t('bakDetail.id')" v-model="curbak.id" readonly> </v-text-field>
  1213. <v-text-field :label="$t('bakDetail.name')" v-model="curbak.name" @change="updateGlobalBak"> </v-text-field>
  1214. </v-card-text>
  1215. <v-divider></v-divider>
  1216. <v-card-actions>
  1217. <v-spacer></v-spacer>
  1218. <v-btn small text color="error" @click="delGlobalBak">{{ $t('base.cmd.del') }}</v-btn>
  1219. </v-card-actions>
  1220. </v-card>
  1221. <v-card class="mb-4">
  1222. <v-subheader>
  1223. {{ $t('bakDetail.dataTitle') }}
  1224. <v-spacer></v-spacer>
  1225. <v-btn color="primary" small @click="copy(JSON.stringify(curbak.bak))"> {{ $t('base.cmd.cp') }} </v-btn>
  1226. </v-subheader>
  1227. </v-card>
  1228. </v-container>
  1229. <!-- 计算器 -->
  1230. <v-container fluid v-else-if="view === 'calculator'">
  1231. <h3>Surge 费用计算器(open AI 编写)</h3>
  1232. <div class="text-body-2 grey--text text--darken-2">仅供参考,最终价格以实际为准</div>
  1233. <div class="mt-4">
  1234. <v-text-field v-model="purchaseDate" label="购买日期" type="date"></v-text-field>
  1235. <v-select v-model="licenseType" :items="licenseTypes" label="设备授权数"></v-select>
  1236. <v-btn @click="calculateCost">计算费用</v-btn>
  1237. <p class="mt-2">费用:{{ cost.toFixed(2) }}</p>
  1238. </div>
  1239. </v-container>
  1240. </v-main>
  1241. <!-- 底部 -->
  1242. <v-bottom-navigation ref="naviBar" v-bind="naviBarBind" v-touch="{ down: () => isHidedNaviBottom = true }">
  1243. <v-progress-linear :active="isLoading" height="2" absolute top indeterminate></v-progress-linear>
  1244. <v-btn @click="switchView('')" value="">{{ $t('menus.home') }}<v-icon>mdi-home</v-icon></v-btn>
  1245. <v-btn @click="switchView('app')" value="app">{{ $t('menus.apps') }}<v-icon>mdi-application</v-icon></v-btn>
  1246. <v-btn @click="switchView('sub')" value="sub">{{ $t('menus.subs') }}<v-icon>mdi-database</v-icon></v-btn>
  1247. <v-btn @click="switchView('my')" value="my">
  1248. <template v-if="myIcon">
  1249. <span v-if="!isHideMyTitle">{{ $t('menus.profile') }}</span>
  1250. <v-avatar :size="isHideMyTitle ? 36 : 24"><v-img :src="myIcon" /></v-avatar>
  1251. </template>
  1252. <template v-else>
  1253. <span v-if="!isHideMyTitle">{{ $t('menus.profile') }}</span>
  1254. <v-icon :size="isHideMyTitle ? 36 : 24">mdi-face-profile</v-icon>
  1255. </template>
  1256. </v-btn>
  1257. </v-bottom-navigation>
  1258. <v-fab-transition>
  1259. <v-speed-dial
  1260. v-show="!box.usercfgs.isHideBoxIcon && !isWallpaperMode"
  1261. direction="top"
  1262. fixed
  1263. fab
  1264. bottom
  1265. :left="box.usercfgs.isLeftBoxIcon"
  1266. :right="!box.usercfgs.isLeftBoxIcon === true"
  1267. >
  1268. <template #activator>
  1269. <v-btn
  1270. fab
  1271. text
  1272. @dblclick="reload()"
  1273. v-touch="{
  1274. left: () => box.usercfgs.isLeftBoxIcon = true,
  1275. right: () => box.usercfgs.isLeftBoxIcon = false,
  1276. up: () => {
  1277. clearWallpaper()
  1278. setWallpaper()
  1279. },
  1280. down: () => {
  1281. isWallpaperMode = !!!isWallpaperMode
  1282. changeWallpaper()
  1283. }
  1284. }"
  1285. >
  1286. <v-avatar><img :src="box.syscfgs.boxjs.icons[iconThemeIdx]" /></v-avatar>
  1287. </v-btn>
  1288. </template>
  1289. <v-btn dark v-if="!box.usercfgs.isHideHelp" fab small color="grey" @click="open('https://chavyleung.gitbook.io/boxjs')">
  1290. <v-icon>mdi-help</v-icon>
  1291. </v-btn>
  1292. <v-btn dark v-if="!box.usercfgs.isHideHelp" fab small color="purple" @click="ui.versionSheet.show = true">
  1293. <v-icon>mdi-new-box</v-icon>
  1294. </v-btn>
  1295. <v-btn dark v-if="!box.usercfgs.isCalculator" fab small color="yellow" @click="switchView('calculator')">
  1296. <v-icon>mdi-calculator-variant-outline</v-icon>
  1297. </v-btn>
  1298. <v-btn dark fab small color="pink" @click="box.usercfgs.isLeftBoxIcon = !box.usercfgs.isLeftBoxIcon">
  1299. <v-icon> {{box.usercfgs.isLeftBoxIcon ? 'mdi-format-horizontal-align-right' : 'mdi-format-horizontal-align-left'}} </v-icon>
  1300. </v-btn>
  1301. <v-btn dark v-if="!box.usercfgs.isHideRefresh" fab small color="orange" @click="reload()">
  1302. <v-icon>mdi-refresh</v-icon>
  1303. </v-btn>
  1304. <v-btn dark v-if="!box.usercfgs.isHideCoding" fab small @click="switchView('coding')">
  1305. <v-icon>mdi-code-tags</v-icon>
  1306. </v-btn>
  1307. <v-btn dark v-if="!box.usercfgs.isHidedSearch" fab small color="green" @click="ui.searchDialog.show = true">
  1308. <v-icon>mdi-magnify</v-icon>
  1309. </v-btn>
  1310. </v-speed-dial>
  1311. </v-fab-transition>
  1312. <v-bottom-sheet v-model="ui.versionSheet.show" scrollable fullscreen>
  1313. <v-card v-if="box.versions">
  1314. <v-subheader v-touch="{ down: () => ui.versionSheet.show = false }">
  1315. <v-btn icon small @click="open('https://chavyleung.gitbook.io/boxjs/base/upgrade')">
  1316. <v-icon>mdi-help-circle</v-icon>
  1317. </v-btn>
  1318. <v-spacer></v-spacer>
  1319. <template v-if="hasNewVersion">
  1320. <v-btn text small v-if="env.id === 'Loon'" @click="update('loon://update?sub=all')"
  1321. >{{ $t('versionSheet.updateButton') }}</v-btn
  1322. >
  1323. <v-btn
  1324. text
  1325. small
  1326. v-else-if="env.id === 'QuanX'"
  1327. @click="update('quantumult-x:///add-resource?remote-resource=%7B%22rewrite_remote%22%3A%5B%22https%3A%2F%2Fgithub.com%2Fchavyleung%2Fscripts%2Fraw%2Fmaster%2Fbox%2Frewrite%2Fboxjs.rewrite.quanx.conf%2Ctag%3Dboxjs%22%5D%7D')"
  1328. >{{ $t('versionSheet.updateButton') }}</v-btn
  1329. >
  1330. </template>
  1331. <template v-else>
  1332. <v-btn text small>{{ $t('versionSheet.versionButton') }}</v-btn>
  1333. </template>
  1334. <v-spacer></v-spacer>
  1335. <v-btn icon small @click="ui.versionSheet.show = false">
  1336. <v-icon>mdi-chevron-double-down</v-icon>
  1337. </v-btn>
  1338. </v-subheader>
  1339. <v-divider></v-divider>
  1340. <v-card-text style="height: 80%">
  1341. <div class="mt-6" v-for="(ver, verIdx) in box.versions">
  1342. <h2 :class="version === ver.version ? 'primary--text' : undefined">v{{ver.version}}</h2>
  1343. <div class="pl-4 pt-2" v-for="(note, noteIdx) in ver.notes">
  1344. <strong>{{note.name}}</strong>
  1345. <ul>
  1346. <li v-for="(desc, descIdx) in note.descs">{{desc}}</li>
  1347. </ul>
  1348. </div>
  1349. </div>
  1350. </v-card-text>
  1351. <v-dialog persistent v-model="ui.reloadDialog.show">
  1352. <v-card>
  1353. <v-card-title>{{ $t('reloadDialog.title') }}</v-card-title>
  1354. <v-card-text>{{ $t('reloadDialog.text') }}</v-card-text>
  1355. <v-card-actions>
  1356. <v-spacer></v-spacer>
  1357. <v-btn text small color="grey" @click="ui.reloadDialog.show = false">{{ $t('base.dialog.close') }}</v-btn>
  1358. <v-btn text small color="primary" @click="reload()" :loading="isLoading">{{ $t('reloadDialog.reload') }}</v-btn>
  1359. </v-card-actions>
  1360. </v-card>
  1361. </v-dialog>
  1362. </v-card>
  1363. </v-bottom-sheet>
  1364. <v-bottom-sheet v-model="ui.exeScriptSheet.show" scrollable fullscreen>
  1365. <v-card>
  1366. <v-card-title v-touch="{ down: () => ui.exeScriptSheet.show = false }">
  1367. 执行结果
  1368. <v-spacer></v-spacer>
  1369. <v-btn icon @click="ui.exeScriptSheet.show = false">
  1370. <v-icon>mdi-chevron-double-down</v-icon>
  1371. </v-btn>
  1372. </v-card-title>
  1373. <v-divider></v-divider>
  1374. <v-card-text style="height: 80%">
  1375. <div class="mt-4" v-if="ui.exeScriptSheet.resp">
  1376. <p class="text-pre-wrap" v-if="ui.exeScriptSheet.resp.exception" v-text="ui.exeScriptSheet.resp.exception"></p>
  1377. <p class="text-pre-wrap" v-else-if="ui.exeScriptSheet.resp.output" v-text="ui.exeScriptSheet.resp.output"></p>
  1378. <p v-else v-text="JSON.stringify(ui.exeScriptSheet.resp)"></p>
  1379. </div>
  1380. </v-card-text>
  1381. </v-card>
  1382. </v-bottom-sheet>
  1383. </v-app>
  1384. </div>
  1385. <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js"></script>
  1386. <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue-i18n.min.js"></script>
  1387. <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.min.js"></script>
  1388. <script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>
  1389. <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js"></script>
  1390. <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/dayjs.min.js"></script>
  1391. <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/timeago.min.js"></script>
  1392. <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/uuidv4.min.js"></script>
  1393. <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue-clipboard.min.js"></script>
  1394. <script src="https://cdn.jsdelivr.net/npm/[email protected]/min/vs/loader.js"></script>
  1395. <script>
  1396. Vue.prototype.timeago = timeago
  1397. Vue.prototype.dayjs = dayjs
  1398. new Vue({
  1399. el: '#app',
  1400. vuetify: new Vuetify(),
  1401. i18n: new VueI18n({
  1402. locale: 'zh-CN',
  1403. messages: {
  1404. 'en-US': {
  1405. base: {
  1406. dialog: {
  1407. apply: 'Apply',
  1408. save: 'Save',
  1409. view: 'View',
  1410. close: 'Close',
  1411. ok: 'OK'
  1412. },
  1413. sort: {
  1414. up: 'Up',
  1415. dn: 'Down'
  1416. },
  1417. cmd: {
  1418. cp: 'Copy',
  1419. del: 'Delete',
  1420. imp: 'Import',
  1421. exp: 'Export',
  1422. mod: 'Modify',
  1423. share: 'Share',
  1424. recovery: 'Recovery',
  1425. duplicate: 'Duplicate'
  1426. }
  1427. },
  1428. menus: {
  1429. home: 'Home',
  1430. apps: 'Applications',
  1431. subs: 'Subscriptions',
  1432. profile: 'Profile'
  1433. },
  1434. apps: {
  1435. fav: 'Favorites',
  1436. unStar: 'Unstar',
  1437. sysApps: 'System Applications'
  1438. },
  1439. appDetail: {
  1440. scripts: 'Scripts',
  1441. settings: 'Settings',
  1442. curSession: 'Current Session',
  1443. copyDatas: 'Copy Datas',
  1444. clearDatas: 'Clear Datas',
  1445. noDatas: 'No datas',
  1446. impDialog: {
  1447. title: 'Import Session',
  1448. data: 'Session Data (JSON)',
  1449. dataDesc: 'You can get session data via `Current Session` more > Copy'
  1450. }
  1451. },
  1452. subs: {
  1453. addDialog: {
  1454. title: 'Add Subscription',
  1455. name: 'Session Name',
  1456. url: 'Subscription url',
  1457. urlDesc: 'https://raw.githubusercontent.com/chavyleung/scripts/master/box/chavy.boxjs.json'
  1458. },
  1459. sessionEditor: {
  1460. title: 'Modify Session',
  1461. name: 'Name',
  1462. nameDesc: 'Leave your name',
  1463. avatar: 'Avatar (Optional)',
  1464. avatarDesc: 'Your avatar link'
  1465. },
  1466. install: 'Install',
  1467. add: 'Add Subscription',
  1468. subs: 'More Subscriptions',
  1469. appSubs: 'App Subscriptions',
  1470. errData: 'Error Data',
  1471. updated: 'Updated',
  1472. repo: 'Repository'
  1473. },
  1474. prefs: {
  1475. appearance: 'Appearance',
  1476. appearances: {
  1477. auto: 'Auto',
  1478. dark: 'Dark',
  1479. light: 'Light'
  1480. },
  1481. background: 'Background',
  1482. icon: 'Dark icon',
  1483. iconDesc: 'Available in dark mode',
  1484. bgMode: 'Backgroud Mode',
  1485. bgModeDesc: 'Hides bars & icons',
  1486. hideTopBar: 'Hide TopBar',
  1487. hideTopBarDesc: 'Restore via sidebar',
  1488. autoTopBar: 'Auto TopBar',
  1489. autoTopBarDesc: 'Hides when scrolling',
  1490. hideBottomBar: 'Hide BottomBar',
  1491. hideBottomBarDesc: 'Restore via sidebar',
  1492. autoBottomBar: 'Auto BottomBar',
  1493. autoBottomBarDesc: 'Hides when scrolling',
  1494. muteMode: 'Do Not Disturb',
  1495. muteModeDesc: 'Disable notifications',
  1496. hideHelp: 'Hide Help',
  1497. hideHelpDesc: 'Hides help button',
  1498. hideBoxJs: 'Hide BoxJs',
  1499. hideBoxJsDesc: 'Hides BoxJs button',
  1500. hideProfileTitle: 'Hide Profile Title',
  1501. hideProfileTitleDesc: 'Show avatar only',
  1502. hideCodding: 'Hide Codding',
  1503. hideCoddingDesc: 'Hides codding button',
  1504. hideReload: 'Hide Reload',
  1505. hideReloadDesc: 'Reload by double tap BoxJs',
  1506. debugMode: 'Debug Mode',
  1507. debugModeDesc: 'No page caches',
  1508. debugPage: 'Debug Page Addr',
  1509. debugPageDesc: 'Load page from...'
  1510. },
  1511. profile: {
  1512. leaveName: 'Leave a name',
  1513. dataviewer: 'Data Viewer',
  1514. editor: {
  1515. title: 'Profile',
  1516. name: 'Name',
  1517. nameDesc: 'Leave your name',
  1518. avatar: 'Avatar (Optional)',
  1519. avatarDesc: 'Your avatar link'
  1520. },
  1521. impDialog: {
  1522. title: 'Import Backup',
  1523. impData: 'Backup Data',
  1524. impDataDesc: ''
  1525. },
  1526. datas: 'Datas',
  1527. apps: 'Apps',
  1528. subs: 'Subs',
  1529. sessions: 'Sessions',
  1530. imp: 'Import',
  1531. bak: 'Backup',
  1532. bakName: 'Global Backup'
  1533. },
  1534. bakDetail: {
  1535. note: 'Note: ',
  1536. id: 'Backup Index',
  1537. name: 'Backup Name',
  1538. title: 'Backup Informations',
  1539. dataTitle: 'Backup Datas'
  1540. },
  1541. codding: {
  1542. title: 'Script Editor'
  1543. },
  1544. viewer: {
  1545. dataViewer: 'Data Viewer',
  1546. dataKey: 'Data Key',
  1547. dataKeyDesc: 'Input the data key',
  1548. dataEditor: 'Data Editor',
  1549. dataVal: 'Data Value'
  1550. },
  1551. reloadDialog: {
  1552. title: 'Next',
  1553. text: 'Reload page?',
  1554. reload: 'GO'
  1555. },
  1556. versionSheet: {
  1557. updateButton: 'UPDATE NOW',
  1558. versionButton: 'NEW VERSION'
  1559. }
  1560. },
  1561. 'zh-CN': {
  1562. base: {
  1563. dialog: {
  1564. apply: '应用',
  1565. save: '保存',
  1566. close: '关闭',
  1567. ok: '好'
  1568. },
  1569. sort: {
  1570. up: '上移',
  1571. dn: '下移'
  1572. },
  1573. cmd: {
  1574. cp: '复制',
  1575. del: '删除',
  1576. imp: '导入',
  1577. mod: '修改',
  1578. share: '分享',
  1579. recovery: '恢复',
  1580. duplicate: '克隆'
  1581. }
  1582. },
  1583. menus: {
  1584. home: '主页',
  1585. apps: '应用',
  1586. subs: '订阅',
  1587. profile: '我的'
  1588. },
  1589. apps: {
  1590. fav: '收藏应用',
  1591. unStar: '取消收藏',
  1592. sysApps: '内置应用'
  1593. },
  1594. appDetail: {
  1595. scripts: '应用脚本',
  1596. settings: '应用设置',
  1597. curSession: '当前会话',
  1598. copyDatas: '复制数据',
  1599. clearDatas: '清除数据',
  1600. noDatas: '无数据',
  1601. impDialog: {
  1602. title: '导入会话',
  1603. data: '会话数据 (JSON)',
  1604. dataDesc: '你可通过 `当前会话` 更多 > 复制 来获得会话数据'
  1605. }
  1606. },
  1607. subs: {
  1608. addDialog: {
  1609. title: '添加订阅',
  1610. name: 'Session Name',
  1611. url: '订阅地址',
  1612. urlDesc: 'https://raw.githubusercontent.com/chavyleung/scripts/master/box/chavy.boxjs.json'
  1613. },
  1614. sessionEditor: {
  1615. title: '修改会话',
  1616. name: '会话名称'
  1617. },
  1618. install: '安装',
  1619. add: '添加订阅',
  1620. moreSubs: '更多订阅',
  1621. appSubs: '应用订阅',
  1622. errData: '格式错误',
  1623. updated: '更新于',
  1624. repo: '仓库'
  1625. },
  1626. prefs: {
  1627. appearance: '外观',
  1628. appearances: {
  1629. auto: '自动',
  1630. dark: '暗黑',
  1631. light: '明亮'
  1632. },
  1633. background: '背景图标',
  1634. icon: '透明图标',
  1635. iconDesc: '明亮主题下强制使用彩色图标',
  1636. bgMode: '壁纸模式',
  1637. bgModeDesc: '同时隐藏顶栏、底栏、图标',
  1638. hideTopBar: '隐藏顶栏',
  1639. hideTopBarDesc: '通过侧栏恢复',
  1640. autoTopBar: '自动顶栏',
  1641. autoTopBarDesc: '滚动时自动隐藏',
  1642. hideBottomBar: '隐藏底栏',
  1643. hideBottomBarDesc: '通过侧栏恢复',
  1644. autoBottomBar: '自动底栏',
  1645. autoBottomBarDesc: '滚动时自动隐藏',
  1646. muteMode: '勿扰模式',
  1647. muteModeDesc: '不发出通知 (仍记日志)',
  1648. hideHelp: '隐藏帮助按钮',
  1649. hideHelpDesc: '隐藏帮助按钮',
  1650. hideBoxJs: '隐藏悬浮按钮',
  1651. hideBoxJsDesc: '隐藏右下角 BoxJs 悬浮按钮',
  1652. hideProfileTitle: '隐藏我的标题',
  1653. hideProfileTitleDesc: '只显示头像',
  1654. hideCodding: '隐藏编码按钮',
  1655. hideCoddingDesc: 'Hides script editor entrance',
  1656. hideReload: '隐藏刷新按钮',
  1657. hideReloadDesc: '仍可双击悬浮按钮刷新页面',
  1658. debugMode: '调试模式',
  1659. debugModeDesc: '每次请求都获取最新的页面',
  1660. debugPage: '调试页面地址',
  1661. debugPageDesc: '页面源码的获取地址'
  1662. },
  1663. profile: {
  1664. leaveName: '大侠, 请留名!',
  1665. dataviewer: '数据查看器',
  1666. editor: {
  1667. title: '个人资源',
  1668. name: '昵称',
  1669. nameDesc: '大侠, 请留名!',
  1670. avatar: '头像 (可选)',
  1671. avatarDesc: '头像链接, 建议从 Github 获取'
  1672. },
  1673. impDialog: {
  1674. title: 'Import Backup',
  1675. impData: 'Backup Data',
  1676. impDataDesc: ''
  1677. },
  1678. datas: '我的数据',
  1679. apps: '应用',
  1680. subs: '订阅',
  1681. sessions: '会话',
  1682. imp: '导入',
  1683. bak: '备份',
  1684. bakName: '全局备份'
  1685. },
  1686. bakDetail: {
  1687. id: '备份索引',
  1688. name: '备份名称',
  1689. title: '备份信息',
  1690. dataTitle: '备份数据'
  1691. },
  1692. codding: {
  1693. title: '脚本编辑器'
  1694. },
  1695. viewer: {
  1696. dataViewer: '数据查看器',
  1697. dataKey: '数据键 (Key)',
  1698. dataKeyDesc: '输入要查询的数据键, 如: boxjs_host',
  1699. dataEditor: '数据编辑器',
  1700. dataVal: '数据内容'
  1701. },
  1702. reloadDialog: {
  1703. title: '接下来',
  1704. text: '更新完成, 需要刷新页面吗?',
  1705. reload: '马上刷新'
  1706. },
  1707. versionSheet: {
  1708. updateButton: ' 立即更新',
  1709. versionButton: '新版本'
  1710. }
  1711. }
  1712. }
  1713. }),
  1714. data() {
  1715. return {
  1716. ui: {
  1717. // 请求类
  1718. isCors: false, // 是否需要发起跨域请求
  1719. // 路径类
  1720. path: null,
  1721. bfpath: null,
  1722. view: null,
  1723. bfview: null,
  1724. subview: null,
  1725. bfsubview: null,
  1726. // 数据类
  1727. collaborators: [],
  1728. contributors: [],
  1729. isSaveUserCfgs: false,
  1730. // 界面类
  1731. scrollY: {}, //记录每个界面的滚动值
  1732. overlay: { show: false, val: 60 },
  1733. snackbar: { show: false, color: 'primary', msg: '' },
  1734. searchBar: {
  1735. isActive: true,
  1736. color: 'primary',
  1737. class: 'rounded-xl',
  1738. readonly: true,
  1739. input: '',
  1740. hideNoData: true,
  1741. hideDetails: true,
  1742. solo: true
  1743. },
  1744. viewer: {
  1745. key: '',
  1746. val: ''
  1747. },
  1748. searchDialog: { show: false },
  1749. versionSheet: { show: false },
  1750. updatesheet: { show: false },
  1751. exeScriptSheet: { show: false, resp: null },
  1752. naviDrawer: { show: false },
  1753. reloadDialog: { show: false },
  1754. modSessionDialog: { show: false },
  1755. editProfileDialog: { show: false },
  1756. impGlobalBakDialog: { show: false, impval: '' },
  1757. impAppDatasDialog: { show: false, impval: '' },
  1758. addAppSubDialog: { show: false, url: '' },
  1759. installConfirmDialog: { show: false, title: '安装确认', message: '是否自动安装外部资源?' },
  1760. defaultIcons: [
  1761. 'https://raw.githubusercontent.com/Orz-3/mini/master/appstore.png',
  1762. 'https://raw.githubusercontent.com/Orz-3/task/master/appstore.png'
  1763. ]
  1764. },
  1765. boxServerData: null,
  1766. box: null,
  1767. purchaseDate: null,
  1768. cost: 0,
  1769. licenseTypes: ["1 Device License", "3 Devices License", "5 Devices License"],
  1770. licenseType: ''
  1771. }
  1772. },
  1773. computed: {
  1774. // 获取当前版本
  1775. version() {
  1776. return this.box.syscfgs.version
  1777. },
  1778. // 标题
  1779. title() {
  1780. const isDebugWeb = this.box.usercfgs.isDebugWeb
  1781. const debugger_web = this.box.usercfgs.debugger_web
  1782. const isDebugMode = this.box.syscfgs.isDebugMode
  1783. return `BoxJs - v${this.version}${isDebugMode ? ` - ${debugger_web}` : ''}`
  1784. },
  1785. // 判断是否有新版本
  1786. hasNewVersion() {
  1787. const curver = this.box.syscfgs.version
  1788. const vers = this.box.versions
  1789. if (curver && vers && vers.length > 0) {
  1790. const lastestVer = vers[0].version
  1791. return this.compareVersion(lastestVer, curver) > 0
  1792. }
  1793. },
  1794. timeagoLang() {
  1795. const lang = this.box.usercfgs.lang
  1796. return lang ? lang.replace('-', '_') : 'zh_CN'
  1797. },
  1798. // 判断是否需要跨域请求
  1799. isCors() {
  1800. return this.ui.isCors
  1801. },
  1802. // 是否加载中
  1803. isLoading: {
  1804. set(val) {
  1805. this.ui.overlay.show = val
  1806. },
  1807. get() {
  1808. return this.ui.overlay.show
  1809. }
  1810. },
  1811. // 判断当前是否`WebApp`
  1812. isWebApp() {
  1813. return window.navigator.standalone
  1814. },
  1815. // 是否壁纸模式
  1816. isWallpaperMode: {
  1817. get() {
  1818. return this.box.usercfgs.isWallpaperMode
  1819. },
  1820. set(val) {
  1821. this.box.usercfgs.isWallpaperMode = val === true
  1822. }
  1823. },
  1824. // 切换壁纸
  1825. changeWallpaper() {
  1826. if (this.isWallpaperMode) {
  1827. if (this.box.usercfgs.changeBgImgEnterDefault) {
  1828. const bgUrl = this.bgimgs.find((bgimg) => bgimg.name === this.box.usercfgs.changeBgImgEnterDefault).url
  1829. if (bgUrl) {
  1830. this.box.usercfgs.bgimg = bgUrl
  1831. this.saveUserCfgs(false)
  1832. }
  1833. }
  1834. } else {
  1835. if (this.box.usercfgs.changeBgImgOutDefault) {
  1836. const bgUrl = this.bgimgs.find((bgimg) => bgimg.name === this.box.usercfgs.changeBgImgOutDefault).url
  1837. if (bgUrl || bgUrl === '') {
  1838. this.box.usercfgs.bgimg = bgUrl
  1839. this.saveUserCfgs(false)
  1840. }
  1841. }
  1842. }
  1843. },
  1844. // 当前环境: Surge、QuanX、Loon、NodeJs
  1845. env: {
  1846. // 获取当前容器环境
  1847. get() {
  1848. return this.envs.find((env) => env.id === this.box.syscfgs.env)
  1849. },
  1850. // 设置当前容器环境
  1851. set(val) {
  1852. this.box.syscfgs.env = val
  1853. }
  1854. },
  1855. // 获取容器列表
  1856. envs() {
  1857. const envs = this.box.syscfgs.envs
  1858. envs.forEach((env) => (env.icon = env.icons[this.iconThemeIdx]))
  1859. return envs
  1860. },
  1861. // 获取当前路径
  1862. path: {
  1863. get() {
  1864. return this.ui.path
  1865. },
  1866. set(path) {
  1867. this.ui.path = path
  1868. }
  1869. },
  1870. // 获取上一个路径
  1871. bfpath() {
  1872. return this.ui.bfpath || ''
  1873. },
  1874. // 获取当前页面: http://boxjs.com/app/baidu => `app`
  1875. view() {
  1876. return this.ui.view || ''
  1877. },
  1878. // 获取当前页面: http://boxjs.com/app/baidu => `baidu`
  1879. subview() {
  1880. return this.ui.subview ? this.ui.subview : ''
  1881. },
  1882. // 判断当前是否`主页面` (非二级页面)
  1883. isMainView() {
  1884. return !this.subview
  1885. },
  1886. // 判断当前是否`暗黑模式`
  1887. isDarkMode() {
  1888. let isDark = true
  1889. const theme = this.box.usercfgs.theme
  1890. if (theme === 'auto') {
  1891. isDark = this.isSystemDarkMode
  1892. } else if (theme === 'light') {
  1893. isDark = false
  1894. }
  1895. return isDark
  1896. },
  1897. // 判断系统是否`暗黑模式`
  1898. isSystemDarkMode() {
  1899. return window.matchMedia('(prefers-color-scheme: dark)').matches
  1900. },
  1901. // 是否透明图标
  1902. isTransparentIcons() {
  1903. return this.box.usercfgs.isTransparentIcons
  1904. },
  1905. // 获取图标下标, 透明: 0, 彩色: 1 (默认)
  1906. iconThemeIdx() {
  1907. if (this.isDarkMode) {
  1908. return this.isTransparentIcons ? 0 : 1
  1909. }
  1910. return 1
  1911. },
  1912. // 获取环境图标下标, 透明: 0, 彩色: 1 (默认)
  1913. iconEnvThemeIdx() {
  1914. return this.isDarkMode ? 0 : 1
  1915. },
  1916. isHidedSearchBar: {
  1917. get() {
  1918. return this.box.usercfgs.isHidedSearchBar || this.isWallpaperMode
  1919. },
  1920. set(val) {
  1921. this.box.usercfgs.isHidedSearchBar = val === true
  1922. }
  1923. },
  1924. isAutoSearchBar: {
  1925. get() {
  1926. return this.box.usercfgs.isAutoSearchBar
  1927. },
  1928. set(val) {
  1929. this.box.usercfgs.isAutoSearchBar = val === true
  1930. if (val === false && !this.isHidedSearchBar) {
  1931. this.$refs.appBar.isActive = true
  1932. }
  1933. }
  1934. },
  1935. isHidedAppIcons: {
  1936. get() {
  1937. return this.box.usercfgs.isHidedAppIcons || this.isWallpaperMode
  1938. },
  1939. set(val) {
  1940. this.box.usercfgs.isHidedAppIcons = val === true
  1941. }
  1942. },
  1943. isHidedNaviBottom: {
  1944. get() {
  1945. return this.box.usercfgs.isHidedNaviBottom || this.isWallpaperMode
  1946. },
  1947. set(val) {
  1948. this.box.usercfgs.isHidedNaviBottom = val === true
  1949. }
  1950. },
  1951. isAutoNaviBottom: {
  1952. get() {
  1953. return this.box.usercfgs.isAutoNaviBottom
  1954. },
  1955. set(val) {
  1956. this.box.usercfgs.isAutoNaviBottom = val === true
  1957. if (val === false && !this.isHidedNaviBottom) {
  1958. this.$refs.naviBar.isActive = true
  1959. }
  1960. }
  1961. },
  1962. // 判断是否有壁纸
  1963. isWallpaper() {
  1964. return !!this.box.usercfgs.bgimg
  1965. },
  1966. // 是否存在多张壁纸
  1967. isMutiWallpaper() {
  1968. return this.bgimgs && this.bgimgs.length > 2
  1969. },
  1970. // 背景图片列表
  1971. bgimgs() {
  1972. const items = []
  1973. const bgimgs = this.box.usercfgs.bgimgs
  1974. if (bgimgs) {
  1975. bgimgs.split('\n').forEach((img) => {
  1976. const [name, url] = img.split(',')
  1977. items.push({ name, url })
  1978. })
  1979. }
  1980. return items
  1981. },
  1982. // 样式
  1983. appViewStyle() {
  1984. // 主题发生变化时给 <body> 设置背景色
  1985. if (this.isWallpaper) {
  1986. this.setWallpaper()
  1987. } else {
  1988. this.clearWallpaper()
  1989. const darkBg = `background: #121212;`
  1990. const lightWebappBg = `background-image: linear-gradient(to bottom,rgba(0,0,0,.2) 0,transparent 76px);`
  1991. const lightBg = `${this.isWebApp ? lightWebappBg : 'background: #fff;'}`
  1992. document.querySelector('#BG').setAttribute('style', this.isDarkMode ? darkBg : lightBg)
  1993. }
  1994. if (this.isWebApp) {
  1995. return { background: 'none' }
  1996. } else if (this.isWallpaper && !this.isTransparent) {
  1997. return { background: 'transparent' }
  1998. } else if (!this.isWallpaper && this.isTransparent) {
  1999. return { background: 'none' }
  2000. } else {
  2001. return
  2002. }
  2003. },
  2004. appTitleStyle() {
  2005. const style = {}
  2006. if (this.isWallpaper) {
  2007. style['color'] = '#fff'
  2008. style['text-shadow'] = 'black 0.1em 0.1em 0.2em'
  2009. }
  2010. return style
  2011. },
  2012. appBarBind() {
  2013. const app = true
  2014. const isEmptyLight = this.isWebApp && !this.isDarkMode && !this.isWallpaper
  2015. const color = isEmptyLight ? 'primary' : 'transparent'
  2016. const flat = color === 'transparent'
  2017. const hideOnScroll = !this.isHidedSearchBar && this.isAutoSearchBar
  2018. const collapseOnScroll = false
  2019. const scrollThreshold = 20
  2020. return { app, color, flat, hideOnScroll, collapseOnScroll, scrollThreshold }
  2021. },
  2022. searchBarBind() {
  2023. const color = this.isDarkMode ? null : 'primary'
  2024. return { color }
  2025. },
  2026. naviBarBind() {
  2027. const app = true
  2028. const grow = true
  2029. const color = 'primary'
  2030. const value = this.view
  2031. const inputValue = !this.isHidedNaviBottom
  2032. const hideOnScroll = !this.isHidedNaviBottom && this.isAutoNaviBottom
  2033. const scrollThreshold = 160
  2034. return { app, grow, color, value, inputValue, hideOnScroll, scrollThreshold }
  2035. },
  2036. appIconFontStyle() {
  2037. const style = {
  2038. 'font-size': '10px',
  2039. 'max-width': '54px'
  2040. }
  2041. if (this.isWallpaper) {
  2042. style['color'] = '#fff'
  2043. style['text-shadow'] = 'black 0.1em 0.1em 0.2em'
  2044. }
  2045. return style
  2046. },
  2047. // 是否保存用户偏好
  2048. isSaveUserCfgs: {
  2049. set(val) {
  2050. this.ui.isSaveUserCfgs = val
  2051. },
  2052. get() {
  2053. return this.ui.isSaveUserCfgs
  2054. }
  2055. },
  2056. // 我的图标
  2057. myIcon() {
  2058. return this.box.usercfgs.icon
  2059. },
  2060. // 是否隐藏`我的`标题
  2061. isHideMyTitle() {
  2062. return this.box.usercfgs.isHideMyTitle
  2063. },
  2064. // 添加`应用订阅`对话框
  2065. addAppSubDialog: {
  2066. get() {
  2067. return this.ui.addAppSubDialog.show
  2068. },
  2069. set(show) {
  2070. this.ui.addAppSubDialog.show = show
  2071. }
  2072. },
  2073. // 获取持久化数据
  2074. datas() {
  2075. return this.box.datas
  2076. },
  2077. // 应用会话数据
  2078. sessions() {
  2079. return this.box.sessions
  2080. },
  2081. // 获取`收藏`应用
  2082. favApps() {
  2083. const favapps = []
  2084. const favAppIds = this.box.usercfgs.favapps || []
  2085. if (favAppIds) {
  2086. favAppIds.forEach((favAppId) => {
  2087. const app = this.apps.find((app) => app.id === favAppId)
  2088. if (app) {
  2089. favapps.push(app)
  2090. }
  2091. })
  2092. }
  2093. return favapps
  2094. },
  2095. // 获取`内置`应用
  2096. sysApps() {
  2097. const sysapps = this.box.sysapps || []
  2098. sysapps.forEach((app) => this.loadAppBaseInfo(app))
  2099. sysapps.sort((a, b) => a.name.localeCompare(b.name))
  2100. return sysapps
  2101. },
  2102. // 获取`订阅`应用 (注意: 这个接口是获取`应用`)
  2103. subApps() {
  2104. const apps = []
  2105. this.appSubs.forEach((appsub) => {
  2106. const sub = this.appSubCaches[appsub.url]
  2107. if (sub && sub.apps && Array.isArray(sub.apps) && !appsub.isErr) {
  2108. sub.apps.forEach((app) => {
  2109. this.loadAppBaseInfo(app)
  2110. apps.push(app)
  2111. })
  2112. }
  2113. })
  2114. return apps
  2115. },
  2116. // 获取`应用`订阅 (注意: 这个接口是获取`订阅`)
  2117. appSubs() {
  2118. // 深拷贝一份数据, 避免污染`usercfgs`
  2119. const subs = JSON.parse(JSON.stringify(this.box.usercfgs.appsubs))
  2120. subs.forEach((sub) => {
  2121. const raw = JSON.parse(JSON.stringify(sub))
  2122. const cacheSub = this.appSubCaches[sub.url]
  2123. const isValidSub = cacheSub && Array.isArray(cacheSub.apps) && cacheSub.apps.length > 0
  2124. const isValidSubApps = isValidSub && !cacheSub.apps.find((app) => !app.id)
  2125. if (cacheSub && isValidSub && isValidSubApps) {
  2126. Object.assign(sub, cacheSub)
  2127. } else {
  2128. sub.isErr = true
  2129. sub.apps = []
  2130. }
  2131. sub.name = sub.name ? sub.name : '匿名订阅'
  2132. sub.author = sub.author ? sub.author : '@anonymous'
  2133. sub.repo = sub.repo ? sub.repo : sub.url
  2134. sub.raw = raw
  2135. })
  2136. return subs
  2137. },
  2138. // 获取`订阅`缓存
  2139. appSubCaches() {
  2140. return this.box.appSubCaches
  2141. },
  2142. // 获取所有应用`内置应用`+`订阅应用`
  2143. apps() {
  2144. const apps = []
  2145. apps.push(...this.subApps)
  2146. apps.push(...this.sysApps)
  2147. return apps
  2148. },
  2149. searchApps() {
  2150. return this.apps.filter((app) => app.id.includes(this.ui.searchBar.input) || app.name.includes(this.ui.searchBar.input))
  2151. },
  2152. // 获取全局备份
  2153. baks() {
  2154. return this.box.globalbaks
  2155. },
  2156. // 当前应用
  2157. curapp() {
  2158. if (this.view === 'app' && !!this.subview) {
  2159. const appId = decodeURIComponent(decodeURIComponent(this.subview))
  2160. const app = this.apps.find((app) => app.id === appId)
  2161. this.loadAppDataInfo(app)
  2162. return app
  2163. }
  2164. },
  2165. // 当前备份
  2166. curbak() {
  2167. if (this.view === 'bak' && !!this.subview) {
  2168. const bakId = decodeURIComponent(decodeURIComponent(this.subview))
  2169. const bak = this.baks.find((bak) => bak.id === bakId)
  2170. return bak
  2171. }
  2172. }
  2173. },
  2174. watch: {
  2175. 'ui.path': {
  2176. handler(newval, oldval) {
  2177. if (/^\/#/.test(newval)) {
  2178. newval = newval.replace('/#', '')
  2179. }
  2180. const [, view, subview] = newval.split('/')
  2181. this.ui.view = view
  2182. this.ui.subview = subview
  2183. if (oldval) {
  2184. const [, bfview, bfsubview] = oldval.split('/')
  2185. this.ui.bfpath = oldval
  2186. this.ui.bfview = bfview
  2187. this.ui.bfsubview = bfsubview
  2188. }
  2189. if (newval === '/coding') {
  2190. require.config({ paths: { vs: 'https://cdn.jsdelivr.net/npm/[email protected]/min/vs' } })
  2191. require(['vs/editor/editor.main'], () => {
  2192. const envjs_demo = [
  2193. '/** ',
  2194. ' * 注意: ',
  2195. ' * 在这里你可以使用完整的 EnvJs 环境',
  2196. ' * ',
  2197. ' * 同时: ',
  2198. ' * 你`必须`手动调用 $done()',
  2199. ' * ',
  2200. ' * 因为: ',
  2201. ' * BoxJs 不为主动执行的脚本调用 $done()',
  2202. ' * 而把 $done 的时机完全交由脚本控制',
  2203. ' * ',
  2204. ' * 最后: ',
  2205. ' * 这段脚本是可以直接运行的!',
  2206. ' */ ',
  2207. 'const host = $.getdata("boxjs_host")',
  2208. 'console.log("输出的内容是返回给浏览器的!")',
  2209. '$.msg($.name, host)',
  2210. '$.done()',
  2211. '// $done() 或 $.done() 都可以'
  2212. ]
  2213. const surgejs_demo = [
  2214. '/** ',
  2215. ' * 注意: ',
  2216. ' * 你正在使用 Surge HTTP-API 环境',
  2217. ' * ',
  2218. ' * 在这里:你不可以使用 EnvJs',
  2219. ' * 请确保:你的脚本能被 Surge 独立运行',
  2220. ' * ',
  2221. ' * 最后: ',
  2222. ' * 这段脚本是可以直接运行的!',
  2223. ' */ ',
  2224. 'const host = $persistentStore.read("boxjs_host")',
  2225. 'const msgs = [""]',
  2226. '',
  2227. 'msgs.push("这是日志的内容")',
  2228. 'msgs.push("BoxJs host: " + host)',
  2229. '',
  2230. 'console.log(msgs.join("\\n"))',
  2231. '$done()'
  2232. ]
  2233. this.ui.editor = monaco.editor.create(document.getElementById('container'), {
  2234. fontSize: 12,
  2235. tabSize: 2,
  2236. value: this.env.id === 'Surge' && this.box.usercfgs.httpapi ? surgejs_demo.join('\n') : envjs_demo.join('\n'),
  2237. language: 'javascript',
  2238. minimap: { enabled: false },
  2239. theme: this.isDarkMode ? 'vs-dark' : 'vs'
  2240. })
  2241. })
  2242. }
  2243. window.onresize = () => {
  2244. if (this.ui.editor) {
  2245. this.ui.editor.layout()
  2246. }
  2247. }
  2248. // 还原视图当时的滚动值
  2249. const scrollY = this.ui.scrollY[this.path] || 0
  2250. const offsetTop = -this.$vuetify.application.top
  2251. this.$vuetify.goTo(scrollY, { duration: 0, offset: offsetTop })
  2252. }
  2253. },
  2254. 'ui.searchDialog.show': {
  2255. handler(newval) {
  2256. if (newval === false) {
  2257. this.ui.searchBar.input = ''
  2258. } else {
  2259. if (this.$refs.search) {
  2260. this.$nextTick(() => {
  2261. setTimeout(() => this.$refs.search.$refs.input.focus(), 0)
  2262. })
  2263. }
  2264. }
  2265. }
  2266. },
  2267. 'ui.naviDrawer.show': {
  2268. handler(newval, oldval) {
  2269. // 获取贡献者列表
  2270. if (_.isEmpty(this.ui.contributors)) {
  2271. this.getContributors()
  2272. }
  2273. }
  2274. },
  2275. 'box.usercfgs': {
  2276. deep: true,
  2277. handler(newval, oldval) {
  2278. if (oldval && this.isSaveUserCfgs) {
  2279. this.saveUserCfgs()
  2280. }
  2281. this.isSaveUserCfgs = true
  2282. }
  2283. },
  2284. 'box.usercfgs.lang': {
  2285. handler(newval) {
  2286. this.$i18n.locale = newval
  2287. }
  2288. },
  2289. 'box.usercfgs.theme': {
  2290. handler() {
  2291. if (this.ui.editor) {
  2292. this.ui.editor._themeService.setTheme(this.isDarkMode ? 'vs-dark' : 'vs')
  2293. }
  2294. }
  2295. },
  2296. 'box.usercfgs.color_dark_primary': {
  2297. handler() {
  2298. this.loadTheme()
  2299. }
  2300. },
  2301. 'box.usercfgs.color_light_primary': {
  2302. handler() {
  2303. this.loadTheme()
  2304. }
  2305. }
  2306. },
  2307. beforeCreate() {
  2308. // 请求&响应拦截器, 显示&隐藏加载条
  2309. axios.interceptors.request.use((cfg) => {
  2310. this.isLoading = true
  2311. return cfg
  2312. })
  2313. axios.interceptors.response.use(
  2314. (resp) => {
  2315. this.isLoading = false
  2316. return resp
  2317. },
  2318. (error) => (this.isLoading = false)
  2319. )
  2320. },
  2321. created() {
  2322. // 如果 url 参数中指定的 baseURL, 则后续的请求都请求到指定的域
  2323. if (window.location.search) {
  2324. const [, baseURL] = /baseURL=(.*?)(&|$)/.exec(window.location.search)
  2325. axios.defaults.baseURL = baseURL || ''
  2326. this.ui.isCors = true
  2327. }
  2328. // 根据路径跳转视图
  2329. const defview = '/'
  2330. if (!this.isCors) {
  2331. let path = location.pathname + location.hash
  2332. this.path = path === '/' ? defview : path
  2333. } else {
  2334. this.path = defview
  2335. }
  2336. // 监听浏览器后退事件
  2337. window.addEventListener('popstate', (e) => (this.path = e.state ? e.state.url : '/'), false)
  2338. // 如果后端没有渲染数据, 则发出请求获取数据
  2339. if (this.boxServerData) {
  2340. this.box = this.boxServerData
  2341. this.setHttpBackend()
  2342. } else {
  2343. axios.get('/query/boxdata').then((resp) => {
  2344. this.box = resp.data
  2345. this.setHttpBackend()
  2346. })
  2347. }
  2348. // 延时执行, 避免多个请求抢占资源
  2349. this.getVersions()
  2350. this.loadTheme()
  2351. },
  2352. mounted() {
  2353. const el = document.getElementById('appList')
  2354. const _this = this
  2355. const sortable = Sortable.create(el, {
  2356. animation: 600,
  2357. delay: 200,
  2358. onEnd(evt) {
  2359. const favApps = _this.box.usercfgs.favapps
  2360. const oldIdx = evt.oldIndex
  2361. const newIdx = evt.newIndex
  2362. const moveItem = favApps[oldIdx]
  2363. favApps.splice(oldIdx, 1)
  2364. favApps.splice(newIdx, 0, moveItem)
  2365. }
  2366. })
  2367. if (!this.box.usercfgs.lang) {
  2368. const locale = this.$i18n.locale
  2369. this.box.usercfgs.lang = locale === 'zh-CN' ? locale : 'en-US'
  2370. this.saveUserCfgs()
  2371. } else {
  2372. this.$i18n.locale = this.box.usercfgs.lang
  2373. }
  2374. if (this.path.includes('/#/bak/')) {
  2375. const [, backupId] = this.path.split('/#/bak/')
  2376. this.loadGlobalBak(backupId)
  2377. } else if (this.path.includes('/#/sub/add/')) {
  2378. const [, url] = this.path.split('/#/sub/add/')
  2379. this.addAppSub(decodeURIComponent(url), true)
  2380. }
  2381. },
  2382. methods: {
  2383. reload() {
  2384. this.isLoading = true
  2385. window.location.reload()
  2386. },
  2387. open(url) {
  2388. window.open(url)
  2389. },
  2390. update(url) {
  2391. this.open(url)
  2392. this.ui.versionSheet.show = false
  2393. this.ui.reloadDialog.show = true
  2394. },
  2395. openInstall(subId) {
  2396. const newsub = this.appSubs.find((s) => s.raw.id === subId)
  2397. const event = newsub.onInstall
  2398. if (event) {
  2399. const install = event.install
  2400. const redirect = install[this.env.id]
  2401. if (!redirect) return
  2402. this.ui.installConfirmDialog.show = true
  2403. this.ui.installConfirmDialog.title = event.title
  2404. this.ui.installConfirmDialog.message = event.message
  2405. this.ui.installConfirmDialog.url = redirect
  2406. }
  2407. },
  2408. install(url) {
  2409. this.open(url)
  2410. this.ui.installConfirmDialog.show = false
  2411. },
  2412. // 记录每个页面的滚动值
  2413. onScroll(event) {
  2414. const currentY = event.currentTarget.scrollY
  2415. const historyY = this.ui.scrollY[this.path]
  2416. this.ui.scrollY[this.path] = event.currentTarget.scrollY
  2417. // 下拉显示/隐藏顶栏
  2418. // 回弹时才触发显示与隐藏, 1 秒内不重复触发
  2419. if (currentY < historyY && currentY < -80 && !this.ui.isWaitToggleSearchBar) {
  2420. // 壁纸模式: 取消模式模式
  2421. if (this.isWallpaperMode) {
  2422. this.isWallpaperMode = false
  2423. }
  2424. // 非壁纸模式: 显示&隐藏顶栏
  2425. else {
  2426. this.ui.isWaitToggleSearchBar = true
  2427. this.isHidedSearchBar = !this.isHidedSearchBar
  2428. this.toggleWaitSearchBar()
  2429. }
  2430. }
  2431. },
  2432. setWallpaper() {
  2433. let bgimg = ''
  2434. if (this.box.usercfgs.bgimg === '跟随系统') {
  2435. const hasdark = this.bgimgs.find((bgimg) => bgimg.name == '暗黑' || bgimg.name == 'dark')
  2436. const haslight = this.bgimgs.find((bgimg) => bgimg.name == '明亮' || bgimg.name == 'light')
  2437. const darkbgimg = hasdark ? hasdark.url : ``
  2438. const lightbgimg = haslight ? haslight.url : ``
  2439. this.isDarkMode ? (bgimg = darkbgimg) : (bgimg = lightbgimg)
  2440. const bgStyle = [
  2441. `background-image: linear-gradient(to bottom,rgba(0,0,0,.2) 0,transparent 76px), url(${bgimg}?_=${Math.random()})`
  2442. ]
  2443. document.querySelector('#BG').setAttribute('style', bgStyle.join('; '))
  2444. } else {
  2445. const bgStyle = [
  2446. `background-image: linear-gradient(to bottom,rgba(0,0,0,.2) 0,transparent 76px), url(${
  2447. this.box.usercfgs.bgimg
  2448. }?_=${Math.random()})`
  2449. ]
  2450. document.querySelector('#BG').setAttribute('style', bgStyle.join('; '))
  2451. }
  2452. },
  2453. clearWallpaper() {
  2454. document.querySelector('#BG').removeAttribute('style')
  2455. },
  2456. toggleWaitSearchBar: _.debounce(function () {
  2457. this.ui.isWaitToggleSearchBar = false
  2458. }, 1000),
  2459. handleHistory(path) {
  2460. const { hash } = window.location.href
  2461. const state = { title: 'BoxJs', url: '/' + (hash ? hash : '#/') }
  2462. if (!history.state) {
  2463. history.replaceState(state, '')
  2464. }
  2465. state.url = path
  2466. history.pushState(state, '', path)
  2467. },
  2468. // 页面返回
  2469. back() {
  2470. history.back()
  2471. },
  2472. // 切换当前容器环境
  2473. switchEnv(env) {
  2474. this.env = env
  2475. },
  2476. // 切换当前视图
  2477. switchView(path) {
  2478. path = `/#/${path}`
  2479. if (this.path !== path) {
  2480. this.path = path
  2481. this.handleHistory(this.path)
  2482. } else {
  2483. const scrollY = this.ui.scrollY[this.path]
  2484. const isTopY = _.isNil(scrollY) || scrollY === 0
  2485. if (path === '/#/' && isTopY) {
  2486. this.clearWallpaper()
  2487. this.setWallpaper()
  2488. } else if (path === '/#/app' && isTopY) {
  2489. Object.assign(this.box.usercfgs, { favapppanel: [], subapppanel: [], sysapppanel: [] })
  2490. } else if (path === '/#/sub' && isTopY) {
  2491. this.reloadAppSub()
  2492. }
  2493. if (this.ui.scrollY[path] !== 0) {
  2494. this.$vuetify.goTo(0, { duration: 200, offset: 0 })
  2495. }
  2496. }
  2497. },
  2498. // 切换应用视图
  2499. switchAppView(appId) {
  2500. const path = `/#/app/${appId}`
  2501. this.path = path
  2502. this.handleHistory(this.path)
  2503. },
  2504. // 切换备份视图
  2505. switchBakView(backupId) {
  2506. const path = `/#/bak/${backupId}`
  2507. this.loadGlobalBak(backupId).then((resp) => {
  2508. this.path = path
  2509. this.handleHistory(path)
  2510. })
  2511. },
  2512. // 重载主题
  2513. loadTheme() {
  2514. this.$vuetify.theme.dark = this.isDarkMode
  2515. this.$vuetify.theme.themes.light.primary = this.box.usercfgs.color_light_primary || '#F7BB0E'
  2516. this.$vuetify.theme.themes.dark.primary = this.box.usercfgs.color_dark_primary || '#2196F3'
  2517. },
  2518. // 复制文本
  2519. copy(str) {
  2520. this.$copyText(str).then(
  2521. (e) => {
  2522. this.ui.snackbar.show = true
  2523. this.ui.snackbar.msg = '复制成功!'
  2524. this.ui.snackbar.color = 'primary'
  2525. },
  2526. (e) => {
  2527. this.ui.snackbar.show = true
  2528. this.ui.snackbar.msg = '复制失败!'
  2529. this.ui.snackbar.color = 'error'
  2530. }
  2531. )
  2532. },
  2533. // 复制文本
  2534. share(str) {
  2535. const url = `http://boxjs.com/#/sub/add/${encodeURIComponent(str)}`
  2536. this.copy(url)
  2537. },
  2538. // 移动收藏
  2539. moveFav(favIdx, moveCnt) {
  2540. const favapps = this.box.usercfgs.favapps
  2541. const fromIdx = favIdx
  2542. const toIdx = favIdx + moveCnt
  2543. favapps.splice(fromIdx, 1, ...favapps.splice(toIdx, 1, favapps[fromIdx]))
  2544. },
  2545. // 移动订阅
  2546. moveSub(subIdx, moveCnt) {
  2547. const appsubs = this.box.usercfgs.appsubs
  2548. const fromIdx = subIdx
  2549. const toIdx = subIdx + moveCnt
  2550. appsubs.splice(fromIdx, 1, ...appsubs.splice(toIdx, 1, appsubs[fromIdx]))
  2551. },
  2552. // 删除订阅
  2553. delSub(subIdx) {
  2554. this.box.usercfgs.appsubs.splice(subIdx, 1)
  2555. },
  2556. // 收藏应用
  2557. favApp(appId) {
  2558. const favAppIdx = this.box.usercfgs.favapps.findIndex((favAppId) => favAppId === appId)
  2559. if (favAppIdx === -1) {
  2560. this.box.usercfgs.favapps.push(appId)
  2561. } else {
  2562. this.box.usercfgs.favapps.splice(favAppIdx, 1)
  2563. }
  2564. },
  2565. // 加载应用信息
  2566. loadAppBaseInfo(app) {
  2567. // 应用图标
  2568. app.icons = Array.isArray(app.icons) ? app.icons : this.ui.defaultIcons
  2569. const isBrokenIcons = app.icons.find((i) => i.includes('/Orz-3/task/master/'))
  2570. if (isBrokenIcons) {
  2571. app.icons[0] = app.icons[0].replace('/Orz-3/mini/master/', '/Orz-3/mini/master/Alpha/')
  2572. app.icons[1] = app.icons[1].replace('/Orz-3/task/master/', '/Orz-3/mini/master/Color/')
  2573. }
  2574. app.icon = app.icons[this.iconThemeIdx]
  2575. // 是否收藏
  2576. const isFav = this.box.usercfgs.favapps.includes(app.id)
  2577. app.favIcon = isFav ? 'mdi-star' : 'mdi-star-outline'
  2578. app.favIconColor = isFav ? 'primary' : 'grey'
  2579. },
  2580. // 加载应用数据
  2581. loadAppDataInfo(app) {
  2582. if (app.isLoaded) return
  2583. // 加载应用设置
  2584. if (app.settings) {
  2585. app.settings.forEach((setting) => {
  2586. const key = setting.id
  2587. const datval = this.datas[key]
  2588. if (setting.type === 'boolean') {
  2589. setting.val = _.isEmpty(datval) ? setting.val : (datval === 'true' || datval === true)
  2590. } else if (setting.type === 'int') {
  2591. setting.val = datval * 1 || setting.val
  2592. } else if (setting.type === 'checkboxes') {
  2593. if (!_.isEmpty(datval)) {
  2594. setting.val = datval ? datval.split(',') : []
  2595. } else {
  2596. setting.val = Array.isArray(setting.val) ? setting.val : setting.val.split(',')
  2597. }
  2598. } else {
  2599. setting.val = datval || setting.val
  2600. }
  2601. })
  2602. }
  2603. // 加载当前会话数据
  2604. if (app.keys) {
  2605. app.datas = []
  2606. app.keys.forEach((key) => {
  2607. const val = this.datas[key] || ''
  2608. app.datas.push({ key, val })
  2609. })
  2610. }
  2611. // 加载会话列表
  2612. const sessions = this.sessions.filter((session) => session.appId === app.id)
  2613. app.sessions = sessions || []
  2614. // 加载当前切换会话
  2615. const curSessionId = this.box.curSessions[app.id]
  2616. if (curSessionId) {
  2617. const curSession = this.sessions.find((session) => session.id === curSessionId)
  2618. app.curSession = curSession
  2619. }
  2620. app.isLoaded = true
  2621. },
  2622. // 运行远程脚本
  2623. runRemoteScript(url, timeout) {
  2624. const opts = { url, timeout, isRemote: true }
  2625. this.runScript(opts)
  2626. },
  2627. // 运行文本脚本
  2628. runTxtScript() {
  2629. const script = this.ui.editor.getValue()
  2630. const opts = { script }
  2631. this.runScript(opts)
  2632. },
  2633. runScript(opts) {
  2634. axios.post('/api/runScript', opts).then((resp) => {
  2635. if (!this.box.usercfgs.isMute) {
  2636. this.ui.exeScriptSheet.resp = resp.data
  2637. this.ui.exeScriptSheet.show = true
  2638. }
  2639. })
  2640. },
  2641. // 保存用户偏好
  2642. saveUserCfgs() {
  2643. const key = 'chavy_boxjs_userCfgs'
  2644. const val = JSON.stringify(this.box.usercfgs)
  2645. axios.post('/api/save', [{ key, val }]).then((resp) => {
  2646. this.loadTheme()
  2647. })
  2648. },
  2649. // 保存应用设置
  2650. saveAppSettings() {
  2651. const datas = []
  2652. this.curapp.settings.forEach((setting) => {
  2653. const isNilVal = _.isNil(setting.val)
  2654. const key = setting.id
  2655. const val = !isNilVal ? _.toString(setting.val) : ''
  2656. datas.push({ key, val })
  2657. })
  2658. axios.post('/api/save', datas).then((resp) => {
  2659. if (this.curapp.id === 'BoxSetting') {
  2660. this.isSaveUserCfgs = false
  2661. } else {
  2662. delete resp.data.usercfgs
  2663. }
  2664. Object.assign(this.box, resp.data)
  2665. if (this.curapp.id === 'BoxSetting') {
  2666. this.setHttpBackend()
  2667. }
  2668. })
  2669. },
  2670. // 保存应用会话
  2671. saveAppSession() {
  2672. const session = {
  2673. id: uuidv4(),
  2674. name: '会话 ' + (this.curapp.sessions.length + 1),
  2675. appId: this.curapp.id,
  2676. appName: this.curapp.name,
  2677. enable: true,
  2678. createTime: new Date(),
  2679. datas: this.curapp.datas
  2680. }
  2681. this.box.sessions.push(session)
  2682. const key = 'chavy_boxjs_sessions'
  2683. const val = JSON.stringify(this.box.sessions)
  2684. axios.post('/api/save', [{ key, val }]).then((resp) => {
  2685. delete resp.data.usercfgs
  2686. Object.assign(this.box, resp.data)
  2687. })
  2688. },
  2689. // 修改应用会话
  2690. updateAppSession(session) {
  2691. session.datas.forEach((dat) => {
  2692. // 如果属性值是 undefined 或 null, 则修改为 ``, 否则转为字符串
  2693. dat.val = !_.isNil(dat.val) ? _.toString(dat.val) : ''
  2694. })
  2695. const key = 'chavy_boxjs_sessions'
  2696. const val = JSON.stringify(this.box.sessions)
  2697. axios
  2698. .post('/api/save', [{ key, val }])
  2699. .then((resp) => {
  2700. delete resp.data.usercfgs
  2701. Object.assign(this.box, resp.data)
  2702. })
  2703. .finally(() => (this.ui.modSessionDialog.show = false))
  2704. },
  2705. // 删除应用会话
  2706. delAppSession(sessionId) {
  2707. const sessions = this.box.sessions
  2708. const sessionIdx = sessions.findIndex((session) => session.id === sessionId)
  2709. sessions.splice(sessionIdx, 1)
  2710. const key = 'chavy_boxjs_sessions'
  2711. const val = JSON.stringify(this.box.sessions)
  2712. axios.post('/api/save', [{ key, val }]).then((resp) => {
  2713. delete resp.data.usercfgs
  2714. Object.assign(this.box, resp.data)
  2715. })
  2716. },
  2717. // 使用应用会话
  2718. useAppSession(sessionId) {
  2719. const sessions = this.box.sessions
  2720. const session = sessions.find((session) => session.id === sessionId)
  2721. this.box.curSessions[session.appId] = sessionId
  2722. const key = 'chavy_boxjs_cur_sessions'
  2723. const val = JSON.stringify(this.box.curSessions)
  2724. const curSessions = [{ key, val }]
  2725. const datas = [...session.datas, ...curSessions]
  2726. this.clearAppDatas()
  2727. axios.post('/api/save', datas).then((resp) => {
  2728. delete resp.data.usercfgs
  2729. Object.assign(this.box, resp.data)
  2730. })
  2731. },
  2732. // 复制应用数据为对象数据
  2733. copyData(curdata) {
  2734. const datas = curdata.datas
  2735. let result = {}
  2736. datas.forEach(({ key, val }) => {
  2737. result[key] = val
  2738. })
  2739. this.copy(JSON.stringify(result))
  2740. },
  2741. // 保存应用数据
  2742. clearAppDatas(key) {
  2743. const datas = this.curapp.datas
  2744. if (key) {
  2745. const data = datas.find((data) => data.key === key)
  2746. data.val = ''
  2747. } else {
  2748. datas.forEach((data) => (data.val = ''))
  2749. }
  2750. axios.post('/api/save', datas).then((resp) => {
  2751. if (this.curapp.id === 'BoxSetting') {
  2752. this.isSaveUserCfgs = false
  2753. } else {
  2754. delete resp.data.usercfgs
  2755. }
  2756. Object.assign(this.box, resp.data)
  2757. })
  2758. },
  2759. // 导入应用数据
  2760. impAppDatas() {
  2761. const impval = this.ui.impAppDatasDialog.impval
  2762. const impapp = JSON.parse(impval)
  2763. const datas = impapp.datas || []
  2764. const settings = impapp.settings || []
  2765. settings.forEach((setting) => {
  2766. const { id: key, val } = setting
  2767. datas.push({ key, val })
  2768. })
  2769. axios
  2770. .post('/api/save', datas)
  2771. .then((resp) => {
  2772. delete resp.data.usercfgs
  2773. Object.assign(this.box, resp.data)
  2774. })
  2775. .finally(() => {
  2776. this.ui.impAppDatasDialog.show = false
  2777. this.ui.impAppDatasDialog.impval = ''
  2778. })
  2779. },
  2780. // 添加应用订阅
  2781. addAppSub(url, isRedirect) {
  2782. const sub = { id: uuidv4(), url, enable: true }
  2783. axios
  2784. .post('/api/addAppSub', sub)
  2785. .then((resp) => {
  2786. this.isSaveUserCfgs = false
  2787. Object.assign(this.box, resp.data)
  2788. if (isRedirect) {
  2789. this.switchView('sub')
  2790. this.ui.snackbar.show = true
  2791. this.ui.snackbar.msg = '一键订阅: 成功!'
  2792. this.ui.snackbar.color = 'success'
  2793. }
  2794. this.openInstall(sub.id)
  2795. })
  2796. .finally(() => (this.addAppSubDialog = false))
  2797. },
  2798. // 重载应用订阅
  2799. reloadAppSub(sub) {
  2800. axios.post('/api/reloadAppSub', sub).then((resp) => {
  2801. delete resp.data.usercfgs
  2802. Object.assign(this.box, resp.data)
  2803. })
  2804. },
  2805. // 加载完整的全局备份 (页面渲染时加载只是备份列表)
  2806. loadGlobalBak(backupId) {
  2807. return axios.get(`/query/baks/${backupId}`).then((resp) => {
  2808. const backup = this.box.globalbaks.find((backup) => backup.id === backupId)
  2809. backup.bak = resp.data
  2810. })
  2811. },
  2812. // 删除全局备份
  2813. delGlobalBak() {
  2814. const { id } = this.curbak
  2815. axios.post('/api/delGlobalBak', { id }).then((resp) => {
  2816. this.back()
  2817. delete resp.data.usercfgs
  2818. Object.assign(this.box, resp.data)
  2819. })
  2820. },
  2821. // 保存当前备份
  2822. updateGlobalBak() {
  2823. const { id, name } = this.curbak
  2824. axios.post('/api/updateGlobalBak', { id, name }).then((resp) => {
  2825. delete resp.data.usercfgs
  2826. Object.assign(this.box, resp.data)
  2827. })
  2828. },
  2829. // 导入全局备份
  2830. impGlobalBak() {
  2831. const impval = this.ui.impGlobalBakDialog.impval
  2832. const bak = {
  2833. id: uuidv4(),
  2834. name: `${this.$t('profile.bakName')} ` + (this.box.globalbaks.length + 1),
  2835. env: this.box.syscfgs.env,
  2836. version: this.box.syscfgs.version,
  2837. versionType: this.box.syscfgs.versionType,
  2838. createTime: new Date(),
  2839. bak: JSON.parse(impval)
  2840. }
  2841. bak.tags = [bak.env, bak.version, bak.versionType]
  2842. axios
  2843. .post('/api/impGlobalBak', bak)
  2844. .then((resp) => {
  2845. delete resp.data.usercfgs
  2846. Object.assign(this.box, resp.data)
  2847. })
  2848. .finally(() => {
  2849. this.ui.impGlobalBakDialog.impval = ''
  2850. this.ui.impGlobalBakDialog.show = false
  2851. })
  2852. },
  2853. // 保存备份
  2854. saveGlobalBak() {
  2855. const bak = {
  2856. id: uuidv4(),
  2857. name: `${this.$t('profile.bakName')} ` + (this.box.globalbaks.length + 1),
  2858. env: this.box.syscfgs.env,
  2859. version: this.box.syscfgs.version,
  2860. versionType: this.box.syscfgs.versionType,
  2861. createTime: new Date()
  2862. }
  2863. bak.tags = [bak.env, bak.version, bak.versionType]
  2864. axios.post('/api/saveGlobalBak', bak).then((resp) => {
  2865. delete resp.data.usercfgs
  2866. Object.assign(this.box, resp.data)
  2867. })
  2868. },
  2869. // 还原备份
  2870. revertGlobalBak() {
  2871. const { id } = this.curbak
  2872. axios
  2873. .post('/api/revertGlobalBak', { id })
  2874. .then((resp) => {
  2875. this.isSaveUserCfgs = false
  2876. Object.assign(this.box, resp.data)
  2877. })
  2878. .finally(() => this.loadTheme())
  2879. },
  2880. // 获取仓库贡献者
  2881. getContributors() {
  2882. const url = 'https://api.github.com/repos/chavyleung/scripts/contributors'
  2883. axios.get(url).then((resp) => {
  2884. if (!resp) return
  2885. resp.data.forEach((contributor) => {
  2886. const { login: id, login, html_url: repo, avatar_url: icon } = contributor
  2887. if ([29748519, 39037656, 9592236].includes(contributor.id)) {
  2888. this.ui.collaborators.push({ id, login, repo, icon })
  2889. } else {
  2890. this.ui.contributors.push({ id, login, repo, icon })
  2891. }
  2892. })
  2893. })
  2894. },
  2895. // 获取版本清单
  2896. getVersions() {
  2897. axios.get('/query/versions').then((resp) => {
  2898. if (resp.data && resp.data.releases) {
  2899. Object.assign(this.box, { versions: resp.data.releases })
  2900. if (this.hasNewVersion) {
  2901. this.ui.versionSheet.show = true
  2902. }
  2903. }
  2904. })
  2905. },
  2906. // 查询数据
  2907. queryData() {
  2908. const key = this.ui.viewer.key
  2909. this.ui.viewer.key = key ? key : 'boxjs_host'
  2910. axios.get(`/query/data/${this.ui.viewer.key}`).then((resp) => {
  2911. this.ui.viewer.val = resp.data.val
  2912. this.box.usercfgs.viewkeys.unshift(this.ui.viewer.key)
  2913. })
  2914. },
  2915. saveData() {
  2916. const key = this.ui.viewer.key
  2917. const val = this.ui.viewer.val
  2918. if (key) {
  2919. axios.post('/api/saveData/', { key, val }).then((resp) => {
  2920. this.ui.viewer.val = resp.data.val
  2921. })
  2922. }
  2923. },
  2924. // 对比版本号
  2925. compareVersion(v1, v2) {
  2926. var _v1 = v1.split('.'),
  2927. _v2 = v2.split('.'),
  2928. _r = _v1[0] - _v2[0]
  2929. return _r == 0 && v1 != v2 ? this.compareVersion(_v1.splice(1).join('.'), _v2.splice(1).join('.')) : _r
  2930. },
  2931. // 设置HTTP Backend
  2932. setHttpBackend() {
  2933. // 目前HTTP Backend不能修改端口号
  2934. var regex = /^http:\/\/(.*):9999$/
  2935. if (this.box.syscfgs.env === 'QuanX') {
  2936. if (regex.test(window.location.origin)) {
  2937. axios.defaults.baseURL = ''
  2938. return
  2939. }
  2940. // 如果是Quantumult X环境并且配置了正确格式的HTTP Backend,将axios请求指向到HTTP Backend
  2941. if (this.box.usercfgs.http_backend && regex.test(this.box.usercfgs.http_backend)) {
  2942. axios.defaults.baseURL = this.box.usercfgs.http_backend
  2943. this.ui.isCors = true
  2944. } else {
  2945. axios.defaults.baseURL = ''
  2946. }
  2947. }
  2948. },
  2949. calculateUpgradePrice(purchaseDate, licenseType) {
  2950. const licensePrices = {
  2951. '1 Device License': 34.99,
  2952. '3 Devices License': 48.99,
  2953. '5 Devices License': 69.99,
  2954. }
  2955. const upgradePrice = licensePrices[licenseType]
  2956. if (!upgradePrice) {
  2957. throw new Error(`Invalid license type: ${licenseType}`)
  2958. }
  2959. const discountEndDate = dayjs('2022-04-15')
  2960. const freeDate = dayjs('2022-10-15')
  2961. if (dayjs(purchaseDate).isBefore(discountEndDate)) {
  2962. return licensePrices[licenseType]
  2963. }
  2964. const equivalentPurchaseDate = dayjs(purchaseDate)
  2965. const diffDays = freeDate.diff(discountEndDate, 'day')
  2966. const daysFromDiscountEndDate = equivalentPurchaseDate.diff(discountEndDate, 'day')
  2967. if (daysFromDiscountEndDate >= diffDays) {
  2968. // After free upgrade date
  2969. return 0
  2970. } else {
  2971. const ratio = 1 - daysFromDiscountEndDate / diffDays
  2972. const price = Math.ceil(ratio * upgradePrice * 100) / 100 - 0.01
  2973. return price < 1.99 ? 1.99 : price
  2974. }
  2975. },
  2976. calculateCost() {
  2977. this.cost = this.calculateUpgradePrice(this.purchaseDate, this.licenseType)
  2978. },
  2979. }
  2980. })
  2981. </script>
  2982. </body>
  2983. </html>