| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- import { get } from '@/core/http/http-client'
- import { HttpError } from '@/core/http/http-error'
- import { parseApiResponse, snapshotEnvelope } from '@/core/http/response-evaluator'
- import { buildRestPath } from '@/core/config/api-paths'
- import appSettings from '@/core/config/app-settings'
- import { pullHomeCategories } from '@/features/travel/gateway'
- const uploadConfigPath = () => buildRestPath('common/uploadData')
- const publishPath = () => buildRestPath('find/find/addData')
- const videoUploadPath = () => '/common/localuploadvideo'
- function resolveUploadUrl(path) {
- if (!path) return ''
- if (/^https?:\/\//.test(path)) return path
- if (path.startsWith('//')) return `https:${path}`
- const base = (appSettings.appurl || '').replace(/\/$/, '')
- return `${base}${path.startsWith('/') ? path : `/${path}`}`
- }
- function unwrapUploadConfig(payload) {
- const body = payload && typeof payload === 'object' ? payload : {}
- if (body.uploadurl) return body
- if (body.data && typeof body.data === 'object') return body.data
- return body
- }
- function parseUploadResponse(raw) {
- if (!raw) return ''
- if (typeof raw === 'string') {
- try {
- const parsed = JSON.parse(raw)
- if (parsed.data && parsed.data.fullurl) return parsed.data.fullurl
- if (parsed.data && parsed.data.url) return parsed.data.url
- if (parsed.fullurl) return parsed.fullurl
- if (parsed.url) return parsed.url
- } catch (_) {
- return ''
- }
- }
- if (typeof raw === 'object') {
- if (raw.data && raw.data.fullurl) return raw.data.fullurl
- if (raw.data && raw.data.url) return raw.data.url
- if (raw.fullurl) return raw.fullurl
- if (raw.url) return raw.url
- }
- return ''
- }
- /**
- * 上传前解码并压缩图片,降低 App 端大图 / HEIC 导致的内存闪退。
- *
- * @param {string} src
- * @returns {Promise<string>} 可用于 uploadFile 的本地路径
- */
- export function prepareImageForUpload(src) {
- return new Promise((resolve) => {
- const fallback = () => resolve(src || '')
- if (!src) {
- fallback()
- return
- }
- const compress = (path, width = 0, height = 0) => {
- if (typeof uni.compressImage !== 'function') {
- resolve(path)
- return
- }
- const opts = {
- src: path,
- quality: 60,
- success: (res) => resolve((res && res.tempFilePath) || path),
- fail: () => resolve(path)
- }
- const w = Number(width) || 0
- const h = Number(height) || 0
- if (w > 1280 || h > 1280) {
- if (w >= h) opts.compressedWidth = 1280
- else opts.compressedHeight = 1280
- }
- try {
- uni.compressImage(opts)
- } catch (_) {
- resolve(path)
- }
- }
- uni.getImageInfo({
- src,
- success: (info) => {
- const path = (info && info.path) || src
- compress(path, info && info.width, info && info.height)
- },
- fail: () => {
- // 解码失败时仍尝试压缩原路径(部分机型 getImageInfo 对 HEIC 不稳定)
- compress(src)
- }
- })
- })
- }
- /**
- * 获取图片上传配置(wanlshop/common/uploadData)
- */
- export async function fetchUploadConfig() {
- const { data, raw } = await get(uploadConfigPath())
- const cfg = unwrapUploadConfig(data)
- if (cfg.uploadurl) return cfg
- return unwrapUploadConfig(raw?.res?.data ?? raw?.data)
- }
- /**
- * 上传单张图片,返回相对路径 url
- */
- export function uploadImageFile(filePath, config) {
- return new Promise((resolve, reject) => {
- const uploadurl = resolveUploadUrl(config && config.uploadurl)
- if (!uploadurl) {
- reject(new Error('上传地址无效'))
- return
- }
- if (!filePath) {
- reject(new Error('图片文件无效'))
- return
- }
- const opts = {
- url: uploadurl,
- filePath,
- name: 'file',
- success: (res) => {
- const url = parseUploadResponse(res.data)
- if (url) resolve(url)
- else reject(new Error('上传失败'))
- },
- fail: (err) => reject(err || new Error('上传失败'))
- }
- // local 不传 formData;非 local 才带 multipart(与参考工程一致)
- if (config && config.storage !== 'local' && config.multipart) {
- opts.formData = config.multipart
- }
- uni.uploadFile(opts)
- })
- }
- function postFindData(body) {
- return new Promise((resolve, reject) => {
- let settled = false
- const finish = (fn, value) => {
- if (settled) return
- settled = true
- fn(value)
- }
- uni.request({
- url: publishPath(),
- method: 'POST',
- data: body,
- success(res) {
- try {
- snapshotEnvelope(res)
- const parsed = parseApiResponse(res)
- if (parsed.kind === 'success' || parsed.kind === 'passthrough') {
- finish(resolve, parsed.data)
- return
- }
- finish(reject, HttpError.fromParsed(parsed))
- } catch (err) {
- finish(reject, err)
- }
- },
- fail(err) {
- finish(reject, HttpError.fromNetwork(err))
- },
- complete() {
- setTimeout(() => {
- if (!settled) {
- finish(reject, new HttpError('BUSINESS', '发布失败'))
- }
- }, 0)
- }
- })
- })
- }
- function normalizeRelativeVideoPath(path) {
- if (!path) return ''
- const raw = String(path).replace(/\\\//g, '/')
- if (/^https?:\/\//i.test(raw)) {
- const match = raw.match(/^https?:\/\/[^/]+(\/.*)$/i)
- return match ? match[1] : raw
- }
- return raw.startsWith('/') ? raw : `/${raw}`
- }
- function resolveVideoPreviewUrl(data) {
- if (!data || typeof data !== 'object') return ''
- const full = data.fullurl || data.full_url || ''
- if (full) return String(full).replace(/\\\//g, '/')
- const rel = normalizeRelativeVideoPath(data.url || '')
- if (!rel) return ''
- const base = (appSettings.cdnurl || appSettings.appurl || '')
- .replace(/\/api\/?$/i, '')
- .replace(/\/$/, '')
- return `${base}${rel}`
- }
- function parseVideoUploadEnvelope(raw) {
- if (!raw) return null
- let envelope = raw
- if (typeof raw === 'string') {
- try {
- envelope = JSON.parse(raw)
- } catch (_) {
- return null
- }
- }
- const body = envelope && envelope.data
- if (body && body.video_id != null && (envelope.code === 1 || envelope.code === '1')) {
- return {
- video_id: body.video_id,
- url: normalizeRelativeVideoPath(body.url || ''),
- previewUrl: resolveVideoPreviewUrl(body)
- }
- }
- return null
- }
- /**
- * 视频标签列表(/appvideolabel/list)
- */
- export async function fetchVideoLabels() {
- try {
- return await pullHomeCategories()
- } catch (_) {
- return []
- }
- }
- /**
- * 上传短视频(/common/localuploadvideo)
- */
- export function uploadVideoFile(filePath, { fileName, onProgress } = {}) {
- return new Promise((resolve, reject) => {
- if (!filePath) {
- reject(new Error('视频文件无效'))
- return
- }
- const uploadTask = uni.uploadFile({
- url: videoUploadPath(),
- filePath,
- name: 'file',
- formData: {
- name: fileName || `video-${Date.now()}.mp4`
- },
- success: (res) => {
- const parsed = parseVideoUploadEnvelope(res.data)
- if (res.statusCode === 200 && parsed) {
- resolve(parsed)
- return
- }
- let message = '视频上传失败'
- try {
- const envelope = typeof res.data === 'string' ? JSON.parse(res.data) : res.data
- if (envelope && envelope.msg) message = String(envelope.msg)
- } catch (_) {}
- reject(new Error(message))
- },
- fail: (err) => reject(err || new Error('视频上传失败'))
- })
- if (onProgress && uploadTask && typeof uploadTask.onProgressUpdate === 'function') {
- uploadTask.onProgressUpdate(onProgress)
- }
- })
- }
- /**
- * 发布种草动态(type=want)
- * 使用 uni.request + complete,避免拦截器 return false 时 Promise 永不 settle
- */
- export function publishGrassPost({ content, images, goodsIds = [] }) {
- const body = {
- type: 'want',
- content,
- images,
- goods_ids: goodsIds,
- video_id: null,
- label_id: null
- }
- return postFindData(body)
- }
- /**
- * 发布短视频(type=video)
- */
- export function publishVideoPost({ content, videoId, videoUrl, labelId, goodsIds = [] }) {
- const body = {
- type: 'video',
- content,
- images: [],
- goods_ids: goodsIds,
- video_id: videoId,
- video: videoUrl || null,
- label_id: labelId
- }
- return postFindData(body)
- }
|