1
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
VITE_API_BASE_URL_DEV=http://localhost:3000/api
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
VITE_API_BASE_URL_PROD=https://api.yourdomain.com
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
"format": "prettier --write src/"
|
"format": "prettier --write src/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^1.7.7",
|
||||||
"lru-cache": "^11.0.1",
|
"lru-cache": "^11.0.1",
|
||||||
"lucide-vue-next": "^0.445.0",
|
"lucide-vue-next": "^0.445.0",
|
||||||
"pinia": "^2.2.2",
|
"pinia": "^2.2.2",
|
||||||
|
|||||||
Generated
+63
@@ -12,6 +12,7 @@ specifiers:
|
|||||||
'@vue/eslint-config-typescript': ^13.0.0
|
'@vue/eslint-config-typescript': ^13.0.0
|
||||||
'@vue/tsconfig': ^0.5.1
|
'@vue/tsconfig': ^0.5.1
|
||||||
autoprefixer: ^10.4.20
|
autoprefixer: ^10.4.20
|
||||||
|
axios: ^1.7.7
|
||||||
eslint: ^8.57.1
|
eslint: ^8.57.1
|
||||||
eslint-plugin-vue: ^9.28.0
|
eslint-plugin-vue: ^9.28.0
|
||||||
glob: ^11.0.0
|
glob: ^11.0.0
|
||||||
@@ -32,6 +33,7 @@ specifiers:
|
|||||||
vue-tsc: ^2.1.6
|
vue-tsc: ^2.1.6
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
axios: 1.7.7
|
||||||
lru-cache: 11.0.1
|
lru-cache: 11.0.1
|
||||||
lucide-vue-next: 0.445.0_vue@3.5.8
|
lucide-vue-next: 0.445.0_vue@3.5.8
|
||||||
pinia: 2.2.2_typescript@5.4.5+vue@3.5.8
|
pinia: 2.2.2_typescript@5.4.5+vue@3.5.8
|
||||||
@@ -1391,6 +1393,10 @@ packages:
|
|||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/asynckit/0.4.0:
|
||||||
|
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/autoprefixer/10.4.20_postcss@8.4.47:
|
/autoprefixer/10.4.20_postcss@8.4.47:
|
||||||
resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==}
|
resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==}
|
||||||
engines: {node: ^10 || ^12 || >=14}
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
@@ -1407,6 +1413,16 @@ packages:
|
|||||||
postcss-value-parser: 4.2.0
|
postcss-value-parser: 4.2.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/axios/1.7.7:
|
||||||
|
resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==}
|
||||||
|
dependencies:
|
||||||
|
follow-redirects: 1.15.9
|
||||||
|
form-data: 4.0.0
|
||||||
|
proxy-from-env: 1.1.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- debug
|
||||||
|
dev: false
|
||||||
|
|
||||||
/balanced-match/1.0.2:
|
/balanced-match/1.0.2:
|
||||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -1529,6 +1545,13 @@ packages:
|
|||||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/combined-stream/1.0.8:
|
||||||
|
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
dependencies:
|
||||||
|
delayed-stream: 1.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/commander/4.1.1:
|
/commander/4.1.1:
|
||||||
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
|
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
@@ -1609,6 +1632,11 @@ packages:
|
|||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/delayed-stream/1.0.0:
|
||||||
|
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||||
|
engines: {node: '>=0.4.0'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/didyoumean/1.2.2:
|
/didyoumean/1.2.2:
|
||||||
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
|
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -1929,6 +1957,16 @@ packages:
|
|||||||
resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==}
|
resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/follow-redirects/1.15.9:
|
||||||
|
resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
|
||||||
|
engines: {node: '>=4.0'}
|
||||||
|
peerDependencies:
|
||||||
|
debug: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
debug:
|
||||||
|
optional: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
/foreground-child/3.3.0:
|
/foreground-child/3.3.0:
|
||||||
resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==}
|
resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
@@ -1937,6 +1975,15 @@ packages:
|
|||||||
signal-exit: 4.1.0
|
signal-exit: 4.1.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/form-data/4.0.0:
|
||||||
|
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
|
||||||
|
engines: {node: '>= 6'}
|
||||||
|
dependencies:
|
||||||
|
asynckit: 0.4.0
|
||||||
|
combined-stream: 1.0.8
|
||||||
|
mime-types: 2.1.35
|
||||||
|
dev: false
|
||||||
|
|
||||||
/fraction.js/4.3.7:
|
/fraction.js/4.3.7:
|
||||||
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -2365,6 +2412,18 @@ packages:
|
|||||||
picomatch: 2.3.1
|
picomatch: 2.3.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/mime-db/1.52.0:
|
||||||
|
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/mime-types/2.1.35:
|
||||||
|
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
dependencies:
|
||||||
|
mime-db: 1.52.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/mimic-fn/4.0.0:
|
/mimic-fn/4.0.0:
|
||||||
resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
|
resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -2731,6 +2790,10 @@ packages:
|
|||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/proxy-from-env/1.1.0:
|
||||||
|
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/punycode/2.3.1:
|
/punycode/2.3.1:
|
||||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|||||||
Binary file not shown.
+12
-3
@@ -1,3 +1,12 @@
|
|||||||
@tailwind base;
|
@import 'tailwindcss/base';
|
||||||
@tailwind components;
|
@import 'tailwindcss/components';
|
||||||
@tailwind utilities;
|
@import 'tailwindcss/utilities';
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'DingTalk';
|
||||||
|
src: url('../assets/font/DingTalk.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
font-family: 'DingTalk', sans-serif !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
<template>
|
||||||
|
<transition name="alert-fade">
|
||||||
|
<div v-if="show" class="fixed top-4 right-4 max-w-sm w-full z-50">
|
||||||
|
<div :class="[
|
||||||
|
'rounded-lg shadow-lg overflow-hidden transform transition-all duration-300',
|
||||||
|
alertTypeClasses[type]
|
||||||
|
]">
|
||||||
|
<div class="p-4">
|
||||||
|
<div class="flex items-start">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<CheckCircle v-if="type === 'success'" class="h-6 w-6 text-white" />
|
||||||
|
<AlertTriangle v-else-if="type === 'error'" class="h-6 w-6 text-white" />
|
||||||
|
<AlertCircle v-else-if="type === 'warning'" class="h-6 w-6 text-white" />
|
||||||
|
<Info v-else class="h-6 w-6 text-white" />
|
||||||
|
</div>
|
||||||
|
<div class="ml-3 w-0 flex-1 pt-0.5">
|
||||||
|
<p class="text-sm font-medium text-white">{{ message }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4 flex-shrink-0 flex">
|
||||||
|
<button @click="$emit('close')" class="inline-flex text-white hover:text-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-white">
|
||||||
|
<span class="sr-only">关闭</span>
|
||||||
|
<X class="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="h-1 bg-white bg-opacity-25">
|
||||||
|
<div
|
||||||
|
:class="['h-full transition-all duration-300 ease-out bg-white', { 'w-full': !autoClose }]"
|
||||||
|
:style="{ width: `${progressWidth}%` }"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, onUnmounted, watch } from 'vue';
|
||||||
|
import { CheckCircle, AlertTriangle, AlertCircle, Info, X } from 'lucide-vue-next';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
show: Boolean,
|
||||||
|
message: String,
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'info',
|
||||||
|
validator: (value: string) => ['success', 'error', 'warning', 'info'].includes(value)
|
||||||
|
},
|
||||||
|
duration: {
|
||||||
|
type: Number,
|
||||||
|
default: 3000
|
||||||
|
},
|
||||||
|
autoClose: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['close']);
|
||||||
|
|
||||||
|
const alertTypeClasses: {[key: string]: string} = {
|
||||||
|
success: 'bg-green-500',
|
||||||
|
error: 'bg-red-500',
|
||||||
|
warning: 'bg-yellow-500',
|
||||||
|
info: 'bg-blue-500'
|
||||||
|
};
|
||||||
|
|
||||||
|
const progressWidth = ref(100);
|
||||||
|
let timer: number | null = null;
|
||||||
|
|
||||||
|
const startTimer = () => {
|
||||||
|
if (timer) {
|
||||||
|
clearInterval(timer);
|
||||||
|
}
|
||||||
|
progressWidth.value = 100;
|
||||||
|
const startTime = Date.now();
|
||||||
|
const endTime = startTime + props.duration;
|
||||||
|
|
||||||
|
timer = setInterval(() => {
|
||||||
|
const now = Date.now();
|
||||||
|
const remaining = endTime - now;
|
||||||
|
progressWidth.value = (remaining / props.duration) * 100;
|
||||||
|
|
||||||
|
if (remaining <= 0) {
|
||||||
|
clearInterval(timer!);
|
||||||
|
emit('close');
|
||||||
|
}
|
||||||
|
}, 10);
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(() => props.show, (newValue) => {
|
||||||
|
if (newValue && props.autoClose) {
|
||||||
|
startTimer();
|
||||||
|
} else if (!newValue && timer) {
|
||||||
|
clearInterval(timer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.show && props.autoClose) {
|
||||||
|
startTimer();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (timer) {
|
||||||
|
clearInterval(timer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.alert-fade-enter-active,
|
||||||
|
.alert-fade-leave-active {
|
||||||
|
transition: opacity 0.3s, transform 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-fade-enter-from,
|
||||||
|
.alert-fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(20px);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
// 从环境变量中获取 API 基础 URL
|
||||||
|
const baseURL =
|
||||||
|
import.meta.env.MODE === 'production'
|
||||||
|
? import.meta.env.VITE_API_BASE_URL_PROD
|
||||||
|
: import.meta.env.VITE_API_BASE_URL_DEV
|
||||||
|
console.log(baseURL)
|
||||||
|
console.log(import.meta.env.MODE)
|
||||||
|
|
||||||
|
// 创建 axios 实例
|
||||||
|
const api = axios.create({
|
||||||
|
baseURL,
|
||||||
|
timeout: 10000, // 请求超时时间
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 请求拦截器
|
||||||
|
api.interceptors.request.use(
|
||||||
|
(config) => {
|
||||||
|
// 从 localStorage 获取 token
|
||||||
|
const token = localStorage.getItem('token')
|
||||||
|
if (token) {
|
||||||
|
config.headers['Authorization'] = `Bearer ${token}`
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 响应拦截器
|
||||||
|
api.interceptors.response.use(
|
||||||
|
(response) => {
|
||||||
|
return response
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
// 处理错误响应
|
||||||
|
if (error.response) {
|
||||||
|
switch (error.response.status) {
|
||||||
|
case 401:
|
||||||
|
// 未授权,可能需要重新登录
|
||||||
|
console.error('未授权,请重新登录')
|
||||||
|
break
|
||||||
|
case 403:
|
||||||
|
// 禁止访问
|
||||||
|
console.error('禁止访问')
|
||||||
|
break
|
||||||
|
case 404:
|
||||||
|
// 未找到
|
||||||
|
console.error('请求的资源不存在')
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
console.error('发生错误:', error.response.data)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('发生错误:', error.message)
|
||||||
|
}
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export default api
|
||||||
+278
-79
@@ -1,5 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="min-h-screen flex items-center justify-center p-4 overflow-hidden transition-colors duration-300">
|
<div
|
||||||
|
class="min-h-screen flex items-center justify-center p-4 overflow-hidden transition-colors duration-300"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
class="rounded-3xl shadow-2xl overflow-hidden border w-full max-w-md transition-colors duration-300"
|
class="rounded-3xl shadow-2xl overflow-hidden border w-full max-w-md transition-colors duration-300"
|
||||||
:class="[
|
:class="[
|
||||||
@@ -93,26 +95,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else key="text" class="grid grid-cols-1 gap-8">
|
<div v-else key="text" class="grid grid-cols-1 gap-8">
|
||||||
<!-- 文本输入区域 -->
|
<!-- 文本输入区域 -->
|
||||||
<div v-if="sendType === 'text'" class="flex flex-col">
|
<div v-if="sendType === 'text'" class="flex flex-col">
|
||||||
<textarea
|
<textarea
|
||||||
id="text-content"
|
id="text-content"
|
||||||
v-model="textContent"
|
v-model="textContent"
|
||||||
rows="7"
|
rows="7"
|
||||||
:class="[
|
:class="[
|
||||||
'flex-grow px-4 py-3 rounded-xl placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-indigo-500 transition duration-300 resize-none',
|
'flex-grow px-4 py-3 rounded-xl placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-indigo-500 transition duration-300 resize-none',
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'bg-gray-800 bg-opacity-50 text-white'
|
? 'bg-gray-800 bg-opacity-50 text-white'
|
||||||
: 'bg-white text-gray-900 border border-gray-300'
|
: 'bg-white text-gray-900 border border-gray-300'
|
||||||
]"
|
]"
|
||||||
placeholder="在此输入要发送的文本..."
|
placeholder="在此输入要发送的文本..."
|
||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
<!-- 过期方式选择 -->
|
<!-- 过期方式选择 -->
|
||||||
<div class="flex flex-col space-y-4">
|
<div class="flex flex-col space-y-4">
|
||||||
<label :class="['text-sm font-medium', isDarkMode ? 'text-gray-300' : 'text-gray-700']">过期方式</label>
|
<label :class="['text-sm font-medium', isDarkMode ? 'text-gray-300' : 'text-gray-700']"
|
||||||
|
>过期方式</label
|
||||||
|
>
|
||||||
<select
|
<select
|
||||||
v-model="expirationMethod"
|
v-model="expirationMethod"
|
||||||
:class="[
|
:class="[
|
||||||
@@ -171,12 +175,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="px-8 py-4 bg-opacity-50 flex justify-between items-center" :class="[isDarkMode ? 'bg-gray-800' : 'bg-gray-100']">
|
<div
|
||||||
<span class="text-sm flex items-center" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-800']">
|
class="px-8 py-4 bg-opacity-50 flex justify-between items-center"
|
||||||
|
:class="[isDarkMode ? 'bg-gray-800' : 'bg-gray-100']"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="text-sm flex items-center"
|
||||||
|
:class="[isDarkMode ? 'text-gray-300' : 'text-gray-800']"
|
||||||
|
>
|
||||||
<ShieldCheckIcon class="w-4 h-4 mr-1 text-green-400" />
|
<ShieldCheckIcon class="w-4 h-4 mr-1 text-green-400" />
|
||||||
安全加密
|
安全加密
|
||||||
</span>
|
</span>
|
||||||
<button @click="toggleDrawer" class="text-sm hover:text-indigo-300 transition duration-300 flex items-center" :class="[isDarkMode ? 'text-indigo-400' : 'text-indigo-600']">
|
<button
|
||||||
|
@click="toggleDrawer"
|
||||||
|
class="text-sm hover:text-indigo-300 transition duration-300 flex items-center"
|
||||||
|
:class="[isDarkMode ? 'text-indigo-400' : 'text-indigo-600']"
|
||||||
|
>
|
||||||
发件记录
|
发件记录
|
||||||
<ClipboardListIcon class="w-4 h-4 ml-1" />
|
<ClipboardListIcon class="w-4 h-4 ml-1" />
|
||||||
</button>
|
</button>
|
||||||
@@ -185,30 +199,72 @@
|
|||||||
|
|
||||||
<!-- 抽屉式发件记录 -->
|
<!-- 抽屉式发件记录 -->
|
||||||
<transition name="drawer">
|
<transition name="drawer">
|
||||||
<div v-if="showDrawer" class="fixed inset-y-0 right-0 w-full sm:w-96 bg-opacity-70 backdrop-filter backdrop-blur-xl shadow-2xl z-50 overflow-hidden flex flex-col" :class="[isDarkMode ? 'bg-gray-900' : 'bg-white']">
|
<div
|
||||||
<div class="flex justify-between items-center p-6 border-b" :class="[isDarkMode ? 'border-gray-700' : 'border-gray-200']">
|
v-if="showDrawer"
|
||||||
<h3 class="text-2xl font-bold" :class="[isDarkMode ? 'text-white' : 'text-gray-800']">发件记录</h3>
|
class="fixed inset-y-0 right-0 w-full sm:w-96 bg-opacity-70 backdrop-filter backdrop-blur-xl shadow-2xl z-50 overflow-hidden flex flex-col"
|
||||||
<button @click="toggleDrawer" class="hover:text-white transition duration-300" :class="[isDarkMode ? 'text-gray-400' : 'text-gray-800']">
|
:class="[isDarkMode ? 'bg-gray-900' : 'bg-white']"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex justify-between items-center p-6 border-b"
|
||||||
|
:class="[isDarkMode ? 'border-gray-700' : 'border-gray-200']"
|
||||||
|
>
|
||||||
|
<h3 class="text-2xl font-bold" :class="[isDarkMode ? 'text-white' : 'text-gray-800']">
|
||||||
|
发件记录
|
||||||
|
</h3>
|
||||||
|
<button
|
||||||
|
@click="toggleDrawer"
|
||||||
|
class="hover:text-white transition duration-300"
|
||||||
|
:class="[isDarkMode ? 'text-gray-400' : 'text-gray-800']"
|
||||||
|
>
|
||||||
<XIcon class="w-6 h-6" />
|
<XIcon class="w-6 h-6" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow overflow-y-auto p-6">
|
<div class="flex-grow overflow-y-auto p-6">
|
||||||
<transition-group name="list" tag="div" class="space-y-4">
|
<transition-group name="list" tag="div" class="space-y-4">
|
||||||
<div v-for="record in sendRecords" :key="record.id" class="bg-opacity-50 rounded-lg p-4 flex justify-between items-center shadow-md hover:shadow-lg transition duration-300 transform hover:scale-102" :class="[isDarkMode ? 'bg-gray-800 hover:bg-gray-700' : 'bg-gray-100 hover:bg-white']">
|
<div
|
||||||
|
v-for="record in sendRecords"
|
||||||
|
:key="record.id"
|
||||||
|
class="bg-opacity-50 rounded-lg p-4 flex justify-between items-center shadow-md hover:shadow-lg transition duration-300 transform hover:scale-102"
|
||||||
|
:class="[isDarkMode ? 'bg-gray-800 hover:bg-gray-700' : 'bg-gray-100 hover:bg-white']"
|
||||||
|
>
|
||||||
<div class="flex items-center space-x-4">
|
<div class="flex items-center space-x-4">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<FileIcon class="w-10 h-10" :class="[isDarkMode ? 'text-indigo-400' : 'text-indigo-600']" />
|
<FileIcon
|
||||||
|
class="w-10 h-10"
|
||||||
|
:class="[isDarkMode ? 'text-indigo-400' : 'text-indigo-600']"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p class="font-medium text-lg" :class="[isDarkMode ? 'text-white' : 'text-gray-800']">{{ record.filename }}</p>
|
<p
|
||||||
<p class="text-sm" :class="[isDarkMode ? 'text-gray-400' : 'text-gray-600']">{{ record.date }} · {{ record.size }}</p>
|
class="font-medium text-lg"
|
||||||
|
:class="[isDarkMode ? 'text-white' : 'text-gray-800']"
|
||||||
|
>
|
||||||
|
{{ record.filename }}
|
||||||
|
</p>
|
||||||
|
<p class="text-sm" :class="[isDarkMode ? 'text-gray-400' : 'text-gray-600']">
|
||||||
|
{{ record.date }} · {{ record.size }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex space-x-2">
|
<div class="flex space-x-2">
|
||||||
<button @click="viewDetails(record)" class="p-2 rounded-full hover:bg-opacity-20 transition duration-300" :class="[isDarkMode ? 'hover:bg-indigo-400 text-indigo-400' : 'hover:bg-indigo-100 text-indigo-600']">
|
<button
|
||||||
|
@click="viewDetails(record)"
|
||||||
|
class="p-2 rounded-full hover:bg-opacity-20 transition duration-300"
|
||||||
|
:class="[
|
||||||
|
isDarkMode
|
||||||
|
? 'hover:bg-indigo-400 text-indigo-400'
|
||||||
|
: 'hover:bg-indigo-100 text-indigo-600'
|
||||||
|
]"
|
||||||
|
>
|
||||||
<EyeIcon class="w-5 h-5" />
|
<EyeIcon class="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
<button @click="deleteRecord(record.id)" class="p-2 rounded-full hover:bg-opacity-20 transition duration-300" :class="[isDarkMode ? 'hover:bg-red-400 text-red-400' : 'hover:bg-red-100 text-red-600']">
|
<button
|
||||||
|
@click="deleteRecord(record.id)"
|
||||||
|
class="p-2 rounded-full hover:bg-opacity-20 transition duration-300"
|
||||||
|
:class="[
|
||||||
|
isDarkMode ? 'hover:bg-red-400 text-red-400' : 'hover:bg-red-100 text-red-600'
|
||||||
|
]"
|
||||||
|
>
|
||||||
<TrashIcon class="w-5 h-5" />
|
<TrashIcon class="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -220,65 +276,133 @@
|
|||||||
|
|
||||||
<!-- 记录详情弹窗 -->
|
<!-- 记录详情弹窗 -->
|
||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
<div v-if="selectedRecord" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
<div
|
||||||
<div class="p-8 rounded-2xl max-w-md w-full mx-4 shadow-2xl transform transition-all duration-300 ease-out backdrop-filter backdrop-blur-lg bg-opacity-70" :class="[isDarkMode ? 'bg-gray-800' : 'bg-white']">
|
v-if="selectedRecord"
|
||||||
<h3 class="text-2xl font-bold mb-6" :class="[isDarkMode ? 'text-white' : 'text-gray-800']">文件详情</h3>
|
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="p-8 rounded-2xl max-w-md w-full mx-4 shadow-2xl transform transition-all duration-300 ease-out backdrop-filter backdrop-blur-lg bg-opacity-70"
|
||||||
|
:class="[isDarkMode ? 'bg-gray-800' : 'bg-white']"
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
class="text-2xl font-bold mb-6"
|
||||||
|
:class="[isDarkMode ? 'text-white' : 'text-gray-800']"
|
||||||
|
>
|
||||||
|
文件详情
|
||||||
|
</h3>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<FileIcon class="w-6 h-6 mr-3" :class="[isDarkMode ? 'text-indigo-400' : 'text-indigo-600']" />
|
<FileIcon
|
||||||
<p :class="[isDarkMode ? 'text-gray-300' : 'text-gray-800']"><span class="font-medium">文件名:</span>{{ selectedRecord.filename }}</p>
|
class="w-6 h-6 mr-3"
|
||||||
|
:class="[isDarkMode ? 'text-indigo-400' : 'text-indigo-600']"
|
||||||
|
/>
|
||||||
|
<p :class="[isDarkMode ? 'text-gray-300' : 'text-gray-800']">
|
||||||
|
<span class="font-medium">文件名:</span>{{ selectedRecord.filename }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<CalendarIcon class="w-6 h-6 mr-3" :class="[isDarkMode ? 'text-indigo-400' : 'text-indigo-600']" />
|
<CalendarIcon
|
||||||
<p :class="[isDarkMode ? 'text-gray-300' : 'text-gray-800']"><span class="font-medium">发送日期:</span>{{ selectedRecord.date }}</p>
|
class="w-6 h-6 mr-3"
|
||||||
|
:class="[isDarkMode ? 'text-indigo-400' : 'text-indigo-600']"
|
||||||
|
/>
|
||||||
|
<p :class="[isDarkMode ? 'text-gray-300' : 'text-gray-800']">
|
||||||
|
<span class="font-medium">发送日期:</span>{{ selectedRecord.date }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<HardDriveIcon class="w-6 h-6 mr-3" :class="[isDarkMode ? 'text-indigo-400' : 'text-indigo-600']" />
|
<HardDriveIcon
|
||||||
<p :class="[isDarkMode ? 'text-gray-300' : 'text-gray-800']"><span class="font-medium">文件大小:</span>{{ selectedRecord.size }}</p>
|
class="w-6 h-6 mr-3"
|
||||||
|
:class="[isDarkMode ? 'text-indigo-400' : 'text-indigo-600']"
|
||||||
|
/>
|
||||||
|
<p :class="[isDarkMode ? 'text-gray-300' : 'text-gray-800']">
|
||||||
|
<span class="font-medium">文件大小:</span>{{ selectedRecord.size }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<ClockIcon class="w-6 h-6 mr-3" :class="[isDarkMode ? 'text-indigo-400' : 'text-indigo-600']" />
|
<ClockIcon
|
||||||
<p :class="[isDarkMode ? 'text-gray-300' : 'text-gray-800']"><span class="font-medium">过期时间:</span>{{ selectedRecord.expiration }}</p>
|
class="w-6 h-6 mr-3"
|
||||||
|
:class="[isDarkMode ? 'text-indigo-400' : 'text-indigo-600']"
|
||||||
|
/>
|
||||||
|
<p :class="[isDarkMode ? 'text-gray-300' : 'text-gray-800']">
|
||||||
|
<span class="font-medium">过期时间:</span>{{ selectedRecord.expiration }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 取件码和二维码部分 -->
|
<!-- 取件码和二维码部分 -->
|
||||||
<div class="mt-6 flex justify-between items-center">
|
<div class="mt-6 flex justify-between items-center">
|
||||||
<div class="flex flex-col items-center w-1/2 pr-2">
|
<div class="flex flex-col items-center w-1/2 pr-2">
|
||||||
<h4 class="text-lg font-semibold mb-3" :class="[isDarkMode ? 'text-white' : 'text-gray-800']">取件码</h4>
|
<h4
|
||||||
|
class="text-lg font-semibold mb-3"
|
||||||
|
:class="[isDarkMode ? 'text-white' : 'text-gray-800']"
|
||||||
|
>
|
||||||
|
取件码
|
||||||
|
</h4>
|
||||||
<div
|
<div
|
||||||
class="bg-gray-100 p-3 rounded-lg shadow-md cursor-pointer hover:bg-gray-200 transition-colors duration-300 w-full text-center"
|
class="bg-gray-100 p-3 rounded-lg shadow-md cursor-pointer hover:bg-gray-200 transition-colors duration-300 w-full text-center"
|
||||||
@click="copyRetrieveCode(selectedRecord.retrieveCode)"
|
@click="copyRetrieveCode(selectedRecord.retrieveCode)"
|
||||||
>
|
>
|
||||||
<p class="text-2xl font-bold text-indigo-600">{{ selectedRecord.retrieveCode }}</p>
|
<p class="text-2xl font-bold text-indigo-600">{{ selectedRecord.retrieveCode }}</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-2 text-sm" :class="[isDarkMode ? 'text-gray-400' : 'text-gray-600']">点击复制取件码</p>
|
<p class="mt-2 text-sm" :class="[isDarkMode ? 'text-gray-400' : 'text-gray-600']">
|
||||||
|
点击复制取件码
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col items-center w-1/2 pl-2">
|
<div class="flex flex-col items-center w-1/2 pl-2">
|
||||||
<h4 class="text-lg font-semibold mb-3" :class="[isDarkMode ? 'text-white' : 'text-gray-800']">二维码</h4>
|
<h4
|
||||||
|
class="text-lg font-semibold mb-3"
|
||||||
|
:class="[isDarkMode ? 'text-white' : 'text-gray-800']"
|
||||||
|
>
|
||||||
|
二维码
|
||||||
|
</h4>
|
||||||
<div class="bg-white p-2 rounded-lg shadow-md">
|
<div class="bg-white p-2 rounded-lg shadow-md">
|
||||||
<QRCode :value="getQRCodeValue(selectedRecord)" :size="128" level="M" />
|
<QRCode :value="getQRCodeValue(selectedRecord)" :size="128" level="M" />
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-2 text-sm" :class="[isDarkMode ? 'text-gray-400' : 'text-gray-600']">扫描二维码快速取件</p>
|
<p class="mt-2 text-sm" :class="[isDarkMode ? 'text-gray-400' : 'text-gray-600']">
|
||||||
|
扫描二维码快速取件
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button @click="selectedRecord = null" class="mt-8 w-full bg-gradient-to-r from-indigo-500 to-purple-600 text-white px-6 py-3 rounded-lg font-medium hover:from-indigo-600 hover:to-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-opacity-50 transition duration-300 transform hover:scale-105">
|
<button
|
||||||
|
@click="selectedRecord = null"
|
||||||
|
class="mt-8 w-full bg-gradient-to-r from-indigo-500 to-purple-600 text-white px-6 py-3 rounded-lg font-medium hover:from-indigo-600 hover:to-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-opacity-50 transition duration-300 transform hover:scale-105"
|
||||||
|
>
|
||||||
关闭
|
关闭
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
|
<!-- 使用新的 AlertComponent -->
|
||||||
|
<AlertComponent
|
||||||
|
:show="showAlert"
|
||||||
|
:message="alertMessage"
|
||||||
|
:type="alertType"
|
||||||
|
@close="closeAlert"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, inject, onMounted } from 'vue'
|
import { ref, inject, onMounted } from 'vue'
|
||||||
import { UploadCloudIcon, SendIcon, ClipboardListIcon, XIcon, TrashIcon, FileIcon, CalendarIcon, HardDriveIcon, ClockIcon, EyeIcon, ShieldCheckIcon } from 'lucide-vue-next'
|
import {
|
||||||
|
UploadCloudIcon,
|
||||||
|
SendIcon,
|
||||||
|
ClipboardListIcon,
|
||||||
|
XIcon,
|
||||||
|
TrashIcon,
|
||||||
|
FileIcon,
|
||||||
|
CalendarIcon,
|
||||||
|
HardDriveIcon,
|
||||||
|
ClockIcon,
|
||||||
|
EyeIcon,
|
||||||
|
ShieldCheckIcon
|
||||||
|
} from 'lucide-vue-next'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import BorderProgressBar from '../components/BorderProgressBar.vue'
|
import BorderProgressBar from '../components/BorderProgressBar.vue'
|
||||||
import QRCode from 'qrcode.vue'
|
import QRCode from 'qrcode.vue'
|
||||||
|
import AlertComponent from '../components/AlertComponent.vue'
|
||||||
|
import api from '../utils/api'
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const isDarkMode = inject('isDarkMode')
|
const isDarkMode = inject('isDarkMode')
|
||||||
|
|
||||||
@@ -292,15 +416,83 @@ const expirationViews = ref('')
|
|||||||
const uploadProgress = ref(0)
|
const uploadProgress = ref(0)
|
||||||
const showDrawer = ref(false)
|
const showDrawer = ref(false)
|
||||||
const selectedRecord = ref<any>(null)
|
const selectedRecord = ref<any>(null)
|
||||||
|
api
|
||||||
|
.post('/', {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
sendType: sendType.value,
|
||||||
|
selectedFile: selectedFile.value,
|
||||||
|
textContent: textContent.value,
|
||||||
|
expirationMethod: expirationMethod.value,
|
||||||
|
expirationTime: expirationTime.value,
|
||||||
|
expirationViews: expirationViews.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
console.log(res)
|
||||||
|
})
|
||||||
const sendRecords = ref([
|
const sendRecords = ref([
|
||||||
{ id: 1, filename: '项目报告.pdf', date: '2023-05-15', size: '3.2 MB', expiration: '2023-05-22', retrieveCode: 'ABC123' },
|
{
|
||||||
{ id: 2, filename: '会议纪要.docx', date: '2023-05-10', size: '1.5 MB', expiration: '2023-05-17', retrieveCode: 'DEF456' },
|
id: 1,
|
||||||
{ id: 3, filename: '财务数据.xlsx', date: '2023-05-05', size: '2.8 MB', expiration: '2023-05-12', retrieveCode: 'GHI789' },
|
filename: '项目报告.pdf',
|
||||||
{ id: 4, filename: '产品设计.psd', date: '2023-05-01', size: '15.7 MB', expiration: '2023-05-08', retrieveCode: 'JKL012' },
|
date: '2023-05-15',
|
||||||
{ id: 5, filename: '客户反馈.txt', date: '2023-04-28', size: '0.5 MB', expiration: '2023-05-05', retrieveCode: 'MNO345' },
|
size: '3.2 MB',
|
||||||
|
expiration: '2023-05-22',
|
||||||
|
retrieveCode: 'ABC123'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
filename: '会议纪要.docx',
|
||||||
|
date: '2023-05-10',
|
||||||
|
size: '1.5 MB',
|
||||||
|
expiration: '2023-05-17',
|
||||||
|
retrieveCode: 'DEF456'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
filename: '财务数据.xlsx',
|
||||||
|
date: '2023-05-05',
|
||||||
|
size: '2.8 MB',
|
||||||
|
expiration: '2023-05-12',
|
||||||
|
retrieveCode: 'GHI789'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
filename: '产品设计.psd',
|
||||||
|
date: '2023-05-01',
|
||||||
|
size: '15.7 MB',
|
||||||
|
expiration: '2023-05-08',
|
||||||
|
retrieveCode: 'JKL012'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
filename: '客户反馈.txt',
|
||||||
|
date: '2023-04-28',
|
||||||
|
size: '0.5 MB',
|
||||||
|
expiration: '2023-05-05',
|
||||||
|
retrieveCode: 'MNO345'
|
||||||
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const showAlert = ref(false)
|
||||||
|
const alertMessage = ref('')
|
||||||
|
const alertType = ref('success')
|
||||||
|
|
||||||
|
const showAlertMessage = (
|
||||||
|
message: string,
|
||||||
|
type: 'success' | 'error' | 'warning' | 'info' = 'success'
|
||||||
|
) => {
|
||||||
|
alertMessage.value = message
|
||||||
|
alertType.value = type
|
||||||
|
showAlert.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeAlert = () => {
|
||||||
|
showAlert.value = false
|
||||||
|
}
|
||||||
|
|
||||||
const triggerFileUpload = () => {
|
const triggerFileUpload = () => {
|
||||||
fileInput.value?.click()
|
fileInput.value?.click()
|
||||||
}
|
}
|
||||||
@@ -322,36 +514,36 @@ const handleFileDrop = (event: DragEvent) => {
|
|||||||
|
|
||||||
const simulateFileUpload = () => {
|
const simulateFileUpload = () => {
|
||||||
uploadProgress.value = 0
|
uploadProgress.value = 0
|
||||||
const duration = 2000; // 总动画时长(毫秒)
|
const duration = 2000 // 总动画时长(毫秒)
|
||||||
const steps = 100; // 动画步数
|
const steps = 100 // 动画步数
|
||||||
const stepDuration = duration / steps;
|
const stepDuration = duration / steps
|
||||||
const stepIncrement = 100 / steps;
|
const stepIncrement = 100 / steps
|
||||||
|
|
||||||
const animate = (step: number) => {
|
const animate = (step: number) => {
|
||||||
if (step <= steps) {
|
if (step <= steps) {
|
||||||
uploadProgress.value = step * stepIncrement;
|
uploadProgress.value = step * stepIncrement
|
||||||
setTimeout(() => animate(step + 1), stepDuration);
|
setTimeout(() => animate(step + 1), stepDuration)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
animate(0);
|
animate(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
if (sendType.value === 'file' && !selectedFile.value) {
|
if (sendType.value === 'file' && !selectedFile.value) {
|
||||||
alert('请选择要上传的文件')
|
showAlertMessage('请选择要上传的文件', 'error')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (sendType.value === 'text' && !textContent.value.trim()) {
|
if (sendType.value === 'text' && !textContent.value.trim()) {
|
||||||
alert('请输入要发送的文本')
|
showAlertMessage('请输入要发送的文本', 'error')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (expirationMethod.value === 'time' && !expirationTime.value) {
|
if (expirationMethod.value === 'time' && !expirationTime.value) {
|
||||||
alert('请输入过期时间')
|
showAlertMessage('请输入过期时间', 'error')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (expirationMethod.value === 'views' && !expirationViews.value) {
|
if (expirationMethod.value === 'views' && !expirationViews.value) {
|
||||||
alert('请输入查看次数')
|
showAlertMessage('请输入查看次数', 'error')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,16 +560,21 @@ const handleSubmit = () => {
|
|||||||
id: sendRecords.value.length + 1,
|
id: sendRecords.value.length + 1,
|
||||||
filename: selectedFile.value ? selectedFile.value.name : '文本内容.txt',
|
filename: selectedFile.value ? selectedFile.value.name : '文本内容.txt',
|
||||||
date: new Date().toISOString().split('T')[0],
|
date: new Date().toISOString().split('T')[0],
|
||||||
size: selectedFile.value ? `${(selectedFile.value.size / (1024 * 1024)).toFixed(1)} MB` : '0.1 MB',
|
size: selectedFile.value
|
||||||
expiration: expirationMethod.value === 'time'
|
? `${(selectedFile.value.size / (1024 * 1024)).toFixed(1)} MB`
|
||||||
? new Date(Date.now() + parseInt(expirationTime.value) * 60 * 60 * 1000).toISOString().split('T')[0]
|
: '0.1 MB',
|
||||||
: `${expirationViews.value}次查看后过期`,
|
expiration:
|
||||||
|
expirationMethod.value === 'time'
|
||||||
|
? new Date(Date.now() + parseInt(expirationTime.value) * 60 * 60 * 1000)
|
||||||
|
.toISOString()
|
||||||
|
.split('T')[0]
|
||||||
|
: `${expirationViews.value}次查看后过期`,
|
||||||
retrieveCode: Math.random().toString(36).substring(2, 8).toUpperCase()
|
retrieveCode: Math.random().toString(36).substring(2, 8).toUpperCase()
|
||||||
}
|
}
|
||||||
sendRecords.value.unshift(newRecord)
|
sendRecords.value.unshift(newRecord)
|
||||||
|
|
||||||
// 显示发送成功消息
|
// 显示发送成功消息
|
||||||
alert('文件发送成功!取件码:' + newRecord.retrieveCode)
|
showAlertMessage(`文件发送成功!取件码:${newRecord.retrieveCode}`, 'success')
|
||||||
|
|
||||||
// 重置表单
|
// 重置表单
|
||||||
selectedFile.value = null
|
selectedFile.value = null
|
||||||
@@ -395,27 +592,27 @@ const toggleDrawer = () => {
|
|||||||
showDrawer.value = !showDrawer.value
|
showDrawer.value = !showDrawer.value
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewDetails = (record:any) => {
|
const viewDetails = (record: any) => {
|
||||||
selectedRecord.value = record
|
selectedRecord.value = record
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteRecord = (id:any) => {
|
const deleteRecord = (id: any) => {
|
||||||
sendRecords.value = sendRecords.value.filter(record => record.id !== id)
|
sendRecords.value = sendRecords.value.filter((record) => record.id !== id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getQRCodeValue = (record:any) => {
|
const getQRCodeValue = (record: any) => {
|
||||||
// 这里返回你想要在二维码中编码的信息
|
// 这里返回你想要在二维码中编码的信息
|
||||||
// 例如,可以是一个包含文件ID和取件码的URL
|
// 例如,可以是一个包含文件ID和取件码的URL
|
||||||
return `https://your-domain.com/retrieve/${record.id}?code=${record.retrieveCode}`
|
return `https://your-domain.com/retrieve/${record.id}?code=${record.retrieveCode}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const copyRetrieveCode = async (code:any) => {
|
const copyRetrieveCode = async (code: string) => {
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(code)
|
await navigator.clipboard.writeText(code)
|
||||||
alert('取件码已复制到剪贴板')
|
showAlertMessage('取件码已复制到剪贴板', 'success')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('无法复制取件码: ', err)
|
console.error('无法复制取件码: ', err)
|
||||||
alert('复制失败,请手动复制取件码')
|
showAlertMessage('复制失败,请手动复制取件码', 'error')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -429,7 +626,9 @@ onMounted(() => {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.fade-enter-active,
|
.fade-enter-active,
|
||||||
.fade-leave-active {
|
.fade-leave-active {
|
||||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
transition:
|
||||||
|
opacity 0.3s ease,
|
||||||
|
transform 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fade-enter-from,
|
.fade-enter-from,
|
||||||
|
|||||||
@@ -6,4 +6,3 @@ export default {
|
|||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user