mirror of
https://github.com/ProudMuBai/GoFilm.git
synced 2026-02-03 22:44:47 +08:00
add BAM
This commit is contained in:
30
client/components.d.ts
vendored
30
client/components.d.ts
vendored
@@ -10,25 +10,55 @@ export {}
|
||||
declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
CustomDialog: typeof import('./src/components/Popup/CustomDialog.vue')['default']
|
||||
ElAside: typeof import('element-plus/es')['ElAside']
|
||||
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElCol: typeof import('element-plus/es')['ElCol']
|
||||
ElCollapseTransition: typeof import('element-plus/es')['ElCollapseTransition']
|
||||
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
|
||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
||||
ElFooter: typeof import('element-plus/es')['ElFooter']
|
||||
ElForm: typeof import('element-plus/es')['ElForm']
|
||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||
ElHeader: typeof import('element-plus/es')['ElHeader']
|
||||
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||
ElImage: typeof import('element-plus/es')['ElImage']
|
||||
ElImageViewer: typeof import('element-plus/es')['ElImageViewer']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
||||
ElLink: typeof import('element-plus/es')['ElLink']
|
||||
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']
|
||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||
ElRadio: typeof import('element-plus/es')['ElRadio']
|
||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||
ElRow: typeof import('element-plus/es')['ElRow']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||
ElTag: typeof import('element-plus/es')['ElTag']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
ElUpload: typeof import('element-plus/es')['ElUpload']
|
||||
FilmList: typeof import('./src/components/FilmList.vue')['default']
|
||||
Footer: typeof import('./src/components/Footer.vue')['default']
|
||||
Header: typeof import('./src/components/Header.vue')['default']
|
||||
ImageViewer: typeof import('./src/components/Global/ImageViewer.vue')['default']
|
||||
Loading: typeof import('./src/components/Loading/Loading.vue')['default']
|
||||
ManageHeader: typeof import('./src/components/Manage/ManageHeader.vue')['default']
|
||||
RelateList: typeof import('./src/components/RelateList.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
Sidebar: typeof import('./src/components/Manage/Sidebar.vue')['default']
|
||||
Util: typeof import('./src/components/Util.vue')['default']
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<meta name="referrer" content="never">
|
||||
<meta charset="UTF-8"/>
|
||||
<title>(╥﹏╥)</title>
|
||||
<link rel="stylesheet" href="//at.alicdn.com/t/c/font_3992367_r0ou59wsgum.css">
|
||||
<link rel="stylesheet" href="//at.alicdn.com/t/c/font_3992367_chvdxwo1gkp.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"@element-plus/icons-vue": "^2.1.0",
|
||||
"@videojs-player/vue": "^1.0.0",
|
||||
"axios": "^1.3.4",
|
||||
"element-plus": "^2.3.2",
|
||||
"element-plus": "^2.4.4",
|
||||
"video.js": "^8.0.4",
|
||||
"vue": "^3.2.47"
|
||||
},
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
<template>
|
||||
<el-config-provider :locale="zhCn">
|
||||
<div class="main" >
|
||||
<router-view></router-view>
|
||||
<Util/>
|
||||
<router-view></router-view>
|
||||
<Util/>
|
||||
</div>
|
||||
</el-config-provider>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
|
||||
import Util from "./components/Util.vue";
|
||||
import zhCn from "element-plus/dist/locale/zh-cn.min.js";
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
BIN
client/src/assets/image/bg.mp4
Normal file
BIN
client/src/assets/image/bg.mp4
Normal file
Binary file not shown.
BIN
client/src/assets/image/managebg.png
Normal file
BIN
client/src/assets/image/managebg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 MiB |
58
client/src/components/Global/ImageViewer.vue
Normal file
58
client/src/components/Global/ImageViewer.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<el-image-viewer
|
||||
v-if="data.show"
|
||||
:urlList="data.list"
|
||||
:z-index="data.zIndex"
|
||||
:initial-index="data.initialIndex"
|
||||
:infinite="data.infinite"
|
||||
:hideOnClickModal="data.hideOnClickModal"
|
||||
@close="data.show = false"
|
||||
></el-image-viewer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import {onMounted, reactive, watch} from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
options: {
|
||||
type:Object,
|
||||
default: {
|
||||
list:Array,
|
||||
currentLink: String,
|
||||
show:Boolean,
|
||||
}
|
||||
},
|
||||
remove: {
|
||||
type:Function,
|
||||
default: null,
|
||||
}
|
||||
})
|
||||
|
||||
const data = reactive({
|
||||
show: false,
|
||||
list: [{link:''}],
|
||||
zIndex: 2000,
|
||||
initialIndex: 0,
|
||||
infinite: true,
|
||||
hideOnClickModal: false,
|
||||
})
|
||||
|
||||
onMounted(()=>{
|
||||
data.list = props.options.list
|
||||
data.list.forEach((item,index)=>{
|
||||
if (item == props.options.currentLink) {
|
||||
data.initialIndex = index
|
||||
}
|
||||
})
|
||||
data.show = props.options?.show
|
||||
})
|
||||
|
||||
watch([data],()=>{
|
||||
!data.show && props.remove()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
20
client/src/components/Global/preview.ts
Normal file
20
client/src/components/Global/preview.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import ImageViewer from "./ImageViewer.vue";
|
||||
import {createApp} from "vue";
|
||||
|
||||
const Preview = (options:any) =>{
|
||||
// 默认创建 ImageViewer 组件时为显示状态
|
||||
options.show = true
|
||||
// 创建节点用户挂载
|
||||
const el = document.createElement("div")
|
||||
document.body.appendChild(el)
|
||||
const app = createApp(ImageViewer, {
|
||||
options,
|
||||
remove(){
|
||||
app.unmount()
|
||||
document.body.removeChild(el)
|
||||
}
|
||||
})
|
||||
return app.mount(el)
|
||||
}
|
||||
|
||||
export {Preview}
|
||||
@@ -3,7 +3,8 @@
|
||||
<!-- 左侧logo以及搜索 -->
|
||||
<div class="nav_left">
|
||||
<!-- <img class="logo" src="/src/assets/logo.png">-->
|
||||
<a href="/" class="site">GoFilm</a>
|
||||
<!--<el-avatar class="logo" :size="45" :src="data.site.logo" alt="GoFilm"/>-->
|
||||
<a href="/" class="site">{{ data.site.siteName }}</a>
|
||||
<div class="search_group">
|
||||
<input v-model="keyword" @keydown="(e)=>{e.keyCode == 13 && searchFilm()}" placeholder="搜索 动漫,剧集,电影 "
|
||||
class="search"/>
|
||||
@@ -13,10 +14,13 @@
|
||||
<!--右侧顶级分类导航 -->
|
||||
<div class="nav_right">
|
||||
<a href="/">首页</a>
|
||||
<a :href="`/filmClassify?Pid=${nav.film.id}`">电影</a>
|
||||
<a :href="`/filmClassify?Pid=${nav.tv.id}`">剧集</a>
|
||||
<a :href="`/filmClassify?Pid=${nav.cartoon.id}`">动漫</a>
|
||||
<a :href="`/filmClassify?Pid=${nav.variety.id}`">综艺</a>
|
||||
<!--<a :href="`/filmClassify?Pid=${nav.film.id}`">电影</a>-->
|
||||
<!--<a :href="`/filmClassify?Pid=${nav.tv.id}`">剧集</a>-->
|
||||
<!--<a :href="`/filmClassify?Pid=${nav.cartoon.id}`">动漫</a>-->
|
||||
<!--<a :href="`/filmClassify?Pid=${nav.variety.id}`">综艺</a>-->
|
||||
<template v-for="n in data.nav">
|
||||
<a :href="`/filmClassify?Pid=${n.id}`">{{ n.name }}</a>
|
||||
</template>
|
||||
|
||||
<div class="history-link hidden-md-and-down" v-on:mouseenter="handleHistory(true)"
|
||||
v-on:mouseleave="handleHistory(false)">
|
||||
@@ -65,6 +69,8 @@ const keyword = ref<string>('')
|
||||
const data = reactive({
|
||||
historyFlag: false,
|
||||
historyList: [{}],
|
||||
nav: Array,
|
||||
site: Object,
|
||||
})
|
||||
// 加载观看历史记录信息
|
||||
const handleHistory = (flag: boolean) => {
|
||||
@@ -106,17 +112,30 @@ const nav = reactive({
|
||||
tv: {},
|
||||
variety: {},
|
||||
})
|
||||
|
||||
// 获取站点信息
|
||||
const getBasicInfo = ()=>{
|
||||
ApiGet(`/manage/config/basic`).then((resp: any) => {
|
||||
if (resp.code === 0) {
|
||||
data.site = resp.data
|
||||
} else {
|
||||
ElMessage.error({message: resp.data.msg})
|
||||
}
|
||||
})
|
||||
}
|
||||
onMounted(() => {
|
||||
ApiGet('/navCategory').then((resp: any) => {
|
||||
if (resp.status === 'ok') {
|
||||
nav.tv = resp.data.tv
|
||||
nav.film = resp.data.film
|
||||
nav.cartoon = resp.data.cartoon
|
||||
nav.variety = resp.data.variety
|
||||
// nav.tv = resp.data.tv
|
||||
// nav.film = resp.data.film
|
||||
// nav.cartoon = resp.data.cartoon
|
||||
// nav.variety = resp.data.variety
|
||||
data.nav = resp.data
|
||||
} else {
|
||||
ElMessage.error({message: "请先输入影片名称关键字再进行搜索", duration: 1000})
|
||||
}
|
||||
})
|
||||
getBasicInfo()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ defineProps({
|
||||
margin-left: 10%;
|
||||
transform: translate3d(-50%, -50%, 0);
|
||||
background: rgba(0,0,0, 0.65);
|
||||
z-index: 2002;
|
||||
z-index: 5000;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
.loader-container {
|
||||
|
||||
239
client/src/components/Manage/ManageHeader.vue
Normal file
239
client/src/components/Manage/ManageHeader.vue
Normal file
@@ -0,0 +1,239 @@
|
||||
<template>
|
||||
<div class="header_container">
|
||||
<div class="left">
|
||||
<a href="javascript:;" @click="collapse.changeCollapse"
|
||||
:class="`iconfont ${ collapse.collapse.value ? 'icon-unfold': 'icon-fold'}`"></a>
|
||||
<h3>后台管理中心</h3>
|
||||
</div>
|
||||
<div class="right">
|
||||
<el-dropdown placement="bottom">
|
||||
<div class="dropdown_user">
|
||||
<el-avatar class="avatar" :size="35" :src="data.userInfo.avatar.toString()" alt="admin"/>
|
||||
<span>{{ data.userInfo.nickName }}</span>
|
||||
<el-icon class="el-icon--right">
|
||||
<arrow-down/>
|
||||
</el-icon>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="a"><em class="iconfont icon-user-info"/>个人信息</el-dropdown-item>
|
||||
<el-dropdown-item command="a" @click="dialogV.changePwd = true"><em class="iconfont icon-change-pwd2"/>修改密码
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="e" divided @click="logout"><em class="iconfont icon-logout"/>退出登录
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
|
||||
<!--密码修改弹窗-->
|
||||
<el-dialog v-model="dialogV.changePwd" width="480px" title="用户密码修改">
|
||||
<el-form :model="form.changePwd" :rules="rules" label-width="80px">
|
||||
<el-form-item label="原始密码" prop="password">
|
||||
<el-input v-model="form.changePwd.password" :type="form.type.password?'text':'password'"/>
|
||||
<i :class="`cus-pwd iconfont ${form.type.password?'icon-eye2':'icon-eye'}`"
|
||||
@click="form.type.password = !form.type.password"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码" prop="newPassword">
|
||||
<el-input v-model="form.changePwd.newPassword" :type="form.type.newPassword?'text':'password'"/>
|
||||
<i :class="`cus-pwd iconfont ${form.type.newPassword?'icon-eye2':'icon-eye'}`"
|
||||
@click="form.type.newPassword = !form.type.newPassword"/>
|
||||
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码" prop="confirmPassword">
|
||||
<el-input v-model="form.changePwd.confirmPassword" :type="form.type.confirmPassword?'text':'password'"/>
|
||||
<i :class="`cus-pwd iconfont ${form.type.confirmPassword?'icon-eye2':'icon-eye'}`"
|
||||
@click="form.type.confirmPassword = !form.type.confirmPassword"/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button color="#9b49e7" @click="changePassword">确认</el-button>
|
||||
<el-button @click="cancelDialog">取消</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ArrowDown} from "@element-plus/icons-vue";
|
||||
import {inject, onMounted, reactive} from "vue";
|
||||
import {ApiGet, ApiPost} from "../../utils/request";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {useRouter} from "vue-router";
|
||||
import {clearAuthToken} from "../../utils/token";
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const data = reactive({
|
||||
userInfo: {id: Number, userName: String, email: String, gender: Number, nickName: String, avatar: String, status: Number}
|
||||
})
|
||||
|
||||
// 侧边菜单栏展开状态
|
||||
const collapse = inject('collapse')
|
||||
|
||||
|
||||
|
||||
// 用户密码修改对话框
|
||||
const dialogV = reactive({
|
||||
changePwd: false,
|
||||
})
|
||||
|
||||
// 用户密码修改表单
|
||||
const form = reactive({
|
||||
changePwd: {password: '', newPassword: '', confirmPassword: ''},
|
||||
type: {password: false, newPassword: false, confirmPassword: false},
|
||||
})
|
||||
|
||||
// 校验密码一致性
|
||||
const regex = `^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[$@$!%*?&])[A-Za-z\\d$@$!%*?&]{8,12}$`
|
||||
const validateNewPwd = (rule: any, value: any, callback: any) => {
|
||||
console.log(value)
|
||||
if (value === '') {
|
||||
callback(new Error('新密码不能为空'))
|
||||
} else if (!value.match(regex)) {
|
||||
// ruleFormRef.value.validateField('checkPass', () => null)
|
||||
callback(new Error('密码必须为8-12位且包含大小写字母数字和特殊字符'))
|
||||
}
|
||||
callback()
|
||||
}
|
||||
const validateConfirmPwd = (rule: any, value: any, callback: any) => {
|
||||
if (value === '') {
|
||||
callback(new Error('确认密码不能为空'))
|
||||
} else if (form.changePwd.newPassword !== '' && form.changePwd.newPassword != form.changePwd.confirmPassword) {
|
||||
callback(new Error('新密码与确认密码不一致'))
|
||||
}
|
||||
callback()
|
||||
}
|
||||
// 表单校验
|
||||
const rules = reactive({
|
||||
password: [{required: true, message: '原始密码信息不能为空', trigger: 'blur'}],
|
||||
newPassword: [{required: true, validator: validateNewPwd, trigger: 'blur'}],
|
||||
confirmPassword: [{required: true, validator: validateConfirmPwd, trigger: 'blur'}],
|
||||
})
|
||||
|
||||
|
||||
// 修改密码
|
||||
const changePassword = ()=>{
|
||||
ApiPost(`/changePassword`, {password: form.changePwd.password, newPassword: form.changePwd.newPassword}).then((resp: any) => {
|
||||
if (resp.status === 'ok') {
|
||||
// 退出登录成功则删除本地的token信息并返回到 登录 /login 界面
|
||||
// clearAuthToken()
|
||||
// router.push(`/login`)
|
||||
ElMessage.success({message: resp.message})
|
||||
} else {
|
||||
ElMessage.error({message: resp.message})
|
||||
}
|
||||
})
|
||||
}
|
||||
// 对话框关闭
|
||||
const cancelDialog = () => {
|
||||
dialogV.changePwd = false
|
||||
form.changePwd = {password: '', newPassword: '', confirmPassword: ''}
|
||||
}
|
||||
|
||||
// 退出登录
|
||||
const logout = () => {
|
||||
// 发送请求使当前的token信息失效
|
||||
ApiGet(`/logout`).then((resp: any) => {
|
||||
if (resp.status === 'ok') {
|
||||
// 退出登录成功则删除本地的token信息并返回到 登录 /login 界面
|
||||
clearAuthToken()
|
||||
router.push(`/login`)
|
||||
} else {
|
||||
ElMessage.error({message: resp.message})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
const getUserInfo = ()=>{
|
||||
ApiGet(`/manage/user/info` ).then((resp: any) => {
|
||||
if (resp.code === 0) {
|
||||
resp.data.avatar = resp.data.avatar == 'empty'?'https://s2.loli.net/2023/12/05/O2SEiUcMx5aWlv4.jpg': resp.data.avatar
|
||||
data.userInfo = resp.data
|
||||
} else {
|
||||
ElMessage.error({message: resp.msg})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(()=>{
|
||||
// 获取用户信息, 初始化组件数据
|
||||
getUserInfo()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
/*密码输入框后缀*/
|
||||
.cus-pwd {
|
||||
color: #b07ada;
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header_container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.left a {
|
||||
font-size: 30px;
|
||||
color: #9b49e7;
|
||||
}
|
||||
|
||||
.left h3 {
|
||||
color: #d9ecff;
|
||||
}
|
||||
|
||||
|
||||
.left a:hover {
|
||||
color: #9b49e7b8;
|
||||
}
|
||||
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:deep(.el-dropdown) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
:deep(.el-dropdown-menu__item) {
|
||||
padding: 8px 20px !important;
|
||||
--el-dropdown-menuItem-hover-color: #8b40ff;
|
||||
}
|
||||
|
||||
.dropdown_user {
|
||||
outline: none;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
.iconfont {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
margin-right: 13px;
|
||||
}
|
||||
</style>
|
||||
129
client/src/components/Manage/Sidebar.vue
Normal file
129
client/src/components/Manage/Sidebar.vue
Normal file
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<div>
|
||||
<!--:collapse-transition="false" 关闭过渡动画-->
|
||||
<el-menu default-active="2" class="side-nav" router :collapse="collapse.collapse.value" >
|
||||
<el-menu-item index="" @click="toIndex" >
|
||||
<el-avatar class="logo" :size="30" :src="data.site.logo.toString()" alt="GoFilm"/>
|
||||
<template #title>
|
||||
<b class="site_name">{{ data.site.siteName }}</b>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
<el-sub-menu index="/manage/index">
|
||||
<template #title>
|
||||
<el-icon><HomeFilled /></el-icon>
|
||||
<span>网站管理</span>
|
||||
</template>
|
||||
<el-menu-item index="/manage/system/webSite">站点管理</el-menu-item>
|
||||
<!--<el-menu-item index="">预留配置</el-menu-item>-->
|
||||
</el-sub-menu>
|
||||
<el-sub-menu index="/manage/collect">
|
||||
<template #title>
|
||||
<el-icon><MagicStick /></el-icon>
|
||||
<span>采集管理</span>
|
||||
</template>
|
||||
<el-menu-item index="/manage/collect/index">影视采集</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<el-sub-menu index="/manage/cron">
|
||||
<template #title>
|
||||
<el-icon><Timer /></el-icon>
|
||||
<span>定时任务</span>
|
||||
</template>
|
||||
<el-menu-item index="/manage/cron/index">任务管理</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<!--<el-menu-item index="/manage/category/index">-->
|
||||
<!-- <el-icon><Menu /></el-icon>-->
|
||||
<!-- <template #title>分类管理</template>-->
|
||||
<!--</el-menu-item>-->
|
||||
<el-sub-menu index="/manage/film">
|
||||
<template #title>
|
||||
<el-icon><Film /></el-icon>
|
||||
<span>影片管理</span>
|
||||
</template>
|
||||
<el-menu-item index="/manage/film/class">影视分类</el-menu-item>
|
||||
<el-menu-item index="/manage/film">影视信息</el-menu-item>
|
||||
<el-menu-item index="/manage/film/add">影片添加</el-menu-item>
|
||||
<el-menu-item index="/manage/film/detail">视频详情</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<el-sub-menu index="/manage/file">
|
||||
<template #title>
|
||||
<el-icon><FolderOpened /></el-icon>
|
||||
<span>文件管理</span>
|
||||
</template>
|
||||
<el-menu-item index="/manage/file/upload">文件上传</el-menu-item>
|
||||
<el-menu-item index="/manage/file/gallery">图库管理</el-menu-item>
|
||||
</el-sub-menu>
|
||||
</el-menu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {Menu , HomeFilled, MagicStick, Timer, Film, FolderOpened} from '@element-plus/icons-vue'
|
||||
import {inject, onMounted, reactive} from "vue";
|
||||
import {ApiGet} from "../../utils/request";
|
||||
import {ElMessage} from "element-plus";
|
||||
|
||||
// 菜单栏展开状态
|
||||
const collapse = inject('collapse')
|
||||
|
||||
const data = reactive({
|
||||
site: {siteName: String, logo:String},
|
||||
})
|
||||
|
||||
// 网站logo点击事件
|
||||
const toIndex = ()=>{
|
||||
window.open('/index')
|
||||
}
|
||||
|
||||
// 初始化网站图标名称等组件数据
|
||||
const getSiteInfo = ()=>{
|
||||
ApiGet(`/manage/config/basic`).then((resp: any) => {
|
||||
if (resp.code == 0) {
|
||||
data.site = resp.data
|
||||
} else {
|
||||
ElMessage.error({message: resp.data.msg})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(()=>{
|
||||
getSiteInfo()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<style scoped>
|
||||
:deep(.el-menu){
|
||||
--el-menu-bg-color: #191a23!important;
|
||||
--el-menu-hover-bg-color: rgb(20, 21, 28);
|
||||
--el-menu-level: 0;
|
||||
--el-menu-text-color: #fff;
|
||||
--el-menu-active-color: skyblue;
|
||||
}
|
||||
.side-nav {
|
||||
padding: 20px 0;
|
||||
height: 100vh;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
|
||||
.side_head{
|
||||
display: flex;
|
||||
font-size: 16px;
|
||||
}
|
||||
.logo{
|
||||
margin-right: 10px;
|
||||
min-width: 30px;
|
||||
}
|
||||
.site_name {
|
||||
color: transparent;
|
||||
font-size: 20px;
|
||||
font-style: italic;
|
||||
-webkit-background-clip: text!important;
|
||||
background-clip: text;
|
||||
background: linear-gradient(118deg, #e91a90, #c965b3, #988cd7, #00acfd);
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
@@ -24,7 +24,7 @@
|
||||
<MoreFilled/>
|
||||
</el-icon>
|
||||
</a>
|
||||
<CustomDialog />
|
||||
<!--<CustomDialog />-->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -12,6 +12,18 @@ import SearchFilm from "../views/index/SearchFilm.vue";
|
||||
import NotFound from '../views/error/Error404.vue'
|
||||
import FilmClassifySearch from "../views/index/FilmClassifySearch.vue";
|
||||
import FilmClassify from "../views/index/FilmClassify.vue";
|
||||
import ManageIndex from "../views/manage/Index.vue"
|
||||
import Login from "../views/Login.vue"
|
||||
import ManageHome from "../views/manage/ManageHome.vue";
|
||||
import {getToken} from "../utils/token";
|
||||
import CollectManage from "../views/manage/collect/CollectManage.vue";
|
||||
import SiteConfig from "../views/manage/system/SiteConfig.vue";
|
||||
import CronManage from "../views/manage/cron/CronManage.vue";
|
||||
import Temp from "../views/manage/file/Temp.vue";
|
||||
import FilmClass from "../views/manage/film/FilmClass.vue";
|
||||
import Film from "../views/manage/film/Film.vue";
|
||||
import FileUpload from "../views/manage/file/FileUpload.vue";
|
||||
import FilmAdd from "../views/manage/film/FilmAdd.vue";
|
||||
|
||||
|
||||
// 2. 定义一个路由
|
||||
@@ -29,6 +41,25 @@ const routes = [
|
||||
{path: 'filmClassifySearch', component: FilmClassifySearch},
|
||||
]
|
||||
},
|
||||
{path: '/login', component: Login},
|
||||
{
|
||||
path: '/manage',
|
||||
component: ManageHome,
|
||||
redirect: '/manage/index',
|
||||
children: [
|
||||
{path: 'index', component: ManageIndex},
|
||||
{path: 'collect/index', component: CollectManage},
|
||||
{path: 'system/webSite', component: SiteConfig},
|
||||
{path: 'cron/index', component: CronManage},
|
||||
{path: 'file/upload', component: FileUpload},
|
||||
{path: 'file/gallery', component: Temp},
|
||||
{path: 'film', component: Film},
|
||||
{path: 'film/class', component: FilmClass},
|
||||
{path: 'film/add', component: FilmAdd},
|
||||
{path: 'film/detail', component: Temp},
|
||||
|
||||
]
|
||||
},
|
||||
{path: `/:pathMatch(.*)*`, component: NotFound},
|
||||
]
|
||||
|
||||
@@ -38,6 +69,20 @@ const router = createRouter({
|
||||
routes
|
||||
})
|
||||
|
||||
// 添加全局前置守卫拦截未登录的跳转
|
||||
router.beforeEach((to, from, next) =>{
|
||||
// 如果访问的是 /manage 下的路由, 且 token信息为空 则跳转到登录界面
|
||||
let matchPath = new RegExp(/^\/manage\//).test(to.path)
|
||||
let token = getToken()
|
||||
if ( matchPath && !token ) {
|
||||
next('/login')
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
export {router}
|
||||
|
||||
|
||||
@@ -13,9 +13,24 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
|
||||
/*公共颜色*/
|
||||
--bg-light: #ffffff;
|
||||
|
||||
--content-text-color: #888888;
|
||||
|
||||
--paging-parmary-color: #9b49e7d6;
|
||||
|
||||
--border-gray-color: #0000001f;
|
||||
|
||||
--btn-primary-color: #9b49e7;
|
||||
--btn-pink-color: #d942bf;
|
||||
--btn-bg-linght: #fff;
|
||||
|
||||
--bg-fill-light: #8d00fb1a;
|
||||
}
|
||||
|
||||
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
@@ -134,6 +149,15 @@ button:focus-visible {
|
||||
margin-right: 0!important;
|
||||
}
|
||||
|
||||
.el-dialog__headerbtn{
|
||||
outline: none!important;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/*分页插件颜色*/
|
||||
.el-popper {
|
||||
--el-color-primary: var(--paging-parmary-color);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
28
client/src/utils/format.ts
Normal file
28
client/src/utils/format.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
|
||||
const fmt = {
|
||||
dateFormat( dateStamp:number, format:string='YYYY-mm-dd HH:MM:SS') {
|
||||
// 根据时间戳生成当前时间, 单位 毫秒
|
||||
let date = new Date(dateStamp*1000)
|
||||
const opt = {
|
||||
"Y+": date.getFullYear().toString(), // 年
|
||||
"m+": (date.getMonth() + 1).toString(), // 月
|
||||
"d+": date.getDate().toString(), // 日
|
||||
"H+": date.getHours().toString(), // 时
|
||||
"M+": date.getMinutes().toString(), // 分
|
||||
"S+": date.getSeconds().toString() // 秒
|
||||
// 有其他格式化字符需求可以继续添加,必须转化成字符串
|
||||
};
|
||||
for (let k in opt) {
|
||||
// 正则匹配对应的 opt key
|
||||
let r = new RegExp("(" + k + ")").exec(format);
|
||||
// 如果有匹配成功项
|
||||
if (r) {
|
||||
format = format.replace(r[1], (r[1].length == 1) ? (opt[k as keyof typeof opt]) : (opt[k as keyof typeof opt].padStart(r[1].length, "0")))
|
||||
}
|
||||
}
|
||||
return format;
|
||||
}
|
||||
}
|
||||
|
||||
export {fmt}
|
||||
@@ -3,9 +3,12 @@ import {ElMessage} from "element-plus";
|
||||
|
||||
// 自定义loading加载动画
|
||||
import {load} from "../components/Loading";
|
||||
import {router} from "../router/router";
|
||||
import {getToken, setToken} from "./token";
|
||||
|
||||
let loadingCount: number = 0;
|
||||
|
||||
|
||||
const http = (options: any) => {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -21,13 +24,13 @@ const http = (options: any) => {
|
||||
// 开启loading动画
|
||||
loadingCount++
|
||||
load.start('')
|
||||
// let token: string = ""; //此处换成自己获取回来的token,通常存在在cookie或者store里面
|
||||
// if (token) {
|
||||
// // 让每个请求携带token-- ['X-Token']为自定义key 请根据实际情况自行修改
|
||||
// config.headers["X-Token"] = token;
|
||||
//
|
||||
// config.headers.Authorization = +token;
|
||||
// }
|
||||
let token = getToken();
|
||||
//此处换成自己获取回来的token,通常存在在cookie或者store里面
|
||||
if (token &&token.value.length > 0) {
|
||||
// 让每个请求携带token-- ['X-Token']为自定义key 请根据实际情况自行修改
|
||||
config.headers[token.key] = token.value;
|
||||
// config.headers.Authorization = +token;
|
||||
}
|
||||
return config;
|
||||
}, (error) => {
|
||||
// Do something with request error
|
||||
@@ -40,13 +43,25 @@ const http = (options: any) => {
|
||||
// 关闭loading动画
|
||||
loadingCount--
|
||||
loadingCount == 0 && load.close()
|
||||
// 如果
|
||||
let token = response.headers['new-token']
|
||||
if (token && token.length > 0) {
|
||||
setToken(token)
|
||||
}
|
||||
return response.data;
|
||||
}, (error) => {
|
||||
if (error.response.status == 403) {
|
||||
ElMessage.error("请求异常: ", error)
|
||||
loadingCount--
|
||||
loadingCount == 0 && load.close()
|
||||
if (error.response.status == 401) {
|
||||
router.replace('/login')
|
||||
ElMessage.error(error.response.data.message)
|
||||
// ElMessage.error(`未获取授权信息, 请先登录!!!`)
|
||||
} else if (error.response.status == 403) {
|
||||
ElMessage.error(`无访问权限!!!`)
|
||||
} else {
|
||||
ElMessage.error("服务器繁忙,请稍后再试");
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
18
client/src/utils/token.ts
Normal file
18
client/src/utils/token.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
|
||||
// 将 token 存储到 localStorage 中
|
||||
const setToken = (token:string)=>{
|
||||
const auth = {key: "auth-token", value: token}
|
||||
localStorage.setItem("auth", JSON.stringify(auth))
|
||||
}
|
||||
|
||||
// 获取 localStorage 中的 token 信息
|
||||
const getToken = ()=>{
|
||||
return JSON.parse(localStorage.getItem("auth") as string)
|
||||
}
|
||||
|
||||
// 删除 localStorage 中的 token 信息
|
||||
const clearAuthToken = ()=>{
|
||||
localStorage.removeItem("auth")
|
||||
}
|
||||
export {setToken, getToken,clearAuthToken}
|
||||
126
client/src/views/Login.vue
Normal file
126
client/src/views/Login.vue
Normal file
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<video class="container-bg" src="../assets/image/bg.mp4"
|
||||
autoplay="autoplay" loop="loop" muted="muted"></video>
|
||||
<div class="container-form" >
|
||||
<form class="cus-form">
|
||||
<h2 class="form-title">GoFilm Manage</h2>
|
||||
<div class="cus-form-item">
|
||||
<input type="text" v-model="data.userName" placeholder="用户名 / 邮箱" class="cus-input">
|
||||
<i class="cus-item-icon iconfont icon-account"/>
|
||||
</div>
|
||||
<div class="cus-form-item">
|
||||
<i class="cus-item-icon iconfont icon-password"/>
|
||||
<input :type="`${data.pwdIsShow?'text':'password'}`" v-model="data.password" placeholder="密码" class="cus-input" @keydown.enter="login" >
|
||||
<i :class="`cus-pwd iconfont ${data.pwdIsShow?'icon-eye2':'icon-eye'}`" @click="data.pwdIsShow = !data.pwdIsShow" />
|
||||
</div>
|
||||
<el-button native-type="button" type="primary" size="large" color="#9b49e7" round @click="login" >登录</el-button>
|
||||
<el-button native-type="button" disabled type="primary" size="large" color="#9b49e7" round>注册</el-button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import {reactive} from "vue";
|
||||
import {ApiPost} from "../utils/request";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {useRouter} from "vue-router";
|
||||
|
||||
const data = reactive({
|
||||
userName: "",
|
||||
password: "",
|
||||
pwdIsShow: false,
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
const login = ()=>{
|
||||
ApiPost('/login', {userName: data.userName, password: data.password}).then((resp:any)=>{
|
||||
if (resp.status == 'ok') {
|
||||
router.push('/manage/index')
|
||||
} else {
|
||||
ElMessage.error({message: resp.message})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
color: #343333;
|
||||
}
|
||||
.container-bg {
|
||||
background-color: #b07ada;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.container-form {
|
||||
background: rgba(255,255,255,0.45);
|
||||
width: 480px;
|
||||
height: 460px;
|
||||
border-radius: 8px;
|
||||
position: relative;
|
||||
top: 20%;
|
||||
left: 30%;
|
||||
}
|
||||
.form-title{
|
||||
color: #6e00bf;
|
||||
}
|
||||
|
||||
.cus-input {
|
||||
font-size: 16px;
|
||||
width: 100%;
|
||||
padding: 0 40px;
|
||||
border: none;
|
||||
outline-style: none ;
|
||||
border-radius: 26px;
|
||||
min-height: 40px;
|
||||
background: rgba(255,255,255,0.55);
|
||||
}
|
||||
.cus-input:focus{
|
||||
outline: 2px solid rgb( 169,52,217,72%);
|
||||
border: 0;
|
||||
}
|
||||
.cus-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px 8px;
|
||||
gap: 32px;
|
||||
}
|
||||
.el-button {
|
||||
width: 70%;
|
||||
margin: 0 auto!important;
|
||||
}
|
||||
|
||||
/*自定义密码框组件*/
|
||||
.cus-form-item {
|
||||
margin: 0 auto;
|
||||
width: 80%;
|
||||
position: relative;
|
||||
}
|
||||
.cus-item-icon{
|
||||
position: absolute;
|
||||
left: 18px;
|
||||
top: 6px;
|
||||
color: #b07ada;
|
||||
}
|
||||
.cus-pwd {
|
||||
color: #b07ada;
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@@ -10,8 +10,10 @@
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<ul class="nav_ul">
|
||||
<li v-for="c in item.nav.children" class="nav_category hidden-md-and-down"><a
|
||||
:href="`/filmClassifySearch?Pid=${c.pid}&Category=${c.id}`">{{ c.name }}</a></li>
|
||||
<template v-for="c in item.nav.children">
|
||||
<li class="nav_category hidden-md-and-down" v-if="c.show" ><a
|
||||
:href="`/filmClassifySearch?Pid=${c.pid}&Category=${c.id}`">{{ c.name }}</a></li>
|
||||
</template>
|
||||
<li class="nav_category hidden-md-and-down"><a :href="`/filmClassify?Pid=${item.nav.id}`">更多 ></a></li>
|
||||
</ul>
|
||||
</el-col>
|
||||
@@ -19,7 +21,7 @@
|
||||
<el-row class="cus_content">
|
||||
<el-col :md="24" :lg="20" :xl="20" class="cus_content">
|
||||
<!--影片列表-->
|
||||
<FilmList :col="6" :list="item.movies.slice(0,12)"/>
|
||||
<FilmList v-if="item.movies" :col="6" :list="item.movies.slice(0,12)"/>
|
||||
</el-col>
|
||||
<el-col :md="0" :lg="4" :xl="4" class="hidden-md-and-down content_right">
|
||||
<h3 class="hot_title">🔥热播{{item.nav.name}}</h3>
|
||||
|
||||
27
client/src/views/manage/Index.vue
Normal file
27
client/src/views/manage/Index.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<h3>管理后台首页, 不知道放点啥, 先空着</h3>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import {onMounted} from "vue";
|
||||
import {ApiGet} from "../../utils/request";
|
||||
|
||||
onMounted(()=>{
|
||||
ApiGet('/manage/index')
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
//background: url("/src/assets/image/managebg.png");
|
||||
//width: 100vw;
|
||||
//height: 100vh;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
126
client/src/views/manage/ManageHome.vue
Normal file
126
client/src/views/manage/ManageHome.vue
Normal file
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<el-container>
|
||||
<el-aside :width="collapse?'auto':'190px'" class="side">
|
||||
<Sidebar/>
|
||||
</el-aside>
|
||||
<el-container>
|
||||
<el-header class="header">
|
||||
<ManageHeader/>
|
||||
|
||||
</el-header>
|
||||
<el-main class="view">
|
||||
<router-view/>
|
||||
</el-main>
|
||||
<!--<el-footer>Footer</el-footer>-->
|
||||
</el-container>
|
||||
</el-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
|
||||
import ManageHeader from "../../components/Manage/ManageHeader.vue";
|
||||
import {provide, reactive, ref} from "vue";
|
||||
import Sidebar from "../../components/Manage/Sidebar.vue";
|
||||
|
||||
// 侧边栏菜单状态
|
||||
const collapse = ref(false)
|
||||
|
||||
// 改变侧边栏菜单的 展开|收起 状态
|
||||
const changeCollapse = () => {
|
||||
console.log(collapse.value)
|
||||
collapse.value = !collapse.value
|
||||
}
|
||||
// 传递给子组件使用
|
||||
provide('collapse', {
|
||||
collapse,
|
||||
changeCollapse,
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
|
||||
|
||||
.header {
|
||||
background-image: linear-gradient(135deg, #81bfff 10%, #e267f0 100%);
|
||||
}
|
||||
|
||||
.side {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.view {
|
||||
background: rgb(245 245 245);
|
||||
min-height: calc(100vh - 200px);
|
||||
}
|
||||
.el-container{
|
||||
max-height: 100vh;
|
||||
}
|
||||
:deep(.el-container){
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*管理后台公共样式*/
|
||||
:deep(.el-switch) {
|
||||
--el-switch-on-color: #9b49e7b8;
|
||||
--el-switch-off-color: #cf2dae80;
|
||||
}
|
||||
|
||||
:deep(.el-table) {
|
||||
--el-table-header-bg-color: #9b49e726;
|
||||
--el-table-border-color: #9b49e733;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
//color: skyblue;
|
||||
}
|
||||
|
||||
:deep(.el-radio-group) {
|
||||
--el-color-primary: #9b49e7;
|
||||
--el-text-color-placeholder: #73859f;
|
||||
}
|
||||
|
||||
.cus_util {
|
||||
display: flex;
|
||||
padding: 10px 8px;
|
||||
border-left: 2px solid #9b49e733;
|
||||
border-right: 2px solid #9b49e733;
|
||||
border-bottom: 2px solid #9b49e733;
|
||||
background: #ffffff;
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
:deep(.el-input-number) {
|
||||
--el-fill-color-light: #e163ff8f;
|
||||
border-radius: var(--el-border-radius-base);
|
||||
padding: 0 0 !important;
|
||||
}
|
||||
|
||||
:deep(.el-input-number__decrease) {
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
:deep(.el-input-number__increase) {
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
:deep(.el-tag--info) {
|
||||
--el-fill-color: #67d9e863;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__error) {
|
||||
--el-color-danger: #b3249a;
|
||||
}
|
||||
|
||||
</style>
|
||||
407
client/src/views/manage/collect/CollectManage.vue
Normal file
407
client/src/views/manage/collect/CollectManage.vue
Normal file
@@ -0,0 +1,407 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
|
||||
<el-table
|
||||
:data="data.siteList" style="width: 100%" border size="default"
|
||||
:row-class-name="'cus-tr'" table-layout="auto">
|
||||
<el-table-column prop="name" label="资源名称"/>
|
||||
<el-table-column prop="resultModel" align="center" label="数据类型">
|
||||
<template #default="scope">
|
||||
<el-tag disable-transitions>{{ scope.row.resultModel == 0 ? 'JSON' : 'XML' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="collectType" align="center" label="资源类型">
|
||||
<template #default="scope">
|
||||
<el-tag disable-transitions>{{ scope.row.collectTypeText }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="uri" label="资源站">
|
||||
<template #default="scope">
|
||||
<el-link :href="scope.row.uri" target="_blank">{{ scope.row.uri }}</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="syncPictures" align="center" label="同步图片">
|
||||
<template #default="scope">
|
||||
<el-switch @change="changeSourceState(scope.row)" :disabled="scope.row.grade == 1" v-model="scope.row.syncPictures" inline-prompt active-text="开启" inactive-text="关闭"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="state" align="center" label="是否启用">
|
||||
<template #default="scope">
|
||||
<el-switch @change="changeSourceState(scope.row)" v-model="scope.row.state" inline-prompt active-text="启用" inactive-text="禁用"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="grade" align="center" label="站点权重">
|
||||
<template #default="scope">
|
||||
<el-tag disable-transitions :type="`${scope.row.grade == 0 ? 'success': 'info'}`">
|
||||
{{ scope.row.grade == 0 ? '采集主站' : '附属站点' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="采集方式">
|
||||
<template #default="scope">
|
||||
<el-select v-model="scope.row.cd" class="m-2" placeholder="Select" size="small">
|
||||
<el-option
|
||||
v-for="item in data.collectDuration"
|
||||
:key="item.time"
|
||||
:label="item.label"
|
||||
:value="item.time"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button type="success" :icon="SwitchButton" plain circle @click="startTask(scope.row)" />
|
||||
<el-button type="primary" :icon="Edit" plain circle @click="openEditDialog(scope.row.id)" />
|
||||
<el-button type="danger" :icon="Delete" plain circle @click="delSourceSite(scope.row.id)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="cus_util">
|
||||
<el-button color="#9b49e7" :icon="CirclePlus" @click="dialogV.addV = true">添加采集站</el-button>
|
||||
<el-button color="#d942bf" @click="openBatchCollect" :icon="Promotion">一键采集</el-button>
|
||||
<el-button type="danger" :icon="BellFilled">ReZero</el-button>
|
||||
</div>
|
||||
<!--站点添加弹窗-->
|
||||
<el-dialog v-model="dialogV.addV" title="添加采集站点">
|
||||
<el-form :model="form.add">
|
||||
<el-form-item label="资源名称">
|
||||
<el-input v-model="form.add.name" placeholder="自定义资源名称(禁用汉字)"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="接口地址">
|
||||
<el-input v-model="form.add.uri" placeholder="资源采集链接,本站只采集综合资源或m3u8资源"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="接口类型">
|
||||
<el-radio-group v-model="form.add.resultModel">
|
||||
<el-radio :label="0">JSON</el-radio>
|
||||
<el-radio disabled :label="1">XML</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="资源类型">
|
||||
<el-radio-group fill="#9b49e7" v-model="form.add.collectType">
|
||||
<el-radio fill="#9b49e7" :label="0">视频</el-radio>
|
||||
<el-radio disabled :label="1">文章</el-radio>
|
||||
<el-radio disabled :label="2">演员</el-radio>
|
||||
<el-radio disabled :label="3">角色</el-radio>
|
||||
<el-radio disabled :label="4">网站</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="站点权重">
|
||||
<el-radio-group @change="restrict(0)" fill="#9b49e7" v-model="form.add.grade">
|
||||
<el-radio :label="0">主站点</el-radio>
|
||||
<el-radio :label="1">附属站点</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="图片同步">
|
||||
<el-switch v-model="form.add.syncPictures" @change="restrict(0)" inline-prompt active-text="开启" inactive-text="关闭"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否启用">
|
||||
<el-switch v-model="form.add.state" inline-prompt active-text="启用" inactive-text="禁用"/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button color="#cf48be" @click="apiTest(form.add)" >测试</el-button>
|
||||
<el-button color="#9b49e7" @click="addSite" >添加</el-button>
|
||||
<el-button @click="dialogV.addV = false">取消</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<!--站点修改弹窗-->
|
||||
<el-dialog v-model="dialogV.editV" title="修改资源站信息">
|
||||
<el-form :model="form.edit">
|
||||
<el-form-item label="资源名称">
|
||||
<el-input v-model="form.edit.name" placeholder="自定义资源名称(禁用汉字)"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="接口地址">
|
||||
<el-input v-model="form.edit.uri" placeholder="资源采集链接,本站只采集综合资源或m3u8资源"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="接口类型">
|
||||
<el-radio-group v-model="form.edit.resultModel">
|
||||
<el-radio :label="0">JSON</el-radio>
|
||||
<el-radio disabled :label="1">XML</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="资源类型">
|
||||
<el-radio-group fill="#9b49e7" v-model="form.edit.collectType">
|
||||
<el-radio fill="#9b49e7" :label="0">视频</el-radio>
|
||||
<el-radio disabled :label="1">文章</el-radio>
|
||||
<el-radio disabled :label="2">演员</el-radio>
|
||||
<el-radio disabled :label="3">角色</el-radio>
|
||||
<el-radio disabled :label="4">网站</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="站点权重">
|
||||
<el-radio-group fill="#9b49e7" @change="restrict(1)" v-model="form.edit.grade">
|
||||
<el-radio :label="0">主站点</el-radio>
|
||||
<el-radio :label="1">附属站点</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="图片同步">
|
||||
<el-switch v-model="form.edit.syncPictures" @change="restrict(1)" inline-prompt active-text="开启" inactive-text="关闭"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否启用">
|
||||
<el-switch v-model="form.edit.state" inline-prompt active-text="启用" inactive-text="禁用"/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button color="#cf48be" @click="apiTest(form.edit)" >测试</el-button>
|
||||
<el-button color="#9b49e7" @click="updateSite(form.edit)" >更新</el-button>
|
||||
<el-button @click="dialogV.editV = false">取消</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<!--一键执行功能弹窗-->
|
||||
<el-dialog v-model="dialogV.batchV" width="450px" title="多资源站一键采集">
|
||||
<el-form :model="form.batch">
|
||||
<el-form-item label="执行站点">
|
||||
<el-select v-model="form.batch.ids" multiple collapse-tags collapse-tags-tooltip placeholder="Select" style="width: 240px">
|
||||
<el-option v-for="item in form.options" :key="item.id" :label="item.name" :value="item.id"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="采集时长">
|
||||
<el-tooltip class="box-item" effect="dark" content="采集最近x小时更新的影片,负数则默认采集所有资源" placement="top">
|
||||
<el-input-number v-model="form.batch.time" :step="1" step-strictly />
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button color="#9b49e7" @click="startBatchCollect" >确认执行</el-button>
|
||||
<el-button @click="cancelDialog">取消</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
|
||||
import {onMounted, reactive} from "vue";
|
||||
import {ApiGet, ApiPost} from "../../../utils/request";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {Delete, Edit, SwitchButton, CirclePlus, Promotion, BellFilled} from "@element-plus/icons-vue";
|
||||
|
||||
const data = reactive({
|
||||
siteList: [],
|
||||
collectDuration: [
|
||||
{time: 24, label: '采集今日'},
|
||||
{time: 24 * 7, label: '采集本周'},
|
||||
{time: -1, label: '采集全部'},
|
||||
]
|
||||
})
|
||||
|
||||
const dialogV = reactive({
|
||||
addV: false,
|
||||
editV: false,
|
||||
batchV:false,
|
||||
})
|
||||
|
||||
interface FilmSource {
|
||||
id: string
|
||||
name: string
|
||||
uri: string
|
||||
resultModel: number
|
||||
grade: number
|
||||
collectType: number
|
||||
syncPictures: boolean
|
||||
state: boolean
|
||||
}
|
||||
|
||||
const form = reactive({
|
||||
add: {name: '', uri: '', resultModel: 0, grade: 1, collectType: 0, syncPictures: false, state: false,},
|
||||
edit: {id:'', name: '', uri: '', resultModel: 0, grade: 1, collectType: 0, syncPictures: false, state: false,},
|
||||
batch: {ids:[],time: 0},
|
||||
options:[]
|
||||
|
||||
})
|
||||
|
||||
const openBatchCollect = ()=>{
|
||||
dialogV.batchV = true
|
||||
ApiGet(`/manage/collect/options`, ).then((resp: any) => {
|
||||
if (resp.status === "ok") {
|
||||
form.options = resp.data
|
||||
} else {
|
||||
ElMessage.error({message: resp.message})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const startBatchCollect = ()=>{
|
||||
ApiPost(`/manage/spider/start`, {ids: form.batch.ids, time: form.batch.time, batch: true}).then((resp:any)=>{
|
||||
if (resp.status ==='ok') {
|
||||
ElMessage.success({message: resp.message})
|
||||
cancelDialog()
|
||||
getCollectList()
|
||||
} else {
|
||||
ElMessage.error({message: resp.message})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 开启采集
|
||||
const startTask = (row:any)=>{
|
||||
ApiPost(`/manage/spider/start`, {id:row.id, time: row.cd, batch: false}).then((resp:any)=>{
|
||||
if (resp.status ==='ok') {
|
||||
ElMessage.success({message: resp.message})
|
||||
getCollectList()
|
||||
} else {
|
||||
ElMessage.error({message: resp.message})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 弹窗图片同步开关限制
|
||||
const restrict = (t:number)=>{
|
||||
// t 弹窗类型 0 - add | 1 - edit
|
||||
switch (t){
|
||||
case 0:
|
||||
// 只有 主站点才能开启图片同步, 否则自动为false
|
||||
form.add.syncPictures = (form.add.syncPictures)&&(form.add.grade == 0)
|
||||
break
|
||||
case 1:
|
||||
// 只有 主站点才能开启图片同步, 否则自动为false
|
||||
form.edit.syncPictures = (form.edit.syncPictures)&&(form.edit.grade == 0)
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// 添加采集资源站
|
||||
const addSite = ()=>{
|
||||
ApiPost(`/manage/collect/add`, form.add).then((resp:any)=>{
|
||||
if (resp.status ==='ok') {
|
||||
ElMessage.success({message: resp.message})
|
||||
cancelDialog()
|
||||
getCollectList()
|
||||
} else {
|
||||
ElMessage.error({message: resp.message})
|
||||
}
|
||||
})
|
||||
}
|
||||
// 测试添加的采集接口是否可用
|
||||
const apiTest = (params:any)=>{
|
||||
ApiPost(`/manage/collect/test`, params).then((resp:any)=>{
|
||||
if (resp.status ==='ok') {
|
||||
ElMessage.success({message: resp.message})
|
||||
} else {
|
||||
ElMessage.error({message: resp.message})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 编辑按钮事件, 打开修改对话框
|
||||
const openEditDialog = (id:string)=>{
|
||||
// 从后台获取采集站信息
|
||||
ApiGet(`/manage/collect/find`, {id:id}).then((resp: any) => {
|
||||
if (resp.status === "ok") {
|
||||
form.edit = resp.data
|
||||
} else {
|
||||
ElMessage.error({message: resp.message})
|
||||
}
|
||||
})
|
||||
dialogV.editV = true
|
||||
}
|
||||
|
||||
|
||||
// switch 开关
|
||||
const changeSourceState = (s:any)=>{
|
||||
ApiPost(`/manage/collect/change`, {id:s.id, state: s.state, syncPictures: s.syncPictures}).then((resp: any) => {
|
||||
if (resp.status === "ok") {
|
||||
ElMessage.success({message: resp.message})
|
||||
getCollectList()
|
||||
} else {
|
||||
ElMessage.error({message: resp.message})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//更新资源站点信息
|
||||
const updateSite = (params:FilmSource)=>{
|
||||
ApiPost(`/manage/collect/update`, params).then((resp: any) => {
|
||||
if (resp.status === "ok") {
|
||||
ElMessage.success({message: resp.message})
|
||||
dialogV.editV = false
|
||||
getCollectList()
|
||||
} else {
|
||||
ElMessage.error({message: resp.message})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 删除采集资源站
|
||||
const delSourceSite = (id:string) =>{
|
||||
ApiGet(`/manage/collect/del`, {id:id}).then((resp:any)=>{
|
||||
if (resp.status ==='ok') {
|
||||
ElMessage.success({message: resp.message})
|
||||
getCollectList()
|
||||
} else {
|
||||
ElMessage.error({message: resp.message})
|
||||
}
|
||||
})
|
||||
}
|
||||
const cancelDialog = ()=>{
|
||||
// 关闭对话框
|
||||
dialogV.addV = false
|
||||
dialogV.editV = false
|
||||
dialogV.batchV = false
|
||||
// 还原表单状态
|
||||
form.add = {name: '', uri: '', resultModel: 0, grade: 1, collectType: 0, syncPictures: false, state: false,}
|
||||
}
|
||||
|
||||
const getCollectList = ()=>{
|
||||
ApiGet(`/manage/collect/list`).then((resp: any) => {
|
||||
if (resp.status === "ok") {
|
||||
data.siteList = resp.data.map((item: any) => {
|
||||
switch (item.collectType) {
|
||||
case 0:
|
||||
item.collectTypeText = "视频"
|
||||
break
|
||||
case 1:
|
||||
item.collectTypeText = "文章"
|
||||
break
|
||||
case 2:
|
||||
item.collectTypeText = "演员"
|
||||
break
|
||||
case 3:
|
||||
item.collectTypeText = "角色"
|
||||
break
|
||||
case 4:
|
||||
item.collectTypeText = "网站"
|
||||
break
|
||||
}
|
||||
item.cd = 24
|
||||
return item
|
||||
})
|
||||
} else {
|
||||
ElMessage.error({message: resp.message})
|
||||
}
|
||||
})
|
||||
}
|
||||
onMounted(() => {
|
||||
getCollectList()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
}
|
||||
|
||||
.cus_util {
|
||||
display: flex;
|
||||
padding: 10px 8px;
|
||||
border-left: 2px solid #9b49e733;
|
||||
border-right: 2px solid #9b49e733;
|
||||
border-bottom: 2px solid #9b49e733;
|
||||
background: #ffffff;
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
</style>
|
||||
274
client/src/views/manage/cron/CronManage.vue
Normal file
274
client/src/views/manage/cron/CronManage.vue
Normal file
@@ -0,0 +1,274 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<el-table
|
||||
:data="data.taskList" style="width: 100%" border size="default"
|
||||
:row-class-name="'cus-tr'" table-layout="auto">
|
||||
<el-table-column prop="id" label="任务ID">
|
||||
<template #default="scope">
|
||||
<el-tag disable-transitions>{{ scope.row.id }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="任务描述" />
|
||||
<el-table-column prop="model" align="center" label="任务类型">
|
||||
<template #default="scope">
|
||||
<el-tag disable-transitions>{{ scope.row.model == 0 ? '自动更新':'自定义任务'}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="state" align="center" label="是否启用">
|
||||
<template #default="scope">
|
||||
<el-switch v-model="scope.row.state" @change="changeTaskState(scope.row.id, scope.row.state)" inline-prompt active-text="启用" inactive-text="禁用"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="preV" align="center" label="上次执行时间">
|
||||
<template #default="scope">
|
||||
<el-tag type="success" disable-transitions>{{ scope.row.preV }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="next" align="center" label="下次执行时间">
|
||||
<template #default="scope">
|
||||
<el-tag type="warning" disable-transitions>{{ scope.row.next }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" :icon="Edit" plain circle @click="openEditDialog(scope.row.id)" />
|
||||
<el-button type="danger" :icon="Delete" plain circle @click="delTask(scope.row.id)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="cus_util">
|
||||
<el-button color="#9b49e7" :icon="Clock" @click="openAddDialog">创建定时任务</el-button>
|
||||
<!--<el-button color="#d942bf" :icon="Promotion">一键采集</el-button>-->
|
||||
<!--<el-button type="danger" :icon="BellFilled">ReZero</el-button>-->
|
||||
</div>
|
||||
<!--定时任务添加弹窗-->
|
||||
<el-dialog v-model="dialogV.addV" title="创建定时任务">
|
||||
<el-form :model="form.add">
|
||||
<el-form-item label="任务周期">
|
||||
<el-input v-model="form.add.spec" placeholder="定时任务Cron表达式 (例: [0 */20 * * * ?] 每20分钟执行一次)"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="任务描述">
|
||||
<el-input v-model="form.add.remark" placeholder="定时任务描述信息"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="任务类型">
|
||||
<el-radio-group fill="#9b49e7" v-model="form.add.model">
|
||||
<el-tooltip class="box-item" effect="dark" content="执行所有已启用站点的采集任务" placement="top">
|
||||
<el-radio :label="0">自动更新</el-radio>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="box-item" effect="dark" content="只执行指定站点的采集任务" placement="top">
|
||||
<el-radio :label="1">自定义更新</el-radio>
|
||||
</el-tooltip>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.add.model == 1" label="资源绑定">
|
||||
<el-select v-model="form.add.ids" multiple collapse-tags collapse-tags-tooltip placeholder="Select" style="width: 240px">
|
||||
<el-option v-for="item in form.options" :key="item.id" :label="item.name" :value="item.id"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="采集时长">
|
||||
<el-tooltip class="box-item" effect="dark" content="采集最近x小时更新的影片,负数则默认采集所有资源" placement="top">
|
||||
<el-input-number v-model="form.add.time" :step="1" step-strictly />
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
<el-form-item label="任务状态">
|
||||
<el-switch v-model="form.add.state" inline-prompt active-text="开启" inactive-text="禁用"/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button color="#9b49e7" @click="addTask" >添加</el-button>
|
||||
<el-button @click="cancelDialog">取消</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<!--定时任务更新弹窗-->
|
||||
<el-dialog v-model="dialogV.editV" title="创建定时任务">
|
||||
<el-form :model="form.edit">
|
||||
<el-form-item label="任务标识">
|
||||
<el-tag type="success" disable-transitions>{{ form.edit.id }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="任务描述">
|
||||
<el-input v-model="form.edit.remark" placeholder="定时任务描述信息"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="任务周期">
|
||||
<el-tag disable-transitions>{{ form.edit.spec }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="任务类型">
|
||||
<el-tag disable-transitions>{{ form.edit.model == 0?'自动更新':'自定义更新' }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.edit.model == 1" label="资源绑定">
|
||||
<el-select v-model="form.edit.ids" multiple collapse-tags collapse-tags-tooltip placeholder="Select" style="width: 240px">
|
||||
<el-option v-for="item in form.options" :key="item.id" :label="item.name" :value="item.id"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="采集时长">
|
||||
<el-tooltip class="box-item" effect="dark" content="采集最近x小时更新的影片,负数则默认采集所有资源" placement="top">
|
||||
<el-input-number v-model="form.edit.time" :step="1" step-strictly />
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
<el-form-item label="任务状态">
|
||||
<el-switch v-model="form.edit.state" inline-prompt active-text="开启" inactive-text="禁用"/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button color="#9b49e7" @click="updateTask" >更新</el-button>
|
||||
<el-button @click="cancelDialog">取消</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
import {Clock, Delete, Edit} from "@element-plus/icons-vue";
|
||||
import {onMounted, reactive} from "vue";
|
||||
import {ApiGet, ApiPost} from "../../../utils/request";
|
||||
import {ElMessage} from "element-plus";
|
||||
|
||||
const data = reactive({
|
||||
taskList: []
|
||||
})
|
||||
|
||||
const dialogV = reactive({
|
||||
addV: false,
|
||||
editV: false,
|
||||
})
|
||||
|
||||
const form = reactive({
|
||||
add: { spec:'', remark: '', model: 1,ids:[], time:0, state: false},
|
||||
options: [],
|
||||
edit: { id: '', cid: '', spec:'', remark: '', model: 1, ids:[], time:0, state: false},
|
||||
})
|
||||
|
||||
const addTask = ()=>{
|
||||
ApiPost(`/manage/cron/add`, form.add).then((resp:any)=>{
|
||||
if (resp.status ==='ok') {
|
||||
ElMessage.success({message: resp.message})
|
||||
cancelDialog()
|
||||
getTaskList()
|
||||
} else {
|
||||
ElMessage.error({message: resp.message})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const updateTask = ()=>{
|
||||
console.log(form.edit)
|
||||
ApiPost(`/manage/cron/update`, {id: form.edit.id, ids: form.edit.ids, time: form.edit.time, state: form.edit.state, remark: form.edit.remark}).then((resp:any)=>{
|
||||
if (resp.status ==='ok') {
|
||||
ElMessage.success({message: resp.message})
|
||||
cancelDialog()
|
||||
getTaskList()
|
||||
} else {
|
||||
ElMessage.error({message: resp.message})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 关闭弹窗还原表单属性
|
||||
const cancelDialog = ()=>{
|
||||
// 关闭对话框
|
||||
dialogV.addV = false
|
||||
dialogV.editV = false
|
||||
// 还原表单状态
|
||||
form.add = { spec:'', remark: '', model: 1,ids:[], time:0, state: false}
|
||||
form.edit = { id: '', cid: '', spec:'', remark: '', model: 1, ids:[], time:0, state: false}
|
||||
}
|
||||
|
||||
const openAddDialog = ()=>{
|
||||
dialogV.addV = true
|
||||
getOptions()
|
||||
}
|
||||
|
||||
// 删除定时任务
|
||||
const delTask = (id:string)=>{
|
||||
ApiGet(`/manage/cron/del`, {id:id}).then((resp:any)=>{
|
||||
if (resp.status ==='ok') {
|
||||
ElMessage.success({message: resp.message})
|
||||
getTaskList()
|
||||
} else {
|
||||
ElMessage.error({message: resp.message})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const changeTaskState = (id:string,state:boolean)=>{
|
||||
ApiPost(`/manage/cron/change`, {id:id,state:state}).then((resp:any)=>{
|
||||
if (resp.status ==='ok') {
|
||||
ElMessage.success({message: resp.message})
|
||||
getTaskList()
|
||||
} else {
|
||||
ElMessage.error({message: resp.message})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const openEditDialog = (id:string)=>{
|
||||
dialogV.editV = true
|
||||
getOptions()
|
||||
ApiGet(`/manage/cron/find`,{id:id}).then((resp: any) => {
|
||||
if (resp.status === "ok") {
|
||||
form.edit = resp.data
|
||||
} else {
|
||||
ElMessage.error({message: resp.message})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getOptions = ()=>{
|
||||
ApiGet(`/manage/collect/options`).then((resp: any) => {
|
||||
if (resp.status === "ok") {
|
||||
form.options = resp.data
|
||||
} else {
|
||||
ElMessage.error({message: resp.message})
|
||||
}
|
||||
})
|
||||
}
|
||||
const getTaskList = ()=>{
|
||||
ApiGet(`/manage/cron/list`).then((resp: any) => {
|
||||
if (resp.status === "ok") {
|
||||
data.taskList = resp.data
|
||||
} else {
|
||||
ElMessage.error({message: resp.message})
|
||||
}
|
||||
})
|
||||
}
|
||||
onMounted(()=>{
|
||||
getTaskList()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.cus_util {
|
||||
display: flex;
|
||||
padding: 10px 8px;
|
||||
border-left: 2px solid #9b49e733;
|
||||
border-right: 2px solid #9b49e733;
|
||||
border-bottom: 2px solid #9b49e733;
|
||||
background: #ffffff;
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
:deep(.el-input-number){
|
||||
--el-fill-color-light: #e163ff8f;
|
||||
border-radius: var(--el-border-radius-base);
|
||||
padding: 0 0 !important;
|
||||
}
|
||||
:deep(.el-input-number__decrease){
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
:deep(.el-input-number__increase){
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
:deep(.el-tag--info){
|
||||
--el-fill-color: #67d9e863;
|
||||
}
|
||||
</style>
|
||||
107
client/src/views/manage/file/FileUpload.vue
Normal file
107
client/src/views/manage/file/FileUpload.vue
Normal file
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="title_container">
|
||||
<h3>文件上传</h3>
|
||||
</div>
|
||||
<div class="content">
|
||||
<el-upload v-model:file-list="data.photoWall" action="#" list-type="picture-card"
|
||||
:on-remove="handleRemove" :http-request="customUpload">
|
||||
<template #file="{ file }">
|
||||
<div>
|
||||
<el-image class="el-upload-list__item-thumbnail" :src="file.link" fit="cover" />
|
||||
<span class="el-upload-list__item-actions">
|
||||
<span class="el-upload-list__item-preview" @click="handlePictureCardPreview(file)">
|
||||
<el-icon><zoom-in /></el-icon>
|
||||
</span>
|
||||
<span class="el-upload-list__item-delete">
|
||||
<el-icon><Download /></el-icon>
|
||||
</span>
|
||||
<span class="el-upload-list__item-delete">
|
||||
<el-icon><Delete /></el-icon>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-icon><Plus /></el-icon>
|
||||
</el-upload>
|
||||
|
||||
<el-upload v-if="false" class="upload-demo" drag action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15" multiple>
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">
|
||||
删除文件 或<em>点击上传</em>
|
||||
</div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">
|
||||
jpg/png files with a size less than 500kb
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import {Delete, Download, Plus, UploadFilled, ZoomIn} from "@element-plus/icons-vue";
|
||||
|
||||
|
||||
import {ElMessage, UploadProps, UploadUserFile} from 'element-plus'
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import {ApiGet, ApiPost} from "../../../utils/request";
|
||||
import {Preview} from "../../../components/Global/preview";
|
||||
|
||||
const data = reactive({
|
||||
photoWall: [],
|
||||
imgList:[""]
|
||||
})
|
||||
const customUpload = (options:any)=>{
|
||||
console.log(options)
|
||||
console.log(options.file)
|
||||
let file = options.file
|
||||
let formData = new FormData();
|
||||
formData.append("file", file)
|
||||
ApiPost(`/manage/file/upload`, formData).then((resp:any)=>{
|
||||
if (resp.code === 0) {
|
||||
ElMessage.success({message: resp.msg})
|
||||
getPhotoPage()
|
||||
} else {
|
||||
ElMessage.error({message: resp.msg})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getPhotoPage = ()=>{
|
||||
ApiGet(`/manage/file/list`, ).then((resp: any) => {
|
||||
if (resp.code === 0) {
|
||||
data.photoWall = resp.data
|
||||
} else {
|
||||
ElMessage.error({message: resp.msg})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(()=>{
|
||||
getPhotoPage()
|
||||
})
|
||||
const handleRemove: UploadProps['onRemove'] = (uploadFile, uploadFiles) => {
|
||||
console.log(uploadFile, uploadFiles)
|
||||
}
|
||||
|
||||
// const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
|
||||
const handlePictureCardPreview = (currentFile:any) => {
|
||||
let list = data.photoWall.map((item:any)=>{
|
||||
return item.link
|
||||
})
|
||||
Preview({list:list,currentLink: currentFile.link})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
background: var(--bg-light);
|
||||
}
|
||||
.content {
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
}
|
||||
</style>
|
||||
17
client/src/views/manage/file/Temp.vue
Normal file
17
client/src/views/manage/file/Temp.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2 style="color: #8e48b4">功能开发中, 请关注后续更新</h2>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
292
client/src/views/manage/film/Film.vue
Normal file
292
client/src/views/manage/film/Film.vue
Normal file
@@ -0,0 +1,292 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="params_form">
|
||||
<el-form :model="data.params" class="cus_form">
|
||||
<el-form-item>
|
||||
<el-input v-model="data.params.name" style="display: inline-block;text-align: left" placeholder="片名搜素"
|
||||
:suffix-icon="Search"/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-select v-model="data.classId" @change="changeClass" placeholder="影片分类">
|
||||
<el-option v-for="item in data.options.class" :key="item.id" :label="item.name" :value="item.id"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-select v-model="data.params.plot" placeholder="剧情筛选">
|
||||
<el-option v-for="item in data.options.Plot" :key="item.Value" :label="item.Name" :value="item.Value"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-select v-model="data.params.area" placeholder="地区筛选">
|
||||
<el-option v-for="item in data.options.Area" :key="item.Value" :label="item.Name" :value="item.Value"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-select v-model="data.params.language" placeholder="语言筛选">
|
||||
<el-option v-for="item in data.options.Language" :key="item.Value" :label="item.Name" :value="item.Value"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-select v-model="data.params.year" placeholder="上映年份">
|
||||
<el-option v-for="item in data.options.year" :key="item.Value" :label="item.Name" :value="item.Value"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-select v-model="data.params.remarks" placeholder="更新状态">
|
||||
<el-option v-for="item in data.options.remarks" :key="item.Value" :label="item.Name" :value="item.Value"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-date-picker v-model="data.dateGroup" value-format="YYYY-MM-DD HH:mm:ss" type="datetimerange" start-placeholder="起始时间"
|
||||
end-placeholder="终止时间" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="searchFilm" >查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="content">
|
||||
<el-table
|
||||
:data="data.list" style="width: 100%" border size="default"
|
||||
table-layout="auto" max-height="calc(68vh - 20px)"
|
||||
row-key="id"
|
||||
:row-class-name="'cus-tr'">
|
||||
<el-table-column type="index" min-width="50px" align="left" label="序号">
|
||||
<template #default="scope">
|
||||
<span style="color: #8b40ff">{{ serialNum(scope.$index) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="mid" align="center" label="影片ID">
|
||||
<template #default="scope">
|
||||
<el-tag type="success" disable-transitions>{{ scope.row.mid }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" align="left" label="影片名称" show-overflow-tooltip class-name="col_name"/>
|
||||
<!--<el-table-column prop="subTitle" align="center" label="影片别名" />-->
|
||||
<el-table-column prop="cName" align="center" label="所属分类">
|
||||
<template #default="scope">
|
||||
<el-tag type="warning" disable-transitions>{{ scope.row.cName }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!--<el-table-column prop="classTag" align="left" label="剧情标签" >-->
|
||||
<!-- <template #default="scope">-->
|
||||
<!-- <el-tag v-for="t in scope.row.classTag" style="margin: 2px 3px 2px 0" type="warning" disable-transitions>{{ t }}</el-tag>-->
|
||||
<!-- </template>-->
|
||||
<!--</el-table-column>-->
|
||||
<el-table-column prop="year" align="center" label="年份">
|
||||
<template #default="scope">
|
||||
<el-tag type="warning" disable-transitions>{{ scope.row.year }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column sortable prop="score" align="center" label="评分">
|
||||
<template #default="scope">
|
||||
<el-tag type="success" disable-transitions>{{ scope.row.score }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column sortable prop="hits" align="center" label="热度">
|
||||
<template #default="scope">
|
||||
<el-tag type="danger" disable-transitions>🔥{{ scope.row.hits }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remarks" align="center" label="更新状态">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.remarks == '已完结' ?'success':''" disable-transitions>{{scope.row.remarks }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column sortable prop="updateStamp" align="center" label="更新时间">
|
||||
<template #default="scope">
|
||||
<el-tag type="success" disable-transitions>{{fmt.dateFormat(scope.row.updateStamp) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button type="success" :icon="Aim" @click="" plain circle/>
|
||||
<el-button type="primary" :icon="Edit" @click="" plain circle/>
|
||||
<el-button type="danger" :icon="Delete" @click="" plain circle/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pagination">
|
||||
<el-pagination :page-sizes="[10, 20, 50, 100, 500]" background layout="prev, pager, next, sizes, total, jumper"
|
||||
:total="data.page.total" v-model:page-size="data.page.pageSize"
|
||||
v-model:current-page="data.page.current"
|
||||
@change="getFilmPage" hide-on-single-page/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {Delete, Edit, Aim, Search, Calendar} from "@element-plus/icons-vue";
|
||||
import {onMounted, reactive} from "vue";
|
||||
import {ApiGet} from "../../../utils/request";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {fmt} from '../../../utils/format'
|
||||
|
||||
const data = reactive({
|
||||
list: [],
|
||||
page: {current: 1, pageCount: 0, pageSize: 10, total: 0},
|
||||
params: {name:'', pid: 0, cid: 0, plot: '', area: '', language: '', year: '',remarks: '',beginTime:'', endTime: '',},
|
||||
options: {class: [{id: 0, pid: -1, name: '', show: true}], Plot: [], Area: [], Language: [], year: [], remarks: []},
|
||||
dateGroup: [],
|
||||
classId: 0,
|
||||
})
|
||||
let tags = {}
|
||||
|
||||
// 选择影片分类时触发连锁事件
|
||||
const changeClass = (value: any) => {
|
||||
for (let i = 0; i < data.options.class.length; i++) {
|
||||
if (data.options.class[i].id == value) {
|
||||
// 从分类列表中获取匹配的分类信息
|
||||
let c = data.options.class[i]
|
||||
// 设置请求参数中的分类id和父级分类id信息
|
||||
|
||||
// 如果选择的是一级分类 则直接设置cid作为分类参数
|
||||
if (c.pid <= 0) {
|
||||
data.params.pid = c.id
|
||||
data.params.cid = 0
|
||||
return
|
||||
} else if (c.pid == data.params.pid){
|
||||
// 如果一级分类没有改变则只改变分类ID,并退出
|
||||
data.params.pid = c.pid
|
||||
data.params.cid = c.id
|
||||
return
|
||||
}
|
||||
// 如果一级分类改变则改变分类ID,并改变关联的tag参数信息
|
||||
data.params.pid = c.pid
|
||||
data.params.cid = c.id
|
||||
// 从tags列表中获取当前分类下的可用tag信息 (一级分类使用id获取, 二级分类使用pid获取)
|
||||
let t = c.pid == 0 ? tags[c.id as keyof typeof tags]:tags[c.pid as keyof typeof tags]
|
||||
// 匹配成功则设置对应的options参数
|
||||
if (t) {
|
||||
data.options.Plot = t['Plot']
|
||||
console.log(data.options.Plot)
|
||||
data.options.Area = t['Area']
|
||||
data.options.Language = t['Language']
|
||||
} else {
|
||||
// 匹配失败则清空已有的options参数
|
||||
data.options.Plot = []
|
||||
data.options.Area = []
|
||||
data.options.Language = []
|
||||
}
|
||||
// tags改变时清空对应的param参数
|
||||
data.params.plot = ''
|
||||
data.params.area = ''
|
||||
data.params.language = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const serialNum = (index: number) => {
|
||||
return (data.page.current - 1) * data.page.pageSize + index + 1
|
||||
}
|
||||
|
||||
const searchFilm = ()=>{
|
||||
let p = data.params
|
||||
// 时间选择器参数处理 如果 dateGroup 不为空 则追加时间范围参数
|
||||
if (data.dateGroup && data.dateGroup.length == 2) {
|
||||
p.beginTime = data.dateGroup[0]
|
||||
p.endTime = data.dateGroup[1]
|
||||
} else {
|
||||
p.beginTime = ''
|
||||
p.endTime = ''
|
||||
}
|
||||
getFilmPage()
|
||||
|
||||
}
|
||||
|
||||
const getFilmPage = () => {
|
||||
let {current, pageSize} = data.page
|
||||
let params = data.params
|
||||
ApiGet(`/manage/film/search/list`, {...params,current, pageSize}).then((resp: any) => {
|
||||
if (resp.code === 0) {
|
||||
data.list = resp.data.list ? resp.data.list.map((item: any) => {
|
||||
// 对数据进行格式化处理
|
||||
item.year = item.year <= 0 ? '未知' : item.year
|
||||
item.score = item.score == 0 ? '暂无' : item.score
|
||||
// if (item.classTag) {
|
||||
// item.classTag = [...item.classTag.toString().split(',')]
|
||||
// } else {
|
||||
// item.classTag = ['未知']
|
||||
// }
|
||||
return item
|
||||
}) : []
|
||||
data.page = resp.data.params.paging
|
||||
data.options.class = resp.data.options.class
|
||||
data.options.remarks = resp.data.options.remarks
|
||||
data.options.year = resp.data.options.year
|
||||
tags = resp.data.options.tags
|
||||
} else {
|
||||
ElMessage.error({message: resp.msg})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getFilmPage()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.params_form {
|
||||
background: var(--bg-light);
|
||||
margin-bottom: 20px;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
.cus_form {
|
||||
width: 100%;
|
||||
flex-flow: wrap;
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
}
|
||||
|
||||
:deep(.el-form-item) {
|
||||
width: calc(16% - 12px);
|
||||
margin: 10px 6px;
|
||||
}
|
||||
|
||||
|
||||
:deep(.el-table) {
|
||||
color: var(--content-text-color);
|
||||
}
|
||||
|
||||
.content {
|
||||
border: 1px solid #9b49e733;
|
||||
background: var(--bg-light);
|
||||
--el-color-primary: var(--paging-parmary-color);
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin: 20px auto;
|
||||
max-width: 100%;
|
||||
text-align: center;
|
||||
padding-right: 50px;
|
||||
|
||||
}
|
||||
|
||||
:deep(.el-pagination) {
|
||||
width: 100% !important;
|
||||
justify-content: end;
|
||||
--el-color-primary: var(--paging-parmary-color);
|
||||
}
|
||||
|
||||
:deep(.el-select-dropdown__item) {
|
||||
--el-color-primary: red !important;
|
||||
}
|
||||
|
||||
|
||||
:deep(.el-pager li) {
|
||||
--el-pagination-button-bg-color: var(--btn-bg-linght);
|
||||
border: 1px solid var(--border-gray-color);
|
||||
}
|
||||
|
||||
:deep(.el-pagination button) {
|
||||
--el-disabled-bg-color: var(--btn-bg-linght);
|
||||
--el-pagination-button-bg-color: var(--btn-bg-linght);
|
||||
border: 1px solid var(--border-gray-color);
|
||||
}
|
||||
</style>
|
||||
267
client/src/views/manage/film/FilmAdd.vue
Normal file
267
client/src/views/manage/film/FilmAdd.vue
Normal file
@@ -0,0 +1,267 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<h2 style="text-align: start">添加影片</h2>
|
||||
<el-form :model="data.form" class="film_add_form">
|
||||
<el-form-item>
|
||||
<div class="el-input-group__prepend" style="border: 1px solid #dcdfe6;border-right: none;border-radius: 3px;height: 32px">影片分类: </div>
|
||||
<el-select v-model="data.currentClass" style="width: calc(100% - 103px)" @change="changeClass" placeholder="影片分类选择">
|
||||
<el-option v-for="item in data.options.category" :key="item.id" :label="item.name" :value="item.id"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="data.form.name" placeholder="请输入影片名称" clearable >
|
||||
<template #prepend>影片名称: </template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="data.form.subTitle" placeholder="影片别名, 可留空" clearable >
|
||||
<template #prepend>影片别名: </template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="data.form.initial" placeholder="影片检索首字母, 大写" clearable >
|
||||
<template #prepend>首字母: </template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="data.form.classTag" placeholder="影片剧情标签(多标签以逗号分隔): 奇幻,校园,爱情" clearable >
|
||||
<template #prepend>剧情Tag: </template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="data.form.director" placeholder="导演名, 多个名称以逗号进行分隔" clearable >
|
||||
<template #prepend>导演: </template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="data.form.actor" placeholder="主演名, 多个名称以逗号进行分隔" clearable >
|
||||
<template #prepend>主演: </template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="data.form.writer" placeholder="作者名, 多个名称以逗号进行分隔" clearable >
|
||||
<template #prepend>作者: </template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="data.form.remarks" placeholder="影片更新进度信息, 完结, HD, 更新至xx集" clearable >
|
||||
<template #prepend>更新状态: </template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="data.form.releaseDate" placeholder="影片上映时间: YYYY-MM-DD" clearable >
|
||||
<template #prepend>上映时间: </template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="data.form.area" placeholder="影片来源地区信息" clearable >
|
||||
<template #prepend>地区: </template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="data.form.lang" placeholder="影片语言信息" clearable >
|
||||
<template #prepend>语言: </template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="data.form.year" placeholder="影片上映年份信息: YYYY" clearable >
|
||||
<template #prepend>年份: </template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="data.form.state" placeholder=" 影片状态: 正片 | 预告片" clearable >
|
||||
<template #prepend>影片状态: </template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="data.form.dbId" placeholder="豆瓣ID" clearable >
|
||||
<template #prepend>豆瓣Id: </template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="data.form.dbScore" placeholder="豆瓣评分" clearable >
|
||||
<template #prepend>豆瓣评分: </template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="data.form.hits" placeholder="影片热度(播放数)" clearable >
|
||||
<template #prepend>影片热度: </template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="data.form.picture" placeholder="输入图片URL链接或点击上传到服务器并自动生成URL连接信息)" clearable >
|
||||
<template #prepend>影片海报: </template>
|
||||
<template #append>
|
||||
<!--<el-button> 上传图片</el-button>-->
|
||||
<el-upload class="upload-demo" :show-file-list="false" action="#" :http-request="customUpload">
|
||||
<el-button type="primary">上传图片</el-button>
|
||||
</el-upload>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="data.form.playForm" placeholder="影片播放资源来源: xxXm3u8" clearable >
|
||||
<template #prepend>播放来源: </template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<span class="el-input-group__prepend cus_label" >剧情简介: </span>
|
||||
</template>
|
||||
<el-input v-model="data.form.content" :autosize="{ minRows: 2, maxRows: 5 }" type="textarea" placeholder="影片剧情描述信息" />
|
||||
</el-form-item>
|
||||
<el-form-item label="播放地址:">
|
||||
<template #label>
|
||||
<span class="el-input-group__prepend cus_label" >播放地址: </span>
|
||||
</template>
|
||||
<el-input v-model="data.form.playLink" :autosize="{ minRows: 2, maxRows: 5 }" type="textarea"
|
||||
placeholder="影片播放地址信息: 格式: 第01集$https://xxx/xxx/index.m3u8#第02集$https://xxx/xxx/index.m3u8" />
|
||||
</el-form-item>
|
||||
<el-form-item class="form_btn">
|
||||
<el-button type="primary" @click="addFilm" >添加影片</el-button>
|
||||
<el-button >清空信息</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {PictureFilled} from "@element-plus/icons-vue";
|
||||
import {onMounted, reactive} from "vue";
|
||||
import {ApiGet, ApiPost} from "../../../utils/request";
|
||||
import {ElMessage} from "element-plus";
|
||||
|
||||
// 表单数据初始化
|
||||
const formInit = {
|
||||
id: 0,
|
||||
cid: 0,
|
||||
pid: 0,
|
||||
name: '',
|
||||
picture: '',
|
||||
subTitle: '',
|
||||
cName: '',
|
||||
enName: '',
|
||||
initial: '',
|
||||
classTag: '',
|
||||
actor: '',
|
||||
director: '',
|
||||
writer: '',
|
||||
blurb: '',
|
||||
content: '',
|
||||
remarks: '',
|
||||
releaseDate: '',
|
||||
area: '',
|
||||
lang: '',
|
||||
year: '',
|
||||
state: '',
|
||||
updateTime: '',
|
||||
addTime: '',
|
||||
dbId: 0,
|
||||
dbScore: '',
|
||||
hits: 0,
|
||||
playForm: '',
|
||||
playLink: '',
|
||||
}
|
||||
const data = reactive({
|
||||
form: formInit,
|
||||
options: {
|
||||
category: [{id:0,name:'分类名称',pid: 0}],
|
||||
},
|
||||
})
|
||||
|
||||
const customUpload = (options:any)=>{
|
||||
console.log(options)
|
||||
console.log(options.file)
|
||||
let file = options.file
|
||||
let formData = new FormData();
|
||||
formData.append("file", file)
|
||||
ApiPost(`/manage/file/upload`, formData).then((resp:any)=>{
|
||||
if (resp.code === 0) {
|
||||
ElMessage.success({message: resp.msg})
|
||||
data.form.picture = resp.data
|
||||
} else {
|
||||
ElMessage.error({message: resp.msg})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 选择影片分类
|
||||
const changeClass = (value:any)=>{
|
||||
data.options.category.forEach(item=>{
|
||||
if (item.id == value) {
|
||||
data.form.cid = item.id
|
||||
data.form.pid = item.pid
|
||||
data.form.cName =item.name
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 添加影片
|
||||
const addFilm = ()=>{
|
||||
// 对数字类型参数进行转换
|
||||
let params = data.form
|
||||
params.dbId = params.dbId - 0
|
||||
params.hits = params.hits - 0
|
||||
ApiPost(`/manage/film/add`,{...data.form}).then((resp: any) => {
|
||||
if (resp.code === 0) {
|
||||
// 更新成功后关闭弹窗, 重新获取最新的分类表格信息
|
||||
ElMessage.success({message: resp.msg})
|
||||
// 重置表单数据
|
||||
data.form = formInit
|
||||
} else {
|
||||
ElMessage.error({message: resp.msg})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(()=>{
|
||||
ApiGet(`/manage/film/class/tree` ).then((resp: any) => {
|
||||
if (resp.code === 0) {
|
||||
let l = [{id:0,name:'分类名称',pid: 0}]
|
||||
l.pop()
|
||||
resp.data.children.forEach((item:any)=>{
|
||||
if (item.children && item.children.length > 0) {
|
||||
l = [...l,...item.children]
|
||||
}
|
||||
})
|
||||
data.options.category = l
|
||||
} else {
|
||||
ElMessage.error({message: resp.msg})
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
background: var(--bg-light);
|
||||
}
|
||||
.film_add_form{
|
||||
width: 100%;
|
||||
flex-flow: wrap;
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
}
|
||||
:deep(.el-form-item) {
|
||||
--el-fill-color-light: var(--bg-fill-light);
|
||||
width: calc(50% - 120px);
|
||||
margin: 15px 60px;
|
||||
}
|
||||
.form_btn{
|
||||
width: 100%!important;
|
||||
margin: 40px auto;
|
||||
}
|
||||
|
||||
:deep(.form_btn .el-form-item__content){
|
||||
justify-content: center;
|
||||
}
|
||||
:deep(.el-form-item__label){
|
||||
padding-right: 0!important;
|
||||
}
|
||||
.cus_label{
|
||||
border: 1px solid #dcdfe6;
|
||||
border-right: none;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
</style>
|
||||
181
client/src/views/manage/film/FilmClass.vue
Normal file
181
client/src/views/manage/film/FilmClass.vue
Normal file
@@ -0,0 +1,181 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<el-table
|
||||
:data="data.classTree" style="width: 100%" border size="default"
|
||||
table-layout="auto" max-height="calc(90vh - 20px)"
|
||||
row-key="id"
|
||||
:row-class-name="'cus-tr'" >
|
||||
<el-table-column prop="name" label="分类名称">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.pid==0?'success':'warning'" disable-transitions>{{ scope.row.name}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="show" align="center" label="是否展示">
|
||||
<template #default="scope">
|
||||
<el-switch v-model="scope.row.show" inline-prompt active-text="展示" @change="changeClassState(scope.row.id, scope.row.show)" inactive-text="隐藏"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" :icon="Edit" @click="openEditDialog(scope.row.id)" plain circle />
|
||||
<el-button type="danger" :icon="Delete" @click="delClass(scope.row.id)" plain circle/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--功能按钮-->
|
||||
<div class="cus_util">
|
||||
<el-button color="#9b49e7" :icon="RefreshLeft" @click="resetFilmClass">重置分类信息</el-button>
|
||||
</div>
|
||||
<!--影片分类信息修改弹窗-->
|
||||
<el-dialog v-model="dialog.editV" @close="cancelDialog" width="480px" title="更新分类信息">
|
||||
<el-form :model="dialog.editForm">
|
||||
<el-form-item label="分类名称">
|
||||
<el-input v-model="dialog.editForm.name" placeholder="分类名称,用于首页导航展示"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="分类层级">
|
||||
<el-tag :type="dialog.editForm.pid==0?'success':'warning'" disable-transitions>{{ dialog.editForm.pid == 0 ? '一级分类':'二级分类' }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否展示">
|
||||
<el-switch v-model="dialog.editForm.show" inline-prompt active-text="展示" inactive-text="隐藏"/>
|
||||
</el-form-item>
|
||||
<el-form-item class="class_sub" label="拓展分类" v-if="dialog.editForm.children">
|
||||
<el-tag class="class_sub_tag" v-for="c in dialog.editForm.children" type="warning" disable-transitions>{{ c.name }}</el-tag>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button color="#9b49e7" @click="updateClass" >更新</el-button>
|
||||
<el-button @click="dialog.editV = false">取消</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Delete, Edit,RefreshLeft,} from "@element-plus/icons-vue";
|
||||
import {onMounted, reactive} from "vue";
|
||||
import {ApiGet, ApiPost} from "../../../utils/request";
|
||||
import {ElMessage} from "element-plus";
|
||||
|
||||
// table数据
|
||||
const data = reactive({
|
||||
classTree: []
|
||||
})
|
||||
|
||||
// dialog 弹窗数据
|
||||
const dialog = reactive({
|
||||
editV: false,
|
||||
editForm: {id: -99, pid: -99, name:'', show: true, children:[]},
|
||||
})
|
||||
|
||||
// 删除分类信息
|
||||
const delClass = (id:number)=>{
|
||||
ApiGet(`/manage/film/class/del`, {id: id}).then((resp: any) => {
|
||||
if (resp.code === 0) {
|
||||
getFilmClassTree()
|
||||
ElMessage.success({message: resp.msg})
|
||||
} else {
|
||||
ElMessage.error({message: resp.msg})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 打开修改弹窗
|
||||
const openEditDialog = (id:number)=>{
|
||||
dialog.editV = true
|
||||
ApiGet(`/manage/film/class/find`,{id:id}).then((resp: any) => {
|
||||
if (resp.code === 0) {
|
||||
dialog.editForm = resp.data
|
||||
} else {
|
||||
ElMessage.error({message: resp.msg})
|
||||
}
|
||||
})
|
||||
}
|
||||
// 更新影片分类信息
|
||||
const updateClass = ()=>{
|
||||
let { id, name, show } = dialog.editForm
|
||||
ApiPost(`/manage/film/class/update`,{ id: id, name:name, show:show }).then((resp: any) => {
|
||||
if (resp.code === 0) {
|
||||
// 更新成功后关闭弹窗, 重新获取最新的分类表格信息
|
||||
dialog.editV = false
|
||||
getFilmClassTree()
|
||||
ElMessage.success({message: resp.msg})
|
||||
} else {
|
||||
ElMessage.error({message: resp.msg})
|
||||
}
|
||||
})
|
||||
}
|
||||
// 影片是否展示Switch按钮
|
||||
const changeClassState = (id:string, show: number)=>{
|
||||
ApiPost(`/manage/film/class/update`,{ id: id, show:show }).then((resp: any) => {
|
||||
if (resp.code === 0) {
|
||||
// 更新成功后关闭弹窗, 重新获取最新的分类表格信息
|
||||
dialog.editV = false
|
||||
getFilmClassTree()
|
||||
ElMessage.success({message: resp.msg})
|
||||
} else {
|
||||
ElMessage.error({message: resp.msg})
|
||||
}
|
||||
})
|
||||
}
|
||||
// 对话框关闭时重置表单数据为初始值
|
||||
const cancelDialog = ()=>{
|
||||
dialog.editForm = {id: -99, pid: -99, name:'', show: true, children:[]}
|
||||
}
|
||||
|
||||
// 重置分类信息
|
||||
const resetFilmClass = ()=>{
|
||||
ApiGet(`/manage/spider/class/cover`).then((resp: any) => {
|
||||
if (resp.code === 0) {
|
||||
ElMessage.success({message: resp.msg})
|
||||
} else {
|
||||
ElMessage.error({message: resp.msg})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取影片分类树信息
|
||||
const getFilmClassTree = ()=>{
|
||||
ApiGet(`/manage/film/class/tree`).then((resp: any) => {
|
||||
if (resp.code === 0) {
|
||||
data.classTree = resp.data.children
|
||||
} else {
|
||||
ElMessage.error({message: resp.msg})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(()=>{
|
||||
getFilmClassTree()
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<style scoped>
|
||||
:deep(.el-table){
|
||||
--el-table-row-hover-bg-color: #9b49e71a;
|
||||
}
|
||||
.class_sub {
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
}
|
||||
.class_sub_tag{
|
||||
width: calc(20% - 8px);
|
||||
margin: 5px 4px;
|
||||
}
|
||||
|
||||
.cus_util {
|
||||
display: flex;
|
||||
padding: 10px 8px;
|
||||
border-left: 2px solid #9b49e733;
|
||||
border-right: 2px solid #9b49e733;
|
||||
border-bottom: 2px solid #9b49e733;
|
||||
background: #ffffff;
|
||||
justify-content: end;
|
||||
}
|
||||
</style>
|
||||
108
client/src/views/manage/system/SiteConfig.vue
Normal file
108
client/src/views/manage/system/SiteConfig.vue
Normal file
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<h2 class="title">网站基础参数配置</h2>
|
||||
<div class="content">
|
||||
<el-form size="large" :model="c.site" label-width="120px">
|
||||
<el-form-item label="网站名称">
|
||||
<el-input v-model="c.site.siteName"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="网站域名">
|
||||
<el-input v-model="c.site.domain"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="网站Logo">
|
||||
<el-input v-model="c.site.logo"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="搜索关键字">
|
||||
<el-input v-model="c.site.keyword"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="网站描述">
|
||||
<el-input v-model="c.site.describe"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="网站状态">
|
||||
<el-switch v-model="c.site.state" inline-prompt active-text="开启" inactive-text="关闭"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="维护提示">
|
||||
<el-input v-model="c.site.hint"/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button color="#9b49e7" @click="updateBasicConfig" >更新</el-button>
|
||||
<el-button @click="getBasicInfo" >重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
|
||||
import {onMounted, reactive} from "vue";
|
||||
import {ApiGet, ApiPost} from "../../../utils/request";
|
||||
import {ElMessage} from "element-plus";
|
||||
|
||||
const c = reactive({
|
||||
site: {
|
||||
siteName: '',
|
||||
domain: '',
|
||||
logo: '',
|
||||
keyword: '',
|
||||
describe: '',
|
||||
state: true,
|
||||
hint: '',
|
||||
}
|
||||
})
|
||||
|
||||
const updateBasicConfig = ()=>{
|
||||
ApiPost(`/manage/config/basic/update`, c.site).then((resp: any) => {
|
||||
if (resp.status === 'ok') {
|
||||
// console.log(window.location.hostname)
|
||||
ElMessage.success({message: resp.message})
|
||||
getBasicInfo()
|
||||
} else {
|
||||
ElMessage.error({message: resp.message})
|
||||
}
|
||||
})
|
||||
// 更新后重新从后端获取最新数据
|
||||
}
|
||||
const getBasicInfo = ()=>{
|
||||
ApiGet(`/manage/config/basic`).then((resp: any) => {
|
||||
if (resp.code === 0) {
|
||||
// console.log(window.location.hostname)
|
||||
c.site = resp.data
|
||||
} else {
|
||||
ElMessage.error({message: resp.data.msg})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
getBasicInfo()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
background: #ffffff;
|
||||
padding: 20px 0;
|
||||
height: 100%;
|
||||
}
|
||||
.title {
|
||||
color: #2b333fb3;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 2px solid #00000005;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 60%;
|
||||
margin: 36px auto;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__label) {
|
||||
color: #888888;
|
||||
font-size: 18px;
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user