Compare commits

...

92 Commits

Author SHA1 Message Date
jxxghp
8236d80b42 feat:优化插件升级使用体验 2024-03-12 21:31:56 +08:00
jxxghp
90e7eb1c79 Merge pull request #86 from WangEdward/main 2024-03-11 16:32:30 +08:00
WangEdward
ef09868af1 fix: display search_imdbid status 2024-03-11 16:30:24 +08:00
jxxghp
028981e3ae 更新 package.json 2024-03-10 21:07:17 +08:00
jxxghp
e8a6274cf6 Merge pull request #85 from honue/main 2024-03-10 17:16:54 +08:00
honue
ffd0265526 设置每周一为第一天,当天背景突显 2024-03-10 16:49:40 +08:00
jxxghp
13d7344bc0 fix bug 2024-03-09 20:53:37 +08:00
jxxghp
2ad36f92c5 feat:下载器多选 2024-03-09 18:52:48 +08:00
jxxghp
36b02f4423 release 2024-03-09 17:35:45 +08:00
jxxghp
01df990aa8 fix #1638 2024-03-09 17:05:44 +08:00
jxxghp
49b71fcf5d fix #1640 2024-03-09 16:49:48 +08:00
jxxghp
9e43d77ac4 feat:新增官种优先级规则 2024-03-09 09:15:03 +08:00
jxxghp
3ab9af720b Merge pull request #84 from WangEdward/main 2024-03-08 22:11:13 +08:00
WangEdward
abae304f87 chore: add half-increments for v-rating 2024-03-08 21:23:25 +08:00
jxxghp
659d8bff66 fix 下载及订阅用户匹配 2024-03-08 15:27:51 +08:00
jxxghp
1786e10101 fix:修改编译策略,避免一直loading 2024-03-08 14:18:30 +08:00
jxxghp
bda7f929e7 try fix loading 2024-03-08 13:31:38 +08:00
jxxghp
c309f80a94 更新 ModuleTestView.vue 2024-03-06 22:05:12 +08:00
jxxghp
97c987c561 fix ui 2024-03-06 21:52:20 +08:00
jxxghp
48949104e0 feat:目录检测 2024-03-06 21:41:06 +08:00
jxxghp
a38cc4fe34 add min_seeders 2024-03-06 20:15:23 +08:00
jxxghp
495dfbcb28 feat:add VoceChat 2024-03-06 15:55:01 +08:00
jxxghp
6e4dbd912b feat:健康检查 2024-03-06 13:27:17 +08:00
jxxghp
82904d956d thetvdb network test 2024-03-06 11:16:12 +08:00
jxxghp
ec7118b376 fix #82 后台登录成功但前端报错 2024-03-05 20:21:05 +08:00
jxxghp
058b32a263 release 2024-03-05 17:27:27 +08:00
jxxghp
e7b960838e Merge pull request #82 from honue/main 2024-03-05 15:24:46 +08:00
honue
14e776a287 登录后返回原始页面功能 2024-03-05 15:20:27 +08:00
jxxghp
73f11b920f Merge pull request #81 from WangEdward/main
feat: add search_imdbid
2024-03-05 12:57:44 +08:00
jxxghp
5c93040a8e fanart nettest 2024-03-05 12:57:02 +08:00
WangEdward
a517769e8a feat: add search_imdbid 2024-03-04 21:36:06 +08:00
jxxghp
4bb59a9f05 fix 2024-03-03 09:02:41 +08:00
jxxghp
b37879d2d4 release 2024-03-03 08:48:04 +08:00
jxxghp
05defc39d7 rollback VTooltip 2024-03-03 08:47:20 +08:00
jxxghp
18bfad07d2 fix 2024-03-02 09:58:42 +08:00
jxxghp
b83591255d Merge pull request #80 from maicss/main 2024-03-02 09:14:56 +08:00
maicss
804350bc81 feat: search box add border for contrast 2024-03-01 23:33:50 +08:00
maicss
46e1cae0bb feat: add session valid chack 2024-03-01 23:33:50 +08:00
maicss
81062d4580 fix: enhance some interactive experiences 2024-03-01 23:33:50 +08:00
maicss
55481db2ee Merge branch 'jxxghp:main' into main 2024-03-01 20:16:10 +08:00
jxxghp
ecdd12f5a9 fix 2024-02-29 20:21:30 +08:00
jxxghp
ef92cdc183 fix torrent type 2024-02-29 16:15:28 +08:00
jxxghp
08f4a6cf2c Merge pull request #78 from maicss/maicss 2024-02-29 11:16:48 +08:00
maicss
38889acb4e fix: toast message not visible
When install plugin failed, it will yield an error toast. But attribute `z-index` of toast is `1090`, while `v-dialog` is `2400`
2024-02-29 09:51:20 +08:00
maicss
c0517cd29a fix: toast message not visible
When install plugin failed, `z-index` of toast is `1090` while `v-dialog` is `2400`.
2024-02-29 09:48:23 +08:00
jxxghp
084449ccf3 rebuild 2024-02-28 18:54:35 +08:00
jxxghp
0e8203ae03 fix app 2024-02-27 19:25:12 +08:00
jxxghp
236440be52 fix form 2024-02-27 15:23:07 +08:00
jxxghp
6f7e4bb272 release 2024-02-26 13:21:37 +08:00
jxxghp
38dcd3635a fix text 2024-02-25 17:17:53 +08:00
jxxghp
a3f3330dad fix 2024-02-25 08:58:46 +08:00
jxxghp
bbc6c57c08 Merge pull request #77 from cikezhu/main 2024-02-24 06:47:02 +08:00
叮叮当
2f36a8edef fix 2024-02-23 22:27:01 +08:00
叮叮当
df637fb887 定时作业添加提供者条目 2024-02-23 22:26:16 +08:00
jxxghp
be74c92a35 更新 package.json 2024-02-23 16:22:57 +08:00
jxxghp
a219a64e20 Merge pull request #76 from alkalixin/main 2024-02-23 16:22:19 +08:00
alkalixin
25c22a276a 给部分按钮增加浮动提示 2024-02-23 15:07:01 +08:00
jxxghp
6e6be057ca fix ui 2024-02-23 10:45:21 +08:00
jxxghp
af69efa48b feat:插件日志单独查看 2024-02-22 13:32:23 +08:00
jxxghp
c551083fa4 优化插件页面加载速度 2024-02-21 17:50:02 +08:00
jxxghp
9767feed29 更新 package.json 2024-02-18 15:19:52 +08:00
jxxghp
4392818e92 Merge pull request #75 from honue/main 2024-02-18 15:17:53 +08:00
honue
8d22bafeb6 订阅卡片增加剧集详情跳转功能 2024-02-18 15:08:22 +08:00
jxxghp
89ddd1fb78 更新 package.json 2024-02-17 13:19:04 +08:00
jxxghp
24513fa22b Merge pull request #74 from honue/main 2024-02-17 13:18:46 +08:00
honue
cddde0c2a0 fix 2024-02-17 13:09:48 +08:00
jxxghp
9c674e0018 更新 package.json 2024-02-15 21:06:19 +08:00
jxxghp
0c6476d283 更新 AccountSettingSystem.vue 2024-02-15 21:05:58 +08:00
jxxghp
bf0c529a59 fix ui 2024-02-15 20:01:51 +08:00
jxxghp
877bb4d4a2 fix ui 2024-02-15 18:58:22 +08:00
jxxghp
dc4db0b2b3 fix settings 2024-02-15 18:17:36 +08:00
jxxghp
a738d4a3b9 feat:系统设置面板 2024-02-15 15:04:59 +08:00
jxxghp
e9866a04df v1.6.4 2024-02-14 21:07:09 +08:00
jxxghp
4f5193d602 feat:更新Cookie支持两步验证 2024-02-14 21:06:42 +08:00
jxxghp
37b92c55ba feat:文件管理手动刮削 2024-02-10 19:32:49 +08:00
jxxghp
9299f1bcb6 release 2024-02-10 09:48:45 +08:00
jxxghp
7fe12192df add apexcharts 2024-02-10 09:36:54 +08:00
jxxghp
1169644ab3 fix render 2024-02-09 08:43:36 +08:00
jxxghp
6f7770ed43 fix 2024-02-08 20:58:41 +08:00
jxxghp
8059fd6f90 fix 2024-02-08 20:57:09 +08:00
jxxghp
556dbd8d78 fix bug 2024-02-08 20:07:23 +08:00
jxxghp
6695fd8c14 add ace-editor 2024-02-08 19:56:28 +08:00
jxxghp
3ab0229275 fix ui 2024-02-08 13:43:37 +08:00
jxxghp
99467127a0 更新 package.json 2024-02-08 13:28:11 +08:00
jxxghp
90d73b7bd5 Merge pull request #73 from cikezhu/main 2024-02-08 13:27:40 +08:00
叮叮当
2e326e1798 fix 全部日志url反向代理时被serviceWorker拦截 2024-02-08 12:45:52 +08:00
jxxghp
251eac93c7 更新 package.json 2024-02-08 07:11:49 +08:00
jxxghp
c74d70808c Merge pull request #72 from cikezhu/main 2024-02-08 07:10:48 +08:00
叮叮当
e63b2d7152 新窗口打开全部日志 2024-02-08 00:06:52 +08:00
jxxghp
16b29b56a5 更新 package.json 2024-01-29 23:11:57 +08:00
jxxghp
6d79c4fe2f Merge pull request #71 from cikezhu/main 2024-01-29 23:11:34 +08:00
叮叮当
4b1fb60ee3 fix 媒体信息页面person跳转问题 2024-01-29 23:01:22 +08:00
44 changed files with 2433 additions and 492 deletions

View File

@@ -1,214 +1,162 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="initial-scale=1, viewport-fit=cover, width=device-width, user-scalable=no" />
<title>MoviePilot</title>
<meta name="Robots" content="noindex,nofollow,noarchive" />
<meta name="referrer" content="origin" />
<link rel="icon" type="image/png" href="/logo.png" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<link rel="apple-touch-startup-image" href="/splash/apple-splash.jpg" />
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
<link rel="manifest" href="manifest.json" crossorigin="use-credentials" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="MoviePilot" />
<meta name="description" content="MoviePilot" />
<meta name="format-detection" content="telephone=no" />
<meta name="referrer" content="never" />
<meta name="msapplication-TileColor" content="#7D34FD" />
<meta name="color-scheme" content="light dark" />
<meta name="theme-color" content="#28243D" media="(prefers-color-scheme: dark)" />
<meta name="theme-color" content="#F4F5FA" media="(prefers-color-scheme: light)" />
<meta name="HandheldFriendly" content="True" />
<meta name="MobileOptimized" content="320" />
<link rel="stylesheet" type="text/css" href="/loader.css" />
</head>
<body>
<div id="loading-bg">
<div class="loading-logo">
<!-- Logo -->
<svg
width="100px"
height="100px"
viewBox="0 0 192 192"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
style="fill-rule: evenodd; clip-rule: evenodd; stroke-linejoin: round; stroke-miterlimit: 2"
>
<g transform="matrix(1,0,0,1,-2606,-236)">
<g id="a2-c" transform="matrix(1,0,0,1,2606,236)">
<rect x="0" y="0" width="192" height="192" style="fill: none" />
<g transform="matrix(-0.800798,0.462341,-0.769972,-1.33363,1869.11,-896.718)">
<head>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="expires" content="0">
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="initial-scale=1, viewport-fit=cover, width=device-width, user-scalable=no" />
<title>MoviePilot</title>
<meta name="Robots" content="noindex,nofollow,noarchive" />
<meta name="referrer" content="origin" />
<link rel="icon" type="image/png" href="/logo.png" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<link rel="apple-touch-startup-image" href="/splash/apple-splash.jpg" />
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
<link rel="manifest" href="manifest.json" crossorigin="use-credentials" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="MoviePilot" />
<meta name="description" content="MoviePilot" />
<meta name="format-detection" content="telephone=no" />
<meta name="referrer" content="never" />
<meta name="msapplication-TileColor" content="#7D34FD" />
<meta name="color-scheme" content="light dark" />
<meta name="theme-color" content="#28243D" media="(prefers-color-scheme: dark)" />
<meta name="theme-color" content="#F4F5FA" media="(prefers-color-scheme: light)" />
<meta name="HandheldFriendly" content="True" />
<meta name="MobileOptimized" content="320" />
<link rel="stylesheet" type="text/css" href="/loader.css" />
</head>
<body>
<div id="loading-bg">
<div class="loading-logo">
<!-- Logo -->
<svg width="100px" height="100px" viewBox="0 0 192 192" version="1.1" xmlns="http://www.w3.org/2000/svg"
style="fill-rule: evenodd; clip-rule: evenodd; stroke-linejoin: round; stroke-miterlimit: 2">
<g transform="matrix(1,0,0,1,-2606,-236)">
<g id="a2-c" transform="matrix(1,0,0,1,2606,236)">
<rect x="0" y="0" width="192" height="192" style="fill: none" />
<g transform="matrix(-0.800798,0.462341,-0.769972,-1.33363,1869.11,-896.718)">
<path
d="M2241.27,-28.175C2238.86,-28.931 2236.64,-29.181 2234.48,-29.254L2159.78,-29.286L2165.01,-11.207C2167.16,-13.121 2169.64,-13.722 2172.26,-13.808L2222.12,-13.822C2223.52,-13.824 2225,-13.701 2226.78,-13.108L2241.27,-28.175Z"
style="fill: url(#_Linear1)" />
</g>
<g transform="matrix(1,0,0,1,-2157.67,-208.423)">
<path
d="M2205.67,331.428L2205.67,332.25L2205.67,352.835C2205.67,354.263 2204.91,355.583 2203.67,356.298C2202.43,357.012 2200.91,357.013 2199.67,356.3L2190.78,351.174C2189.73,350.595 2188.83,350.083 2188.03,349.59L2187.45,349.257C2186.66,348.725 2185.91,348.142 2185.21,347.461C2185.08,347.331 2184.95,347.198 2184.82,347.061C2184.26,346.457 2183.75,345.778 2183.3,344.995C2182.16,343.05 2181.69,341.024 2181.68,338.948L2181.67,268.923L2209.77,274.425C2207.5,275.639 2205.68,278.3 2205.67,281.429L2205.67,331.428Z"
style="fill: url(#_Linear2)" />
</g>
<g transform="matrix(1,0,0,1,-2157.67,-208.423)">
<path
d="M2295.93,363.064C2299.48,360.882 2301.46,357.55 2301.67,352.926L2301.67,277.879L2301.67,276.775L2301.67,252.515C2301.67,251.801 2302.05,251.14 2302.67,250.783C2303.29,250.426 2304.05,250.426 2304.67,250.784L2319.81,259.54C2321.59,260.617 2322.95,262.115 2324.04,263.875C2325.03,265.551 2325.56,267.37 2325.67,269.835L2325.67,339.91C2325.18,343.645 2323.51,346.705 2320.3,348.887L2295.93,363.064ZM2295.93,363.064C2295.73,363.184 2295.53,363.301 2295.32,363.414L2295.93,363.064Z"
style="fill: rgb(141, 81, 249)" />
</g>
<g transform="matrix(1,0,0,1,-2157.67,-208.423)">
<path
d="M2295.93,363.064C2299.48,360.882 2301.46,357.55 2301.67,352.926L2301.67,277.879L2301.67,276.775L2301.67,252.515C2301.67,251.801 2302.05,251.14 2302.67,250.783C2303.29,250.426 2304.05,250.426 2304.67,250.784L2319.81,259.54C2321.59,260.617 2322.95,262.115 2324.04,263.875C2325.03,265.551 2325.56,267.37 2325.67,269.835L2325.67,339.91C2325.18,343.645 2323.51,346.705 2320.3,348.887L2295.93,363.064ZM2299.79,360.238C2299.79,360.238 2320.03,348.464 2320.04,348.461C2323.1,346.372 2324.69,343.444 2325.17,339.877C2325.17,339.877 2325.17,269.846 2325.17,269.839C2325.06,267.482 2324.56,265.739 2323.61,264.133C2322.56,262.445 2321.26,261.005 2319.55,259.97L2304.42,251.217C2303.96,250.949 2303.39,250.948 2302.92,251.216C2302.46,251.484 2302.17,251.979 2302.17,252.515L2302.17,276.775L2302.17,277.879L2302.17,352.926C2302.17,352.933 2302.17,352.941 2302.17,352.948C2302.04,355.861 2301.23,358.279 2299.79,360.238Z"
style="fill: url(#_Linear3)" />
</g>
<g transform="matrix(1,0,0,1,-2157.67,-208.423)">
<path
d="M2253.67,223.256C2255.26,223.245 2257.02,223.56 2259.11,224.557L2275.67,234.102C2276.91,234.816 2277.67,236.138 2277.67,237.568L2277.67,259.508C2277.67,260.222 2277.29,260.882 2276.67,261.239C2276.05,261.597 2275.29,261.597 2274.67,261.24L2257.52,251.353C2256.38,250.731 2255.12,250.341 2253.67,250.347C2252.26,250.339 2250.99,250.721 2249.82,251.353L2187.87,287.04C2184.23,289.147 2181.96,292.478 2181.67,297.57L2181.68,269.865C2181.85,265.167 2183.93,261.653 2187.92,259.322L2248.23,224.557C2249.69,223.796 2251.5,223.29 2253.67,223.256Z"
style="fill: rgb(165, 118, 255)" />
</g>
<g transform="matrix(1,0,0,1,-2157.67,-208.423)">
<path
d="M2253.67,223.256C2255.26,223.245 2257.02,223.56 2259.11,224.557L2275.67,234.102C2276.91,234.816 2277.67,236.138 2277.67,237.568L2277.67,259.508C2277.67,260.222 2277.29,260.882 2276.67,261.239C2276.05,261.597 2275.29,261.597 2274.67,261.24L2257.52,251.353C2256.38,250.731 2255.12,250.341 2253.67,250.347C2252.26,250.339 2250.99,250.721 2249.82,251.353L2187.87,287.04C2184.23,289.147 2181.96,292.478 2181.67,297.57L2181.68,269.865C2181.85,265.167 2183.93,261.653 2187.92,259.322L2248.23,224.557C2249.69,223.796 2251.5,223.29 2253.67,223.256ZM2253.68,223.756C2251.6,223.789 2249.87,224.269 2248.47,224.996L2188.17,259.754C2184.35,261.992 2182.35,265.367 2182.18,269.874C2182.18,269.874 2182.17,292.759 2182.17,292.757C2183.25,290.047 2185.13,288.051 2187.62,286.607L2249.57,250.919C2249.58,250.917 2249.58,250.915 2249.59,250.913C2250.83,250.243 2252.17,249.839 2253.67,249.847C2255.21,249.841 2256.54,250.253 2257.76,250.914C2257.76,250.916 2257.76,250.917 2257.76,250.919L2274.92,260.807C2275.38,261.075 2275.95,261.074 2276.42,260.806C2276.88,260.538 2277.17,260.043 2277.17,259.508L2277.17,237.568C2277.17,236.317 2276.5,235.16 2275.42,234.535C2275.42,234.535 2258.88,225 2258.87,224.996C2256.87,224.049 2255.2,223.746 2253.68,223.756Z"
style="fill: url(#_Linear4)" />
</g>
<g transform="matrix(0.800798,0.462341,0.769972,-1.33363,-1677.22,-896.858)">
<path
d="M2241.55,-28.184C2239.1,-28.989 2236.83,-29.204 2234.68,-29.295C2234.68,-29.295 2220.82,-29.3 2215.03,-29.303C2213.48,-29.303 2212.05,-28.808 2211.28,-28.004C2208.65,-25.275 2202.56,-18.936 2199.45,-15.709C2199.07,-15.306 2199.07,-14.809 2199.46,-14.406C2199.85,-14.004 2200.57,-13.758 2201.34,-13.761C2208.36,-13.788 2222.72,-13.845 2222.72,-13.845C2223.98,-13.851 2225.44,-13.657 2227.06,-13.117L2241.55,-28.184Z"
style="fill: rgb(141, 81, 249)" />
</g>
<g transform="matrix(-4.32309,0,0,12.4454,9610.35,-1450.35)">
<path
d="M2205.31,121.966C2205.31,121.88 2205.18,121.8 2204.96,121.757C2204.74,121.714 2204.48,121.714 2204.27,121.757C2201.75,122.263 2195.36,123.547 2192.85,124.052C2192.63,124.095 2192.5,124.174 2192.5,124.261C2192.5,124.347 2192.63,124.426 2192.85,124.469C2195.36,124.974 2201.75,126.255 2204.27,126.759C2204.48,126.802 2204.74,126.802 2204.96,126.759C2205.18,126.716 2205.31,126.636 2205.31,126.55C2205.31,125.541 2205.31,122.976 2205.31,121.966Z"
style="fill: rgb(104, 0, 197)" />
<clipPath id="_clip5">
<path
d="M2241.27,-28.175C2238.86,-28.931 2236.64,-29.181 2234.48,-29.254L2159.78,-29.286L2165.01,-11.207C2167.16,-13.121 2169.64,-13.722 2172.26,-13.808L2222.12,-13.822C2223.52,-13.824 2225,-13.701 2226.78,-13.108L2241.27,-28.175Z"
style="fill: url(#_Linear1)"
/>
</g>
<g transform="matrix(1,0,0,1,-2157.67,-208.423)">
<path
d="M2205.67,331.428L2205.67,332.25L2205.67,352.835C2205.67,354.263 2204.91,355.583 2203.67,356.298C2202.43,357.012 2200.91,357.013 2199.67,356.3L2190.78,351.174C2189.73,350.595 2188.83,350.083 2188.03,349.59L2187.45,349.257C2186.66,348.725 2185.91,348.142 2185.21,347.461C2185.08,347.331 2184.95,347.198 2184.82,347.061C2184.26,346.457 2183.75,345.778 2183.3,344.995C2182.16,343.05 2181.69,341.024 2181.68,338.948L2181.67,268.923L2209.77,274.425C2207.5,275.639 2205.68,278.3 2205.67,281.429L2205.67,331.428Z"
style="fill: url(#_Linear2)"
/>
</g>
<g transform="matrix(1,0,0,1,-2157.67,-208.423)">
<path
d="M2295.93,363.064C2299.48,360.882 2301.46,357.55 2301.67,352.926L2301.67,277.879L2301.67,276.775L2301.67,252.515C2301.67,251.801 2302.05,251.14 2302.67,250.783C2303.29,250.426 2304.05,250.426 2304.67,250.784L2319.81,259.54C2321.59,260.617 2322.95,262.115 2324.04,263.875C2325.03,265.551 2325.56,267.37 2325.67,269.835L2325.67,339.91C2325.18,343.645 2323.51,346.705 2320.3,348.887L2295.93,363.064ZM2295.93,363.064C2295.73,363.184 2295.53,363.301 2295.32,363.414L2295.93,363.064Z"
style="fill: rgb(141, 81, 249)"
/>
</g>
<g transform="matrix(1,0,0,1,-2157.67,-208.423)">
<path
d="M2295.93,363.064C2299.48,360.882 2301.46,357.55 2301.67,352.926L2301.67,277.879L2301.67,276.775L2301.67,252.515C2301.67,251.801 2302.05,251.14 2302.67,250.783C2303.29,250.426 2304.05,250.426 2304.67,250.784L2319.81,259.54C2321.59,260.617 2322.95,262.115 2324.04,263.875C2325.03,265.551 2325.56,267.37 2325.67,269.835L2325.67,339.91C2325.18,343.645 2323.51,346.705 2320.3,348.887L2295.93,363.064ZM2299.79,360.238C2299.79,360.238 2320.03,348.464 2320.04,348.461C2323.1,346.372 2324.69,343.444 2325.17,339.877C2325.17,339.877 2325.17,269.846 2325.17,269.839C2325.06,267.482 2324.56,265.739 2323.61,264.133C2322.56,262.445 2321.26,261.005 2319.55,259.97L2304.42,251.217C2303.96,250.949 2303.39,250.948 2302.92,251.216C2302.46,251.484 2302.17,251.979 2302.17,252.515L2302.17,276.775L2302.17,277.879L2302.17,352.926C2302.17,352.933 2302.17,352.941 2302.17,352.948C2302.04,355.861 2301.23,358.279 2299.79,360.238Z"
style="fill: url(#_Linear3)"
/>
</g>
<g transform="matrix(1,0,0,1,-2157.67,-208.423)">
<path
d="M2253.67,223.256C2255.26,223.245 2257.02,223.56 2259.11,224.557L2275.67,234.102C2276.91,234.816 2277.67,236.138 2277.67,237.568L2277.67,259.508C2277.67,260.222 2277.29,260.882 2276.67,261.239C2276.05,261.597 2275.29,261.597 2274.67,261.24L2257.52,251.353C2256.38,250.731 2255.12,250.341 2253.67,250.347C2252.26,250.339 2250.99,250.721 2249.82,251.353L2187.87,287.04C2184.23,289.147 2181.96,292.478 2181.67,297.57L2181.68,269.865C2181.85,265.167 2183.93,261.653 2187.92,259.322L2248.23,224.557C2249.69,223.796 2251.5,223.29 2253.67,223.256Z"
style="fill: rgb(165, 118, 255)"
/>
</g>
<g transform="matrix(1,0,0,1,-2157.67,-208.423)">
<path
d="M2253.67,223.256C2255.26,223.245 2257.02,223.56 2259.11,224.557L2275.67,234.102C2276.91,234.816 2277.67,236.138 2277.67,237.568L2277.67,259.508C2277.67,260.222 2277.29,260.882 2276.67,261.239C2276.05,261.597 2275.29,261.597 2274.67,261.24L2257.52,251.353C2256.38,250.731 2255.12,250.341 2253.67,250.347C2252.26,250.339 2250.99,250.721 2249.82,251.353L2187.87,287.04C2184.23,289.147 2181.96,292.478 2181.67,297.57L2181.68,269.865C2181.85,265.167 2183.93,261.653 2187.92,259.322L2248.23,224.557C2249.69,223.796 2251.5,223.29 2253.67,223.256ZM2253.68,223.756C2251.6,223.789 2249.87,224.269 2248.47,224.996L2188.17,259.754C2184.35,261.992 2182.35,265.367 2182.18,269.874C2182.18,269.874 2182.17,292.759 2182.17,292.757C2183.25,290.047 2185.13,288.051 2187.62,286.607L2249.57,250.919C2249.58,250.917 2249.58,250.915 2249.59,250.913C2250.83,250.243 2252.17,249.839 2253.67,249.847C2255.21,249.841 2256.54,250.253 2257.76,250.914C2257.76,250.916 2257.76,250.917 2257.76,250.919L2274.92,260.807C2275.38,261.075 2275.95,261.074 2276.42,260.806C2276.88,260.538 2277.17,260.043 2277.17,259.508L2277.17,237.568C2277.17,236.317 2276.5,235.16 2275.42,234.535C2275.42,234.535 2258.88,225 2258.87,224.996C2256.87,224.049 2255.2,223.746 2253.68,223.756Z"
style="fill: url(#_Linear4)"
/>
</g>
<g transform="matrix(0.800798,0.462341,0.769972,-1.33363,-1677.22,-896.858)">
<path
d="M2241.55,-28.184C2239.1,-28.989 2236.83,-29.204 2234.68,-29.295C2234.68,-29.295 2220.82,-29.3 2215.03,-29.303C2213.48,-29.303 2212.05,-28.808 2211.28,-28.004C2208.65,-25.275 2202.56,-18.936 2199.45,-15.709C2199.07,-15.306 2199.07,-14.809 2199.46,-14.406C2199.85,-14.004 2200.57,-13.758 2201.34,-13.761C2208.36,-13.788 2222.72,-13.845 2222.72,-13.845C2223.98,-13.851 2225.44,-13.657 2227.06,-13.117L2241.55,-28.184Z"
style="fill: rgb(141, 81, 249)"
/>
</g>
<g transform="matrix(-4.32309,0,0,12.4454,9610.35,-1450.35)">
<path
d="M2205.31,121.966C2205.31,121.88 2205.18,121.8 2204.96,121.757C2204.74,121.714 2204.48,121.714 2204.27,121.757C2201.75,122.263 2195.36,123.547 2192.85,124.052C2192.63,124.095 2192.5,124.174 2192.5,124.261C2192.5,124.347 2192.63,124.426 2192.85,124.469C2195.36,124.974 2201.75,126.255 2204.27,126.759C2204.48,126.802 2204.74,126.802 2204.96,126.759C2205.18,126.716 2205.31,126.636 2205.31,126.55C2205.31,125.541 2205.31,122.976 2205.31,121.966Z"
style="fill: rgb(104, 0, 197)"
/>
<clipPath id="_clip5">
d="M2205.31,121.966C2205.31,121.88 2205.18,121.8 2204.96,121.757C2204.74,121.714 2204.48,121.714 2204.27,121.757C2201.75,122.263 2195.36,123.547 2192.85,124.052C2192.63,124.095 2192.5,124.174 2192.5,124.261C2192.5,124.347 2192.63,124.426 2192.85,124.469C2195.36,124.974 2201.75,126.255 2204.27,126.759C2204.48,126.802 2204.74,126.802 2204.96,126.759C2205.18,126.716 2205.31,126.636 2205.31,126.55C2205.31,125.541 2205.31,122.976 2205.31,121.966Z" />
</clipPath>
<g clip-path="url(#_clip5)">
<g transform="matrix(0.124502,0.074907,0.206623,-0.0414384,1997.62,-7.40235)">
<path
d="M2205.31,121.966C2205.31,121.88 2205.18,121.8 2204.96,121.757C2204.74,121.714 2204.48,121.714 2204.27,121.757C2201.75,122.263 2195.36,123.547 2192.85,124.052C2192.63,124.095 2192.5,124.174 2192.5,124.261C2192.5,124.347 2192.63,124.426 2192.85,124.469C2195.36,124.974 2201.75,126.255 2204.27,126.759C2204.48,126.802 2204.74,126.802 2204.96,126.759C2205.18,126.716 2205.31,126.636 2205.31,126.55C2205.31,125.541 2205.31,122.976 2205.31,121.966Z"
/>
</clipPath>
<g clip-path="url(#_clip5)">
<g transform="matrix(0.124502,0.074907,0.206623,-0.0414384,1997.62,-7.40235)">
<path
d="M1726.17,-64.249L1708.16,-72.303L1708.05,-23.514L1721.88,-32.386C1722.96,-33.241 1723.09,-33.944 1723.15,-34.636L1723.15,-54.373C1723.19,-56.238 1724.96,-57.594 1726.87,-56.686L1726.17,-64.249Z"
style="fill: url(#_Linear6)"
/>
</g>
<g transform="matrix(-0.126036,0.0767377,0.569859,0.112933,2435.01,-3.09225)">
<path
d="M1726.17,-45.661L1704.47,-40.254C1706.28,-40.527 1708.14,-40.212 1708.16,-39.416L1708.16,-18.976L1726.17,-18.976L1726.17,-45.661Z"
style="fill: rgb(141, 81, 249)"
/>
</g>
<g transform="matrix(-0.126036,0.0767377,0.569859,0.112933,2435.01,-3.09225)">
<path
d="M1726.17,-45.661L1726.17,-18.976L1708.16,-18.976L1708.16,-39.416C1707.79,-40.732 1704.5,-40.298 1702.68,-40.025L1726.17,-45.661ZM1705.49,-40.491C1706.2,-40.507 1706.87,-40.464 1707.4,-40.327C1708.01,-40.173 1708.48,-39.899 1708.62,-39.436C1708.62,-39.429 1708.62,-39.423 1708.62,-39.416L1708.62,-19.152C1708.62,-19.152 1725.72,-19.152 1725.72,-19.152L1725.72,-45.345L1705.49,-40.491Z"
style="fill: url(#_Radial7)"
/>
</g>
d="M1726.17,-64.249L1708.16,-72.303L1708.05,-23.514L1721.88,-32.386C1722.96,-33.241 1723.09,-33.944 1723.15,-34.636L1723.15,-54.373C1723.19,-56.238 1724.96,-57.594 1726.87,-56.686L1726.17,-64.249Z"
style="fill: url(#_Linear6)" />
</g>
<g transform="matrix(-0.126036,0.0767377,0.569859,0.112933,2435.01,-3.09225)">
<path
d="M1726.17,-45.661L1704.47,-40.254C1706.28,-40.527 1708.14,-40.212 1708.16,-39.416L1708.16,-18.976L1726.17,-18.976L1726.17,-45.661Z"
style="fill: rgb(141, 81, 249)" />
</g>
<g transform="matrix(-0.126036,0.0767377,0.569859,0.112933,2435.01,-3.09225)">
<path
d="M1726.17,-45.661L1726.17,-18.976L1708.16,-18.976L1708.16,-39.416C1707.79,-40.732 1704.5,-40.298 1702.68,-40.025L1726.17,-45.661ZM1705.49,-40.491C1706.2,-40.507 1706.87,-40.464 1707.4,-40.327C1708.01,-40.173 1708.48,-39.899 1708.62,-39.436C1708.62,-39.429 1708.62,-39.423 1708.62,-39.416L1708.62,-19.152C1708.62,-19.152 1725.72,-19.152 1725.72,-19.152L1725.72,-45.345L1705.49,-40.491Z"
style="fill: url(#_Radial7)" />
</g>
</g>
</g>
</g>
<defs>
<linearGradient
id="_Linear1"
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-70.0711,-0.927611,1.54482,-42.0752,2233.59,-20.1891)"
>
<stop offset="0" style="stop-color: rgb(141, 81, 249); stop-opacity: 1" />
<stop offset="1" style="stop-color: rgb(116, 50, 223); stop-opacity: 1" />
</linearGradient>
<linearGradient
id="_Linear2"
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(4.78193e-15,-78.0949,78.0949,4.78193e-15,2195.72,354.021)"
>
<stop offset="0" style="stop-color: rgb(141, 81, 249); stop-opacity: 1" />
<stop offset="1" style="stop-color: rgb(116, 50, 223); stop-opacity: 1" />
</linearGradient>
<linearGradient
id="_Linear3"
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(41.6089,41.5866,-41.5866,41.6089,2282.31,262.837)"
>
<stop offset="0" style="stop-color: rgb(211, 187, 255); stop-opacity: 1" />
<stop offset="1" style="stop-color: rgb(211, 187, 255); stop-opacity: 0" />
</linearGradient>
<linearGradient
id="_Linear4"
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(9.25616,16.7005,-16.7005,9.25616,2215,243.712)"
>
<stop offset="0" style="stop-color: rgb(211, 187, 255); stop-opacity: 1" />
<stop offset="1" style="stop-color: rgb(211, 187, 255); stop-opacity: 0" />
</linearGradient>
<linearGradient
id="_Linear6"
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-0.130164,-61.9937,59.4003,-0.135847,1711.63,-25.7957)"
>
<stop offset="0" style="stop-color: rgb(116, 50, 223); stop-opacity: 1" />
<stop offset="0.51" style="stop-color: rgb(110, 38, 217); stop-opacity: 1" />
<stop offset="1" style="stop-color: rgb(91, 0, 197); stop-opacity: 1" />
</linearGradient>
<radialGradient
id="_Radial7"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(13.8659,4.71436,-12.1609,5.37534,1708.16,-32.287)"
>
<stop offset="0" style="stop-color: rgb(211, 187, 255); stop-opacity: 1" />
<stop offset="1" style="stop-color: rgb(211, 187, 255); stop-opacity: 0" />
</radialGradient>
</defs>
</svg>
</div>
<div class="loading">
<div class="effect-1 effects"></div>
<div class="effect-2 effects"></div>
<div class="effect-3 effects"></div>
</div>
</g>
<defs>
<linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-70.0711,-0.927611,1.54482,-42.0752,2233.59,-20.1891)">
<stop offset="0" style="stop-color: rgb(141, 81, 249); stop-opacity: 1" />
<stop offset="1" style="stop-color: rgb(116, 50, 223); stop-opacity: 1" />
</linearGradient>
<linearGradient id="_Linear2" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse"
gradientTransform="matrix(4.78193e-15,-78.0949,78.0949,4.78193e-15,2195.72,354.021)">
<stop offset="0" style="stop-color: rgb(141, 81, 249); stop-opacity: 1" />
<stop offset="1" style="stop-color: rgb(116, 50, 223); stop-opacity: 1" />
</linearGradient>
<linearGradient id="_Linear3" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse"
gradientTransform="matrix(41.6089,41.5866,-41.5866,41.6089,2282.31,262.837)">
<stop offset="0" style="stop-color: rgb(211, 187, 255); stop-opacity: 1" />
<stop offset="1" style="stop-color: rgb(211, 187, 255); stop-opacity: 0" />
</linearGradient>
<linearGradient id="_Linear4" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse"
gradientTransform="matrix(9.25616,16.7005,-16.7005,9.25616,2215,243.712)">
<stop offset="0" style="stop-color: rgb(211, 187, 255); stop-opacity: 1" />
<stop offset="1" style="stop-color: rgb(211, 187, 255); stop-opacity: 0" />
</linearGradient>
<linearGradient id="_Linear6" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-0.130164,-61.9937,59.4003,-0.135847,1711.63,-25.7957)">
<stop offset="0" style="stop-color: rgb(116, 50, 223); stop-opacity: 1" />
<stop offset="0.51" style="stop-color: rgb(110, 38, 217); stop-opacity: 1" />
<stop offset="1" style="stop-color: rgb(91, 0, 197); stop-opacity: 1" />
</linearGradient>
<radialGradient id="_Radial7" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse"
gradientTransform="matrix(13.8659,4.71436,-12.1609,5.37534,1708.16,-32.287)">
<stop offset="0" style="stop-color: rgb(211, 187, 255); stop-opacity: 1" />
<stop offset="1" style="stop-color: rgb(211, 187, 255); stop-opacity: 0" />
</radialGradient>
</defs>
</svg>
</div>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
<script>
const loaderColor = localStorage.getItem('materio-initial-loader-bg') || '#FFFFFF'
const primaryColor = localStorage.getItem('materio-initial-loader-color') || '#9155FD'
<div class="loading">
<div class="effect-1 effects"></div>
<div class="effect-2 effects"></div>
<div class="effect-3 effects"></div>
</div>
</div>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
<script>
const loaderColor = localStorage.getItem('materio-initial-loader-bg') || '#FFFFFF'
const primaryColor = localStorage.getItem('materio-initial-loader-color') || '#9155FD'
if (loaderColor) document.documentElement.style.setProperty('--initial-loader-bg', loaderColor)
if (loaderColor)
document.documentElement.style.setProperty('--initial-loader-bg', loaderColor)
if (primaryColor) document.documentElement.style.setProperty('--initial-loader-color', primaryColor)
</script>
</body>
</html>
if (primaryColor)
document.documentElement.style.setProperty('--initial-loader-color', primaryColor)
</script>
</body>
</html>

View File

@@ -1,6 +1,6 @@
{
"name": "moviepilot",
"version": "1.5.9",
"version": "1.7.2",
"private": true,
"bin": "dist/service.js",
"scripts": {
@@ -24,6 +24,7 @@
"@floating-ui/dom": "1.2.8",
"@vueuse/core": "^10.1.2",
"@vueuse/math": "^10.1.2",
"ace-builds": "^1.32.6",
"apexcharts-clevision": "^3.28.5",
"axios": "1.4.0",
"axios-mock-adapter": "^1.21.4",
@@ -48,6 +49,7 @@
"vue-prism-component": "^2.0.0",
"vue-router": "^4.2.0",
"vue-toast-notification": "^3",
"vue3-ace-editor": "^2.2.4",
"vue3-apexcharts": "^1.4.1",
"vue3-perfect-scrollbar": "^1.6.0",
"vuetify": "3.3.5",

View File

@@ -1,8 +1,13 @@
<script lang="ts" setup>
import { useToast } from 'vue-toast-notification'
import { useTheme } from 'vuetify'
import store from './store'
// 提示框
const $toast = useToast()
// 设置主题
function setTheme() {
const { global: globalTheme } = useTheme()
let theme = localStorage.getItem('theme') || 'light'
@@ -10,11 +15,6 @@ function setTheme() {
theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
globalTheme.name.value = theme
}
// 第一时间应用主题
setTheme()
// 提示框
const $toast = useToast()
// SSE持续接收消息
function startSSEMessager() {
@@ -38,6 +38,7 @@ function startSSEMessager() {
// 页面加载时,加载当前用户数据
onBeforeMount(async () => {
setTheme()
startSSEMessager()
})
</script>

54
src/ace-config.ts Normal file
View File

@@ -0,0 +1,54 @@
import ace from 'ace-builds'
import modeJsonUrl from 'ace-builds/src-noconflict/mode-json?url'
import modeJavascriptUrl from 'ace-builds/src-noconflict/mode-javascript?url'
import modeHtmlUrl from 'ace-builds/src-noconflict/mode-html?url'
import modeYamlUrl from 'ace-builds/src-noconflict/mode-yaml?url'
import themeGithubUrl from 'ace-builds/src-noconflict/theme-github?url'
import themeChromeUrl from 'ace-builds/src-noconflict/theme-chrome?url'
import themeMonokaiUrl from 'ace-builds/src-noconflict/theme-monokai?url'
import workerBaseUrl from 'ace-builds/src-noconflict/worker-base?url'
import workerJsonUrl from 'ace-builds/src-noconflict/worker-json?url'
import workerJavascriptUrl from 'ace-builds/src-noconflict/worker-javascript?url'
import workerHtmlUrl from 'ace-builds/src-noconflict/worker-html?url'
import workerYamlUrl from 'ace-builds/src-noconflict/worker-yaml?url'
import snippetsHtmlUrl from 'ace-builds/src-noconflict/snippets/html?url'
import snippetsJsUrl from 'ace-builds/src-noconflict/snippets/javascript?url'
import snippetsYamlUrl from 'ace-builds/src-noconflict/snippets/yaml?url'
import snippetsJsonUrl from 'ace-builds/src-noconflict/snippets/json?url'
import 'ace-builds/src-noconflict/ext-language_tools'
ace.config.setModuleUrl('ace/mode/json', modeJsonUrl)
ace.config.setModuleUrl('ace/mode/javascript', modeJavascriptUrl)
ace.config.setModuleUrl('ace/mode/html', modeHtmlUrl)
ace.config.setModuleUrl('ace/mode/yaml', modeYamlUrl)
ace.config.setModuleUrl('ace/theme/github', themeGithubUrl)
ace.config.setModuleUrl('ace/theme/chrome', themeChromeUrl)
ace.config.setModuleUrl('ace/theme/monokai', themeMonokaiUrl)
ace.config.setModuleUrl('ace/mode/base', workerBaseUrl)
ace.config.setModuleUrl('ace/mode/json_worker', workerJsonUrl)
ace.config.setModuleUrl('ace/mode/javascript_worker', workerJavascriptUrl)
ace.config.setModuleUrl('ace/mode/html_worker', workerHtmlUrl)
ace.config.setModuleUrl('ace/mode/yaml_worker', workerYamlUrl)
ace.config.setModuleUrl('ace/snippets/html', snippetsHtmlUrl)
ace.config.setModuleUrl('ace/snippets/javascript', snippetsJsUrl)
ace.config.setModuleUrl('ace/snippets/javascript', snippetsYamlUrl)
ace.config.setModuleUrl('ace/snippets/json', snippetsJsonUrl)
ace.require('ace/ext/language_tools')

View File

@@ -80,6 +80,9 @@ export interface Subscribe {
// 是否洗版数字或者boolean
best_version: any
// 使用 imdbid 搜索
search_imdbid?: any
// 当前优先级
current_priority: number
@@ -510,8 +513,11 @@ export interface DownloadingInfo {
// 媒体信息
media: { [key: string]: any }
// 下载用户
// 下载用户ID
userid?: string
// 下载用户名称
username?: string
}
// 缺失剧集信息
@@ -658,6 +664,9 @@ export interface TorrentInfo {
// 剩余免费时间
freedate_diff: string
// 种子类型
category: string
}
// 识别元数据
@@ -877,6 +886,9 @@ export interface ScheduleInfo {
// 名称
name: string
// 提供者
provider: string
// 状态
status: string
@@ -895,6 +907,7 @@ export interface NotificationSwitch {
telegram: boolean
slack: boolean
synologychat: boolean
vocechat: boolean
}
// 环境设置

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@@ -36,6 +36,7 @@ const selectFilterOptions = ref<{ [key: string]: string }[]>([
{ title: '特效字幕', value: ' SPECSUB ' },
{ title: '中文字幕', value: ' CNSUB ' },
{ title: '国语配音', value: ' CNVOI ' },
{ title: '官种', value: ' GZ ' },
{ title: '排除: 国语配音', value: ' !CNVOI ' },
{ title: '粤语配音', value: ' HKVOI ' },
{ title: '排除: 粤语配音', value: ' !HKVOI ' },

View File

@@ -49,7 +49,7 @@ async function installPlugin() {
try {
// 显示等待提示框
progressDialog.value = true
progressText.value = `正在安装 ${props.plugin?.plugin_name} ${props?.plugin?.plugin_version} 插件...`
progressText.value = `正在安装 ${props.plugin?.plugin_name} v${props?.plugin?.plugin_version} ...`
const result: { [key: string]: any } = await api.get(
`plugin/install/${props.plugin?.id}`,
@@ -163,15 +163,6 @@ const dropdownItems = ref([
</VMenu>
</IconBtn>
</div>
<div
v-if="props.plugin?.has_update"
class="me-n3 absolute top-0 left-1"
>
<VIcon
icon="mdi-new-box"
class="text-white"
/>
</div>
<VAvatar
size="8rem"
>

View File

@@ -8,6 +8,7 @@ import PageRender from '@/components/render/PageRender.vue'
import { isNullOrEmptyObject } from '@core/utils'
import noImage from '@images/logos/plugin.png'
import { getDominantColor } from '@/@core/utils/image'
import store from '@/store'
// 输入参数
const props = defineProps({
@@ -40,12 +41,18 @@ const pluginConfigDialog = ref(false)
// 插件配置表单数据
const pluginConfigForm = ref({})
// 进度框
const progressDialog = ref(false)
// 插件表单配置项
let pluginFormItems = reactive([])
// 插件数据页面
const pluginInfoDialog = ref(false)
// 进度框文本
const progressText = ref('正在更新插件...')
// 插件数据页面配置项
let pluginPageItems = reactive([])
@@ -82,7 +89,12 @@ async function uninstallPlugin() {
return
try {
// 显示等待提示框
progressDialog.value = true
progressText.value = `正在卸载 ${props.plugin?.plugin_name} ...`
const result: { [key: string]: any } = await api.delete(`plugin/${props.plugin?.id}`)
// 隐藏等待提示框
progressDialog.value = false
if (result.success) {
$toast.success(`插件 ${props.plugin?.plugin_name} 已卸载`)
@@ -220,11 +232,53 @@ async function resetPlugin() {
}
}
// 更新插件
async function updatePlugin() {
try {
// 显示等待提示框
progressDialog.value = true
progressText.value = `正在更新 ${props.plugin?.plugin_name} ...`
const result: { [key: string]: any } = await api.get(
`plugin/install/${props.plugin?.id}`,
{
params: {
repo_url: props.plugin?.repo_url,
force: true,
},
},
)
// 隐藏等待提示框
progressDialog.value = false
if (result.success) {
$toast.success(`插件 ${props.plugin?.plugin_name} 更新成功!`)
// 通知父组件刷新
emit('save')
}
else {
$toast.error(`插件 ${props.plugin?.plugin_name} 更新失败:${result.message}`)
}
}
catch (error) {
console.error(error)
}
}
// 访问作者主页
function visitAuthorPage() {
window.open(props.plugin?.author_url, '_blank')
}
// 查看日志URL
function openLoggerWindow() {
const token = store.state.auth.token
const url = `${import.meta.env.VITE_API_BASE_URL}system/logging?token=${token}&length=-1&logfile=plugins/${props.plugin?.id?.toLowerCase()}.log`
window.open(url, '_blank')
}
// 弹出菜单
const dropdownItems = ref([
{
@@ -246,8 +300,18 @@ const dropdownItems = ref([
},
},
{
title: '重置',
title: '更新',
value: 3,
show: props.plugin?.has_update,
props: {
prependIcon: 'mdi-cancel',
color: 'success',
click: updatePlugin,
},
},
{
title: '重置',
value: 4,
show: true,
props: {
prependIcon: 'mdi-cancel',
@@ -257,7 +321,7 @@ const dropdownItems = ref([
},
{
title: '卸载',
value: 4,
value: 5,
show: true,
props: {
prependIcon: 'mdi-trash-can-outline',
@@ -265,9 +329,20 @@ const dropdownItems = ref([
click: uninstallPlugin,
},
},
{
title: '查看日志',
value: 6,
show: true,
props: {
prependIcon: 'mdi-file-document-outline',
click: () => {
openLoggerWindow()
},
},
},
{
title: '作者主页',
value: 4,
value: 7,
show: true,
props: {
prependIcon: 'mdi-home-circle-outline',
@@ -275,6 +350,13 @@ const dropdownItems = ref([
},
},
])
// 监听插件状态变化
watch(() => props.plugin?.has_update, (newHasUpdate, oldHasUpdate) => {
const updateItemIndex = dropdownItems.value.findIndex(item => item.value === 3)
if (updateItemIndex !== -1)
dropdownItems.value[updateItemIndex].show = newHasUpdate
})
</script>
<template>
@@ -294,6 +376,15 @@ const dropdownItems = ref([
class="relative pa-4 text-center card-cover-blurred"
:style="{ background: `${backgroundColor}` }"
>
<div
v-if="props.plugin?.has_update"
class="me-n3 absolute top-0 left-1"
>
<VIcon
icon="mdi-new-box"
class="text-white"
/>
</div>
<div class="me-n3 absolute top-0 right-3">
<IconBtn>
<VIcon icon="mdi-dots-vertical" class="text-white" />
@@ -411,6 +502,25 @@ const dropdownItems = ref([
</VCardActions>
</VCard>
</VDialog>
<!-- 更新插件进度框 -->
<VDialog
v-model="progressDialog"
:scrim="false"
width="25rem"
>
<VCard
color="primary"
>
<VCardText class="text-center">
{{ progressText }}
<VProgressLinear
indeterminate
color="white"
class="mb-0 mt-1"
/>
</VCardText>
</VCard>
</VDialog>
</template>
<style lang="scss" scoped>

View File

@@ -80,6 +80,7 @@ const resourceItemsPerPage = ref(25)
const userPwForm = ref({
username: '',
password: '',
code: '',
})
// 打开种子详情页面
@@ -152,6 +153,7 @@ async function updateSiteCookie() {
params: {
username: userPwForm.value.username,
password: userPwForm.value.password,
code: userPwForm.value.code,
},
},
)
@@ -335,7 +337,7 @@ onMounted(() => {
<VRow>
<VCol
cols="12"
md="6"
md="4"
>
<VTextField
v-model="userPwForm.username"
@@ -345,7 +347,7 @@ onMounted(() => {
</VCol>
<VCol
cols="12"
md="6"
md="4"
>
<VTextField
v-model="userPwForm.password"
@@ -359,6 +361,15 @@ onMounted(() => {
@keydown.enter="updateSiteCookie"
/>
</VCol>
<VCol
cols="12"
md="4"
>
<VTextField
v-model="userPwForm.code"
label="两步验证"
/>
</VCol>
</VRow>
</VForm>
</VCardText>

View File

@@ -1,10 +1,11 @@
<script lang="ts" setup>
<script lang='ts' setup>
import { useToast } from 'vue-toast-notification'
import SubscribeEditForm from '../form/SubscribeEditForm.vue'
import { calculateTimeDifference } from '@/@core/utils'
import { formatSeason } from '@/@core/utils/formatters'
import api from '@/api'
import type { Subscribe } from '@/api/types'
import router from '@/router'
// 输入参数
const props = defineProps({
@@ -55,7 +56,7 @@ function getPercentage() {
return Math.round(
(((props.media?.total_episode ?? 0) - (props.media?.lack_episode ?? 0))
/ (props.media?.total_episode ?? 1))
* 100,
* 100,
)
}
@@ -126,8 +127,28 @@ const dropdownItems = ref([
},
},
{
title: '取消订阅',
title: '查看详情',
value: 3,
props: {
prependIcon: 'mdi-open-in-new',
click: () => {
router.push({
path: '/media',
query: {
mediaid: `${
props.media?.tmdbid
? `tmdb:${props.media?.tmdbid}`
: `douban:${props.media?.doubanid}`
}`,
type: props.media?.type,
},
})
},
},
},
{
title: '取消订阅',
value: 4,
props: {
prependIcon: 'mdi-trash-can-outline',
color: 'error',
@@ -162,7 +183,7 @@ const dropdownItems = ref([
</template>
<VCardTitle :class="getTextClass()">
{{ props.media?.name }}
{{ formatSeason(props.media?.season ? props.media?.season.toString() : "") }}
{{ formatSeason(props.media?.season ? props.media?.season.toString() : '') }}
</VCardTitle>
<template #append>
<div class="me-n3">
@@ -252,7 +273,8 @@ const dropdownItems = ref([
<VIcon
icon="mdi-download"
class="me-1"
/> {{ lastUpdateText }}
/>
{{ lastUpdateText }}
</VCardText>
<VProgressLinear
v-if="getPercentage() > 0"

View File

@@ -267,6 +267,28 @@ async function recognize(path: string) {
}
}
// 调用API刮削
async function scrape(path: string) {
try {
// 显示进度条
progressDialog.value = true
progressText.value = `正在刮削 ${path} ...`
const result: { [key: string]: any } = await api.get('media/scrape', {
params: {
path,
},
})
// 关闭进度条
progressDialog.value = false
if (!result.success)
$toast.error(result.message)
else
$toast.success(`${path}削刮完成!`)
}
catch (error) {
console.error(error)
}
}
// 弹出菜单
const dropdownItems = ref([
{
@@ -279,8 +301,17 @@ const dropdownItems = ref([
},
},
}, {
title: '重命名',
title: '刮削',
value: 2,
props: {
prependIcon: 'mdi-auto-fix',
click: (_item: FileItem) => {
scrape(_item.path || '')
},
},
}, {
title: '重命名',
value: 3,
props: {
prependIcon: 'mdi-rename',
click: showRenmae,
@@ -288,7 +319,7 @@ const dropdownItems = ref([
},
{
title: '整理',
value: 3,
value: 4,
props: {
prependIcon: 'mdi-folder-arrow-right',
click: showTransfer,
@@ -296,7 +327,7 @@ const dropdownItems = ref([
},
{
title: '删除',
value: 4,
value: 5,
props: {
prependIcon: 'mdi-delete-outline',
color: 'error',
@@ -345,111 +376,173 @@ onMounted(() => {
<VCardText v-else-if="dirs.length || files.length" class="p-0">
<VList v-if="dirs.length" subheader>
<VListSubheader>目录</VListSubheader>
<VListItem
<VHover
v-for="(item, index) in dirs"
:key="index"
class="px-3 pe-1"
@click="changePath(item.path)"
>
<template #prepend>
<VIcon icon="mdi-folder-outline" />
</template>
<VListItemTitle v-text="item.name" />
<template #append>
<IconBtn class="d-sm-none">
<VIcon
icon="mdi-dots-vertical"
/>
<VMenu
activator="parent"
close-on-content-click
>
<VList>
<VListItem
v-for="(menu, i) in dropdownItems"
:key="i"
variant="plain"
:base-color="menu.props.color"
@click="menu.props.click(item)"
<template #default="hover">
<VListItem
v-bind="hover.props"
class="px-3 pe-1"
@click="changePath(item.path)"
>
<template #prepend>
<VIcon icon="mdi-folder-outline" />
</template>
<VListItemTitle v-text="item.name" />
<template #append>
<IconBtn class="d-sm-none">
<VIcon
icon="mdi-dots-vertical"
/>
<VMenu
activator="parent"
close-on-content-click
>
<template #prepend>
<VIcon :icon="menu.props.prependIcon" />
<VList>
<VListItem
v-for="(menu, i) in dropdownItems"
:key="i"
variant="plain"
:base-color="menu.props.color"
@click="menu.props.click(item)"
>
<template #prepend>
<VIcon :icon="menu.props.prependIcon" />
</template>
<VListItemTitle v-text="menu.title" />
</VListItem>
</VList>
</VMenu>
</IconBtn>
<span v-show="hover.isHovering" class="flex">
<VTooltip text="识别">
<template #activator="{ props }">
<IconBtn v-bind="props" class="d-none d-sm-block" @click.stop="recognize(item.path)">
<VIcon icon="mdi-text-recognition" />
</IconBtn>
</template>
<VListItemTitle v-text="menu.title" />
</VListItem>
</VList>
</VMenu>
</IconBtn>
<IconBtn class="d-none d-sm-block" @click.stop="recognize(item.path)">
<VIcon icon="mdi-text-recognition" />
</IconBtn>
<IconBtn class="d-none d-sm-block" @click.stop="showRenmae(item)">
<VIcon icon="mdi-rename" />
</IconBtn>
<IconBtn class="d-none d-sm-block" @click.stop="showTransfer(item)">
<VIcon icon="mdi-folder-arrow-right" />
</IconBtn>
<IconBtn class="d-none d-sm-block" @click.stop="deleteItem(item)">
<VIcon icon="mdi-delete-outline" />
</IconBtn>
</VTooltip>
<VTooltip text="刮削">
<template #activator="{ props }">
<IconBtn v-bind="props" class="d-none d-sm-block" @click.stop="scrape(item.path)">
<VIcon icon="mdi-auto-fix" />
</IconBtn>
</template>
</VTooltip>
<VTooltip text="重命名">
<template #activator="{ props }">
<IconBtn v-bind="props" class="d-none d-sm-block" @click.stop="showRenmae(item)">
<VIcon icon="mdi-rename" />
</IconBtn>
</template>
</VTooltip>
<VTooltip text="整理">
<template #activator="{ props }">
<IconBtn v-bind="props" class="d-none d-sm-block" @click.stop="showTransfer(item)">
<VIcon icon="mdi-folder-arrow-right" />
</IconBtn>
</template>
</VTooltip>
<VTooltip text="删除">
<template #activator="{ props }">
<IconBtn v-bind="props" class="d-none d-sm-block" @click.stop="deleteItem(item)">
<VIcon icon="mdi-delete-outline" color="error" />
</IconBtn>
</template>
</VTooltip>
</span>
</template>
</VListItem>
</template>
</VListItem>
</VHover>
</VList>
<VDivider v-if="dirs.length && files.length" />
<VList v-if="files.length" subheader>
<VListSubheader>文件</VListSubheader>
<VListItem
<VHover
v-for="(item, index) in files"
:key="index"
class="pl-3 pe-1"
@click="changePath(item.path)"
>
<template #prepend>
<VIcon v-if="inProps.icons" :icon="inProps.icons[item.extension.toLowerCase()] || inProps.icons?.other" />
</template>
<template #default="hover">
<VListItem
v-bind="hover.props"
class="pl-3 pe-1"
@click="changePath(item.path)"
>
<template #prepend>
<VIcon v-if="inProps.icons" :icon="inProps.icons[item.extension.toLowerCase()] || inProps.icons?.other" />
</template>
<VListItemTitle v-text="item.name" />
<VListItemSubtitle> {{ formatBytes(item.size) }}</VListItemSubtitle>
<VListItemTitle v-text="item.name" />
<VListItemSubtitle> {{ formatBytes(item.size) }}</VListItemSubtitle>
<template #append>
<IconBtn class="d-sm-none">
<VIcon
icon="mdi-dots-vertical"
/>
<VMenu
activator="parent"
close-on-content-click
>
<VList>
<VListItem
v-for="(menu, i) in dropdownItems"
:key="i"
variant="plain"
:base-color="menu.props.color"
@click="menu.props.click(item)"
<template #append>
<IconBtn class="d-sm-none">
<VIcon
icon="mdi-dots-vertical"
/>
<VMenu
activator="parent"
close-on-content-click
>
<template #prepend>
<VIcon :icon="menu.props.prependIcon" />
<VList>
<VListItem
v-for="(menu, i) in dropdownItems"
:key="i"
variant="plain"
:base-color="menu.props.color"
@click="menu.props.click(item)"
>
<template #prepend>
<VIcon :icon="menu.props.prependIcon" />
</template>
<VListItemTitle v-text="menu.title" />
</VListItem>
</VList>
</VMenu>
</IconBtn>
<span v-show="hover.isHovering" class="flex">
<VTooltip text="识别">
<template #activator="{ props }">
<IconBtn v-bind="props" class="d-none d-sm-block" @click.stop="recognize(item.path)">
<VIcon icon="mdi-text-recognition" />
</IconBtn>
</template>
<VListItemTitle v-text="menu.title" />
</VListItem>
</VList>
</VMenu>
</IconBtn>
<IconBtn class="d-none d-sm-block" @click.stop="recognize(item.path)">
<VIcon icon="mdi-text-recognition" />
</IconBtn>
<IconBtn class="d-none d-sm-block" @click.stop="showRenmae(item)">
<VIcon icon="mdi-rename" />
</IconBtn>
<IconBtn class="d-none d-sm-block" @click.stop="showTransfer(item)">
<VIcon icon="mdi-folder-arrow-right" />
</IconBtn>
<IconBtn class="d-none d-sm-block" @click.stop="deleteItem(item)">
<VIcon icon="mdi-delete-outline" />
</IconBtn>
</VTooltip>
<VTooltip text="刮削">
<template #activator="{ props }">
<IconBtn v-bind="props" class="d-none d-sm-block" @click.stop="scrape(item.path)">
<VIcon icon="mdi-auto-fix" />
</IconBtn>
</template>
</VTooltip>
<VTooltip text="重命名">
<template #activator="{ props }">
<IconBtn v-bind="props" class="d-none d-sm-block" @click.stop="showRenmae(item)">
<VIcon icon="mdi-rename" />
</IconBtn>
</template>
</VTooltip>
<VTooltip text="整理">
<template #activator="{ props }">
<IconBtn v-bind="props" class="d-none d-sm-block" @click.stop="showTransfer(item)">
<VIcon icon="mdi-folder-arrow-right" />
</IconBtn>
</template>
</VTooltip>
<VTooltip text="删除">
<template #activator="{ props }">
<IconBtn v-bind="props" class="d-none d-sm-block" @click.stop="deleteItem(item)">
<VIcon icon="mdi-delete-outline" color="error" />
</IconBtn>
</template>
</VTooltip>
</span>
</template>
</VListItem>
</template>
</VListItem>
</VHover>
</VList>
</VCardText>
<VCardText

View File

@@ -144,19 +144,31 @@ const sortIcon = computed(() => {
</template>
</VToolbarItems>
<div class="flex-grow-1" />
<IconBtn @click="changeSort">
<VIcon :icon="sortIcon" />
</IconBtn>
<IconBtn v-if="pathSegments.length > 0" @click="goUp">
<VIcon icon="mdi-arrow-up-bold-outline" />
</IconBtn>
<VTooltip text="调整排序">
<template #activator="{ props }">
<IconBtn v-bind="props" @click="changeSort">
<VIcon :icon="sortIcon" />
</IconBtn>
</template>
</VTooltip>
<VTooltip text="返回上一级" v-if="pathSegments.length > 0">
<template #activator="{ props }">
<IconBtn v-bind="props" @click="goUp">
<VIcon icon="mdi-arrow-up-bold-outline" />
</IconBtn>
</template>
</VTooltip>
<VDialog
v-model="newFolderPopper"
max-width="50rem"
>
<template #activator="{ props }">
<IconBtn title="新建文件夹" v-bind="props">
<VIcon icon="mdi-folder-plus-outline" />
<IconBtn v-bind="props">
<VTooltip text="新建文件夹">
<template #activator="{ props: _props }">
<VIcon v-bind="_props" icon="mdi-folder-plus-outline" />
</template>
</VTooltip>
</IconBtn>
</template>
<VCard title="新建文件夹">

View File

@@ -121,6 +121,9 @@ async function updateSiteInfo() {
<template>
<VDialog
scrollable
:close-on-back="false"
persistent
eager
max-width="60rem"
>
<VCard

View File

@@ -30,6 +30,7 @@ const subscribeForm = ref<Subscribe>({
total_episode: 0,
start_episode: 0,
best_version: 0,
search_imdbid: 0,
sites: [],
type: '',
name: '',
@@ -99,6 +100,7 @@ async function getSubscribeInfo() {
)
subscribeForm.value = result
subscribeForm.value.best_version = subscribeForm.value.best_version === 1
subscribeForm.value.search_imdbid = subscribeForm.value.search_imdbid === 1
}
catch (e) {
console.log(e)
@@ -326,7 +328,6 @@ watchEffect(() => {
<VRow>
<VCol
cols="12"
md="4"
>
<VTextField
v-model="subscribeForm.save_path"
@@ -344,6 +345,15 @@ watchEffect(() => {
label="洗版"
/>
</VCol>
<VCol
cols="12"
md="4"
>
<VSwitch
v-model="subscribeForm.search_imdbid"
label="使用 ImdbID 搜索"
/>
</VCol>
</VRow>
</VForm>
</VCardText>

View File

@@ -32,23 +32,58 @@ const formData = ref<any>(elementProps.form || {})
<template>
<Component
:is="formItem.component"
v-if="!formItem.html"
v-if="!formItem.html && !!formItem.props?.modelvalue"
v-bind="formItem.props"
v-model="formData[formItem.props?.model || '']"
v-model:value="formData[formItem.props?.modelvalue]"
>
{{ formItem.text }}
<FormRender
<template
v-for="(innerItem, innerIndex) in (formItem.content || [])"
:key="innerIndex"
v-model="formData[innerItem.props?.model || '']"
:config="innerItem"
:form="formData"
/>
>
<FormRender
v-if="!!innerItem.props?.modelvalue"
v-model:value="formData[innerItem.props?.modelvalue]"
:config="innerItem"
:form="formData"
/>
<FormRender
v-else
v-model="formData[innerItem.props?.model]"
:config="innerItem"
:form="formData"
/>
</template>
</Component>
<Component
:is="formItem.component"
v-if="formItem.html"
v-else-if="formItem.html"
v-bind="formItem.props"
v-html="formItem.html"
/>
<Component
:is="formItem.component"
v-else
v-bind="formItem.props"
v-model="formData[formItem.props?.model]"
>
{{ formItem.text }}
<template
v-for="(innerItem, innerIndex) in (formItem.content || [])"
:key="innerIndex"
>
<FormRender
v-if="!!innerItem.props?.modelvalue"
v-model:value="formData[innerItem.props?.modelvalue]"
:config="innerItem"
:form="formData"
/>
<FormRender
v-else
v-model="formData[innerItem.props?.model]"
:config="innerItem"
:form="formData"
/>
</template>
</Component>
</template>

View File

@@ -84,7 +84,7 @@ function openSearchDialog() {
<VTextField
key="search_navbar"
v-model="searchWord"
class="d-none d-lg-block text-disabled"
class="d-none d-lg-block text-disabled search-box"
density="compact"
variant="solo"
label="搜索电影、电视剧"
@@ -98,3 +98,9 @@ function openSearchDialog() {
/>
</span>
</template>
<style lang="scss">
.search-box div.v-input__control div[role="textbox"] {
border: 1px solid rgb(var(--v-theme-background));
}
</style>

View File

@@ -3,6 +3,8 @@ import NameTestView from '@/views/system/NameTestView.vue'
import NetTestView from '@/views/system/NetTestView.vue'
import LoggingView from '@/views/system/LoggingView.vue'
import RuleTestView from '@/views/system/RuleTestView.vue'
import ModuleTestView from '@/views/system/ModuleTestView.vue'
import store from '@/store'
// App捷径
const appsMenu = ref(false)
@@ -18,6 +20,15 @@ const loggingDialog = ref(false)
// 过滤规则弹窗
const ruleTestDialog = ref(false)
// 系统健康检查弹窗
const systemTestDialog = ref(false)
// 拼接全部日志url
function allLoggingUrl() {
const token = store.state.auth.token
return `${import.meta.env.VITE_API_BASE_URL}system/logging?token=${token}&length=-1`
}
</script>
<template>
@@ -72,6 +83,50 @@ const ruleTestDialog = ref(false)
<span class="text-sm">名称识别测试</span>
</VListItem>
</VCol>
<VCol
cols="6"
class="text-center cursor-pointer pa-0 shortcut-icon border-e"
@click="() => {}"
>
<VListItem
class="pa-4"
@click="ruleTestDialog = true"
>
<VAvatar
size="48"
variant="tonal"
>
<VIcon icon="mdi-filter-cog-outline" />
</VAvatar>
<h6 class="text-base font-weight-medium mt-2 mb-0">
优先级
</h6>
<span class="text-sm">优先级规则测试</span>
</VListItem>
</VCol>
</VRow>
<VRow class="ma-0 mt-n1 border-t">
<VCol
cols="6"
class="text-center cursor-pointer pa-0 shortcut-icon border-e"
@click="() => {}"
>
<VListItem
class="pa-4"
@click="loggingDialog = true"
>
<VAvatar
size="48"
variant="tonal"
>
<VIcon icon="mdi-file-document-outline" />
</VAvatar>
<h6 class="text-base font-weight-medium mt-2 mb-0">
日志
</h6>
<span class="text-sm">查看实时日志</span>
</VListItem>
</VCol>
<VCol
cols="6"
class="text-center cursor-pointer pa-0 shortcut-icon"
@@ -102,39 +157,18 @@ const ruleTestDialog = ref(false)
>
<VListItem
class="pa-4"
@click="loggingDialog = true"
@click="systemTestDialog = true"
>
<VAvatar
size="48"
variant="tonal"
>
<VIcon icon="mdi-file-document-outline" />
<VIcon icon="mdi-cog-outline" />
</VAvatar>
<h6 class="text-base font-weight-medium mt-2 mb-0">
日志
系统
</h6>
<span class="text-sm">系统实时日志</span>
</VListItem>
</VCol>
<VCol
cols="6"
class="text-center cursor-pointer pa-0 shortcut-icon border-e"
@click="() => {}"
>
<VListItem
class="pa-4"
@click="ruleTestDialog = true"
>
<VAvatar
size="48"
variant="tonal"
>
<VIcon icon="mdi-filter-cog-outline" />
</VAvatar>
<h6 class="text-base font-weight-medium mt-2 mb-0">
优先级
</h6>
<span class="text-sm">优先级规则测试</span>
<span class="text-sm">系统健康检查</span>
</VListItem>
</VCol>
</VRow>
@@ -171,8 +205,19 @@ const ruleTestDialog = ref(false)
class="w-full lg:w-4/5"
scrollable
>
<VCard title="实时日志">
<VCard>
<DialogCloseBtn @click="loggingDialog = false" />
<VCardItem>
<VCardTitle class="inline-flex">
实时日志
<a class="mx-2 inline-flex items-center justify-center" :href="allLoggingUrl()" target="_blank">
<div class="inline-flex cursor-pointer items-center rounded-full bg-gray-600 px-2 text-sm text-gray-200 ring-1 ring-gray-500 transition hover:bg-gray-700">
<VIcon icon="mdi-open-in-new" />
<span class="ms-1">在新窗口中打开</span>
</div>
</a>
</VCardTitle>
</VCardItem>
<VCardText>
<LoggingView />
</VCardText>
@@ -191,4 +236,17 @@ const ruleTestDialog = ref(false)
</VCardText>
</VCard>
</VDialog>
<!-- 系统健康检查弹窗 -->
<VDialog
v-model="systemTestDialog"
max-width="50rem"
scrollable
>
<VCard title="系统健康检查">
<DialogCloseBtn @click="systemTestDialog = false" />
<VCardText>
<ModuleTestView />
</VCardText>
</VCard>
</VDialog>
</template>

View File

@@ -1,14 +1,26 @@
<script lang="ts" setup>
import DefaultLayoutWithVerticalNav from './components/DefaultLayoutWithVerticalNav.vue'
import api from '@/api'
const router = useRouter()
const route = useRoute()
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
api.get('user/current')
.catch(() => {
router.replace('/login')
})
}
})
</script>
<template>
<DefaultLayoutWithVerticalNav>
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" v-if="$route.meta.keepAlive" :key="$route.fullPath" />
<component :is="Component" v-if="route.meta.keepAlive" :key="route.fullPath" />
</keep-alive>
<component :is="Component" v-if="!$route.meta.keepAlive" :key="$route.fullPath" />
<component :is="Component" v-if="!route.meta.keepAlive" :key="route.fullPath" />
</router-view>
</DefaultLayoutWithVerticalNav>
</template>

View File

@@ -1,7 +1,10 @@
import { VAceEditor } from 'vue3-ace-editor'
import { createApp } from 'vue'
import '@/@iconify/icons-bundle'
import ToastPlugin from 'vue-toast-notification'
import VuetifyUseDialog from 'vuetify-use-dialog'
import './ace-config'
import VueApexCharts from 'vue3-apexcharts'
import { removeEl } from './@core/utils/dom'
import App from '@/App.vue'
import vuetify from '@/plugins/vuetify'
@@ -15,10 +18,14 @@ import 'vue-toast-notification/dist/theme-bootstrap.css'
loadFonts()
// Create vue app
// 创建Vue实例
const app = createApp(App)
// Use plugins Mount vue app
// 注册全局组件
app.component('VAceEditor', VAceEditor)
.component('VApexChart', VueApexCharts)
// 注册插件
app
.use(vuetify)
.use(router)

View File

@@ -77,8 +77,8 @@ function login() {
store.dispatch('auth/updateUserName', username)
store.dispatch('auth/updateAvatar', avatar)
// 跳转到首页
router.push('/')
// 跳转到首页或回原始页面
router.push(store.state.auth.originalPath ?? '/')
})
.catch((error: any) => {
// 登录失败,显示错误提示

View File

@@ -8,6 +8,7 @@ import AccountSettingAbout from '@/views/setting/AccountSettingAbout.vue'
import AccountSettingSearch from '@/views/setting/AccountSettingSearch.vue'
import AccountSettingSubscribe from '@/views/setting/AccountSettingSubscribe.vue'
import AccountSettingService from '@/views/setting/AccountSettingService.vue'
import AccountSettingSystem from '@/views/setting/AccountSettingSystem.vue'
const route = useRoute()
@@ -20,6 +21,11 @@ const tabs = [
icon: 'mdi-account',
tab: 'account',
},
{
title: '系统',
icon: 'mdi-cog',
tab: 'system',
},
{
title: '站点',
icon: 'mdi-web',
@@ -83,6 +89,13 @@ const tabs = [
</transition>
</VWindowItem>
<!-- 系统 -->
<VWindowItem value="system">
<transition name="fade-slide" appear>
<AccountSettingSystem />
</transition>
</VWindowItem>
<!-- 站点 -->
<VWindowItem value="site">
<transition name="fade-slide" appear>

View File

@@ -80,6 +80,7 @@ export default {
// set v-rating default color to primary
color: 'rgba(var(--v-theme-on-background),0.23)',
activeColor: 'warning',
halfIncrements: true,
},
VProgressCircular: {
// set v-progress-circular default color to primary

View File

@@ -162,6 +162,7 @@ router.beforeEach((to, from, next) => {
const isAuthenticated = store.state.auth.token !== null
if (to.meta.requiresAuth && !isAuthenticated) {
store.state.auth.originalPath = to.fullPath
next('/login')
}
else {

View File

@@ -7,6 +7,7 @@ interface AuthState {
superUser: boolean
userName: string
avatar: string
originalPath: string | null
}
// 定义根状态类型
@@ -23,6 +24,7 @@ const authModule: Module<AuthState, RootState> = {
superUser: false,
userName: '',
avatar: '',
originalPath: null,
},
mutations: {
setToken(state, token: string) {

View File

@@ -544,7 +544,9 @@ onBeforeMount(() => {
<ul v-if="mediaDetail.tmdb_id" class="media-crew">
<li v-for="director in mediaDetail.directors" :key="director.id">
<span>{{ director.job }}</span>
<a class="crew-name" :href="`person?personid=${director.id}`" target="_blank">{{ director.name }}</a>
<RouterLink :to="`/person?personid=${director.id}`" class="crew-name" target="_blank">
{{ director.name }}
</RouterLink>
</li>
</ul>
<ul v-if="!mediaDetail.tmdb_id && mediaDetail.douban_id" class="media-crew">

View File

@@ -68,6 +68,14 @@ function initOptions(data: Context) {
optionValue(resolutionFilterOptions.value, meta_info?.resource_pix)
}
// 对季过滤选项进行排序
const sortSeasonFilterOptions = computed(() => {
return seasonFilterOptions.value.sort((a, b) => {
// 按字符串升序排序
return a.localeCompare(b, 'zh-Hans-CN', { sensitivity: 'accent' })
})
})
// 计算分组后的列表
onMounted(() => {
// 数据分组
@@ -154,7 +162,7 @@ watchEffect(() => {
<VCol v-if="seasonFilterOptions.length > 0" cols="6" md="">
<VSelect
v-model="filterForm.season"
:items="seasonFilterOptions"
:items="sortSeasonFilterOptions"
size="small"
density="compact"
chips

View File

@@ -5,25 +5,21 @@ import NoDataFound from '@/components/NoDataFound.vue'
import PluginAppCard from '@/components/cards/PluginAppCard.vue'
import PluginCard from '@/components/cards/PluginCard.vue'
// 数据列表
// 已安装插件列表
const dataList = ref<Plugin[]>([])
// 未安装插件列表
const uninstalledList = ref<Plugin[]>([])
// 是否刷新过
const isRefreshed = ref(false)
// APP市场是否加载完成
const isAppMarketLoaded = ref(false)
// APP市场窗口
const PluginAppDialog = ref(false)
// 获取已安装的插件列表
const getInstalledPluginList = computed(() => {
return dataList.value.filter(item => item.installed)
})
// 获取未安装或者有更新的插件列表
const getUninstalledPluginList = computed(() => {
return dataList.value.filter(item => !item.installed || item.has_update)
})
// 关闭插件市场窗口
function pluginDialogClose() {
PluginAppDialog.value = false
@@ -31,14 +27,19 @@ function pluginDialogClose() {
// 新安装了插件
function pluginInstalled() {
fetchData()
fetchInstalledPlugins()
pluginDialogClose()
fetchUninstalledPlugins()
}
// 获取插件列表数据
async function fetchData() {
async function fetchInstalledPlugins() {
try {
dataList.value = await api.get('plugin/')
dataList.value = await api.get('plugin/', {
params: {
state: 'installed',
},
})
isRefreshed.value = true
}
catch (error) {
@@ -46,8 +47,46 @@ async function fetchData() {
}
}
// 获取未安装插件列表数据
async function fetchUninstalledPlugins() {
try {
uninstalledList.value = await api.get('plugin/', {
params: {
state: 'market',
},
})
// 设置APP市场加载完成
isAppMarketLoaded.value = true
// 设置更新状态
for (const uninstalled of uninstalledList.value) {
for (const data of dataList.value) {
if (uninstalled.id === data.id) {
data.has_update = true
data.repo_url = uninstalled.repo_url
}
}
}
}
catch (error) {
console.error(error)
}
}
// 加载所有数据
function refreshData() {
fetchInstalledPlugins()
fetchUninstalledPlugins()
}
// 获取没有更新的插件
const getUnupdatedPlugins = computed(() => {
return uninstalledList.value.filter(item => !item.has_update)
})
// 加载时获取数据
onBeforeMount(fetchData)
onBeforeMount(() => {
refreshData()
})
</script>
<template>
@@ -63,19 +102,19 @@ onBeforeMount(fetchData)
/>
</div>
<div
v-if="getInstalledPluginList.length > 0"
v-if="dataList.length > 0"
class="grid gap-4 grid-plugin-card"
>
<PluginCard
v-for="data in getInstalledPluginList"
v-for="data in dataList"
:key="data.id"
:plugin="data"
@remove="fetchData"
@save="fetchData"
@remove="refreshData"
@save="refreshData"
/>
</div>
<NoDataFound
v-if="getInstalledPluginList.length === 0 && isRefreshed"
v-if="dataList.length === 0 && isRefreshed"
error-code="404"
error-title="没有安装插件"
error-description="点击右下角按钮前往插件市场安装插件"
@@ -86,6 +125,7 @@ onBeforeMount(fetchData)
fullscreen
scrollable
:scrim="false"
:z-index="1010"
transition="dialog-bottom-transition"
>
<!-- Dialog Activator -->
@@ -121,16 +161,27 @@ onBeforeMount(fetchData)
</VToolbar>
</div>
<VCardText>
<div class="grid gap-4 grid-plugin-card">
<div
v-if="!isAppMarketLoaded"
class="mt-12 w-full text-center text-gray-500 text-sm flex flex-col items-center"
>
<VProgressCircular
v-if="!isAppMarketLoaded"
size="48"
indeterminate
color="primary"
/>
</div>
<div v-if="isAppMarketLoaded" class="grid gap-4 grid-plugin-card">
<PluginAppCard
v-for="data in getUninstalledPluginList"
v-for="data in getUnupdatedPlugins"
:key="data.id"
:plugin="data"
@install="pluginInstalled"
/>
</div>
<NoDataFound
v-if="getUninstalledPluginList.length === 0 && isRefreshed"
v-if="uninstalledList.length === 0 && isAppMarketLoaded"
error-code="404"
error-title="没有未安装插件"
error-description="所有可用插件均已安装"

View File

@@ -6,10 +6,6 @@ import NoDataFound from '@/components/NoDataFound.vue'
import DownloadingCard from '@/components/cards/DownloadingCard.vue'
import store from '@/store'
// 从Vuex Store中获取用户信息
const superUser = store.state.auth.superUser
const userName = store.state.auth.userName
// 定时器
let refreshTimer: NodeJS.Timer | null = null
@@ -42,10 +38,13 @@ function onRefresh() {
// 过滤数据,管理员用户显示全部,非管理员只显示自己的订阅
const filteredDataList = computed(() => {
// 从Vuex Store中获取用户信息
const superUser = store.state.auth.superUser
const userName = store.state.auth.userName
if (superUser)
return dataList.value
else
return dataList.value.filter(data => data.userid === userName)
return dataList.value.filter(data => data.userid === userName || data.username === userName)
})
// 加载时获取数据

View File

@@ -357,7 +357,7 @@ const dropdownItems = ref([
<VIcon :icon="getIcon(item.value.type || '')" />
</VAvatar>
<div class="d-flex flex-column ms-1">
<span class="d-block whitespace-nowrap text-high-emphasis">
<span class="d-block text-high-emphasis">
{{ item.value.title }} {{ item.value.seasons }}{{ item.value.episodes }}
</span>
<small>{{ item.value.category }}</small>
@@ -415,8 +415,16 @@ const dropdownItems = ref([
</VCard>
<!-- 底部操作按钮 -->
<span v-if="selected.length > 0" class="fixed right-5 bottom-5">
<VBtn icon="mdi-redo-variant" class="me-2" color="primary" size="x-large" @click="retransferBatch" />
<VBtn icon="mdi-trash-can-outline" color="error" size="x-large" @click="removeHistoryBatch" />
<VTooltip text="批量重新整理">
<template #activator="{ props }">
<VBtn v-bind="props" icon="mdi-redo-variant" class="me-2" color="primary" size="x-large" @click="retransferBatch" />
</template>
</VTooltip>
<VTooltip text="批量删除">
<template #activator="{ props }">
<VBtn v-bind="props" icon="mdi-trash-can-outline" color="error" size="x-large" @click="removeHistoryBatch" />
</template>
</VTooltip>
</span>
<!-- 底部弹窗 -->
<VBottomSheet v-model="deleteConfirmDialog" inset>

View File

@@ -5,6 +5,59 @@ import type { NotificationSwitch } from '@/api/types'
const messagemTypes = ref<NotificationSwitch[]>([])
// 选中的消息渠道
const selectedChannels = ref([])
// 消息渠道标签页
const messagerTab = ref('wechat')
// 消息设置
const notificationSettings = ref({
WECHAT_CORPID: '',
WECHAT_APP_SECRET: '',
WECHAT_APP_ID: '',
WECHAT_PROXY: '',
WECHAT_TOKEN: '',
WECHAT_ENCODING_AESKEY: '',
WECHAT_ADMINS: '',
TELEGRAM_TOKEN: '',
TELEGRAM_CHAT_ID: '',
TELEGRAM_USERS: '',
TELEGRAM_ADMINS: '',
SLACK_OAUTH_TOKEN: '',
SLACK_APP_TOKEN: '',
SLACK_CHANNEL: '',
SYNOLOGYCHAT_WEBHOOK: '',
SYNOLOGYCHAT_TOKEN: '',
VOCECHAT_HOST: '',
VOCECHAT_API_KEY: '',
VOCECHAT_CHANNEL_ID: '',
})
// 消息渠道
const NotificationChannels = [
{
title: '微信',
value: 'wechat',
},
{
title: 'Telegram',
value: 'telegram',
},
{
title: 'Slack',
value: 'slack',
},
{
title: 'SynologyChat',
value: 'synologychat',
},
{
title: 'VoceChat',
value: 'vocechat',
},
]
// 提示框
const $toast = useToast()
@@ -32,87 +85,405 @@ async function saveNotificationSwitchs() {
$toast.success('保存通知消息设置成功')
else
$toast.error('保存通知消息设置失败!')
// messagemTypes.value = messagemTypes.value
}
catch (error) {
console.log(error)
}
}
// 调用API查询消息渠道设置
async function loadNotificationSettings() {
try {
const result1: { [key: string]: any } = await api.get('system/setting/MESSAGER')
if (result1.success)
selectedChannels.value = result1.data?.value?.split(',')
const result2: { [key: string]: any } = await api.get('system/env')
if (result2.success) {
const {
WECHAT_CORPID,
WECHAT_APP_SECRET,
WECHAT_APP_ID,
WECHAT_PROXY,
WECHAT_TOKEN,
WECHAT_ENCODING_AESKEY,
WECHAT_ADMINS,
TELEGRAM_TOKEN,
TELEGRAM_CHAT_ID,
TELEGRAM_USERS,
TELEGRAM_ADMINS,
SLACK_OAUTH_TOKEN,
SLACK_APP_TOKEN,
SLACK_CHANNEL,
SYNOLOGYCHAT_WEBHOOK,
SYNOLOGYCHAT_TOKEN,
VOCECHAT_HOST,
VOCECHAT_API_KEY,
VOCECHAT_CHANNEL_ID,
} = result2.data
notificationSettings.value = {
WECHAT_CORPID,
WECHAT_APP_SECRET,
WECHAT_APP_ID,
WECHAT_PROXY,
WECHAT_TOKEN,
WECHAT_ENCODING_AESKEY,
WECHAT_ADMINS,
TELEGRAM_TOKEN,
TELEGRAM_CHAT_ID,
TELEGRAM_USERS,
TELEGRAM_ADMINS,
SLACK_OAUTH_TOKEN,
SLACK_APP_TOKEN,
SLACK_CHANNEL,
SYNOLOGYCHAT_WEBHOOK,
SYNOLOGYCHAT_TOKEN,
VOCECHAT_HOST,
VOCECHAT_API_KEY,
VOCECHAT_CHANNEL_ID,
}
}
}
catch (error) {
console.log(error)
}
}
// 调用API保存消息渠道设置
async function saveNotificationSettings() {
try {
const result1: { [key: string]: any } = await api.post(
'system/setting/MESSAGER',
selectedChannels.value.join(','),
)
const result2: { [key: string]: any } = await api.post(
'system/env',
notificationSettings.value,
)
if (result1.success && result2.success) {
$toast.success('保存通知渠道设置成功')
reloadModule()
}
else { $toast.error('保存通知渠道设置失败!') }
}
catch (error) {
console.log(error)
}
}
// 调用API接口重新加载模块
async function reloadModule() {
try {
const result: { [key: string]: any } = await api.get('system/reload')
if (result.success)
$toast.success('重新加载模块成功')
else
$toast.error('重新加载模块失败!')
}
catch (error) {
console.log(error)
}
}
// 加载数据
onMounted(() => {
loadNotificationSwitchs()
loadNotificationSettings()
})
</script>
<template>
<VCard title="消息通知">
<VCardText> 对应消息类型只会发送给选中的消息渠道 </VCardText>
<VRow>
<VCol cols="12">
<VCard title="通知渠道">
<VCardSubtitle>只有选中的渠道才会发送消息</VCardSubtitle>
<VCardText>
<VForm>
<VRow>
<VCol cols="12" md="6">
<VSelect
v-model="selectedChannels"
multiple
chips
:items="NotificationChannels"
label="当前使用通知渠道"
/>
</VCol>
</VRow>
<VRow>
<VCol>
<VTabs
v-model="messagerTab"
stacked
>
<VTab value="wechat">
微信
</VTab>
<VTab value="telegram">
Telegram
</VTab>
<VTab value="slack">
Slack
</VTab>
<VTab value="synologychat">
SynologyChat
</VTab>
<VTab value="vocechat">
VoceChat
</VTab>
</VTabs>
<VWindow
v-model="messagerTab"
class="mt-5 disable-tab-transition"
:touch="false"
>
<VWindowItem value="wechat">
<VForm>
<VRow>
<VCol cols="12" md="4">
<VTextField
v-model="notificationSettings.WECHAT_CORPID"
label="企业ID"
/>
</VCol>
<VCol cols="12" md="4">
<VTextField
v-model="notificationSettings.WECHAT_APP_SECRET"
label="应用密钥"
/>
</VCol>
<VCol cols="12" md="4">
<VTextField
v-model="notificationSettings.WECHAT_APP_ID"
label="应用ID"
/>
</VCol>
<VCol cols="12" md="4">
<VTextField
v-model="notificationSettings.WECHAT_PROXY"
label="代理地址"
/>
</VCol>
<VCol cols="12" md="4">
<VTextField
v-model="notificationSettings.WECHAT_TOKEN"
label="Token"
/>
</VCol>
<VCol cols="12" md="4">
<VTextField
v-model="notificationSettings.WECHAT_ENCODING_AESKEY"
label="EncodingAESKey"
/>
</VCol>
<VCol cols="12" md="4">
<VTextField
v-model="notificationSettings.WECHAT_ADMINS"
label="管理员白名单"
placeholder="多个用,分隔"
/>
</VCol>
</VRow>
</VForm>
</VWindowItem>
<VWindowItem value="telegram">
<VForm>
<VRow>
<VCol cols="12" md="6">
<VTextField
v-model="notificationSettings.TELEGRAM_TOKEN"
label="Bot Token"
/>
</VCol>
<VCol cols="12" md="6">
<VTextField
v-model="notificationSettings.TELEGRAM_CHAT_ID"
label="Chat ID"
/>
</VCol>
<VCol cols="12" md="6">
<VTextField
v-model="notificationSettings.TELEGRAM_USERS"
label="用户白名单"
placeholder="多个用,分隔"
/>
</VCol>
<VCol cols="12" md="6">
<VTextField
v-model="notificationSettings.TELEGRAM_ADMINS"
label="管理员白名单"
placeholder="多个用,分隔"
/>
</VCol>
</VRow>
</VForm>
</VWindowItem>
<VWindowItem value="slack">
<VForm>
<VRow>
<VCol cols="12" md="5">
<VTextField
v-model="notificationSettings.SLACK_OAUTH_TOKEN"
label="Slack Bot User OAuth Token"
/>
</VCol>
<VCol cols="12" md="5">
<VTextField
v-model="notificationSettings.SLACK_APP_TOKEN"
label="Slack App-Level Token"
/>
</VCol>
<VCol cols="12" md="2">
<VTextField
v-model="notificationSettings.SLACK_CHANNEL"
label="频道名称"
placeholder="全体"
/>
</VCol>
</VRow>
</VForm>
</VWindowItem>
<VWindowItem value="synologychat">
<VForm>
<VRow>
<VCol cols="12" md="6">
<VTextField
v-model="notificationSettings.SYNOLOGYCHAT_WEBHOOK"
label="Webhook"
/>
</VCol>
<VCol cols="12" md="6">
<VTextField
v-model="notificationSettings.SYNOLOGYCHAT_TOKEN"
label="Token"
/>
</VCol>
</VRow>
</VForm>
</VWindowItem>
<VWindowItem value="vocechat">
<VForm>
<VRow>
<VCol cols="12" md="4">
<VTextField
v-model="notificationSettings.VOCECHAT_HOST"
label="地址"
/>
</VCol>
<VCol cols="12" md="4">
<VTextField
v-model="notificationSettings.VOCECHAT_API_KEY"
label="机器人密钥"
/>
</VCol>
<VCol cols="12" md="4">
<VTextField
v-model="notificationSettings.VOCECHAT_CHANNEL_ID"
label="频道ID"
placeholder="不包含#号"
/>
</VCol>
</VRow>
</VForm>
</VWindowItem>
</VWindow>
</VCol>
</VRow>
</VForm>
</VCardText>
<VCardText>
<VForm @submit.prevent="() => {}">
<div class="d-flex flex-wrap gap-4 mt-4">
<VBtn
mtype="submit"
@click="saveNotificationSettings"
>
保存
</VBtn>
</div>
</VForm>
</VCardText>
</VCard>
</VCol>
</VRow>
<VRow>
<VCol cols="12">
<VCard title="消息类型">
<VCardSubtitle> 对应消息类型只会发送给选中的消息渠道 </VCardSubtitle>
<VTable class="text-no-wrap">
<thead>
<tr>
<th scope="col">
消息类型
</th>
<th scope="col">
微信
</th>
<th scope="col">
Telegram
</th>
<th scope="col">
Slack
</th>
<th scope="col">
SynologyChat
</th>
<th scope="col">
VoceChat
</th>
</tr>
</thead>
<tbody>
<tr
v-for="message in messagemTypes"
:key="message.mtype"
>
<td>
{{ message.mtype }}
</td>
<td>
<VCheckbox v-model="message.wechat" />
</td>
<td>
<VCheckbox v-model="message.telegram" />
</td>
<td>
<VCheckbox v-model="message.slack" />
</td>
<td>
<VCheckbox v-model="message.synologychat" />
</td>
<td>
<VCheckbox v-model="message.vocechat" />
</td>
</tr>
<tr v-if="messagemTypes.length === 0">
<td
colspan="6"
class="text-center"
>
没有设置任何通知渠道
</td>
</tr>
</tbody>
</VTable>
<VDivider />
<VTable class="text-no-wrap">
<thead>
<tr>
<th scope="col">
消息类型
</th>
<th scope="col">
微信
</th>
<th scope="col">
Telegram
</th>
<th scope="col">
Slack
</th>
<th scope="col">
SynologyChat
</th>
</tr>
</thead>
<tbody>
<tr
v-for="message in messagemTypes"
:key="message.mtype"
>
<td>
{{ message.mtype }}
</td>
<td>
<VCheckbox v-model="message.wechat" />
</td>
<td>
<VCheckbox v-model="message.telegram" />
</td>
<td>
<VCheckbox v-model="message.slack" />
</td>
<td>
<VCheckbox v-model="message.synologychat" />
</td>
</tr>
<tr v-if="messagemTypes.length === 0">
<td
colspan="4"
class="text-center"
>
没有设置任何通知渠道
</td>
</tr>
</tbody>
</VTable>
<VDivider />
<VCardText>
<VForm @submit.prevent="() => {}">
<div class="d-flex flex-wrap gap-4 mt-4">
<VBtn
mtype="submit"
@click="saveNotificationSwitchs"
>
保存
</VBtn>
</div>
</VForm>
</VCardText>
</VCard>
<VCardText>
<VForm @submit.prevent="() => {}">
<div class="d-flex flex-wrap gap-4 mt-4">
<VBtn
mtype="submit"
@click="saveNotificationSwitchs"
>
保存
</VBtn>
</div>
</VForm>
</VCardText>
</VCard>
</VCol>
</VRow>
</template>

View File

@@ -78,11 +78,14 @@ onUnmounted(() => {
<template>
<VCard title="定时作业">
<VCardText> 手动执行不会影响作业正常的时间表 </VCardText>
<VCardSubtitle> 包含系统内置服务以及插件提供的服务手动执行不会影响作业正常的时间表 </VCardSubtitle>
<VTable class="text-no-wrap">
<thead>
<tr>
<th scope="col">
提供者
</th>
<th scope="col">
任务名称
</th>
@@ -100,6 +103,9 @@ onUnmounted(() => {
v-for="scheduler in schedulerList"
:key="scheduler.id"
>
<td>
{{ scheduler.provider }}
</td>
<td>
{{ scheduler.name }}
</td>

View File

@@ -17,12 +17,32 @@ const resetSitesDisabled = ref(false)
// 种子优先规则
const selectedTorrentPriority = ref<string>('seeder')
// CookieCloud设置项
const cookieCloudSetting = ref({
COOKIECLOUD_HOST: '',
COOKIECLOUD_KEY: '',
COOKIECLOUD_PASSWORD: '',
COOKIECLOUD_INTERVAL: 0,
USER_AGENT: '',
})
// 种子优先规则下拉框
const TorrentPriorityItems = [
{ title: '站点优先', value: 'site' },
{ title: '做种数优先', value: 'seeder' },
]
// 同步间隔下拉框
const CookieCloudIntervalItems = [
{ title: '每小时', value: 60 },
{ title: '每6小时', value: 360 },
{ title: '每12小时', value: 720 },
{ title: '每天', value: 1440 },
{ title: '每周', value: 10080 },
{ title: '每月', value: 43200 },
{ title: '永不', value: 0 },
]
// 重置站点
async function resetSites() {
try {
@@ -77,13 +97,111 @@ async function saveTorrentPriority() {
}
}
// 加载CookieCloud设置
async function loadCookieCloudSettings() {
try {
const result: { [key: string]: any } = await api.get('system/env')
if (result.success) {
const {
COOKIECLOUD_HOST,
COOKIECLOUD_KEY,
COOKIECLOUD_PASSWORD,
COOKIECLOUD_INTERVAL,
USER_AGENT,
} = result.data
cookieCloudSetting.value = {
COOKIECLOUD_HOST,
COOKIECLOUD_KEY,
COOKIECLOUD_PASSWORD,
COOKIECLOUD_INTERVAL,
USER_AGENT,
}
}
}
catch (error) {
console.log(error)
}
}
// 调用API保存CookieCloud设置
async function saveCookieCloudetting() {
try {
const result: { [key: string]: any } = await api.post(
'system/env',
cookieCloudSetting.value,
)
if (result.success)
$toast.success('保存站点同步设置成功')
else
$toast.error('保存站点同步设置失败!')
}
catch (error) {
console.log(error)
}
}
// 加载数据
onMounted(() => {
queryTorrentPriority()
loadCookieCloudSettings()
})
</script>
<template>
<VRow>
<VCol cols="12">
<VCard title="站点同步">
<VCardSubtitle> 从CookieCloud快速同步站点数据 </VCardSubtitle>
<VCardText>
<VForm>
<VRow>
<VCol cols="12" md="6">
<VTextField
v-model="cookieCloudSetting.COOKIECLOUD_HOST"
label="CookieCloud服务器地址"
placeholder="https://movie-pilot.org/cookiecloud"
/>
</VCol>
<VCol cols="12" md="6">
<VTextField
v-model="cookieCloudSetting.COOKIECLOUD_KEY"
label="用户KEY"
/>
</VCol>
<VCol cols="12" md="6">
<VTextField
v-model="cookieCloudSetting.COOKIECLOUD_PASSWORD"
type="password"
label="端对端加密密码"
/>
</VCol>
<VCol cols="12" md="6">
<VSelect
v-model="cookieCloudSetting.COOKIECLOUD_INTERVAL"
label="自动同步间隔"
:items="CookieCloudIntervalItems"
/>
</VCol>
<VCol cols="12">
<VTextField
v-model="cookieCloudSetting.USER_AGENT"
label="浏览器User-Agent"
/>
</VCol>
</VRow>
</VForm>
</VCardText>
<VCardItem>
<VBtn
type="submit"
@click="saveCookieCloudetting"
>
保存
</VBtn>
</VCardItem>
</VCard>
</VCol>
<VCol cols="12">
<VCard title="下载优先规则">
<VCardSubtitle> 按站点或做种数量优先下载 </VCardSubtitle>
@@ -94,8 +212,7 @@ onMounted(() => {
<VSelect
v-model="selectedTorrentPriority"
:items="TorrentPriorityItems"
label="优先规则"
outlined
label="当前使用下载优先规则"
/>
</VCol>
</VRow>

View File

@@ -1,4 +1,4 @@
<script lang="ts" setup>
<script lang='ts' setup>
import { useToast } from 'vue-toast-notification'
import api from '@/api'
import FilterRuleCard from '@/components/cards/FilterRuleCard.vue'
@@ -32,15 +32,42 @@ const selectedRssSites = ref<number[]>([])
// 当前规则类型
const currentRuleType = ref('SubscribeFilterRules')
// 是否开启订阅定时搜索
const enableIntervalSearch = ref(false)
// 包含与排除规则
const defaultFilterRules = ref({
include: '',
exclude: '',
movie_size: '',
tv_size: '',
min_seeders: 0,
show_edit_dialog: false,
})
// 订阅模式选择项
const subscribeModeItems = [
{ title: '自动', value: 'spider' },
{ title: '站点RSS', value: 'rss' },
]
// 选择的订阅模式
const selectedSubscribeMode = ref('spider')
// RSS运行周期选择项
const rssIntervalItems = [
{ title: '5分钟', value: 5 },
{ title: '10分钟', value: 10 },
{ title: '20分钟', value: 20 },
{ title: '半小时', value: 30 },
{ title: '1小时', value: 60 },
{ title: '12小时', value: 720 },
{ title: '1天', value: 1440 },
]
// 选择的RSS运行周期
const selectedRssInterval = ref<number>(5)
// 导入代码弹窗
const importCodeDialog = ref(false)
@@ -62,9 +89,26 @@ async function querySelectedRssSites() {
// 保存用户选中的订阅站点
async function saveSelectedRssSites() {
try {
const result: { [key: string]: any } = await api.post('system/setting/RssSites', selectedRssSites.value)
const result1: { [key: string]: any } = await api.post(
'system/setting/RssSites',
selectedRssSites.value)
if (result.success)
const result2: { [key: string]: any } = await api.post(
'system/setting/SUBSCRIBE_SEARCH',
enableIntervalSearch.value ? 'True' : 'False',
)
const result3: { [key: string]: any } = await api.post(
'system/setting/SUBSCRIBE_MODE',
selectedSubscribeMode.value,
)
const result4: { [key: string]: any } = await api.post(
'system/setting/SUBSCRIBE_RSS_INTERVAL',
selectedRssInterval.value,
)
if (result1.success && result2.success && result3.success && result4.success)
$toast.success('订阅站点保存成功')
else
$toast.error('订阅站点保存失败!')
@@ -82,6 +126,19 @@ async function querySites() {
// 过滤站点,只有启用的站点才显示
allSites.value = data.filter(item => item.is_active)
querySelectedRssSites()
// 查询订阅搜索开关
const result: { [key: string]: any } = await api.get('system/setting/SUBSCRIBE_SEARCH')
if (result.success)
enableIntervalSearch.value = result.data?.value
// 查询订阅模式
const result2: { [key: string]: any } = await api.get('system/setting/SUBSCRIBE_MODE')
if (result2.success)
selectedSubscribeMode.value = result2.data?.value
// 查询站点RSS周期
const result3: { [key: string]: any } = await api.get('system/setting/SUBSCRIBE_RSS_INTERVAL')
if (result3.success)
selectedRssInterval.value = result3.data?.value
}
catch (error) {
console.log(error)
@@ -346,7 +403,34 @@ onMounted(() => {
</VChip>
</VChipGroup>
</VCardItem>
<VCardText>
<VForm>
<VRow>
<VCol cols="12" md="6">
<VSelect
v-model="selectedSubscribeMode"
:items="subscribeModeItems"
label="订阅模式"
/>
</VCol>
<VCol cols="12" md="6">
<VSelect
v-model="selectedRssInterval"
:items="rssIntervalItems"
label="站点RSS周期"
/>
</VCol>
</VRow>
<VRow>
<VCol cols="12" md="6">
<VSwitch
v-model="enableIntervalSearch"
label="开启订阅定时搜索"
/>
</VCol>
</VRow>
</VForm>
</VCardText>
<VCardItem>
<VBtn type="submit" @click="saveSelectedRssSites">
保存
@@ -386,7 +470,7 @@ onMounted(() => {
</VMenu>
</IconBtn>
</template>
<VCardSubtitle> 设置在正常订阅时默认使用的优先级未在优先级中的资源将不会自动下载 </VCardSubtitle>
<VCardSubtitle> 设置在正常订阅时默认使用的优先级未在优先级中的资源将不会自动下载</VCardSubtitle>
<VCardItem>
<div class="grid gap-3 grid-filterrule-card">
<FilterRuleCard
@@ -452,7 +536,7 @@ onMounted(() => {
</VMenu>
</IconBtn>
</template>
<VCardSubtitle> 设置在订阅洗版时使用的优先级匹配优先级1时洗版完成 </VCardSubtitle>
<VCardSubtitle> 设置在订阅洗版时使用的优先级匹配优先级1时洗版完成</VCardSubtitle>
<VCardItem>
<div class="grid gap-3 grid-filterrule-card">
<FilterRuleCard
@@ -488,7 +572,7 @@ onMounted(() => {
</VCol>
<VCol cols="12">
<VCard title="默认过滤规则">
<VCardSubtitle> 设置在订阅时默认使用的过滤规则 </VCardSubtitle>
<VCardSubtitle> 设置在订阅时默认使用的过滤规则</VCardSubtitle>
<VCardText>
<VForm>
<VRow>
@@ -506,7 +590,7 @@ onMounted(() => {
label="排除(关键字、正则式)"
/>
</VCol>
<VCol cols="12" md="6">
<VCol cols="12" md="4">
<VTextField
v-model="defaultFilterRules.movie_size"
type="text"
@@ -514,7 +598,7 @@ onMounted(() => {
placeholder="0-30"
/>
</VCol>
<VCol cols="12" md="6">
<VCol cols="12" md="4">
<VTextField
v-model="defaultFilterRules.tv_size"
type="text"
@@ -522,6 +606,14 @@ onMounted(() => {
placeholder="0-10"
/>
</VCol>
<VCol cols="12" md="4">
<VTextField
v-model="defaultFilterRules.min_seeders"
type="text"
label="最小做种数"
placeholder="0"
/>
</VCol>
<VCol cols="12" md="6">
<VSwitch
v-model="defaultFilterRules.show_edit_dialog"
@@ -555,7 +647,7 @@ onMounted(() => {
</VDialog>
</template>
<style lang="scss">
<style lang='scss'>
.grid-filterrule-card {
grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
padding-block-end: 1rem;

View File

@@ -0,0 +1,746 @@
<!-- eslint-disable sonarjs/no-duplicate-string -->
<script lang="ts" setup>
import { useToast } from 'vue-toast-notification'
import { VRow } from 'vuetify/lib/components/index.mjs'
import api from '@/api'
import { requiredValidator } from '@/@validators'
// 选中的媒体服务器
const selectedMediaServers = ref([])
// 选中的下载器
const selectedDownloaders = ref([])
// 下载器选中标签页
const downloaderTab = ref('qbittorrent')
// 媒体服务器选中标签页
const mediaserverTab = ref('emby')
// 媒体库设置项
const mediaSettings = ref({
SCRAP_METADATA: true,
DOWNLOAD_PATH: '',
DOWNLOAD_MOVIE_PATH: '',
DOWNLOAD_TV_PATH: '',
DOWNLOAD_ANIME_PATH: '',
DOWNLOAD_CATEGORY: false,
TRANSFER_TYPE: 'copy',
OVERWRITE_MODE: 'size',
LIBRARY_PATH: '',
LIBRARY_MOVIE_NAME: '',
LIBRARY_TV_NAME: '',
LIBRARY_ANIME_NAME: '',
LIBRARY_CATEGORY: false,
})
// 下载器设置项
const downloaderSettings = ref({
DOWNLOADER_MONITOR: true,
TORRENT_TAG: '',
QB_HOST: '',
QB_USER: '',
QB_PASSWORD: '',
QB_CATEGORY: false,
QB_SEQUENTIAL: false,
QB_FORCE_RESUME: false,
TR_HOST: '',
TR_USER: '',
TR_PASSWORD: '',
})
// 媒体服务器设置项
const mediaServerSettings = ref({
MEDIASERVER_SYNC_INTERVAL: 6,
MEDIASERVER_SYNC_BLACKLIST: '',
EMBY_HOST: '',
EMBY_PLAY_HOST: '',
EMBY_API_KEY: '',
JELLYFIN_HOST: '',
JELLYFIN_PLAY_HOST: '',
JELLYFIN_API_KEY: '',
PLEX_HOST: '',
PLEX_PLAY_HOST: '',
PLEX_TOKEN: '',
})
// 下载器字典项
const Downloaders = [
{
title: 'Qbittorrent',
value: 'qbittorrent',
},
{
title: 'Transmission',
value: 'transmission',
},
]
// 媒体服务器字典项
const MediaServers = [
{
title: 'Emby',
value: 'emby',
},
{
title: 'Jellyfin',
value: 'jellyfin',
},
{
title: 'Plex',
value: 'plex',
},
]
// 转移方式字典
const transferTypeItems = [
{ title: '硬链接', value: 'link' },
{ title: '复制', value: 'copy' },
{ title: '移动', value: 'move' },
{ title: '软链接', value: 'softlink' },
{ title: 'rclone复制', value: 'rclone_copy' },
{ title: 'rclone移动', value: 'rclone_move' },
]
// 覆盖模式字典
const overwriteModeItems = [
{ title: '从不覆盖', value: 'never' },
{ title: '按大小覆盖', value: 'size' },
{ title: '总是覆盖', value: 'always' },
{ title: '仅保留最新版本', value: 'latest' },
]
// 媒体库同步周期字典
const syncIntervalItems = [
{ title: '从不', value: 0 },
{ title: '每小时', value: 1 },
{ title: '每6小时', value: 6 },
{ title: '每12小时', value: 12 },
{ title: '每天', value: 24 },
{ title: '每周', value: 168 },
]
// 提示框
const $toast = useToast()
// 加载媒体库设置
async function loadMediaSettings() {
try {
const result: { [key: string]: any } = await api.get('system/env')
if (result.success) {
const {
SCRAP_METADATA,
DOWNLOAD_PATH,
DOWNLOAD_MOVIE_PATH,
DOWNLOAD_TV_PATH,
DOWNLOAD_ANIME_PATH,
DOWNLOAD_CATEGORY,
TRANSFER_TYPE,
OVERWRITE_MODE,
LIBRARY_PATH,
LIBRARY_MOVIE_NAME,
LIBRARY_TV_NAME,
LIBRARY_ANIME_NAME,
LIBRARY_CATEGORY,
} = result.data
mediaSettings.value = {
SCRAP_METADATA,
DOWNLOAD_PATH,
DOWNLOAD_MOVIE_PATH,
DOWNLOAD_TV_PATH,
DOWNLOAD_ANIME_PATH,
DOWNLOAD_CATEGORY,
TRANSFER_TYPE,
OVERWRITE_MODE,
LIBRARY_PATH,
LIBRARY_MOVIE_NAME,
LIBRARY_TV_NAME,
LIBRARY_ANIME_NAME,
LIBRARY_CATEGORY,
}
}
}
catch (error) {
console.log(error)
}
}
// 调用API保存媒体设置
async function saveMediaSetting() {
try {
const result: { [key: string]: any } = await api.post(
'system/env',
mediaSettings.value,
)
if (result.success)
$toast.success('保存媒体库设置成功')
else
$toast.error('保存媒体库设置失败!')
}
catch (error) {
console.log(error)
}
}
// 调用API查询下载器设置
async function loadDownladerSetting() {
try {
const result1: { [key: string]: any } = await api.get('system/setting/DOWNLOADER')
if (result1.success)
selectedDownloaders.value = result1.data?.value?.split(',')
const result2: { [key: string]: any } = await api.get('system/env')
if (result2.success) {
const {
DOWNLOADER_MONITOR,
TORRENT_TAG,
QB_HOST,
QB_USER,
QB_PASSWORD,
QB_CATEGORY,
QB_SEQUENTIAL,
QB_FORCE_RESUME,
TR_HOST,
TR_USER,
TR_PASSWORD,
} = result2.data
downloaderSettings.value = {
DOWNLOADER_MONITOR,
TORRENT_TAG,
QB_HOST,
QB_USER,
QB_PASSWORD,
QB_CATEGORY,
QB_SEQUENTIAL,
QB_FORCE_RESUME,
TR_HOST,
TR_USER,
TR_PASSWORD,
}
}
}
catch (error) {
console.log(error)
}
}
// 调用API保存下载器设置
async function saveDownloaderSetting() {
try {
const result1: { [key: string]: any } = await api.post(
'system/setting/DOWNLOADER',
selectedDownloaders.value.join(','),
)
const result2: { [key: string]: any } = await api.post(
'system/env',
downloaderSettings.value,
)
if (result1.success && result2.success) {
$toast.success('保存下载器设置成功')
reloadModule()
}
else { $toast.error('保存下载器设置失败!') }
}
catch (error) {
console.log(error)
}
}
// 调用API查询媒体服务器设置
async function loadMediaServerSetting() {
try {
const result1: { [key: string]: any } = await api.get('system/setting/MEDIASERVER')
if (result1.success)
selectedMediaServers.value = result1.data?.value?.split(',')
const result2: { [key: string]: any } = await api.get('system/env')
if (result2.success) {
const {
MEDIASERVER_SYNC_INTERVAL,
MEDIASERVER_SYNC_BLACKLIST,
EMBY_HOST,
EMBY_PLAY_HOST,
EMBY_API_KEY,
JELLYFIN_HOST,
JELLYFIN_PLAY_HOST,
JELLYFIN_API_KEY,
PLEX_HOST,
PLEX_PLAY_HOST,
PLEX_TOKEN,
} = result2.data
mediaServerSettings.value = {
MEDIASERVER_SYNC_INTERVAL,
MEDIASERVER_SYNC_BLACKLIST,
EMBY_HOST,
EMBY_PLAY_HOST,
EMBY_API_KEY,
JELLYFIN_HOST,
JELLYFIN_PLAY_HOST,
JELLYFIN_API_KEY,
PLEX_HOST,
PLEX_PLAY_HOST,
PLEX_TOKEN,
}
}
}
catch (error) {
console.log(error)
}
}
// 调用API保存媒体服务器设置
async function saveMediaServerSetting() {
try {
const result1: { [key: string]: any } = await api.post(
'system/setting/MEDIASERVER',
selectedMediaServers.value.join(','),
)
const result2: { [key: string]: any } = await api.post(
'system/env',
mediaServerSettings.value,
)
if (result1.success && result2.success) {
$toast.success('保存媒体服务器设置成功')
reloadModule()
}
else { $toast.error('保存媒体服务器设置失败!') }
}
catch (error) {
console.log(error)
}
}
// 调用API接口重新加载模块
async function reloadModule() {
try {
const result: { [key: string]: any } = await api.get('system/reload')
if (result.success)
$toast.success('重新加载模块成功')
else
$toast.error('重新加载模块失败!')
}
catch (error) {
console.log(error)
}
}
// 加载数据
onMounted(() => {
loadDownladerSetting()
loadMediaServerSetting()
loadMediaSettings()
})
</script>
<template>
<VRow>
<VCol cols="12">
<VCard title="下载器">
<VCardSubtitle>只有选中的第1个下载器才会被默认使用</VCardSubtitle>
<VCardText>
<VForm>
<VRow>
<VCol cols="12" md="6">
<VSelect
v-model="selectedDownloaders"
multiple
chips
:items="Downloaders"
label="当前使用下载器"
/>
</VCol>
<VCol cols="12" md="6">
<VTextField
v-model="downloaderSettings.TORRENT_TAG"
label="下载器种子标签"
/>
</VCol>
</VRow>
<VRow>
<VCol cols="12" md="6">
<VSwitch
v-model="downloaderSettings.DOWNLOADER_MONITOR"
label="监控默认下载器"
/>
</VCol>
</VRow>
<VRow>
<VCol>
<VTabs
v-model="downloaderTab"
stacked
>
<VTab value="qbittorrent">
Qbittorrent
</VTab>
<VTab value="transmission">
Transmission
</VTab>
</VTabs>
<VWindow
v-model="downloaderTab"
class="mt-5 disable-tab-transition"
:touch="false"
>
<VWindowItem value="qbittorrent">
<VForm>
<VRow>
<VCol cols="12" md="4">
<VTextField
v-model="downloaderSettings.QB_HOST"
label="地址"
placeholder="IP:PORT"
/>
</VCol>
<VCol cols="12" md="4">
<VTextField
v-model="downloaderSettings.QB_USER"
label="用户名"
placeholder="admin"
/>
</VCol>
<VCol cols="12" md="4">
<VTextField
v-model="downloaderSettings.QB_PASSWORD"
type="password"
label="密码"
/>
</VCol>
<VCol cols="12" md="4">
<VSwitch
v-model="downloaderSettings.QB_CATEGORY"
label="自动分类管理"
/>
</VCol>
<VCol cols="12" md="4">
<VSwitch
v-model="downloaderSettings.QB_SEQUENTIAL"
label="顺序下载"
/>
</VCol>
<VCol cols="12" md="4">
<VSwitch
v-model="downloaderSettings.QB_FORCE_RESUME"
label="强制继续"
/>
</VCol>
</VRow>
</VForm>
</VWindowItem>
<VWindowItem value="transmission">
<VForm>
<VRow>
<VCol cols="12" md="4">
<VTextField
v-model="downloaderSettings.TR_HOST"
label="地址"
placeholder="IP:PORT"
/>
</VCol>
<VCol cols="12" md="4">
<VTextField
v-model="downloaderSettings.TR_USER"
label="用户名"
placeholder="admin"
/>
</VCol>
<VCol cols="12" md="4">
<VTextField
v-model="downloaderSettings.TR_PASSWORD"
type="password"
label="密码"
/>
</VCol>
</VRow>
</VForm>
</VWindowItem>
</VWindow>
</VCol>
</VRow>
</VForm>
</VCardText>
<VCardText>
<VForm @submit.prevent="() => {}">
<div class="d-flex flex-wrap gap-4 mt-4">
<VBtn
mtype="submit"
@click="saveDownloaderSetting"
>
保存
</VBtn>
</div>
</VForm>
</VCardText>
</VCard>
</VCol>
</VRow>
<VRow>
<VCol cols="12">
<VCard title="媒体服务器">
<VCardSubtitle>只有选中的媒体服务器才会被默认使用</VCardSubtitle>
<VCardText>
<VForm>
<VRow>
<VCol cols="12" md="4">
<VSelect
v-model="selectedMediaServers"
multiple
chips
:items="MediaServers"
label="当前使用媒体服务器"
/>
</VCol>
<VCol cols="12" md="4">
<VSelect
v-model="mediaServerSettings.MEDIASERVER_SYNC_INTERVAL"
:items="syncIntervalItems"
label="同步周期"
/>
</VCol>
<VCol cols="12" md="4">
<VTextField
v-model="mediaServerSettings.MEDIASERVER_SYNC_BLACKLIST"
label="媒体库同步黑名单"
placeholder="使用,分隔"
/>
</VCol>
</VRow>
<VRow>
<VCol>
<VTabs
v-model="mediaserverTab"
stacked
>
<VTab value="emby">
Emby
</VTab>
<VTab value="jellyfin">
Jellyfin
</VTab>
<VTab value="plex">
Plex
</vtab>
</VTabs>
<VWindow
v-model="mediaserverTab"
class="mt-5 disable-tab-transition"
:touch="false"
>
<VWindowItem value="emby">
<VForm>
<VRow>
<VCol cols="12" md="4">
<VTextField
v-model="mediaServerSettings.EMBY_HOST"
label="地址"
placeholder="IP:PORT"
/>
</VCol>
<VCol cols="12" md="4">
<VTextField
v-model="mediaServerSettings.EMBY_PLAY_HOST"
label="外网播放地址"
placeholder="http(s)://domain:port"
/>
</VCol>
<VCol cols="12" md="4">
<VTextField
v-model="mediaServerSettings.EMBY_API_KEY"
label="API密钥"
/>
</VCol>
</VRow>
</VForm>
</VWindowItem>
<VWindowItem value="jellyfin">
<VForm>
<VRow>
<VCol cols="12" md="4">
<VTextField
v-model="mediaServerSettings.JELLYFIN_HOST"
label="地址"
placeholder="IP:PORT"
/>
</VCol>
<VCol cols="12" md="4">
<VTextField
v-model="mediaServerSettings.JELLYFIN_PLAY_HOST"
label="外网播放地址"
placeholder="http(s)://domain:port"
/>
</VCol>
<VCol cols="12" md="4">
<VTextField
v-model="mediaServerSettings.JELLYFIN_API_KEY"
label="API密钥"
/>
</VCol>
</VRow>
</VForm>
</VWindowItem>
<VWindowItem value="plex">
<VForm>
<VRow>
<VCol cols="12" md="4">
<VTextField
v-model="mediaServerSettings.PLEX_HOST"
label="地址"
placeholder="IP:PORT"
/>
</VCol>
<VCol cols="12" md="4">
<VTextField
v-model="mediaServerSettings.PLEX_PLAY_HOST"
label="外网播放地址"
placeholder="http(s)://domain:port"
/>
</VCol>
<VCol cols="12" md="4">
<VTextField
v-model="mediaServerSettings.PLEX_TOKEN"
label="API密钥"
/>
</VCol>
</VRow>
</VForm>
</VWindowItem>
</VWindow>
</VCol>
</VRow>
</VForm>
</VCardText>
<VCardText>
<VForm @submit.prevent="() => {}">
<div class="d-flex flex-wrap gap-4 mt-4">
<VBtn
mtype="submit"
@click="saveMediaServerSetting"
>
保存
</VBtn>
</div>
</VForm>
</VCardText>
</VCard>
</VCol>
</VRow>
<VRow>
<VCol cols="12">
<VCard title="媒体库">
<VCardSubtitle>设置下载目录媒体库目录以及整理方式</VCardSubtitle>
<VCardText>
<VForm>
<VRow>
<VCol cols="12" md="6">
<VTextField
v-model="mediaSettings.DOWNLOAD_PATH"
label="下载目录"
:rules="[requiredValidator]"
/>
</VCol>
<VCol cols="12" md="6">
<VTextField
v-model="mediaSettings.DOWNLOAD_MOVIE_PATH"
label="电影下载目录"
/>
</VCol>
<VCol cols="12" md="6">
<VTextField
v-model="mediaSettings.DOWNLOAD_TV_PATH"
label="电视剧下载目录"
/>
</VCol>
<VCol cols="12" md="6">
<VTextField
v-model="mediaSettings.DOWNLOAD_ANIME_PATH"
label="动漫下载目录"
/>
</VCol>
<VCol cols="12" md="6">
<VSwitch
v-model="mediaSettings.DOWNLOAD_CATEGORY"
label="下载目录自动分类"
/>
</VCol>
</VRow>
<VRow>
<VCol cols="12" md="6">
<VSelect
v-model="mediaSettings.TRANSFER_TYPE"
:items="transferTypeItems"
label="整理方式"
/>
</VCol>
<VCol cols="12" md="6">
<VSelect
v-model="mediaSettings.OVERWRITE_MODE"
:items="overwriteModeItems"
label="覆盖模式"
/>
</VCol>
<VCol cols="12" md="6">
<VSwitch
v-model="mediaSettings.SCRAP_METADATA"
label="自动刮削媒体信息"
/>
</VCol>
</VRow>
<VRow>
<VCol cols="12" md="6">
<VTextField
v-model="mediaSettings.LIBRARY_PATH"
label="媒体库目录"
placeholder="多个目录使用,分隔"
:rules="[requiredValidator]"
/>
</VCol>
<VCol cols="12" md="6">
<VTextField
v-model="mediaSettings.LIBRARY_MOVIE_NAME"
label="电影目录名称"
placeholder="电影"
/>
</VCol>
<VCol cols="12" md="6">
<VTextField
v-model="mediaSettings.LIBRARY_TV_NAME"
label="电视剧目录名称"
placeholder="电视剧"
/>
</VCol>
<VCol cols="12" md="6">
<VTextField
v-model="mediaSettings.LIBRARY_ANIME_NAME"
label="动漫目录名称"
placeholder="动漫"
/>
</VCol>
<VCol cols="12" md="6">
<VSwitch
v-model="mediaSettings.LIBRARY_CATEGORY"
label="媒体库目录自动分类"
/>
</VCol>
</VRow>
</VForm>
</VCardText>
<VCardText>
<VForm @submit.prevent="() => {}">
<div class="d-flex flex-wrap gap-4 mt-4">
<VBtn
mtype="submit"
@click="saveMediaSetting"
>
保存
</VBtn>
</div>
</VForm>
</VCardText>
</VCard>
</VCol>
</VRow>
</template>

View File

@@ -68,6 +68,7 @@ onBeforeMount(fetchData)
@click="siteAddDialog = true"
/>
<SiteAddEditForm
v-if="siteAddDialog"
v-model="siteAddDialog"
oper="add"
@save="siteAddDialog = false; fetchData()"

View File

@@ -20,6 +20,7 @@ const calendarOptions: Ref<CalendarOptions> = ref({
],
initialView: 'dayGridMonth',
weekends: true,
firstDay: 1,
headerToolbar: {
left: 'prev',
center: 'title',
@@ -197,6 +198,11 @@ onMounted(() => {
--fc-event-border-color: currentcolor;
}
// 当天背景渐变
.fc-day-today {
background-image: linear-gradient(to bottom, #AF85FD ,rgba(var(--v-theme-on-surface), 0.04));
}
.v-application .fc a {
color: inherit;
}

View File

@@ -11,10 +11,6 @@ const props = defineProps({
type: String,
})
// 从Vuex Store中获取用户信息
const superUser = store.state.auth.superUser
const userName = store.state.auth.userName
// 是否刷新过
const isRefreshed = ref(false)
@@ -47,10 +43,13 @@ function onRefresh() {
// 过滤数据,管理员用户显示全部,非管理员只显示自己的订阅
const filteredDataList = computed(() => {
// 从Vuex Store中获取用户信息
const superUser = store.state.auth.superUser
const userName = store.state.auth.userName
if (superUser)
return dataList.value.filter(data => data.type === props.type)
else
return dataList.value.filter(data => data.type === props.type && data.username === userName)
return dataList.value.filter(data => data.type === props.type && (data.username === userName))
})
</script>

View File

@@ -34,7 +34,7 @@ function extractLogDetailsFromLogs(logs: string[]): { level: string; time: strin
const matches = RegExp(logPattern).exec(log)
if (matches && matches.length === 5) {
const [_, level, time, program, content] = matches
logDetails.push({ level, time, program, content })
logDetails.unshift({ level, time, program, content })
}
}

View File

@@ -0,0 +1,75 @@
<script setup lang="ts">
import api from '@/api'
// 定义所有的模块ID、名称列表
const modules = ref([
{ id: 'FileTransferModule', name: '媒体目录', state: '', errmsg: '', loading: false },
{ id: 'IndexerModule', name: '站点索引', state: '', errmsg: '', loading: false },
{ id: 'DoubanModule', name: '豆瓣', state: '', errmsg: '', loading: false },
{ id: 'TheMovieDbModule', name: 'TheMovieDb', state: '', errmsg: '', loading: false },
{ id: 'TheTvDbModule', name: 'TheTvDb', state: '', errmsg: '', loading: false },
{ id: 'FanartModule', name: 'Fanart', state: '', errmsg: '', loading: false },
{ id: 'EmbyModule', name: 'Emby', state: '', errmsg: '', loading: false },
{ id: 'JellyfinModule', name: 'Jellyfin', state: '', errmsg: '', loading: false },
{ id: 'PlexModule', name: 'Plex', state: '', errmsg: '', loading: false },
{ id: 'WechatModule', name: '微信', state: '', errmsg: '', loading: false },
{ id: 'TelegramModule', name: 'Telegram', state: '', errmsg: '', loading: false },
{ id: 'SlackModule', name: 'Slack', state: '', errmsg: '', loading: false },
{ id: 'SynologyChatModule', name: 'Synology Chat', state: '', errmsg: '', loading: false },
{ id: 'VoceChatModule', name: 'VoceChat', state: '', errmsg: '', loading: false },
{ id: 'QbittorrentModule', name: 'Qbittorrent', state: '', errmsg: '', loading: false },
{ id: 'TransmissionModule', name: 'Transmission', state: '', errmsg: '', loading: false },
])
// 调用API测试模块
async function moduleTest(index: number) {
try {
const target = modules.value[index]
const moduleid = target.id
target.loading = true
const result: { [key: string]: any } = await api.get(`system/moduletest/${moduleid}`)
target.loading = false
if (result.success) {
target.state = 'success'
target.name = `${target.name} - 正常`
}
else if (result.message?.includes('模块未加载')) {
target.state = ''
target.name = `${target.name} - 未启用`
}
else {
target.state = 'error'
target.name = `${target.name} - 错误!`
target.errmsg = result.message
}
}
catch (error) {
console.error(error)
}
}
// 加载
onMounted(async () => {
// 逐个检查所有模块
for (let i = 0; i < modules.value.length; i++)
await moduleTest(i)
})
</script>
<template>
<VAlert
v-for="(module, index) in modules"
:key="index"
:type="module.state"
:title="module.name"
class="mb-2"
variant="tonal"
>
{{ module.errmsg }}
<template #append>
<VProgressCircular
v-if="module.loading"
indeterminate
/>
</template>
</VAlert>
</template>

View File

@@ -6,6 +6,8 @@ import slack from '@images/logos/slack.png'
import telegram from '@images/logos/telegram.webp'
import tmdb from '@images/logos/tmdb.png'
import wechat from '@images/logos/wechat.png'
import fanart from '@images/logos/fanart.webp'
import tvdb from '@images/logos/thetvdb.jpeg'
interface Status {
OK: string
@@ -57,6 +59,26 @@ const targets = ref<Address[]>([
message: '未测试',
btndisable: false,
},
{
image: tvdb,
name: 'api.thetvdb.com',
url: 'https://api.thetvdb.com/series/81189',
proxy: true,
status: 'Normal',
time: '',
message: '未测试',
btndisable: false,
},
{
image: fanart,
name: 'webservice.fanart.tv',
url: 'https://webservice.fanart.tv',
proxy: true,
status: 'Normal',
time: '',
message: '未测试',
btndisable: false,
},
{
image: telegram,
name: 'api.telegram.org',

View File

@@ -28,7 +28,16 @@ export default defineConfig({
imports: ['vue', 'vue-router', '@vueuse/core', '@vueuse/math', 'vuex'],
vueTemplate: true,
}),
VitePWA({ registerType: 'autoUpdate', injectRegister: 'script', manifest: false }),
VitePWA({
registerType: 'autoUpdate',
injectRegister: 'script',
manifest: false,
workbox: {
navigateFallbackDenylist: [
/.*\/api\/v\d+\/system\/logging.*/,
],
},
}),
],
define: { 'process.env': {} },
resolve: {
@@ -45,6 +54,12 @@ export default defineConfig({
build: {
chunkSizeWarningLimit: 5000,
cssCodeSplit: false,
rollupOptions: {
output: {
entryFileNames: '[name].js',
chunkFileNames: '[name].js',
},
},
},
optimizeDeps: {
exclude: ['vuetify'],

View File

@@ -2422,6 +2422,11 @@ accepts@~1.3.8:
mime-types "~2.1.34"
negotiator "0.6.3"
ace-builds@^1.32.6:
version "1.32.6"
resolved "https://registry.yarnpkg.com/ace-builds/-/ace-builds-1.32.6.tgz#454ec8bc9235fbb960b8d8b86e698f941c104de2"
integrity sha512-dO5BnyDOhCnznhOpILzXq4jqkbhRXxNkf3BuVTmyxGyRLrhddfdyk6xXgy+7A8LENrcYoFi/sIxMuH3qjNUN4w==
acorn-jsx@^5.2.0, acorn-jsx@^5.3.2:
version "5.3.2"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
@@ -6651,6 +6656,11 @@ require-from-string@^2.0.2:
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
resize-observer-polyfill@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
@@ -7912,6 +7922,13 @@ vue-tsc@^1.6.5:
"@volar/vue-typescript" "1.6.5"
semver "^7.3.8"
vue3-ace-editor@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/vue3-ace-editor/-/vue3-ace-editor-2.2.4.tgz#1f2a787f91cf7979f27fab29e0e0604bb3ee1c17"
integrity sha512-FZkEyfpbH068BwjhMyNROxfEI8135Sc+x8ouxkMdCNkuj/Tuw83VP/gStFQqZHqljyX9/VfMTCdTqtOnJZGN8g==
dependencies:
resize-observer-polyfill "^1.5.1"
vue3-apexcharts@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/vue3-apexcharts/-/vue3-apexcharts-1.4.1.tgz#ea561308430a1c5213b7f17c44ba3c845f6c490d"