ai测试
This commit is contained in:
parent
37f9b0b6e8
commit
be1663f21f
456
components/task/AttachmentFileUploader.vue
Normal file
456
components/task/AttachmentFileUploader.vue
Normal file
|
|
@ -0,0 +1,456 @@
|
||||||
|
<template>
|
||||||
|
<view class="attachment-block">
|
||||||
|
<view class="form-item clickable-item" @click="handleChooseFiles">
|
||||||
|
<view class="form-icon">{{ icon }}</view>
|
||||||
|
<text class="form-label">{{ title }}</text>
|
||||||
|
<text class="arrow">›</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="files-list" v-if="files.length">
|
||||||
|
<view
|
||||||
|
class="file-item"
|
||||||
|
v-for="(file, index) in files"
|
||||||
|
:key="file.path + index"
|
||||||
|
@click="previewFile(file)"
|
||||||
|
>
|
||||||
|
<text class="file-icon">{{ getFileIcon(file.name) }}</text>
|
||||||
|
<view class="file-info">
|
||||||
|
<text class="file-name">{{ file.name }}</text>
|
||||||
|
<text class="file-size" v-if="file.size > 0">{{ formatFileSize(file.size) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="remove-btn" @click.stop="removeFile(index)">✕</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { batchUploadFilesToQiniu } from '@/utils/qiniu.js';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
maxCount: {
|
||||||
|
type: Number,
|
||||||
|
default: 5
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: '添加文件'
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
default: '📄'
|
||||||
|
},
|
||||||
|
extensions: {
|
||||||
|
type: Array,
|
||||||
|
default: () => ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.txt', '.zip', '.rar', '.jpg', '.png']
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'change']);
|
||||||
|
|
||||||
|
const files = computed({
|
||||||
|
get: () => props.modelValue,
|
||||||
|
set: (val) => {
|
||||||
|
emit('update:modelValue', val);
|
||||||
|
emit('change', val);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleChooseFiles = async () => {
|
||||||
|
const remainingCount = props.maxCount - files.value.length;
|
||||||
|
if (remainingCount <= 0) {
|
||||||
|
uni.showToast({
|
||||||
|
title: `最多只能添加${props.maxCount}个文件`,
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// #ifdef H5 || MP-WEIXIN || APP-PLUS
|
||||||
|
await chooseFilesWithUni(remainingCount);
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifndef H5 || MP-WEIXIN || APP-PLUS
|
||||||
|
await chooseFilesNative(remainingCount);
|
||||||
|
// #endif
|
||||||
|
} catch (error) {
|
||||||
|
console.error('选择文件失败:', error);
|
||||||
|
uni.showToast({
|
||||||
|
title: error?.message || '选择文件失败',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const chooseFilesWithUni = (remainingCount) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
uni.chooseFile({
|
||||||
|
count: remainingCount,
|
||||||
|
extension: props.extensions,
|
||||||
|
success: async (res) => {
|
||||||
|
try {
|
||||||
|
await uploadFiles(res.tempFiles.map(file => ({
|
||||||
|
path: file.path,
|
||||||
|
name: file.name
|
||||||
|
})));
|
||||||
|
resolve();
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: async () => {
|
||||||
|
try {
|
||||||
|
await chooseFilesNative(remainingCount);
|
||||||
|
resolve();
|
||||||
|
} catch (nativeError) {
|
||||||
|
reject(nativeError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const chooseFilesNative = async (remainingCount) => {
|
||||||
|
if (typeof plus === 'undefined') {
|
||||||
|
uni.showToast({
|
||||||
|
title: '当前环境不支持文件选择',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
throw new Error('native file picker not available');
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const Intent = plus.android.importClass('android.content.Intent');
|
||||||
|
const main = plus.android.runtimeMainActivity();
|
||||||
|
const intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
|
intent.setType('*/*');
|
||||||
|
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
|
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
|
||||||
|
|
||||||
|
const originalOnActivityResult = main.onActivityResult;
|
||||||
|
main.startActivityForResult(intent, 1001);
|
||||||
|
|
||||||
|
main.onActivityResult = async (requestCode, resultCode, data) => {
|
||||||
|
if (requestCode === 1001) {
|
||||||
|
if (resultCode === -1 && data) {
|
||||||
|
try {
|
||||||
|
const filesToUpload = extractFilesFromIntent(data, main, remainingCount);
|
||||||
|
if (filesToUpload.length) {
|
||||||
|
await uploadFiles(filesToUpload);
|
||||||
|
}
|
||||||
|
cleanup();
|
||||||
|
resolve();
|
||||||
|
} catch (error) {
|
||||||
|
cleanup();
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cleanup();
|
||||||
|
reject(new Error('用户取消选择'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cleanup();
|
||||||
|
reject(new Error('无效的请求'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const cleanup = () => {
|
||||||
|
if (originalOnActivityResult) {
|
||||||
|
main.onActivityResult = originalOnActivityResult;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const extractFilesFromIntent = (data, main, remainingCount) => {
|
||||||
|
const clipData = data.getClipData();
|
||||||
|
const filesToUpload = [];
|
||||||
|
|
||||||
|
const getFileName = (uri) => {
|
||||||
|
try {
|
||||||
|
const cursor = main.getContentResolver().query(uri, null, null, null, null);
|
||||||
|
if (cursor && cursor.moveToFirst()) {
|
||||||
|
const nameIndex = cursor.getColumnIndex('_display_name');
|
||||||
|
if (nameIndex !== -1) {
|
||||||
|
const fileName = cursor.getString(nameIndex);
|
||||||
|
cursor.close();
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('获取文件名失败:', e);
|
||||||
|
}
|
||||||
|
return `file_${Date.now()}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (clipData) {
|
||||||
|
const count = clipData.getItemCount();
|
||||||
|
for (let i = 0; i < count && filesToUpload.length < remainingCount; i++) {
|
||||||
|
const item = clipData.getItemAt(i);
|
||||||
|
const uri = item.getUri();
|
||||||
|
const uriString = uri.toString();
|
||||||
|
const fileName = getFileName(uri);
|
||||||
|
filesToUpload.push({
|
||||||
|
name: fileName,
|
||||||
|
path: uriString,
|
||||||
|
size: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const uri = data.getData();
|
||||||
|
if (uri) {
|
||||||
|
const uriString = uri.toString();
|
||||||
|
const fileName = getFileName(uri);
|
||||||
|
filesToUpload.push({
|
||||||
|
name: fileName,
|
||||||
|
path: uriString,
|
||||||
|
size: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filesToUpload;
|
||||||
|
};
|
||||||
|
|
||||||
|
const uploadFiles = async (fileList) => {
|
||||||
|
if (!fileList.length) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
uni.showLoading({
|
||||||
|
title: '上传中...',
|
||||||
|
mask: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const uploadResults = await batchUploadFilesToQiniu(fileList);
|
||||||
|
const newFiles = uploadResults.map(result => ({
|
||||||
|
name: result.name,
|
||||||
|
path: result.url,
|
||||||
|
size: result.size || 0
|
||||||
|
}));
|
||||||
|
|
||||||
|
files.value = [...files.value, ...newFiles];
|
||||||
|
uni.showToast({
|
||||||
|
title: `成功添加${newFiles.length}个文件`,
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('上传文件失败:', error);
|
||||||
|
uni.showToast({
|
||||||
|
title: error?.message || '上传文件失败',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
uni.hideLoading();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeFile = (index) => {
|
||||||
|
const next = [...files.value];
|
||||||
|
next.splice(index, 1);
|
||||||
|
files.value = next;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFileIcon = (fileName) => {
|
||||||
|
if (!fileName) return '📄';
|
||||||
|
const ext = fileName.split('.').pop().toLowerCase();
|
||||||
|
const iconMap = {
|
||||||
|
pdf: '📕',
|
||||||
|
doc: '📘',
|
||||||
|
docx: '📘',
|
||||||
|
xls: '📗',
|
||||||
|
xlsx: '📗',
|
||||||
|
ppt: '📙',
|
||||||
|
pptx: '📙',
|
||||||
|
txt: '📄',
|
||||||
|
zip: '📦',
|
||||||
|
rar: '📦',
|
||||||
|
jpg: '🖼️',
|
||||||
|
jpeg: '🖼️',
|
||||||
|
png: '🖼️',
|
||||||
|
gif: '🖼️'
|
||||||
|
};
|
||||||
|
return iconMap[ext] || '📄';
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatFileSize = (bytes) => {
|
||||||
|
if (!bytes || bytes === 0) return '';
|
||||||
|
const k = 1024;
|
||||||
|
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
|
||||||
|
};
|
||||||
|
|
||||||
|
const previewFile = (file) => {
|
||||||
|
if (!file?.path) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '文件路径不存在',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
||||||
|
const ext = (file.name || '').split('.').pop().toLowerCase();
|
||||||
|
|
||||||
|
if (imageExts.includes(ext)) {
|
||||||
|
uni.previewImage({
|
||||||
|
urls: [file.path],
|
||||||
|
current: file.path
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// #ifdef H5
|
||||||
|
window.open(file.path, '_blank');
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
plus.runtime.openURL(file.path);
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifndef H5 || APP-PLUS
|
||||||
|
uni.showToast({
|
||||||
|
title: '点击下载文件',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
uni.downloadFile({
|
||||||
|
url: file.path,
|
||||||
|
success: (res) => {
|
||||||
|
if (res.statusCode === 200) {
|
||||||
|
uni.openDocument({
|
||||||
|
filePath: res.tempFilePath,
|
||||||
|
success: () => {
|
||||||
|
console.log('打开文档成功');
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('打开文档失败:', err);
|
||||||
|
uni.showToast({
|
||||||
|
title: '无法打开此文件',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('下载文件失败:', err);
|
||||||
|
uni.showToast({
|
||||||
|
title: '下载文件失败',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// #endif
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.attachment-block {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clickable-item {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 15px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #999;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.files-list {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-icon {
|
||||||
|
font-size: 24px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-info {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-name {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-size {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-btn {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
175
components/task/AttachmentImageUploader.vue
Normal file
175
components/task/AttachmentImageUploader.vue
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
<template>
|
||||||
|
<view class="attachment-block">
|
||||||
|
<view class="form-item clickable-item" @click="handleChooseImages">
|
||||||
|
<view class="form-icon">{{ icon }}</view>
|
||||||
|
<text class="form-label">{{ title }}</text>
|
||||||
|
<text class="arrow">›</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="images-preview" v-if="images.length">
|
||||||
|
<view
|
||||||
|
class="image-item"
|
||||||
|
v-for="(image, index) in images"
|
||||||
|
:key="image + index"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
:src="image"
|
||||||
|
mode="aspectFill"
|
||||||
|
class="preview-image"
|
||||||
|
@click="previewImage(index)"
|
||||||
|
/>
|
||||||
|
<view class="remove-btn" @click.stop="removeImage(index)">✕</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { chooseAndUploadImages } from '@/utils/qiniu.js';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
maxCount: {
|
||||||
|
type: Number,
|
||||||
|
default: 9
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: '添加照片'
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
default: '🏔️'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'change']);
|
||||||
|
|
||||||
|
const images = computed({
|
||||||
|
get: () => props.modelValue,
|
||||||
|
set: (val) => {
|
||||||
|
emit('update:modelValue', val);
|
||||||
|
emit('change', val);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleChooseImages = async () => {
|
||||||
|
const remainingCount = props.maxCount - images.value.length;
|
||||||
|
if (remainingCount <= 0) {
|
||||||
|
uni.showToast({
|
||||||
|
title: `最多只能添加${props.maxCount}张图片`,
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const urls = await chooseAndUploadImages({
|
||||||
|
count: remainingCount,
|
||||||
|
sizeType: ['original', 'compressed'],
|
||||||
|
sourceType: ['album', 'camera']
|
||||||
|
});
|
||||||
|
images.value = [...images.value, ...urls];
|
||||||
|
} catch (err) {
|
||||||
|
console.error('选择或上传图片失败:', err);
|
||||||
|
uni.showToast({
|
||||||
|
title: err?.message || '选择图片失败',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const previewImage = (index) => {
|
||||||
|
uni.previewImage({
|
||||||
|
urls: images.value,
|
||||||
|
current: index
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeImage = (index) => {
|
||||||
|
const next = [...images.value];
|
||||||
|
next.splice(index, 1);
|
||||||
|
images.value = next;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.attachment-block {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clickable-item {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 15px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #999;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.images-preview {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-item {
|
||||||
|
position: relative;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 6px;
|
||||||
|
right: 6px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
@ -69,46 +69,8 @@
|
||||||
<view class="form-card">
|
<view class="form-card">
|
||||||
<view class="section-title">附件</view>
|
<view class="section-title">附件</view>
|
||||||
<view class="attachment-tip">请上传不超过200MB的文件,支持常见图片、Office、PDF、压缩包等格式。</view>
|
<view class="attachment-tip">请上传不超过200MB的文件,支持常见图片、Office、PDF、压缩包等格式。</view>
|
||||||
<!-- 添加照片 -->
|
<AttachmentImageUploader v-model="formData.images" />
|
||||||
<view class="form-item clickable-item" @click="chooseImages">
|
<AttachmentFileUploader v-model="formData.files" />
|
||||||
<view class="form-icon">🏔️</view>
|
|
||||||
<text class="form-label">添加照片</text>
|
|
||||||
<text class="arrow">›</text>
|
|
||||||
</view>
|
|
||||||
<!-- 照片预览 -->
|
|
||||||
<view class="images-preview" v-if="formData.images.length > 0">
|
|
||||||
<view
|
|
||||||
class="image-item"
|
|
||||||
v-for="(image, index) in formData.images"
|
|
||||||
:key="index"
|
|
||||||
>
|
|
||||||
<image :src="image" mode="aspectFill" class="preview-image" @click="previewImage(index)" />
|
|
||||||
<view class="remove-btn" @click="removeImage(index)">✕</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 添加文件 -->
|
|
||||||
<view class="form-item clickable-item" @click="chooseFiles">
|
|
||||||
<view class="form-icon">📄</view>
|
|
||||||
<text class="form-label">添加文件</text>
|
|
||||||
<text class="arrow">›</text>
|
|
||||||
</view>
|
|
||||||
<!-- 文件列表 -->
|
|
||||||
<view class="files-list" v-if="formData.files.length > 0">
|
|
||||||
<view
|
|
||||||
class="file-item"
|
|
||||||
v-for="(file, index) in formData.files"
|
|
||||||
:key="index"
|
|
||||||
@click="previewFile(file)"
|
|
||||||
>
|
|
||||||
<text class="file-icon">{{ getFileIcon(file.name) }}</text>
|
|
||||||
<view class="file-info">
|
|
||||||
<text class="file-name">{{ file.name }}</text>
|
|
||||||
<text class="file-size" v-if="file.size > 0">{{ formatFileSize(file.size) }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="remove-btn" @click.stop="removeFile(index)">✕</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="form-card">
|
<view class="form-card">
|
||||||
|
|
@ -209,12 +171,11 @@ import { ref, computed } from 'vue';
|
||||||
import { onLoad } from '@dcloudio/uni-app';
|
import { onLoad } from '@dcloudio/uni-app';
|
||||||
import { createTask, getProjectListAll, getUserList } from '@/api';
|
import { createTask, getProjectListAll, getUserList } from '@/api';
|
||||||
import { useDictStore } from '@/store/dict';
|
import { useDictStore } from '@/store/dict';
|
||||||
import { chooseAndUploadImages, batchUploadFilesToQiniu } from '@/utils/qiniu.js';
|
import AttachmentImageUploader from '@/components/task/AttachmentImageUploader.vue';
|
||||||
|
import AttachmentFileUploader from '@/components/task/AttachmentFileUploader.vue';
|
||||||
|
|
||||||
const dictStore = useDictStore();
|
const dictStore = useDictStore();
|
||||||
|
|
||||||
const ATTACHMENT_LIMIT = 9;
|
|
||||||
|
|
||||||
const formData = ref({
|
const formData = ref({
|
||||||
projectId: '',
|
projectId: '',
|
||||||
projectName: '',
|
projectName: '',
|
||||||
|
|
@ -245,28 +206,6 @@ const projectPicker=ref(null);
|
||||||
const expirePickerRef = ref(null);
|
const expirePickerRef = ref(null);
|
||||||
const expirePickerValue = ref(Date.now());
|
const expirePickerValue = ref(Date.now());
|
||||||
|
|
||||||
const getFileIcon = (fileName) => {
|
|
||||||
if (!fileName) return '📄';
|
|
||||||
const ext = fileName.split('.').pop().toLowerCase();
|
|
||||||
const iconMap = {
|
|
||||||
'pdf': '📕',
|
|
||||||
'doc': '📘',
|
|
||||||
'docx': '📘',
|
|
||||||
'xls': '📗',
|
|
||||||
'xlsx': '📗',
|
|
||||||
'ppt': '📙',
|
|
||||||
'pptx': '📙',
|
|
||||||
'txt': '📄',
|
|
||||||
'zip': '📦',
|
|
||||||
'rar': '📦',
|
|
||||||
'jpg': '🖼️',
|
|
||||||
'jpeg': '🖼️',
|
|
||||||
'png': '🖼️',
|
|
||||||
'gif': '🖼️'
|
|
||||||
};
|
|
||||||
return iconMap[ext] || '📄';
|
|
||||||
};
|
|
||||||
|
|
||||||
const typeOptions = computed(() => {
|
const typeOptions = computed(() => {
|
||||||
return dictStore.getDictByType('task_type').map(item => ({
|
return dictStore.getDictByType('task_type').map(item => ({
|
||||||
label: item.dictLabel,
|
label: item.dictLabel,
|
||||||
|
|
@ -418,371 +357,6 @@ const onExpireTimeConfirm = (event) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddAttachment = () => {
|
|
||||||
const remaining = ATTACHMENT_LIMIT - formData.value.attachments.length;
|
|
||||||
if (remaining <= 0) {
|
|
||||||
uni.showToast({
|
|
||||||
title: `最多上传${ATTACHMENT_LIMIT}个附件`,
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uni.showActionSheet({
|
|
||||||
itemList: ['上传图片', '上传文件'],
|
|
||||||
success: ({ tapIndex }) => {
|
|
||||||
if (tapIndex === 0) {
|
|
||||||
chooseImages();
|
|
||||||
} else if (tapIndex === 1) {
|
|
||||||
chooseFiles();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 选择图片并自动上传到七牛云
|
|
||||||
const chooseImages = async () => {
|
|
||||||
try {
|
|
||||||
const remainingCount = 9 - formData.value.images.length;
|
|
||||||
if (remainingCount <= 0) {
|
|
||||||
uni.showToast({
|
|
||||||
title: '最多只能添加9张图片',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用封装好的选择并上传功能
|
|
||||||
const urls = await chooseAndUploadImages({
|
|
||||||
count: remainingCount,
|
|
||||||
sizeType: ['original', 'compressed'],
|
|
||||||
sourceType: ['album', 'camera']
|
|
||||||
});
|
|
||||||
|
|
||||||
// 将上传后的URL添加到图片列表
|
|
||||||
formData.value.images = [...formData.value.images, ...urls];
|
|
||||||
} catch (err) {
|
|
||||||
console.error('选择或上传图片失败:', err);
|
|
||||||
uni.showToast({
|
|
||||||
title: err.message || '选择图片失败',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 预览图片
|
|
||||||
const previewImage = (index) => {
|
|
||||||
uni.previewImage({
|
|
||||||
urls: formData.value.images,
|
|
||||||
current: index
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 删除图片
|
|
||||||
const removeImage = (index) => {
|
|
||||||
formData.value.images.splice(index, 1);
|
|
||||||
};
|
|
||||||
const chooseFiles = async () => {
|
|
||||||
const remainingCount = 5 - formData.value.files.length;
|
|
||||||
if (remainingCount <= 0) {
|
|
||||||
uni.showToast({
|
|
||||||
title: '最多只能添加5个文件',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 优先使用 uni.chooseFile(H5和部分平台支持)
|
|
||||||
// #ifdef H5 || MP-WEIXIN || APP-PLUS
|
|
||||||
try {
|
|
||||||
uni.chooseFile({
|
|
||||||
count: remainingCount,
|
|
||||||
extension: ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.txt', '.zip', '.rar','.jpg','.png'],
|
|
||||||
success: async (res) => {
|
|
||||||
try {
|
|
||||||
uni.showLoading({
|
|
||||||
title: '上传中...',
|
|
||||||
mask: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// 批量上传文件到七牛云
|
|
||||||
const uploadResults = await batchUploadFilesToQiniu(
|
|
||||||
res.tempFiles.map(file => ({
|
|
||||||
path: file.path,
|
|
||||||
name: file.name
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
// 将上传结果添加到文件列表
|
|
||||||
const newFiles = uploadResults.map(result => ({
|
|
||||||
name: result.name,
|
|
||||||
path: result.url, // 保存七牛云URL
|
|
||||||
size: result.size
|
|
||||||
}));
|
|
||||||
|
|
||||||
formData.value.files = [...formData.value.files, ...newFiles];
|
|
||||||
|
|
||||||
uni.hideLoading();
|
|
||||||
uni.showToast({
|
|
||||||
title: `成功添加${newFiles.length}个文件`,
|
|
||||||
icon: 'success'
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
uni.hideLoading();
|
|
||||||
console.error('上传文件失败:', error);
|
|
||||||
uni.showToast({
|
|
||||||
title: error.message || '上传文件失败',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
console.error('选择文件失败:', err);
|
|
||||||
// 如果uni.chooseFile不支持,尝试使用原生方法
|
|
||||||
chooseFilesNative();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
// 如果不支持uni.chooseFile,使用原生方法
|
|
||||||
chooseFilesNative();
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifndef H5 || MP-WEIXIN || APP-PLUS
|
|
||||||
// 其他平台使用原生方法
|
|
||||||
chooseFilesNative();
|
|
||||||
// #endif
|
|
||||||
};
|
|
||||||
|
|
||||||
// 原生文件选择方法(安卓平台)
|
|
||||||
const chooseFilesNative = async () => {
|
|
||||||
const remainingCount = 5 - formData.value.files.length;
|
|
||||||
|
|
||||||
// 安卓平台使用 plus API 调用原生文件选择器
|
|
||||||
if (typeof plus !== 'undefined') {
|
|
||||||
try {
|
|
||||||
const Intent = plus.android.importClass('android.content.Intent');
|
|
||||||
const main = plus.android.runtimeMainActivity();
|
|
||||||
|
|
||||||
// 创建文件选择 Intent
|
|
||||||
const intent = new Intent(Intent.ACTION_GET_CONTENT);
|
|
||||||
intent.setType('*/*');
|
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
|
||||||
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); // 允许多选
|
|
||||||
|
|
||||||
// 启动文件选择器
|
|
||||||
main.startActivityForResult(intent, 1001);
|
|
||||||
|
|
||||||
// 监听文件选择结果
|
|
||||||
const originalOnActivityResult = main.onActivityResult;
|
|
||||||
main.onActivityResult = async (requestCode, resultCode, data) => {
|
|
||||||
if (requestCode === 1001) {
|
|
||||||
if (resultCode === -1 && data) { // RESULT_OK = -1
|
|
||||||
try {
|
|
||||||
const clipData = data.getClipData();
|
|
||||||
const files = [];
|
|
||||||
|
|
||||||
// 获取文件名的方法
|
|
||||||
const getFileName = (uri) => {
|
|
||||||
try {
|
|
||||||
const cursor = main.getContentResolver().query(uri, null, null, null, null);
|
|
||||||
if (cursor && cursor.moveToFirst()) {
|
|
||||||
const nameIndex = cursor.getColumnIndex('_display_name');
|
|
||||||
if (nameIndex !== -1) {
|
|
||||||
const fileName = cursor.getString(nameIndex);
|
|
||||||
cursor.close();
|
|
||||||
return fileName;
|
|
||||||
}
|
|
||||||
cursor.close();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('获取文件名失败:', e);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (clipData) {
|
|
||||||
// 多选文件
|
|
||||||
const count = clipData.getItemCount();
|
|
||||||
for (let i = 0; i < count && files.length < remainingCount; i++) {
|
|
||||||
const item = clipData.getItemAt(i);
|
|
||||||
const uri = item.getUri();
|
|
||||||
const uriString = uri.toString();
|
|
||||||
|
|
||||||
// 获取文件名
|
|
||||||
let fileName = getFileName(uri) || `file_${Date.now()}_${i}`;
|
|
||||||
|
|
||||||
files.push({
|
|
||||||
name: fileName,
|
|
||||||
path: uriString, // 保存 URI 字符串
|
|
||||||
size: 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 单选文件
|
|
||||||
const uri = data.getData();
|
|
||||||
if (uri) {
|
|
||||||
const uriString = uri.toString();
|
|
||||||
let fileName = getFileName(uri) || `file_${Date.now()}`;
|
|
||||||
|
|
||||||
files.push({
|
|
||||||
name: fileName,
|
|
||||||
path: uriString, // 保存 URI 字符串
|
|
||||||
size: 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (files.length > 0) {
|
|
||||||
// 显示上传中提示
|
|
||||||
uni.showLoading({
|
|
||||||
title: '上传中...',
|
|
||||||
mask: true
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 批量上传文件到七牛云
|
|
||||||
const uploadResults = await batchUploadFilesToQiniu(files);
|
|
||||||
|
|
||||||
// 将上传结果添加到文件列表
|
|
||||||
const newFiles = uploadResults.map(result => ({
|
|
||||||
name: result.name,
|
|
||||||
path: result.url, // 保存七牛云URL
|
|
||||||
size: result.size
|
|
||||||
}));
|
|
||||||
|
|
||||||
formData.value.files = [...formData.value.files, ...newFiles];
|
|
||||||
|
|
||||||
uni.hideLoading();
|
|
||||||
uni.showToast({
|
|
||||||
title: `成功添加${newFiles.length}个文件`,
|
|
||||||
icon: 'success'
|
|
||||||
});
|
|
||||||
} catch (uploadError) {
|
|
||||||
uni.hideLoading();
|
|
||||||
console.error('上传文件失败:', uploadError);
|
|
||||||
uni.showToast({
|
|
||||||
title: uploadError.message || '上传文件失败',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 恢复原始的 onActivityResult
|
|
||||||
if (originalOnActivityResult) {
|
|
||||||
main.onActivityResult = originalOnActivityResult;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
uni.hideLoading();
|
|
||||||
console.error('处理文件选择结果失败:', error);
|
|
||||||
uni.showToast({
|
|
||||||
title: '处理文件失败',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 调用原始的 onActivityResult
|
|
||||||
if (originalOnActivityResult) {
|
|
||||||
originalOnActivityResult(requestCode, resultCode, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error('打开文件选择器失败:', error);
|
|
||||||
uni.showToast({
|
|
||||||
title: '文件选择功能暂不可用',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
uni.showToast({
|
|
||||||
title: '当前环境不支持文件选择',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 删除文件
|
|
||||||
const removeFile = (index) => {
|
|
||||||
formData.value.files.splice(index, 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 格式化文件大小
|
|
||||||
const formatFileSize = (bytes) => {
|
|
||||||
if (!bytes || bytes === 0) return '';
|
|
||||||
const k = 1024;
|
|
||||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
||||||
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
|
|
||||||
};
|
|
||||||
|
|
||||||
// 预览/下载文件
|
|
||||||
const previewFile = (file) => {
|
|
||||||
if (!file.path) {
|
|
||||||
uni.showToast({
|
|
||||||
title: '文件路径不存在',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果是图片,使用预览图片功能
|
|
||||||
const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
|
||||||
const ext = file.name.split('.').pop().toLowerCase();
|
|
||||||
|
|
||||||
if (imageExts.includes(ext)) {
|
|
||||||
uni.previewImage({
|
|
||||||
urls: [file.path],
|
|
||||||
current: file.path
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// 其他文件类型,尝试打开或下载
|
|
||||||
// #ifdef H5
|
|
||||||
window.open(file.path, '_blank');
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifdef APP-PLUS
|
|
||||||
plus.runtime.openURL(file.path);
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifndef H5 || APP-PLUS
|
|
||||||
uni.showToast({
|
|
||||||
title: '点击下载文件',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
// 可以调用下载API
|
|
||||||
uni.downloadFile({
|
|
||||||
url: file.path,
|
|
||||||
success: (res) => {
|
|
||||||
if (res.statusCode === 200) {
|
|
||||||
uni.openDocument({
|
|
||||||
filePath: res.tempFilePath,
|
|
||||||
success: () => {
|
|
||||||
console.log('打开文档成功');
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
console.error('打开文档失败:', err);
|
|
||||||
uni.showToast({
|
|
||||||
title: '无法打开此文件',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
console.error('下载文件失败:', err);
|
|
||||||
uni.showToast({
|
|
||||||
title: '下载文件失败',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// #endif
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1060,6 +634,7 @@ onLoad(async (options) => {
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-line-clamp: 2;
|
-webkit-line-clamp: 2;
|
||||||
|
line-clamp: 2;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,82 +24,11 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- 添加照片 -->
|
<AttachmentImageUploader v-model="formData.images" />
|
||||||
<view class="form-item clickable-item" @click="chooseImages">
|
<AttachmentFileUploader v-model="formData.files" />
|
||||||
<view class="form-icon">🏔️</view>
|
|
||||||
<text class="form-label">添加照片</text>
|
|
||||||
<text class="arrow">›</text>
|
|
||||||
</view>
|
|
||||||
<!-- 照片预览 -->
|
|
||||||
<view class="images-preview" v-if="formData.images.length > 0">
|
|
||||||
<view
|
|
||||||
class="image-item"
|
|
||||||
v-for="(image, index) in formData.images"
|
|
||||||
:key="index"
|
|
||||||
>
|
|
||||||
<image :src="image" mode="aspectFill" class="preview-image" @click="previewImage(index)" />
|
|
||||||
<view class="remove-btn" @click="removeImage(index)">✕</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 添加文件 -->
|
|
||||||
<view class="form-item clickable-item" @click="chooseFiles">
|
|
||||||
<view class="form-icon">📄</view>
|
|
||||||
<text class="form-label">添加文件</text>
|
|
||||||
<text class="arrow">›</text>
|
|
||||||
</view>
|
|
||||||
<!-- 文件列表 -->
|
|
||||||
<view class="files-list" v-if="formData.files.length > 0">
|
|
||||||
<view
|
|
||||||
class="file-item"
|
|
||||||
v-for="(file, index) in formData.files"
|
|
||||||
:key="index"
|
|
||||||
@click="previewFile(file)"
|
|
||||||
>
|
|
||||||
<text class="file-icon">{{ getFileIcon(file.name) }}</text>
|
|
||||||
<view class="file-info">
|
|
||||||
<text class="file-name">{{ file.name }}</text>
|
|
||||||
<text class="file-size" v-if="file.size > 0">{{ formatFileSize(file.size) }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="remove-btn" @click.stop="removeFile(index)">✕</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
|
|
||||||
<!-- 进度选择弹窗 -->
|
|
||||||
<view v-if="showProgressPicker" class="modal-mask" @click="showProgressPicker = false">
|
|
||||||
<view class="modal-content progress-modal" @click.stop>
|
|
||||||
<view class="modal-title">选择任务进度</view>
|
|
||||||
<view class="progress-content">
|
|
||||||
<slider
|
|
||||||
:value="tempProgress !== null ? tempProgress : 0"
|
|
||||||
:min="0"
|
|
||||||
:max="100"
|
|
||||||
:step="10"
|
|
||||||
:show-value="true"
|
|
||||||
activeColor="#1976d2"
|
|
||||||
@change="onProgressChange"
|
|
||||||
/>
|
|
||||||
<view class="progress-options">
|
|
||||||
<view
|
|
||||||
class="progress-option"
|
|
||||||
v-for="progress in progressOptions"
|
|
||||||
:key="progress"
|
|
||||||
:class="{ active: tempProgress === progress }"
|
|
||||||
@click="selectProgress(progress)"
|
|
||||||
>
|
|
||||||
<text>{{ progress }}%</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="modal-actions">
|
|
||||||
<text class="modal-btn cancel-btn" @click="showProgressPicker = false">取消</text>
|
|
||||||
<text class="modal-btn confirm-btn" @click="confirmProgress">确定</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 确认提交按钮 -->
|
<!-- 确认提交按钮 -->
|
||||||
<view class="submit-button-wrapper">
|
<view class="submit-button-wrapper">
|
||||||
<uv-button
|
<uv-button
|
||||||
|
|
@ -115,15 +44,15 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
import { onLoad } from '@dcloudio/uni-app';
|
import { onLoad } from '@dcloudio/uni-app';
|
||||||
import { chooseAndUploadImages, uploadFileToQiniu, batchUploadFilesToQiniu } from '@/utils/qiniu.js';
|
|
||||||
import { submitTask } from '@/api';
|
import { submitTask } from '@/api';
|
||||||
|
import AttachmentImageUploader from '@/components/task/AttachmentImageUploader.vue';
|
||||||
|
import AttachmentFileUploader from '@/components/task/AttachmentFileUploader.vue';
|
||||||
|
|
||||||
// 表单数据
|
// 表单数据
|
||||||
const formData = ref({
|
const formData = ref({
|
||||||
description: '',
|
description: '',
|
||||||
progress: null,
|
|
||||||
images: [],
|
images: [],
|
||||||
files: []
|
files: []
|
||||||
});
|
});
|
||||||
|
|
@ -135,19 +64,6 @@ const isEditMode = ref(false);
|
||||||
const editRecordIndex = ref(-1);
|
const editRecordIndex = ref(-1);
|
||||||
const editRecordData = ref(null);
|
const editRecordData = ref(null);
|
||||||
|
|
||||||
// 进度选择弹窗
|
|
||||||
const showProgressPicker = ref(false);
|
|
||||||
const tempProgress = ref(null);
|
|
||||||
|
|
||||||
// 打开进度选择器
|
|
||||||
const openProgressPicker = () => {
|
|
||||||
tempProgress.value = formData.value.progress !== null ? formData.value.progress : 0;
|
|
||||||
showProgressPicker.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 进度选项
|
|
||||||
const progressOptions = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
|
|
||||||
|
|
||||||
// 是否可以提交
|
// 是否可以提交
|
||||||
const canSubmit = computed(() => {
|
const canSubmit = computed(() => {
|
||||||
return formData.value.description.trim() !== '' ||
|
return formData.value.description.trim() !== '' ||
|
||||||
|
|
@ -171,10 +87,6 @@ onLoad((options) => {
|
||||||
|
|
||||||
// 预填充表单数据
|
// 预填充表单数据
|
||||||
formData.value.description = editData.record.content || '';
|
formData.value.description = editData.record.content || '';
|
||||||
formData.value.progress = editData.record.progress !== null && editData.record.progress !== undefined
|
|
||||||
? editData.record.progress
|
|
||||||
: null;
|
|
||||||
|
|
||||||
// 处理附件
|
// 处理附件
|
||||||
if (editData.record.attachments && editData.record.attachments.length > 0) {
|
if (editData.record.attachments && editData.record.attachments.length > 0) {
|
||||||
editData.record.attachments.forEach(attachment => {
|
editData.record.attachments.forEach(attachment => {
|
||||||
|
|
@ -209,391 +121,6 @@ const handleCancel = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 进度变化
|
|
||||||
const onProgressChange = (e) => {
|
|
||||||
tempProgress.value = e.detail.value;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 选择进度(点击按钮直接确认)
|
|
||||||
const selectProgress = (progress) => {
|
|
||||||
formData.value.progress = progress;
|
|
||||||
showProgressPicker.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 确认进度
|
|
||||||
const confirmProgress = () => {
|
|
||||||
formData.value.progress = tempProgress.value;
|
|
||||||
showProgressPicker.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 选择图片并自动上传到七牛云
|
|
||||||
const chooseImages = async () => {
|
|
||||||
try {
|
|
||||||
const remainingCount = 9 - formData.value.images.length;
|
|
||||||
if (remainingCount <= 0) {
|
|
||||||
uni.showToast({
|
|
||||||
title: '最多只能添加9张图片',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用封装好的选择并上传功能
|
|
||||||
const urls = await chooseAndUploadImages({
|
|
||||||
count: remainingCount,
|
|
||||||
sizeType: ['original', 'compressed'],
|
|
||||||
sourceType: ['album', 'camera']
|
|
||||||
});
|
|
||||||
|
|
||||||
// 将上传后的URL添加到图片列表
|
|
||||||
formData.value.images = [...formData.value.images, ...urls];
|
|
||||||
} catch (err) {
|
|
||||||
console.error('选择或上传图片失败:', err);
|
|
||||||
uni.showToast({
|
|
||||||
title: err.message || '选择图片失败',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 预览图片
|
|
||||||
const previewImage = (index) => {
|
|
||||||
uni.previewImage({
|
|
||||||
urls: formData.value.images,
|
|
||||||
current: index
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 删除图片
|
|
||||||
const removeImage = (index) => {
|
|
||||||
formData.value.images.splice(index, 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 选择文件(支持多平台)
|
|
||||||
const chooseFiles = async () => {
|
|
||||||
const remainingCount = 5 - formData.value.files.length;
|
|
||||||
if (remainingCount <= 0) {
|
|
||||||
uni.showToast({
|
|
||||||
title: '最多只能添加5个文件',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 优先使用 uni.chooseFile(H5和部分平台支持)
|
|
||||||
// #ifdef H5 || MP-WEIXIN || APP-PLUS
|
|
||||||
try {
|
|
||||||
uni.chooseFile({
|
|
||||||
count: remainingCount,
|
|
||||||
extension: ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.txt', '.zip', '.rar','.jpg','.png'],
|
|
||||||
success: async (res) => {
|
|
||||||
try {
|
|
||||||
uni.showLoading({
|
|
||||||
title: '上传中...',
|
|
||||||
mask: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// 批量上传文件到七牛云
|
|
||||||
const uploadResults = await batchUploadFilesToQiniu(
|
|
||||||
res.tempFiles.map(file => ({
|
|
||||||
path: file.path,
|
|
||||||
name: file.name
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
// 将上传结果添加到文件列表
|
|
||||||
const newFiles = uploadResults.map(result => ({
|
|
||||||
name: result.name,
|
|
||||||
path: result.url, // 保存七牛云URL
|
|
||||||
size: result.size
|
|
||||||
}));
|
|
||||||
|
|
||||||
formData.value.files = [...formData.value.files, ...newFiles];
|
|
||||||
|
|
||||||
uni.hideLoading();
|
|
||||||
uni.showToast({
|
|
||||||
title: `成功添加${newFiles.length}个文件`,
|
|
||||||
icon: 'success'
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
uni.hideLoading();
|
|
||||||
console.error('上传文件失败:', error);
|
|
||||||
uni.showToast({
|
|
||||||
title: error.message || '上传文件失败',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
console.error('选择文件失败:', err);
|
|
||||||
// 如果uni.chooseFile不支持,尝试使用原生方法
|
|
||||||
chooseFilesNative();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
// 如果不支持uni.chooseFile,使用原生方法
|
|
||||||
chooseFilesNative();
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifndef H5 || MP-WEIXIN || APP-PLUS
|
|
||||||
// 其他平台使用原生方法
|
|
||||||
chooseFilesNative();
|
|
||||||
// #endif
|
|
||||||
};
|
|
||||||
|
|
||||||
// 原生文件选择方法(安卓平台)
|
|
||||||
const chooseFilesNative = async () => {
|
|
||||||
const remainingCount = 5 - formData.value.files.length;
|
|
||||||
|
|
||||||
// 安卓平台使用 plus API 调用原生文件选择器
|
|
||||||
if (typeof plus !== 'undefined') {
|
|
||||||
try {
|
|
||||||
const Intent = plus.android.importClass('android.content.Intent');
|
|
||||||
const main = plus.android.runtimeMainActivity();
|
|
||||||
|
|
||||||
// 创建文件选择 Intent
|
|
||||||
const intent = new Intent(Intent.ACTION_GET_CONTENT);
|
|
||||||
intent.setType('*/*');
|
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
|
||||||
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); // 允许多选
|
|
||||||
|
|
||||||
// 启动文件选择器
|
|
||||||
main.startActivityForResult(intent, 1001);
|
|
||||||
|
|
||||||
// 监听文件选择结果
|
|
||||||
const originalOnActivityResult = main.onActivityResult;
|
|
||||||
main.onActivityResult = async (requestCode, resultCode, data) => {
|
|
||||||
if (requestCode === 1001) {
|
|
||||||
if (resultCode === -1 && data) { // RESULT_OK = -1
|
|
||||||
try {
|
|
||||||
const clipData = data.getClipData();
|
|
||||||
const files = [];
|
|
||||||
|
|
||||||
// 获取文件名的方法
|
|
||||||
const getFileName = (uri) => {
|
|
||||||
try {
|
|
||||||
const cursor = main.getContentResolver().query(uri, null, null, null, null);
|
|
||||||
if (cursor && cursor.moveToFirst()) {
|
|
||||||
const nameIndex = cursor.getColumnIndex('_display_name');
|
|
||||||
if (nameIndex !== -1) {
|
|
||||||
const fileName = cursor.getString(nameIndex);
|
|
||||||
cursor.close();
|
|
||||||
return fileName;
|
|
||||||
}
|
|
||||||
cursor.close();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('获取文件名失败:', e);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (clipData) {
|
|
||||||
// 多选文件
|
|
||||||
const count = clipData.getItemCount();
|
|
||||||
for (let i = 0; i < count && files.length < remainingCount; i++) {
|
|
||||||
const item = clipData.getItemAt(i);
|
|
||||||
const uri = item.getUri();
|
|
||||||
const uriString = uri.toString();
|
|
||||||
|
|
||||||
// 获取文件名
|
|
||||||
let fileName = getFileName(uri) || `file_${Date.now()}_${i}`;
|
|
||||||
|
|
||||||
files.push({
|
|
||||||
name: fileName,
|
|
||||||
path: uriString, // 保存 URI 字符串
|
|
||||||
size: 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 单选文件
|
|
||||||
const uri = data.getData();
|
|
||||||
if (uri) {
|
|
||||||
const uriString = uri.toString();
|
|
||||||
let fileName = getFileName(uri) || `file_${Date.now()}`;
|
|
||||||
|
|
||||||
files.push({
|
|
||||||
name: fileName,
|
|
||||||
path: uriString, // 保存 URI 字符串
|
|
||||||
size: 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (files.length > 0) {
|
|
||||||
// 显示上传中提示
|
|
||||||
uni.showLoading({
|
|
||||||
title: '上传中...',
|
|
||||||
mask: true
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 批量上传文件到七牛云
|
|
||||||
const uploadResults = await batchUploadFilesToQiniu(files);
|
|
||||||
|
|
||||||
// 将上传结果添加到文件列表
|
|
||||||
const newFiles = uploadResults.map(result => ({
|
|
||||||
name: result.name,
|
|
||||||
path: result.url, // 保存七牛云URL
|
|
||||||
size: result.size
|
|
||||||
}));
|
|
||||||
|
|
||||||
formData.value.files = [...formData.value.files, ...newFiles];
|
|
||||||
|
|
||||||
uni.hideLoading();
|
|
||||||
uni.showToast({
|
|
||||||
title: `成功添加${newFiles.length}个文件`,
|
|
||||||
icon: 'success'
|
|
||||||
});
|
|
||||||
} catch (uploadError) {
|
|
||||||
uni.hideLoading();
|
|
||||||
console.error('上传文件失败:', uploadError);
|
|
||||||
uni.showToast({
|
|
||||||
title: uploadError.message || '上传文件失败',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 恢复原始的 onActivityResult
|
|
||||||
if (originalOnActivityResult) {
|
|
||||||
main.onActivityResult = originalOnActivityResult;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
uni.hideLoading();
|
|
||||||
console.error('处理文件选择结果失败:', error);
|
|
||||||
uni.showToast({
|
|
||||||
title: '处理文件失败',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 调用原始的 onActivityResult
|
|
||||||
if (originalOnActivityResult) {
|
|
||||||
originalOnActivityResult(requestCode, resultCode, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error('打开文件选择器失败:', error);
|
|
||||||
uni.showToast({
|
|
||||||
title: '文件选择功能暂不可用',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
uni.showToast({
|
|
||||||
title: '当前环境不支持文件选择',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 删除文件
|
|
||||||
const removeFile = (index) => {
|
|
||||||
formData.value.files.splice(index, 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 获取文件图标
|
|
||||||
const getFileIcon = (fileName) => {
|
|
||||||
if (!fileName) return '📄';
|
|
||||||
const ext = fileName.split('.').pop().toLowerCase();
|
|
||||||
const iconMap = {
|
|
||||||
'pdf': '📕',
|
|
||||||
'doc': '📘',
|
|
||||||
'docx': '📘',
|
|
||||||
'xls': '📗',
|
|
||||||
'xlsx': '📗',
|
|
||||||
'ppt': '📙',
|
|
||||||
'pptx': '📙',
|
|
||||||
'txt': '📄',
|
|
||||||
'zip': '📦',
|
|
||||||
'rar': '📦',
|
|
||||||
'jpg': '🖼️',
|
|
||||||
'jpeg': '🖼️',
|
|
||||||
'png': '🖼️',
|
|
||||||
'gif': '🖼️'
|
|
||||||
};
|
|
||||||
return iconMap[ext] || '📄';
|
|
||||||
};
|
|
||||||
|
|
||||||
// 格式化文件大小
|
|
||||||
const formatFileSize = (bytes) => {
|
|
||||||
if (!bytes || bytes === 0) return '';
|
|
||||||
const k = 1024;
|
|
||||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
||||||
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
|
|
||||||
};
|
|
||||||
|
|
||||||
// 预览/下载文件
|
|
||||||
const previewFile = (file) => {
|
|
||||||
if (!file.path) {
|
|
||||||
uni.showToast({
|
|
||||||
title: '文件路径不存在',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果是图片,使用预览图片功能
|
|
||||||
const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
|
||||||
const ext = file.name.split('.').pop().toLowerCase();
|
|
||||||
|
|
||||||
if (imageExts.includes(ext)) {
|
|
||||||
uni.previewImage({
|
|
||||||
urls: [file.path],
|
|
||||||
current: file.path
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// 其他文件类型,尝试打开或下载
|
|
||||||
// #ifdef H5
|
|
||||||
window.open(file.path, '_blank');
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifdef APP-PLUS
|
|
||||||
plus.runtime.openURL(file.path);
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifndef H5 || APP-PLUS
|
|
||||||
uni.showToast({
|
|
||||||
title: '点击下载文件',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
// 可以调用下载API
|
|
||||||
uni.downloadFile({
|
|
||||||
url: file.path,
|
|
||||||
success: (res) => {
|
|
||||||
if (res.statusCode === 200) {
|
|
||||||
uni.openDocument({
|
|
||||||
filePath: res.tempFilePath,
|
|
||||||
success: () => {
|
|
||||||
console.log('打开文档成功');
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
console.error('打开文档失败:', err);
|
|
||||||
uni.showToast({
|
|
||||||
title: '无法打开此文件',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
console.error('下载文件失败:', err);
|
|
||||||
uni.showToast({
|
|
||||||
title: '下载文件失败',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// #endif
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 格式化时间为中文格式:年月日星期几时分秒
|
// 格式化时间为中文格式:年月日星期几时分秒
|
||||||
const formatTimeToChinese = (date) => {
|
const formatTimeToChinese = (date) => {
|
||||||
const weekdays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
|
const weekdays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
|
||||||
|
|
@ -657,7 +184,6 @@ const handleSubmit = async () => {
|
||||||
userName: editRecordData.value?.userName || '当前用户', // 保持原用户名
|
userName: editRecordData.value?.userName || '当前用户', // 保持原用户名
|
||||||
time: formatTimeToChinese(new Date()), // 更新时间
|
time: formatTimeToChinese(new Date()), // 更新时间
|
||||||
content: formData.value.description.trim() || '',
|
content: formData.value.description.trim() || '',
|
||||||
progress: formData.value.progress,
|
|
||||||
attachments: [
|
attachments: [
|
||||||
...formData.value.images.map(img => ({ type: 'image', path: img })),
|
...formData.value.images.map(img => ({ type: 'image', path: img })),
|
||||||
...formData.value.files.map(file => ({ type: 'file', name: file.name, path: file.path }))
|
...formData.value.files.map(file => ({ type: 'file', name: file.name, path: file.path }))
|
||||||
|
|
@ -682,7 +208,6 @@ const handleSubmit = async () => {
|
||||||
userName: '当前用户', // TODO: 从用户信息获取
|
userName: '当前用户', // TODO: 从用户信息获取
|
||||||
time: formatTimeToChinese(new Date()),
|
time: formatTimeToChinese(new Date()),
|
||||||
content: formData.value.description.trim() || '',
|
content: formData.value.description.trim() || '',
|
||||||
progress: formData.value.progress,
|
|
||||||
attachments: [
|
attachments: [
|
||||||
...formData.value.images.map(img => ({ type: 'image', path: img })),
|
...formData.value.images.map(img => ({ type: 'image', path: img })),
|
||||||
...formData.value.files.map(file => ({ type: 'file', name: file.name, path: file.path }))
|
...formData.value.files.map(file => ({ type: 'file', name: file.name, path: file.path }))
|
||||||
|
|
@ -827,177 +352,6 @@ const handleSubmit = async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 图片预览 */
|
/* 图片预览 */
|
||||||
.images-preview {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 8px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
padding: 0 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-item {
|
|
||||||
position: relative;
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-image {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remove-btn {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
background-color: rgba(0, 0, 0, 0.6);
|
|
||||||
color: #fff;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 14px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 文件列表 */
|
|
||||||
.files-list {
|
|
||||||
padding: 0 16px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 12px;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
gap: 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-icon {
|
|
||||||
font-size: 24px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-info {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 4px;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-name {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #333;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-size {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 进度选择弹窗 */
|
|
||||||
.modal-mask {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-content {
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 12px;
|
|
||||||
width: 90%;
|
|
||||||
max-width: 400px;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-title {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #333;
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-content {
|
|
||||||
padding: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-options {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 8px;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-option {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 60px;
|
|
||||||
padding: 10px;
|
|
||||||
text-align: center;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #666;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background-color: #1976d2;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-btn {
|
|
||||||
flex: 1;
|
|
||||||
padding: 12px;
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 16px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cancel-btn {
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirm-btn {
|
|
||||||
background-color: #1976d2;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 提交按钮 */
|
/* 提交按钮 */
|
||||||
.submit-button-wrapper {
|
.submit-button-wrapper {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user