소스 검색

新增pvcv统计

main
leiyun 1 개월 전
부모
커밋
b08a3d530e
6개의 변경된 파일156개의 추가작업 그리고 7개의 파일을 삭제
  1. +1
    -0
      api/index.js
  2. +7
    -0
      main.js
  3. +3
    -0
      pages/login/login.vue
  4. +4
    -0
      pages/recharge/recharge.vue
  5. +69
    -7
      pages/tool/portrait-photo.vue
  6. +72
    -0
      utils/pvcv.js

+ 1
- 0
api/index.js 파일 보기

@@ -46,6 +46,7 @@ export const commonApi = {
getConfig: (params) => request({ url: API.INTERFACE_COMMON_GET_CONFIG, method: 'POST', params }),
getHomeConfig: (params) => request({ url: API.INTERFACE_HOME_GET_CONFIG, method: 'POST', params }),
getHtml: (params) => request({ url: API.INTERFACE_COMMON_GET_HTML, method: 'POST', params }),
pvcv: (params) => request({ url: API.INTERFACE_PVCV_ADD, method: 'POST', params }),
feedback: (params) => request({ url: API.INTERFACE_COMMON_POST_FEEDBACK, method: 'POST', params })
}



+ 7
- 0
main.js 파일 보기

@@ -3,6 +3,7 @@ import App from './App.vue'
import { createPinia } from 'pinia'
import uviewPlus from '@/uni_modules/uview-plus'
import * as filters from '@/utils/filter.js'
import { reportPagePv } from '@/utils/pvcv.js'

function lockLightTheme() {
if (typeof uni === 'undefined') return
@@ -27,6 +28,12 @@ export function createApp() {
// 全局过滤器/工具(Vue3 已移除 filter,这里挂到 globalProperties 供模板使用)
app.config.globalProperties.$f = filters

app.mixin({
onShow() {
reportPagePv()
}
})

// #ifdef H5-DEV || H5-TEST
// 开发/测试环境可按需引入 vconsole
// #endif


+ 3
- 0
pages/login/login.vue 파일 보기

@@ -69,6 +69,7 @@ import { useUserStore } from '@/store/user.js'
import { useApp } from '@/utils/useApp.js'
import { isInWechat } from '@/utils/util.js'
import { rememberLoginSource, returnAfterLogin } from '@/utils/login.js'
import { reportCv } from '@/utils/pvcv.js'
import CONFIG from '@/utils/config.js'

const userStore = useUserStore()
@@ -86,6 +87,7 @@ isIOS = uni.getSystemInfoSync().platform === 'ios'
// #endif

onLoad((options = {}) => {
reportCv('login_page_show')
type.value = options.type || '1'
source.value = options.source || ''
const redirect = options.redirect || options.url || ''
@@ -251,6 +253,7 @@ async function postLogin(code) {
function afterLogin(res) {
const inviteCode = uni.getStorageSync('invite_code')
userStore.setLogin({ token: res.data.token, userInfo: res.data })
reportCv('login_success')

if (res.data.is_register) {
toast(inviteCode ? '登录并领取成功!' : '登录成功!')


+ 4
- 0
pages/recharge/recharge.vue 파일 보기

@@ -65,6 +65,7 @@ import { onShow } from '@dcloudio/uni-app'
import { useUserStore } from '@/store/user.js'
import { useApp } from '@/utils/useApp.js'
import { pointApi } from '@/api/index.js'
import { reportCv } from '@/utils/pvcv.js'

const userStore = useUserStore()
const { toast } = useApp()
@@ -174,6 +175,7 @@ async function pollOrder(orderNo, times = 5) {
async function handleBuy() {
if (!ensureLogin()) return
if (!current.value || paying.value) return
reportCv('point_recharge_click')

paying.value = true
uni.showLoading({ mask: true, title: '发起支付...' })
@@ -181,6 +183,7 @@ async function handleBuy() {
const code = await getWxLoginCode()
const res = await pointApi.createOrder({ package_id: current.value.id, code })
const order = res.data
reportCv('point_recharge_order')
uni.hideLoading()

// 打印后端返回的下单结果(含 env,1=沙箱不扣费 / 0=现网真实扣费)
@@ -197,6 +200,7 @@ async function handleBuy() {
const ok = await pollOrder(order.order_no)
uni.hideLoading()
if (ok) {
reportCv('point_recharge_paid')
toast('充值成功')
} else {
toast('支付成功,点数到账处理中')


+ 69
- 7
pages/tool/portrait-photo.vue 파일 보기

@@ -179,19 +179,30 @@
</view>

<!-- ==================== 数量 ==================== -->
<view v-if="canEdit" class="section">
<view v-if="canEdit && !configLoading && quantityEnabled" class="section">
<view class="section-head">
<text class="tool-section-title">生成数量</text>
<text class="price">{{ unitPoints }}点/张,共{{ totalPoints }}点</text>
</view>
<view class="quantity">
<view class="quantity-btn" @click="decreaseQuantity">-</view>
<view class="quantity-btn" :class="{ disabled: quantity <= 1 }" @click="decreaseQuantity">-</view>
<text class="quantity-value">{{ quantity }}</text>
<view class="quantity-btn" @click="increaseQuantity">+</view>
<view class="quantity-btn" :class="{ disabled: quantity >= maxCount }" @click="increaseQuantity">+</view>
</view>
<text class="note">按单张收费,一次最多生成{{ maxCount }}张,失败的不扣点</text>
</view>

<view v-else-if="canEdit && !configLoading" class="section">
<view class="section-head">
<text class="tool-section-title">生成数量</text>
<text class="price">{{ unitPoints }}点/张</text>
</view>
<view class="single-quantity">
<text>固定 1 张</text>
</view>
<text class="note">后台已关闭多张生成,本次只生成1张</text>
</view>

<!-- ==================== 补充描述 ==================== -->
<view v-if="canEdit" class="section">
<view class="section-head">
@@ -285,6 +296,7 @@ const activeRatio = ref('')
const options = reactive({})
const quantity = ref(1)
const maxCount = ref(4)
const quantityEnabled = ref(true)
const unitPoints = ref(0)
const extraPrompt = ref('')
const localImage = ref('')
@@ -306,7 +318,8 @@ const hasDemo = computed(() => Boolean(demoPair.value.original && demoPair.value
const currentStyle = computed(() => styles.value.find((item) => item.key === activeStyle.value) || {})
const currentGroups = computed(() => currentStyle.value.groups || [])
const currentPoints = computed(() => Number(balance.value.points || balance.value.point_balance || 0))
const totalPoints = computed(() => unitPoints.value * quantity.value)
const effectiveQuantity = computed(() => (quantityEnabled.value ? quantity.value : 1))
const totalPoints = computed(() => unitPoints.value * effectiveQuantity.value)
const isPending = computed(() => [10, 20].includes(Number(job.value.status)))
const canEdit = computed(() => !job.value.photo_open_id || Number(job.value.status) < 0)
const resultUrls = computed(() => job.value.result_urls || [])
@@ -359,19 +372,50 @@ watch([activeModel, quantity], () => {
if (!configLoading.value) loadConfig(true)
})

function pickFirstDefined(values) {
return values.find((value) => value !== undefined && value !== null && value !== '')
}

function normalizeQuantityEnabled(data = {}) {
const raw = pickFirstDefined([
data.quantity_enabled,
data.quantity_supported,
data.product && data.product.quantity_enabled,
data.billing && data.billing.product && data.billing.product.quantity_enabled,
data.billing && data.billing.quote && data.billing.quote.product && data.billing.quote.product.quantity_enabled
])
if (raw === undefined) return Number(data.max_count || 1) > 1
if (typeof raw === 'boolean') return raw
return !['0', 'false', 'no', 'off', 'unsupported', 'not_support', '不支持'].includes(String(raw).toLowerCase())
}

function applyQuantityConfig(data = {}) {
const enabled = normalizeQuantityEnabled(data)
const rawMax = pickFirstDefined([
data.max_count,
data.product && data.product.max_quantity,
data.billing && data.billing.product && data.billing.product.max_quantity,
data.billing && data.billing.quote && data.billing.quote.product && data.billing.quote.product.max_quantity
])
const nextMax = enabled ? Math.max(1, Number(rawMax || maxCount.value || 1)) : 1
quantityEnabled.value = enabled
maxCount.value = nextMax
quantity.value = enabled ? Math.min(Math.max(1, quantity.value), nextMax) : 1
}

async function loadConfig(silent = false) {
try {
const res = await portraitApi.getConfig({
style: activeStyle.value || undefined,
model: activeModel.value || undefined,
count: quantity.value
count: effectiveQuantity.value
})
const data = res.data || {}
models.value = data.models || []
styles.value = data.styles || []
ratios.value = data.ratios || []
tips.value = data.tips || []
maxCount.value = Number(data.max_count || 4)
applyQuantityConfig(data)
unitPoints.value = Number(data.unit_points || 0)
if (!activeModel.value) activeModel.value = data.default_model || (models.value[0] || {}).key || ''
if (!activeRatio.value) {
@@ -506,7 +550,7 @@ async function submit() {
style: activeStyle.value,
model: activeModel.value,
ratio: activeRatio.value,
count: quantity.value,
count: effectiveQuantity.value,
extra_prompt: extraPrompt.value,
request_id: guid(),
...options
@@ -640,10 +684,12 @@ function goRecharge() {
}

function decreaseQuantity() {
if (!quantityEnabled.value || quantity.value <= 1) return
quantity.value = Math.max(1, quantity.value - 1)
}

function increaseQuantity() {
if (!quantityEnabled.value || quantity.value >= maxCount.value) return
quantity.value = Math.min(maxCount.value, quantity.value + 1)
}
</script>
@@ -962,7 +1008,23 @@ function increaseQuantity() {
text-align: center;
background: #eef7ff;
}
.quantity-btn.disabled {
color: #a8bfd6;
background: #f3f7fb;
}
.quantity-value { min-width: 60rpx; color: #172033; font-size: 32rpx; font-weight: 900; text-align: center; }
.single-quantity {
margin-top: 22rpx;
height: 76rpx;
padding: 0 26rpx;
border-radius: 20rpx;
color: #0b8cff;
font-size: 28rpx;
font-weight: 900;
line-height: 76rpx;
background: #eef7ff;
box-sizing: border-box;
}
.note { display: block; margin-top: 16rpx; color: #8a95a6; font-size: 22rpx; }

.textarea {


+ 72
- 0
utils/pvcv.js 파일 보기

@@ -0,0 +1,72 @@
import request from '@/utils/request.js'
import API from '@/api/config.js'
import { getUA } from '@/utils/util.js'
import { getUser } from '@/utils/auth.js'

let lastReportKey = ''
let lastReportAt = 0

function stringifyQuery(query = {}) {
return Object.keys(query)
.filter(key => query[key] !== undefined && query[key] !== null && query[key] !== '')
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(query[key])}`)
.join('&')
}

function getCurrentPageInfo() {
const pages = getCurrentPages()
const page = pages[pages.length - 1]
if (!page) return null

const route = page.route || ''
const query = page.options || {}
const queryString = stringifyQuery(query)
const path = route ? `/${route}` : '/'
const url = queryString ? `${path}?${queryString}` : path

return {
route,
path,
url
}
}

export function reportPagePv(extra = {}) {
const page = getCurrentPageInfo()
if (!page) return

const now = Date.now()
const key = extra.key ? `${page.url}::${extra.key}` : page.url
if (key === lastReportKey && now - lastReportAt < 800) return
lastReportKey = key
lastReportAt = now

const user = getUser() || {}
const referrer = uni.getStorageSync('pvcv_last_url') || ''
uni.setStorageSync('pvcv_last_url', page.url)

request({
url: API.INTERFACE_PVCV_ADD,
method: 'POST',
params: {
source: uni.getStorageSync('source') || '',
referrer,
url: page.url,
path: page.path,
page_key: page.route,
user_id: user.user_id || 0,
user_agent: getUA(),
...extra
}
}).catch(() => {
// 统计失败不能影响页面主流程
})
}

export function reportCv(key, extra = {}) {
if (!key) return
reportPagePv({
...extra,
key
})
}

불러오는 중...
취소
저장