first commit
This commit is contained in:
+33
@@ -0,0 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from "vue";
|
||||
import { request } from "@/utils/request";
|
||||
import { ElNotification } from 'element-plus'
|
||||
onMounted(() => {
|
||||
request({
|
||||
url:'/',
|
||||
method:'post'
|
||||
}).then((res:any)=>{
|
||||
if (res.code === 200) {
|
||||
localStorage.setItem('config', JSON.stringify(res.detail));
|
||||
if (res.detail.notify_title && res.detail.notify_content && localStorage.getItem('notify') !== res.detail.notify_title + res.detail.notify_content) {
|
||||
localStorage.setItem('notify', res.detail.notify_title + res.detail.notify_content);
|
||||
ElNotification({
|
||||
title: res.detail.notify_title,
|
||||
dangerouslyUseHTMLString: true,
|
||||
message: res.detail.notify_content,
|
||||
type: 'success'
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<RouterView />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -0,0 +1,86 @@
|
||||
/* color palette from <https://github.com/vuejs/theme> */
|
||||
:root {
|
||||
--vt-c-white: #ffffff;
|
||||
--vt-c-white-soft: #f5f5f5;
|
||||
--vt-c-white-mute: #f2f2f2;
|
||||
--vt-c-black: #181818;
|
||||
--vt-c-black-soft: #212121;
|
||||
--vt-c-black-mute: #282828;
|
||||
|
||||
--vt-c-indigo: #2c3e50;
|
||||
|
||||
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
||||
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
||||
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
||||
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
||||
|
||||
--vt-c-text-light-1: var(--vt-c-indigo);
|
||||
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
||||
--vt-c-text-dark-1: var(--vt-c-white);
|
||||
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
||||
}
|
||||
|
||||
/* semantic color variables for this project */
|
||||
:root {
|
||||
--color-background: var(--vt-c-white);
|
||||
--color-background-soft: var(--vt-c-white-soft);
|
||||
--color-background-mute: var(--vt-c-white-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-light-2);
|
||||
--color-border-hover: var(--vt-c-divider-light-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-light-1);
|
||||
--color-text: var(--vt-c-text-light-1);
|
||||
|
||||
--section-gap: 160px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-background: var(--vt-c-black);
|
||||
--color-background-soft: var(--vt-c-black-soft);
|
||||
--color-background-mute: var(--vt-c-black-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-dark-2);
|
||||
--color-border-hover: var(--vt-c-divider-dark-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-dark-1);
|
||||
--color-text: var(--vt-c-text-dark-2);
|
||||
}
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
}
|
||||
html{
|
||||
background: var(--vt-c-white-soft);
|
||||
}
|
||||
html.dark{
|
||||
background: var(--vt-c-black-soft);
|
||||
}
|
||||
body {
|
||||
color: var(--color-text);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
line-height: 1.6;
|
||||
font-family: Inter,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
Roboto,
|
||||
Oxygen,
|
||||
Ubuntu,
|
||||
Cantarell,
|
||||
'Fira Sans',
|
||||
'Droid Sans',
|
||||
'Helvetica Neue',
|
||||
sans-serif;
|
||||
font-size: 15px;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,206 @@
|
||||
html.dark {
|
||||
pre code.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 1em
|
||||
}
|
||||
|
||||
code.hljs {
|
||||
padding: 3px 5px
|
||||
}
|
||||
|
||||
.hljs {
|
||||
color: #abb2bf;
|
||||
background: #282c34
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-operator,
|
||||
.hljs-pattern-match {
|
||||
color: #f92672
|
||||
}
|
||||
|
||||
.hljs-function,
|
||||
.hljs-pattern-match .hljs-constructor {
|
||||
color: #61aeee
|
||||
}
|
||||
|
||||
.hljs-function .hljs-params {
|
||||
color: #a6e22e
|
||||
}
|
||||
|
||||
.hljs-function .hljs-params .hljs-typing {
|
||||
color: #fd971f
|
||||
}
|
||||
|
||||
.hljs-module-access .hljs-module {
|
||||
color: #7e57c2
|
||||
}
|
||||
|
||||
.hljs-constructor {
|
||||
color: #e2b93d
|
||||
}
|
||||
|
||||
.hljs-constructor .hljs-string {
|
||||
color: #9ccc65
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-quote {
|
||||
color: #b18eb1;
|
||||
font-style: italic
|
||||
}
|
||||
|
||||
.hljs-doctag,
|
||||
.hljs-formula {
|
||||
color: #c678dd
|
||||
}
|
||||
|
||||
.hljs-deletion,
|
||||
.hljs-name,
|
||||
.hljs-section,
|
||||
.hljs-selector-tag,
|
||||
.hljs-subst {
|
||||
color: #e06c75
|
||||
}
|
||||
|
||||
.hljs-literal {
|
||||
color: #56b6c2
|
||||
}
|
||||
|
||||
.hljs-addition,
|
||||
.hljs-attribute,
|
||||
.hljs-meta .hljs-string,
|
||||
.hljs-regexp,
|
||||
.hljs-string {
|
||||
color: #98c379
|
||||
}
|
||||
|
||||
.hljs-built_in,
|
||||
.hljs-class .hljs-title,
|
||||
.hljs-title.class_ {
|
||||
color: #e6c07b
|
||||
}
|
||||
|
||||
.hljs-attr,
|
||||
.hljs-number,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-class,
|
||||
.hljs-selector-pseudo,
|
||||
.hljs-template-variable,
|
||||
.hljs-type,
|
||||
.hljs-variable {
|
||||
color: #d19a66
|
||||
}
|
||||
|
||||
.hljs-bullet,
|
||||
.hljs-link,
|
||||
.hljs-meta,
|
||||
.hljs-selector-id,
|
||||
.hljs-symbol,
|
||||
.hljs-title {
|
||||
color: #61aeee
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: 700
|
||||
}
|
||||
|
||||
.hljs-link {
|
||||
text-decoration: underline
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
pre code.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 1em
|
||||
}
|
||||
|
||||
code.hljs {
|
||||
padding: 3px 5px;
|
||||
&::-webkit-scrollbar {
|
||||
height: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.hljs {
|
||||
color: #383a42;
|
||||
background: #fafafa
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-quote {
|
||||
color: #a0a1a7;
|
||||
font-style: italic
|
||||
}
|
||||
|
||||
.hljs-doctag,
|
||||
.hljs-formula,
|
||||
.hljs-keyword {
|
||||
color: #a626a4
|
||||
}
|
||||
|
||||
.hljs-deletion,
|
||||
.hljs-name,
|
||||
.hljs-section,
|
||||
.hljs-selector-tag,
|
||||
.hljs-subst {
|
||||
color: #e45649
|
||||
}
|
||||
|
||||
.hljs-literal {
|
||||
color: #0184bb
|
||||
}
|
||||
|
||||
.hljs-addition,
|
||||
.hljs-attribute,
|
||||
.hljs-meta .hljs-string,
|
||||
.hljs-regexp,
|
||||
.hljs-string {
|
||||
color: #50a14f
|
||||
}
|
||||
|
||||
.hljs-attr,
|
||||
.hljs-number,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-class,
|
||||
.hljs-selector-pseudo,
|
||||
.hljs-template-variable,
|
||||
.hljs-type,
|
||||
.hljs-variable {
|
||||
color: #986801
|
||||
}
|
||||
|
||||
.hljs-bullet,
|
||||
.hljs-link,
|
||||
.hljs-meta,
|
||||
.hljs-selector-id,
|
||||
.hljs-symbol,
|
||||
.hljs-title {
|
||||
color: #4078f2
|
||||
}
|
||||
|
||||
.hljs-built_in,
|
||||
.hljs-class .hljs-title,
|
||||
.hljs-title.class_ {
|
||||
color: #c18401
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: 700
|
||||
}
|
||||
|
||||
.hljs-link {
|
||||
text-decoration: underline
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
@import 'katex/dist/katex.min.css';
|
||||
@import '@/assets/code/highlight.scss';
|
||||
@import '@/assets/code/github-markdown.scss';
|
||||
@import '@/assets/code/markdown-add.scss';
|
||||
.markdown-body {
|
||||
box-sizing: border-box;
|
||||
background:none ;
|
||||
*{
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.markdown-body {
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: auto;
|
||||
max-width: 1080px;
|
||||
padding: 0 5px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.chat-messages {
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
border-radius: 10px 10px 0 0;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.chat-message {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.chat-message-bubble {
|
||||
padding: 8px;
|
||||
align-self: flex-start;
|
||||
color: #2f4f4f;
|
||||
border-radius: 8px 8px 8px 0;
|
||||
background: linear-gradient(to right, #c9d6ff, #e2e2e2); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
|
||||
box-shadow: 0 16px 20px 0 rgba(174, 167, 223, .06);
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
.chat-toolbar-icon{
|
||||
margin-left: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.chat-message-meta {
|
||||
display: flex;
|
||||
position: relative;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.n-input-wrapper {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.chat-message-nickname-and-time {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.is-me {
|
||||
justify-content: flex-end;
|
||||
|
||||
}
|
||||
|
||||
.n-input .n-input__suffix, .n-input .n-input__prefix {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.n-input .n-input-wrapper {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.n-input .n-input__suffix .n-button {
|
||||
border-radius: 8px !important;
|
||||
width: 80px;
|
||||
height: 30px;
|
||||
position: absolute; right: 10px; bottom: 10px;
|
||||
box-shadow: inset 0 1px 3px 0 rgba(0, 0, 0, 0.3);
|
||||
background-image: linear-gradient(-56deg, #0773ff 5%, #797eff);
|
||||
--n-border: none !important;
|
||||
}
|
||||
|
||||
.chat-message.is-me .chat-message-bubble {
|
||||
background: linear-gradient(to right, #56ccf2, #2f80ed); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
|
||||
border-radius: 8px 8px 0 !important;
|
||||
box-shadow: 0 16px 20px 0 rgba(174, 167, 223, .06);
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
.markdown-body {
|
||||
background-color: transparent;
|
||||
font-size: 14px;
|
||||
|
||||
p {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
pre code,
|
||||
pre tt {
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
// .highlight pre,
|
||||
// pre {
|
||||
// background-color: #fff;
|
||||
// }
|
||||
|
||||
code.hljs {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
&-wrapper {
|
||||
position: relative;
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
&-header {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
padding: 0 1rem;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
color: #b3b3b3;
|
||||
|
||||
&__copy {
|
||||
cursor: pointer;
|
||||
margin-left: 0.5rem;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
color: #65a665;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
html.dark {
|
||||
|
||||
.message-reply {
|
||||
.whitespace-pre-wrap {
|
||||
white-space: pre-wrap;
|
||||
color: var(--n-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
.highlight pre,
|
||||
pre {
|
||||
background-color: #282c34;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
@import './base.css';
|
||||
|
||||
#app {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.card {
|
||||
max-width: 400px;
|
||||
min-width: 398px;
|
||||
height: 100%;
|
||||
background-color: #F8FBFE;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
|
||||
border-radius: 20px !important;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tools {
|
||||
display: flex;
|
||||
z-index: 999;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
.box {
|
||||
display: inline-block;
|
||||
align-items: center;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
padding: 1px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.circle {
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.red {
|
||||
padding: 3px;
|
||||
background-color: #ff605c;
|
||||
}
|
||||
|
||||
.yellow {
|
||||
padding: 3px;
|
||||
background-color: #ffbd44;
|
||||
}
|
||||
|
||||
.green {
|
||||
background-color: #00ca4e;
|
||||
padding: 3px;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<script setup lang="ts">
|
||||
import { useDark, useToggle } from '@vueuse/core'
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const isDark = useDark()
|
||||
const toggleDark = useToggle(isDark)
|
||||
import { Moon, Sunny, Upload, Back, TakeawayBox } from "@element-plus/icons-vue";
|
||||
import FileBox from "@/components/FileBox.vue";
|
||||
import { useFileBoxStore } from "@/stores/fileBox";
|
||||
const fileBoxStore = useFileBoxStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="tools">
|
||||
<div class="circle">
|
||||
<el-icon size="17" color="#212121" class="red box" @click="router.push({'name':route.name=='home'?'send':'home'})">
|
||||
<Back v-if="route.name=='send'"></Back>
|
||||
<Upload v-else></Upload>
|
||||
</el-icon>
|
||||
</div>
|
||||
<div class="circle">
|
||||
<el-icon size="17" color="#212121" class="yellow box" @click="toggleDark(!isDark)">
|
||||
<Moon v-if="isDark"></Moon>
|
||||
<Sunny v-else></Sunny>
|
||||
</el-icon>
|
||||
</div>
|
||||
<div class="circle">
|
||||
<el-icon size="17" color="#212121" class="green box" @click="fileBoxStore.showFileBox=true">
|
||||
<TakeawayBox></TakeawayBox>
|
||||
</el-icon>
|
||||
</div>
|
||||
<file-box></file-box>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,151 @@
|
||||
<script setup lang="ts">
|
||||
import { useFileDataStore } from "@/stores/fileData";
|
||||
import { useFileBoxStore } from "@/stores/fileBox";
|
||||
import hljs from 'highlight.js';
|
||||
import mila from 'markdown-it-link-attributes';
|
||||
import markdownIt from 'markdown-it';
|
||||
import markdownItKatex from '@traptitech/markdown-it-katex';
|
||||
import '@/assets/code/main.scss'
|
||||
const fileStore = useFileDataStore();
|
||||
const fileBoxStore = useFileBoxStore();
|
||||
import QrcodeVue from "qrcode.vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { ref } from "vue";
|
||||
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
import { ElMessage } from "element-plus";
|
||||
const openUrl = (url: string) => {
|
||||
if (url.startsWith('/')) {
|
||||
url = window.location.origin + url;
|
||||
}
|
||||
window.open(url);
|
||||
};
|
||||
const route = useRoute();
|
||||
|
||||
const copyText = (text: any, style = 0) => {
|
||||
ElMessage.success(t('fileBox.copySuccess'));
|
||||
if (style === 1) {
|
||||
text = window.location.origin + '/#/?code=' + text;
|
||||
}
|
||||
const temp: any = document.createElement('textarea');
|
||||
temp.value = text;
|
||||
document.body.appendChild(temp);
|
||||
temp.select();
|
||||
if (document.execCommand('copy')) {
|
||||
document.execCommand('copy');
|
||||
}
|
||||
document.body.removeChild(temp);
|
||||
};
|
||||
const nowText = ref('');
|
||||
const showTextDetail = (text: any) => {
|
||||
showTextDetailVisible.value = true;
|
||||
nowText.value = text;
|
||||
};
|
||||
|
||||
const showTextDetailVisible = ref(false);
|
||||
|
||||
const md = new markdownIt({
|
||||
html: false,
|
||||
linkify: true,
|
||||
highlight(code: any, language: any) {
|
||||
const validLang = Boolean(language && hljs.getLanguage(language));
|
||||
return `<pre class="code-block-wrapper"><code class="hljs code-block-body ${validLang}">${hljs.highlight(code, { language: language ?? '' }).value}</code></pre>`;
|
||||
}
|
||||
});
|
||||
|
||||
md.use(mila, { attrs: { target: '_blank', rel: 'noopener' } });
|
||||
md.use(markdownItKatex, { blockClass: 'katexmath-block rounded-md p-[10px]', errorColor: ' #cc0000' });
|
||||
function renderMarkdown(message: string) {
|
||||
return md.render(message);
|
||||
}
|
||||
|
||||
function getQrCodeUrl(code: string) {
|
||||
// 否是接收的文件,如果是则返回下载直链,否则拼接分享链接
|
||||
if (code) {
|
||||
return code.toString().startsWith('/share/download') ? window.location.origin + code : window.location.origin + '/#/?code=' + code;
|
||||
}
|
||||
else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-drawer :append-to-body="true" v-model="fileBoxStore.showFileBox" direction="btt" style="max-width: 1080px;margin: auto;"
|
||||
size="400">
|
||||
<template #header>
|
||||
<h4 v-if="route.name=='home'">{{t('fileBox.receiveFileBox')}}</h4>
|
||||
<h4 v-else>{{t('fileBox.sendFileBox')}}</h4>
|
||||
</template>
|
||||
<template #default>
|
||||
<div v-if="route.name=='home'" style="display: flex;flex-wrap: wrap;justify-content: center">
|
||||
<el-dialog
|
||||
append-to-body
|
||||
align-center
|
||||
:title="t('fileBox.textDetail')"
|
||||
width="70%"
|
||||
style="height: 70%;overflow-y: scroll"
|
||||
v-model="showTextDetailVisible"
|
||||
>
|
||||
<div style="max-width: 100%;overflow-y: scroll" v-html="renderMarkdown(nowText)"></div>
|
||||
<template #footer>
|
||||
<el-button type="success" @click="copyText(nowText);showTextDetailVisible = false">{{t('fileBox.copy')}}</el-button>
|
||||
<el-button type="primary" @click="showTextDetailVisible = false">{{ t('fileBox.close') }}</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-card v-for="(value,index) in fileStore.receiveData" :key="index" style="margin: 0.5rem">
|
||||
<template #header>
|
||||
<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)">{{ t('fileBox.delete') }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<div style="width: 200px;">
|
||||
<div style="display: flex;justify-content: space-between">
|
||||
<!-- 下载二维码白色边框 -->
|
||||
<div v-if="value.name!=='Text'"
|
||||
style="padding: 5px; background-color: white; border: 2px solid white; box-sizing: border-box; height: 114px;">
|
||||
<qrcode-vue :value="getQrCodeUrl(value.text)" :size="100"></qrcode-vue>
|
||||
</div>
|
||||
<div v-else style="width: 100px;height: 100px;flex-wrap: wrap;overflow-y:scroll ">{{value.text}}</div>
|
||||
<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!=='Text'" size="large" type="success" style="cursor: pointer" @click="openUrl(value.text);">
|
||||
{{ t('fileBox.download') }}
|
||||
</el-tag>
|
||||
<el-tag v-else size="large" type="success" style="cursor: pointer" @click="showTextDetail(value.text);">{{ t('fileBox.detail') }}</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
<div v-else style="display: flex;flex-wrap: wrap;justify-content: center">
|
||||
<el-card v-for="(value,index) in fileStore.shareData" :key="index" style="margin: 0.5rem">
|
||||
<template #header>
|
||||
<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.deleteShareData(index)">{{ t('fileBox.delete') }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<div style="width: 200px;">
|
||||
<el-progress v-if="value.status!='success' && value.status!='fail'" striped :percentage="value.percentage" :text-inside="true"
|
||||
:stroke-width="20"></el-progress>
|
||||
<div v-else style="display: flex;justify-content: space-between">
|
||||
<!-- 分享二维码白色边框 -->
|
||||
<div style="padding: 5px; background-color: white; border: 2px solid white; box-sizing: border-box; height: 114px;">
|
||||
<qrcode-vue :value="getQrCodeUrl(value.code)" :size="100"></qrcode-vue>
|
||||
</div>
|
||||
<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 size="large" type="success" style="cursor: pointer" @click="copyText(value.code,1);">{{ t('fileBox.copyLink') }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
@@ -0,0 +1,183 @@
|
||||
<script setup lang="ts">
|
||||
import { UploadFilled } from '@element-plus/icons-vue'
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { request } from "@/utils/request";
|
||||
import { useFileDataStore } from "@/stores/fileData";
|
||||
import { useFileBoxStore } from "@/stores/fileBox";
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useConfigStore } from "@/stores/config";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
const { config } = useConfigStore();
|
||||
const { t } = useI18n()
|
||||
const fileBoxStore = useFileBoxStore();
|
||||
const fileStore = useFileDataStore();
|
||||
const props = defineProps({
|
||||
shareData: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
expire_value: 1,
|
||||
expire_style: 'day',
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const fileList: any = ref([])
|
||||
const uploadBox: any = ref(null)
|
||||
const handleOnChangeFileList = (file: any) => {
|
||||
fileStore.addShareData({
|
||||
'name': file.name,
|
||||
'text': '',
|
||||
'status': file.status,
|
||||
'percentage': 0,
|
||||
'size': file.size,
|
||||
'type': file.raw.type,
|
||||
'uid': file.uid,
|
||||
});
|
||||
};
|
||||
|
||||
const handleHttpRequest = (options: any) => {
|
||||
fileBoxStore.showFileBox = true;
|
||||
const formData = new FormData();
|
||||
if (config.openUpload === 0 && localStorage.getItem('adminPassword') === null) {
|
||||
fileStore.shareData.forEach((file: any) => {
|
||||
if (file.uid === options.file.uid) {
|
||||
ElMessage.error(t('msg.uploadClose'));
|
||||
file.status = 'fail';
|
||||
file.code = t('msg.fileUploadFail');
|
||||
fileStore.save();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (options.file.size > config.uploadSize) {
|
||||
fileStore.shareData.forEach((file: any) => {
|
||||
if (file.uid === options.file.uid) {
|
||||
ElMessage.error(t('msg.fileOverSize'));
|
||||
file.status = 'fail';
|
||||
file.code = t('msg.fileOverSize');
|
||||
fileStore.save();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
formData.append('file', options.file);
|
||||
formData.append('expire_value', props.shareData.expireValue);
|
||||
formData.append('expire_style', props.shareData.expireStyle);
|
||||
request(
|
||||
{
|
||||
url: "share/file/",
|
||||
method: "post",
|
||||
data: formData,
|
||||
onUploadProgress: (event: any) => {
|
||||
const percentage = Math.round((event.loaded * 100) / event.total) || 0;
|
||||
fileStore.shareData.forEach((file: any) => {
|
||||
if (file.uid === options.file.uid) {
|
||||
file.percentage = percentage;
|
||||
fileStore.save();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
).then((res: any) => {
|
||||
const data = res.detail;
|
||||
fileStore.shareData.forEach((file: any) => {
|
||||
if (file.uid === options.file.uid) {
|
||||
file.status = 'success';
|
||||
file.text = data.text;
|
||||
file.code = data.code;
|
||||
ElMessage.success(t('msg.fileUploadSuccess'));
|
||||
fileStore.save();
|
||||
}
|
||||
});
|
||||
}).catch(() => {
|
||||
fileStore.shareData.forEach((file: any) => {
|
||||
if (file.uid === options.file.uid) {
|
||||
file.status = 'fail';
|
||||
file.code = t('msg.fileUploadFail');
|
||||
ElMessage.error(t('msg.fileUploadFail'));
|
||||
fileStore.save();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
function pasteLister(event: any) {
|
||||
const items = event.clipboardData && event.clipboardData.items;
|
||||
if (items && items.length) {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (items[i].kind === 'string') {
|
||||
if (items[i].type.match(/^text\/plain/)) {
|
||||
items[i].getAsString(function(str:any) {
|
||||
console.log(str);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const file: any = items[i].getAsFile();
|
||||
if (file) {
|
||||
const uid = Date.now();
|
||||
file.uid = uid;
|
||||
fileStore.addShareData({
|
||||
'name': file.name,
|
||||
'text': '',
|
||||
'status': 'ready',
|
||||
'percentage': 0,
|
||||
'size': file.size,
|
||||
'type': file.type,
|
||||
'uid': uid,
|
||||
});
|
||||
handleHttpRequest({
|
||||
file: file,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
onUnmounted(()=>{
|
||||
// 清除剪切板事件
|
||||
document.removeEventListener('paste', pasteLister);
|
||||
})
|
||||
onMounted(()=>{
|
||||
document.addEventListener('paste', pasteLister);
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<el-upload
|
||||
class="upload-demo"
|
||||
drag
|
||||
multiple
|
||||
:show-file-list="false"
|
||||
ref="uploadBox"
|
||||
v-model:file-list="fileList"
|
||||
:on-change="handleOnChangeFileList"
|
||||
:http-request="handleHttpRequest"
|
||||
>
|
||||
<el-icon class="el-icon--upload">
|
||||
<upload-filled/>
|
||||
</el-icon>
|
||||
<div class="el-upload__text">
|
||||
{{t('send.prompt1')}}<em>{{t('send.clickUpload')}}</em>
|
||||
</div>
|
||||
<div class="el-upload__text" style="font-size: 10px;">{{t('send.prompt2')}}</div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.el-upload {
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.el-upload-dragger {
|
||||
box-shadow: 3px 3px 0 0 rgba(0, 0, 0, 0.2);
|
||||
border-radius: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,75 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { request } from "@/utils/request";
|
||||
const shareText = ref('')
|
||||
import { useFileDataStore } from "@/stores/fileData";
|
||||
import { useFileBoxStore } from "@/stores/fileBox";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useConfigStore } from "@/stores/config";
|
||||
|
||||
const { t } = useI18n()
|
||||
const {config} = useConfigStore();
|
||||
const fileBoxStore = useFileBoxStore();
|
||||
const fileStore = useFileDataStore();
|
||||
const props = defineProps({
|
||||
shareData: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
expire_value: 1,
|
||||
expire_style: 'day',
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
const handleSubmitShareText = ()=>{
|
||||
if (shareText.value === '') {
|
||||
ElMessage.warning(t('send.prompt3'));
|
||||
} else if(config.openUpload === 0 && localStorage.getItem('adminPassword') === null){
|
||||
ElMessage.error(t('msg.uploadClose'));
|
||||
} else {
|
||||
const formData = new FormData();
|
||||
formData.append('text', shareText.value);
|
||||
formData.append('expire_value', props.shareData.expireValue);
|
||||
formData.append('expire_style', props.shareData.expireStyle);
|
||||
request({
|
||||
'url': 'share/text/',
|
||||
'method': 'post',
|
||||
'data': formData,
|
||||
}).then((res: any) => {
|
||||
const data = res.detail;
|
||||
fileBoxStore.showFileBox = true;
|
||||
fileStore.addShareData({
|
||||
'name': t('send.textShare'),
|
||||
'text': data.text,
|
||||
'code': data.code,
|
||||
'status': 'success',
|
||||
'percentage': 100,
|
||||
'size': shareText.value.length,
|
||||
'type': 'text',
|
||||
'uid': Date.now(),
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="position: relative">
|
||||
<el-input
|
||||
:placeholder="t('send.prompt3')"
|
||||
v-model="shareText"
|
||||
type="textarea"
|
||||
:rows="9"
|
||||
:input-style="{'border-radius':'20px','border':'1px dashed var(--el-border-color)','box-shadow':'none'}"
|
||||
>
|
||||
</el-input>
|
||||
<el-button @click="handleSubmitShareText" style="position: absolute;right: 0;bottom: 0;border-radius: 20px 0 20px 0;margin: 1px;background: rgba(255,255,255,0.2)" size="large">{{t('send.share')}}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,140 @@
|
||||
export default {
|
||||
send: {
|
||||
disclaimers: 'Terms and Conditions',
|
||||
prompt1: 'Drag and drop text or files here, or ',
|
||||
prompt2: 'Days <7 or limited number of times (deleted after 24h)',
|
||||
prompt3: 'Please enter the text you want to send, supports MarkDown format',
|
||||
share: 'Share',
|
||||
textShare: 'Text Share',
|
||||
clickUpload: 'Click to Upload',
|
||||
pleaseInputExpireValue: 'Please enter the validity period',
|
||||
expireStyle: 'Expiration Method',
|
||||
expireData: {
|
||||
day: 'Days',
|
||||
hour: 'Hours',
|
||||
forever: 'Permanent',
|
||||
minute: 'Minutes',
|
||||
count: 'Times'
|
||||
},
|
||||
expireValue: {
|
||||
day: 'Days',
|
||||
hour: 'Hours',
|
||||
minute: 'Minutes',
|
||||
count: 'Times'
|
||||
},
|
||||
fileType: {
|
||||
file: 'File',
|
||||
text: 'Text'
|
||||
}
|
||||
},
|
||||
fileBox: {
|
||||
copySuccess: 'Copy Successful',
|
||||
inputNotEmpty: 'Please enter a five-digit retrieval code',
|
||||
sendFileBox: 'Outgoing Box',
|
||||
ok: 'OK',
|
||||
receiveFileBox: 'Incoming Box',
|
||||
textDetail: 'Text Details',
|
||||
copy: 'Copy',
|
||||
close: 'Close',
|
||||
delete: 'Delete',
|
||||
download: 'Click to Download',
|
||||
detail: 'View Details',
|
||||
copyLink: 'Copy Link',
|
||||
},
|
||||
admin: {
|
||||
about: {
|
||||
source1: 'This project is open-source on Github: ',
|
||||
source2: 'FileCodeBox'
|
||||
},
|
||||
settings: {
|
||||
name: 'Website Name',
|
||||
description: 'Website Description',
|
||||
uploadlimit: 'Upload Limit',
|
||||
explain: 'Interface Instructions',
|
||||
errorlimit: 'Error Limit',
|
||||
keywords: 'Keywords',
|
||||
themeSelect: 'Theme Selection',
|
||||
notify_title: 'Notification Title',
|
||||
notify_content: 'Notification Content',
|
||||
background: 'Background Image',
|
||||
max_save_seconds: 'Maximum Save Time',
|
||||
maxSaveSecondsNote: 'Maximum save time, unit: (seconds), default is 0 which means 7 days',
|
||||
admin_token: 'Admin Password',
|
||||
uploadSize: 'File Size',
|
||||
expireStyle: 'Expiration Method',
|
||||
uploadSizeNote: 'Maximum file size, unit: (Bytes), 1mb = 1 * 1024 * 1024',
|
||||
showAdmin: {
|
||||
open: 'Open',
|
||||
close: 'Close',
|
||||
note: 'Is the backend entrance displayed at the bottom of the page?',
|
||||
},
|
||||
openUpload: {
|
||||
title: 'Enable Upload',
|
||||
open: 'Enable Guest Upload',
|
||||
close: 'Disable Guest Upload',
|
||||
note: 'After disabling, you need to log in to the backend to upload',
|
||||
},
|
||||
file_storage: {
|
||||
title: 'Storage Engine',
|
||||
local: 'Local Storage',
|
||||
s3: 'S3 Storage',
|
||||
note: 'After updating, you need to restart FileCodeBox',
|
||||
},
|
||||
mei: 'Every',
|
||||
minute: 'Minutes',
|
||||
upload: 'Upload',
|
||||
files: 'Files',
|
||||
allow: 'Allow',
|
||||
errors: 'Errors',
|
||||
save: 'Save',
|
||||
saveSuccess: 'Save Successful',
|
||||
},
|
||||
fileView: {
|
||||
code: 'Retrieval Code',
|
||||
prefix: 'File Prefix',
|
||||
suffix: 'File Suffix',
|
||||
text: 'Text',
|
||||
used_count: 'Times Used',
|
||||
expired_count: 'Available Times',
|
||||
size: 'File Size',
|
||||
expired_at: 'Expiration Time',
|
||||
file_path: 'File Path',
|
||||
action: 'Action',
|
||||
delete: 'Delete',
|
||||
delete_success: 'Delete Successful',
|
||||
forever: 'Permanent',
|
||||
unlimited_count: 'Unlimited Times',
|
||||
download: 'Download',
|
||||
download_fail: 'File save failed, please try again later~',
|
||||
},
|
||||
menu: {
|
||||
fileManage: 'File Management',
|
||||
systemSetting: 'System Settings',
|
||||
about: 'About Us',
|
||||
color: 'Color Mode',
|
||||
send: 'Send',
|
||||
local:'Local File',
|
||||
receive: 'Receive',
|
||||
signout: 'Sign Out',
|
||||
},
|
||||
login: {
|
||||
managePassword: 'Admin Password',
|
||||
passwordNotEmpty: 'Password cannot be empty',
|
||||
login: 'Login',
|
||||
loginSuccess: 'Login Successful',
|
||||
loginError: 'Login Failed',
|
||||
},
|
||||
local: {
|
||||
Name: '文件',
|
||||
Expire: '过期',
|
||||
Cancel: '取消',
|
||||
Confirm: '确定',
|
||||
}
|
||||
},
|
||||
msg: {
|
||||
fileOverSize: 'File Too Large',
|
||||
fileUploadFail: 'Upload Failed',
|
||||
fileUploadSuccess: 'Upload Successful',
|
||||
uploadClose: 'Guest upload is closed on this site',
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,140 @@
|
||||
export default {
|
||||
send: {
|
||||
disclaimers: 'Términos y condiciones',
|
||||
prompt1: 'Arrastre, pegue el texto, archivos aquí, o ',
|
||||
prompt2: 'Días<7 o límite de veces (elimina después de 24h)',
|
||||
prompt3: 'Ingrese el texto que desea enviar, admite formato MarkDown',
|
||||
share: 'Compartir',
|
||||
textShare: 'Compartir texto',
|
||||
clickUpload: 'Hacer clic para subir',
|
||||
pleaseInputExpireValue: 'Ingrese el período de validez',
|
||||
expireStyle: 'Método de caducidad',
|
||||
expireData: {
|
||||
day: 'Días',
|
||||
hour: 'Horas',
|
||||
forever: 'Para siempre',
|
||||
minute: 'Minutos',
|
||||
count: 'Veces'
|
||||
},
|
||||
expireValue: {
|
||||
day: 'Días',
|
||||
hour: 'Horas',
|
||||
minute: 'Minutos',
|
||||
count: 'Veces'
|
||||
},
|
||||
fileType: {
|
||||
file: 'Archivo',
|
||||
text: 'Texto'
|
||||
}
|
||||
},
|
||||
fileBox: {
|
||||
copySuccess: 'Copiado exitosamente',
|
||||
inputNotEmpty: 'Ingrese un código de recogida de cinco dígitos',
|
||||
sendFileBox: 'Buzón de envíos',
|
||||
ok: 'Aceptar',
|
||||
receiveFileBox: 'Buzón de recepción',
|
||||
textDetail: 'Detalles del texto',
|
||||
copy: 'Copiar',
|
||||
close: 'Cerrar',
|
||||
delete: 'Eliminar',
|
||||
download: 'Hacer clic para descargar',
|
||||
detail: 'Ver detalles',
|
||||
copyLink: 'Copiar enlace',
|
||||
},
|
||||
admin: {
|
||||
about: {
|
||||
source1: 'Proyecto de código abierto en Github:',
|
||||
source2: 'FileCodeBox'
|
||||
},
|
||||
settings: {
|
||||
name: 'Nombre del sitio web',
|
||||
description: 'Descripción del sitio web',
|
||||
uploadlimit: 'Límite de subida',
|
||||
explain: 'Descripción de la interfaz',
|
||||
errorlimit: 'Límite de errores',
|
||||
keywords: 'Palabras clave',
|
||||
themeSelect: 'Selección de tema',
|
||||
notify_title: 'Título de notificación',
|
||||
notify_content: 'Contenido de notificación',
|
||||
background: 'Imagen de fondo',
|
||||
max_save_seconds: 'Tiempo máximo de almacenamiento',
|
||||
maxSaveSecondsNote: 'Tiempo máximo de almacenamiento, en segundos. El valor predeterminado es 0, que equivale a 7 días',
|
||||
admin_token: 'Contraseña de administración',
|
||||
uploadSize: 'Tamaño de archivo',
|
||||
expireStyle: 'Método de caducidad',
|
||||
uploadSizeNote: 'Tamaño máximo de archivo, en bytes. 1mb=1 * 1024 * 1024',
|
||||
openUpload: {
|
||||
title: 'Habilitar subida',
|
||||
open: 'Habilitar subida para visitantes',
|
||||
close: 'Deshabilitar subida para visitantes',
|
||||
note: 'Después de deshabilitar, es necesario iniciar sesión en el backend para poder subir archivos',
|
||||
},
|
||||
showAdmin: {
|
||||
open: 'abierto',
|
||||
close: 'cierre',
|
||||
note: 'si mostrar la entrada de fondo en la parte inferior de la página',
|
||||
},
|
||||
file_storage: {
|
||||
title: 'Motor de almacenamiento',
|
||||
local: 'Almacenamiento local',
|
||||
s3: 'Almacenamiento S3',
|
||||
note: 'Es necesario reiniciar FileCodeBox después de actualizar',
|
||||
},
|
||||
mei: 'Cada',
|
||||
minute: 'Minuto',
|
||||
upload: 'Subir',
|
||||
files: 'Archivos',
|
||||
allow: 'Permitir',
|
||||
errors: 'Errores',
|
||||
save: 'Guardar',
|
||||
saveSuccess: 'Guardado exitosamente',
|
||||
},
|
||||
fileView: {
|
||||
code: 'Código de recogida',
|
||||
prefix: 'Prefijo de archivo',
|
||||
suffix: 'Sufijo de archivo',
|
||||
text: 'Texto',
|
||||
used_count: 'Número de veces utilizado',
|
||||
expired_count: 'Número de veces disponible',
|
||||
size: 'Tamaño de archivo',
|
||||
expired_at: 'Fecha de caducidad',
|
||||
file_path: 'Ruta del archivo',
|
||||
action: 'Acción',
|
||||
delete: 'Eliminar',
|
||||
delete_success: 'Eliminado exitosamente',
|
||||
forever: 'Válido para siempre',
|
||||
unlimited_count: 'Número de veces ilimitado',
|
||||
download: 'Descargar',
|
||||
download_fail: 'Error al guardar el archivo, intente nuevamente más tarde~',
|
||||
},
|
||||
menu: {
|
||||
fileManage: 'Gestión de archivos',
|
||||
systemSetting: 'Configuración del sistema',
|
||||
about: 'Sobre nosotros',
|
||||
color: 'Modo de color',
|
||||
send: 'Enviar',
|
||||
local:'Archivo Local',
|
||||
receive: 'Recibir',
|
||||
signout: 'Cerrar sesión',
|
||||
},
|
||||
login: {
|
||||
managePassword: 'Contraseña de administración',
|
||||
passwordNotEmpty: 'La contraseña no puede estar vacía',
|
||||
login: 'Iniciar sesión',
|
||||
loginSuccess: 'Inicio de sesión exitoso',
|
||||
loginError: 'Error de inicio de sesión',
|
||||
},
|
||||
local: {
|
||||
Name: '文件',
|
||||
Expire: '过期',
|
||||
Cancel: '取消',
|
||||
Confirm: '确定',
|
||||
}
|
||||
},
|
||||
msg: {
|
||||
fileOverSize: 'Archivo demasiado grande',
|
||||
fileUploadFail: 'Error al subir',
|
||||
fileUploadSuccess: 'Subida exitosa',
|
||||
uploadClose: 'El sitio ha deshabilitado la subida para visitantes',
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
import { createI18n } from 'vue-i18n' //引入vue-i18n组件
|
||||
import messages from './index'
|
||||
let language = (
|
||||
navigator.language || 'zh_cn'
|
||||
).toLowerCase().replace(/-/, '_');
|
||||
if (['zh','zh_cn','en','es','zh_tw'].indexOf(language) === -1) {
|
||||
language = 'zh_cn'
|
||||
}
|
||||
const lang = (localStorage.getItem('language') || language);
|
||||
const i18n = createI18n({
|
||||
silentTranslationWarn: true,
|
||||
globalInjection: true,
|
||||
legacy: false,
|
||||
locale: lang,
|
||||
messages,
|
||||
});
|
||||
|
||||
export default i18n
|
||||
@@ -0,0 +1,13 @@
|
||||
import zh_cn from './zh_cn';
|
||||
import zh from './zh';
|
||||
import es from './es';
|
||||
import en from './en';
|
||||
import zh_tw from './zh_tw'
|
||||
|
||||
export default {
|
||||
zh_cn,
|
||||
en,
|
||||
zh_tw,
|
||||
zh,
|
||||
es
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
export default {
|
||||
send: {
|
||||
disclaimers:'使用须知',
|
||||
prompt1: '将文字、文件拖、粘贴到此处,或 ',
|
||||
prompt2: '天数<7或限制次数(24h后删除)',
|
||||
prompt3: '请输入您要寄出的文本,支持MarkDown格式',
|
||||
share: '分享',
|
||||
textShare: '文本分享',
|
||||
clickUpload: '点击上传',
|
||||
pleaseInputExpireValue: '请输入有效期',
|
||||
expireStyle: '过期方式',
|
||||
expireData: {
|
||||
day: '天数',
|
||||
hour: '小时',
|
||||
forever: '永久',
|
||||
minute: '分钟',
|
||||
count: '次数'
|
||||
},
|
||||
expireValue: {
|
||||
day: '天',
|
||||
hour: '时',
|
||||
minute: '分',
|
||||
count: '次'
|
||||
},
|
||||
fileType: {
|
||||
file: '文件',
|
||||
text: '文本'
|
||||
}
|
||||
},
|
||||
fileBox: {
|
||||
copySuccess: '复制成功',
|
||||
inputNotEmpty: '请输入五位取件码',
|
||||
sendFileBox: '寄件箱',
|
||||
ok:'确定',
|
||||
receiveFileBox: '收件箱',
|
||||
textDetail: '文本详情',
|
||||
copy: '复 制',
|
||||
close: '关 闭',
|
||||
delete: '删 除',
|
||||
download: '点 击 下 载',
|
||||
detail: '查 看 详 情',
|
||||
copyLink: '复制链接',
|
||||
},
|
||||
admin: {
|
||||
about: {
|
||||
source1: '本项目开源于Github:',
|
||||
source2: 'FileCodeBox'
|
||||
},
|
||||
settings: {
|
||||
name: '网站名称',
|
||||
description: '网站描述',
|
||||
uploadlimit: '上传限制',
|
||||
explain:'界面说明',
|
||||
errorlimit: '错误限制',
|
||||
keywords: '关键词',
|
||||
themeSelect: '主题选择',
|
||||
notify_title: '通知标题',
|
||||
notify_content: '通知内容',
|
||||
background: '背景图片',
|
||||
max_save_seconds: '最长保存',
|
||||
maxSaveSecondsNote: '最长保存时间,单位:(秒),默认为0则为7天',
|
||||
admin_token: '管理密码',
|
||||
uploadSize: '文件大小',
|
||||
expireStyle: '过期方式',
|
||||
uploadSizeNote: '最大文件大小,单位:(Bytes),1mb=1 * 1024 * 1024',
|
||||
openUpload: {
|
||||
title: '开启上传',
|
||||
open: '开启游客上传',
|
||||
close: '关闭游客上传',
|
||||
note: '关闭之后需要登录后台方可上传',
|
||||
},
|
||||
showAdmin:{
|
||||
open: '开启',
|
||||
close: '关闭',
|
||||
note: '是否在页面底部显示后台入口',
|
||||
},
|
||||
file_storage: {
|
||||
title: '存储引擎',
|
||||
local: '本地存储',
|
||||
s3: 'S3存储',
|
||||
note: '更新后需要重启FileCodeBox',
|
||||
},
|
||||
mei: '每',
|
||||
minute: '分钟',
|
||||
upload: '上传',
|
||||
files: '个文件',
|
||||
allow: '允许',
|
||||
errors: '次错误',
|
||||
save: '保存',
|
||||
saveSuccess: '保存成功',
|
||||
},
|
||||
fileView: {
|
||||
code: '取件码',
|
||||
prefix: '文件前缀',
|
||||
suffix: '文件后缀',
|
||||
text: '文本',
|
||||
used_count: '已使用次数',
|
||||
expired_count: '可用次数',
|
||||
size: '文件大小',
|
||||
expired_at: '过期时间',
|
||||
file_path: '文件路径',
|
||||
action: '操作',
|
||||
delete: '删除',
|
||||
delete_success: '删除成功',
|
||||
forever: '永久有效',
|
||||
unlimited_count: '不限次数',
|
||||
download: '下载',
|
||||
download_fail: '文件保存失败,请稍后再试~',
|
||||
},
|
||||
menu: {
|
||||
fileManage: '文件管理',
|
||||
systemSetting: '系统设置',
|
||||
about: '关于我们',
|
||||
color: '颜色模式',
|
||||
local:'本地文件',
|
||||
send: '寄件',
|
||||
receive: '收件',
|
||||
signout: '退出登录',
|
||||
},
|
||||
login: {
|
||||
managePassword: '管理密码',
|
||||
passwordNotEmpty: '密码不能为空',
|
||||
login: '登 录',
|
||||
loginSuccess: '登录成功',
|
||||
loginError: '登录失败',
|
||||
},
|
||||
local: {
|
||||
Name: '文件',
|
||||
Expire: '过期',
|
||||
Cancel: '取消',
|
||||
Confirm: '确定',
|
||||
}
|
||||
},
|
||||
msg:{
|
||||
fileOverSize: '文件过大',
|
||||
fileUploadFail: '上传失败',
|
||||
fileUploadSuccess: '上传成功',
|
||||
uploadClose: '本站已关闭游客上传',
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,142 @@
|
||||
export default {
|
||||
send: {
|
||||
disclaimers:'使用须知',
|
||||
prompt1: '将文字、文件拖、粘贴到此处,或 ',
|
||||
prompt2: '天数<7或限制次数(24h后删除)',
|
||||
prompt3: '请输入您要寄出的文本,支持MarkDown格式',
|
||||
share: '分享',
|
||||
textShare: '文本分享',
|
||||
clickUpload: '点击上传',
|
||||
pleaseInputExpireValue: '请输入有效期',
|
||||
expireStyle: '过期方式',
|
||||
expireData: {
|
||||
day: '天数',
|
||||
hour: '小时',
|
||||
forever: '永久',
|
||||
minute: '分钟',
|
||||
count: '次数'
|
||||
},
|
||||
expireValue: {
|
||||
day: '天',
|
||||
hour: '时',
|
||||
minute: '分',
|
||||
count: '次'
|
||||
},
|
||||
fileType: {
|
||||
file: '文件',
|
||||
text: '文本'
|
||||
}
|
||||
},
|
||||
fileBox: {
|
||||
copySuccess: '复制成功',
|
||||
inputNotEmpty: '请输入五位取件码',
|
||||
sendFileBox: '寄件箱',
|
||||
ok:'确定',
|
||||
receiveFileBox: '收件箱',
|
||||
textDetail: '文本详情',
|
||||
copy: '复 制',
|
||||
close: '关 闭',
|
||||
delete: '删 除',
|
||||
download: '点 击 下 载',
|
||||
detail: '查 看 详 情',
|
||||
copyLink: '复制链接',
|
||||
},
|
||||
admin: {
|
||||
about: {
|
||||
source1: '本项目开源于Github:',
|
||||
source2: 'FileCodeBox'
|
||||
},
|
||||
settings: {
|
||||
name: '网站名称',
|
||||
description: '网站描述',
|
||||
uploadlimit: '上传限制',
|
||||
explain:'界面说明',
|
||||
errorlimit: '错误限制',
|
||||
keywords: '关键词',
|
||||
themeSelect: '主题选择',
|
||||
notify_title: '通知标题',
|
||||
notify_content: '通知内容',
|
||||
background: '背景图片',
|
||||
max_save_seconds: '最长保存',
|
||||
maxSaveSecondsNote: '最长保存时间,单位:(秒),默认为0则为7天',
|
||||
admin_token: '管理密码',
|
||||
uploadSize: '文件大小',
|
||||
expireStyle: '过期方式',
|
||||
showAdminAddr: '显示后台',
|
||||
showAdmin:{
|
||||
open: '开启',
|
||||
close: '关闭',
|
||||
note: '是否在页面底部显示后台入口',
|
||||
},
|
||||
robotsText: 'robots.txt',
|
||||
uploadSizeNote: '最大文件大小,单位:(Bytes),1mb=1 * 1024 * 1024',
|
||||
openUpload: {
|
||||
title: '开启上传',
|
||||
open: '开启游客上传',
|
||||
close: '关闭游客上传',
|
||||
note: '关闭之后需要登录后台方可上传',
|
||||
},
|
||||
file_storage: {
|
||||
title: '存储引擎',
|
||||
local: '本地存储',
|
||||
s3: 'S3存储',
|
||||
note: '更新后需要重启FileCodeBox',
|
||||
},
|
||||
mei: '每',
|
||||
minute: '分钟',
|
||||
upload: '上传',
|
||||
files: '个文件',
|
||||
allow: '允许',
|
||||
errors: '次错误',
|
||||
save: '保存',
|
||||
saveSuccess: '保存成功',
|
||||
},
|
||||
fileView: {
|
||||
code: '取件码',
|
||||
prefix: '文件前缀',
|
||||
suffix: '文件后缀',
|
||||
text: '文本',
|
||||
used_count: '已使用次数',
|
||||
expired_count: '可用次数',
|
||||
size: '文件大小',
|
||||
expired_at: '过期时间',
|
||||
file_path: '文件路径',
|
||||
action: '操作',
|
||||
delete: '删除',
|
||||
delete_success: '删除成功',
|
||||
forever: '永久有效',
|
||||
unlimited_count: '不限次数',
|
||||
download: '下载',
|
||||
download_fail: '文件保存失败,请稍后再试~',
|
||||
},
|
||||
menu: {
|
||||
fileManage: '文件管理',
|
||||
systemSetting: '系统设置',
|
||||
about: '关于我们',
|
||||
local:'本地文件',
|
||||
color: '颜色模式',
|
||||
send: '寄件',
|
||||
receive: '收件',
|
||||
signout: '退出登录',
|
||||
},
|
||||
login: {
|
||||
managePassword: '管理密码',
|
||||
passwordNotEmpty: '密码不能为空',
|
||||
login: '登 录',
|
||||
loginSuccess: '登录成功',
|
||||
loginError: '登录失败',
|
||||
},
|
||||
local: {
|
||||
Name: '文件',
|
||||
Expire: '过期',
|
||||
Cancel: '取消',
|
||||
Confirm: '确定',
|
||||
}
|
||||
},
|
||||
msg:{
|
||||
fileOverSize: '文件过大',
|
||||
fileUploadFail: '上传失败',
|
||||
fileUploadSuccess: '上传成功',
|
||||
uploadClose: '本站已关闭游客上传',
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,139 @@
|
||||
export default {
|
||||
send: {
|
||||
disclaimers:'使用須知',
|
||||
prompt1: '將文字、文件拖、貼到此處,或 ',
|
||||
prompt2: '天數<7或限制次數(24h後刪除)',
|
||||
prompt3: '請輸入您要寄出的文本,支持MarkDown格式',
|
||||
share: '分享',
|
||||
textShare: '文本分享',
|
||||
clickUpload: '點擊上傳',
|
||||
pleaseInputExpireValue: '請輸入有效期',
|
||||
expireStyle: '過期方式',
|
||||
expireData: {
|
||||
day: '天數',
|
||||
hour: '小時',
|
||||
forever: '永久',
|
||||
minute: '分鐘',
|
||||
count: '次數'
|
||||
},
|
||||
expireValue: {
|
||||
day: '天',
|
||||
hour: '時',
|
||||
minute: '分',
|
||||
count: '次'
|
||||
},
|
||||
fileType: {
|
||||
file: '文件',
|
||||
text: '文本'
|
||||
}
|
||||
},
|
||||
fileBox: {
|
||||
copySuccess: '複製成功',
|
||||
inputNotEmpty: '請輸入五位取件碼',
|
||||
sendFileBox: '寄件箱',
|
||||
ok:'確定',
|
||||
receiveFileBox: '收件箱',
|
||||
textDetail: '文本詳情',
|
||||
copy: '複 製',
|
||||
close: '關 閉',
|
||||
delete: '刪 除',
|
||||
download: '點 擊 下 載',
|
||||
detail: '查 看 詳 情',
|
||||
copyLink: '複製連結',
|
||||
},
|
||||
admin: {
|
||||
about: {
|
||||
source1: '本項目開源於Github:',
|
||||
source2: 'FileCodeBox'
|
||||
},
|
||||
settings: {
|
||||
name: '網站名稱',
|
||||
description: '網站描述',
|
||||
uploadlimit: '上傳限制',
|
||||
explain: '界面說明',
|
||||
errorlimit: '錯誤限制',
|
||||
keywords: '關鍵詞',
|
||||
themeSelect: 'Theme選擇',
|
||||
notify_title: '通知標題',
|
||||
notify_content: '通知內容',
|
||||
background: '背景圖片',
|
||||
max_save_seconds: '最長保存',
|
||||
maxSaveSecondsNote: '最長保存時間,單位:(秒),默認為0則為7天',
|
||||
admin_token: '管理密碼',
|
||||
uploadSize: '文件大小',
|
||||
expireStyle: '過期方式',
|
||||
uploadSizeNote: '最大文件大小,單位:(Bytes),1mb=1 * 1024 * 1024',
|
||||
showAdmin: {
|
||||
open: '開啟',
|
||||
close: '關閉',
|
||||
note: '是否在頁面底部顯示後臺入口',
|
||||
},
|
||||
openUpload: {
|
||||
title: '開啟上傳',
|
||||
open: '開啟遊客上傳',
|
||||
close: '關閉遊客上傳',
|
||||
note: '關閉之後需要登錄後臺方可上傳',
|
||||
},
|
||||
file_storage: {
|
||||
title: '存儲引擎',
|
||||
local: '本地存儲',
|
||||
s3: 'S3存儲',
|
||||
note: '更新後需要重啟FileCodeBox',
|
||||
},
|
||||
mei: '每',
|
||||
minute: '分鐘',
|
||||
upload: '上傳',
|
||||
files: '個文件',
|
||||
allow: '允許',
|
||||
errors: '次錯誤',
|
||||
save: '保存',
|
||||
saveSuccess: '保存成功',
|
||||
},
|
||||
fileView: {
|
||||
code: '取件碼',
|
||||
prefix: '文件前綴',
|
||||
suffix: '文件後綴',
|
||||
text: '文本',
|
||||
used_count: '已使用次數',
|
||||
expired_count: '可用次數',
|
||||
size: '文件大小',
|
||||
expired_at: '過期時間',
|
||||
file_path: '文件路徑',
|
||||
action: '操作',
|
||||
delete: '刪除',
|
||||
delete_success: '刪除成功',
|
||||
forever: '永久有效',
|
||||
unlimited_count: '不限次數',
|
||||
download: '下載',
|
||||
download_fail: '文件保存失敗,請稍後再試~',
|
||||
},
|
||||
menu: {
|
||||
fileManage: '文件管理',
|
||||
systemSetting: '系統設置',
|
||||
about: '關於我們',
|
||||
color: '顏色模式',
|
||||
send: '寄件',
|
||||
local:'本地文件',
|
||||
receive: '收件',
|
||||
signout: '退出登錄',
|
||||
},
|
||||
login: {
|
||||
managePassword: '管理密碼',
|
||||
passwordNotEmpty: '密碼不能為空',
|
||||
login: '登 錄',
|
||||
loginSuccess: '登錄成功',
|
||||
loginError: '登錄失敗',
|
||||
},local: {
|
||||
Name: '文件',
|
||||
Expire: '過期',
|
||||
Cancel: '取消',
|
||||
Confirm: '確定',
|
||||
},
|
||||
},
|
||||
msg:{
|
||||
fileOverSize: '文件過大',
|
||||
fileUploadFail: '上傳失敗',
|
||||
fileUploadSuccess: '上傳成功',
|
||||
uploadClose: '本站已關閉遊客上傳',
|
||||
}
|
||||
};
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import './assets/main.css'
|
||||
import 'element-plus/dist/index.css'
|
||||
import 'element-plus/theme-chalk/dark/css-vars.css'
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import i18n from "./locals/i18n";
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(createPinia())
|
||||
app.use(i18n)
|
||||
app.use(router)
|
||||
app.mount('#app')
|
||||
@@ -0,0 +1,46 @@
|
||||
import { createRouter, createWebHashHistory } from 'vue-router';
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: () => import('@/views/Share/HomeView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/send',
|
||||
name: 'send',
|
||||
component: () => import('@/views/Share/SendView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/admin',
|
||||
name: 'admin',
|
||||
component: () => import('@/views/Admin/AdminView.vue'),
|
||||
children:[
|
||||
{
|
||||
path: '',
|
||||
name: 'file',
|
||||
component: () => import('@/views/Admin/FileView.vue'),
|
||||
},
|
||||
{
|
||||
path: 'setting',
|
||||
name: 'setting',
|
||||
component: () => import('@/views/Admin/SettingView.vue'),
|
||||
},
|
||||
{
|
||||
path: 'local',
|
||||
name: 'local',
|
||||
component: () => import('@/views/Admin/LocalView.vue'),
|
||||
},
|
||||
{
|
||||
path: 'about',
|
||||
name: 'about',
|
||||
component: () => import('@/views/Admin/AboutView.vue'),
|
||||
},
|
||||
]
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,13 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import {ref} from "vue";
|
||||
|
||||
export const useAdminData = defineStore('adminData', () => {
|
||||
const adminPassword = ref(localStorage.getItem('adminPassword') || '');
|
||||
const isAdmin = ref(!!localStorage.getItem('adminPassword'));
|
||||
function updateAdminPwd(pwd: string) {
|
||||
adminPassword.value = pwd;
|
||||
isAdmin.value = true;
|
||||
localStorage.setItem('adminPassword', pwd);
|
||||
}
|
||||
return { adminPassword,updateAdminPwd,isAdmin };
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import {ref} from "vue";
|
||||
|
||||
export const useConfigStore = defineStore('config', () => {
|
||||
const config:any = ref(JSON.parse(localStorage.getItem('config') || '{}') || {}); // 配置
|
||||
return { config };
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import {ref} from "vue";
|
||||
|
||||
export const useFileBoxStore = defineStore('fileBox', () => {
|
||||
const showFileBox = ref(false);
|
||||
return { showFileBox };
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import {reactive} from "vue";
|
||||
|
||||
export const useFileDataStore = defineStore('fileData', () => {
|
||||
const receiveData = reactive(JSON.parse(localStorage.getItem('receiveData')||'[]') || []); // 接收的数据
|
||||
const shareData = reactive(JSON.parse(localStorage.getItem('shareData')||'[]') || []); // 接收的数据
|
||||
function save() {
|
||||
localStorage.setItem('receiveData', JSON.stringify(receiveData));
|
||||
localStorage.setItem('shareData', JSON.stringify(shareData));
|
||||
}
|
||||
function addReceiveData(data:any) {
|
||||
receiveData.unshift(data);
|
||||
save();
|
||||
}
|
||||
|
||||
function addShareData(data:any) {
|
||||
shareData.unshift(data);
|
||||
save();
|
||||
}
|
||||
|
||||
function deleteReceiveData(index: number) {
|
||||
receiveData.splice(index, 1);
|
||||
save();
|
||||
}
|
||||
|
||||
function deleteShareData(index: number) {
|
||||
shareData.splice(index, 1);
|
||||
save();
|
||||
}
|
||||
return { receiveData, shareData, save, addShareData, addReceiveData, deleteReceiveData, deleteShareData };
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
// @ts-ignore
|
||||
import axios from "axios";
|
||||
import { ElMessage } from "element-plus";
|
||||
const instance = axios.create({
|
||||
baseURL: import.meta.env.DEV ? "http://localhost:12345" : "/",
|
||||
timeout: 6000000,
|
||||
});
|
||||
instance.interceptors.request.use(
|
||||
(config: any) => {
|
||||
config.headers= {
|
||||
'Authorization': 'Bearer '+ localStorage.getItem('adminPassword') || '',
|
||||
}
|
||||
return config;
|
||||
});
|
||||
instance.interceptors.response.use(
|
||||
(response:any) => {
|
||||
if (response.status === 200 && response.config.url === '/admin/file/download') {
|
||||
return response;
|
||||
}
|
||||
if (response.data.code === 200) {
|
||||
return response.data;
|
||||
} else {
|
||||
ElMessage.error(response.data.detail);
|
||||
return Promise.reject(response.data);
|
||||
}
|
||||
}, (error:any) => {
|
||||
localStorage.clear()
|
||||
ElMessage.error(error.response.data.detail);
|
||||
return Promise.reject(error);
|
||||
});
|
||||
|
||||
export const request = instance;
|
||||
@@ -0,0 +1,10 @@
|
||||
export function formatTimestamp(timestamp: string): string {
|
||||
const date = new Date(timestamp);
|
||||
const year = date.getFullYear();
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
||||
const day = date.getDate().toString().padStart(2, '0');
|
||||
const hours = date.getHours().toString().padStart(2, '0');
|
||||
const minutes = date.getMinutes().toString().padStart(2, '0');
|
||||
const seconds = date.getSeconds().toString().padStart(2, '0');
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<main style="text-align: center">
|
||||
<span>{{ t('admin.about.source1') }}
|
||||
<em>
|
||||
<a style="color: #333" href="https://github.com/vastsa/FileCodeBox">{{ t('admin.about.source2') }}</a>
|
||||
</em>
|
||||
</span>
|
||||
</main>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<el-container v-if="adminData.isAdmin" style="height: 100vh;width: 100vw;position: relative;user-select: none">
|
||||
<el-header>
|
||||
<el-menu mode="horizontal" router :default-active="route.path">
|
||||
<el-menu-item v-for="menu in menus" :index="menu.path" :key="menu.path">{{menu.name}}</el-menu-item>
|
||||
<el-menu-item style="float: right" @click="toggleDark(!isDark)">{{ t('admin.menu.color') }}</el-menu-item>
|
||||
<el-menu-item style="float: right" @click="logout()">{{ t('admin.menu.signout') }}</el-menu-item>
|
||||
</el-menu>
|
||||
</el-header>
|
||||
<el-main>
|
||||
<router-view/>
|
||||
</el-main>
|
||||
</el-container>
|
||||
<el-form size="large" v-else>
|
||||
<el-form-item :label="t('admin.login.managePassword')">
|
||||
<el-input
|
||||
v-model="adminData.adminPassword"
|
||||
:placeholder="t('admin.login.passwordNotEmpty')"
|
||||
type="password"
|
||||
>
|
||||
<template #append>
|
||||
<el-button @click="refreshLoginStatus">{{ t('admin.login.login') }}</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { useDark, useToggle } from '@vueuse/core';
|
||||
import { ref } from "vue";
|
||||
const isDark = useDark()
|
||||
const toggleDark = useToggle(isDark)
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useAdminData } from "@/stores/adminData";
|
||||
import { request } from "@/utils/request";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const adminData = useAdminData();
|
||||
const route = useRoute();
|
||||
const menus = ref([
|
||||
{
|
||||
name: t('admin.menu.fileManage'),
|
||||
path: '/admin',
|
||||
},
|
||||
{
|
||||
name: t('admin.menu.systemSetting'),
|
||||
path: '/admin/setting',
|
||||
},
|
||||
{
|
||||
name: t('admin.menu.local'),
|
||||
path: '/admin/local',
|
||||
},
|
||||
{
|
||||
name: t('admin.menu.about'),
|
||||
path: '/admin/about',
|
||||
},
|
||||
{
|
||||
name: t('admin.menu.send'),
|
||||
path: '/#/send',
|
||||
},
|
||||
{
|
||||
name: t('admin.menu.receive'),
|
||||
path: '/#/',
|
||||
},
|
||||
]);
|
||||
const refreshLoginStatus = () => {
|
||||
request({
|
||||
url: '/admin/login',
|
||||
method: 'post',
|
||||
data: {
|
||||
password: adminData.adminPassword,
|
||||
},
|
||||
}).then((res: any) => {
|
||||
if (res.code === 200) {
|
||||
adminData.updateAdminPwd(res.detail.token);
|
||||
ElMessage.success(t('admin.login.loginSuccess'));
|
||||
} else {
|
||||
localStorage.clear();
|
||||
ElMessage.error(t('admin.login.loginError'));
|
||||
}
|
||||
});
|
||||
};
|
||||
const logout = () => {
|
||||
localStorage.clear();
|
||||
};
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<main>
|
||||
<el-table size="large" stripe :data="tableData" style="width: 100%">
|
||||
<el-table-column prop="code" :label="t('admin.fileView.code')" />
|
||||
<el-table-column prop="prefix" :label="t('admin.fileView.prefix')" />
|
||||
<el-table-column prop="suffix" :label="t('admin.fileView.suffix')" />
|
||||
<el-table-column prop="text" :label="t('admin.fileView.text')">
|
||||
<template #default="scope">
|
||||
<span style="width: 6rem;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;">{{ scope.row.text }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="used_count" :label="t('admin.fileView.used_count')" />
|
||||
<el-table-column prop="expired_count" :label="t('admin.fileView.expired_count')">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.expired_count > -1 ? scope.row.expired_count : t('admin.fileView.unlimited_count') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="size" :label="t('admin.fileView.size')">
|
||||
<template #default="scope">
|
||||
<span>{{ Math.round(scope.row.size/1024/1024*100)/100 }}MB</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="expired_at" :label="t('admin.fileView.expired_at')">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.expired_at ? formatTimestamp(scope.row.expired_at) : t('admin.fileView.forever') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="file_path" :label="t('admin.fileView.file_path')" />
|
||||
<el-table-column>
|
||||
<template #header>
|
||||
{{ t('admin.fileView.action')}}
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<el-button type="danger" size="small" @click="deleteFile(scope.row.id)">{{ t('admin.fileView.delete') }}</el-button>
|
||||
<el-button type="success" size="small" @click="downloadFile(scope.row.id)" v-if="scope.row.file_path">{{ t('admin.fileView.download') }}</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination @size-change="updatePageSize" @current-change="updateCurrentPage" background layout="prev, pager, next" :page-size="params.size" :page-count="Math.round(params.total/params.size)" :total="params.total" />
|
||||
</main>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { request } from "@/utils/request";
|
||||
import { formatTimestamp } from "@/utils/timestamp-format"
|
||||
import { ref } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
const tableData = ref([]);
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const params = ref({
|
||||
page: 1,
|
||||
size: 10,
|
||||
total: 0,
|
||||
});
|
||||
const updateCurrentPage = (currentPage: number) => {
|
||||
params.value.page = currentPage;
|
||||
refreshData();
|
||||
};
|
||||
const deleteFile = (id: number) => {
|
||||
request({
|
||||
url: '/admin/file/delete',
|
||||
method: 'delete',
|
||||
data: {
|
||||
id,
|
||||
},
|
||||
}).then(() => {
|
||||
ElMessage.success(t('admin.fileView.delete_success'));
|
||||
refreshData();
|
||||
});
|
||||
};
|
||||
const downloadFile = (id: number) => {
|
||||
request({
|
||||
url: '/admin/file/download',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
responseType: 'blob'
|
||||
}).then((response: any) => {
|
||||
const contentDisposition = response.headers['content-disposition'];
|
||||
let filename = 'file';
|
||||
// 使用正则表达式来提取文件名
|
||||
const filenameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
|
||||
if (filenameMatch != null && filenameMatch[1]) {
|
||||
// 去除文件名周围的引号
|
||||
filename = filenameMatch[1].replace(/['"]/g, '');
|
||||
}
|
||||
// @ts-ignore
|
||||
if (window.showSaveFilePicker)
|
||||
saveFileByWebApi(response.data, filename).catch(() => {
|
||||
// ElMessage.error('文件保存函数不支持您的浏览器');
|
||||
saveFileByElementA(response.data, filename).catch(() => {
|
||||
ElMessage.error(t('admin.fileView.download_fail'));
|
||||
})
|
||||
})
|
||||
else
|
||||
saveFileByElementA(response.data, filename).catch(() => {
|
||||
ElMessage.error(t('admin.fileView.download_fail'));
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
async function saveFileByElementA(fileBlob: Blob, filename: string) {
|
||||
const downloadUrl = window.URL.createObjectURL(fileBlob);
|
||||
const link = document.createElement('a');
|
||||
link.href = downloadUrl;
|
||||
link.download = filename; // 设置下载文件名
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
// 清理并释放URL对象
|
||||
window.URL.revokeObjectURL(downloadUrl);
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
|
||||
async function saveFileByWebApi(fileBlob: Blob, filename: string) {
|
||||
// 创建一个新句柄。
|
||||
// @ts-ignore
|
||||
const newHandle = await window.showSaveFilePicker({
|
||||
suggestedName: filename,
|
||||
});
|
||||
// 创建一个 FileSystemWritableFileStream 用于写入。
|
||||
const writableStream = await newHandle.createWritable();
|
||||
// 写入我们的文件。
|
||||
await writableStream.write(fileBlob);
|
||||
// 关闭文件并将内容写入磁盘。
|
||||
await writableStream.close();
|
||||
}
|
||||
|
||||
|
||||
const updatePageSize=(pageSize: number) => {
|
||||
params.value.size = pageSize;
|
||||
refreshData();
|
||||
};
|
||||
const refreshData=() => {
|
||||
request({
|
||||
url: '/admin/file/list',
|
||||
method: 'get',
|
||||
params: params.value,
|
||||
}).then((res:any) => {
|
||||
tableData.value = res.detail.data;
|
||||
params.value.total = res.detail.total;
|
||||
});
|
||||
}
|
||||
refreshData();
|
||||
</script>
|
||||
@@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<div class="file-list">
|
||||
<el-empty style="width: 90vw;" v-if="localFiles.length===0" description="请在/opt/filecodebox/local目录上传您需要分享的文件" />
|
||||
<el-card v-for="file in localFiles" :key="file.name" class="file-card" shadow="hover">
|
||||
<div class="file-info">
|
||||
<div class="file-name">{{ file.file }}</div>
|
||||
<div class="file-date">{{ file.ctime }}</div>
|
||||
<div style="width: 100%;text-align: right">
|
||||
<el-button type="primary" style="margin-top: 1rem" @click="shareLocalFile(file)" plain>分享</el-button>
|
||||
<el-button type="danger" style="margin-top: 1rem" @click="deleteLocalFile(file)" plain>删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-dialog v-model="dialogFormVisible" width="500">
|
||||
<el-form :model="form">
|
||||
<el-form-item :label="t('admin.local.Name')" >
|
||||
<el-input v-model="form.name" readonly autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('admin.local.Expire')" >
|
||||
<el-input
|
||||
v-model="form.expireValue"
|
||||
style="width: 200px"
|
||||
:placeholder="t('send.pleaseInputExpireValue')"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-select v-model="form.expireStyle" :placeholder="t('send.expireStyle')" style="width: 75px">
|
||||
<el-option v-for="item in config.expireStyle" :key="item" :label="t(`send.expireData.${item}`)" :value="item" />
|
||||
</el-select>
|
||||
</template>
|
||||
<template #append>
|
||||
<span v-if="form.expireStyle==='day'">{{t('send.expireValue.day')}}</span>
|
||||
<span v-else-if="form.expireStyle==='hour'">{{t('send.expireValue.hour')}}</span>
|
||||
<span v-else-if="form.expireStyle==='minute'">{{t('send.expireValue.minute')}}</span>
|
||||
<span v-else-if="form.expireStyle==='forever'">👌</span>
|
||||
<span v-else-if="form.expireStyle==='count'">{{t('send.expireValue.count')}}</span>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="dialogFormVisible = false">{{ t('admin.local.Cancel') }}</el-button>
|
||||
<el-button type="primary" @click="toShareFile()">
|
||||
{{ t('admin.local.Confirm') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, reactive} from 'vue';
|
||||
import {request} from "@/utils/request";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {useConfigStore} from "@/stores/config";
|
||||
import {useI18n} from "vue-i18n";
|
||||
const { t } = useI18n()
|
||||
|
||||
const { config } = useConfigStore();
|
||||
const localFiles:any = ref([]);
|
||||
const loadLocalFiles = ()=>{
|
||||
request({
|
||||
url: '/admin/local/lists',
|
||||
method: 'get',
|
||||
}).then((data:any) => {
|
||||
localFiles.value = data.detail;
|
||||
});
|
||||
}
|
||||
const dialogFormVisible = ref(false);
|
||||
|
||||
const form = reactive({
|
||||
name: '1',
|
||||
expireStyle: 'day',
|
||||
expireValue: 1,
|
||||
})
|
||||
loadLocalFiles()
|
||||
const deleteLocalFile = (file: any) => {
|
||||
request({
|
||||
url: '/admin/local/delete',
|
||||
method: 'delete',
|
||||
data: {
|
||||
filename: file.file
|
||||
}
|
||||
}).then((data:any) => {
|
||||
ElMessage.success(data.detail);
|
||||
loadLocalFiles()
|
||||
});
|
||||
};
|
||||
|
||||
const shareLocalFile = (file:any) => {
|
||||
form.name = file.file;
|
||||
dialogFormVisible.value = true;
|
||||
};
|
||||
|
||||
const toShareFile=()=>{
|
||||
request({
|
||||
url: '/admin/local/share',
|
||||
method: 'post',
|
||||
data: {
|
||||
filename: form.name,
|
||||
expire_style: form.expireStyle,
|
||||
expire_value: form.expireValue
|
||||
}
|
||||
}).then((data:any) => {
|
||||
dialogFormVisible.value = false;
|
||||
ElMessage.success(
|
||||
{
|
||||
showClose: true,
|
||||
message: 'Code:' + data.detail.code,
|
||||
duration: 0
|
||||
}
|
||||
);
|
||||
loadLocalFiles()
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.file-list {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.file-card {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-radius: 10px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.file-card:hover {
|
||||
box-shadow: 0px 6px 15px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.file-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.file-date {
|
||||
font-size: 12px;
|
||||
color: #a0a0b2;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,219 @@
|
||||
<template>
|
||||
<el-form>
|
||||
<el-form-item size="large" :label="t('admin.settings.name')">
|
||||
<el-input v-model="config.name" />
|
||||
</el-form-item>
|
||||
<el-form-item size="large" :label="t('admin.settings.description')">
|
||||
<el-input v-model="config.description" />
|
||||
</el-form-item>
|
||||
<el-form-item size="large" :label="t('admin.settings.keywords')" style="letter-spacing: 0.3rem">
|
||||
<el-input v-model="config.keywords" />
|
||||
</el-form-item>
|
||||
<el-form-item size="large" :label="t('admin.settings.themeSelect')" >
|
||||
<el-select
|
||||
v-model="config.themesSelect"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option v-for="item in config.themesChoices" :label="item.name" :value="item.key" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item size="large" :label="t('admin.settings.notify_title')">
|
||||
<el-input v-model="config.notify_title" />
|
||||
</el-form-item>
|
||||
<el-form-item size="large" :label="t('admin.settings.notify_content')">
|
||||
<el-input v-model="config.notify_content" />
|
||||
</el-form-item>
|
||||
<el-form-item size="large" :label="t('admin.settings.background')">
|
||||
<span style="display: flex;height: 38px;width: 45%">
|
||||
<el-input v-model="config.background" placeholder="url" />
|
||||
</span>
|
||||
<span style="display: flex;height: 38px;margin-left: 20px">
|
||||
Opacity: <el-input type="number" v-model="config.opacity" />
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item size="large" :label="t('admin.settings.showAdminAddr')">
|
||||
<el-select v-model="config.showAdminAddr" style="width: 80%">
|
||||
<el-option :label="t('admin.settings.showAdmin.open')" :value="1" />
|
||||
<el-option :label="t('admin.settings.showAdmin.close')" :value="0" />
|
||||
</el-select>
|
||||
<small style="margin-left: 0.4rem">{{ t('admin.settings.showAdmin.note') }}</small>
|
||||
</el-form-item>
|
||||
<el-form-item size="large" label="robots.Text">
|
||||
<el-input type="textarea" v-model="config.robotsText" placeholder="explain" />
|
||||
</el-form-item>
|
||||
<el-form-item size="large" :label="t('admin.settings.explain')">
|
||||
<el-input type="textarea" v-model="config.page_explain" placeholder="explain" />
|
||||
</el-form-item>
|
||||
<el-form-item size="large" :label="t('admin.settings.admin_token')">
|
||||
<el-input type="password" v-model="config.admin_token" />
|
||||
</el-form-item>
|
||||
<el-form-item size="large" :label="t('admin.settings.uploadSize')">
|
||||
<el-input type="number" v-model="config.uploadSize" />
|
||||
<template #append></template>
|
||||
<small>Bytes:{{ t('admin.settings.uploadSizeNote') }}</small>
|
||||
</el-form-item>
|
||||
<el-form-item size="large" :label="t('admin.settings.max_save_seconds')">
|
||||
<el-input type="number" v-model="config.max_save_seconds" />
|
||||
<small>Seconds:{{t('admin.settings.maxSaveSecondsNote')}}</small>
|
||||
</el-form-item>
|
||||
<el-form-item size="large" :label="t('admin.settings.expireStyle')" >
|
||||
<el-select
|
||||
v-model="config.expireStyle"
|
||||
multiple
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option :label="t('send.expireData.day')" value="day" />
|
||||
<el-option :label="t('send.expireData.hour')" value="hour" />
|
||||
<el-option :label="t('send.expireData.minute')" value="minute" />
|
||||
<el-option :label="t('send.expireData.forever')" value="forever" />
|
||||
<el-option :label="t('send.expireData.count')" value="count" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item size="large" :label="t('admin.settings.openUpload.title')">
|
||||
<el-select v-model="config.openUpload" style="width: 80%">
|
||||
<el-option :label="t('admin.settings.openUpload.open')" :value="1" />
|
||||
<el-option :label="t('admin.settings.openUpload.close')" :value="0" />
|
||||
</el-select>
|
||||
<small style="margin-left: 0.4rem">{{ t('admin.settings.openUpload.note') }}</small>
|
||||
</el-form-item>
|
||||
<el-form-item size="large" :label="t('admin.settings.file_storage.title')">
|
||||
<el-select v-model="config.file_storage" style="width: 80%">
|
||||
<el-option :label="t('admin.settings.file_storage.local')" value="local" />
|
||||
<el-option :label="t('admin.settings.file_storage.s3')" value="s3" />
|
||||
</el-select>
|
||||
<small style="margin-left: 0.4rem">{{ t('admin.settings.file_storage.note') }}</small>
|
||||
</el-form-item>
|
||||
<div v-if="config.file_storage==='s3'">
|
||||
<el-form-item size="large" label="S3 AccessKeyId">
|
||||
<el-input v-model="config.s3_access_key_id" />
|
||||
</el-form-item>
|
||||
<el-form-item size="large" label="S3 SecretAccessKey">
|
||||
<el-input v-model="config.s3_secret_access_key" />
|
||||
</el-form-item>
|
||||
<el-form-item size="large" label="S3 BucketName">
|
||||
<el-input v-model="config.s3_bucket_name" />
|
||||
</el-form-item>
|
||||
<el-form-item size="large" label="S3 EndpointUrl">
|
||||
<el-input v-model="config.s3_endpoint_url" />
|
||||
</el-form-item>
|
||||
<el-form-item size="large" label="S3 hostname">
|
||||
<el-input v-model="config.s3_hostname" />
|
||||
</el-form-item>
|
||||
<el-form-item size="large" label="S3 region name">
|
||||
<el-input v-model="config.s3_region_name" />
|
||||
</el-form-item>
|
||||
<el-form-item size="large" label="S3 Signature Version">
|
||||
<el-input v-model="config.s3_signature_version" />
|
||||
</el-form-item>
|
||||
<el-form-item size="large" label="Aws Session Token">
|
||||
<el-input v-model="config.aws_session_token" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item size="large" :label="t('admin.settings.uploadlimit')">
|
||||
<span style="display: flex;height: 38px">
|
||||
<span style="margin-right: 0.4rem">{{ t('admin.settings.mei') }}</span>
|
||||
<el-input type="number" v-model="config.uploadMinute" />
|
||||
<span style="width: 200px;margin-left: 0.4rem">{{ t('admin.settings.minute') }}</span>
|
||||
</span>
|
||||
<span style="display: flex;height: 38px">
|
||||
<span style="width:3rem;margin-right: 0.4rem">{{ t('admin.settings.upload') }}</span>
|
||||
<el-input type="number" v-model="config.uploadCount" />
|
||||
<span style="width: 200px;margin-left: 0.4rem">{{ t('admin.settings.files') }}</span>
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item size="large" :label="t('admin.settings.errorlimit')">
|
||||
<span style="display: flex;height: 38px">
|
||||
<span style="margin-right: 0.4rem">{{ t('admin.settings.mei') }}</span>
|
||||
<el-input type="number" v-model="config.errorMinute" />
|
||||
<span style="width: 200px;margin-left: 0.4rem">{{ t('admin.settings.minute') }}</span>
|
||||
</span>
|
||||
<span style="display: flex;height: 38px">
|
||||
<span style="width:3rem;margin-right: 0.4rem">{{ t('admin.settings.allow') }}</span>
|
||||
<el-input type="number" v-model="config.errorCount" />
|
||||
<span style="width: 200px;margin-left: 0.4rem">{{ t('admin.settings.errors') }}</span>
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="submitSave" type="primary" style="margin: auto">{{ t('admin.settings.save') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import {ref} from "vue";
|
||||
import { request } from "@/utils/request";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const config:any = ref({
|
||||
name: '',
|
||||
description: '',
|
||||
file_storage: '',
|
||||
expireStyle: [],
|
||||
themesChoices: [],
|
||||
themesSelect: '',
|
||||
admin_token: '',
|
||||
robotsText:'',
|
||||
keywords: '',
|
||||
notify_title: '',
|
||||
notify_content: '',
|
||||
openUpload: 1,
|
||||
uploadSize: 1,
|
||||
uploadMinute: 1,
|
||||
max_save_seconds: 0,
|
||||
opacity: 0.9,
|
||||
s3_access_key_id: '',
|
||||
background: '',
|
||||
showAdminAddr: 0,
|
||||
page_explain: '',
|
||||
s3_secret_access_key: '',
|
||||
aws_session_token: '',
|
||||
s3_signature_version: '',
|
||||
s3_region_name: '',
|
||||
s3_bucket_name: '',
|
||||
s3_endpoint_url: '',
|
||||
s3_hostname: '',
|
||||
uploadCount: 1,
|
||||
errorMinute: 1,
|
||||
errorCount: 1,
|
||||
});
|
||||
const refreshData = ()=>{
|
||||
request({
|
||||
url: '/admin/config/get',
|
||||
method: 'get'
|
||||
}).then((res: any) => {
|
||||
config.value = res.detail;
|
||||
});
|
||||
}
|
||||
refreshData();
|
||||
const submitSave = () => {
|
||||
request({
|
||||
url: '/admin/config/update',
|
||||
method: 'patch',
|
||||
data: config.value
|
||||
}).then((res: any) => {
|
||||
if (res.code == 200) {
|
||||
ElMessage.success(t('admin.settings.saveSuccess'));
|
||||
} else {
|
||||
ElMessage.error(res.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
|
||||
.left-menu {
|
||||
margin-bottom: 1rem;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
letter-spacing: 0.4rem;
|
||||
}
|
||||
small,.el-form-item__content{
|
||||
color: #909399;
|
||||
margin-left: 0.4rem;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,126 @@
|
||||
<script setup lang="ts">
|
||||
import { Upload, TakeawayBox } from '@element-plus/icons-vue';
|
||||
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 { ElMessageBox } from 'element-plus'
|
||||
|
||||
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,
|
||||
})
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useConfigStore } from "@/stores/config";
|
||||
|
||||
const { config } = useConfigStore()
|
||||
const noDialog = ()=>{
|
||||
ElMessageBox.alert(config.explain, t('send.disclaimers'), {
|
||||
confirmButtonText: t('fileBox.ok'),
|
||||
});
|
||||
}
|
||||
const { t } = useI18n()
|
||||
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) => {
|
||||
fileBoxStore.showFileBox = true;
|
||||
let flag = true;
|
||||
fileStore.receiveData.forEach((file: any) => {
|
||||
if (file.code === res.detail.code) {
|
||||
flag = false;
|
||||
return;
|
||||
}
|
||||
});
|
||||
if (flag) {
|
||||
fileStore.addReceiveData(res.detail);
|
||||
}
|
||||
}).finally(() => {
|
||||
input_status.readonly = false;
|
||||
input_status.loading = false;
|
||||
code.value = '';
|
||||
});
|
||||
}
|
||||
});
|
||||
const listenInput = (num: number) => {
|
||||
if (code.value.length < 5) {
|
||||
code.value += num
|
||||
}
|
||||
};
|
||||
const name = atob('RmlsZUNvZGVCb3ggVjIuMQ==');
|
||||
const url = atob('aHR0cHM6Ly9naXRodWIuY29tL3Zhc3RzYS9GaWxlQ29kZUJveA==');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<el-card class="card" style="padding-bottom: 1rem">
|
||||
<CardTools/>
|
||||
<el-row style="text-align: center">
|
||||
<el-col :span="24">
|
||||
<el-input :readonly="input_status.readonly" v-loading="input_status.loading" v-model="code" class="code-input" round autofocus clearable maxlength="5" :placeholder="t('fileBox.inputNotEmpty')"/>
|
||||
</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>
|
||||
</el-col>
|
||||
<el-col :span=8>
|
||||
<el-button @click="router.push({'name':'send'})" class="key-button" :icon="Upload" round>
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span=8>
|
||||
<el-button class="key-button" round @click="listenInput(0)">0</el-button>
|
||||
</el-col>
|
||||
<el-col :span=8>
|
||||
<el-button class="key-button" round :icon="TakeawayBox" @click="fileBoxStore.showFileBox=true">
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
<div style="text-align: center; margin-top: 1rem;color: #606266">
|
||||
<a style="text-decoration: none;color: #606266" target="_blank" :href="url">
|
||||
{{ name }}
|
||||
</a>
|
||||
<a @click="noDialog" style="text-decoration: none;color: #606266;margin-left: 1rem" href="javascript:void(0)">{{t('send.disclaimers')}}</a>
|
||||
<a v-if="config.show_admin_address" style="color: #606266;margin-left: 1rem;text-decoration: none" href="#/admin">Admin</a>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
<style lang='scss'>
|
||||
.key-button{
|
||||
width: 6rem;
|
||||
height: 6rem;
|
||||
margin: 0.2rem;
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.code-input {
|
||||
height: 100px;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
margin: 1rem 0;
|
||||
.el-input__wrapper{
|
||||
border-radius: 20px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,59 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import CardTools from "@/components/CardTools.vue";
|
||||
import UploadFile from "@/components/UploadFile.vue";
|
||||
import UploadText from "@/components/UploadText.vue";
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useConfigStore } from "@/stores/config";
|
||||
|
||||
const { config } = useConfigStore();
|
||||
|
||||
const { t } = useI18n()
|
||||
const shareData = ref({
|
||||
expireValue: 1,
|
||||
expireStyle: config.expireStyle.length > 0 ? config.expireStyle[0] : 'day',
|
||||
targetType: 'file',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<el-card class="card" style="position: relative" :body-style="{ padding: '0' }">
|
||||
<card-tools style="padding: 1rem"/>
|
||||
<div style="display: flex;margin: 1rem">
|
||||
<div>
|
||||
<el-input
|
||||
v-model="shareData.expireValue"
|
||||
style="width: 200px"
|
||||
:placeholder="t('send.pleaseInputExpireValue')"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-select v-model="shareData.expireStyle" :placeholder="t('send.expireStyle')" style="width: 75px">
|
||||
<el-option v-for="item in config.expireStyle" :key="item" :label="t(`send.expireData.${item}`)" :value="item" />
|
||||
</el-select>
|
||||
</template>
|
||||
<template #append>
|
||||
<span v-if="shareData.expireStyle=='day'">{{t('send.expireValue.day')}}</span>
|
||||
<span v-else-if="shareData.expireStyle=='hour'">{{t('send.expireValue.hour')}}</span>
|
||||
<span v-else-if="shareData.expireStyle=='minute'">{{t('send.expireValue.minute')}}</span>
|
||||
<span v-else-if="shareData.expireStyle=='forever'">👌</span>
|
||||
<span v-else-if="shareData.expireStyle=='count'">{{t('send.expireValue.count')}}</span>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<el-radio-group v-model="shareData.targetType" style="margin-left: 1rem;">
|
||||
<el-radio label="file">
|
||||
{{t('send.fileType.file')}}
|
||||
</el-radio>
|
||||
<el-radio label="text">
|
||||
{{t('send.fileType.text')}}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<div style="margin: 1rem">
|
||||
<upload-file :shareData="shareData" v-if="shareData.targetType=='file'"/>
|
||||
<upload-text :shareData="shareData" v-else-if="shareData.targetType=='text'"/>
|
||||
</div>
|
||||
</el-card>
|
||||
</main>
|
||||
</template>
|
||||
Reference in New Issue
Block a user