teamBackgroundFile.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. const ALLOWED_EXTENSIONS = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'md', 'csv']
  2. const MAX_FILE_SIZE = 50 * 1024 * 1024
  3. const BACKEND_TIMEZONE_OFFSET_MINUTES = 8 * 60
  4. const SESSION_EXPIRES_AT_PATTERN = /^(\d{4})-(\d{2})-(\d{2})([ T])(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?(?:(Z)|([+-])(\d{2}):?(\d{2}))?$/
  5. function validateEnterpriseWebsite(value) {
  6. const normalized = String(value || '').trim()
  7. if (!normalized) return '请输入企业官网,无官网请填写“无”'
  8. if (normalized === '无') return ''
  9. if (!/^https?:\/\/[^/\s?#]+(?:[/?#][^\s]*)?$/i.test(normalized)) return '请输入以http://或https://开头的企业官网'
  10. return ''
  11. }
  12. function validateBackgroundFile(file) {
  13. if (!file || !file.name || !file.size) return '请选择非空文件'
  14. if (file.name.length > 255) return '文件名不能超过255个字符'
  15. const ext = file.name.includes('.') ? file.name.split('.').pop().toLowerCase() : ''
  16. if (!ALLOWED_EXTENSIONS.includes(ext)) return '文件类型不支持'
  17. if (file.size > MAX_FILE_SIZE) return '单个文件不能超过50MB'
  18. return ''
  19. }
  20. function parseSessionExpiresAt(value) {
  21. const matched = SESSION_EXPIRES_AT_PATTERN.exec(String(value || '').trim())
  22. if (!matched) return NaN
  23. const year = Number(matched[1])
  24. const month = Number(matched[2])
  25. const day = Number(matched[3])
  26. const separator = matched[4]
  27. const hour = Number(matched[5])
  28. const minute = Number(matched[6])
  29. const second = Number(matched[7])
  30. const millisecond = Number((matched[8] || '').padEnd(3, '0'))
  31. if (year < 1000 || month < 1 || month > 12 || day < 1 || day > 31
  32. || hour > 23 || minute > 59 || second > 59) return NaN
  33. const hasExplicitTimezone = Boolean(matched[9] || matched[10])
  34. if (separator === 'T' && !hasExplicitTimezone) return NaN
  35. if (separator === ' ' && hasExplicitTimezone) return NaN
  36. const localWallClock = Date.UTC(year, month - 1, day, hour, minute, second, millisecond)
  37. const normalized = new Date(localWallClock)
  38. if (normalized.getUTCFullYear() !== year || normalized.getUTCMonth() !== month - 1
  39. || normalized.getUTCDate() !== day || normalized.getUTCHours() !== hour
  40. || normalized.getUTCMinutes() !== minute || normalized.getUTCSeconds() !== second) return NaN
  41. let offsetMinutes = BACKEND_TIMEZONE_OFFSET_MINUTES
  42. if (matched[9] === 'Z') {
  43. offsetMinutes = 0
  44. } else if (matched[10]) {
  45. const offsetHour = Number(matched[11])
  46. const offsetMinute = Number(matched[12])
  47. if (offsetHour > 23 || offsetMinute > 59) return NaN
  48. offsetMinutes = (offsetHour * 60 + offsetMinute) * (matched[10] === '+' ? 1 : -1)
  49. }
  50. return localWallClock - offsetMinutes * 60 * 1000
  51. }
  52. function getSessionRemainingSeconds(expiresAt, now = Date.now()) {
  53. const expiresAtTime = parseSessionExpiresAt(expiresAt)
  54. if (!Number.isFinite(expiresAtTime)) return 0
  55. return Math.max(0, Math.ceil((expiresAtTime - now) / 1000))
  56. }
  57. function createBackgroundQueueItem(file, id) {
  58. const selectedFile = file || {}
  59. const validationError = validateBackgroundFile(selectedFile)
  60. return {
  61. id,
  62. name: selectedFile.name || '',
  63. path: selectedFile.path || '',
  64. size: selectedFile.size || 0,
  65. progress: 0,
  66. status: validationError ? 'failed' : 'pending',
  67. error: validationError,
  68. validationError
  69. }
  70. }
  71. function getUploadableBackgroundFiles(queue) {
  72. return Array.isArray(queue)
  73. ? queue.filter(file => file.status === 'pending'
  74. || (file.status === 'failed' && !file.validationError))
  75. : []
  76. }
  77. function createTeamSubmitPayload(teamInfo) {
  78. const payload = Object.assign({}, teamInfo || {})
  79. payload.enterpriseWebsite = String(payload.enterpriseWebsite || '').trim()
  80. delete payload.source
  81. delete payload.coachId
  82. delete payload.uploaderId
  83. return payload
  84. }
  85. function createEmptyTextPreviewState() {
  86. return { visible: false, fileName: '', content: '', truncated: false }
  87. }
  88. function createTextPreviewState(result, fallbackFileName) {
  89. const preview = result || {}
  90. return {
  91. visible: true,
  92. fileName: preview.fileName || fallbackFileName || '文本预览',
  93. content: String(preview.content === undefined || preview.content === null ? '' : preview.content),
  94. truncated: Boolean(preview.truncated)
  95. }
  96. }
  97. module.exports = {
  98. ALLOWED_EXTENSIONS,
  99. MAX_FILE_SIZE,
  100. validateEnterpriseWebsite,
  101. validateBackgroundFile,
  102. parseSessionExpiresAt,
  103. getSessionRemainingSeconds,
  104. createBackgroundQueueItem,
  105. getUploadableBackgroundFiles,
  106. createTeamSubmitPayload,
  107. createEmptyTextPreviewState,
  108. createTextPreviewState
  109. }