test/custom-admin-ui #3

Merged
orion merged 673 commits from test/custom-admin-ui into master 2026-06-05 17:20:58 +08:00
47 changed files with 0 additions and 6963 deletions
Showing only changes of commit 7a652be94d - Show all commits
-15
View File
@@ -1,15 +0,0 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
'extends': [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-typescript',
'@vue/eslint-config-prettier/skip-formatting'
],
parserOptions: {
ecmaVersion: 'latest'
}
}
-27
View File
@@ -1,27 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
.vite/
node_modules
.DS_Store
dist-ssr
coverage
*.local
.git
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
-8
View File
@@ -1,8 +0,0 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"tabWidth": 2,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "none"
}
-9
View File
@@ -1,9 +0,0 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
export {}
declare global {
}
-46
View File
@@ -1,46 +0,0 @@
/* eslint-disable */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
CardTools: typeof import('./src/components/CardTools.vue')['default']
ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
ElCol: typeof import('element-plus/es')['ElCol']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput']
ElMain: typeof import('element-plus/es')['ElMain']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElProgress: typeof import('element-plus/es')['ElProgress']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRow: typeof import('element-plus/es')['ElRow']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTag: typeof import('element-plus/es')['ElTag']
ElUpload: typeof import('element-plus/es')['ElUpload']
FileBox: typeof import('./src/components/FileBox.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
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']
}
}
-1
View File
@@ -1 +0,0 @@
/// <reference types="vite/client" />
-16
View File
@@ -1,16 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/assets/logo_small.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" />
<meta name="description" content="{{description}}">
<meta name="keywords" content="{{keywords}}" />
<meta name="generator" content="FileCodeBox2.1" />
<title>{{title}}</title>
</head>
<body style="background: url('{{background}}') no-repeat center center fixed;background-size: cover;">
<div id="app" style="opacity: {{opacity}}"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
-49
View File
@@ -1,49 +0,0 @@
{
"name": "fcb-fronted",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "run-p type-check build-only",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"format": "prettier --write src/"
},
"dependencies": {
"@traptitech/markdown-it-katex": "^3.6.0",
"@types/markdown-it": "^14.1.2",
"@types/markdown-it-link-attributes": "^3.0.5",
"axios": "^1.7.2",
"element-plus": "^2.7.8",
"github-markdown-css": "^5.6.1",
"highlight.js": "^11.10.0",
"markdown-it": "^14.1.0",
"markdown-it-link-attributes": "^4.0.1",
"pinia": "^2.2.0",
"qrcode.vue": "^3.4.1",
"sass": "^1.77.8",
"unplugin-auto-import": "^0.18.2",
"unplugin-vue-components": "^0.27.3",
"vue": "^3.4.34",
"vue-i18n": "^9.13.1",
"vue-router": "^4.4.0"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.10.4",
"@tsconfig/node18": "^18.2.4",
"@types/node": "^22.0.0",
"@vitejs/plugin-vue": "^5.1.1",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^13.0.0",
"@vue/tsconfig": "^0.5.1",
"eslint": "^9.11.1",
"eslint-plugin-vue": "^9.28.0",
"npm-run-all": "^4.1.5",
"prettier": "^3.3.3",
"typescript": "~5.5.4",
"vite": "^5.3.5",
"vue-tsc": "^2.0.29"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

-33
View File
@@ -1,33 +0,0 @@
<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>
-86
View File
@@ -1,86 +0,0 @@
/* 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
-206
View File
@@ -1,206 +0,0 @@
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
}
}
-100
View File
@@ -1,100 +0,0 @@
@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;
}
@@ -1,75 +0,0 @@
.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;
}
}
-54
View File
@@ -1,54 +0,0 @@
@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;
}
-39
View File
@@ -1,39 +0,0 @@
<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>
-151
View File
@@ -1,151 +0,0 @@
<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>
-183
View File
@@ -1,183 +0,0 @@
<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>
-75
View File
@@ -1,75 +0,0 @@
<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>
-140
View File
@@ -1,140 +0,0 @@
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',
}
};
-140
View File
@@ -1,140 +0,0 @@
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',
}
};
-18
View File
@@ -1,18 +0,0 @@
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
-13
View File
@@ -1,13 +0,0 @@
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
}
-140
View File
@@ -1,140 +0,0 @@
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: '本站已关闭游客上传',
}
};
-142
View File
@@ -1,142 +0,0 @@
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: '本站已关闭游客上传',
}
};
-139
View File
@@ -1,139 +0,0 @@
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
View File
@@ -1,15 +0,0 @@
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')
-46
View File
@@ -1,46 +0,0 @@
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;
-13
View File
@@ -1,13 +0,0 @@
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 };
});
-7
View File
@@ -1,7 +0,0 @@
import { defineStore } from 'pinia';
import {ref} from "vue";
export const useConfigStore = defineStore('config', () => {
const config:any = ref(JSON.parse(localStorage.getItem('config') || '{}') || {}); // 配置
return { config };
});
-7
View File
@@ -1,7 +0,0 @@
import { defineStore } from 'pinia';
import {ref} from "vue";
export const useFileBoxStore = defineStore('fileBox', () => {
const showFileBox = ref(false);
return { showFileBox };
});
-31
View File
@@ -1,31 +0,0 @@
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 };
});
-32
View File
@@ -1,32 +0,0 @@
// @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;
-10
View File
@@ -1,10 +0,0 @@
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}`;
}
-15
View File
@@ -1,15 +0,0 @@
<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>
-92
View File
@@ -1,92 +0,0 @@
<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>
-147
View File
@@ -1,147 +0,0 @@
<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>
-155
View File
@@ -1,155 +0,0 @@
<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>
-219
View File
@@ -1,219 +0,0 @@
<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>
-126
View File
@@ -1,126 +0,0 @@
<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>
-59
View File
@@ -1,59 +0,0 @@
<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>
-12
View File
@@ -1,12 +0,0 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"composite": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
-11
View File
@@ -1,11 +0,0 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
}
]
}
-16
View File
@@ -1,16 +0,0 @@
{
"extends": "@tsconfig/node18/tsconfig.json",
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"nightwatch.conf.*",
"playwright.config.*"
],
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"]
}
}
-24
View File
@@ -1,24 +0,0 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
File diff suppressed because it is too large Load Diff