feat : 优化文件上传 优化一体机体验

This commit is contained in:
2025-12-18 18:53:16 +08:00
parent 284d696b25
commit bb67e9ba12
11 changed files with 919 additions and 369 deletions

View File

@@ -10,6 +10,7 @@ const { $api, navTo, appendScriptTagElement, aes_Decrypt, sm2_Decrypt, safeReLau
import config from '@/config.js';
import baseDB from '@/utils/db.js';
import { $confirm } from '@/utils/modal.js';
import useLocationStore from '@/stores/useLocationStore';
usePageAnimation();
const appword = 'aKd20dbGdFvmuwrt'; // 固定值
let uQRListen = null;
@@ -31,6 +32,7 @@ onLaunch((options) => {
useUserStore().logOutApp();
useUserStore().changMiniProgramAppStatus(true);
useUserStore().changMachineEnv(true);
useLocationStore().getLocation();
uQRListen = new IncreaseRevie();
inactivityManager = new GlobalInactivityManager(handleInactivity, 60 * 1000);
inactivityManager.start();

View File

@@ -19,11 +19,14 @@
</template>
<script setup>
import { storeToRefs } from 'pinia';
import { ref, onMounted, computed } from 'vue';
import { useReadMsg } from '@/stores/useReadMsg';
import useScreenStore from '@/stores/useScreenStore'
import useUserStore from '@/stores/useUserStore';
const { isMachineEnv } = storeToRefs(useUserStore());
const screenStore = useScreenStore()
const {isWideScreen} = screenStore
@@ -72,6 +75,7 @@ const tabbarList = computed(() => [
selectedIconPath: '../../static/tabbar/chat4ed.png',
centerItem: false,
badge: readMsg.badges[3].count,
needLogin:true,
},
{
id: 4,
@@ -81,6 +85,7 @@ const tabbarList = computed(() => [
selectedIconPath: '../../static/tabbar/mined.png',
centerItem: false,
badge: readMsg.badges[4].count,
needLogin:true,
},
]);
@@ -90,9 +95,14 @@ onMounted(() => {
});
const changeItem = (item) => {
if(isMachineEnv.value && item.needLogin){
useUserStore().logOut()
}else{
uni.switchTab({
url: item.path,
});
}
};
</script>

View File

@@ -59,7 +59,7 @@ export default {
allowedFileTypes: [
"text/plain", // .txt
"text/markdown", // .md
"text/html", // .html
// "text/html", // .html
"application/msword", // .doc
"application/vnd.openxmlformats-officedocument.wordprocessingml.document", // .docx
"application/pdf", // .pdf

View File

@@ -1,6 +1,6 @@
{
"name" : "qingdao-employment-service",
"appid": "__UNI__C939371",
"appid" : "__UNI__2496162",
"description" : "招聘",
"versionName" : "1.0.0",
"versionCode" : "100",

View File

@@ -5,6 +5,7 @@
<image src="@/static/icon/back.png" @click="navBack"></image>
</view>
</template>
<view v-show="searchFocus" class="search-mask" ></view>
<view class="main">
<view class="content-title">
<view class="title-lf">
@@ -74,7 +75,7 @@ const { $api, navBack } = inject('globalFunction');
import { storeToRefs } from 'pinia';
import useUserStore from '@/stores/useUserStore';
const { getUserResume } = useUserStore();
const { userInfo, isMiniProgram } = storeToRefs(useUserStore());
const { userInfo, isMiniProgram ,hasLogin,isMachineEnv} = storeToRefs(useUserStore());
const popup = ref(null);
const selectJobsModel = ref(null);
@@ -83,6 +84,7 @@ const dataSource = ref([]);
const filterList = ref([]);
const dataItem = ref(null);
const inputVal = ref('');
const searchFocus = ref(false);
onLoad(() => {
getTree();
@@ -93,12 +95,14 @@ function close() {
}
function handleBlur() {
searchFocus.value = false
setTimeout(() => {
filterList.value = [];
}, 100);
}
function handleFocus() {
searchFocus.value = true
const val = inputVal.value.toLowerCase();
if (val && dataSource.value) {
filterList.value = dataSource.value.filter((item) => item.lable.toLowerCase().search(val) !== -1);
@@ -141,6 +145,10 @@ function deleteItem(item, index) {
}
function changeJobs() {
if(isMachineEnv.value && !hasLogin.value){
useUserStore().logOut()
return
}
selectJobsModel.value?.open({
title: '添加岗位',
maskClick: true,
@@ -217,6 +225,7 @@ function flattenTree(treeData, parentPath = '') {
align-items: center;
padding: 0 24rpx;
position: relative;
z-index:10;
.search-input{
flex: 1;
padding: 0 20rpx;
@@ -261,6 +270,15 @@ function flattenTree(treeData, parentPath = '') {
width: 100%;
}
}
.search-mask{
position fixed;
width 100vw;
height:100vh;
top:0;
left:0
z-index:9;
background: #33333355;
}
.main{
padding: 54rpx 28rpx 28rpx 28rpx
.content-list{

View File

@@ -44,7 +44,7 @@
class="msg-files btn-light"
v-for="(file, vInex) in msg.files"
:key="vInex"
@click="jumpUrl(file)"
@click="handleMsgFileClick(file)"
>
<image class="msg-file-icon" src="/static/icon/Vector2.png"></image>
<text class="msg-file-text">{{ file.name || '附件' }}</text>
@@ -216,9 +216,10 @@
>
<image
class="file-iconImg"
@click="jumpUrl(file)"
@click="preViewImage(file)"
v-if="isImage(file.type)"
:src="file.url"
mode="scaleToFill"
></image>
<view class="file-doc" @click="jumpUrl(file)" v-else>
<FileIcon class="doc-icon" :type="file.type"></FileIcon>
@@ -241,7 +242,7 @@
</view>
</view>
<PopupFeeBack ref="feeback" @onClose="colseFeeBack" @onSend="confirmFeeBack"></PopupFeeBack>
<UploadQrcode ref="qrcodeRef" @onSend="handleFileSend" :sessionId="chatSessionID"></UploadQrcode>
<UploadQrcode ref="qrcodeRef" @onSend="handleFileSend" :leaveFileCount="leaveFileCount" :sessionId="chatSessionID"></UploadQrcode>
<MsgTips ref="feeBackTips" content="已收到反馈,感谢您的关注" title="反馈成功" :icon="successIcon"></MsgTips>
</view>
</template>
@@ -341,6 +342,10 @@ const audiowaveStyle = computed(() => {
: '#f1f1f1';
});
const leaveFileCount = computed(()=>{ //还剩多少文件可以上传 给扫码传参使用
return config.allowedFileNumber - filesList.value.length
})
onMounted(async () => {
changeQueries();
scrollToBottom();
@@ -484,11 +489,40 @@ function isFile(type) {
function jumpUrl(file) {
if (file.url) {
window.open(file.url);
if(!isMachineEnv.value)window.open(file.url);
else $api.msg('该文件无法预览');
} else {
$api.msg('文件地址丢失');
}
}
function preViewImage(file) {
if (file.url) {
uni.previewImage({
urls: [file.url]
})
} else {
$api.msg('文件地址丢失');
}
}
function handleMsgFileClick(file) {
if(isImage(file.type)){
if (file.url) {
uni.previewImage({
urls: [file.url]
})
} else {
$api.msg('文件地址丢失');
}
}else{
if (file.url) {
if(!isMachineEnv.value)window.open(file.url);
else $api.msg('该文件无法预览');
} else {
$api.msg('文件地址丢失');
}
}
}
function VerifyNumberFiles(num) {
if (filesList.value.length >= config.allowedFileNumber) {
@@ -535,7 +569,7 @@ function getUploadFile(type = 'camera') {
const allowedTypes = config.allowedFileTypes || [];
const size = $api.formatFileSize(file.size);
if (!allowedTypes.includes(file.type)) {
return $api.msg('仅支持 txt md html word pdf ppt csv excel 格式类型');
return $api.msg('仅支持 txt md word pdf ppt csv excel 格式类型');
}
// 继续上传
$api.uploadFile(tempFilePaths[0], true).then((resData) => {
@@ -553,20 +587,19 @@ function getUploadFile(type = 'camera') {
}
const handleFileSend = (rows)=>{
filesList.value = []
try {
rows.map(item=>{
if(isImage(item.fileSuffix)){
filesList.value.push({
url: item.fileUrl,
type: item.fileSuffix,
name: item.fileName,
name: item.originalName,
});
}else{
filesList.value.push({
url: item.fileUrl,
type: item.fileSuffix,
name: item.fileName,
name: item.originalName,
});
}
})
@@ -639,9 +672,13 @@ function changeVoice() {
function changeShowFile() {
if(isMachineEnv.value){
if (filesList.value.length >= config.allowedFileNumber){
return $api.msg(`最大上传文件数量 ${config.allowedFileNumber}`);
}else{
qrcodeRef.value?.open()
return
}
}
showfile.value = !showfile.value;
}
@@ -1118,7 +1155,7 @@ image-margin-top = 40rpx
color: #7B7B7B;
max-width: 100%
.file-iconImg
height: 100%
// height: 100%
width: 100%
.filerow
display: flex

View File

@@ -32,7 +32,16 @@ const fileAbbreviation = computed(() => {
'xlsx': 'XLSX',
'md':'MD',
'txt':'TXT',
'html':'HTML'
'html':'HTML',
'jpg':'JPG',
'img':'IMG',
'png':'PNG',
'jpeg':'JPEG',
'gif':'GIF',
'webp':'WEBP',
'svg':'SVG',
'tiff':'TIFF',
};
return typeMap[props.type] || 'OTHER';
});

View File

@@ -1,8 +1,8 @@
<template>
<uni-popup ref="popup" type="center" borderRadius="12px 12px 0 0" @change="changePopup">
<view class="popup-inner">
<view class="title">请扫码上传附件</view>
<view class="img-box">
<view v-show="!fileCount" class="title">请扫码上传附件</view>
<view v-show="!fileCount" class="img-box">
<!-- 加载状态 -->
<view v-if="loading" class="loading-container">
<uni-load-more status="loading" :icon-size="24" :content-text="loadingText" />
@@ -14,7 +14,25 @@
<span class="num">{{ fileCount }}</span>
个文件
</view>
<view class="tips" v-if="!loading">请使用手机扫描二维码上传文件</view>
<view v-show="!fileCount" class="tips" v-if="!loading">请使用手机扫描二维码上传文件</view>
<view v-show="fileCount" class="file-list">
<view v-for="(file, index) in fileList" class="file-item">
<image
class="file-icon"
@click="preViewImage(file)"
v-if="isImage(file.fileSuffix)"
:src="file.fileUrl"
mode="scaleToFill"
></image>
<FileIcon v-else class="file-icon" :type="file.fileSuffix"></FileIcon>
<view class="right">
<view class="file-name">{{ file.originalName }}</view>
<FileText :type="file.fileSuffix"></FileText>
</view>
<view class="remove-btn" @click="delFile(file, index)">×</view>
</view>
</view>
<view v-show="fileCount" class="confirm-btn btn-feel" @click="handleConfirm">确认</view>
<view class="close-btn" @click="close"></view>
</view>
</uni-popup>
@@ -22,6 +40,8 @@
<script setup>
import { ref, inject, onUnmounted, watch, computed } from 'vue';
import FileIcon from './fileIcon.vue';
import FileText from './fileText.vue';
import uQRCode from '@/static/js/qrcode';
import config from '@/config';
import { onShow, onHide } from '@dcloudio/uni-app';
@@ -31,6 +51,10 @@ const props = defineProps({
type: [Number, String],
default: '',
},
leaveFileCount: {
type: [Number, String],
default: 2,
},
});
const emit = defineEmits(['onSend', 'onClose']);
@@ -43,6 +67,8 @@ const isPolling = ref(false);
const isVisible = ref(false);
const uuid = ref(null);
const fileCount = ref(0);
const fileList = ref([]);
const delFiles = ref([]); //本地记录删除的文件
// 计算加载文本
const loadingText = computed(() => ({
@@ -55,6 +81,44 @@ onUnmounted(() => {
clearResources();
});
function isImage(type) {
if (!type || typeof type !== 'string') return false;
const imageTypes = [
'jpg',
'jpeg',
'png',
'gif',
'bmp',
'webp',
'svg',
'tiff',
'tif',
'ico',
'apng',
'avif',
'heic',
'heif',
'jfif',
];
const lowerType = type.toLowerCase();
return imageTypes.some((item) => lowerType.includes(item));
}
function preViewImage(file) {
if (file.fileUrl) {
uni.previewImage({
urls: [file.fileUrl],
});
} else {
$api.msg('文件地址丢失');
}
}
function delFile(file, idx) {
fileList.value.splice(idx, 1);
fileCount.value = fileList.value.length
delFiles.value.push(file.fileUrl);
}
function open() {
uuid.value = UUID.generate();
resetState();
@@ -78,9 +142,16 @@ function changePopup(e) {
}
}
function handleConfirm() {
emit('onSend', fileList.value);
close();
}
// 重置所有状态
function resetState() {
fileCount.value = 0
delFiles.value = []
fileList.value = [];
fileCount.value = 0;
loading.value = false;
stopPolling();
}
@@ -112,7 +183,7 @@ function makeQrcode() {
const host = window.location.host;
const isLocal = host.includes('localhost') || host.includes('127.0.0.1');
const pathPrefix = isLocal ? '' : '/rgpp-api/all-in-one';
const htmlPath = `${protocol}//${host}${pathPrefix}/static/upload.html?sessionId=${uuid.value}&uploadApi=${config.baseUrl}/app/kiosk/upload`;
const htmlPath = `${protocol}//${host}${pathPrefix}/static/upload.html?sessionId=${uuid.value}&uploadApi=${config.baseUrl}/app/kiosk/upload&fileCount=${props.leaveFileCount}`;
// const htmlPath = `${window.location.host}/static/upload.html?sessionId=${uuid.value}&uploadApi=${
// config.baseUrl + '/app/kiosk/upload'
@@ -121,6 +192,7 @@ function makeQrcode() {
// config.baseUrl + '/app/kiosk/upload'
// }`;
console.log(htmlPath);
console.log('剩余可上传文件数量:',props.leaveFileCount)
return new Promise((resolve, reject) => {
setTimeout(() => {
uQRCode.make({
@@ -158,8 +230,9 @@ function startPolling() {
// const { data } = await $api.createRequest('/app/kiosk/list',{sessionId:props.sessionId});
if (data && data.length) {
// 上传完成,触发事件
fileCount.value = data.length;
emit('onSend', data);
fileList.value = data.filter((item) => !delFiles.value.includes(item.fileUrl))
fileCount.value = fileList.value.length;
// emit('onSend', data);
}
if (isPolling.value && isVisible.value) {
pollingTimer.value = setTimeout(poll, 2000); // 每2秒轮询一次
@@ -188,40 +261,216 @@ defineExpose({ open, close });
<style lang="scss" scoped>
.popup-inner {
padding: 40rpx 30rpx;
padding-bottom: 50rpx;
width: 440rpx;
padding: 40rpx 30rpx 50rpx;
width: 520rpx;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
border-radius: 20rpx;
background: #fafafa;
background: linear-gradient(135deg, #fafafa 0%, #f0f7ff 100%);
box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.15);
}
.title {
font-size: 32rpx;
color: #333;
color: #1a1a1a;
margin-bottom: 30rpx;
font-weight: bold;
font-weight: 600;
text-align: center;
position: relative;
padding-bottom: 16rpx;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60rpx;
height: 4rpx;
background: linear-gradient(90deg, #4191fe 0%, #256bfa 100%);
border-radius: 2rpx;
}
}
.img-box {
margin: 0 auto 30rpx;
width: 300rpx;
height: 300rpx;
background-color: #ffffff;
border-radius: 10rpx;
background: linear-gradient(145deg, #ffffff 0%, #f8faff 100%);
border-radius: 16rpx;
overflow: hidden;
position: relative;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.3);
box-shadow: 0 8rpx 32rpx rgba(65, 145, 254, 0.2);
padding: 20rpx;
border: 2rpx solid rgba(65, 145, 254, 0.1);
canvas {
width: 100%;
height: 100%;
transition: opacity 0.3s ease;
border-radius: 8rpx;
transition: all 0.3s ease;
animation: canvasFadeIn 0.5s ease;
}
}
@keyframes canvasFadeIn {
0% {
opacity: 0;
transform: scale(0.95);
}
100% {
opacity: 1;
transform: scale(1);
}
}
@keyframes fadeIn {
0% {
opacity: 0;
transform: translateY(20rpx);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
.file-list {
margin-top: 30rpx;
width: 470rpx;
max-height: 60vh;
overflow-x: hidden;
overflow-y: auto;
padding-right: 10rpx;
.file-item {
width: 100%;
padding: 25rpx;
padding-right: 15rpx;
box-sizing: border-box;
border: 1px solid #bbc5d1;
display: flex;
align-items: center;
animation: fadeIn 0.5s ease;
background: linear-gradient(135deg, #ffffff 0%, #f0f8ff 100%);
border-radius: 16rpx;
margin-bottom: 20rpx;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
left: 0;
top: 0;
width: 6rpx;
height: 100%;
background: linear-gradient(180deg, #4191fe 0%, #256bfa 100%);
border-radius: 3rpx 0 0 3rpx;
}
&:hover {
transform: translateY(-2rpx);
box-shadow: 0 8rpx 24rpx rgba(65, 145, 254, 0.15);
}
.file-icon {
width: 80rpx;
height: 80rpx;
margin-right: 20rpx;
border-radius: 12rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
transition: transform 0.3s ease;
&:active {
transform: scale(0.95);
}
}
.right {
flex: 1;
overflow: hidden;
min-width: 0;
.file-name {
font-size: 30rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-weight: 500;
color: #1a1a1a;
margin-bottom: 8rpx;
transition: color 0.3s ease;
}
.file-size {
font-size: 24rpx;
color: #838383;
}
}
.remove-btn {
background: none;
border: none;
color: #ff6b6b;
font-size: 52rpx;
cursor: pointer;
margin-left: 20rpx;
flex-shrink: 0;
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.3s ease;
margin-top: -15rpx;
&:active {
background-color: rgba(255, 107, 107, 0.1);
transform: scale(0.9);
}
}
}
}
.confirm-btn {
font-weight: 500;
font-size: 32rpx;
color: #ffffff;
text-align: center;
width: 350rpx;
height: 80rpx;
line-height: 80rpx;
background: linear-gradient(135deg, #4191fe 0%, #256bfa 100%);
border-radius: 16rpx;
margin-top: 30rpx;
transition: all 0.3s ease;
box-shadow: 0 8rpx 24rpx rgba(37, 107, 250, 0.3);
position: relative;
overflow: hidden;
&::after {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.6s ease;
}
&:active {
transform: scale(0.98);
box-shadow: 0 4rpx 16rpx rgba(37, 107, 250, 0.4);
&::after {
left: 100%;
}
}
}
@@ -231,18 +480,25 @@ defineExpose({ open, close });
display: flex;
align-items: center;
justify-content: center;
background-color: #ffffff;
background: linear-gradient(145deg, #ffffff 0%, #f8faff 100%);
border-radius: 16rpx;
}
.tips {
font-size: 24rpx;
font-size: 26rpx;
color: #666;
text-align: center;
line-height: 1.6;
padding: 0 20rpx;
margin-top: 10rpx;
animation: fadeIn 0.5s ease;
.num {
color: #4191fe;
font-size: 28rpx;
font-weight: 600;
margin: 0 8rpx;
position: relative;
}
}
@@ -253,39 +509,40 @@ defineExpose({ open, close });
width: 56rpx;
height: 56rpx;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.8);
background: linear-gradient(135deg, #ffffff 0%, #f8faff 100%);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background-color 0.2s;
transition: all 0.3s ease;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
z-index: 10;
&:active {
background-color: rgba(0, 0, 0, 0.1);
transform: scale(0.9) rotate(90deg);
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.15);
}
&::before,
&::after {
content: '';
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 24rpx;
height: 2rpx;
background: #5a5a68;
border-radius: 1rpx;
transition: all 0.3s ease;
}
&::before {
position: absolute;
left: 50%;
top: 50%;
content: '';
width: 4rpx;
height: 28rpx;
border-radius: 2rpx;
background: #5a5a68;
transform: translate(50%, -50%) rotate(-45deg);
transform: translate(-50%, -50%) rotate(45deg);
}
&::after {
position: absolute;
left: 50%;
top: 50%;
content: '';
width: 4rpx;
height: 28rpx;
border-radius: 2rpx;
background: #5a5a68;
transform: translate(50%, -50%) rotate(45deg);
transform: translate(-50%, -50%) rotate(-45deg);
}
}
</style>

View File

@@ -62,38 +62,6 @@
margin-top: 10px;
}
.code-card {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
border-radius: 16px;
padding: 20px;
margin-bottom: 25px;
text-align: center;
border: 1px solid rgba(255, 255, 255, 0.3);
}
.code-label {
font-size: 14px;
color: #666;
margin-bottom: 8px;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
}
.code-value {
font-size: 22px;
color: #2d3436;
font-weight: 700;
letter-spacing: 2px;
padding: 12px;
background: white;
border-radius: 12px;
margin-top: 8px;
word-break: break-all;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
.upload-section {
margin-bottom: 25px;
}
@@ -131,6 +99,17 @@
background: rgba(102, 126, 234, 0.05);
}
.upload-area.disabled {
opacity: 0.6;
cursor: not-allowed;
background: #f5f5f5;
border-color: #e0e0e0;
}
.upload-area.disabled:active {
transform: none;
}
.upload-icon {
font-size: 48px;
color: #667eea;
@@ -161,6 +140,10 @@
cursor: pointer;
}
.file-input:disabled {
cursor: not-allowed;
}
.selected-files {
display: none;
}
@@ -516,6 +499,62 @@
font-size: 20px;
}
}
/* 限制提示样式 */
.limit-info {
background: #fff8e1;
border: 1px solid #ffd54f;
border-radius: 12px;
padding: 12px 16px;
margin-bottom: 20px;
font-size: 14px;
color: #f57c00;
display: none;
align-items: center;
gap: 8px;
animation: slideIn 0.3s ease;
}
.limit-info.show {
display: flex;
}
.limit-icon {
font-size: 18px;
flex-shrink: 0;
}
.limit-text {
flex: 1;
}
/* 限制警告提示 */
.limit-warning-info {
background: #ffebee;
border: 1px solid #ffcdd2;
border-radius: 12px;
padding: 12px 16px;
margin-bottom: 20px;
font-size: 14px;
color: #d32f2f;
display: none;
align-items: center;
gap: 8px;
animation: slideIn 0.3s ease;
}
.limit-warning-info.show {
display: flex;
}
.limit-warning-icon {
font-size: 18px;
flex-shrink: 0;
}
.limit-warning-text {
flex: 1;
}
</style>
</head>
<body>
@@ -526,18 +565,30 @@
</div>
<div class="main-content">
<!-- 限制提示 -->
<div class="limit-info" id="limitInfo">
<span class="limit-icon">⚠️</span>
<div class="limit-text" id="limitText">加载中...</div>
</div>
<!-- 总数量限制警告 -->
<div class="limit-warning-info" id="limitWarningInfo">
<span class="limit-warning-icon">⚠️</span>
<div class="limit-warning-text" id="limitWarningText">已超过文件上传总数限制</div>
</div>
<div class="upload-section">
<h3 class="section-title">📎 选择文件</h3>
<div class="upload-area" id="uploadArea">
<div class="upload-icon">📁</div>
<div class="upload-text">点击选择文件</div>
<div class="upload-hint">支持图片、文档、文本等格式<br />单个文件不超过10MB</div>
<div class="upload-text" id="uploadText">点击选择文件</div>
<div class="upload-hint" id="uploadHint">支持图片、文档、文本等格式</div>
<input
type="file"
id="fileInput"
class="file-input"
multiple
accept="image/*,.pdf,.doc,.docx,.xls,.xlsx,.csv,.ppt,.pptx,.txt,.md,.html"
accept="image/*,.pdf,.doc,.docx,.xls,.xlsx,.csv,.ppt,.pptx,.txt,.md"
/>
</div>
</div>
@@ -566,7 +617,7 @@
<!-- 状态提示框 -->
<div class="status-message" id="statusMessage">
<span class="status-icon"></span>
<span class="status-icon" id="statusIcon"></span>
<h3 class="status-title" id="statusTitle">上传成功!</h3>
<p class="status-desc" id="statusDesc">文件已成功上传到电脑端</p>
<button class="status-btn" id="statusBtn">完成</button>
@@ -579,15 +630,24 @@
</div>
<script>
// 获取URL中的code参数
// 获取URL中的参数
const urlParams = new URLSearchParams(window.location.search);
const sessionId = urlParams.get('sessionId');
const uploadApi = urlParams.get('uploadApi');
console.log(sessionId);
console.log(uploadApi);
const fileCountParam = urlParams.get('fileCount');
// 配置常量
const MAX_FILE_COUNT = fileCountParam ? parseInt(fileCountParam) : 2; // 从URL参数获取默认为2
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
console.log('Session ID:', sessionId);
console.log('Upload API:', uploadApi);
console.log('Max file count:', MAX_FILE_COUNT);
// DOM元素
const uploadArea = document.getElementById('uploadArea');
const uploadText = document.getElementById('uploadText');
const uploadHint = document.getElementById('uploadHint');
const fileInput = document.getElementById('fileInput');
const fileList = document.getElementById('fileList');
const selectedFilesContainer = document.getElementById('selectedFiles');
@@ -596,19 +656,34 @@
const loadingOverlay = document.getElementById('loadingOverlay');
const loadingText = document.getElementById('loadingText');
const statusMessage = document.getElementById('statusMessage');
const statusIcon = document.getElementById('statusIcon');
const statusTitle = document.getElementById('statusTitle');
const statusDesc = document.getElementById('statusDesc');
const statusBtn = document.getElementById('statusBtn');
const imagePreviewModal = document.getElementById('imagePreviewModal');
const previewImage = document.getElementById('previewImage');
const closePreview = document.getElementById('closePreview');
const limitInfo = document.getElementById('limitInfo');
const limitText = document.getElementById('limitText');
const limitWarningInfo = document.getElementById('limitWarningInfo');
const limitWarningText = document.getElementById('limitWarningText');
// 状态变量
let selectedFiles = [];
let isUploading = false;
let uploadedCount = 0; // 已上传的总文件数量
// 初始化
function init() {
// 更新限制提示文本
updateLimitText();
// 显示限制提示
limitInfo.classList.add('show');
// 检查本地存储中已上传的文件数量
checkUploadedCount();
// 事件监听
fileInput.addEventListener('change', handleFileSelect);
clearBtn.addEventListener('click', clearAllFiles);
@@ -632,6 +707,46 @@
// 防止页面滚动
document.body.addEventListener('touchmove', preventScroll, { passive: false });
// 更新UI
updateUI();
}
// 更新限制提示文本
function updateLimitText() {
if (MAX_FILE_COUNT <= 0) {
limitText.textContent = '未设置可上传文件数量';
} else {
limitText.textContent = `总共可上传 ${MAX_FILE_COUNT} 个文件每个文件不超过10MB已上传: ${uploadedCount}`;
}
}
// 检查已上传的文件数量
function checkUploadedCount() {
// 使用sessionStorage存储当前会话的上传数量
// 如果要永久存储可以改用localStorage
const storedCount = sessionStorage.getItem('uploadedFileCount');
uploadedCount = storedCount ? parseInt(storedCount) : 0;
// 更新警告提示
updateWarningText();
}
// 更新警告提示
function updateWarningText() {
if (MAX_FILE_COUNT > 0 && uploadedCount >= MAX_FILE_COUNT) {
limitWarningInfo.classList.add('show');
limitWarningText.textContent = `已超过文件上传总数限制(${MAX_FILE_COUNT}个)`;
} else {
limitWarningInfo.classList.remove('show');
}
}
// 保存已上传的文件数量
function saveUploadedCount() {
sessionStorage.setItem('uploadedFileCount', uploadedCount.toString());
updateLimitText();
updateWarningText();
}
// 处理文件选择
@@ -646,6 +761,12 @@
// 设置拖拽上传
function setupDragAndDrop() {
uploadArea.addEventListener('dragover', (e) => {
// 如果已达总数限制,禁止拖拽
if (MAX_FILE_COUNT > 0 && uploadedCount >= MAX_FILE_COUNT) {
e.preventDefault();
return;
}
e.preventDefault();
uploadArea.classList.add('dragover');
});
@@ -659,6 +780,12 @@
e.preventDefault();
uploadArea.classList.remove('dragover');
// 如果已达总数限制,禁止拖拽上传
if (MAX_FILE_COUNT > 0 && uploadedCount >= MAX_FILE_COUNT) {
showError(`已超过文件上传总数限制(${MAX_FILE_COUNT}个)`);
return;
}
if (e.dataTransfer.files.length) {
handleFiles(e.dataTransfer.files);
}
@@ -667,12 +794,41 @@
// 处理文件
function handleFiles(files) {
const maxSize = 20 * 1024 * 1024; // 20MB
// 检查是否已达到总文件数量上限
if (MAX_FILE_COUNT > 0 && uploadedCount >= MAX_FILE_COUNT) {
showError(`已超过文件上传总数限制(${MAX_FILE_COUNT}个)`);
return;
}
// 检查当前选择的文件是否会导致超过总数限制
const potentialTotalCount = uploadedCount + files.length;
if (MAX_FILE_COUNT > 0 && potentialTotalCount > MAX_FILE_COUNT) {
showError(`最多只能上传 ${MAX_FILE_COUNT} 个文件,已上传 ${uploadedCount}`);
return;
}
// 检查是否已达到本次选择的文件数量上限
if (selectedFiles.length >= MAX_FILE_COUNT) {
showError(`最多只能选择 ${MAX_FILE_COUNT} 个文件`);
return;
}
for (let file of files) {
// 检查是否已达到本次选择的文件数量上限
if (selectedFiles.length >= MAX_FILE_COUNT) {
showError(`最多只能选择 ${MAX_FILE_COUNT} 个文件,已忽略多余文件`);
break;
}
// 检查是否会导致超过总数限制
if (MAX_FILE_COUNT > 0 && (uploadedCount + selectedFiles.length) >= MAX_FILE_COUNT) {
showError(`已超过文件上传总数限制(${MAX_FILE_COUNT}个)`);
break;
}
// 检查文件大小
if (file.size > maxSize) {
showError(`文件 ${file.name} 超过20MB限制`);
if (file.size > MAX_FILE_SIZE) {
showError(`文件 ${file.name} 超过10MB限制`);
continue;
}
@@ -843,18 +999,55 @@
// 更新UI状态
function updateUI() {
const hasFiles = selectedFiles.length > 0;
const isTotalLimitReached = MAX_FILE_COUNT > 0 && uploadedCount >= MAX_FILE_COUNT;
// 显示/隐藏文件列表
selectedFilesContainer.classList.toggle('show', hasFiles);
// 检查是否已达总文件数量上限
if (isTotalLimitReached) {
// 禁用所有上传相关功能
uploadArea.classList.add('disabled');
fileInput.disabled = true;
uploadText.textContent = '文件上传总数已达上限';
uploadHint.innerHTML = `总共可上传 ${MAX_FILE_COUNT} 个文件<br />已上传: ${uploadedCount}`;
// 禁用按钮
clearBtn.disabled = true;
uploadBtn.disabled = true;
// 显示限制警告提示
limitWarningInfo.classList.add('show');
limitWarningText.textContent = `已超过文件上传总数限制(${MAX_FILE_COUNT}个)`;
} else {
// 更新上传区域状态
uploadArea.classList.remove('disabled');
// 根据文件数量更新上传区域
if (MAX_FILE_COUNT <= 0) {
// 如果没有设置文件数量限制,始终可以上传
uploadText.textContent = '点击选择文件';
uploadHint.innerHTML = '支持图片、文档、文本等格式<br />文件大小不超过10MB';
fileInput.disabled = false;
} else if (selectedFiles.length >= MAX_FILE_COUNT) {
uploadText.textContent = `已达到最大文件数量(${MAX_FILE_COUNT}个)`;
uploadHint.innerHTML = `最多${MAX_FILE_COUNT}个文件每个不超过10MB<br /><strong>已选满</strong>`;
fileInput.disabled = true;
} else {
uploadText.textContent = '点击选择文件';
uploadHint.innerHTML = `支持图片、文档、文本等格式<br />最多${MAX_FILE_COUNT}个文件每个不超过10MB<br />已上传: ${uploadedCount}/${MAX_FILE_COUNT}`;
fileInput.disabled = false;
}
// 更新按钮状态
clearBtn.disabled = !hasFiles || isUploading;
uploadBtn.disabled = !hasFiles || isUploading;
}
// 更新上传按钮文本
if (hasFiles) {
if (hasFiles && !isTotalLimitReached) {
const totalSize = selectedFiles.reduce((sum, file) => sum + file.size, 0);
uploadBtn.innerHTML = `<span>⬆️</span> 上传 (${selectedFiles.length}, ${formatFileSize(
uploadBtn.innerHTML = `<span>⬆️</span> 上传 (${selectedFiles.length}/${MAX_FILE_COUNT}, ${formatFileSize(
totalSize
)})`;
} else {
@@ -866,6 +1059,19 @@
async function startUpload() {
if (isUploading || selectedFiles.length === 0) return;
// 检查是否已达总数限制
if (MAX_FILE_COUNT > 0 && uploadedCount >= MAX_FILE_COUNT) {
showError(`已超过文件上传总数限制(${MAX_FILE_COUNT}个)`);
return;
}
// 检查是否会导致超过总数限制
const potentialTotalCount = uploadedCount + selectedFiles.length;
if (MAX_FILE_COUNT > 0 && potentialTotalCount > MAX_FILE_COUNT) {
showError(`最多只能上传 ${MAX_FILE_COUNT} 个文件,已上传 ${uploadedCount}`);
return;
}
isUploading = true;
updateUI();
showLoading('正在准备上传...');
@@ -903,6 +1109,7 @@
} catch (error) {
console.error(`文件上传失败: ${file.name}`, error);
failCount++;
const progressBar = document.querySelector(`#progress-${CSS.escape(file.name)}`);
if (progressBar) {
progressBar.style.width = '0%';
}
@@ -913,9 +1120,15 @@
hideLoading();
if (successCount > 0) {
// 更新已上传的文件数量
uploadedCount += successCount;
saveUploadedCount();
showSuccess(`成功上传 ${successCount} 个文件${failCount > 0 ? `${failCount} 个失败` : ''}`);
// 清空文件列表
clearAllFiles();
// 更新UI
updateUI();
} else {
showError('文件上传失败,请重试');
}
@@ -984,6 +1197,7 @@
// 显示成功提示
function showSuccess(message) {
statusMessage.className = 'status-message status-success';
statusIcon.textContent = '✅';
statusTitle.textContent = '上传成功!';
statusDesc.textContent = message;
statusMessage.style.display = 'block';
@@ -993,6 +1207,7 @@
// 显示错误提示
function showError(message) {
statusMessage.className = 'status-message status-error';
statusIcon.textContent = '❌';
statusTitle.textContent = '出错了';
statusDesc.textContent = message;
statusMessage.style.display = 'block';

View File

@@ -84,6 +84,8 @@ const useLocationStore = defineStore("location", () => {
latitudeVal
}
},{
unistorage: true,
})
export default useLocationStore;

View File

@@ -9,5 +9,5 @@ export default defineConfig({
// 只保留这一个自定义插件,它只负责改 template 字符串
viteInjectPopup(),
uni(),
]
],
});