test/custom-admin-ui #3

Merged
orion merged 673 commits from test/custom-admin-ui into master 2026-06-05 17:20:58 +08:00
14 changed files with 203 additions and 69 deletions
Showing only changes of commit 296d25bb45 - Show all commits
+6 -4
View File
@@ -9,16 +9,18 @@ from tortoise import fields
from tortoise.models import Model
from tortoise.contrib.pydantic import pydantic_model_creator
from core.utils import get_now
class FileCodes(Model):
id: Optional[int] = fields.IntField(pk=True)
code: Optional[int] = fields.CharField(description='分享码', max_length=255, index=True, unique=True)
prefix: Optional[str] = fields.CharField(max_length=255, description='前缀', default='')
suffix: Optional[str] = fields.CharField(max_length=255, description='后缀', default='')
uuid_file_name: Optional[str] = fields.CharField(max_length=255, description='uuid文件名')
file_path: Optional[str] = fields.CharField(max_length=255, description='文件路径')
uuid_file_name: Optional[str] = fields.CharField(max_length=255, description='uuid文件名', null=True)
file_path: Optional[str] = fields.CharField(max_length=255, description='文件路径', null=True)
size: Optional[int] = fields.IntField(description='文件大小', default=0)
text: Optional[str] = fields.TextField(description='文本内容', null=True)
expired_at: Optional[datetime] = fields.DatetimeField(null=True, description='过期时间')
expired_count: Optional[int] = fields.IntField(description='可用次数', default=0)
used_count: Optional[int] = fields.IntField(description='已用次数', default=0)
@@ -27,7 +29,7 @@ class FileCodes(Model):
async def is_expired(self):
if self.expired_at:
return self.expired_at < datetime.now()
return self.expired_at < await get_now()
else:
return self.expired_count != -1 and self.used_count >= self.expired_count
+1 -1
View File
@@ -30,7 +30,7 @@ async def get_file_path_name(file: UploadFile):
file_uuid = f"{uuid.uuid4().hex}"
uuid_file_name = f"{file_uuid}{suffix}"
save_path = f"{path}/{uuid_file_name}"
return path, suffix, prefix, uuid_file_name, file_uuid, save_path
return path, suffix, prefix, uuid_file_name, save_path
async def get_expire_info(expire_value: int, expire_style: str):
+91
View File
@@ -0,0 +1,91 @@
# @Time : 2023/8/14 03:59
# @Author : Lan
# @File : views.py
# @Software: PyCharm
from fastapi import APIRouter, Form, UploadFile, File
from pydantic import BaseModel
from apps.base.models import FileCodes
from apps.base.utils import get_expire_info, get_file_path_name
from core.storage import file_storage
share_api = APIRouter(
prefix='/share',
tags=['分享'],
)
@share_api.post('/text/')
async def share_text(text: str = Form(...), expire_value: int = Form(default=1, gt=0), expire_style: str = Form(default='day')):
expired_at, expired_count, used_count, code = await get_expire_info(expire_value, expire_style)
await FileCodes.create(
code=code,
text=text,
expired_at=expired_at,
expired_count=expired_count,
used_count=used_count,
size=len(text),
prefix='文本分享'
)
return {
'code': 200,
'msg': 'success',
'data': {
'code': code,
}
}
@share_api.post('/file/')
async def share_file(expire_value: int = Form(default=1, gt=0), expire_style: str = Form(default='day'), file: UploadFile = File(...)):
expired_at, expired_count, used_count, code = await get_expire_info(expire_value, expire_style)
path, suffix, prefix, uuid_file_name, save_path = await get_file_path_name(file)
await file_storage.save_file(file, save_path)
await FileCodes.create(
code=code,
prefix=prefix,
suffix=suffix,
uuid_file_name=uuid_file_name,
file_path=path,
size=file.size,
expired_at=expired_at,
expired_count=expired_count,
used_count=used_count,
)
return {
'code': 200,
'msg': 'success',
'data': {
'code': code,
'name': file.filename,
}
}
class SelectFileModel(BaseModel):
code: str
@share_api.post('/select/')
async def select_file(data: SelectFileModel):
file_code = await FileCodes.filter(code=data.code).first()
if not file_code:
return {
'code': 404,
'msg': '文件不存在',
}
if await file_code.is_expired():
return {
'code': 403,
'msg': '文件已过期',
}
return {
'code': 200,
'msg': 'success',
'data': {
'code': file_code.code,
'name': file_code.prefix + file_code.suffix,
'size': file_code.size,
'text': await file_storage.get_file_url(file_code),
}
}
+3 -1
View File
@@ -56,9 +56,11 @@ class S3FileStorage:
await s3.delete_object(Bucket=self.bucket_name, Key=await file_code.get_file_path())
async def get_file_url(self, file_code: FileCodes):
if file_code.prefix == '文本分享':
return file_code.text
async with self.session.client("s3", endpoint_url=self.endpoint_url) as s3:
result = await s3.generate_presigned_url('get_object', Params={'Bucket': self.bucket_name, 'Key': await file_code.get_file_path()}, ExpiresIn=3600)
return result
file_storage = SystemFileStorage()
file_storage = S3FileStorage()
+11
View File
@@ -2,6 +2,7 @@
# @Author : Lan
# @File : utils.py
# @Software: PyCharm
import datetime
import random
import string
@@ -23,3 +24,13 @@ async def get_random_string():
:return:
"""
return ''.join(random.choice(r_s) for _ in range(5))
async def get_now():
"""
获取当前时间
:return:
"""
return datetime.datetime.now(
datetime.timezone(datetime.timedelta(hours=8))
)
+5
View File
@@ -11,9 +11,11 @@ declare module 'vue' {
ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
ElCol: typeof import('element-plus/es')['ElCol']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput']
ElModal: typeof import('element-plus/es')['ElModal']
ElOption: typeof import('element-plus/es')['ElOption']
ElProgress: typeof import('element-plus/es')['ElProgress']
ElRadio: typeof import('element-plus/es')['ElRadio']
@@ -28,4 +30,7 @@ declare module 'vue' {
UploadFile: typeof import('./src/components/UploadFile.vue')['default']
UploadText: typeof import('./src/components/UploadText.vue')['default']
}
export interface ComponentCustomProperties {
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
}
}
+26 -6
View File
@@ -6,6 +6,14 @@ const fileStore = useFileDataStore();
const fileBoxStore = useFileBoxStore();
import QrcodeVue from "qrcode.vue";
import { useRoute } from "vue-router";
import { ref } from "vue";
const openUrl = (url: string) => {
if (url.startsWith('/')) {
url = window.location.origin + url;
}
window.open(url);
};
const route = useRoute();
const copyText = (text: any, style = 0) => {
if (style === 1) {
@@ -20,22 +28,34 @@ const copyText = (text: any, style = 0) => {
}
document.body.removeChild(temp);
};
const route = useRoute();
</script>
<template>
<el-drawer append-to-body v-model="fileBoxStore.showFileBox" direction="btt" style="max-width: 1080px;margin: auto;"
<el-drawer :append-to-body="true" v-model="fileBoxStore.showFileBox" direction="btt" style="max-width: 1080px;margin: auto;"
size="400">
<template #header>
<h4>文件箱</h4>
</template>
<template #default>
<div v-if="route.name=='home'">
<el-card v-for="i in fileStore.receiveData" :key="i">
{{ i }}
<div v-if="route.name=='home'" style="display: flex;flex-wrap: wrap;justify-content: center">
<el-card v-for="(value,index) in fileStore.receiveData" :key="index" style="margin: 0.5rem">
<template #header>
<el-button type="danger" @click="fileStore.deleteReceiveData(i)">删除</el-button>
<div style="display: flex;justify-content: space-between">
<h4 style="width: 6rem;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;">{{ value.name }}</h4>
<el-button size="small" type="danger" @click="fileStore.deleteReceiveData(index)">删除</el-button>
</div>
</template>
<div style="width: 200px;">
<div style="display: flex;justify-content: space-between">
<qrcode-vue :value="value.text" :size="100"></qrcode-vue>
<div style="display: flex;flex-direction: column;justify-content: space-around">
<el-tag size="large" style="cursor: pointer" @click="copyText(value.code)">{{ value.code }}</el-tag>
<el-tag v-if="value.name!=='文本分享'" size="large" type="success" style="cursor: pointer" @click="openUrl(value.text);">点击下载
</el-tag>
<el-tag v-else size="large" type="success" style="cursor: pointer" @click="copyText(value.text);">点击复制</el-tag>
</div>
</div>
</div>
</el-card>
</div>
<div v-else style="display: flex;flex-wrap: wrap;justify-content: center">
@@ -13,7 +13,6 @@ const props = defineProps({
return {
expireValue: 1,
expireStyle: 'day',
targetType: 'file',
}
}
}
@@ -39,7 +38,6 @@ const handleHttpRequest = (options: any) => {
formData.append('file', options.file);
formData.append('expireValue', props.shareData.expireValue);
formData.append('expireStyle', props.shareData.expireStyle);
formData.append('targetType', props.shareData.targetType);
request(
{
url: "share/file/",
@@ -122,7 +120,6 @@ onMounted(()=>{
<el-upload
class="upload-demo"
drag
action="http://127.0.0.1:8000/share"
multiple
:show-file-list="false"
ref="uploadBox"
@@ -13,7 +13,6 @@ const props = defineProps({
return {
expireValue: 1,
expireStyle: 'day',
targetType: 'text',
}
}
}
@@ -23,7 +22,6 @@ const handleSubmitShareText = ()=>{
formData.append('text', shareText.value);
formData.append('expireValue', props.shareData.expireValue);
formData.append('expireStyle', props.shareData.expireStyle);
formData.append('targetType', props.shareData.targetType);
request({
'url': 'share/text/',
'method': 'post',
+1 -1
View File
@@ -10,7 +10,7 @@ const instance = axios.create({
});
const Request = axios.create({
baseURL: "http://localhost:8000",
baseURL: "http://localhost:12345",
timeout: 6000000,
});
+54 -4
View File
@@ -1,15 +1,65 @@
<script setup lang="ts">
import { Upload, TakeawayBox } from '@element-plus/icons-vue';
import { ref } from 'vue'
import { useRouter } from "vue-router";
import { onMounted, reactive, ref, watch } from 'vue'
import { useRoute, useRouter } from "vue-router";
import CardTools from "@/components/CardTools.vue";
import { useFileBoxStore } from "@/stores/fileBox";
import { useFileDataStore } from "@/stores/fileData";
import { request } from "@/utils/request";
const fileBoxStore = useFileBoxStore();
const fileStore = useFileDataStore();
const router = useRouter()
const route = useRoute()
const code = ref('')
const input_status = reactive({
'readonly': false,
'loading': false,
})
onMounted(() => {
const query_code = route.query.code as string;
if (query_code) {
code.value = query_code
}
})
watch(code, (newVal) => {
if (newVal.length === 5) {
input_status.readonly = true;
input_status.loading = true;
request({
'url': '/share/select',
'method': 'POST',
'data': {
'code': newVal
}
}).then((res: any) => {
input_status.readonly = false;
input_status.loading = false;
code.value = '';
if (res.data.code === 200) {
fileBoxStore.showFileBox = true;
let flag = true;
fileStore.receiveData.forEach((file: any) => {
if (file.code === res.data.data.code) {
flag = false;
return;
}
});
if (flag) {
fileStore.addReceiveData(res.data.data);
}
} else {
alert(res.data.msg||'未知错误')
}
});
}
});
const listenInput = (num: number) => {
console.log('listenInput',num)
if (code.value.length < 5) {
code.value += num
}
};
</script>
<template>
@@ -18,7 +68,7 @@ const listenInput = (num: number) => {
<CardTools/>
<el-row style="text-align: center">
<el-col :span="24">
<el-input v-model="code" class="code-input" round autofocus clearable maxlength="5" placeholder="请输入五位取件码"/>
<el-input :readonly="input_status.readonly" v-loading="input_status.loading" v-model="code" class="code-input" round autofocus clearable maxlength="5" placeholder="请输入五位取件码"/>
</el-col>
<el-col :span=8 v-for="i in 9" :key="i">
<el-button class="key-button" round @click="listenInput(i)">{{ i }}</el-button>
Binary file not shown.
View File
+5 -47
View File
@@ -2,15 +2,10 @@
# @Author : Lan
# @File : main.py
# @Software: PyCharm
import random
from fastapi import FastAPI, UploadFile, File, Form
from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware
from tortoise.contrib.fastapi import register_tortoise
from apps.base.models import FileCodes
from apps.base.utils import get_file_path_name, get_expire_info
from core.storage import file_storage
from apps.base.views import share_api
app = FastAPI()
@@ -42,43 +37,6 @@ register_tortoise(
)
@app.post('/share/text/')
async def share_text(text: str = Form(...)):
return {
'code': 200,
'msg': 'success',
'data': {
'code': random.randint(100000, 999999),
'text': text,
'name': '文本分享',
}
}
@app.post('/share/file/')
async def share_file(expire_value: int = Form(default=1, gt=0), expire_style: str = Form(default='day'), file: UploadFile = File(...)):
expired_at, expired_count, used_count, code = await get_expire_info(expire_value, expire_style)
path, suffix, prefix, uuid_file_name, file_uuid, save_path = await get_file_path_name(file)
await file_storage.save_file(file, save_path)
file_code = await FileCodes.create(
code=code,
prefix=prefix,
suffix=suffix,
uuid_file_name=uuid_file_name,
file_path=path,
size=file.size,
expired_at=expired_at,
expired_count=expired_count,
used_count=used_count,
)
await file_storage.delete_file(file_code)
return {
'code': 200,
'msg': 'success',
'data': {
'code': code,
'text': '/share/{}'.format(random.randint(100000, 999999)),
'name': file.filename,
}
}
app.include_router(
share_api
)