You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
235 lines
8.3 KiB
235 lines
8.3 KiB
import * as w6s from '@w6s/sdk'
|
|
import sha1 from 'crypto-js/sha1'
|
|
import { siqCode } from '@/api/enterprise'
|
|
import { baseUrl } from '@/config/env'
|
|
import { $globalStore } from '@/common/globalStore'
|
|
|
|
// ========== w6s 鉴权(参照 enterpriseHome.vue 的写法) ==========
|
|
const generateRandomNumber = () => {
|
|
const min = 100000
|
|
const max = 999999
|
|
const randomNum = (Math.floor(Math.random() * (max - min + 1)) + min)
|
|
return randomNum.toString().padStart(6, '0')
|
|
}
|
|
|
|
let w6sAuthPromise = null
|
|
|
|
// 确保 w6s 鉴权完成,缓存 Promise 避免重复鉴权
|
|
export const ensureW6sAuth = () => {
|
|
if (w6sAuthPromise) return w6sAuthPromise
|
|
w6sAuthPromise = new Promise((resolve, reject) => {
|
|
siqCode(null, success => {
|
|
let access_key = ''
|
|
let access_secret = ''
|
|
if (success.success) {
|
|
access_key = success.data.accessKey
|
|
access_secret = success.data.accessSecret
|
|
}
|
|
|
|
if (!access_key || !access_secret) {
|
|
w6sAuthPromise = null
|
|
reject(new Error('获取深i企认证信息错误: access_key 或 access_secret 为空'))
|
|
return
|
|
}
|
|
|
|
const nonce = generateRandomNumber()
|
|
const timestamp = `${Date.now()}`
|
|
|
|
const str = [access_secret, nonce, timestamp]
|
|
.sort()
|
|
.reduce((s, item) => s + item, '')
|
|
|
|
const signature = sha1(str).toString().toLowerCase()
|
|
|
|
// SDK 默认接口超时仅 5s,getLocation 等 API 未在 5s 内回调会报"接口调用超时"
|
|
// 且 takePhotoAndAddWaterMark 内部会先调 getLocation,必须放宽超时
|
|
w6s.init({
|
|
auth: true,
|
|
timeout: 5 * 60 * 1000, // 5 分钟,覆盖拍照+定位全流程
|
|
})
|
|
|
|
w6s.config({
|
|
access_key: access_key,
|
|
nonce: nonce,
|
|
timestamp: timestamp,
|
|
signature: signature,
|
|
}).then((res) => {
|
|
console.log('w6s 鉴权成功', res)
|
|
}).catch((err) => {
|
|
w6sAuthPromise = null
|
|
reject(err)
|
|
})
|
|
|
|
w6s.ready(() => {
|
|
console.log('w6s ready')
|
|
resolve()
|
|
})
|
|
}, error => {
|
|
w6sAuthPromise = null
|
|
reject(error)
|
|
})
|
|
})
|
|
return w6sAuthPromise
|
|
}
|
|
|
|
// 定位缓存(避免每次拍照都重新搜星,SDK 内部 locationEnable=true 时会阻塞等 GPS)
|
|
let _cachedLocation = null
|
|
let _locationCacheTime = 0
|
|
const LOCATION_CACHE_TTL = 2 * 60 * 1000 // 2 分钟内复用同一位置
|
|
|
|
const getCachedLocation = async () => {
|
|
if (_cachedLocation && Date.now() - _locationCacheTime < LOCATION_CACHE_TTL) {
|
|
console.log('[w6sMedia] 使用缓存定位:', _cachedLocation)
|
|
return _cachedLocation
|
|
}
|
|
await ensureW6sAuth()
|
|
_cachedLocation = await new Promise((resolve, reject) => {
|
|
w6s.location.getLocation({ success: resolve, fail: reject })
|
|
})
|
|
_locationCacheTime = Date.now()
|
|
console.log('[w6sMedia] 定位获取成功并已缓存:', _cachedLocation)
|
|
return _cachedLocation
|
|
}
|
|
|
|
// 格式化时间 yyyy-MM-dd HH:mm(与 SDK 内部 getTimeStr 格式一致)
|
|
const getTimeStr = () => {
|
|
const d = new Date()
|
|
const pad = (n) => String(n).padStart(2, '0')
|
|
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`
|
|
}
|
|
|
|
// 拍照并加水印
|
|
// 自己拼接时间+定位到 content,设 timeEnable/locationEnable 为 false 跳过 SDK 内部阻塞式 getLocation 调用
|
|
export const takePhotoWithWaterMark = async () => {
|
|
await ensureW6sAuth()
|
|
// console.log('[w6sMedia] 鉴权完成, 准备获取定位')
|
|
|
|
// // 预获取定位(带缓存),第二次拍照起直接用缓存不搜星
|
|
// let locationStr = ''
|
|
// try {
|
|
// const loc = await getCachedLocation()
|
|
// locationStr = (loc.city || '') + (loc.district || '') + (loc.street || '')
|
|
// console.log('[w6sMedia] 定位地址:', locationStr)
|
|
// } catch (e) {
|
|
// console.warn('[w6sMedia] 获取定位失败, 水印不含地址:', e)
|
|
// }
|
|
|
|
// const content = `${getTimeStr()} ${locationStr}`.trim()
|
|
// console.log('[w6sMedia] 水印 content:', content, '准备调起拍照')
|
|
|
|
return new Promise((resolve, reject) => {
|
|
w6s.image.takePhotoAndAddWaterMark({
|
|
content: '',
|
|
fontSize: 14,
|
|
color: '#FF5858',
|
|
markDisable: false,
|
|
timeEnable: true,
|
|
locationEnable: false,
|
|
success: (res) => {
|
|
console.log('[w6sMedia] takePhotoAndAddWaterMark success 原始返回:', res)
|
|
const result = res?.result || res || {}
|
|
console.log('[w6sMedia] 返回字段 imageURL:', result?.imageURL, 'key:', result?.key)
|
|
resolve(result)
|
|
},
|
|
fail: (err) => {
|
|
console.error('[w6sMedia] takePhotoAndAddWaterMark fail:', err)
|
|
reject(err)
|
|
},
|
|
})
|
|
})
|
|
}
|
|
|
|
// 录制视频(w6s 官方 API,文档 https://open.beeworks.cn/js-sdk/video.html)
|
|
// 返回 info: { video_path 本地路径, video_duration 时长, video_size 大小(K), video_thumbnail 首帧 base64 }
|
|
export const startVideoRecorder = async ({
|
|
duration = 0, // 最大录制时长(秒),0 不限制
|
|
quality = 1, // 0 高清 / 1 一般 / 2 流畅
|
|
front = false,
|
|
syncSystemAlbum = false,
|
|
} = {}) => {
|
|
console.log('[w6sMedia] startVideoRecorder 开始, 入参:', { duration, quality, front, syncSystemAlbum })
|
|
await ensureW6sAuth()
|
|
console.log('[w6sMedia] 录制前鉴权完成, 准备调起录制')
|
|
return new Promise((resolve, reject) => {
|
|
w6s.video.startVideoRecoder({
|
|
duration,
|
|
quality: String(quality),
|
|
sync_system_album: syncSystemAlbum,
|
|
front,
|
|
success: (res) => {
|
|
console.log('[w6sMedia] startVideoRecoder success 原始返回:', res)
|
|
// 实际返回结构为 { result: { code, message, info } }(SDK jsonParser 会包一层 result),而非 d.ts 声明的 { info }
|
|
const result = res?.result || res || {}
|
|
const info = result?.info || {}
|
|
if (!info.video_path) {
|
|
reject(new Error('录制未生成视频文件'))
|
|
return
|
|
}
|
|
// 客户端返回的是裸绝对路径(如 /data/data/.../xxx.mp4),FileTransfer 需要 file:// 前缀
|
|
const rawPath = info.video_path
|
|
const videoPath = /^file:\/\//.test(rawPath) ? rawPath : `file://${rawPath}`
|
|
// 从路径提取文件名(可能带查询参数)
|
|
const name = (videoPath.split('/').pop() || '').split('?')[0] || `video-${Date.now()}.mp4`
|
|
console.log('[w6sMedia] 解析出 video_path:', videoPath, 'name:', name)
|
|
resolve({
|
|
video_path: videoPath,
|
|
video_duration: info.video_duration,
|
|
video_size: info.video_size,
|
|
video_thumbnail: info.video_thumbnail,
|
|
name,
|
|
type: 'video/mp4',
|
|
})
|
|
},
|
|
fail: (err) => {
|
|
console.error('[w6sMedia] startVideoRecoder fail:', err)
|
|
reject(err)
|
|
},
|
|
})
|
|
})
|
|
}
|
|
|
|
// 获取定位(经纬度)
|
|
export const getLocation = async () => {
|
|
await ensureW6sAuth()
|
|
return new Promise((resolve, reject) => {
|
|
w6s.location.getLocation({ success: resolve, fail: reject })
|
|
})
|
|
}
|
|
|
|
// 上传本地文件到后端 /app-enf/common/file/upload(Cordova FileTransfer,图片/视频共用)
|
|
export const uploadLocalFileToBackend = ({ fileURL, fileName = 'upload', mimeType = 'image/jpeg' }) => {
|
|
console.log('[w6sMedia] uploadLocalFileToBackend 开始, 入参:', { fileURL, fileName, mimeType })
|
|
return ensureW6sAuth().then(() => uploadViaFileTransfer({ fileURL, fileName, mimeType }))
|
|
}
|
|
|
|
// FileTransfer 实际实现(@w6s/sdk 2.x 的类式 API:new FileUpload(options) → .upload(params))
|
|
const uploadViaFileTransfer = async ({ fileURL, fileName, mimeType }) => {
|
|
const token = $globalStore.useCommon?.token || ''
|
|
const api = `${baseUrl}/app-enf/common/file/upload`
|
|
const server = /^https?:/.test(api) ? api : `${window.location.origin}${api}`
|
|
console.log('[w6sMedia] FileTransfer 上传, server:', server, 'fileURL:', fileURL)
|
|
|
|
const opts = {
|
|
fileKey: 'file',
|
|
fileName,
|
|
mimeType,
|
|
headers: {
|
|
Authorization: token ? `Bearer ${token}` : '',
|
|
clientid: '9e6eb24cd4e169a98b0d2cd8034d877b',
|
|
},
|
|
}
|
|
|
|
const uploader = new w6s.file.upload(opts)
|
|
// upload 返回 Promise:responseCode === 200 时 resolve,否则 reject
|
|
const res = await uploader.upload({
|
|
fileURL,
|
|
server,
|
|
trustAllHosts: false,
|
|
})
|
|
console.log('[w6sMedia] FileTransfer success 原始返回:', res)
|
|
try {
|
|
return JSON.parse(res.response)
|
|
} catch {
|
|
return res.response
|
|
}
|
|
}
|
|
|