|
@@ -38,11 +38,7 @@
|
|
|
>
|
|
>
|
|
|
<image
|
|
<image
|
|
|
class="task-detail-field__file-icon"
|
|
class="task-detail-field__file-icon"
|
|
|
- :src="
|
|
|
|
|
- attachment.ext === 'pdf'
|
|
|
|
|
- ? '/static/images/icon/pdf.svg'
|
|
|
|
|
- : '/static/images/icon/ppt.svg'
|
|
|
|
|
- "
|
|
|
|
|
|
|
+ :src="getFileIcon(attachment.ext)"
|
|
|
mode="aspectFit"
|
|
mode="aspectFit"
|
|
|
/>
|
|
/>
|
|
|
</view>
|
|
</view>
|
|
@@ -82,8 +78,34 @@ const IMAGE_AND_FILE_FIELD_TYPE = 'imgandfile'
|
|
|
const FILE_URL_FIELD_TYPE = 'fileurl'
|
|
const FILE_URL_FIELD_TYPE = 'fileurl'
|
|
|
const DICT_FIELD_TYPES = new Set(['select', 'domain', 'multiple_select'])
|
|
const DICT_FIELD_TYPES = new Set(['select', 'domain', 'multiple_select'])
|
|
|
const FILE_EXTENSIONS = new Set(['pdf', 'ppt', 'pptx'])
|
|
const FILE_EXTENSIONS = new Set(['pdf', 'ppt', 'pptx'])
|
|
|
|
|
+const IMAGE_EXTENSIONS = new Set([
|
|
|
|
|
+ 'avif',
|
|
|
|
|
+ 'bmp',
|
|
|
|
|
+ 'gif',
|
|
|
|
|
+ 'heic',
|
|
|
|
|
+ 'heif',
|
|
|
|
|
+ 'jfif',
|
|
|
|
|
+ 'jpeg',
|
|
|
|
|
+ 'jpg',
|
|
|
|
|
+ 'png',
|
|
|
|
|
+ 'svg',
|
|
|
|
|
+ 'tif',
|
|
|
|
|
+ 'tiff',
|
|
|
|
|
+ 'webp',
|
|
|
|
|
+])
|
|
|
|
|
+const DOCUMENT_EXTENSIONS = new Set(['doc', 'docx'])
|
|
|
|
|
+const PRESENTATION_EXTENSIONS = new Set(['ppt', 'pptx'])
|
|
|
|
|
+const SPREADSHEET_EXTENSIONS = new Set(['xls', 'xlsx'])
|
|
|
const FILE_DOWNLOAD_TIMEOUT = 60_000
|
|
const FILE_DOWNLOAD_TIMEOUT = 60_000
|
|
|
|
|
|
|
|
|
|
+const FILE_ICONS = {
|
|
|
|
|
+ default: '/static/images/icon/file.svg',
|
|
|
|
|
+ document: '/static/images/icon/doc.svg',
|
|
|
|
|
+ pdf: '/static/images/icon/pdf.svg',
|
|
|
|
|
+ presentation: '/static/images/icon/ppt.svg',
|
|
|
|
|
+ spreadsheet: '/static/images/icon/xls.svg',
|
|
|
|
|
+} as const
|
|
|
|
|
+
|
|
|
type DetailAttachmentType = 'image' | 'file'
|
|
type DetailAttachmentType = 'image' | 'file'
|
|
|
|
|
|
|
|
interface DetailAttachment {
|
|
interface DetailAttachment {
|
|
@@ -144,7 +166,7 @@ const dictLabelsByField = computed(() => {
|
|
|
* 对应的字典中获取 label,字典缺失或值无法完整匹配时直接回显 value。
|
|
* 对应的字典中获取 label,字典缺失或值无法完整匹配时直接回显 value。
|
|
|
* 其余情况在 item.label 为空时用 value 回显。
|
|
* 其余情况在 item.label 为空时用 value 回显。
|
|
|
* img、sign 类型转成图片列表;imgAndFile 根据值末尾来源标记转成图片或文件;
|
|
* img、sign 类型转成图片列表;imgAndFile 根据值末尾来源标记转成图片或文件;
|
|
|
- * fileurl 解析 value 中的 JSON 数组,取 url 和 fileName 展示附件;
|
|
|
|
|
|
|
+ * fileurl 解析 value 中的 JSON 数组,取 url 和 fileName 展示附件,仅图片后缀使用图片预览;
|
|
|
* 其余类型显示标准化后的 label。
|
|
* 其余类型显示标准化后的 label。
|
|
|
*/
|
|
*/
|
|
|
const contentFields = computed(() => {
|
|
const contentFields = computed(() => {
|
|
@@ -245,15 +267,16 @@ const parseFileUrlAttachments = (value: string): DetailAttachment[] => {
|
|
|
return files.flatMap((file): DetailAttachment[] => {
|
|
return files.flatMap((file): DetailAttachment[] => {
|
|
|
if (!file || typeof file.url !== 'string' || !file.url.trim()) return []
|
|
if (!file || typeof file.url !== 'string' || !file.url.trim()) return []
|
|
|
|
|
|
|
|
|
|
+ const urlFileName = extractFileName(file.url)
|
|
|
const name =
|
|
const name =
|
|
|
typeof file.fileName === 'string' && file.fileName.trim()
|
|
typeof file.fileName === 'string' && file.fileName.trim()
|
|
|
? file.fileName.trim()
|
|
? file.fileName.trim()
|
|
|
- : extractFileName(file.url)
|
|
|
|
|
- const ext = getFileExtension(name)
|
|
|
|
|
|
|
+ : urlFileName
|
|
|
|
|
+ const ext = getFileExtension(name) || getFileExtension(urlFileName)
|
|
|
|
|
|
|
|
return [
|
|
return [
|
|
|
{
|
|
{
|
|
|
- type: isFileAttachment(getAttachmentSuffix(file.url), ext) ? 'file' : 'image',
|
|
|
|
|
|
|
+ type: isFileUrlImage(getAttachmentSuffix(file.url), ext) ? 'image' : 'file',
|
|
|
url: toFullUrl(file.url),
|
|
url: toFullUrl(file.url),
|
|
|
name,
|
|
name,
|
|
|
ext,
|
|
ext,
|
|
@@ -266,7 +289,10 @@ const parseFileUrlAttachments = (value: string): DetailAttachment[] => {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const parseAttachments = (value: string, fieldMode: 'image' | 'mixed'): DetailAttachment[] => {
|
|
const parseAttachments = (value: string, fieldMode: 'image' | 'mixed'): DetailAttachment[] => {
|
|
|
- return value
|
|
|
|
|
|
|
+ const normalizedValue = value.trim()
|
|
|
|
|
+ if (!normalizedValue || /^\[\s*\]$/.test(normalizedValue)) return []
|
|
|
|
|
+
|
|
|
|
|
+ return normalizedValue
|
|
|
.split(',')
|
|
.split(',')
|
|
|
.map((storedUrl) => storedUrl.trim())
|
|
.map((storedUrl) => storedUrl.trim())
|
|
|
.filter(Boolean)
|
|
.filter(Boolean)
|
|
@@ -298,6 +324,21 @@ const isFileAttachment = (suffix: string, ext: string): boolean => {
|
|
|
return FILE_EXTENSIONS.has(ext)
|
|
return FILE_EXTENSIONS.has(ext)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+const isFileUrlImage = (suffix: string, ext: string): boolean => {
|
|
|
|
|
+ if (ext) return IMAGE_EXTENSIONS.has(ext)
|
|
|
|
|
+
|
|
|
|
|
+ return Boolean(suffix && suffix !== '4')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const getFileIcon = (ext: string): string => {
|
|
|
|
|
+ if (ext === 'pdf') return FILE_ICONS.pdf
|
|
|
|
|
+ if (DOCUMENT_EXTENSIONS.has(ext)) return FILE_ICONS.document
|
|
|
|
|
+ if (PRESENTATION_EXTENSIONS.has(ext)) return FILE_ICONS.presentation
|
|
|
|
|
+ if (SPREADSHEET_EXTENSIONS.has(ext)) return FILE_ICONS.spreadsheet
|
|
|
|
|
+
|
|
|
|
|
+ return FILE_ICONS.default
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
const extractFileName = (url: string): string => {
|
|
const extractFileName = (url: string): string => {
|
|
|
const cleanUrl = url.replace(/;[1-4](?=$|[?#])/i, '').split(/[?#]/)[0]
|
|
const cleanUrl = url.replace(/;[1-4](?=$|[?#])/i, '').split(/[?#]/)[0]
|
|
|
const encodedName = cleanUrl.slice(cleanUrl.lastIndexOf('/') + 1)
|
|
const encodedName = cleanUrl.slice(cleanUrl.lastIndexOf('/') + 1)
|