add:管理页面页码+Banner配置
This commit is contained in:
+8
-1
@@ -1,5 +1,5 @@
|
|||||||
import datetime
|
import datetime
|
||||||
from sqlalchemy import Boolean, Column, Integer, String, DateTime
|
from sqlalchemy import Boolean, Column, Integer, String, DateTime, JSON
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
from sqlalchemy.ext.asyncio import create_async_engine
|
from sqlalchemy.ext.asyncio import create_async_engine
|
||||||
from sqlalchemy.ext.asyncio.session import AsyncSession
|
from sqlalchemy.ext.asyncio.session import AsyncSession
|
||||||
@@ -20,6 +20,13 @@ async def get_session():
|
|||||||
yield s
|
yield s
|
||||||
|
|
||||||
|
|
||||||
|
class Values(Base):
|
||||||
|
__tablename__ = 'values'
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
key = Column(String, unique=True)
|
||||||
|
value = Column(JSON)
|
||||||
|
|
||||||
|
|
||||||
class Codes(Base):
|
class Codes(Base):
|
||||||
__tablename__ = "codes"
|
__tablename__ = "codes"
|
||||||
id = Column(Integer, primary_key=True, index=True)
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
|||||||
@@ -4,15 +4,16 @@ import asyncio
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from fastapi import FastAPI, Depends, UploadFile, Form, File, HTTPException, BackgroundTasks
|
from fastapi import FastAPI, Depends, UploadFile, Form, File, HTTPException, BackgroundTasks
|
||||||
|
from starlette.requests import Request
|
||||||
from starlette.responses import HTMLResponse, FileResponse
|
from starlette.responses import HTMLResponse, FileResponse
|
||||||
from starlette.staticfiles import StaticFiles
|
from starlette.staticfiles import StaticFiles
|
||||||
|
|
||||||
from sqlalchemy import select, update
|
from sqlalchemy import select, update, func
|
||||||
from sqlalchemy.ext.asyncio.session import AsyncSession
|
from sqlalchemy.ext.asyncio.session import AsyncSession
|
||||||
|
|
||||||
import settings
|
import settings
|
||||||
from utils import delete_expire_files, storage, get_code, error_ip_limit, upload_ip_limit
|
from utils import delete_expire_files, storage, get_code, error_ip_limit, upload_ip_limit
|
||||||
from database import get_session, Codes, init_models
|
from database import get_session, Codes, init_models, Values
|
||||||
from depends import admin_required
|
from depends import admin_required
|
||||||
|
|
||||||
# 实例化FastAPI
|
# 实例化FastAPI
|
||||||
@@ -48,16 +49,33 @@ admin_html = open('templates/admin.html', 'r', encoding='utf-8').read() \
|
|||||||
.replace('{{keywords}}', settings.KEYWORDS)
|
.replace('{{keywords}}', settings.KEYWORDS)
|
||||||
|
|
||||||
|
|
||||||
@app.get(f'/{settings.ADMIN_ADDRESS}', description='管理页面', response_class=HTMLResponse)
|
@app.get(f'/{settings.ADMIN_ADDRESS}', description='管理页面')
|
||||||
async def admin():
|
async def admin():
|
||||||
return HTMLResponse(admin_html)
|
return HTMLResponse(admin_html)
|
||||||
|
|
||||||
|
|
||||||
@app.post(f'/{settings.ADMIN_ADDRESS}', dependencies=[Depends(admin_required)], description='查询数据库列表')
|
@app.post(f'/{settings.ADMIN_ADDRESS}', dependencies=[Depends(admin_required)], description='查询数据库列表')
|
||||||
async def admin_post(s: AsyncSession = Depends(get_session)):
|
async def admin_post(page: int = Form(default=1), size: int = Form(default=10), s: AsyncSession = Depends(get_session)):
|
||||||
# 查询数据库列表
|
codes = (await s.execute(select(Codes).offset((page - 1) * size).limit(size))).scalars().all()
|
||||||
codes = (await s.execute(select(Codes))).scalars().all()
|
total = (await s.execute(select(func.count(Codes.id)))).scalar()
|
||||||
return {'detail': '查询成功', 'data': codes}
|
return {'detail': '查询成功', 'data': codes, 'paginate': {
|
||||||
|
'page': page,
|
||||||
|
'size': size,
|
||||||
|
'total': total
|
||||||
|
}}
|
||||||
|
|
||||||
|
|
||||||
|
@app.patch(f'/{settings.ADMIN_ADDRESS}', dependencies=[Depends(admin_required)], description='修改数据库数据')
|
||||||
|
async def admin_patch(request: Request, s: AsyncSession = Depends(get_session)):
|
||||||
|
# 从数据库获取系统配置
|
||||||
|
# 如果不存在config这个key,就创建一个
|
||||||
|
config = (await s.execute(select(Values).filter(Values.key == 'config'))).scalar_one_or_none()
|
||||||
|
if not config:
|
||||||
|
s.add(Values(key='config', value=await request.json()))
|
||||||
|
else:
|
||||||
|
await s.execute(update(Values).where(Values.key == 'config').values(value=await request.json()))
|
||||||
|
await s.commit()
|
||||||
|
return {'detail': '修改成功'}
|
||||||
|
|
||||||
|
|
||||||
@app.delete(f'/{settings.ADMIN_ADDRESS}', dependencies=[Depends(admin_required)], description='删除数据库记录')
|
@app.delete(f'/{settings.ADMIN_ADDRESS}', dependencies=[Depends(admin_required)], description='删除数据库记录')
|
||||||
@@ -76,16 +94,40 @@ async def admin_delete(code: str, s: AsyncSession = Depends(get_session)):
|
|||||||
return {'detail': '删除成功'}
|
return {'detail': '删除成功'}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get('/config', description='获取系统配置', dependencies=[Depends(admin_required)])
|
||||||
|
async def config(s: AsyncSession = Depends(get_session)):
|
||||||
|
# 从数据库获取系统配置
|
||||||
|
data = (await s.execute(select(Values).filter(Values.key == 'config'))).scalar_one_or_none()
|
||||||
|
return {'detail': '获取成功', 'data': data.value}
|
||||||
|
|
||||||
|
|
||||||
@app.get('/')
|
@app.get('/')
|
||||||
async def index():
|
async def index():
|
||||||
return HTMLResponse(index_html)
|
return HTMLResponse(index_html)
|
||||||
|
|
||||||
|
|
||||||
@app.get('/banner')
|
@app.get('/banner')
|
||||||
async def banner():
|
async def banner(s: AsyncSession = Depends(get_session)):
|
||||||
|
# 数据库查询config
|
||||||
|
config = (await s.execute(select(Values).filter(Values.key == 'config'))).scalar_one_or_none()
|
||||||
|
# 如果存在config,就返回config的value
|
||||||
|
if config and config.value.get('banners'):
|
||||||
|
return {
|
||||||
|
'detail': '查询成功',
|
||||||
|
'data': config.value['banners']
|
||||||
|
}
|
||||||
|
# 如果不存在config,就返回默认的banner
|
||||||
return {
|
return {
|
||||||
'detail': 'banner',
|
'detail': 'banner',
|
||||||
'data': settings.UPLOAD_BANNERS.split(',')
|
'data': [{
|
||||||
|
'text': 'FileCodeBox',
|
||||||
|
'url': 'https://github.com/vastsa/FileCodeBox',
|
||||||
|
'src': '/static/banners/img_1.png'
|
||||||
|
}, {
|
||||||
|
'text': 'FileCodeBox',
|
||||||
|
'url': 'https://www.lanol.cn',
|
||||||
|
'src': '/static/banners/img_2.png'
|
||||||
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+159
-14
@@ -12,17 +12,25 @@
|
|||||||
<meta name="keywords" content="{{keywords}}"/>
|
<meta name="keywords" content="{{keywords}}"/>
|
||||||
<meta name="generator" content="FileCodeBox"/>
|
<meta name="generator" content="FileCodeBox"/>
|
||||||
<meta name="template" content="Lan"/>
|
<meta name="template" content="Lan"/>
|
||||||
<script src="/static/asserts/vue.min.js"></script>
|
<script src="/static/asserts/vue.js"></script>
|
||||||
<script src="/static/asserts/index.js"></script>
|
<script src="/static/asserts/index.js"></script>
|
||||||
|
<style>
|
||||||
|
.el-menu-demo {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<el-row v-if="login" :gutter="10">
|
<el-row v-if="login" :gutter="10">
|
||||||
<el-col :xs="0" :sm="4" :md="4" :lg="4" :xl="4">
|
<el-col :span="24" style="text-align: center">
|
||||||
|
<el-menu :default-active="activeIndex" class="el-menu-demo" mode="horizontal" @select="handleSelect">
|
||||||
|
<el-menu-item index="2">文件管理</el-menu-item>
|
||||||
|
<el-menu-item index="3">系统配置</el-menu-item>
|
||||||
|
</el-menu>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :xs="24" :sm="16" :md="16" :lg="16" :xl="16">
|
<el-col v-if="activeIndex === '2'">
|
||||||
<el-card>
|
<el-card style="height: 80vh;overflow: scroll;margin-bottom: 1rem">
|
||||||
<el-empty v-if="files.length === 0" description="暂时还没有文件"></el-empty>
|
<el-empty v-if="files.length === 0" description="暂时还没有文件"></el-empty>
|
||||||
<el-card v-for="file in files" :key="file.code">
|
<el-card v-for="file in files" :key="file.code">
|
||||||
<el-row>
|
<el-row>
|
||||||
@@ -49,8 +57,87 @@
|
|||||||
</el-row>
|
</el-row>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
<el-pagination
|
||||||
|
background
|
||||||
|
style="text-align: center"
|
||||||
|
layout="prev, pager, next, sizes"
|
||||||
|
:page-size="paginate.page_size"
|
||||||
|
@current-change="loginAdmin"
|
||||||
|
@size-change="updateSize"
|
||||||
|
:total="paginate.total"
|
||||||
|
>
|
||||||
|
</el-pagination>
|
||||||
|
</el-col>
|
||||||
|
<el-col v-if="activeIndex === '3'">
|
||||||
|
<el-form ref="form" style="width: 50%;margin:2rem auto" :model="config" label-width="200px">
|
||||||
|
<!--? <el-form-item label="错误限制">-->
|
||||||
|
<!--? <div>-->
|
||||||
|
<!--? 间隔:-->
|
||||||
|
<!--? <el-input-number v-model="config.error_count" :min="1"></el-input-number>-->
|
||||||
|
<!--? 分钟-->
|
||||||
|
<!--? </div>-->
|
||||||
|
<!--? <div>-->
|
||||||
|
<!--? 数量:-->
|
||||||
|
<!--? <el-input-number v-model="config.error_minute" :min="1"></el-input-number>-->
|
||||||
|
<!--? 个-->
|
||||||
|
<!--? </div>-->
|
||||||
|
<!--? </el-form-item>-->
|
||||||
|
<!--? <el-form-item label="上传限制">-->
|
||||||
|
<!--? <div>-->
|
||||||
|
<!--? 开启上传:-->
|
||||||
|
<!--? <el-switch-->
|
||||||
|
<!--? v-model="config.enable_upload"-->
|
||||||
|
<!--? active-color="#13ce66"-->
|
||||||
|
<!--? inactive-color="#ff4949">-->
|
||||||
|
<!--? </el-switch>-->
|
||||||
|
<!--? </div>-->
|
||||||
|
<!--? <div>-->
|
||||||
|
<!--? 间隔:-->
|
||||||
|
<!--? <el-input-number v-model="config.upload_minute" :min="1"></el-input-number>-->
|
||||||
|
<!--? 分钟-->
|
||||||
|
<!--? </div>-->
|
||||||
|
<!--? <div>-->
|
||||||
|
<!--? 数量:-->
|
||||||
|
<!--? <el-input-number v-model="config.upload_count" :min="1"></el-input-number>-->
|
||||||
|
<!--? 个-->
|
||||||
|
<!--? </div>-->
|
||||||
|
<!--? <div>-->
|
||||||
|
<!--? 最长:-->
|
||||||
|
<!--? <el-input-number v-model="config.max_days" :min="1"></el-input-number>-->
|
||||||
|
<!--? 天-->
|
||||||
|
<!--? </div>-->
|
||||||
|
<!--? <div>-->
|
||||||
|
<!--? 大小:-->
|
||||||
|
<!--? <el-input-number v-model="config.file_size_limit" :min="1"></el-input-number>-->
|
||||||
|
<!--? MB-->
|
||||||
|
<!--? </div>-->
|
||||||
|
<!--? </el-form-item>-->
|
||||||
|
<!--? <el-form-item label="删除间隔">-->
|
||||||
|
<!--? 时长:-->
|
||||||
|
<!--? <el-input-number v-model="config.delete_expire_files_interval"></el-input-number>-->
|
||||||
|
<!--? 分钟-->
|
||||||
|
<!--? </el-form-item>-->
|
||||||
|
<!--? <el-form-item label="管理员密码">-->
|
||||||
|
<!--? <el-input v-model="config.admin_password" type="password"></el-input>-->
|
||||||
|
<!--? </el-form-item>-->
|
||||||
|
<el-form-item v-for="(banner,index) in config.banners" :label="'Banner'+ (index+1)">
|
||||||
|
<div style="width: 50%;display: inline-block">
|
||||||
|
<el-input v-model="banner.text" placeholder="文本"></el-input>
|
||||||
|
<el-input v-model="banner.url" placeholder="跳转链接"></el-input>
|
||||||
|
<el-input v-model="banner.src" placeholder="图片路径"></el-input>
|
||||||
|
</div>
|
||||||
|
<div style="display: inline-block">
|
||||||
|
<el-button type="danger" @click="deleteBanner(index)">删除</el-button>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button size="mini" @click="addBanner" type="primary">新增Banner</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button @click="updateConfig" type="primary">更新</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :xs="0" :sm="4" :md="4" :lg="4" :xl="4"> </el-col>
|
|
||||||
</el-row>
|
</el-row>
|
||||||
<div v-else style="width: 250px;text-align: center;margin: 40vh auto">
|
<div v-else style="width: 250px;text-align: center;margin: 40vh auto">
|
||||||
<el-input v-model="pwd" placeholder="请输入登录密码">
|
<el-input v-model="pwd" placeholder="请输入登录密码">
|
||||||
@@ -69,12 +156,25 @@
|
|||||||
return {
|
return {
|
||||||
login: false,
|
login: false,
|
||||||
pwd: '',
|
pwd: '',
|
||||||
files: [],
|
activeIndex: '2',
|
||||||
config: {
|
config: {
|
||||||
error_count: 0,
|
// enable_upload: true,
|
||||||
file_size: 0,
|
// max_days: 7,
|
||||||
admin_pwd: 'admin'
|
// error_count: 3,
|
||||||
}
|
// error_minute: 10,
|
||||||
|
// upload_count: 10,
|
||||||
|
// upload_minute: 10,
|
||||||
|
// delete_expire_files_interval: 10,
|
||||||
|
// admin_password: 'admin',
|
||||||
|
// file_size_limit: 10,
|
||||||
|
banners: []
|
||||||
|
},
|
||||||
|
files: [],
|
||||||
|
paginate: {
|
||||||
|
page: 1,
|
||||||
|
size: 10,
|
||||||
|
total: 0,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted: function () {
|
mounted: function () {
|
||||||
@@ -82,13 +182,55 @@
|
|||||||
if (pwd) {
|
if (pwd) {
|
||||||
login = true;
|
login = true;
|
||||||
this.pwd = pwd;
|
this.pwd = pwd;
|
||||||
|
axios.get('/config', {
|
||||||
|
headers: {
|
||||||
|
pwd: pwd
|
||||||
|
}
|
||||||
|
}).then(res => {
|
||||||
|
console.log(res.data.data)
|
||||||
|
this.config = res.data.data;
|
||||||
|
});
|
||||||
this.loginAdmin();
|
this.loginAdmin();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loginAdmin: function () {
|
updateSize: function (value) {
|
||||||
axios.post('', {}, {'headers': {'pwd': this.pwd}}).then(res => {
|
this.paginate.size = value;
|
||||||
this.files = res.data.data;
|
this.loginAdmin();
|
||||||
|
},
|
||||||
|
deleteBanner: function (index) {
|
||||||
|
this.config.banners.splice(index, 1);
|
||||||
|
},
|
||||||
|
addBanner: function () {
|
||||||
|
this.config.banners.push({
|
||||||
|
'url': '',
|
||||||
|
'src': '',
|
||||||
|
'text': '',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
updateConfig: function () {
|
||||||
|
axios.patch('', this.config, {
|
||||||
|
'headers': {
|
||||||
|
'pwd': this.pwd,
|
||||||
|
'Content-Type': 'multipart/json'
|
||||||
|
}
|
||||||
|
}).then(res => {
|
||||||
|
this.$message({
|
||||||
|
message: res.data.detail,
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
})
|
||||||
|
},
|
||||||
|
loginAdmin: function (current_page = 1) {
|
||||||
|
this.paginate.page = current_page;
|
||||||
|
axios.post('', this.paginate, {
|
||||||
|
'headers': {
|
||||||
|
'pwd': this.pwd,
|
||||||
|
'Content-Type': 'multipart/form-data'
|
||||||
|
}
|
||||||
|
}).then(res => {
|
||||||
|
this.paginate = res.data.paginate;
|
||||||
|
this.files = res.data.data
|
||||||
this.login = true;
|
this.login = true;
|
||||||
localStorage.setItem('pwd', this.pwd);
|
localStorage.setItem('pwd', this.pwd);
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
@@ -96,6 +238,9 @@
|
|||||||
this.$message({'message': e.response.data.detail, 'type': 'error'});
|
this.$message({'message': e.response.data.detail, 'type': 'error'});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
handleSelect(key, keyPath) {
|
||||||
|
this.activeIndex = key;
|
||||||
|
},
|
||||||
deleteFile: function (code) {
|
deleteFile: function (code) {
|
||||||
axios.delete('?code=' + code, {'headers': {'pwd': this.pwd}}).then(res => {
|
axios.delete('?code=' + code, {'headers': {'pwd': this.pwd}}).then(res => {
|
||||||
this.files = this.files.filter(item => item.code !== code)
|
this.files = this.files.filter(item => item.code !== code)
|
||||||
|
|||||||
@@ -93,8 +93,8 @@
|
|||||||
<div class="block" style="margin-bottom: 1rem">
|
<div class="block" style="margin-bottom: 1rem">
|
||||||
<el-carousel height="150px">
|
<el-carousel height="150px">
|
||||||
<el-carousel-item v-for="item in banners" :key="item">
|
<el-carousel-item v-for="item in banners" :key="item">
|
||||||
<a href="" target="_blank">
|
<a :href="item.url" target="_blank">
|
||||||
<img class="image" style="width: 400px" :src="item" alt="1">
|
<img class="image" style="width: 400px" :src="item.src" :alt="item.text">
|
||||||
</a>
|
</a>
|
||||||
</el-carousel-item>
|
</el-carousel-item>
|
||||||
</el-carousel>
|
</el-carousel>
|
||||||
|
|||||||
Reference in New Issue
Block a user