add:fronted
This commit is contained in:
+6
-4
@@ -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
@@ -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):
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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))
|
||||
)
|
||||
|
||||
Vendored
+5
@@ -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']
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -10,7 +10,7 @@ const instance = axios.create({
|
||||
});
|
||||
|
||||
const Request = axios.create({
|
||||
baseURL: "http://localhost:8000",
|
||||
baseURL: "http://localhost:12345",
|
||||
timeout: 6000000,
|
||||
});
|
||||
|
||||
|
||||
@@ -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.
@@ -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,
|
||||
app.include_router(
|
||||
share_api
|
||||
)
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user