mirror of
https://github.com/EstrellaXD/Auto_Bangumi.git
synced 2026-04-14 10:30:35 +08:00
Merge pull request #435 from EstrellaXD/webui
webui: Fit new api model. Add RSS Manage Page.
This commit is contained in:
@@ -10,6 +10,12 @@ from module.security.api import get_current_user, UNAUTHORIZED
|
||||
router = APIRouter(prefix="/bangumi", tags=["bangumi"])
|
||||
|
||||
|
||||
def str_to_list(data: Bangumi):
|
||||
data.filter = data.filter.split(",")
|
||||
data.rss_link = data.rss_link.split(",")
|
||||
return data
|
||||
|
||||
|
||||
@router.get("/get/all", response_model=list[Bangumi])
|
||||
async def get_all_data(current_user=Depends(get_current_user)):
|
||||
if not current_user:
|
||||
|
||||
@@ -6,6 +6,7 @@ from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from module.core import Program
|
||||
from module.models import APIResponse
|
||||
from module.conf import VERSION
|
||||
from module.security.api import get_current_user, UNAUTHORIZED
|
||||
|
||||
@@ -24,20 +25,29 @@ async def shutdown():
|
||||
program.stop()
|
||||
|
||||
|
||||
@router.get("/restart")
|
||||
@router.get("/restart", response_model=APIResponse)
|
||||
async def restart(current_user=Depends(get_current_user)):
|
||||
if not current_user:
|
||||
raise UNAUTHORIZED
|
||||
try:
|
||||
program.restart()
|
||||
return {"status": "ok"}
|
||||
return JSONResponse(
|
||||
status_code=200,
|
||||
content={"msg_en": "Restart program successfully.", "msg_zh": "重启程序成功。"},
|
||||
)
|
||||
except Exception as e:
|
||||
logger.debug(e)
|
||||
logger.warning("Failed to restart program")
|
||||
raise HTTPException(status_code=500, detail="Failed to restart program")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail={
|
||||
"msg_en": "Failed to restart program.",
|
||||
"msg_zh": "重启程序失败。",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@router.get("/start")
|
||||
@router.get("/start", response_model=APIResponse)
|
||||
async def start(current_user=Depends(get_current_user)):
|
||||
if not current_user:
|
||||
raise UNAUTHORIZED
|
||||
@@ -46,7 +56,13 @@ async def start(current_user=Depends(get_current_user)):
|
||||
except Exception as e:
|
||||
logger.debug(e)
|
||||
logger.warning("Failed to start program")
|
||||
raise HTTPException(status_code=500, detail="Failed to start program")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail={
|
||||
"msg_en": "Failed to start program.",
|
||||
"msg_zh": "启动程序失败。",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@router.get("/stop")
|
||||
@@ -79,11 +95,14 @@ async def shutdown_program(current_user=Depends(get_current_user)):
|
||||
program.stop()
|
||||
logger.info("Shutting down program...")
|
||||
os.kill(os.getpid(), signal.SIGINT)
|
||||
return {"status": "ok"}
|
||||
return JSONResponse(
|
||||
status_code=200,
|
||||
content={"msg_en": "Shutdown program successfully.", "msg_zh": "关闭程序成功。"},
|
||||
)
|
||||
|
||||
|
||||
# Check status
|
||||
@router.get("/check/downloader", tags=["check"])
|
||||
@router.get("/check/downloader", tags=["check"], response_model=bool)
|
||||
async def check_downloader_status(current_user=Depends(get_current_user)):
|
||||
if not current_user:
|
||||
raise UNAUTHORIZED
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi.exceptions import HTTPException
|
||||
|
||||
from module.models.response import ResponseModel
|
||||
|
||||
@@ -7,7 +8,8 @@ def u_response(response_model: ResponseModel):
|
||||
return JSONResponse(
|
||||
status_code=response_model.status_code,
|
||||
content={
|
||||
"status": response_model.status,
|
||||
"msg_en": response_model.msg_en,
|
||||
"msg_zh": response_model.msg_zh,
|
||||
},
|
||||
)
|
||||
)
|
||||
@@ -9,5 +9,6 @@ class ResponseModel(BaseModel):
|
||||
|
||||
|
||||
class APIResponse(BaseModel):
|
||||
status: bool = Field(..., example=True)
|
||||
msg_en: str = Field(..., example="Success")
|
||||
msg_zh: str = Field(..., example="成功")
|
||||
@@ -47,7 +47,7 @@ class RSSEngine(Database):
|
||||
else:
|
||||
return ResponseModel(
|
||||
status=False,
|
||||
status_code=400,
|
||||
status_code=406,
|
||||
msg_en="RSS added failed.",
|
||||
msg_zh="RSS 添加失败。",
|
||||
)
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"pinia": "^2.1.3",
|
||||
"vue": "^3.3.4",
|
||||
"vue-i18n": "^9.2.2",
|
||||
"vue-inline-svg": "^3.1.2",
|
||||
"vue-router": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
11
webui/pnpm-lock.yaml
generated
11
webui/pnpm-lock.yaml
generated
@@ -29,6 +29,9 @@ dependencies:
|
||||
vue-i18n:
|
||||
specifier: ^9.2.2
|
||||
version: 9.2.2(vue@3.3.4)
|
||||
vue-inline-svg:
|
||||
specifier: ^3.1.2
|
||||
version: 3.1.2(vue@3.3.4)
|
||||
vue-router:
|
||||
specifier: ^4.2.1
|
||||
version: 4.2.1(vue@3.3.4)
|
||||
@@ -9769,6 +9772,14 @@ packages:
|
||||
vue: 3.3.4
|
||||
dev: true
|
||||
|
||||
/vue-inline-svg@3.1.2(vue@3.3.4):
|
||||
resolution: {integrity: sha512-K01sLANBnjosObee4JrBu/igXpYIFhQfy4EcEyVWxEWf6nmrxp7Isz6pmeRCsWx6XGrGWfrQH3uNwt4nOmrFdA==}
|
||||
peerDependencies:
|
||||
vue: ^3
|
||||
dependencies:
|
||||
vue: 3.3.4
|
||||
dev: false
|
||||
|
||||
/vue-router@4.2.1(vue@3.3.4):
|
||||
resolution: {integrity: sha512-nW28EeifEp8Abc5AfmAShy5ZKGsGzjcnZ3L1yc2DYUo+MqbBClrRP9yda3dIekM4I50/KnEwo1wkBLf7kHH5Cw==}
|
||||
peerDependencies:
|
||||
|
||||
3
webui/public/images/RSS.svg
Normal file
3
webui/public/images/RSS.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.88235 20C2.07843 20 1.39706 19.7206 0.838235 19.1618C0.279412 18.6029 0 17.9216 0 17.1176C0 16.3137 0.279412 15.6324 0.838235 15.0735C1.39706 14.5147 2.07843 14.2353 2.88235 14.2353C3.68627 14.2353 4.36765 14.5147 4.92647 15.0735C5.48529 15.6324 5.76471 16.3137 5.76471 17.1176C5.76471 17.9216 5.48529 18.6029 4.92647 19.1618C4.36765 19.7206 3.68627 20 2.88235 20ZM17.3529 20C17.3529 17.6078 16.8971 15.3578 15.9853 13.25C15.0735 11.1422 13.8333 9.30392 12.2647 7.73529C10.6961 6.16667 8.85784 4.92647 6.75 4.01471C4.64216 3.10294 2.39216 2.64706 0 2.64706V0C2.76471 0 5.35784 0.52451 7.77941 1.57353C10.201 2.62255 12.3186 4.05392 14.1324 5.86765C15.9461 7.68137 17.3775 9.79902 18.4265 12.2206C19.4755 14.6422 20 17.2353 20 20H17.3529ZM10.3529 20C10.3529 16.902 9.40196 14.3627 7.5 12.3824C5.59804 10.402 3.09804 9.41177 0 9.41177V6.76471C1.90196 6.76471 3.64706 7.09804 5.23529 7.76471C6.82353 8.43137 8.19118 9.35294 9.33823 10.5294C10.4853 11.7059 11.3824 13.1029 12.0294 14.7206C12.6765 16.3382 13 18.098 13 20H10.3529Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -1,5 +1,7 @@
|
||||
import type { BangumiRule, BangumiUpdate } from '#/bangumi';
|
||||
import type { ApiSuccess } from '#/api';
|
||||
import {forEach} from "lodash";
|
||||
|
||||
|
||||
export const apiBangumi = {
|
||||
/**
|
||||
@@ -8,7 +10,10 @@ export const apiBangumi = {
|
||||
*/
|
||||
async getAll() {
|
||||
const { data } = await axios.get<BangumiRule[]>('api/v1/bangumi/get/all');
|
||||
|
||||
forEach(data, (item) => {
|
||||
item.rss_link = item.rss_link.split(',');
|
||||
item.filter = item.filter.split(',');
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
@@ -21,7 +26,8 @@ export const apiBangumi = {
|
||||
const { data } = await axios.get<BangumiRule>(
|
||||
`api/v1/bangumi/get/${bangumiId}`
|
||||
);
|
||||
|
||||
data.rss_link = data.rss_link.split(',');
|
||||
data.filter = data.filter.split(',');
|
||||
return data;
|
||||
},
|
||||
|
||||
@@ -33,8 +39,9 @@ export const apiBangumi = {
|
||||
*/
|
||||
async updateRule(bangumiId: number, bangumiRule: BangumiRule) {
|
||||
const rule = omit(bangumiRule, ['id']);
|
||||
|
||||
const { data } = await axios.patch<ApiSuccess>(
|
||||
rule.rss_link = rule.rss_link.join(',');
|
||||
rule.filter = rule.filter.join(',');
|
||||
const { data } = await axios.patch< ApiSuccess >(
|
||||
`api/v1/bangumi/update/${bangumiId}`,
|
||||
rule
|
||||
);
|
||||
@@ -47,15 +54,23 @@ export const apiBangumi = {
|
||||
* @param file - 是否同时删除关联文件。
|
||||
* @returns axios 请求返回的数据
|
||||
*/
|
||||
async deleteRule(bangumiId: number, file: boolean) {
|
||||
const { data } = await axios.delete<ApiSuccess>(
|
||||
`api/v1/bangumi/delete/${bangumiId}`,
|
||||
{
|
||||
params: {
|
||||
file,
|
||||
},
|
||||
}
|
||||
);
|
||||
async deleteRule(bangumiId: number | number[], file: boolean) {
|
||||
let url = 'api/v1/bangumi/delete';
|
||||
let ids: undefined | number[];
|
||||
|
||||
if (typeof bangumiId === 'number') {
|
||||
url = `${url}/${bangumiId}`;
|
||||
} else {
|
||||
url = `${url}/many`;
|
||||
ids = bangumiId;
|
||||
}
|
||||
|
||||
const { data } = await axios.delete< ApiSuccess >(url, {
|
||||
data: ids,
|
||||
params: {
|
||||
file,
|
||||
},
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
@@ -65,15 +80,23 @@ export const apiBangumi = {
|
||||
* @param file - 是否同时删除关联文件。
|
||||
* @returns axios 请求返回的数据
|
||||
*/
|
||||
async disableRule(bangumiId: number, file: boolean) {
|
||||
const { data } = await axios.delete<ApiSuccess>(
|
||||
`api/v1/bangumi/disable/${bangumiId}`,
|
||||
{
|
||||
params: {
|
||||
file,
|
||||
},
|
||||
}
|
||||
);
|
||||
async disableRule(bangumiId: number | number[], file: boolean) {
|
||||
let url = 'api/v1/bangumi/disable';
|
||||
let ids: undefined | number[];
|
||||
|
||||
if (typeof bangumiId === 'number') {
|
||||
url = `${url}/${bangumiId}`;
|
||||
} else {
|
||||
url = `${url}/many`;
|
||||
ids = bangumiId;
|
||||
}
|
||||
|
||||
const { data } = await axios.delete< ApiSuccess >(url, {
|
||||
data: ids,
|
||||
params: {
|
||||
file,
|
||||
},
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
@@ -82,7 +105,7 @@ export const apiBangumi = {
|
||||
* @param bangumiId - 需要启用的 bangumi 的 id
|
||||
*/
|
||||
async enableRule(bangumiId: number) {
|
||||
const { data } = await axios.get<ApiSuccess>(
|
||||
const { data } = await axios.get< ApiSuccess >(
|
||||
`api/v1/bangumi/enable/${bangumiId}`
|
||||
);
|
||||
return data;
|
||||
@@ -92,9 +115,7 @@ export const apiBangumi = {
|
||||
* 重置所有 bangumi 数据
|
||||
*/
|
||||
async resetAll() {
|
||||
const { data } = await axios.get<{
|
||||
message: 'OK';
|
||||
}>('api/v1/bangumi/resetAll');
|
||||
const { data } = await axios.get< ApiSuccess >('api/v1/bangumi/resetAll');
|
||||
return data;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -3,23 +3,8 @@ export const apiCheck = {
|
||||
* 检测下载器
|
||||
*/
|
||||
async downloader() {
|
||||
const { data } = await axios.get('api/v1/check/downloader');
|
||||
const { data } = await axios.get<Boolean>('api/v1/check/downloader');
|
||||
return data;
|
||||
},
|
||||
|
||||
/**
|
||||
* 检测 RSS
|
||||
*/
|
||||
async rss() {
|
||||
const { data } = await axios.get('api/v1/check/rss');
|
||||
return data;
|
||||
},
|
||||
|
||||
/**
|
||||
* 检测所有
|
||||
*/
|
||||
async all() {
|
||||
const { data } = await axios.get('api/v1/check');
|
||||
return data;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
import type { Config } from '#/config';
|
||||
import type { ApiSuccess } from '#/api';
|
||||
|
||||
export const apiConfig = {
|
||||
/**
|
||||
* 获取 config 数据
|
||||
*/
|
||||
async getConfig() {
|
||||
const { data } = await axios.get<Config>('api/v1/getConfig');
|
||||
const { data } = await axios.get<Config>('api/v1/config/get');
|
||||
return data;
|
||||
},
|
||||
|
||||
@@ -14,10 +15,10 @@ export const apiConfig = {
|
||||
* @param newConfig - 需要更新的 config
|
||||
*/
|
||||
async updateConfig(newConfig: Config) {
|
||||
const { data } = await axios.post<{
|
||||
message: 'Success' | 'Failed to update config';
|
||||
}>('api/v1/updateConfig', newConfig);
|
||||
|
||||
return data.message === 'Success';
|
||||
const { data } = await axios.patch<ApiSuccess>(
|
||||
'api/v1/config/update',
|
||||
newConfig
|
||||
);
|
||||
return data;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import type { BangumiRule } from '#/bangumi';
|
||||
|
||||
interface Status {
|
||||
status: 'Success';
|
||||
}
|
||||
import type { ApiSuccess } from '#/api';
|
||||
|
||||
export const apiDownload = {
|
||||
/**
|
||||
@@ -30,11 +27,11 @@ export const apiDownload = {
|
||||
* @param bangumiData - Bangumi 数据
|
||||
*/
|
||||
async collection(bangumiData: BangumiRule) {
|
||||
const { data } = await axios.post<Status>(
|
||||
const { data } = await axios.post<ApiSuccess>(
|
||||
'api/v1/download/collection',
|
||||
bangumiData
|
||||
);
|
||||
return data.status === 'Success';
|
||||
return data;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -42,10 +39,10 @@ export const apiDownload = {
|
||||
* @param bangumiData - Bangumi 数据
|
||||
*/
|
||||
async subscribe(bangumiData: BangumiRule) {
|
||||
const { data } = await axios.post<Status>(
|
||||
const { data } = await axios.post<ApiSuccess>(
|
||||
'api/v1/download/subscribe',
|
||||
bangumiData
|
||||
);
|
||||
return data.status === 'Success';
|
||||
return data;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { ApiSuccess } from "#/api";
|
||||
|
||||
export const apiLog = {
|
||||
async getLog() {
|
||||
const { data } = await axios.get<string>('api/v1/log');
|
||||
@@ -5,7 +7,7 @@ export const apiLog = {
|
||||
},
|
||||
|
||||
async clearLog() {
|
||||
const { data } = await axios.get<{ status: 'ok' }>('api/v1/log/clear');
|
||||
return data.status === 'ok';
|
||||
const { data } = await axios.get<ApiSuccess>('api/v1/log/clear');
|
||||
return data;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
interface Success {
|
||||
status: 'ok';
|
||||
}
|
||||
import type { ApiSuccess } from "#/api";
|
||||
|
||||
|
||||
export const apiProgram = {
|
||||
/**
|
||||
* 重启
|
||||
*/
|
||||
async restart() {
|
||||
const { data } = await axios.get<Success>('api/v1/restart');
|
||||
return data.status === 'ok';
|
||||
const { data } = await axios.get<ApiSuccess>('api/v1/restart');
|
||||
return data;
|
||||
},
|
||||
|
||||
/**
|
||||
* 启动
|
||||
*/
|
||||
async start() {
|
||||
const { data } = await axios.get<Success>('api/v1/start');
|
||||
return data.status === 'ok';
|
||||
const { data } = await axios.get<ApiSuccess>('api/v1/start');
|
||||
return data;
|
||||
},
|
||||
|
||||
/**
|
||||
* 停止
|
||||
*/
|
||||
async stop() {
|
||||
const { data } = await axios.get<Success>('api/v1/stop');
|
||||
return data.status === 'ok';
|
||||
const { data } = await axios.get<ApiSuccess>('api/v1/stop');
|
||||
return data;
|
||||
},
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
async status() {
|
||||
const { data } = await axios.get<{ status: 'running' | 'stop' }>(
|
||||
const { data } = await axios.get<{ status: boolean; version: string }>(
|
||||
'api/v1/status'
|
||||
);
|
||||
return data.status === 'running';
|
||||
|
||||
return data!;
|
||||
},
|
||||
|
||||
/**
|
||||
* 终止
|
||||
*/
|
||||
async shutdown() {
|
||||
const { data } = await axios.get<Success>('api/v1/shutdown');
|
||||
return data.status === 'ok';
|
||||
const { data } = await axios.get<ApiSuccess>('api/v1/shutdown');
|
||||
return data;
|
||||
},
|
||||
};
|
||||
|
||||
40
webui/src/api/rss.ts
Normal file
40
webui/src/api/rss.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import type { RSS } from '#/rss';
|
||||
import type { Torrent } from '#/torrent';
|
||||
import type { ApiSuccess } from '#/api';
|
||||
|
||||
export const apiRSS = {
|
||||
async get() {
|
||||
const { data } = await axios.get<RSS[]>('api/v1/rss');
|
||||
return data!;
|
||||
},
|
||||
|
||||
async add(rss: RSS) {
|
||||
const { data } = await axios.post<ApiSuccess>('api/v1/rss/add', rss);
|
||||
return data;
|
||||
},
|
||||
|
||||
async delete(rss_id: number) {
|
||||
const { data } = await axios.delete<ApiSuccess>(`api/v1/rss/delete/${rss_id}`);
|
||||
return data!;
|
||||
},
|
||||
|
||||
async update(rss_id: number, rss: RSS) {
|
||||
const { data } = await axios.patch<ApiSuccess>(`api/v1/rss/update/${rss_id}`, rss);
|
||||
return data!;
|
||||
},
|
||||
|
||||
async refreshAll() {
|
||||
const { data } = await axios.get<ApiSuccess>('api/v1/rss/refresh/all');
|
||||
return data!;
|
||||
},
|
||||
|
||||
async refresh(rss_id: number) {
|
||||
const { data } = await axios.get<ApiSuccess>(`api/v1/rss/refresh/${rss_id}`);
|
||||
return data!;
|
||||
},
|
||||
|
||||
async getTorrent(rss_id: number) {
|
||||
const { data } = await axios.get<Torrent[]>(`api/v1/rss/torrent/${rss_id}`);
|
||||
return data!;
|
||||
},
|
||||
};
|
||||
11
webui/src/api/search.ts
Normal file
11
webui/src/api/search.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export const apiSearch = {
|
||||
async get(keyword: string, site = 'mikan') {
|
||||
const { data } = await axios.get('api/v1/search', {
|
||||
params: {
|
||||
site,
|
||||
keyword,
|
||||
},
|
||||
});
|
||||
return data!;
|
||||
},
|
||||
};
|
||||
@@ -1,7 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { useMessage } from 'naive-ui';
|
||||
import type { BangumiRule } from '#/bangumi';
|
||||
import type { RSS } from '#/rss';
|
||||
import { rssTemplate } from '#/rss';
|
||||
import { ruleTemplate } from '#/bangumi';
|
||||
import { registerSW } from 'virtual:pwa-register';
|
||||
import type { ApiError } from "#/api";
|
||||
|
||||
/** v-model show */
|
||||
const show = defineModel('show', { default: false });
|
||||
@@ -9,9 +13,11 @@ const show = defineModel('show', { default: false });
|
||||
const message = useMessage();
|
||||
const { getAll } = useBangumiStore();
|
||||
|
||||
const rss = ref('');
|
||||
const rss = ref<RSS>(rssTemplate);
|
||||
const rule = ref<BangumiRule>(ruleTemplate);
|
||||
|
||||
const parserType = ['mikan', 'tmdb', 'parser'];
|
||||
|
||||
const analysis = reactive({
|
||||
loading: false,
|
||||
next: false,
|
||||
@@ -23,29 +29,44 @@ const loading = reactive({
|
||||
|
||||
watch(show, (val) => {
|
||||
if (!val) {
|
||||
rss.value = '';
|
||||
rss.value = rssTemplate;
|
||||
setTimeout(() => {
|
||||
analysis.next = false;
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
async function analysisRss() {
|
||||
if (rss.value === '') {
|
||||
async function addRss() {
|
||||
if (rss.value.url === '') {
|
||||
message.error('Please enter the RSS link!');
|
||||
} else if (rss.value.aggregate) {
|
||||
try {
|
||||
analysis.loading = true;
|
||||
const data = await apiRSS.add(rss.value);
|
||||
analysis.loading = false;
|
||||
analysis.next = true;
|
||||
message.success(data.msg_en);
|
||||
show.value = false;
|
||||
console.log('rss', data);
|
||||
} catch (error) {
|
||||
const err = error as ApiError;
|
||||
message.error(err.msg_en);
|
||||
console.log('error', err.msg_en);
|
||||
analysis.loading = false;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
analysis.loading = true;
|
||||
const data = await apiDownload.analysis(rss.value);
|
||||
const data = await apiDownload.analysis(rss.value.url);
|
||||
analysis.loading = false;
|
||||
|
||||
rule.value = data;
|
||||
analysis.next = true;
|
||||
console.log('rule', data);
|
||||
} catch (error) {
|
||||
const err = error as { status: string };
|
||||
message.error(err.status);
|
||||
console.log('error', err);
|
||||
const err = error as ApiError;
|
||||
message.error(err.msg_en);
|
||||
console.log('error', err.msg_en);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,11 +115,32 @@ async function subscribe() {
|
||||
<ab-popup v-model:show="show" :title="$t('topbar.add.title')" css="w-360px">
|
||||
<div v-if="!analysis.next" space-y-12px>
|
||||
<ab-setting
|
||||
v-model:data="rss"
|
||||
v-model:data="rss.url"
|
||||
:label="$t('topbar.add.rss_link')"
|
||||
type="input"
|
||||
:prop="{
|
||||
placeholder: $t('topbar.add.placeholder'),
|
||||
placeholder: $t('topbar.add.placeholder_link'),
|
||||
}"
|
||||
></ab-setting>
|
||||
<ab-setting
|
||||
v-model:data="rss.name"
|
||||
:label="$t('topbar.add.name')"
|
||||
type="input"
|
||||
:prop="{
|
||||
placeholder: $t('topbar.add.placeholder_name'),
|
||||
}"
|
||||
></ab-setting>
|
||||
<ab-setting
|
||||
v-model:data="rss.aggregate"
|
||||
:label="$t('topbar.add.aggregate')"
|
||||
type="switch"
|
||||
></ab-setting>
|
||||
<ab-setting
|
||||
v-model:data="rss.parser"
|
||||
:label="$t('topbar.add.parser')"
|
||||
type="select"
|
||||
:prop="{
|
||||
items: parserType,
|
||||
}"
|
||||
:bottom-line="true"
|
||||
></ab-setting>
|
||||
@@ -107,8 +149,8 @@ async function subscribe() {
|
||||
<ab-button
|
||||
size="small"
|
||||
:loading="analysis.loading"
|
||||
@click="analysisRss"
|
||||
>{{ $t('topbar.add.analyse') }}</ab-button
|
||||
@click="addRss"
|
||||
>{{ $t('topbar.add.button') }}</ab-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
63
webui/src/components/ab-rss-item.vue
Normal file
63
webui/src/components/ab-rss-item.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
select: boolean;
|
||||
name: string;
|
||||
url: string;
|
||||
enable: boolean;
|
||||
aggregate: boolean;
|
||||
parser: string;
|
||||
}>(),
|
||||
{
|
||||
select: false,
|
||||
enable: false,
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="rss-group">
|
||||
<div class="left-side" flex space-x-40px>
|
||||
<ab-checkbox
|
||||
small
|
||||
:model-value="select"
|
||||
@update:model-value="select = $event"
|
||||
/>
|
||||
<div w-200px text-h3 truncate>{{ name }}</div>
|
||||
<div w-300px text-h3 truncate>{{ url }}</div>
|
||||
</div>
|
||||
<div class="right-side" space-x-8px>
|
||||
<ab-tag
|
||||
v-if="parser"
|
||||
type="primary"
|
||||
:title="parser"
|
||||
/>
|
||||
<ab-tag
|
||||
v-if="aggregate"
|
||||
type="primary"
|
||||
title="aggregate"
|
||||
/>
|
||||
<ab-tag
|
||||
v-if="enable"
|
||||
type="active"
|
||||
title="active"
|
||||
/>
|
||||
<ab-tag
|
||||
v-if="!enable"
|
||||
type="inactive"
|
||||
title="inactive"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.rss-group {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,10 +1,20 @@
|
||||
<script lang="ts" setup>
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
round?: boolean;
|
||||
}>(),
|
||||
{
|
||||
round: false,
|
||||
}
|
||||
);
|
||||
|
||||
defineEmits(['click']);
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
rounded="1/2"
|
||||
:rounded="round ? '1/2' : '8px'"
|
||||
wh-36px
|
||||
f-cer
|
||||
rel
|
||||
@@ -18,8 +28,9 @@ defineEmits(['click']);
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$normal: #5739ca;
|
||||
$hover: #71669f;
|
||||
$normal: #4e3c94;
|
||||
$hover: #281e52;
|
||||
$active: #8e8a9c;
|
||||
|
||||
.box {
|
||||
background: $normal;
|
||||
@@ -34,8 +45,9 @@ $hover: #71669f;
|
||||
}
|
||||
|
||||
.line {
|
||||
width: 6px;
|
||||
width: 4px;
|
||||
height: 18px;
|
||||
border-radius: 1px;
|
||||
background: #fff;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<script lang="ts" setup>
|
||||
import { Switch } from '@headlessui/vue';
|
||||
import {Switch} from '@headlessui/vue';
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
small?: boolean;
|
||||
}>(),
|
||||
{
|
||||
small: false,
|
||||
}
|
||||
defineProps<{
|
||||
small?: boolean;
|
||||
}>(),
|
||||
{
|
||||
small: false,
|
||||
}
|
||||
);
|
||||
|
||||
const checked = defineModel<boolean>({ default: false });
|
||||
const checked = defineModel<boolean>({default: false});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -19,23 +19,26 @@ const checked = defineModel<boolean>({ default: false });
|
||||
<slot name="before"></slot>
|
||||
|
||||
<div
|
||||
rounded-4px
|
||||
rel
|
||||
f-cer
|
||||
bg-white
|
||||
border="3px #3c239f"
|
||||
:class="[small ? 'wh-16px' : 'wh-32px', !checked && 'group']"
|
||||
rel
|
||||
f-cer
|
||||
bg-white
|
||||
:class="[
|
||||
small ? 'wh-16px' : 'wh-32px',
|
||||
!checked && 'group'
|
||||
]"
|
||||
:border="small ? '2px solid #3c239f' : '4px solid #3c239f'"
|
||||
:rounded="small ? '4px' : '6px'"
|
||||
>
|
||||
<div
|
||||
rounded-2px
|
||||
transition-all
|
||||
duration-300
|
||||
:class="[
|
||||
rounded-2px
|
||||
transition-all
|
||||
duration-300
|
||||
:class="[
|
||||
small ? 'wh-8px' : 'wh-16px',
|
||||
checked ? 'bg-[#3c239f]' : 'bg-transparent',
|
||||
]"
|
||||
group-hover:bg="#cccad4"
|
||||
group-active:bg="#3c239f"
|
||||
group-hover:bg="#cccad4"
|
||||
group-active:bg="#3c239f"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
|
||||
23
webui/src/components/basic/ab-tag.stories.ts
Normal file
23
webui/src/components/basic/ab-tag.stories.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Meta, StoryObj} from "@storybook/vue3";
|
||||
|
||||
import AbTag from "./ab-tag.vue";
|
||||
|
||||
const meta: Meta<typeof AbTag> = {
|
||||
title: "basic/ab-tag",
|
||||
component: AbTag,
|
||||
tags: ["autodocs"],
|
||||
}
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof AbTag>;
|
||||
|
||||
|
||||
export const Template: Story = {
|
||||
render: (args) => ({
|
||||
components: { AbTag },
|
||||
setup() {
|
||||
return { args };
|
||||
},
|
||||
template: '<ab-tag v-bind="args" />',
|
||||
}),
|
||||
};
|
||||
86
webui/src/components/basic/ab-tag.vue
Normal file
86
webui/src/components/basic/ab-tag.vue
Normal file
@@ -0,0 +1,86 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
type: 'primary' | 'warn' | 'inactive' | 'active' | 'notify';
|
||||
title: string;
|
||||
}>(),
|
||||
{
|
||||
type: 'primary',
|
||||
title: 'title',
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="round-label" :class="type">
|
||||
{{ title }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// border
|
||||
$primary: #4e3c94;
|
||||
$warn: #892F2F;
|
||||
$inactive: #797979;
|
||||
$active: #104931;
|
||||
$notify: #F5C451;
|
||||
|
||||
//inner
|
||||
$primary-inner: #EEE5F4;
|
||||
$warn-inner: #FFDFDF;
|
||||
$inactive-inner: #E0E0E0;
|
||||
$active-inner:#E5F4E0;
|
||||
$notify-inner: #FFF4DB;
|
||||
|
||||
//font-color
|
||||
$primary-font: #000000;
|
||||
$warn-font: #892F2F;
|
||||
$inactive-font: #3F3F3F;
|
||||
$active-font: #4C6643;
|
||||
$notify-font: #A76E18;
|
||||
|
||||
|
||||
.round-label {
|
||||
padding: 2px 12px;
|
||||
font-size: 8px;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.primary {
|
||||
border: 1px solid $primary;
|
||||
background-color: $primary-inner;
|
||||
color: $primary-font;
|
||||
}
|
||||
|
||||
.warn {
|
||||
border: 1px solid $warn;
|
||||
background-color: $warn-inner;
|
||||
color: $warn-font;
|
||||
}
|
||||
|
||||
.inactive {
|
||||
border: 1px solid $inactive;
|
||||
background-color: $inactive-inner;
|
||||
color: $inactive-font;
|
||||
}
|
||||
|
||||
.active {
|
||||
border: 1px solid $active;
|
||||
background-color: $active-inner;
|
||||
color: $active-font;
|
||||
}
|
||||
|
||||
.notify {
|
||||
border: 1px solid $notify;
|
||||
background-color: $notify-inner;
|
||||
color: $notify-font;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
Play,
|
||||
SettingTwo,
|
||||
} from '@icon-park/vue-next';
|
||||
import InlineSvg from 'vue-inline-svg';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -26,6 +27,12 @@ const toggle = () => (show.value = !show.value);
|
||||
const route = useRoute();
|
||||
const { logout } = useAuth();
|
||||
|
||||
const RSS = h(
|
||||
'span',
|
||||
{ class: ['rel', 'left-2px'] },
|
||||
h(InlineSvg, { src: '/images/RSS.svg' })
|
||||
);
|
||||
|
||||
const items = [
|
||||
{
|
||||
id: 1,
|
||||
@@ -42,25 +49,31 @@ const items = [
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
icon: RSS,
|
||||
label: () => t('sidebar.rss'),
|
||||
path: '/rss',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
icon: Play,
|
||||
label: () => t('sidebar.player'),
|
||||
path: '/player',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
id: 5,
|
||||
icon: Download,
|
||||
label: () => t('sidebar.downloader'),
|
||||
path: '/downloader',
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
id: 6,
|
||||
icon: Log,
|
||||
label: () => t('sidebar.log'),
|
||||
path: '/log',
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
id: 7,
|
||||
icon: SettingTwo,
|
||||
label: () => t('sidebar.config'),
|
||||
path: '/config',
|
||||
@@ -123,6 +136,7 @@ const items = [
|
||||
]"
|
||||
>
|
||||
<Component :is="i.icon" :size="24" />
|
||||
|
||||
<div text-h2>{{ i.label() }}</div>
|
||||
</RouterLink>
|
||||
|
||||
|
||||
@@ -9,14 +9,13 @@ import {
|
||||
} from '@icon-park/vue-next';
|
||||
|
||||
const { t, changeLocale } = useMyI18n();
|
||||
const { running, onUpdate, offUpdate } = useAppInfo();
|
||||
|
||||
const search = ref('');
|
||||
const show = ref(false);
|
||||
const showAdd = ref(false);
|
||||
|
||||
const { onUpdate, offUpdate, start, pause, shutdown, restart, resetRule } =
|
||||
useProgramStore();
|
||||
const { running } = storeToRefs(useProgramStore());
|
||||
const { start, pause, shutdown, restart, resetRule } = useProgramStore();
|
||||
|
||||
const items = [
|
||||
{
|
||||
|
||||
@@ -24,34 +24,6 @@ const items: SettingItem<RssParser>[] = [
|
||||
label: () => t('config.parser_set.enable'),
|
||||
type: 'switch',
|
||||
},
|
||||
{
|
||||
configKey: 'type',
|
||||
label: () => t('config.parser_set.source'),
|
||||
type: 'select',
|
||||
css: 'w-115px',
|
||||
prop: {
|
||||
items: sourceItems,
|
||||
},
|
||||
},
|
||||
{
|
||||
configKey: 'token',
|
||||
label: () => t('config.parser_set.token'),
|
||||
type: 'input',
|
||||
prop: {
|
||||
type: 'text',
|
||||
placeholder: 'token',
|
||||
},
|
||||
},
|
||||
{
|
||||
configKey: 'custom_url',
|
||||
label: () => t('config.parser_set.url'),
|
||||
type: 'input',
|
||||
prop: {
|
||||
type: 'text',
|
||||
placeholder: 'mikanime.tv',
|
||||
},
|
||||
bottomLine: true,
|
||||
},
|
||||
{
|
||||
configKey: 'language',
|
||||
label: () => t('config.parser_set.language'),
|
||||
@@ -60,14 +32,6 @@ const items: SettingItem<RssParser>[] = [
|
||||
items: langs,
|
||||
},
|
||||
},
|
||||
{
|
||||
configKey: 'parser_type',
|
||||
label: () => t('config.parser_set.type'),
|
||||
type: 'select',
|
||||
prop: {
|
||||
items: parserMethods,
|
||||
},
|
||||
},
|
||||
{
|
||||
configKey: 'filter',
|
||||
label: () => t('config.parser_set.exclude'),
|
||||
|
||||
35
webui/src/hooks/useAppInfo.ts
Normal file
35
webui/src/hooks/useAppInfo.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
export const useAppInfo = createSharedComposable(() => {
|
||||
const { auth } = useAuth();
|
||||
const running = ref<boolean>(false);
|
||||
const version = ref<string>('');
|
||||
|
||||
function getStatus() {
|
||||
const { execute, onResult } = useApi(apiProgram.status);
|
||||
|
||||
onResult((res) => {
|
||||
running.value = res.status;
|
||||
version.value = res.version;
|
||||
});
|
||||
|
||||
if (auth.value !== '') {
|
||||
execute();
|
||||
}
|
||||
}
|
||||
|
||||
const { pause: offUpdate, resume: onUpdate } = useIntervalFn(
|
||||
getStatus,
|
||||
3000,
|
||||
{
|
||||
immediate: false,
|
||||
immediateCallback: true,
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
running,
|
||||
version,
|
||||
|
||||
onUpdate,
|
||||
offUpdate,
|
||||
};
|
||||
});
|
||||
@@ -49,7 +49,7 @@ export const useAuth = createSharedComposable(() => {
|
||||
if (error.status === 404) {
|
||||
message.error('请更新AutoBangumi!');
|
||||
} else {
|
||||
message.error(error.detail);
|
||||
message.error(error.msg_zh);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
"config": "Config",
|
||||
"logout": "Logout",
|
||||
"downloader": "Downloader",
|
||||
"calendar": "Calendar"
|
||||
"calendar": "Calendar",
|
||||
"rss": "RSS Manager"
|
||||
},
|
||||
"topbar": {
|
||||
"start": "Start",
|
||||
@@ -30,10 +31,14 @@
|
||||
"update_btn": "Update"
|
||||
},
|
||||
"add": {
|
||||
"title": "Add Bangumi",
|
||||
"title": "Add RSS",
|
||||
"rss_link": "RSS Link",
|
||||
"placeholder": "Please enter the RSS link",
|
||||
"analyse": "Analyse"
|
||||
"name": "Name",
|
||||
"aggregate": "Aggregate RSS",
|
||||
"parser": "Parser",
|
||||
"placeholder_link": "Please enter the RSS link",
|
||||
"placeholder_name": "Optional",
|
||||
"button": "Add"
|
||||
}
|
||||
},
|
||||
"homepage": {
|
||||
@@ -55,6 +60,15 @@
|
||||
"edit_rule": "Edit Rule"
|
||||
}
|
||||
},
|
||||
"rss": {
|
||||
"title": "RSS Item",
|
||||
"selectbox": "Select",
|
||||
"name": "Name",
|
||||
"url": "Url",
|
||||
"status": "Status",
|
||||
"delete": "Delete",
|
||||
"disable": "Disable"
|
||||
},
|
||||
"player": {
|
||||
"hit": "Please set up the media player"
|
||||
},
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
"config": "设置",
|
||||
"logout": "退出",
|
||||
"calendar": "番剧日历",
|
||||
"downloader": "下载器"
|
||||
"downloader": "下载器",
|
||||
"rss": "RSS 管理"
|
||||
},
|
||||
"topbar": {
|
||||
"start": "启动",
|
||||
@@ -30,10 +31,14 @@
|
||||
"update_btn": "更新"
|
||||
},
|
||||
"add": {
|
||||
"title": "添加番剧",
|
||||
"rss_link": "RSS链接",
|
||||
"placeholder": "请输入RSS链接",
|
||||
"analyse": "分析"
|
||||
"title": "添加 RSS",
|
||||
"rss_link": "RSS 链接",
|
||||
"name": "名称",
|
||||
"aggregate": "聚合 RSS",
|
||||
"parser": "解析器",
|
||||
"placeholder_link": "请输入 RSS 链接",
|
||||
"placeholder_name": "可选",
|
||||
"button": "添加"
|
||||
}
|
||||
},
|
||||
"homepage": {
|
||||
@@ -55,6 +60,15 @@
|
||||
"edit_rule": "编辑规则"
|
||||
}
|
||||
},
|
||||
"rss": {
|
||||
"title": "RSS 条目",
|
||||
"selectbox": "选择",
|
||||
"name": "名称",
|
||||
"url": "Url",
|
||||
"status": "状态",
|
||||
"delete": "删除",
|
||||
"disable": "禁用",
|
||||
},
|
||||
"player": {
|
||||
"hit": "请设置媒体播放器地址"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
const { onUpdate, offUpdate, reset, copy } = useLogStore();
|
||||
const { log } = storeToRefs(useLogStore());
|
||||
const { version } = useAppInfo();
|
||||
|
||||
onActivated(() => {
|
||||
onUpdate();
|
||||
@@ -50,10 +51,10 @@ definePage({
|
||||
</ab-button>
|
||||
</ab-label>
|
||||
|
||||
<ab-label label="WebUI Repo">
|
||||
<ab-label label="Official Website">
|
||||
<ab-button
|
||||
size="small"
|
||||
link="https://github.com/Rewrite0/Auto_Bangumi_WebUI"
|
||||
link="https://autobangumi.org"
|
||||
target="_blank"
|
||||
>
|
||||
{{ $t('log.go') }}
|
||||
@@ -62,7 +63,7 @@ definePage({
|
||||
|
||||
<div line></div>
|
||||
|
||||
<ab-label label="Twitter">
|
||||
<ab-label label="X">
|
||||
<ab-button
|
||||
size="small"
|
||||
link="https://twitter.com/Estrella_Pan"
|
||||
@@ -98,15 +99,10 @@ definePage({
|
||||
|
||||
<div line></div>
|
||||
|
||||
<ab-button
|
||||
mx-auto
|
||||
text-16px
|
||||
w-300px
|
||||
h-46px
|
||||
rounded-10px
|
||||
link="mailto:estrellaxd05@gmail.com"
|
||||
>Email Contact</ab-button
|
||||
>
|
||||
<div text-center text-primary text-h3>
|
||||
<span>Version: </span>
|
||||
<span>{{ version }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</ab-container>
|
||||
</div>
|
||||
|
||||
55
webui/src/pages/index/rss.vue
Normal file
55
webui/src/pages/index/rss.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<script lang="ts" setup>
|
||||
const {rss} = storeToRefs(useRSSStore());
|
||||
const {getAll, updateRSS, deleteRSS} = useRSSStore();
|
||||
|
||||
onActivated(() => {
|
||||
getAll();
|
||||
});
|
||||
definePage({
|
||||
name: 'RSS',
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ab-fold-panel :title="$t('rss.title')">
|
||||
<div flex justify-between>
|
||||
<div flex space-x-40px>
|
||||
<div text-h3>{{ $t('rss.selectbox') }}</div>
|
||||
<div class="spacer-1"></div>
|
||||
<div text-h3>{{ $t('rss.name') }}</div>
|
||||
<div class="spacer-2"></div>
|
||||
<div text-h3>{{ $t('rss.url') }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div text-h3>{{ $t('rss.status') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="!open" line my-12px></div>
|
||||
<div>
|
||||
<ab-rss-item
|
||||
v-for="i in rss"
|
||||
:key="i.id"
|
||||
:name="i.name"
|
||||
:url="i.url"
|
||||
:enable="i.enabled"
|
||||
:parser="i.parser"
|
||||
:aggregate="i.aggregate">
|
||||
</ab-rss-item>
|
||||
</div>
|
||||
<div v-show="!open" line my-12px></div>
|
||||
<div flex="~ justify-end" space-x-10px>
|
||||
<ab-button icon="edit">{{ $t('rss.disable') }}</ab-button>
|
||||
<ab-button class="type-warn" text="delete">{{ $t('rss.delete') }}</ab-button>
|
||||
</div>
|
||||
</ab-fold-panel>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.spacer-1 {
|
||||
width: 32px;
|
||||
}
|
||||
.spacer-2 {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { BangumiRule } from '#/bangumi';
|
||||
import { ruleTemplate } from '#/bangumi';
|
||||
import type { ApiSuccess } from '#/api';
|
||||
|
||||
export const useBangumiStore = defineStore('bangumi', () => {
|
||||
const message = useMessage();
|
||||
@@ -45,8 +46,8 @@ export const useBangumiStore = defineStore('bangumi', () => {
|
||||
getAll();
|
||||
}
|
||||
|
||||
function actionSuccess({ msg }) {
|
||||
message.success(msg);
|
||||
function actionSuccess(apiRes: ApiSuccess) {
|
||||
message.success(apiRes.msg_en);
|
||||
refresh();
|
||||
}
|
||||
onUpdateRuleResult(actionSuccess);
|
||||
|
||||
@@ -1,28 +1,4 @@
|
||||
export const useProgramStore = defineStore('program', () => {
|
||||
const { auth } = useAuth();
|
||||
const running = ref(false);
|
||||
|
||||
function getStatus() {
|
||||
const { execute, onResult } = useApi(apiProgram.status);
|
||||
|
||||
onResult((res) => {
|
||||
running.value = res;
|
||||
});
|
||||
|
||||
if (auth.value !== '') {
|
||||
execute();
|
||||
}
|
||||
}
|
||||
|
||||
const { pause: offUpdate, resume: onUpdate } = useIntervalFn(
|
||||
getStatus,
|
||||
3000,
|
||||
{
|
||||
immediate: false,
|
||||
immediateCallback: true,
|
||||
}
|
||||
);
|
||||
|
||||
function opts(handle: string) {
|
||||
return {
|
||||
failRule: (res: boolean) => !res,
|
||||
@@ -43,11 +19,6 @@ export const useProgramStore = defineStore('program', () => {
|
||||
);
|
||||
|
||||
return {
|
||||
running,
|
||||
getStatus,
|
||||
onUpdate,
|
||||
offUpdate,
|
||||
|
||||
start,
|
||||
pause,
|
||||
shutdown,
|
||||
|
||||
49
webui/src/store/rss.ts
Normal file
49
webui/src/store/rss.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import type { RSS } from '#/rss';
|
||||
import type { ApiSuccess } from '#/api';
|
||||
|
||||
export const useRSSStore = defineStore('rss', () => {
|
||||
const message = useMessage();
|
||||
const rss = ref<RSS[]>();
|
||||
|
||||
const { execute: getAll, onResult: onRSSResult } = useApi(
|
||||
apiRSS.get
|
||||
);
|
||||
const { execute: updateRSS, onResult: onUpdateRSSResult } = useApi(
|
||||
apiRSS.update
|
||||
);
|
||||
const { execute: deleteRSS, onResult: onDeleteRSSResult } = useApi(
|
||||
apiRSS.delete
|
||||
);
|
||||
|
||||
|
||||
onRSSResult((res) => {
|
||||
function sort(arr: RSS[]) {
|
||||
return arr.sort((a, b) => b.id - a.id);
|
||||
}
|
||||
|
||||
const enabled = sort(res.filter((e) => e.enabled));
|
||||
const disabled = sort(res.filter((e) => !e.enabled));
|
||||
|
||||
rss.value = [...enabled, ...disabled];
|
||||
});
|
||||
|
||||
function refresh() {
|
||||
getAll();
|
||||
}
|
||||
|
||||
function actionSuccess(apiRes: ApiSuccess) {
|
||||
message.success(apiRes.msg_en);
|
||||
refresh();
|
||||
}
|
||||
|
||||
onUpdateRSSResult(actionSuccess);
|
||||
onDeleteRSSResult(actionSuccess);
|
||||
|
||||
return {
|
||||
rss,
|
||||
getAll,
|
||||
refresh,
|
||||
updateRSS,
|
||||
deleteRSS,
|
||||
};
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import Axios from 'axios';
|
||||
import type { ApiError } from '#/api';
|
||||
import type { ApiError } from "#/api";
|
||||
|
||||
export const axios = Axios.create();
|
||||
|
||||
@@ -19,13 +19,13 @@ axios.interceptors.response.use(
|
||||
},
|
||||
(err) => {
|
||||
const status = err.response.status as ApiError['status'];
|
||||
const detail = (err.response.data.detail ?? '') as ApiError['detail'];
|
||||
const msg = (err.response.data.msg ?? '') as ApiError['msg'];
|
||||
const msg_en = (err.response.data.msg_en ?? '') as ApiError['msg_en'];
|
||||
const msg_zh = (err.response.data.msg_zh ?? '') as ApiError['msg_zh'];
|
||||
|
||||
const error = {
|
||||
status,
|
||||
detail,
|
||||
msg,
|
||||
msg_en,
|
||||
msg_zh,
|
||||
};
|
||||
|
||||
const message = useMessage();
|
||||
@@ -38,11 +38,11 @@ axios.interceptors.response.use(
|
||||
|
||||
/** 执行失败 */
|
||||
if (error.status === 406) {
|
||||
message.error(error.msg);
|
||||
message.error(error.msg_zh);
|
||||
}
|
||||
|
||||
if (error.status === 500) {
|
||||
const msg = error.detail ? error.detail : 'Request Error!';
|
||||
const msg = (err.response.data.msg_en ?? '') as ApiError['msg_en']
|
||||
message.error(msg);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"baseUrl": "./",
|
||||
"types": ["vite-plugin-pwa/client"],
|
||||
"paths": {
|
||||
"~/*": ["./*"],
|
||||
"@/*": ["src/*"],
|
||||
"#/*": ["types/*"]
|
||||
}
|
||||
|
||||
@@ -14,10 +14,11 @@ export type StatusCode = 401 | 404 | 406 | 500;
|
||||
|
||||
export interface ApiError {
|
||||
status: StatusCode;
|
||||
detail: ApiErrorMessage;
|
||||
msg: string;
|
||||
msg_en: string;
|
||||
msg_zh: string;
|
||||
}
|
||||
|
||||
export interface ApiSuccess {
|
||||
msg: string;
|
||||
}
|
||||
msg_en: string;
|
||||
msg_zh: string;
|
||||
}
|
||||
4
webui/types/dts/auto-imports.d.ts
vendored
4
webui/types/dts/auto-imports.d.ts
vendored
@@ -12,6 +12,8 @@ declare global {
|
||||
const apiDownload: typeof import('../../src/api/download')['apiDownload']
|
||||
const apiLog: typeof import('../../src/api/log')['apiLog']
|
||||
const apiProgram: typeof import('../../src/api/program')['apiProgram']
|
||||
const apiRSS: typeof import('../../src/api/rss')['apiRSS']
|
||||
const apiSearch: typeof import('../../src/api/search')['apiSearch']
|
||||
const assert: typeof import('vitest')['assert']
|
||||
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
||||
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
|
||||
@@ -136,6 +138,7 @@ declare global {
|
||||
const until: typeof import('@vueuse/core')['until']
|
||||
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
|
||||
const useApi: typeof import('../../src/hooks/useApi')['useApi']
|
||||
const useAppInfo: typeof import('../../src/hooks/useAppInfo')['useAppInfo']
|
||||
const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue']
|
||||
const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
|
||||
const useAttrs: typeof import('vue')['useAttrs']
|
||||
@@ -231,6 +234,7 @@ declare global {
|
||||
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
|
||||
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
|
||||
const useProgramStore: typeof import('../../src/store/program')['useProgramStore']
|
||||
const useRSSStore: typeof import('../../src/store/rss')['useRSSStore']
|
||||
const useRafFn: typeof import('@vueuse/core')['useRafFn']
|
||||
const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
|
||||
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
|
||||
|
||||
2
webui/types/dts/components.d.ts
vendored
2
webui/types/dts/components.d.ts
vendored
@@ -21,6 +21,7 @@ declare module '@vue/runtime-core' {
|
||||
AbLabel: typeof import('./../../src/components/ab-label.vue')['default']
|
||||
AbPageTitle: typeof import('./../../src/components/basic/ab-page-title.vue')['default']
|
||||
AbPopup: typeof import('./../../src/components/ab-popup.vue')['default']
|
||||
AbRssItem: typeof import('./../../src/components/ab-rss-item.vue')['default']
|
||||
AbRule: typeof import('./../../src/components/ab-rule.vue')['default']
|
||||
AbSearch: typeof import('./../../src/components/basic/ab-search.vue')['default']
|
||||
AbSelect: typeof import('./../../src/components/basic/ab-select.vue')['default']
|
||||
@@ -29,6 +30,7 @@ declare module '@vue/runtime-core' {
|
||||
AbStatus: typeof import('./../../src/components/basic/ab-status.vue')['default']
|
||||
AbStatusBar: typeof import('./../../src/components/ab-status-bar.vue')['default']
|
||||
AbSwitch: typeof import('./../../src/components/basic/ab-switch.vue')['default']
|
||||
AbTag: typeof import('./../../src/components/basic/ab-tag.vue')['default']
|
||||
AbTopbar: typeof import('./../../src/components/layout/ab-topbar.vue')['default']
|
||||
ConfigDownload: typeof import('./../../src/components/setting/config-download.vue')['default']
|
||||
ConfigManage: typeof import('./../../src/components/setting/config-manage.vue')['default']
|
||||
|
||||
1
webui/types/dts/router-type.d.ts
vendored
1
webui/types/dts/router-type.d.ts
vendored
@@ -46,6 +46,7 @@ declare module 'vue-router/auto/routes' {
|
||||
'Downloader': RouteRecordInfo<'Downloader', '/downloader', Record<never, never>, Record<never, never>>,
|
||||
'Log': RouteRecordInfo<'Log', '/log', Record<never, never>, Record<never, never>>,
|
||||
'Player': RouteRecordInfo<'Player', '/player', Record<never, never>, Record<never, never>>,
|
||||
'RSS': RouteRecordInfo<'RSS', '/rss', Record<never, never>, Record<never, never>>,
|
||||
'Login': RouteRecordInfo<'Login', '/login', Record<never, never>, Record<never, never>>,
|
||||
}
|
||||
}
|
||||
|
||||
17
webui/types/rss.ts
Normal file
17
webui/types/rss.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export interface RSS {
|
||||
id: number;
|
||||
name: string;
|
||||
url: string;
|
||||
aggregate: boolean;
|
||||
parser: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export const rssTemplate: RSS = {
|
||||
id: 0,
|
||||
name: '',
|
||||
url: '',
|
||||
aggregate: false,
|
||||
parser: '',
|
||||
enabled: false,
|
||||
};
|
||||
7
webui/types/torrent.ts
Normal file
7
webui/types/torrent.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface Torrent {
|
||||
id: number;
|
||||
name: string;
|
||||
url: string;
|
||||
homepage: string;
|
||||
downloaded: boolean;
|
||||
}
|
||||
@@ -95,6 +95,7 @@ export default defineConfig({
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'~': resolve(__dirname, './'),
|
||||
'@': resolve(__dirname, 'src'),
|
||||
'#': resolve(__dirname, 'types'),
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user