配置页渲染

This commit is contained in:
Rewrite0
2023-04-25 17:35:37 +08:00
parent 9d64e2894d
commit c12906e576
19 changed files with 640 additions and 74 deletions

View File

@@ -1,5 +1,12 @@
import axios from 'axios';
import type { Config } from '#/config';
export const setConfig = () => axios.post('/api/v1/updateConfig');
export async function setConfig(newConfig: Config) {
const { data } = await axios.post('/api/v1/updateConfig', newConfig);
return data;
}
export const getConfig = () => axios.post('/api/v1/getConfig');
export async function getConfig() {
const { data } = await axios.get<Config>('/api/v1/getConfig');
return data;
}

8
src/components.d.ts vendored
View File

@@ -7,6 +7,9 @@ export {}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
ConfigFormCol: typeof import('./components/ConfigFormCol.vue')['default']
ConfigFormRow: typeof import('./components/ConfigFormRow.vue')['default']
copy: typeof import('./components/ConfigFormCol copy.vue')['default']
ElAside: typeof import('element-plus/es')['ElAside']
ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
@@ -16,13 +19,18 @@ declare module '@vue/runtime-core' {
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElHeader: typeof import('element-plus/es')['ElHeader']
ElInput: typeof import('element-plus/es')['ElInput']
ElMain: typeof import('element-plus/es')['ElMain']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption']
ElOptions: typeof import('element-plus/es')['ElOptions']
ElRow: typeof import('element-plus/es')['ElRow']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
RouterLink: typeof import('vue-router')['RouterLink']

View File

@@ -0,0 +1,13 @@
<script lang="ts" setup>
defineProps<{
label: string;
}>();
</script>
<template>
<el-col :xs="24" :sm="12" :md="8">
<el-form-item :label="label">
<slot></slot>
</el-form-item>
</el-col>
</template>

View File

@@ -0,0 +1,17 @@
<script lang="ts" setup>
defineProps<{
title: string;
}>();
</script>
<template>
<el-collapse-item>
<template #title>
<span font-bold text-base>{{ title }}</span>
</template>
<el-row :gutter="20">
<slot></slot>
</el-row>
</el-collapse-item>
</template>

View File

@@ -1,12 +1,6 @@
<script lang="ts" setup>
import AddBangumi from './components/AddBangumi.vue';
import BangumiData from './components/BangumiData.vue';
const program = {
sleep_time: 7200,
rename_times: 20,
webui_port: 7892,
};
</script>
<template>

View File

@@ -0,0 +1,64 @@
<script lang="ts" setup>
import { form, renameMethod, tfOptions } from '../form-data';
const bgmManage = computed(() => form.bangumi_manage);
</script>
<template>
<ConfigFormRow title="番剧管理">
<ConfigFormCol label="启用">
<el-select v-model="bgmManage.enable" flex-1>
<el-option
v-for="(opt, index) in tfOptions"
:key="index"
:label="opt.label"
:value="opt.value"
></el-option>
</el-select>
</ConfigFormCol>
<ConfigFormCol label="历史番剧下载">
<el-select v-model="bgmManage.eps_complete" flex-1>
<el-option
v-for="(opt, index) in tfOptions"
:key="index"
:label="opt.label"
:value="opt.value"
></el-option>
</el-select>
</ConfigFormCol>
<ConfigFormCol label="重命名方法">
<el-select v-model="bgmManage.rename_method" flex-1>
<el-option
v-for="opt in renameMethod"
:key="opt"
:label="opt"
:value="opt"
></el-option>
</el-select>
</ConfigFormCol>
<ConfigFormCol label="下载规则中添加组名">
<el-select v-model="bgmManage.group_tag" flex-1>
<el-option
v-for="(opt, index) in tfOptions"
:key="index"
:label="opt.label"
:value="opt.value"
></el-option>
</el-select>
</ConfigFormCol>
<ConfigFormCol label="删除坏种">
<el-select v-model="bgmManage.remove_bad_torrent" flex-1>
<el-option
v-for="(opt, index) in tfOptions"
:key="index"
:label="opt.label"
:value="opt.value"
></el-option>
</el-select>
</ConfigFormCol>
</ConfigFormRow>
</template>

View File

@@ -0,0 +1,46 @@
<script lang="ts" setup>
import { downloaderType, form, tfOptions } from '../form-data';
const downloader = computed(() => form.downloader);
</script>
<template>
<ConfigFormRow title="下载">
<ConfigFormCol label="下载器">
<el-select v-model="downloader.type" flex-1>
<el-option
v-for="opt in downloaderType"
:key="opt"
:value="opt"
></el-option>
</el-select>
</ConfigFormCol>
<ConfigFormCol label="host">
<el-input v-model="downloader.host"></el-input>
</ConfigFormCol>
<ConfigFormCol label="path">
<el-input v-model="downloader.path"></el-input>
</ConfigFormCol>
<ConfigFormCol label="username">
<el-input v-model="downloader.username"></el-input>
</ConfigFormCol>
<ConfigFormCol label="password">
<el-input v-model="downloader.password"></el-input>
</ConfigFormCol>
<ConfigFormCol label="ssl">
<el-select v-model="downloader.ssl" flex-1>
<el-option
v-for="(opt, index) in tfOptions"
:key="index"
:label="opt.label"
:value="opt.value"
></el-option>
</el-select>
</ConfigFormCol>
</ConfigFormRow>
</template>

View File

@@ -0,0 +1,20 @@
<script lang="ts" setup>
import { form, tfOptions } from '../form-data';
const log = computed(() => form.log);
</script>
<template>
<ConfigFormRow title="日志">
<ConfigFormCol label="启用debug">
<el-select v-model="log.debug_enable" flex-1>
<el-option
v-for="(opt, index) in tfOptions"
:key="index"
:label="opt.label"
:value="opt.value"
></el-option>
</el-select>
</ConfigFormCol>
</ConfigFormRow>
</template>

View File

@@ -0,0 +1,39 @@
<script lang="ts" setup>
import { form, notificationType, tfOptions } from '../form-data';
const notification = computed(() => form.notification);
</script>
<template>
<ConfigFormRow title="通知">
<ConfigFormCol label="启用">
<el-select v-model="notification.enable" flex-1>
<el-option
v-for="(opt, index) in tfOptions"
:key="index"
:label="opt.label"
:value="opt.value"
></el-option>
</el-select>
</ConfigFormCol>
<ConfigFormCol label="通知类型">
<el-select v-model="notification.type" flex-1>
<el-option
v-for="opt in notificationType"
:key="opt"
:label="opt"
:value="opt"
></el-option>
</el-select>
</ConfigFormCol>
<ConfigFormCol label="token">
<el-input v-model="notification.token"></el-input>
</ConfigFormCol>
<ConfigFormCol label="chat_id">
<el-input v-model="notification.chat_id" type="number"></el-input>
</ConfigFormCol>
</ConfigFormRow>
</template>

View File

@@ -0,0 +1,21 @@
<script lang="ts" setup>
import { form } from '../form-data';
const program = computed(() => form.program);
</script>
<template>
<ConfigFormRow title="主程序">
<ConfigFormCol label="RSS 检查间隔">
<el-input v-model="program.sleep_time" type="number" />
</ConfigFormCol>
<ConfigFormCol label="重命名间隔">
<el-input v-model="program.rename_times" type="number" />
</ConfigFormCol>
<ConfigFormCol label="webui端口">
<el-input v-model="program.webui_port" type="number" />
</ConfigFormCol>
</ConfigFormRow>
</template>

View File

@@ -0,0 +1,47 @@
<script lang="ts" setup>
import { form, proxyType, tfOptions } from '../form-data';
const proxy = computed(() => form.proxy);
</script>
<template>
<ConfigFormRow title="代理">
<ConfigFormCol label="启用">
<el-select v-model="proxy.enable" flex-1>
<el-option
v-for="(opt, index) in tfOptions"
:key="index"
:label="opt.label"
:value="opt.value"
></el-option>
</el-select>
</ConfigFormCol>
<ConfigFormCol label="代理类型">
<el-select v-model="proxy.type" flex-1>
<el-option
v-for="opt in proxyType"
:key="opt"
:label="opt"
:value="opt"
></el-option>
</el-select>
</ConfigFormCol>
<ConfigFormCol label="host">
<el-input v-model="proxy.host"></el-input>
</ConfigFormCol>
<ConfigFormCol label="port">
<el-input v-model="proxy.port" type="number"></el-input>
</ConfigFormCol>
<ConfigFormCol label="username">
<el-input v-model="proxy.username"></el-input>
</ConfigFormCol>
<ConfigFormCol label="password">
<el-input v-model="proxy.password"></el-input>
</ConfigFormCol>
</ConfigFormRow>
</template>

View File

@@ -0,0 +1,69 @@
<script lang="ts" setup>
import { form, rssParserLang, rssParserType, tfOptions } from '../form-data';
const rssParser = computed(() => form.rss_parser);
const filter = ref('');
watch(filter, (nv) => {
const value = nv.split(',').filter((e) => e !== '');
rssParser.value.filter = value;
});
</script>
<template>
<ConfigFormRow title="RSS解析">
<ConfigFormCol label="启用">
<el-select v-model="rssParser.enable" flex-1>
<el-option
v-for="(opt, index) in tfOptions"
:key="index"
:label="opt.label"
:value="opt.value"
></el-option>
</el-select>
</ConfigFormCol>
<ConfigFormCol label="源">
<el-select v-model="rssParser.type" flex-1>
<el-option
v-for="opt in rssParserType"
:key="opt"
:value="opt"
></el-option>
</el-select>
</ConfigFormCol>
<ConfigFormCol label="token">
<el-input v-model="rssParser.token"></el-input>
</ConfigFormCol>
<ConfigFormCol label="语言">
<el-select v-model="rssParser.language" flex-1>
<el-option
v-for="opt in rssParserLang"
:key="opt"
:value="opt"
></el-option>
</el-select>
</ConfigFormCol>
<ConfigFormCol label="反代链接">
<el-input v-model="rssParser.custom_url"></el-input>
</ConfigFormCol>
<ConfigFormCol label="ssl">
<el-select v-model="rssParser.enable_tmdb" flex-1>
<el-option
v-for="(opt, index) in tfOptions"
:key="index"
:label="opt.label"
:value="opt.value"
></el-option>
</el-select>
</ConfigFormCol>
<ConfigFormCol label="筛选">
<el-input v-model="filter" :value="rssParser.filter.join(',')"></el-input>
</ConfigFormCol>
</ConfigFormRow>
</template>

View File

@@ -0,0 +1,70 @@
import type {
Config,
DownloaderType,
NotificationType,
ProxyType,
RenameMethod,
RssParserLang,
RssParserType,
} from '#/config';
export const form = reactive<Config>({
data_version: 4,
program: {
sleep_time: 0,
rename_times: 0,
webui_port: 0,
},
downloader: {
type: 'qbittorrent',
host: '',
username: '',
password: '',
path: '',
ssl: false,
},
rss_parser: {
enable: true,
type: 'mikan',
token: '',
custom_url: '',
enable_tmdb: false,
filter: [],
language: 'zh',
},
bangumi_manage: {
enable: true,
eps_complete: true,
rename_method: 'normal',
group_tag: true,
remove_bad_torrent: true,
},
log: {
debug_enable: false,
},
proxy: {
enable: false,
type: 'http',
host: '',
port: 0,
username: '',
password: '',
},
notification: {
enable: false,
type: 'telegram',
token: '',
chat_id: '',
},
});
export const downloaderType: DownloaderType = ['qbittorrent'];
export const rssParserType: RssParserType = ['mikan'];
export const rssParserLang: RssParserLang = ['zh', 'en', 'jp'];
export const renameMethod: RenameMethod = ['normal', 'pn', 'advance', 'none'];
export const proxyType: ProxyType = ['http', 'https', 'socks5'];
export const notificationType: NotificationType = ['telegram', 'server-chan'];
export const tfOptions = [
{ label: '是', value: true },
{ label: '否', value: false },
];

View File

@@ -1,20 +1,55 @@
<script lang="ts" setup></script>
<script lang="ts" setup>
import ProgramItem from './components/ProgramItem.vue';
import DownloaderItem from './components/DownloaderItem.vue';
import RssParserItem from './components/RssParserItem.vue';
import BangumiManageItem from './components/BangumiManageItem.vue';
import LogItem from './components/LogItem.vue';
import ProxyItem from './components/ProxyItem.vue';
import NotificationItem from './components/NotificationItem.vue';
import { form } from './form-data';
const store = configStore();
watch(form, (v) => {
console.log('🚀 ~ file: index.vue:54 ~ v:', v);
});
onBeforeMount(async () => {
await store.get();
if (store.config) {
Object.keys(store.config).forEach((key) => {
if (store.config) {
form[key] = store.config[key];
}
});
}
});
</script>
<template>
<section class="settings">
<el-row :gutter="20">
<!-- S 添加新番 -->
<el-col :xs="24" :sm="24" :md="12" :lg="8">
<AddBangumi type="new" />
<el-col :xs="24" :sm="24">
<el-form :model="form" label-position="right">
<el-collapse>
<ProgramItem />
<DownloaderItem />
<RssParserItem />
<BangumiManageItem />
<LogItem />
<ProxyItem />
<NotificationItem />
</el-collapse>
</el-form>
</el-col>
<!-- E 添加新番 -->
<!-- S 添加旧番 -->
<el-col :xs="24" :sm="24" :md="12" :lg="8">
<AddBangumi type="old" />
</el-col>
<!-- E 添加旧番 -->
</el-row>
<div flex="~ items-center justify-center" mt20>
<el-button type="primary">保存</el-button>
<el-button>还原</el-button>
</div>
</section>
</template>

View File

@@ -3,6 +3,7 @@ import 'element-plus/es/components/message/style/css';
import 'element-plus/es/components/message-box/style/css';
import { ElMessage, ElMessageBox } from 'element-plus';
import { resetRule } from '@/api/debug';
import { appRestart } from '@/api/program';
const loading = ref(false);
async function reset() {
@@ -11,7 +12,7 @@ async function reset() {
loading.value = false;
if (res.data === 'Success') {
ElMessage({
message: '数据已重置, 建议重启容器',
message: '数据已重置, 建议重启程序或容器',
type: 'success',
});
} else {
@@ -25,7 +26,28 @@ async function reset() {
function restart() {
ElMessageBox.confirm('该操作将重启程序!', {
type: 'warning',
});
})
.then(async () => {
appRestart()
.then(({ data }) => {
console.log('🚀 ~ file: index.vue:33 ~ .then ~ data:', data);
if (data.status === 'success') {
ElMessage({
message: '正在重启, 请稍后刷新页面...',
type: 'success',
});
}
})
.catch((error) => {
console.error('🚀 ~ file: index.vue:41 ~ .then ~ e:', error);
ElMessage({
message: '操作失败, 请重试!',
type: 'error',
});
});
})
.catch(() => {});
}
</script>

View File

@@ -1,55 +1,29 @@
import { defineStore } from 'pinia';
export interface Config {
data_version: 4;
program: {
sleep_time: number;
rename_times: number;
webui_port: number;
};
downloader: {
type: 'qbittorrent';
host: string;
username: string;
password: string;
path: string;
ssl: boolean;
};
rss_parser: {
enable: boolean;
type: string;
token: string;
custom_url: string;
enable_tmdb: boolean;
filter: Array<string>;
language: 'zh' | 'en' | 'jp';
};
bangumi_manage: {
enable: boolean;
eps_complete: boolean;
rename_method: 'normal' | 'pn' | 'advance' | 'none';
group_tag: boolean;
remove_bad_torrent: boolean;
};
log: {
debug_enable: boolean;
};
proxy: {
enable: boolean;
type: 'http' | 'https';
host: string;
port: number;
username: string;
password: string;
};
notification: {
enable: boolean;
type: 'telegram' | 'server-chan';
token: string;
chat_id: string;
};
}
import { getConfig, setConfig } from '@/api/config';
import type { Config } from '#/config';
export const configStore = defineStore('config', () => {
const config = ref(null);
const config = ref<Config>();
const get = async () => {
config.value = await getConfig();
};
get();
const set = (newConfig: Omit<Config, 'data_version'>) => {
let finalConfig: Config;
if (config.value !== undefined) {
finalConfig = Object.assign(config.value, newConfig);
return setConfig(finalConfig);
}
return false;
};
return {
get,
set,
config,
};
});

View File

@@ -12,13 +12,20 @@
"esModuleInterop": true,
"lib": ["ESNext", "DOM"],
"skipLibCheck": true,
"noImplicitAny": false,
"baseUrl": "./",
"paths": {
"@/*": ["src/*"],
"#/*": ["src/types/*"]
"#/*": ["types/*"]
},
"types": ["element-plus/global"]
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue",
"types/config.ts"
],
"references": [{ "path": "./tsconfig.node.json" }]
}

74
types/config.ts Normal file
View File

@@ -0,0 +1,74 @@
import type { UnionToTuple } from '#/utils';
export interface Config {
data_version: 4;
program: {
sleep_time: number;
rename_times: number;
webui_port: number;
};
downloader: {
type: 'qbittorrent';
host: string;
username: string;
password: string;
path: string;
ssl: boolean;
};
rss_parser: {
enable: boolean;
type: 'mikan';
token: string;
custom_url: string;
enable_tmdb: boolean;
filter: Array<string>;
language: 'zh' | 'en' | 'jp';
};
bangumi_manage: {
enable: boolean;
eps_complete: boolean;
rename_method: 'normal' | 'pn' | 'advance' | 'none';
group_tag: boolean;
remove_bad_torrent: boolean;
};
log: {
debug_enable: boolean;
};
proxy: {
enable: boolean;
type: 'http' | 'https' | 'socks5';
host: string;
port: number;
username: string;
password: string;
};
notification: {
enable: boolean;
type: 'telegram' | 'server-chan';
token: string;
chat_id: string;
};
}
type getItem<T extends keyof Config> = Pick<Config, T>[T];
export type Program = getItem<'program'>;
export type Downloader = getItem<'downloader'>;
export type RssParser = getItem<'rss_parser'>;
export type BangumiManage = getItem<'bangumi_manage'>;
export type Log = getItem<'log'>;
export type Proxy = getItem<'proxy'>;
export type Notification = getItem<'notification'>;
/** 下载方式 */
export type DownloaderType = UnionToTuple<Downloader['type']>;
/** rss parser 源 */
export type RssParserType = UnionToTuple<RssParser['type']>;
/** rss parser 语言 */
export type RssParserLang = UnionToTuple<RssParser['language']>;
/** 重命名方式 */
export type RenameMethod = UnionToTuple<BangumiManage['rename_method']>;
/** 代理类型 */
export type ProxyType = UnionToTuple<Proxy['type']>;
/** 通知类型 */
export type NotificationType = UnionToTuple<Notification['type']>;

39
types/utils.ts Normal file
View File

@@ -0,0 +1,39 @@
/**
* 将联合类型转为对应的交叉函数类型
* @template U 联合类型
*/
export type UnionToInterFunction<U> = (
U extends any ? (k: () => U) => void : never
) extends (k: infer I) => void
? I
: never;
/**
* 获取联合类型中的最后一个类型
* @template U 联合类型
*/
export type GetUnionLast<U> = UnionToInterFunction<U> extends { (): infer A }
? A
: never;
/**
* 在元组类型中前置插入一个新的类型(元素);
* @template Tuple 元组类型
* @template E 新的类型
*/
export type Prepend<Tuple extends any[], E> = [E, ...Tuple];
/**
* 联合类型转元组类型;
* @template Union 联合类型
* @template T 初始元组类型
* @template Last 传入联合类型中的最后一个类型(元素),自动生成,内部使用
*/
export type UnionToTuple<
Union,
T extends any[] = [],
Last = GetUnionLast<Union>
> = {
0: T;
1: UnionToTuple<Exclude<Union, Last>, Prepend<T, Last>>;
}[[Union] extends [never] ? 0 : 1];