f
4
src/auto-imports.d.ts
vendored
@@ -48,6 +48,7 @@ declare global {
|
|||||||
const getApiPrefix: typeof import('./utils/runtimeEnv.js')['getApiPrefix']
|
const getApiPrefix: typeof import('./utils/runtimeEnv.js')['getApiPrefix']
|
||||||
const getAppDebug: typeof import('./utils/runtimeEnv.js')['getAppDebug']
|
const getAppDebug: typeof import('./utils/runtimeEnv.js')['getAppDebug']
|
||||||
const getAppName: typeof import('./utils/runtimeEnv.js')['getAppName']
|
const getAppName: typeof import('./utils/runtimeEnv.js')['getAppName']
|
||||||
|
const getCachedMergedPosterWeixin: typeof import('./utils/posterQrWeixin.js')['getCachedMergedPosterWeixin']
|
||||||
const getCompanyName: typeof import('./utils/runtimeEnv.js')['getCompanyName']
|
const getCompanyName: typeof import('./utils/runtimeEnv.js')['getCompanyName']
|
||||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||||
@@ -55,6 +56,7 @@ declare global {
|
|||||||
const getCustomerServiceUrl: typeof import('./utils/runtimeEnv.js')['getCustomerServiceUrl']
|
const getCustomerServiceUrl: typeof import('./utils/runtimeEnv.js')['getCustomerServiceUrl']
|
||||||
const getInviteChannelKey: typeof import('./utils/runtimeEnv.js')['getInviteChannelKey']
|
const getInviteChannelKey: typeof import('./utils/runtimeEnv.js')['getInviteChannelKey']
|
||||||
const getMeShareTitle: typeof import('./utils/runtimeEnv.js')['getMeShareTitle']
|
const getMeShareTitle: typeof import('./utils/runtimeEnv.js')['getMeShareTitle']
|
||||||
|
const getPosterOrigin: typeof import('./utils/runtimeEnv.js')['getPosterOrigin']
|
||||||
const getPosterSrcList: typeof import('./utils/posterQrWeixin.js')['getPosterSrcList']
|
const getPosterSrcList: typeof import('./utils/posterQrWeixin.js')['getPosterSrcList']
|
||||||
const getShareTitle: typeof import('./utils/runtimeEnv.js')['getShareTitle']
|
const getShareTitle: typeof import('./utils/runtimeEnv.js')['getShareTitle']
|
||||||
const getSiteOrigin: typeof import('./utils/runtimeEnv.js')['getSiteOrigin']
|
const getSiteOrigin: typeof import('./utils/runtimeEnv.js')['getSiteOrigin']
|
||||||
@@ -393,6 +395,7 @@ declare module 'vue' {
|
|||||||
readonly getApiPrefix: UnwrapRef<typeof import('./utils/runtimeEnv.js')['getApiPrefix']>
|
readonly getApiPrefix: UnwrapRef<typeof import('./utils/runtimeEnv.js')['getApiPrefix']>
|
||||||
readonly getAppDebug: UnwrapRef<typeof import('./utils/runtimeEnv.js')['getAppDebug']>
|
readonly getAppDebug: UnwrapRef<typeof import('./utils/runtimeEnv.js')['getAppDebug']>
|
||||||
readonly getAppName: UnwrapRef<typeof import('./utils/runtimeEnv.js')['getAppName']>
|
readonly getAppName: UnwrapRef<typeof import('./utils/runtimeEnv.js')['getAppName']>
|
||||||
|
readonly getCachedMergedPosterWeixin: UnwrapRef<typeof import('./utils/posterQrWeixin.js')['getCachedMergedPosterWeixin']>
|
||||||
readonly getCompanyName: UnwrapRef<typeof import('./utils/runtimeEnv.js')['getCompanyName']>
|
readonly getCompanyName: UnwrapRef<typeof import('./utils/runtimeEnv.js')['getCompanyName']>
|
||||||
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
|
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
|
||||||
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
|
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
|
||||||
@@ -400,6 +403,7 @@ declare module 'vue' {
|
|||||||
readonly getCustomerServiceUrl: UnwrapRef<typeof import('./utils/runtimeEnv.js')['getCustomerServiceUrl']>
|
readonly getCustomerServiceUrl: UnwrapRef<typeof import('./utils/runtimeEnv.js')['getCustomerServiceUrl']>
|
||||||
readonly getInviteChannelKey: UnwrapRef<typeof import('./utils/runtimeEnv.js')['getInviteChannelKey']>
|
readonly getInviteChannelKey: UnwrapRef<typeof import('./utils/runtimeEnv.js')['getInviteChannelKey']>
|
||||||
readonly getMeShareTitle: UnwrapRef<typeof import('./utils/runtimeEnv.js')['getMeShareTitle']>
|
readonly getMeShareTitle: UnwrapRef<typeof import('./utils/runtimeEnv.js')['getMeShareTitle']>
|
||||||
|
readonly getPosterOrigin: UnwrapRef<typeof import('./utils/runtimeEnv.js')['getPosterOrigin']>
|
||||||
readonly getPosterSrcList: UnwrapRef<typeof import('./utils/posterQrWeixin.js')['getPosterSrcList']>
|
readonly getPosterSrcList: UnwrapRef<typeof import('./utils/posterQrWeixin.js')['getPosterSrcList']>
|
||||||
readonly getShareTitle: UnwrapRef<typeof import('./utils/runtimeEnv.js')['getShareTitle']>
|
readonly getShareTitle: UnwrapRef<typeof import('./utils/runtimeEnv.js')['getShareTitle']>
|
||||||
readonly getSiteOrigin: UnwrapRef<typeof import('./utils/runtimeEnv.js')['getSiteOrigin']>
|
readonly getSiteOrigin: UnwrapRef<typeof import('./utils/runtimeEnv.js')['getSiteOrigin']>
|
||||||
|
|||||||
@@ -3,52 +3,29 @@
|
|||||||
<!-- #ifdef MP-WEIXIN -->
|
<!-- #ifdef MP-WEIXIN -->
|
||||||
<view class="max-h-[calc(100vh-100px)] m-4">
|
<view class="max-h-[calc(100vh-100px)] m-4">
|
||||||
<!-- 单 canvas 离屏绘制,避免 swiper 未挂载项取不到节点 -->
|
<!-- 单 canvas 离屏绘制,避免 swiper 未挂载项取不到节点 -->
|
||||||
<canvas
|
<canvas id="mpPosterCanvas" canvas-id="mpPosterCanvas" type="2d" class="mp-poster-canvas-hidden" />
|
||||||
id="mpPosterCanvas"
|
|
||||||
canvas-id="mpPosterCanvas"
|
|
||||||
type="2d"
|
|
||||||
class="mp-poster-canvas-hidden"
|
|
||||||
/>
|
|
||||||
<view class="p-2 flex justify-center">
|
<view class="p-2 flex justify-center">
|
||||||
<swiper
|
<swiper :key="swiperMountKey" class="mp-poster-swiper w-full" :style="{ height: swiperHeightPx + 'px' }"
|
||||||
:key="swiperMountKey"
|
:current="currentSwiperIndex" :duration="280" :easing-function="easeOutCubic" @change="onSwiperChange">
|
||||||
class="mp-poster-swiper w-full"
|
|
||||||
:style="{ height: swiperHeightPx + 'px' }"
|
|
||||||
:duration="280"
|
|
||||||
:easing-function="easeOutCubic"
|
|
||||||
@change="onSwiperChange"
|
|
||||||
>
|
|
||||||
<swiper-item v-for="(_, idx) in posterSrcList" :key="idx" class="mp-swiper-item">
|
<swiper-item v-for="(_, idx) in posterSrcList" :key="idx" class="mp-swiper-item">
|
||||||
<view class="mp-poster-item">
|
<view class="mp-poster-item">
|
||||||
<image
|
<image v-if="renderedPaths[idx]" :src="renderedPaths[idx]" mode="aspectFit"
|
||||||
v-if="renderedPaths[idx]"
|
class="rounded-xl shadow poster-preview-mp" :style="posterPreviewStyle" />
|
||||||
:src="renderedPaths[idx]"
|
|
||||||
mode="aspectFit"
|
|
||||||
class="rounded-xl shadow poster-preview-mp"
|
|
||||||
:style="posterPreviewStyle"
|
|
||||||
/>
|
|
||||||
<view v-else class="text-gray-400 text-sm py-16">生成海报中…</view>
|
<view v-else class="text-gray-400 text-sm py-16">生成海报中…</view>
|
||||||
</view>
|
</view>
|
||||||
</swiper-item>
|
</swiper-item>
|
||||||
</swiper>
|
</swiper>
|
||||||
</view>
|
</view>
|
||||||
<view
|
<view v-if="mode === 'promote'" class="text-center text-gray-500 text-xs mb-2">
|
||||||
v-if="mode === 'promote'"
|
|
||||||
class="text-center text-gray-500 text-xs mb-2"
|
|
||||||
>
|
|
||||||
← 左右滑动切换海报 →
|
← 左右滑动切换海报 →
|
||||||
</view>
|
</view>
|
||||||
<view class="divider">分享与保存</view>
|
<view class="divider">分享与保存</view>
|
||||||
<view class="mp-share-actions">
|
<view class="mp-share-actions">
|
||||||
<button
|
<!-- <button class="share-mp-btn flex flex-col items-center justify-center" open-type="share" plain
|
||||||
class="share-mp-btn flex flex-col items-center justify-center"
|
@tap="onShareFriendPrepare">
|
||||||
open-type="share"
|
|
||||||
plain
|
|
||||||
@tap="onShareFriendPrepare"
|
|
||||||
>
|
|
||||||
<image src="/static/image/icon_share_friends.svg" class="w-10 h-10 rounded-full" />
|
<image src="/static/image/icon_share_friends.svg" class="w-10 h-10 rounded-full" />
|
||||||
<text class="text-center mt-1 text-gray-600 text-xs">分享给好友</text>
|
<text class="text-center mt-1 text-gray-600 text-xs">分享给好友</text>
|
||||||
</button>
|
</button> -->
|
||||||
<view class="flex flex-col items-center justify-center" @click="savePoster">
|
<view class="flex flex-col items-center justify-center" @click="savePoster">
|
||||||
<image src="/static/image/icon_share_img.svg" class="w-10 h-10 rounded-full" />
|
<image src="/static/image/icon_share_img.svg" class="w-10 h-10 rounded-full" />
|
||||||
<view class="text-center mt-1 text-gray-600 text-xs">保存图片</view>
|
<view class="text-center mt-1 text-gray-600 text-xs">保存图片</view>
|
||||||
@@ -65,14 +42,9 @@
|
|||||||
<view class="max-h-[calc(100vh-100px)] m-4">
|
<view class="max-h-[calc(100vh-100px)] m-4">
|
||||||
<view class="p-4 flex justify-center">
|
<view class="p-4 flex justify-center">
|
||||||
<view class="max-h-[70vh] rounded-xl overflow-hidden">
|
<view class="max-h-[70vh] rounded-xl overflow-hidden">
|
||||||
<image
|
<image :src="posterImageUrlRemote" class="rounded-xl shadow poster-image"
|
||||||
:src="posterImageUrlRemote"
|
:style="{ width: imageWidth + 'px', height: imageHeight + 'px' }" mode="aspectFit" @load="onImageLoad"
|
||||||
class="rounded-xl shadow poster-image"
|
@error="onImageError" />
|
||||||
:style="{ width: imageWidth + 'px', height: imageHeight + 'px' }"
|
|
||||||
mode="aspectFit"
|
|
||||||
@load="onImageLoad"
|
|
||||||
@error="onImageError"
|
|
||||||
/>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="divider">分享到好友</view>
|
<view class="divider">分享到好友</view>
|
||||||
@@ -97,7 +69,7 @@ import { getApiBaseUrl, getAgentTabShareTitle, getShareTitle } from '@/utils/run
|
|||||||
import { buildPromotionH5Url } from '@/utils/promotionH5Url.js'
|
import { buildPromotionH5Url } from '@/utils/promotionH5Url.js'
|
||||||
import { setMiniPromotionShareFriend } from '@/utils/miniPromotionSharePayload.js'
|
import { setMiniPromotionShareFriend } from '@/utils/miniPromotionSharePayload.js'
|
||||||
// #ifdef MP-WEIXIN
|
// #ifdef MP-WEIXIN
|
||||||
import { getPosterSrcList, drawMergedPosterWeixin } from '@/utils/posterQrWeixin.js'
|
import { getPosterSrcList, drawMergedPosterWeixin, getCachedMergedPosterWeixin } from '@/utils/posterQrWeixin.js'
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -184,12 +156,40 @@ function updateMpPosterLayout() {
|
|||||||
|
|
||||||
/** 串行生成,避免多索引共用同一 canvas 竞态 */
|
/** 串行生成,避免多索引共用同一 canvas 竞态 */
|
||||||
let mpGenSeq = Promise.resolve()
|
let mpGenSeq = Promise.resolve()
|
||||||
|
const mpRenderStateCache = new Map()
|
||||||
|
|
||||||
|
function getRenderCacheKey() {
|
||||||
|
return `${mode.value}::${linkIdentifier.value || ''}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateRenderCache(partial) {
|
||||||
|
const key = getRenderCacheKey()
|
||||||
|
const prev = mpRenderStateCache.get(key) || { paths: [], lastIndex: 0 }
|
||||||
|
const next = {
|
||||||
|
paths: Array.isArray(partial.paths) ? [...partial.paths] : prev.paths,
|
||||||
|
lastIndex: Number.isFinite(partial.lastIndex) ? partial.lastIndex : prev.lastIndex,
|
||||||
|
}
|
||||||
|
mpRenderStateCache.set(key, next)
|
||||||
|
}
|
||||||
|
|
||||||
function resetMpPosters() {
|
function resetMpPosters() {
|
||||||
mpGenSeq = Promise.resolve()
|
mpGenSeq = Promise.resolve()
|
||||||
const n = posterSrcList.value.length
|
const n = posterSrcList.value.length
|
||||||
renderedPaths.value = Array.from({ length: n }, () => '')
|
const cache = mpRenderStateCache.get(getRenderCacheKey())
|
||||||
currentSwiperIndex.value = 0
|
const initialPaths = Array.from({ length: n }, (_, idx) => {
|
||||||
|
const saved = cache?.paths?.[idx]
|
||||||
|
if (saved) return saved
|
||||||
|
return getCachedMergedPosterWeixin({
|
||||||
|
linkUrl: generalUrl(),
|
||||||
|
posterSrc: posterSrcList.value[idx],
|
||||||
|
mode: mode.value,
|
||||||
|
index: idx,
|
||||||
|
}) || ''
|
||||||
|
})
|
||||||
|
renderedPaths.value = initialPaths
|
||||||
|
const cachedIndex = cache?.lastIndex ?? 0
|
||||||
|
currentSwiperIndex.value = Math.min(Math.max(cachedIndex, 0), Math.max(n - 1, 0))
|
||||||
|
updateRenderCache({ paths: initialPaths, lastIndex: currentSwiperIndex.value })
|
||||||
}
|
}
|
||||||
|
|
||||||
function generatePosterMp(index) {
|
function generatePosterMp(index) {
|
||||||
@@ -198,7 +198,7 @@ function generatePosterMp(index) {
|
|||||||
if (!proxy) return Promise.resolve()
|
if (!proxy) return Promise.resolve()
|
||||||
|
|
||||||
mpGenSeq = mpGenSeq
|
mpGenSeq = mpGenSeq
|
||||||
.catch(() => {})
|
.catch(() => { })
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
if (renderedPaths.value[index]) return
|
if (renderedPaths.value[index]) return
|
||||||
await new Promise((r) => setTimeout(r, 80))
|
await new Promise((r) => setTimeout(r, 80))
|
||||||
@@ -224,6 +224,7 @@ function generatePosterMp(index) {
|
|||||||
const next = [...renderedPaths.value]
|
const next = [...renderedPaths.value]
|
||||||
next[index] = temp
|
next[index] = temp
|
||||||
renderedPaths.value = next
|
renderedPaths.value = next
|
||||||
|
updateRenderCache({ paths: next })
|
||||||
})
|
})
|
||||||
return mpGenSeq
|
return mpGenSeq
|
||||||
}
|
}
|
||||||
@@ -231,6 +232,7 @@ function generatePosterMp(index) {
|
|||||||
function onSwiperChange(e) {
|
function onSwiperChange(e) {
|
||||||
const cur = e.detail?.current ?? 0
|
const cur = e.detail?.current ?? 0
|
||||||
currentSwiperIndex.value = cur
|
currentSwiperIndex.value = cur
|
||||||
|
updateRenderCache({ lastIndex: cur })
|
||||||
if (!renderedPaths.value[cur]) {
|
if (!renderedPaths.value[cur]) {
|
||||||
// 等 swiper 切换动画走一部分再跑 canvas,减轻主线程卡顿
|
// 等 swiper 切换动画走一部分再跑 canvas,减轻主线程卡顿
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -249,7 +251,7 @@ watch(show, (v) => {
|
|||||||
swiperMountKey.value += 1
|
swiperMountKey.value += 1
|
||||||
resetMpPosters()
|
resetMpPosters()
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
generatePosterMp(0).catch((err) => {
|
generatePosterMp(currentSwiperIndex.value).catch((err) => {
|
||||||
console.error('生成海报失败', err)
|
console.error('生成海报失败', err)
|
||||||
uni.showToast({ title: '海报生成失败', icon: 'none' })
|
uni.showToast({ title: '海报生成失败', icon: 'none' })
|
||||||
})
|
})
|
||||||
@@ -264,7 +266,7 @@ watch([mode, linkIdentifier], () => {
|
|||||||
swiperMountKey.value += 1
|
swiperMountKey.value += 1
|
||||||
resetMpPosters()
|
resetMpPosters()
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
generatePosterMp(0).catch(() => {})
|
generatePosterMp(currentSwiperIndex.value).catch(() => { })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -319,7 +321,7 @@ function onImageLoad() {
|
|||||||
imageWidth.value = size.width
|
imageWidth.value = size.width
|
||||||
imageHeight.value = size.height
|
imageHeight.value = size.height
|
||||||
},
|
},
|
||||||
fail: () => {},
|
fail: () => { },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -518,5 +520,6 @@ function copyUrl() {
|
|||||||
.share-mp-btn::after {
|
.share-mp-btn::after {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* #endif */
|
/* #endif */
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"apiUrl": "http://127.0.0.1:8888",
|
"apiUrl": "http://127.0.0.1:8888",
|
||||||
"apiPrefix": "/api/v1",
|
"apiPrefix": "/api/v1",
|
||||||
|
"posterUrl": "http://127.0.0.1:8888/posts",
|
||||||
"siteOrigin": "http://127.0.0.1:8888",
|
"siteOrigin": "http://127.0.0.1:8888",
|
||||||
"appName": "赤眉",
|
"appName": "赤眉",
|
||||||
"companyName": "戎行技术有限公司有限公司",
|
"companyName": "戎行技术有限公司有限公司",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"apiUrl": "https://chimei.ronsafe.cn",
|
"apiUrl": "https://chimei.ronsafe.cn",
|
||||||
"apiPrefix": "/api/v1",
|
"apiPrefix": "/api/v1",
|
||||||
|
"posterUrl": "https://chimei.ronsafe.cn/posts",
|
||||||
"siteOrigin": "https://chimei.ronsafe.cn",
|
"siteOrigin": "https://chimei.ronsafe.cn",
|
||||||
"appName": "赤眉",
|
"appName": "赤眉",
|
||||||
"companyName": "戎行技术有限公司有限公司",
|
"companyName": "戎行技术有限公司有限公司",
|
||||||
|
|||||||
1
src/env.d.ts
vendored
@@ -4,6 +4,7 @@
|
|||||||
interface BdrpRuntimeConfig {
|
interface BdrpRuntimeConfig {
|
||||||
apiUrl: string
|
apiUrl: string
|
||||||
apiPrefix: string
|
apiPrefix: string
|
||||||
|
posterUrl: string
|
||||||
siteOrigin: string
|
siteOrigin: string
|
||||||
appName: string
|
appName: string
|
||||||
companyName: string
|
companyName: string
|
||||||
|
|||||||
@@ -164,6 +164,5 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"__esModule": true,
|
|
||||||
"subPackages": []
|
"subPackages": []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,21 +111,12 @@
|
|||||||
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
||||||
import { getAgentMembershipUserConfig, saveAgentMembershipUserConfig, getProductConfig } from '@/apis/agent'
|
import { getAgentMembershipUserConfig, saveAgentMembershipUserConfig, getProductConfig } from '@/apis/agent'
|
||||||
|
|
||||||
// 报告类型选项 - 与 webview 保持一致,支持从后端动态加载
|
// 报告类型选项:完全由后端返回
|
||||||
const reportOptions = ref([
|
const reportOptions = ref([])
|
||||||
{ label: '入职风险', value: 1 },
|
|
||||||
{ label: '小微企业', value: 2 },
|
|
||||||
{ label: '家政风险', value: 3 },
|
|
||||||
{ label: '婚恋风险', value: 4 },
|
|
||||||
{ label: '贷前风险', value: 5 },
|
|
||||||
{ label: '租赁风险', value: 6 },
|
|
||||||
{ label: '个人风险', value: 7 },
|
|
||||||
{ label: '个人大数据', value: 27 },
|
|
||||||
])
|
|
||||||
|
|
||||||
// 状态管理
|
// 状态管理
|
||||||
const showPicker = ref(false)
|
const showPicker = ref(false)
|
||||||
const selectedReportId = ref(1)
|
const selectedReportId = ref(null)
|
||||||
const selectedReportText = computed(() => {
|
const selectedReportText = computed(() => {
|
||||||
const opt = reportOptions.value.find((o) => o.value === selectedReportId.value)
|
const opt = reportOptions.value.find((o) => o.value === selectedReportId.value)
|
||||||
return opt ? opt.label : '请选择'
|
return opt ? opt.label : '请选择'
|
||||||
@@ -248,6 +239,8 @@ const validateRatio = () => {
|
|||||||
|
|
||||||
// 获取配置
|
// 获取配置
|
||||||
const getConfig = async () => {
|
const getConfig = async () => {
|
||||||
|
if (!selectedReportId.value)
|
||||||
|
return
|
||||||
try {
|
try {
|
||||||
const res = await getAgentMembershipUserConfig({ product_id: selectedReportId.value })
|
const res = await getAgentMembershipUserConfig({ product_id: selectedReportId.value })
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
@@ -357,36 +350,29 @@ const closeRangeError = () => {
|
|||||||
}, 2000)
|
}, 2000)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从后端加载可选报告列表(可选,若失败则使用默认列表)
|
// 从后端加载可选报告列表
|
||||||
const loadReportOptions = async () => {
|
const loadReportOptions = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await getProductConfig()
|
const res = await getProductConfig()
|
||||||
if (res.code !== 200 || !res.data) return
|
if (res.code !== 200 || !res.data)
|
||||||
|
return
|
||||||
const list = res.data.agent_product_config || res.data.AgentProductConfig || []
|
const list = res.data.agent_product_config || res.data.AgentProductConfig || []
|
||||||
if (list.length) {
|
reportOptions.value = list
|
||||||
const productNameMap = {
|
.map(p => ({
|
||||||
1: '入职风险',
|
label: p.product_name || `报告${p.product_id}`,
|
||||||
2: '小微企业',
|
|
||||||
3: '家政风险',
|
|
||||||
4: '婚恋风险',
|
|
||||||
5: '贷前风险',
|
|
||||||
6: '租赁风险',
|
|
||||||
7: '个人风险',
|
|
||||||
27: '个人大数据',
|
|
||||||
}
|
|
||||||
reportOptions.value = list.map((p) => ({
|
|
||||||
label: productNameMap[p.product_id] || `报告${p.product_id}`,
|
|
||||||
value: p.product_id,
|
value: p.product_id,
|
||||||
}))
|
}))
|
||||||
if (!reportOptions.value.find((o) => o.value === selectedReportId.value)) {
|
.filter(p => p.value != null)
|
||||||
|
if (reportOptions.value.length > 0) {
|
||||||
|
if (!reportOptions.value.find(o => o.value === selectedReportId.value))
|
||||||
selectedReportId.value = reportOptions.value[0].value
|
selectedReportId.value = reportOptions.value[0].value
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadReportOptions()
|
await loadReportOptions()
|
||||||
|
if (selectedReportId.value)
|
||||||
getConfig()
|
getConfig()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<wd-form :model="formData" ref="promotionForm">
|
<wd-form :model="formData" ref="promotionForm">
|
||||||
<wd-cell-group border>
|
<wd-cell-group border>
|
||||||
<!-- 报告类型 -->
|
<!-- 报告类型 -->
|
||||||
<wd-picker label="报告类型" label-width="100px" v-model="formData.productType" :columns="[reportTypes]"
|
<wd-picker label="报告类型" label-width="100px" v-model="formData.productType" :columns="reportTypes"
|
||||||
title="选择报告类型" prop="productType" placeholder="请选择报告类型" @confirm="onConfirmType"
|
title="选择报告类型" prop="productType" placeholder="请选择报告类型" @confirm="onConfirmType"
|
||||||
:rules="[{ required: true, message: '请选择报告类型' }]" />
|
:rules="[{ required: true, message: '请选择报告类型' }]" />
|
||||||
|
|
||||||
@@ -76,17 +76,6 @@ import GzhQrcode from '@/components/GzhQrcode.vue'
|
|||||||
|
|
||||||
usePromotionShareHandlers({ defaultTitle: getAgentTabShareTitle() })
|
usePromotionShareHandlers({ defaultTitle: getAgentTabShareTitle() })
|
||||||
|
|
||||||
// 报告类型
|
|
||||||
const reportTypes = [
|
|
||||||
{ label: '人事背调', value: 'backgroundcheck', id: 1 },
|
|
||||||
{ label: '老板企业报告', value: 'companyinfo', id: 2 },
|
|
||||||
{ label: '家政风险', value: 'homeservice', id: 3 },
|
|
||||||
{ label: '婚恋风险', value: 'marriage', id: 4 },
|
|
||||||
{ label: '贷前背调', value: 'preloanbackgroundcheck', id: 5 },
|
|
||||||
{ label: '租赁风险', value: 'rentalrisk', id: 6 },
|
|
||||||
{ label: '个人风险', value: 'riskassessment', id: 7 }
|
|
||||||
]
|
|
||||||
|
|
||||||
// 状态管理
|
// 状态管理
|
||||||
const promotionForm = ref(null)
|
const promotionForm = ref(null)
|
||||||
const showPricePicker = ref(false)
|
const showPricePicker = ref(false)
|
||||||
@@ -102,6 +91,18 @@ const formData = ref({
|
|||||||
clientPrice: null
|
clientPrice: null
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const reportTypes = computed(() => {
|
||||||
|
if (!productConfig.value?.length)
|
||||||
|
return []
|
||||||
|
return productConfig.value
|
||||||
|
.map(item => ({
|
||||||
|
id: item.product_id,
|
||||||
|
label: item.product_name || `产品${item.product_id}`,
|
||||||
|
value: item.product_en || '',
|
||||||
|
}))
|
||||||
|
.filter(item => !!item.value)
|
||||||
|
})
|
||||||
|
|
||||||
// 计算成本价格
|
// 计算成本价格
|
||||||
const costPrice = computed(() => {
|
const costPrice = computed(() => {
|
||||||
if (!pickerProductConfig.value) return '0.00'
|
if (!pickerProductConfig.value) return '0.00'
|
||||||
@@ -153,16 +154,6 @@ const generatePromotionCode = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 获取选中产品的完整信息
|
|
||||||
const reportType = reportTypes.find(item => item.value === formData.value.productType)
|
|
||||||
if (!reportType) {
|
|
||||||
uni.showToast({
|
|
||||||
title: '请选择有效的报告类型',
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await generatePromotionLink({
|
const res = await generatePromotionLink({
|
||||||
product: formData.value.productType,
|
product: formData.value.productType,
|
||||||
price: formData.value.clientPrice
|
price: formData.value.clientPrice
|
||||||
@@ -187,20 +178,30 @@ const generatePromotionCode = async () => {
|
|||||||
|
|
||||||
// 选择类型
|
// 选择类型
|
||||||
const selectProductType = (reportTypeValue) => {
|
const selectProductType = (reportTypeValue) => {
|
||||||
const reportType = reportTypes.find(item => item.id === reportTypeValue || item.value === reportTypeValue)
|
const reportType = reportTypes.value.find(item => item.id === reportTypeValue || item.value === reportTypeValue)
|
||||||
|
|
||||||
if (!reportType) return
|
if (!reportType) {
|
||||||
|
pickerProductConfig.value = null
|
||||||
|
formData.value.productType = ''
|
||||||
|
formData.value.clientPrice = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
formData.value.productType = reportType.value
|
formData.value.productType = reportType.value
|
||||||
|
|
||||||
if (productConfig.value) {
|
if (!productConfig.value) {
|
||||||
for (let i of productConfig.value) {
|
pickerProductConfig.value = null
|
||||||
if (i.product_id === reportType.id) {
|
formData.value.clientPrice = null
|
||||||
pickerProductConfig.value = i
|
return
|
||||||
formData.value.clientPrice = i.p_pricing_standard.toString()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
const matchedConfig = productConfig.value.find(item => item.product_id === reportType.id)
|
||||||
|
if (!matchedConfig) {
|
||||||
|
pickerProductConfig.value = null
|
||||||
|
formData.value.clientPrice = null
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
pickerProductConfig.value = matchedConfig
|
||||||
|
formData.value.clientPrice = Number(matchedConfig.p_pricing_standard)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取产品配置
|
// 获取产品配置
|
||||||
@@ -209,9 +210,19 @@ const getPromoteConfig = async () => {
|
|||||||
const res = await getProductConfig()
|
const res = await getProductConfig()
|
||||||
|
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
productConfig.value = res.data.AgentProductConfig
|
productConfig.value = res.data.AgentProductConfig || []
|
||||||
// 选择第一个报告类型
|
const firstType = reportTypes.value[0]
|
||||||
selectProductType(1) // 使用ID 1选择第一个报告类型
|
if (firstType) {
|
||||||
|
selectProductType(firstType.value)
|
||||||
|
} else {
|
||||||
|
pickerProductConfig.value = null
|
||||||
|
formData.value.productType = ''
|
||||||
|
formData.value.clientPrice = null
|
||||||
|
uni.showToast({
|
||||||
|
title: '暂无可推广产品',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: res.msg || '获取配置失败',
|
title: res.msg || '获取配置失败',
|
||||||
@@ -233,11 +244,9 @@ const onPriceChange = (price) => {
|
|||||||
|
|
||||||
// 类型选择确认
|
// 类型选择确认
|
||||||
const onConfirmType = (e) => {
|
const onConfirmType = (e) => {
|
||||||
// picker在单列模式下返回的是选中项的值
|
const selectedValue = e?.value?.[0] ?? e?.value ?? e?.selectedOptions?.[0]?.value
|
||||||
if (e && e.value && e.value.length > 0) {
|
if (selectedValue !== undefined && selectedValue !== null && selectedValue !== '')
|
||||||
const selectedValue = e.value[0]
|
|
||||||
selectProductType(selectedValue)
|
selectProductType(selectedValue)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示公众号二维码
|
// 显示公众号二维码
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 262 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 807 KiB |
|
Before Width: | Height: | Size: 409 KiB |
|
Before Width: | Height: | Size: 415 KiB |
|
Before Width: | Height: | Size: 572 KiB |
|
Before Width: | Height: | Size: 618 KiB |
|
Before Width: | Height: | Size: 392 KiB |
|
Before Width: | Height: | Size: 181 KiB |
|
Before Width: | Height: | Size: 297 KiB |
|
Before Width: | Height: | Size: 264 KiB |
|
Before Width: | Height: | Size: 585 KiB |
@@ -1,7 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* 微信小程序:本地海报底图 + uQRCode 合成(与 webview QRcode.vue 配置对齐)
|
* 微信小程序:服务端海报底图 + uQRCode 合成(与 webview QRcode.vue 配置对齐)
|
||||||
*/
|
*/
|
||||||
import UQRCode from 'uqrcodejs'
|
import UQRCode from "uqrcodejs";
|
||||||
|
import { getPosterOrigin } from "@/utils/runtimeEnv.js";
|
||||||
|
|
||||||
/** 与 webview src/components/QRcode.vue 中 qrCodePositions 一致 */
|
/** 与 webview src/components/QRcode.vue 中 qrCodePositions 一致 */
|
||||||
export const POSTER_QR_POSITIONS = {
|
export const POSTER_QR_POSITIONS = {
|
||||||
@@ -16,43 +17,58 @@ export const POSTER_QR_POSITIONS = {
|
|||||||
{ x: 255, y: 940, size: 250 },
|
{ x: 255, y: 940, size: 250 },
|
||||||
],
|
],
|
||||||
invitation: [{ x: 360, y: -1370, size: 360 }],
|
invitation: [{ x: 360, y: -1370, size: 360 }],
|
||||||
|
};
|
||||||
|
|
||||||
|
function buildPosterUrl(filename) {
|
||||||
|
return `${getPosterOrigin()}/${filename}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPosterSrcList(mode) {
|
export function getPosterSrcList(mode) {
|
||||||
if (mode === 'invitation') {
|
if (mode === "invitation") {
|
||||||
return ['/static/poster/yq_qrcode_1.png']
|
return [buildPosterUrl("yq_qrcode_1.png")];
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
'/static/poster/tg_qrcode_1.png',
|
buildPosterUrl("tg_qrcode_1.png"),
|
||||||
'/static/poster/tg_qrcode_2.png',
|
buildPosterUrl("tg_qrcode_2.png"),
|
||||||
'/static/poster/tg_qrcode_3.png',
|
buildPosterUrl("tg_qrcode_3.png"),
|
||||||
'/static/poster/tg_qrcode_4.png',
|
buildPosterUrl("tg_qrcode_4.png"),
|
||||||
'/static/poster/tg_qrcode_5.png',
|
buildPosterUrl("tg_qrcode_5.png"),
|
||||||
'/static/poster/tg_qrcode_6.png',
|
buildPosterUrl("tg_qrcode_6.png"),
|
||||||
'/static/poster/tg_qrcode_7.png',
|
buildPosterUrl("tg_qrcode_7.png"),
|
||||||
'/static/poster/tg_qrcode_8.png',
|
buildPosterUrl("tg_qrcode_8.png"),
|
||||||
]
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getWx() {
|
function getWx() {
|
||||||
return typeof wx !== 'undefined' ? wx : null
|
return typeof wx !== "undefined" ? wx : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mergedPosterCache = new Map();
|
||||||
|
|
||||||
|
function buildMergedPosterCacheKey({ linkUrl, posterSrc, mode, index }) {
|
||||||
|
return `${mode || "promote"}::${index || 0}::${posterSrc || ""}::${linkUrl || ""}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCachedMergedPosterWeixin(opts) {
|
||||||
|
const key = buildMergedPosterCacheKey(opts || {});
|
||||||
|
return mergedPosterCache.get(key) || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 与 webview 一致:优先 png,不存在则尝试同名的 jpg(避免仅提交了 jpg 时小程序侧失败)
|
* 与 webview 一致:优先 png,不存在则尝试同名的 jpg(避免仅提交了 jpg 时小程序侧失败)
|
||||||
* @param {string} posterSrc 如 /static/poster/tg_qrcode_2.png
|
* @param {string} posterSrc 如 https://example.com/posts/tg_qrcode_2.png
|
||||||
*/
|
*/
|
||||||
function getPosterSrcCandidates(posterSrc) {
|
function getPosterSrcCandidates(posterSrc) {
|
||||||
const base = posterSrc.replace(/\.(png|jpg|jpeg)$/i, '')
|
const base = posterSrc.replace(/\.(png|jpg|jpeg)$/i, "");
|
||||||
return [`${base}.png`, `${base}.jpg`, `${base}.jpeg`]
|
return [`${base}.png`, `${base}.jpg`, `${base}.jpeg`];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Promise<{ info: Record<string, unknown>, resolvedSrc: string }>}
|
* @returns {Promise<{ info: Record<string, unknown>, resolvedSrc: string }>}
|
||||||
*/
|
*/
|
||||||
async function resolvePosterImageInfo(posterSrc) {
|
async function resolvePosterImageInfo(posterSrc) {
|
||||||
const candidates = getPosterSrcCandidates(posterSrc)
|
const candidates = getPosterSrcCandidates(posterSrc);
|
||||||
let lastErr = ''
|
let lastErr = "";
|
||||||
for (const src of candidates) {
|
for (const src of candidates) {
|
||||||
try {
|
try {
|
||||||
const info = await new Promise((resolve, reject) => {
|
const info = await new Promise((resolve, reject) => {
|
||||||
@@ -60,16 +76,16 @@ async function resolvePosterImageInfo(posterSrc) {
|
|||||||
src,
|
src,
|
||||||
success: resolve,
|
success: resolve,
|
||||||
fail: (e) => reject(e),
|
fail: (e) => reject(e),
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
return { info, resolvedSrc: src }
|
return { info, resolvedSrc: src };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
lastErr = (e && (e.errMsg || e.message)) || String(e)
|
lastErr = (e && (e.errMsg || e.message)) || String(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`海报底图无法加载(${lastErr})。请将 tg_qrcode_1~8、yq_qrcode_1 等底图放入小程序目录 src/static/poster/,与同仓库 webview 的 assets 命名一致(支持 .png 或 .jpg)。当前已尝试:${candidates.join('、')}`,
|
`海报底图无法加载(${lastErr})。请检查服务端 /posts/ 下是否存在 tg_qrcode_1~8、yq_qrcode_1 等底图,并确认小程序已配置该域名为合法下载域名(支持 .png 或 .jpg)。当前已尝试:${candidates.join("、")}`,
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,21 +93,21 @@ async function resolvePosterImageInfo(posterSrc) {
|
|||||||
* 应用根路径 /static/... 作为 img.src 更可靠,失败再退回 info.path
|
* 应用根路径 /static/... 作为 img.src 更可靠,失败再退回 info.path
|
||||||
*/
|
*/
|
||||||
function loadCanvasImage(canvas, resolvedSrc, infoPath) {
|
function loadCanvasImage(canvas, resolvedSrc, infoPath) {
|
||||||
const img = canvas.createImage()
|
const img = canvas.createImage();
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const tryPath = (p, isFallback) => {
|
const tryPath = (p, isFallback) => {
|
||||||
img.onload = () => resolve(img)
|
img.onload = () => resolve(img);
|
||||||
img.onerror = () => {
|
img.onerror = () => {
|
||||||
if (!isFallback && infoPath && p !== infoPath) {
|
if (!isFallback && infoPath && p !== infoPath) {
|
||||||
tryPath(infoPath, true)
|
tryPath(infoPath, true);
|
||||||
} else {
|
} else {
|
||||||
reject(new Error(`canvas 图片加载失败: ${p}`))
|
reject(new Error(`canvas 图片加载失败: ${p}`));
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
img.src = p
|
img.src = p;
|
||||||
}
|
};
|
||||||
tryPath(resolvedSrc, false)
|
tryPath(resolvedSrc, false);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -105,60 +121,67 @@ function loadCanvasImage(canvas, resolvedSrc, infoPath) {
|
|||||||
* @returns {Promise<string>} tempFilePath
|
* @returns {Promise<string>} tempFilePath
|
||||||
*/
|
*/
|
||||||
export async function drawMergedPosterWeixin(opts) {
|
export async function drawMergedPosterWeixin(opts) {
|
||||||
const { canvas, linkUrl, posterSrc, mode, index, componentInstance } = opts
|
const { canvas, linkUrl, posterSrc, mode, index, componentInstance } = opts;
|
||||||
const ctx = canvas.getContext('2d')
|
const cacheKey = buildMergedPosterCacheKey({ linkUrl, posterSrc, mode, index });
|
||||||
|
const cachedPath = mergedPosterCache.get(cacheKey);
|
||||||
|
if (cachedPath) {
|
||||||
|
return cachedPath;
|
||||||
|
}
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
if (!ctx) {
|
if (!ctx) {
|
||||||
throw new Error('canvas 2d 不可用')
|
throw new Error("canvas 2d 不可用");
|
||||||
}
|
}
|
||||||
|
|
||||||
const wxApi = getWx()
|
const wxApi = getWx();
|
||||||
if (!wxApi?.createOffscreenCanvas) {
|
if (!wxApi?.createOffscreenCanvas) {
|
||||||
throw new Error('当前微信基础库不支持 createOffscreenCanvas,请升级基础库')
|
throw new Error("当前微信基础库不支持 createOffscreenCanvas,请升级基础库");
|
||||||
}
|
}
|
||||||
|
|
||||||
const { info, resolvedSrc } = await resolvePosterImageInfo(posterSrc)
|
const { info, resolvedSrc } = await resolvePosterImageInfo(posterSrc);
|
||||||
const infoPath = typeof info.path === 'string' ? info.path : ''
|
const infoPath = typeof info.path === "string" ? info.path : "";
|
||||||
const img = await loadCanvasImage(canvas, resolvedSrc, infoPath)
|
const img = await loadCanvasImage(canvas, resolvedSrc, infoPath);
|
||||||
|
|
||||||
const w = img.width
|
const w = img.width;
|
||||||
const h = img.height
|
const h = img.height;
|
||||||
const sys = uni.getSystemInfoSync()
|
const dpr = 1;
|
||||||
const dpr = sys.pixelRatio || 2
|
|
||||||
|
|
||||||
canvas.width = w * dpr
|
canvas.width = w * dpr;
|
||||||
canvas.height = h * dpr
|
canvas.height = h * dpr;
|
||||||
ctx.scale(dpr, dpr)
|
ctx.scale(dpr, dpr);
|
||||||
ctx.clearRect(0, 0, w, h)
|
ctx.clearRect(0, 0, w, h);
|
||||||
ctx.drawImage(img, 0, 0, w, h)
|
ctx.drawImage(img, 0, 0, w, h);
|
||||||
|
|
||||||
const positions = POSTER_QR_POSITIONS[mode] || POSTER_QR_POSITIONS.promote
|
const positions = POSTER_QR_POSITIONS[mode] || POSTER_QR_POSITIONS.promote;
|
||||||
const position = positions[index] || positions[0]
|
const position = positions[index] || positions[0];
|
||||||
const qrY = position.y < 0 ? h + position.y : position.y
|
const qrY = position.y < 0 ? h + position.y : position.y;
|
||||||
const s = position.size
|
const s = position.size;
|
||||||
|
|
||||||
const off = wxApi.createOffscreenCanvas({ type: '2d', width: s, height: s })
|
const off = wxApi.createOffscreenCanvas({ type: "2d", width: s, height: s });
|
||||||
const octx = off.getContext('2d')
|
const octx = off.getContext("2d");
|
||||||
const qr = new UQRCode()
|
const qr = new UQRCode();
|
||||||
qr.data = linkUrl
|
qr.data = linkUrl;
|
||||||
qr.size = s
|
qr.size = s;
|
||||||
qr.margin = 0
|
qr.margin = 0;
|
||||||
qr.make()
|
qr.make();
|
||||||
qr.canvasContext = octx
|
qr.canvasContext = octx;
|
||||||
await qr.drawCanvas()
|
await qr.drawCanvas();
|
||||||
ctx.drawImage(off, position.x, qrY, s, s)
|
ctx.drawImage(off, position.x, qrY, s, s);
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const conf = {
|
const conf = {
|
||||||
canvas,
|
canvas,
|
||||||
fileType: 'png',
|
fileType: "png",
|
||||||
quality: 1,
|
quality: 1,
|
||||||
success: (res) => resolve(res.tempFilePath),
|
success: (res) => {
|
||||||
|
mergedPosterCache.set(cacheKey, res.tempFilePath);
|
||||||
|
resolve(res.tempFilePath);
|
||||||
|
},
|
||||||
fail: reject,
|
fail: reject,
|
||||||
}
|
};
|
||||||
if (componentInstance) {
|
if (componentInstance) {
|
||||||
uni.canvasToTempFilePath(conf, componentInstance)
|
uni.canvasToTempFilePath(conf, componentInstance);
|
||||||
} else {
|
} else {
|
||||||
uni.canvasToTempFilePath(conf)
|
uni.canvasToTempFilePath(conf);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,58 @@ const BASE_URL = getApiPrefix()
|
|||||||
// #ifndef H5
|
// #ifndef H5
|
||||||
const BASE_URL = getApiBaseUrl()
|
const BASE_URL = getApiBaseUrl()
|
||||||
// #endif
|
// #endif
|
||||||
|
const TEMP_USER_INVALID_CODE = 100012
|
||||||
|
let wxMiniReauthPromise = null
|
||||||
|
|
||||||
|
const clearAuthData = () => {
|
||||||
|
uni.removeStorageSync('token')
|
||||||
|
uni.removeStorageSync('refreshAfter')
|
||||||
|
uni.removeStorageSync('accessExpire')
|
||||||
|
uni.removeStorageSync('userInfo')
|
||||||
|
uni.removeStorageSync('agentInfo')
|
||||||
|
}
|
||||||
|
|
||||||
|
const silentWxMiniReAuth = () => {
|
||||||
|
if (wxMiniReauthPromise)
|
||||||
|
return wxMiniReauthPromise
|
||||||
|
|
||||||
|
wxMiniReauthPromise = new Promise((resolve, reject) => {
|
||||||
|
clearAuthData()
|
||||||
|
uni.login({
|
||||||
|
success: (loginRes) => {
|
||||||
|
if (!loginRes.code) {
|
||||||
|
reject(new Error('wx.login failed'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uni.request({
|
||||||
|
url: `${BASE_URL}/user/wxMiniAuth`,
|
||||||
|
method: 'POST',
|
||||||
|
data: { code: loginRes.code },
|
||||||
|
header: {
|
||||||
|
'X-Platform': 'wxmini',
|
||||||
|
},
|
||||||
|
success: (authRes) => {
|
||||||
|
if (authRes.statusCode === 200 && authRes.data?.code === 200 && authRes.data?.data) {
|
||||||
|
uni.setStorageSync('token', authRes.data.data.accessToken)
|
||||||
|
uni.setStorageSync('refreshAfter', authRes.data.data.refreshAfter)
|
||||||
|
uni.setStorageSync('accessExpire', authRes.data.data.accessExpire)
|
||||||
|
resolve(authRes.data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
reject(authRes.data || new Error('wxMiniAuth failed'))
|
||||||
|
},
|
||||||
|
fail: reject,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
fail: reject,
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
wxMiniReauthPromise = null
|
||||||
|
})
|
||||||
|
|
||||||
|
return wxMiniReauthPromise
|
||||||
|
}
|
||||||
|
|
||||||
function request(options) {
|
function request(options) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// 请求拦截器逻辑
|
// 请求拦截器逻辑
|
||||||
@@ -38,6 +90,24 @@ function request(options) {
|
|||||||
success: (res) => {
|
success: (res) => {
|
||||||
// 响应拦截器逻辑
|
// 响应拦截器逻辑
|
||||||
if (res.statusCode === 200) {
|
if (res.statusCode === 200) {
|
||||||
|
if (res.data?.code === TEMP_USER_INVALID_CODE) {
|
||||||
|
if (!options.__retriedAfterReauth) {
|
||||||
|
silentWxMiniReAuth()
|
||||||
|
.then(() => {
|
||||||
|
request({
|
||||||
|
...options,
|
||||||
|
hideLoading: true,
|
||||||
|
__retriedAfterReauth: true,
|
||||||
|
}).then(resolve).catch(reject)
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
reject(res.data)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
reject(res.data)
|
||||||
|
return
|
||||||
|
}
|
||||||
resolve(res.data)
|
resolve(res.data)
|
||||||
}
|
}
|
||||||
else if (res.statusCode === 401) {
|
else if (res.statusCode === 401) {
|
||||||
|
|||||||
@@ -1,68 +1,72 @@
|
|||||||
/**
|
/**
|
||||||
* 最简配置:两份 json 用相对路径 import;import.meta.env.PROD 由 Vite 构建期替换成字面量。
|
* 最简配置:两份 json 用相对路径 import;import.meta.env.PROD 由 Vite 构建期替换成字面量。
|
||||||
*/
|
*/
|
||||||
import dev from '../config/runtime.development.json'
|
import dev from "../config/runtime.development.json";
|
||||||
import prod from '../config/runtime.production.json'
|
import prod from "../config/runtime.production.json";
|
||||||
|
|
||||||
const _e = import.meta.env.PROD ? prod : dev
|
const _e = import.meta.env.PROD ? prod : dev;
|
||||||
|
|
||||||
function nonempty(name) {
|
function nonempty(name) {
|
||||||
const v = _e[name]
|
const v = _e[name];
|
||||||
if (v === undefined || v === '') {
|
if (v === undefined || v === "") {
|
||||||
throw new Error(`缺少运行时配置: ${name}(请检查 src/config/runtime.*.json)`)
|
throw new Error(
|
||||||
|
`缺少运行时配置: ${name}(请检查 src/config/runtime.*.json)`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return String(v)
|
return String(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getApiOrigin() {
|
export function getApiOrigin() {
|
||||||
return nonempty('apiUrl').replace(/\/$/, '')
|
return nonempty("apiUrl").replace(/\/$/, "");
|
||||||
|
}
|
||||||
|
export function getPosterOrigin() {
|
||||||
|
return nonempty("posterUrl").replace(/\/$/, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getApiPrefix() {
|
export function getApiPrefix() {
|
||||||
const p = nonempty('apiPrefix')
|
const p = nonempty("apiPrefix");
|
||||||
return p.startsWith('/') ? p : `/${p}`
|
return p.startsWith("/") ? p : `/${p}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getApiBaseUrl() {
|
export function getApiBaseUrl() {
|
||||||
return `${getApiOrigin()}${getApiPrefix()}`
|
return `${getApiOrigin()}${getApiPrefix()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSiteOrigin() {
|
export function getSiteOrigin() {
|
||||||
return nonempty('siteOrigin').replace(/\/$/, '')
|
return nonempty("siteOrigin").replace(/\/$/, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCustomerServiceUrl() {
|
export function getCustomerServiceUrl() {
|
||||||
return nonempty('customerServiceUrl')
|
return nonempty("customerServiceUrl");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCustomerServiceCorpId() {
|
export function getCustomerServiceCorpId() {
|
||||||
return String(_e.customerServiceCorpId || '')
|
return String(_e.customerServiceCorpId || "");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getInviteChannelKey() {
|
export function getInviteChannelKey() {
|
||||||
return nonempty('inviteChannelKey')
|
return nonempty("inviteChannelKey");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAppName() {
|
export function getAppName() {
|
||||||
return nonempty('appName')
|
return nonempty("appName");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCompanyName() {
|
export function getCompanyName() {
|
||||||
return nonempty('companyName')
|
return nonempty("companyName");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getShareTitle() {
|
export function getShareTitle() {
|
||||||
return nonempty('shareTitle')
|
return nonempty("shareTitle");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMeShareTitle() {
|
export function getMeShareTitle() {
|
||||||
return nonempty('shareTitleMe')
|
return nonempty("shareTitleMe");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAgentTabShareTitle() {
|
export function getAgentTabShareTitle() {
|
||||||
return nonempty('shareTitleAgent')
|
return nonempty("shareTitleAgent");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAppDebug() {
|
export function getAppDebug() {
|
||||||
return Boolean(_e.appDebug)
|
return Boolean(_e.appDebug);
|
||||||
}
|
}
|
||||||
|
|||||||