teamBackgroundFile.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  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. function createTeamCreationState() {
  98. return { createdTeamId: '', submitting: false, stage: 'idle', completed: false }
  99. }
  100. function beginTeamSubmission(state) {
  101. if (!state || state.submitting || state.completed) return false
  102. state.submitting = true
  103. return true
  104. }
  105. function rememberCreatedTeam(state, teamId) {
  106. if (!state) return ''
  107. if (!state.createdTeamId && teamId) state.createdTeamId = teamId
  108. return state.createdTeamId
  109. }
  110. function setTeamSubmissionStage(state, stage) {
  111. if (state) state.stage = stage || 'idle'
  112. }
  113. function finishTeamSubmission(state, completed = false) {
  114. if (!state) return
  115. state.submitting = false
  116. state.completed = state.completed || completed
  117. if (completed) state.stage = 'complete'
  118. }
  119. function hasUnresolvedBackgroundFiles(queue) {
  120. return Array.isArray(queue) && queue.some(item => item.status === 'pending'
  121. || item.status === 'uploading'
  122. || (item.status === 'failed' && !item.validationError))
  123. }
  124. module.exports = {
  125. ALLOWED_EXTENSIONS,
  126. MAX_FILE_SIZE,
  127. validateEnterpriseWebsite,
  128. validateBackgroundFile,
  129. parseSessionExpiresAt,
  130. getSessionRemainingSeconds,
  131. createBackgroundQueueItem,
  132. getUploadableBackgroundFiles,
  133. createTeamSubmitPayload,
  134. createEmptyTextPreviewState,
  135. createTextPreviewState,
  136. createTeamCreationState,
  137. beginTeamSubmission,
  138. rememberCreatedTeam,
  139. setTeamSubmissionStage,
  140. finishTeamSubmission,
  141. hasUnresolvedBackgroundFiles
  142. }