| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183 |
- const ALLOWED_EXTENSIONS = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'md', 'csv']
- const MAX_FILE_SIZE = 50 * 1024 * 1024
- const BACKEND_TIMEZONE_OFFSET_MINUTES = 8 * 60
- 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}))?$/
- function validateEnterpriseWebsite(value) {
- const normalized = String(value || '').trim()
- if (!normalized) return '请输入企业官网,无官网请填写“无”'
- if (normalized === '无') return ''
- if (!/^https?:\/\/[^/\s?#]+(?:[/?#][^\s]*)?$/i.test(normalized)) return '请输入以http://或https://开头的企业官网'
- return ''
- }
- function validateBackgroundFile(file) {
- if (!file || !file.name || !file.size) return '请选择非空文件'
- if (file.name.length > 255) return '文件名不能超过255个字符'
- const ext = file.name.includes('.') ? file.name.split('.').pop().toLowerCase() : ''
- if (!ALLOWED_EXTENSIONS.includes(ext)) return '文件类型不支持'
- if (file.size > MAX_FILE_SIZE) return '单个文件不能超过50MB'
- return ''
- }
- function parseSessionExpiresAt(value) {
- const matched = SESSION_EXPIRES_AT_PATTERN.exec(String(value || '').trim())
- if (!matched) return NaN
- const year = Number(matched[1])
- const month = Number(matched[2])
- const day = Number(matched[3])
- const separator = matched[4]
- const hour = Number(matched[5])
- const minute = Number(matched[6])
- const second = Number(matched[7])
- const millisecond = Number((matched[8] || '').padEnd(3, '0'))
- if (year < 1000 || month < 1 || month > 12 || day < 1 || day > 31
- || hour > 23 || minute > 59 || second > 59) return NaN
- const hasExplicitTimezone = Boolean(matched[9] || matched[10])
- if (separator === 'T' && !hasExplicitTimezone) return NaN
- if (separator === ' ' && hasExplicitTimezone) return NaN
- const localWallClock = Date.UTC(year, month - 1, day, hour, minute, second, millisecond)
- const normalized = new Date(localWallClock)
- if (normalized.getUTCFullYear() !== year || normalized.getUTCMonth() !== month - 1
- || normalized.getUTCDate() !== day || normalized.getUTCHours() !== hour
- || normalized.getUTCMinutes() !== minute || normalized.getUTCSeconds() !== second) return NaN
- let offsetMinutes = BACKEND_TIMEZONE_OFFSET_MINUTES
- if (matched[9] === 'Z') {
- offsetMinutes = 0
- } else if (matched[10]) {
- const offsetHour = Number(matched[11])
- const offsetMinute = Number(matched[12])
- if (offsetHour > 23 || offsetMinute > 59) return NaN
- offsetMinutes = (offsetHour * 60 + offsetMinute) * (matched[10] === '+' ? 1 : -1)
- }
- return localWallClock - offsetMinutes * 60 * 1000
- }
- function getSessionRemainingSeconds(expiresAt, now = Date.now()) {
- const expiresAtTime = parseSessionExpiresAt(expiresAt)
- if (!Number.isFinite(expiresAtTime)) return 0
- return Math.max(0, Math.ceil((expiresAtTime - now) / 1000))
- }
- function createBackgroundQueueItem(file, id) {
- const selectedFile = file || {}
- const validationError = validateBackgroundFile(selectedFile)
- return {
- id,
- name: selectedFile.name || '',
- path: selectedFile.path || '',
- size: selectedFile.size || 0,
- progress: 0,
- status: validationError ? 'failed' : 'pending',
- error: validationError,
- validationError
- }
- }
- function getUploadableBackgroundFiles(queue) {
- return Array.isArray(queue)
- ? queue.filter(file => file.status === 'pending'
- || (file.status === 'failed' && !file.validationError))
- : []
- }
- function createTeamSubmitPayload(teamInfo) {
- const payload = Object.assign({}, teamInfo || {})
- payload.enterpriseWebsite = String(payload.enterpriseWebsite || '').trim()
- delete payload.source
- delete payload.coachId
- delete payload.uploaderId
- return payload
- }
- function sortPayloadValue(value) {
- if (Array.isArray(value)) return value.map(sortPayloadValue)
- if (!value || typeof value !== 'object') return value
- return Object.keys(value).sort().reduce((result, key) => {
- result[key] = sortPayloadValue(value[key])
- return result
- }, {})
- }
- function createTeamPayloadSnapshot(teamInfo) {
- return JSON.stringify(sortPayloadValue(createTeamSubmitPayload(teamInfo)))
- }
- function createEmptyTextPreviewState() {
- return { visible: false, fileName: '', content: '', truncated: false }
- }
- function createTextPreviewState(result, fallbackFileName) {
- const preview = result || {}
- return {
- visible: true,
- fileName: preview.fileName || fallbackFileName || '文本预览',
- content: String(preview.content === undefined || preview.content === null ? '' : preview.content),
- truncated: Boolean(preview.truncated)
- }
- }
- function createTeamCreationState() {
- return {
- createdTeamId: '',
- submitting: false,
- stage: 'idle',
- completed: false,
- teamPayloadSnapshot: '',
- teamQuestionnaireId: '',
- publishResult: null
- }
- }
- function beginTeamSubmission(state) {
- if (!state || state.submitting || state.completed) return false
- state.submitting = true
- return true
- }
- function rememberCreatedTeam(state, teamId) {
- if (!state) return ''
- if (!state.createdTeamId && teamId) state.createdTeamId = teamId
- return state.createdTeamId
- }
- function setTeamSubmissionStage(state, stage) {
- if (state) state.stage = stage || 'idle'
- }
- function finishTeamSubmission(state, completed = false) {
- if (!state) return
- state.submitting = false
- state.completed = state.completed || completed
- if (completed) state.stage = 'complete'
- }
- function hasUnresolvedBackgroundFiles(queue) {
- return Array.isArray(queue) && queue.some(item => item.status === 'pending'
- || item.status === 'uploading'
- || (item.status === 'failed' && !item.validationError))
- }
- module.exports = {
- ALLOWED_EXTENSIONS,
- MAX_FILE_SIZE,
- validateEnterpriseWebsite,
- validateBackgroundFile,
- parseSessionExpiresAt,
- getSessionRemainingSeconds,
- createBackgroundQueueItem,
- getUploadableBackgroundFiles,
- createTeamSubmitPayload,
- createTeamPayloadSnapshot,
- createEmptyTextPreviewState,
- createTextPreviewState,
- createTeamCreationState,
- beginTeamSubmission,
- rememberCreatedTeam,
- setTeamSubmissionStage,
- finishTeamSubmission,
- hasUnresolvedBackgroundFiles
- }
|