This commit is contained in:
2026-04-23 14:57:35 +08:00
parent c77780fa0e
commit 739e08157b
97 changed files with 6120 additions and 14939 deletions

View File

@@ -187,50 +187,89 @@ watch(
<template>
<wd-popup v-model="show" destroy-on-close round position="bottom">
<view class="h-12 flex items-center justify-center font-semibold"
style="background-color: var(--van-theme-primary-light); color: var(--van-theme-primary);">
<view
class="h-12 flex items-center justify-center font-semibold"
style="background-color: var(--color-primary-light); color: var(--color-primary);"
>
成为代理
</view>
<view v-if="ancestor" class="my-2 text-center text-xs" style="color: var(--van-text-color-2);">
<view v-if="ancestor" class="my-2 text-center text-xs" style="color: var(--color-text-secondary);">
{{ maskName(ancestor) }}邀您成为赤眉代理方
</view>
<view class="p-4">
<wd-col-picker v-model="region" class="agent-form-field" label-width="42px" label="地区" placeholder="请选择地区"
:columns="columns" :column-change="handleColumnChange" :display-format="displayFormat" :align-right="false"
custom-value-class="agent-col-picker-value" @confirm="handleRegionConfirm" />
<wd-input v-model="form.mobile" class="agent-form-field" label-width="42px" label="手机号" name="mobile"
placeholder="请输入手机号" :align-right="false" :readonly="mobileReadonly" :disabled="mobileReadonly" />
<wd-col-picker
v-model="region"
class="agent-form-field"
label-width="42px"
label="地区"
placeholder="请选择地区"
:columns="columns"
:column-change="handleColumnChange"
:display-format="displayFormat"
:align-right="false"
custom-value-class="agent-col-picker-value"
@confirm="handleRegionConfirm"
/>
<wd-input
v-model="form.mobile"
class="agent-form-field"
label-width="42px"
label="手机号"
name="mobile"
placeholder="请输入手机号"
:align-right="false"
:readonly="mobileReadonly"
:disabled="mobileReadonly"
/>
<!-- 获取验证码按钮 -->
<wd-input v-model="form.code" class="agent-form-field" label-width="42px" label="验证码" name="code"
placeholder="请输入验证码" :align-right="false" use-suffix-slot>
<wd-input
v-model="form.code"
class="agent-form-field"
label-width="42px"
label="验证码"
name="code"
placeholder="请输入验证码"
:align-right="false"
use-suffix-slot
>
<template #suffix>
<button class="ml-2 flex-shrink-0 rounded-lg px-2 py-1 text-sm font-bold transition duration-300" :class="isCountingDown || !isPhoneNumberValid
? 'cursor-not-allowed bg-gray-300 text-gray-500'
: 'text-white hover:opacity-90'" :style="isCountingDown || !isPhoneNumberValid
? ''
: 'background-color: var(--van-theme-primary);'" :disabled="isCountingDown || !isPhoneNumberValid"
@click.stop="getSmsCode">
{{
isCountingDown ? `${countdown}s重新获取` : '获取验证码'
}}
</button>
<wd-button
class="ml-2"
size="small"
type="primary"
plain
:disabled="isCountingDown || !isPhoneNumberValid"
@click.stop="getSmsCode"
>
{{ isCountingDown ? `${countdown}s重新获取` : '获取验证码' }}
</wd-button>
</template>
</wd-input>
<!-- 同意条款的复选框 -->
<view class="p-4">
<view class="flex items-start">
<wd-checkbox v-model="isAgreed" name="agree" icon-size="16px" class="mr-2 flex-shrink-0" />
<view class="text-xs leading-tight" style="color: var(--van-text-color-2);">
<view class="text-xs leading-tight" style="color: var(--color-text-secondary);">
我已阅读并同意
<a class="cursor-pointer hover:underline" style="color: var(--van-theme-primary);"
@click="toUserAgreement">用户协议</a>
<a class="cursor-pointer hover:underline" style="color: var(--van-theme-primary);"
@click="toAgentManageAgreement">推广方管理制度协议</a>
<view class="mt-1 text-xs" style="color: var(--van-text-color-2);">
<text
class="cursor-pointer hover:underline"
style="color: var(--color-primary);"
@click="toUserAgreement"
>
用户协议
</text>
<text
class="cursor-pointer hover:underline"
style="color: var(--color-primary);"
@click="toAgentManageAgreement"
>
推广方管理制度协议
</text>
<view class="mt-1 text-xs" style="color: var(--color-text-secondary);">
点击勾选即代表您同意上述法律文书的相关条款并签署上述法律文书
</view>
<view class="mt-1 text-xs" style="color: var(--van-text-color-2);">
<view class="mt-1 text-xs" style="color: var(--color-text-secondary);">
手机号未在本平台注册账号则申请后将自动生成账号
</view>
</view>

View File

@@ -1,5 +1,5 @@
<script setup>
import { computed, nextTick, ref } from 'vue'
import { computed, nextTick, onUnmounted, ref } from 'vue'
import { useDialogStore } from '@/stores/dialogStore'
import { setAuthSession } from '@/utils/storage'
@@ -25,10 +25,6 @@ function showToast(options) {
})
}
// 聚焦状态变量
const phoneFocused = ref(false)
const codeFocused = ref(false)
const isPhoneNumberValid = computed(() => {
return /^1[3-9]\d{9}$/.test(phoneNumber.value)
})
@@ -120,8 +116,11 @@ function closeDialog() {
phoneNumber.value = ''
verificationCode.value = ''
isAgreed.value = false
isCountingDown.value = false
countdown.value = 60
if (timer) {
clearInterval(timer)
timer = null
}
}
@@ -134,6 +133,12 @@ function toPrivacyPolicy() {
closeDialog()
router.push(`/privacyPolicy`)
}
onUnmounted(() => {
if (timer) {
clearInterval(timer)
}
})
</script>
<template>
@@ -164,56 +169,72 @@ function toPrivacyPolicy() {
</view>
<view class="space-y-5">
<!-- 手机号输入 -->
<view class="input-container bg-blue-300/20" :class="[
phoneFocused ? 'focused' : '',
]">
<input v-model="phoneNumber" class="input-field" type="tel" placeholder="请输入手机号" maxlength="11"
@focus="phoneFocused = true" @blur="phoneFocused = false">
<view class="form-item">
<text class="form-label">
手机号
</text>
<wd-input
v-model="phoneNumber"
class="field-wd-input"
type="number"
placeholder="请输入手机号"
maxlength="11"
no-border
clearable
/>
</view>
<!-- 验证码输入 -->
<view class="flex items-center justify-between">
<view class="input-container bg-blue-300/20" :class="[
codeFocused ? 'focused' : '',
]">
<input id="verificationCode" ref="verificationCodeInputRef" v-model="verificationCode"
class="input-field" placeholder="请输入验证码" maxlength="6" @focus="codeFocused = true"
@blur="codeFocused = false">
</view>
<button class="ml-2 flex-shrink-0 rounded-lg px-4 py-2 text-sm font-bold transition duration-300" :class="isCountingDown || !isPhoneNumberValid
? 'cursor-not-allowed bg-gray-300 text-gray-500'
: 'bg-blue-500 text-white hover:bg-blue-600'
" @click="sendVerificationCode">
{{
isCountingDown
? `${countdown}s重新获取`
: "获取验证码"
}}
</button>
<view class="form-item">
<text class="form-label">
验证码
</text>
<wd-input
ref="verificationCodeInputRef"
v-model="verificationCode"
class="field-wd-input"
placeholder="请输入验证码"
maxlength="6"
no-border
clearable
>
<template #suffix>
<wd-button
size="small"
type="primary"
plain
:disabled="isCountingDown || !isPhoneNumberValid"
@click="sendVerificationCode"
>
{{ isCountingDown ? `${countdown}s重新获取` : '获取验证码' }}
</wd-button>
</template>
</wd-input>
</view>
<!-- 协议同意框 -->
<view class="flex items-start space-x-2">
<input v-model="isAgreed" type="checkbox" class="mt-1">
<text class="text-xs text-gray-400 leading-tight">
<view class="agreement-wrapper">
<wd-checkbox v-model="isAgreed" shape="square" size="18px" />
<text class="agreement-text">
绑定手机号即代表您已阅读并同意
<a class="cursor-pointer text-blue-400" @click="toUserAgreement">
<text class="agreement-link" @click="toUserAgreement">
用户协议
</a>
</text>
<a class="cursor-pointer text-blue-400" @click="toPrivacyPolicy">
<text class="agreement-link" @click="toPrivacyPolicy">
隐私政策
</a>
</text>
</text>
</view>
</view>
<button
class="mt-10 w-full rounded-full bg-blue-500 py-3 text-lg text-white font-bold transition duration-300"
:class="{ 'opacity-50 cursor-not-allowed': !canBind }" @click="handleBind">
<wd-button
class="mt-10"
block
type="primary"
:disabled="!canBind"
@click="handleBind"
>
确认绑定
</button>
</wd-button>
</view>
</view>
</wd-popup>
@@ -242,22 +263,45 @@ function toPrivacyPolicy() {
cursor: pointer;
}
.input-container {
border: 2px solid rgba(125, 211, 252, 0);
border-radius: 1rem;
transition: duration-200;
.form-item {
margin-bottom: 1rem;
display: flex;
align-items: center;
border-radius: 12px;
background-color: #fff;
padding: 0 0.75rem;
min-height: 48px;
}
.input-container.focused {
border: 2px solid #3b82f6;
.form-label {
font-size: 0.9375rem;
color: #111827;
margin-right: 0.75rem;
font-weight: 500;
min-width: 3.25rem;
flex-shrink: 0;
}
.input-field {
width: 100%;
padding: 1rem;
background: transparent;
border: none;
outline: none;
transition: border-color 0.3s ease;
.field-wd-input {
flex: 1;
}
.agreement-wrapper {
display: flex;
align-items: flex-start;
margin-top: 1rem;
}
.agreement-text {
font-size: 0.75rem;
color: #6b7280;
line-height: 1.5;
margin-left: 0.5rem;
flex: 1;
}
.agreement-link {
color: #2563eb;
cursor: pointer;
}
</style>

View File

@@ -1,262 +0,0 @@
<script setup>
import * as echarts from 'echarts'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
const props = defineProps({
score: {
type: Number,
required: true,
},
})
// 根据分数计算风险等级和颜色(分数越高越安全)
const riskLevel = computed(() => {
const score = props.score
if (score >= 75 && score <= 100) {
return {
level: '无任何风险',
color: '#52c41a',
gradient: [
{ offset: 0, color: '#52c41a' },
{ offset: 1, color: '#7fdb42' },
],
}
}
else if (score >= 50 && score < 75) {
return {
level: '风险指数较低',
color: '#faad14',
gradient: [
{ offset: 0, color: '#faad14' },
{ offset: 1, color: '#ffc53d' },
],
}
}
else if (score >= 25 && score < 50) {
return {
level: '风险指数较高',
color: '#fa8c16',
gradient: [
{ offset: 0, color: '#fa8c16' },
{ offset: 1, color: '#ffa940' },
],
}
}
else {
return {
level: '高风险警告',
color: '#f5222d',
gradient: [
{ offset: 0, color: '#f5222d' },
{ offset: 1, color: '#ff4d4f' },
],
}
}
})
// 评分解释文本(分数越高越安全)
const riskDescription = computed(() => {
const score = props.score
if (score >= 75 && score <= 100) {
return '根据综合分析,当前报告未检测到明显风险因素,各项指标表现正常,总体状况良好。'
}
else if (score >= 50 && score < 75) {
return '根据综合分析,当前报告存在少量风险信号,建议关注相关指标变化,保持警惕。'
}
else if (score >= 25 && score < 50) {
return '根据综合分析,当前报告风险指数较高,多项指标显示异常,建议进一步核实相关情况。'
}
else {
return '根据综合分析,当前报告显示高度风险状态,多项重要指标严重异常,请立即采取相应措施。'
}
})
const chartRef = ref(null)
let chartInstance = null
function initChart() {
if (!chartRef.value)
return
// 初始化ECharts实例
chartInstance = echarts.init(chartRef.value)
updateChart()
}
function updateChart() {
if (!chartInstance)
return
// 获取当前风险等级信息
const risk = riskLevel.value
// 配置项
const option = {
series: [
{
type: 'gauge',
startAngle: 180,
endAngle: 0,
min: 0,
max: 100,
radius: '100%',
center: ['50%', '80%'],
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, risk.gradient),
shadowBlur: 6,
shadowColor: risk.color,
},
progress: {
show: true,
width: 20,
roundCap: true,
clip: false,
},
axisLine: {
roundCap: true,
lineStyle: {
width: 20,
color: [
[1, new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{
offset: 0,
color: `${risk.color}30`, // 使用风险颜色透明度20%
},
{
offset: 1,
color: `${risk.color}25`, // 使用风险颜色透明度10%
},
])],
],
},
},
axisTick: {
show: true,
distance: -30,
length: 6,
splitNumber: 10, // 每1分一个小刻度
lineStyle: {
color: risk.color,
width: 1,
opacity: 0.5,
},
},
splitLine: {
show: true,
distance: -36,
length: 12,
splitNumber: 9, // 9个大刻度100分分成9个区间
lineStyle: {
color: risk.color,
width: 2,
opacity: 0.5,
},
},
axisLabel: {
show: false,
},
anchor: {
show: false,
},
pointer: {
icon: 'triangle',
iconStyle: {
color: risk.color,
borderColor: risk.color,
borderWidth: 1,
},
offsetCenter: ['7%', '-67%'],
length: '10%',
width: 15,
},
detail: {
valueAnimation: true,
fontSize: 30,
fontWeight: 'bold',
color: risk.color,
offsetCenter: [0, '-25%'],
formatter(value) {
return `{value|${value}分}\n{level|${risk.level}}`
},
rich: {
value: {
fontSize: 30,
fontWeight: 'bold',
color: risk.color,
padding: [0, 0, 5, 0],
},
level: {
fontSize: 14,
fontWeight: 'normal',
color: risk.color,
padding: [5, 0, 0, 0],
},
},
},
data: [
{
value: props.score,
},
],
title: {
fontSize: 14,
color: risk.color,
offsetCenter: [0, '10%'],
formatter: risk.level,
},
},
],
}
// 使用配置项设置图表
chartInstance.setOption(option)
}
// 监听分数变化
watch(
() => props.score,
() => {
updateChart()
},
)
onMounted(() => {
initChart()
// 处理窗口大小变化
window.addEventListener('resize', () => {
if (chartInstance) {
chartInstance.resize()
}
})
})
// 在组件销毁前清理
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose()
chartInstance = null
}
window.removeEventListener('resize', chartInstance?.resize)
})
</script>
<template>
<view>
<view class="risk-description">
{{ riskDescription }}
</view>
<view ref="chartRef" :style="{ width: '100%', height: '200px' }" />
</view>
</template>
<style scoped>
.risk-description {
margin-bottom: 4px;
padding: 0 12px;
color: #666666;
font-size: 12px;
line-height: 1.5;
text-align: center;
}
</style>

View File

@@ -1,7 +1,5 @@
<script setup>
import { ref, watch } from 'vue'
const props = defineProps({
defineProps({
show: {
type: Boolean,
default: false,
@@ -36,9 +34,11 @@ function close() {
<view v-if="show" class="image-save-guide-overlay">
<view class="guide-content" @click.stop>
<!-- 关闭按钮 -->
<button class="close-button" @click="close">
<text class="close-icon">×</text>
</button>
<wd-button class="close-button" custom-class="image-guide-close-btn" plain @click="close">
<text class="close-icon">
×
</text>
</wd-button>
<!-- 图片区域 -->
<view v-if="imageUrl" class="image-container">
@@ -97,7 +97,6 @@ function close() {
right: 12px;
width: 32px;
height: 32px;
border: none;
background: rgba(0, 0, 0, 0.1);
border-radius: 50%;
display: flex;
@@ -108,7 +107,7 @@ function close() {
z-index: 10;
}
.close-button:hover {
:deep(.image-guide-close-btn.wd-button:hover) {
background: rgba(0, 0, 0, 0.2);
}

View File

@@ -5,7 +5,7 @@ import LoginDialog from '@/components/LoginDialog.vue'
import Payment from '@/components/Payment.vue'
import SectionTitle from '@/components/SectionTitle.vue'
import { useRouter } from '@/composables/uni-router'
import { useEnv } from '@/composables/useEnv'
import { useAppConfig } from '@/composables/useAppConfig'
import { useDialogStore } from '@/stores/dialogStore'
import { useUserStore } from '@/stores/userStore'
import { aesEncrypt } from '@/utils/crypto'
@@ -69,7 +69,8 @@ function loadProductBackground(productType) {
const router = useRouter()
const dialogStore = useDialogStore()
const userStore = useUserStore()
const { isWeChat } = useEnv()
const isWeChat = ref(false)
const { appConfig, loadAppConfig } = useAppConfig()
// 响应式数据
const showPayment = ref(false)
@@ -111,6 +112,7 @@ const buttonText = computed(() => {
})
const hasHeroImage = computed(() => Boolean(productBackground.value))
const queryRetentionDaysText = computed(() => `${appConfig.value.query.retention_days}`)
function loadTrapezoidBackground() {
trapezoidBgImage.value = TRAPEZOID_BACKGROUND_MAP[props.feature] || TRAPEZOID_BACKGROUND_MAP.default
@@ -380,6 +382,7 @@ function toHistory() {
onMounted(async () => {
loadBackgroundImage()
loadTrapezoidBackground()
await loadAppConfig()
})
// 加载背景图片
@@ -427,37 +430,76 @@ watch(feature, async () => {
<!-- 表单输入区域 -->
<view class="mb-6 space-y-4">
<view class="flex items-center border-b border-gray-100 py-3">
<text for="name" class="w-20 text-gray-700 font-medium">
<text class="w-20 text-gray-700 font-medium">
姓名
</text>
<input id="name" v-model="formData.name" type="text" placeholder="请输入正确的姓名"
class="flex-1 border-none outline-none" @click="handleInputClick">
<wd-input
v-model="formData.name"
class="inquire-wd-input flex-1"
type="text"
placeholder="请输入正确的姓名"
no-border
clearable
@focus="handleInputClick"
/>
</view>
<view class="flex items-center border-b border-gray-100 py-3">
<text for="idCard" class="w-20 text-gray-700 font-medium">
<text class="w-20 text-gray-700 font-medium">
身份证号
</text>
<input id="idCard" v-model="formData.idCard" type="text" placeholder="请输入准确的身份证号"
class="flex-1 border-none outline-none" @click="handleInputClick">
<wd-input
v-model="formData.idCard"
class="inquire-wd-input flex-1"
type="text"
placeholder="请输入准确的身份证号"
no-border
clearable
@focus="handleInputClick"
/>
</view>
<view class="flex items-center border-b border-gray-100 py-3">
<text for="mobile" class="w-20 text-gray-700 font-medium">
<text class="w-20 text-gray-700 font-medium">
手机号
</text>
<input id="mobile" v-model="formData.mobile" type="tel" placeholder="请输入手机号"
class="flex-1 border-none outline-none" @click="handleInputClick">
<wd-input
v-model="formData.mobile"
class="inquire-wd-input flex-1"
type="number"
placeholder="请输入手机号"
maxlength="11"
no-border
clearable
@focus="handleInputClick"
/>
</view>
<!-- 小微企业(companyinfo)暂不展示验证码 -->
<view v-if="needVerificationCode" class="flex items-center border-b border-gray-100 py-3">
<text for="verificationCode" class="w-20 text-gray-700 font-medium">
<text class="w-20 text-gray-700 font-medium">
验证码
</text>
<input id="verificationCode" ref="verificationCodeInputRef" v-model="formData.verificationCode"
placeholder="请输入验证码" maxlength="6" class="flex-1 border-none outline-none" @click="handleInputClick">
<wd-button class="captcha-wd-btn" size="small" type="primary" plain
:disabled="isCountingDown || !isPhoneNumberValid" @click="sendVerificationCode">
{{ isCountingDown ? `${countdown}s` : '获取验证码' }}
</wd-button>
<wd-input
ref="verificationCodeInputRef"
v-model="formData.verificationCode"
class="inquire-wd-input flex-1"
placeholder="请输入验证码"
maxlength="6"
no-border
clearable
@focus="handleInputClick"
>
<template #suffix>
<wd-button
class="captcha-wd-btn"
size="small"
type="primary"
plain
:disabled="isCountingDown || !isPhoneNumberValid"
@click="sendVerificationCode"
>
{{ isCountingDown ? `${countdown}s` : '获取验证码' }}
</wd-button>
</template>
</wd-input>
</view>
</view>
@@ -481,27 +523,27 @@ watch(feature, async () => {
</view>
<!-- 查询按钮 -->
<button
class="bg-primary mb-4 mt-10 w-full flex items-center justify-center rounded-[48px] py-4 text-lg text-white font-medium"
@click="handleSubmit">
<text>{{ buttonText }}</text>
<text class="ml-4">
¥{{ featureData.sell_price }}
</text>
</button>
<wd-button class="submit-wd-btn mb-4 mt-10" block type="primary" @click="handleSubmit">
<view class="w-full flex items-center justify-center">
<text>{{ buttonText }}</text>
<text class="ml-4">
¥{{ featureData.sell_price }}
</text>
</view>
</wd-button>
<!-- <view class="text-xs text-gray-500 leading-relaxed mt-8" v-html="featureData.description">
</view> -->
<!-- 免责声明 -->
<view class="mt-2 text-center text-xs text-gray-500 leading-relaxed">
为保证用户的隐私及数据安全查询结果生成30天后将自动删除
为保证用户的隐私及数据安全查询结果生成{{ queryRetentionDaysText }}后将自动删除
</view>
</view>
<!-- 报告包含内容 -->
<view v-if="featureData.features && featureData.features.length > 0" class="card mt-3">
<view class="mb-3 flex items-center text-base font-semibold" style="color: var(--van-text-color);">
<view class="mb-3 flex items-center text-base font-semibold" style="color: var(--color-text-primary);">
<view class="mr-2 h-5 w-1 rounded-full"
style="background: linear-gradient(to bottom, var(--van-theme-primary), var(--van-theme-primary-dark));" />
style="background: linear-gradient(to bottom, var(--color-primary), var(--color-primary-700));" />
报告包含内容
</view>
<view class="grid grid-cols-4 items-stretch gap-2">
@@ -650,9 +692,9 @@ watch(feature, async () => {
</view>
<view class="mt-3 text-center">
<view class="inline-flex items-center border rounded-full px-3 py-1.5 transition-all"
style="background: linear-gradient(135deg, var(--van-theme-primary-light), rgba(255,255,255,0.8)); border-color: var(--van-theme-primary);">
<view class="mr-1.5 h-1.5 w-1.5 rounded-full" style="background-color: var(--van-theme-primary);" />
<text class="text-xs font-medium" style="color: var(--van-theme-primary);">
style="background: linear-gradient(135deg, var(--color-primary-light), rgba(255,255,255,0.8)); border-color: var(--color-primary);">
<view class="mr-1.5 h-1.5 w-1.5 rounded-full" style="background-color: var(--color-primary);" />
<text class="text-xs font-medium" style="color: var(--color-primary);">
更多信息请解锁报告
</text>
</view>
@@ -661,11 +703,11 @@ watch(feature, async () => {
<!-- 产品详情卡片 -->
<view class="card mt-4">
<view class="mb-4 text-xl font-bold" style="color: var(--van-text-color);">
<view class="mb-4 text-xl font-bold" style="color: var(--color-text-primary);">
{{ featureData.product_name }}
</view>
<view class="mb-4 flex items-start justify-between">
<view class="text-lg" style="color: var(--van-text-color-2);">
<view class="text-lg" style="color: var(--color-text-secondary);">
价格:
</view>
<view>
@@ -676,9 +718,9 @@ watch(feature, async () => {
</view>
<image v-if="productMainImage" :src="productMainImage" alt="产品详情主图" class="mb-4 w-full rounded-lg" />
<view class="mb-4 leading-relaxed" style="color: var(--van-text-color-2);" v-html="featureData.description" />
<view class="mb-4 leading-relaxed" style="color: var(--color-text-secondary);" v-html="featureData.description" />
<view class="text-danger mb-2 text-xs italic">
为保证用户的隐私以及数据安全,查询的结果生成30天之后将自动清除。
为保证用户的隐私以及数据安全,查询的结果生成{{ queryRetentionDaysText }}之后将自动清除。
</view>
</view>
</view>
@@ -755,15 +797,6 @@ watch(feature, async () => {
0 0 0 1px rgba(255, 255, 255, 0.05);
}
/* 按钮悬停效果 */
button:hover {
transform: translateY(-1px);
}
button:active {
transform: translateY(0);
}
/* 梯形背景图片样式 */
.trapezoid-bg-image {
background-size: contain;
@@ -790,6 +823,15 @@ button:active {
margin-left: 12px;
}
:deep(.inquire-wd-input .wd-input__inner) {
text-align: left !important;
}
:deep(.submit-wd-btn.wd-button) {
border-radius: 48px;
min-height: 48px;
}
:deep(.captcha-wd-btn.wd-button) {
min-width: 96px;
}

View File

@@ -1,79 +0,0 @@
<script setup>
// 接收 type 和 options props 以及 v-model
const props = defineProps({
type: {
type: String,
default: 'purple-pink', // 默认颜色渐变
},
options: {
type: Array,
required: true, // 动态传入选项
},
modelValue: {
type: String,
default: '', // v-model 绑定的值
},
})
const emit = defineEmits(['update:modelValue'])
// 选中内容绑定 v-model
const selected = ref(props.modelValue)
// 监听 v-model 的变化
watch(() => props.modelValue, (newValue) => {
selected.value = newValue
})
// 根据type动态生成分割线的类名
const lineClass = computed(() => {
// 统一使用主题色渐变
return 'bg-gradient-to-r from-red-600 via-red-500 to-red-700'
})
// 计算滑动线的位置和宽度
const slideLineStyle = computed(() => {
const index = props.options.findIndex(option => option.value === selected.value)
const buttonWidth = 100 / props.options.length
return {
width: `${buttonWidth}%`,
transform: `translateX(${index * 100}%)`,
}
})
// 选择选项函数
function selectOption(option) {
selected.value = option.value
// 触发 v-model 的更新
emit('update:modelValue', option.value)
}
</script>
<template>
<view class="relative flex">
<view
v-for="(option, index) in options"
:key="index"
class="flex-1 shrink-0 cursor-pointer py-2 text-center text-size-sm font-bold transition-transform duration-200 ease-in-out"
:class="{ 'text-gray-900': selected === option.value, 'text-gray-500': selected !== option.value }"
@click="selectOption(option)"
>
{{ option.label }}
</view>
<view
class="absolute bottom-0 h-[3px] rounded transition-all duration-300"
:style="slideLineStyle"
:class="lineClass"
/>
</view>
</template>
<style scoped>
/* 自定义样式 */
button {
outline: none;
border: none;
cursor: pointer;
}
button:focus {
outline: none;
}
</style>

View File

@@ -1,42 +0,0 @@
<script setup>
const route = useRoute()
// 返回上一页逻辑
function goBack() {
route.goBack()
}
</script>
<template>
<view class="card flex flex-col items-center justify-center text-center">
<!-- 图片插画 -->
<image src="/static/images/empty.svg" alt="空状态" class="h-64 w-64" />
<!-- 提示文字 -->
<text class="mb-2 text-xl text-gray-700 font-semibold">
没有查询到相关结果
</text>
<text class="mb-2 text-sm text-gray-500 leading-relaxed">
订单已申请退款预计
<text class="font-medium" style="color: var(--van-theme-primary);">24小时内到账</text>
</text>
<text class="text-xs text-gray-400">
如果已到账您可以忽略本提示
</text>
<!-- 返回按钮 -->
<button
class="mt-4 rounded-lg px-6 py-2 text-white transition duration-300 ease-in-out"
style="background-color: var(--van-theme-primary);"
onmouseover="this.style.backgroundColor='var(--van-theme-primary-dark)'"
onmouseout="this.style.backgroundColor='var(--van-theme-primary)'"
@click="goBack"
>
返回上一页
</button>
</view>
</template>
<style scoped>
/* 你可以添加一些额外的样式(如果需要) */
</style>

View File

@@ -1,94 +0,0 @@
<script setup>
import { ref } from 'vue'
const props = defineProps({
content: {
type: String,
required: true,
},
})
const isExpanded = ref(false)
</script>
<template>
<view class="l-remark-card my-[20px]">
<!-- 顶部连接点 -->
<view class="connection-line">
<image
src="data:image/png;base64,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"
alt="左链条" class="connection-chain left" />
<image
src="data:image/png;base64,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"
alt="右链条" class="connection-chain right" />
</view>
<view>
<wd-icon name="info-o" class="tips-icon" />
<text class="tips-title">温馨提示</text>
</view>
<view>
<wd-text rows="2" :content="content" expand-text="展开" collapse-text="收起" />
</view>
</view>
</template>
<style scoped>
.l-remark-card {
position: relative;
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
border-radius: 0.75rem;
background-color: #ffffff;
padding: 24px;
}
.tips-card {
background: var(--van-theme-primary-light);
border-radius: 8px;
padding: 12px;
}
.tips-icon {
color: var(--van-theme-primary);
margin-right: 5px;
}
.tips-title {
font-weight: bold;
font-size: 16px;
}
.tips-content {
font-size: 14px;
color: #333;
}
/* 连接链条样式 */
.connection-line {
position: absolute;
top: -40px;
left: 0;
right: 0;
height: 60px;
z-index: 20;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 10px;
}
.connection-chain {
height: 60px;
object-fit: contain;
}
.connection-chain.left {
width: 80px;
margin-left: -10px;
}
.connection-chain.right {
width: 80px;
margin-right: -10px;
}
</style>

View File

@@ -1,80 +0,0 @@
<script setup>
import { computed, onMounted } from 'vue'
// 接收表格数据和类型的 props
const props = defineProps({
data: {
type: Array,
required: true,
},
type: {
type: String,
default: 'purple-pink', // 默认渐变颜色
},
})
// 根据 type 设置不同的渐变颜色(偶数行)
const evenClass = computed(() => {
// 统一使用主题色浅色背景
return 'bg-red-50/40'
})
// 动态计算表头的背景颜色和文本颜色
const headerClass = computed(() => {
// 统一使用主题色浅色背景
return 'bg-red-100'
})
// 斑马纹样式,偶数行带颜色,奇数行没有颜色,且从第二行开始
function zebraClass(index) {
return index % 2 === 1 ? evenClass.value : ''
}
</script>
<template>
<view class="l-table overflow-x-auto">
<table
class="min-w-full border-collapse table-auto text-center text-size-xs"
>
<thead :class="headerClass">
<tr>
<!-- 插槽渲染表头 -->
<slot name="header" />
</tr>
</thead>
<tbody>
<tr
v-for="(row, index) in props.data"
:key="index"
:class="zebraClass(index)"
class="border-t"
>
<slot :row="row" />
</tr>
</tbody>
</table>
</view>
</template>
<style scoped>
/* 基础表格样式 */
th {
font-weight: bold;
padding: 12px;
text-align: left;
border: 1px solid #e5e7eb;
}
/* 表格行样式 */
td {
padding: 12px;
border: 1px solid #e5e7eb;
}
table {
width: 100%;
border-spacing: 0;
}
.l-table {
@apply rounded-xl;
overflow: hidden;
}
</style>

View File

@@ -1,27 +0,0 @@
<script setup>
// 接收 props
const props = defineProps({
title: String,
})
const titleClass = computed(() => {
// 统一使用主题色
return 'bg-primary'
})
</script>
<template>
<view class="relative">
<!-- 标题部分 -->
<view :class="titleClass" class="inline-block rounded-lg px-2 py-1 text-white font-bold shadow-md">
{{ title }}
</view>
<!-- 左上角修饰 -->
<view
class="absolute left-0 top-0 h-4 w-4 transform rounded-full bg-white shadow-md -translate-x-2 -translate-y-2"
/>
</view>
</template>
<style scoped></style>

View File

@@ -1,5 +1,5 @@
<script setup>
import { computed, nextTick, ref } from 'vue'
import { computed, nextTick, onUnmounted, ref } from 'vue'
import { useDialogStore } from '@/stores/dialogStore'
import { useUserStore } from '@/stores/userStore'
import { setAuthSession } from '@/utils/storage'
@@ -103,24 +103,30 @@ async function handleLogin() {
}
async function performLogin() {
const { data, error } = await useApiFetch('/user/mobileCodeLogin')
.post({ mobile: phoneNumber.value, code: verificationCode.value })
.json()
uni.showLoading({ title: '登录中...', mask: true })
try {
const { data, error } = await useApiFetch('/user/mobileCodeLogin', { silent: true })
.post({ mobile: phoneNumber.value, code: verificationCode.value })
.json()
if (data.value && !error.value) {
if (data.value.code === 200) {
setAuthSession(data.value.data)
if (data.value && !error.value) {
if (data.value.code === 200) {
setAuthSession(data.value.data)
await userStore.fetchUserInfo()
await userStore.fetchUserInfo()
showToast({ message: '登录成功' })
closeDialog()
emit('login-success')
}
else {
showToast(data.value.msg)
showToast({ message: '登录成功' })
closeDialog()
emit('login-success')
}
else {
showToast(data.value.msg)
}
}
}
finally {
uni.hideLoading()
}
}
function closeDialog() {
@@ -146,11 +152,23 @@ function toPrivacyPolicy() {
closeDialog()
uni.navigateTo({ url: '/pages/privacy-policy' })
}
onUnmounted(() => {
if (timer) {
clearInterval(timer)
}
})
</script>
<template>
<wd-popup v-model="dialogStore.showLogin" round position="bottom" :style="{ maxHeight: '90vh' }" :z-index="2000"
@close="closeDialog">
<wd-popup
v-model="dialogStore.showLogin"
round
position="bottom"
:style="{ maxHeight: '90vh' }"
:z-index="2000"
@close="closeDialog"
>
<view class="login-dialog">
<view class="title-bar">
<view class="title-bar-text">
@@ -174,8 +192,15 @@ function toPrivacyPolicy() {
<text class="form-label">
手机号
</text>
<wd-input v-model="phoneNumber" class="phone-wd-input" type="number" placeholder="请输入手机号" maxlength="11"
no-border clearable />
<wd-input
v-model="phoneNumber"
class="phone-wd-input"
type="number"
placeholder="请输入手机号"
maxlength="11"
no-border
clearable
/>
</view>
<view v-if="!isPasswordLogin" class="form-item">
@@ -183,11 +208,23 @@ function toPrivacyPolicy() {
验证码
</text>
<view class="verification-input-wrapper">
<wd-input ref="verificationCodeInputRef" v-model="verificationCode" class="verification-wd-input"
placeholder="请输入验证码" maxlength="6" no-border clearable>
<wd-input
ref="verificationCodeInputRef"
v-model="verificationCode"
class="verification-wd-input"
placeholder="请输入验证码"
maxlength="6"
no-border
clearable
>
<template #suffix>
<wd-button size="small" type="primary" plain :disabled="isCountingDown || !isPhoneNumberValid"
@click="sendVerificationCode">
<wd-button
size="small"
type="primary"
plain
:disabled="isCountingDown || !isPhoneNumberValid"
@click="sendVerificationCode"
>
{{ isCountingDown ? `${countdown}s` : '获取验证码' }}
</wd-button>
</template>
@@ -199,8 +236,15 @@ function toPrivacyPolicy() {
<text class="form-label">
密码
</text>
<wd-input v-model="password" class="phone-wd-input" type="text" show-password placeholder="请输入密码" no-border
clearable />
<wd-input
v-model="password"
class="phone-wd-input"
type="text"
show-password
placeholder="请输入密码"
no-border
clearable
/>
</view>
<view class="flex items-center justify-end py-1">

View File

@@ -15,25 +15,14 @@ const props = defineProps({
required: true,
},
})
const { isWeChat } = useEnv()
const isDev = import.meta.env.DEV
/** APP 原生端同时展示微信与支付宝;其它端沿用微信内仅微信、否则仅支付宝 */
const isAppClient = computed(() => {
try {
return uni.getSystemInfoSync().uniPlatform === 'app'
}
catch {
return false
}
})
const appName = import.meta.env.VITE_APP_NAME || 'App'
const appLogo = '/static/images/logo.png'
const wechatPayIcon = '/static/images/wechatpay.svg'
const alipayIcon = '/static/images/alipay.svg'
const show = defineModel()
const selectedPaymentMethod = ref(isWeChat.value ? 'wechat' : 'alipay')
const selectedPaymentMethod = ref('alipay')
const paymentDisplayTime = ref('')
function toFiniteNumber(value) {
@@ -61,12 +50,6 @@ const displayAmount = computed(() => {
return payableAmount.value.toFixed(2)
})
const displayDiscountAmount = computed(() => {
if (payableAmount.value === null)
return '--'
return (payableAmount.value * 0.2).toFixed(2)
})
/** 支付弹窗展示用YYYY-MM-DD HH:mm:ss */
function formatPaymentTime(d = new Date()) {
const pad = n => String(n).padStart(2, '0')
@@ -84,11 +67,7 @@ function showToast(options) {
}
function setDefaultPaymentMethod() {
if (isAppClient.value) {
selectedPaymentMethod.value = 'alipay'
return
}
selectedPaymentMethod.value = isWeChat.value ? 'wechat' : 'alipay'
selectedPaymentMethod.value = 'alipay'
}
onMounted(setDefaultPaymentMethod)
@@ -101,7 +80,6 @@ watch(show, (v) => {
})
const router = useRouter()
const discountPrice = ref(false)
/** APP 端 wxpay服务端返回的 prepay_data 对象,供 uni.requestPayment 使用 */
function normalizeWxAppOrderInfo(raw) {
@@ -129,103 +107,54 @@ async function getPayment() {
const prepayData = respData.prepay_data
const orderNoFromResp = respData.order_no
if (prepayId === 'test_payment_success') {
if (selectedPaymentMethod.value === 'alipay' || selectedPaymentMethod.value === 'wechat') {
showToast({ message: '支付参数异常,请重试', type: 'fail' })
return
}
show.value = false
router.push({
path: '/payment/result',
query: { orderNo: orderNoFromResp },
})
return
}
// APP 原生:支付宝 / 微信uni.requestPayment
if (isAppClient.value) {
if (selectedPaymentMethod.value === 'alipay') {
if (!prepayId || typeof prepayId !== 'string') {
showToast({ message: '支付宝下单参数异常', type: 'fail' })
return
}
uni.requestPayment({
provider: 'alipay',
orderInfo: prepayId,
success: () => {
show.value = false
router.push({
path: '/payment/result',
query: { orderNo: orderNoFromResp },
})
},
fail: (e) => {
const msg = (e && (e.errMsg || e.message)) || '支付未完成'
showToast({ message: String(msg), type: 'fail' })
},
})
return
}
if (selectedPaymentMethod.value === 'wechat') {
const orderInfo = normalizeWxAppOrderInfo(prepayData)
if (!orderInfo) {
showToast({ message: '微信支付参数异常', type: 'fail' })
return
}
uni.requestPayment({
provider: 'wxpay',
orderInfo,
success: () => {
show.value = false
router.push({
path: '/payment/result',
query: { orderNo: orderNoFromResp },
})
},
fail: (e) => {
const msg = (e && (e.errMsg || e.message)) || '支付未完成'
showToast({ message: String(msg), type: 'fail' })
},
})
return
}
}
if (selectedPaymentMethod.value === 'alipay') {
if (typeof document === 'undefined') {
showToast({ message: '当前环境不支持网页支付宝支付', type: 'fail' })
if (!prepayId || typeof prepayId !== 'string') {
showToast({ message: '支付宝下单参数异常', type: 'fail' })
return
}
const prepayUrl = prepayId
const paymentForm = document.createElement('form')
paymentForm.method = 'POST'
paymentForm.action = prepayUrl
paymentForm.style.display = 'none'
document.body.appendChild(paymentForm)
paymentForm.submit()
show.value = false
return
}
const payload = prepayData
if (typeof WeixinJSBridge === 'undefined') {
showToast({ message: '请在微信内打开以完成支付', type: 'fail' })
return
}
WeixinJSBridge.invoke(
'getBrandWCPayRequest',
payload,
(res) => {
if (res.err_msg === 'get_brand_wcpay_request:ok') {
uni.requestPayment({
provider: 'alipay',
orderInfo: prepayId,
success: () => {
show.value = false
router.push({
path: '/payment/result',
query: { orderNo: orderNoFromResp },
})
}
},
)
},
fail: (e) => {
const msg = (e && (e.errMsg || e.message)) || '支付未完成'
showToast({ message: String(msg), type: 'fail' })
},
})
return
}
if (selectedPaymentMethod.value === 'wechat') {
const orderInfo = normalizeWxAppOrderInfo(prepayData)
if (!orderInfo) {
showToast({ message: '微信支付参数异常', type: 'fail' })
return
}
uni.requestPayment({
provider: 'wxpay',
orderInfo,
success: () => {
show.value = false
router.push({
path: '/payment/result',
query: { orderNo: orderNoFromResp },
})
},
fail: (e) => {
const msg = (e && (e.errMsg || e.message)) || '支付未完成'
showToast({ message: String(msg), type: 'fail' })
},
})
return
}
showToast({ message: '支付方式不支持', type: 'fail' })
}
function onCancel() {
@@ -234,14 +163,8 @@ function onCancel() {
</script>
<template>
<wd-popup
v-model="show"
round
position="bottom"
:safe-area-inset-bottom="true"
:z-index="2000"
custom-style="max-height: 88vh;"
>
<wd-popup v-model="show" round position="bottom" :safe-area-inset-bottom="true" :z-index="2000"
custom-style="max-height: 88vh;">
<view class="payment-popup">
<view class="payment-popup__header">
<text class="payment-popup__title">
@@ -280,21 +203,11 @@ function onCancel() {
应付金额
</view>
<view class="payment-popup__amount-price">
<view v-if="discountPrice" class="payment-popup__strike">
¥ {{ displayAmount }}
</view>
<view>
¥
{{
discountPrice
? displayDiscountAmount
: displayAmount
}}
{{ displayAmount }}
</view>
</view>
<view v-if="discountPrice" class="payment-popup__discount-tip">
活动价2折优惠
</view>
</view>
<view class="payment-popup__methods">
@@ -302,15 +215,15 @@ function onCancel() {
支付方式
</text>
<wd-radio-group v-model="selectedPaymentMethod" shape="dot" cell class="payment-radio-group">
<wd-radio v-if="isAppClient || isWeChat" value="wechat">
<!-- <wd-radio value="wechat">
<view class="payment-radio-row">
<image class="payment-radio-row__pay-icon" :src="wechatPayIcon" mode="aspectFit" />
<text>
微信支付
</text>
</view>
</wd-radio>
<wd-radio v-if="isAppClient || !isWeChat" value="alipay">
</wd-radio> -->
<wd-radio value="alipay">
<view class="payment-radio-row">
<image class="payment-radio-row__pay-icon" :src="alipayIcon" mode="aspectFit" />
<text>
@@ -318,23 +231,13 @@ function onCancel() {
</text>
</view>
</wd-radio>
<wd-radio v-if="isDev" value="test">
<view class="payment-radio-row">
<wd-icon size="24" name="description" color="#ff976a" class="payment-radio-row__icon" />
<text>
开发环境测试支付
</text>
</view>
</wd-radio>
</wd-radio-group>
</view>
<view class="payment-popup__actions">
<!-- eslint-disable-next-line unocss/order-attributify -- 组件属性 block UnoCSS -->
<wd-button block round size="large" type="primary" custom-class="payment-btn-primary" @click="getPayment">
确认支付
</wd-button>
<!-- eslint-disable-next-line unocss/order-attributify -- 组件属性 block UnoCSS -->
<wd-button block round size="small" type="text" custom-class="payment-btn-cancel" @click="onCancel">
取消
</wd-button>
@@ -433,19 +336,6 @@ function onCancel() {
color: #ee0a24;
}
.payment-popup__strike {
margin-bottom: 8rpx;
font-size: 28rpx;
color: #969799;
text-decoration: line-through;
}
.payment-popup__discount-tip {
margin-top: 8rpx;
font-size: 24rpx;
color: #ee0a24;
}
.payment-popup__section-label {
display: block;
margin-bottom: 16rpx;

View File

@@ -62,6 +62,9 @@ const promotionRevenue = computed(() => {
return safeTruncate(price.value - costPrice.value)
})
/** APP 端 placeholder 需用原生 style否则字号易偏小 */
const PRICE_PLACEHOLDER_STYLE = 'color:#9ca3af;font-size:40rpx;font-weight:500;'
// 价格校验与修正逻辑
function validatePrice(currentPrice) {
if (!productConfig.value) {
@@ -154,24 +157,20 @@ function onBlurPrice() {
</view>
<view class="card m-4">
<view class="flex items-center justify-between">
<view class="text-lg">
<view class="text-lg font-semibold text-gray-800">
客户查询价 ()
</view>
</view>
<view class="price-input-wrap border border-orange-100 rounded-xl bg-orange-50/40 px-2">
<wd-input
v-model="price"
type="number"
label="¥"
size="large"
label-width="34"
no-border
custom-input-class="price-input-inner"
custom-label-class="price-input-label"
<view class="price-input-surface">
<!-- 不用 label 而用 prefix避免 is-cell 下金额区与 被拉成分栏靠右 -->
<wd-input v-model="price" custom-class="price-wd-input" type="number" size="large" no-border
:placeholder="`${productConfig?.price_range_min || 0} - ${productConfig?.price_range_max || 0}`"
@blur="onBlurPrice"
/>
:placeholder-style="PRICE_PLACEHOLDER_STYLE" custom-input-class="price-input-inner" @blur="onBlurPrice">
<template #prefix>
<text class="price-currency"></text>
</template>
</wd-input>
</view>
<view class="mt-2 flex items-center justify-between">
<view>
@@ -222,27 +221,64 @@ function onBlurPrice() {
</template>
<style lang="scss" scoped>
.price-input-wrap {
box-shadow: inset 0 0 0 1px rgba(251, 146, 60, 0.08);
/* 可点击区域:白底+粗边框+阴影APP 上边界更清晰 */
.price-input-surface {
margin-top: 12px;
padding: 24rpx 20rpx;
min-height: 120rpx;
border: 2px solid #fb923c;
border-radius: 20rpx;
background: #fff;
box-shadow: 0 2px 12px rgba(251, 146, 60, 0.12);
}
.price-input-wrap :deep(.price-input-label) {
.price-input-surface :deep(.price-wd-input.wd-input) {
background: transparent;
padding: 0;
}
.price-input-surface :deep(.wd-input__body) {
width: 100%;
padding: 0;
}
/* prefix + 输入同一行,金额紧贴 ¥ 左侧起笔 */
.price-input-surface :deep(.wd-input__value) {
display: flex;
flex-direction: row;
align-items: center;
min-height: 96rpx;
width: 100%;
flex: 1;
}
.price-input-surface :deep(.wd-input__prefix) {
display: flex;
flex-direction: row;
align-items: center;
flex-shrink: 0;
margin-right: 0;
padding-right: 6rpx;
}
.price-currency {
color: #ea580c;
font-size: 22px;
font-size: 64rpx;
font-weight: 700;
line-height: 1;
}
.price-input-wrap :deep(.price-input-inner) {
height: 56px;
font-size: 40px;
/* 金额:与 ¥ 同档字号 */
.price-input-surface :deep(.wd-input__inner),
.price-input-surface :deep(input.wd-input__inner),
.price-input-surface :deep(.price-input-inner) {
flex: 1;
min-width: 0;
min-height: 88rpx;
font-size: 64rpx !important;
font-weight: 700;
color: #111827;
line-height: 56px;
text-align: left !important;
}
.price-input-wrap :deep(.wd-input__placeholder) {
font-size: 20px;
color: #111827 !important;
line-height: 1.15 !important;
text-align: left !important;
}
</style>

View File

@@ -131,14 +131,14 @@ function scaleQrRectForPoster(position, posterInfo, modeKey) {
// QR码位置配置为每个海报单独配置
const qrCodePositions = ref({
promote: [
{ x: 405, y: 1620, size: 480 }, // tg_qrcode_1.png
{ x: 405, y: 1620, size: 480 }, // tg_qrcode_2.jpg
{ x: 405, y: 1620, size: 480 }, // tg_qrcode_3.jpg
{ x: 405, y: 1620, size: 480 }, // tg_qrcode_4.jpg
{ x: 405, y: 1620, size: 480 }, // tg_qrcode_5.jpg
{ x: 180, y: 1440, size: 300 }, // tg_qrcode_6.jpg
{ x: 255, y: 940, size: 250 }, // tg_qrcode_7.jpg
{ x: 255, y: 940, size: 250 }, // tg_qrcode_8.jpg
{ x: 405, y: 1270, size: 440 }, // tg_qrcode_1.png
{ x: 405, y: 1270, size: 440 }, // tg_qrcode_2.jpg
{ x: 405, y: 1270, size: 440 }, // tg_qrcode_3.jpg
{ x: 405, y: 1270, size: 440 }, // tg_qrcode_4.jpg
{ x: 405, y: 1270, size: 440 }, // tg_qrcode_5.jpg
{ x: 210, y: 1660, size: 340 }, // tg_qrcode_6.jpg
{ x: 405, y: 1270, size: 440 }, // tg_qrcode_7.jpg
{ x: 405, y: 1270, size: 440 }, // tg_qrcode_8.jpg
],
// invitation模式的配置 (yq_qrcode)
invitation: [
@@ -784,37 +784,28 @@ export default {
<!-- 放在弹窗外避免 wd-popup 关闭时子树未挂载导致 APP 无法 createCanvasContext -->
<canvas v-if="!isWebPlatform" :id="QR_GEN_CANVAS_ID" :canvas-id="QR_GEN_CANVAS_ID" class="poster-qr-gen-canvas" />
<!-- App仅作 renderjs change 锚点真正画布在 renderjs createElement('canvas') -->
<view
v-if="!isWebPlatform" class="poster-renderjs-anchor" :poster-merge-req="posterMergeRequest"
:change:poster-merge-req="posterRender.onMergeReqChange" aria-hidden="true"
/>
<view v-if="!isWebPlatform" class="poster-renderjs-anchor" :poster-merge-req="posterMergeRequest"
:change:poster-merge-req="posterRender.onMergeReqChange" aria-hidden="true" />
<wd-popup v-model="show" round position="bottom" :style="{ maxHeight: '95vh' }">
<view class="qrcode-popup-container">
<view class="qrcode-content">
<swiper
class="poster-swiper rounded-lg shadow sm:rounded-xl" indicator-color="white" circular indicator-dots
@change="onSwipeChange"
>
<swiper class="poster-swiper rounded-lg shadow sm:rounded-xl" indicator-color="white" circular indicator-dots
@change="onSwipeChange">
<swiper-item v-for="(_, index) in posterImages" :key="index" class="poster-swiper-item">
<view class="poster-canvas-box">
<template v-if="isWebPlatform">
<view class="poster-preview-box web-preview">
<canvas
:id="getCanvasId(index)" :ref="(el) => (posterCanvasRefs[index] = el)"
<canvas :id="getCanvasId(index)" :ref="(el) => (posterCanvasRefs[index] = el)"
:canvas-id="getCanvasId(index)" :width="posterCanvasSizes[index]?.width ?? 300"
:height="posterCanvasSizes[index]?.height ?? 300"
:style="posterCanvasContainStyle(index)"
class="poster-canvas poster-canvas--h5-contain rounded-lg sm:rounded-xl"
/>
:height="posterCanvasSizes[index]?.height ?? 300" :style="posterCanvasContainStyle(index)"
class="poster-canvas poster-canvas--h5-contain rounded-lg sm:rounded-xl" />
</view>
</template>
<template v-else>
<!-- App预览与保存同源aspectFit 在轮播高度内整图可见等比宽可不拉满 -->
<view v-if="posterCompositePath[index]" class="poster-app-preview">
<image
:src="posterCompositePath[index]" mode="aspectFit"
class="poster-app-composite rounded-lg sm:rounded-xl"
/>
<image :src="posterCompositePath[index]" mode="aspectFit"
class="poster-app-composite rounded-lg sm:rounded-xl" />
</view>
<view v-else class="poster-preview-placeholder">
<text class="poster-preview-placeholder-text">
@@ -874,10 +865,8 @@ export default {
</wd-popup>
<!-- 图片保存指引遮罩层 -->
<ImageSaveGuide
:show="showImageGuide" :image-url="currentImageUrl" :title="imageGuideTitle"
@close="closeImageGuide"
/>
<ImageSaveGuide :show="showImageGuide" :image-url="currentImageUrl" :title="imageGuideTitle"
@close="closeImageGuide" />
</template>
<style lang="scss" scoped>
@@ -943,7 +932,7 @@ export default {
box-sizing: border-box;
}
.poster-canvas-box > view {
.poster-canvas-box>view {
flex: 1;
min-width: 0;
min-height: 0;
@@ -1085,13 +1074,13 @@ export default {
}
}
/* 优化 van-divider 在小屏幕上的间距 */
:deep(.van-divider) {
/* 优化 wd-divider 在小屏幕上的间距 */
:deep(.wd-divider) {
margin: 0.5rem 0;
}
@media (min-width: 640px) {
:deep(.van-divider) {
:deep(.wd-divider) {
margin: 0.75rem 0;
}
}

View File

@@ -33,7 +33,7 @@ const isPhoneNumberValid = computed(() => {
})
const isIdCardValid = computed(() => {
return /^(?:\d{15}|\d{17}[\dXx])$/.test(idCard.value)
return /^(?:\d{15}|\d{17}[\dX])$/i.test(idCard.value)
})
const isRealNameValid = computed(() => {
@@ -172,22 +172,22 @@ onUnmounted(() => {
>
<view
class="real-name-auth-dialog"
style="background: linear-gradient(135deg, var(--van-theme-primary-light), rgba(255,255,255,0.9));"
style="background: linear-gradient(135deg, var(--color-primary-light), rgba(255,255,255,0.9));"
>
<view class="title-bar">
<view class="text-base font-bold sm:text-lg" style="color: var(--van-text-color);">
<view class="text-base font-bold sm:text-lg" style="color: var(--color-text-primary);">
实名认证
</view>
<wd-icon name="cross" class="close-icon" style="color: var(--van-text-color-2);" @click="closeDialog" />
<wd-icon name="cross" class="close-icon" style="color: var(--color-text-secondary);" @click="closeDialog" />
</view>
<view class="dialog-content">
<view class="dialog-inner px-4 pb-4 pt-2">
<view
class="auth-notice mb-4 rounded-xl p-3 sm:p-4"
style="background-color: var(--van-theme-primary-light); border: 1px solid rgba(162, 37, 37, 0.2);"
style="background-color: var(--color-primary-light); border: 1px solid rgba(162, 37, 37, 0.2);"
>
<view class="text-xs space-y-1.5 sm:text-sm sm:space-y-2" style="color: var(--van-text-color);">
<text class="font-medium" style="color: var(--van-theme-primary);">
<view class="text-xs space-y-1.5 sm:text-sm sm:space-y-2" style="color: var(--color-text-primary);">
<text class="font-medium" style="color: var(--color-primary);">
实名认证说明
</text>
<text>1. 实名认证是提现的必要条件</text>
@@ -320,7 +320,7 @@ onUnmounted(() => {
justify-content: space-between;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid var(--van-border-color);
border-bottom: 1px solid var(--color-border-primary);
flex-shrink: 0;
}

View File

@@ -1,146 +0,0 @@
<script setup>
import { ref } from 'vue'
const props = defineProps({
orderId: {
type: String,
default: '',
},
orderNo: {
type: String,
default: '',
},
isExample: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
})
const isLoading = ref(false)
function showToast(options) {
const message = typeof options === 'string' ? options : (options?.message || options?.title || '')
if (!message)
return
uni.showToast({
title: message,
icon: options?.type === 'success' ? 'success' : 'none',
})
}
async function copyToClipboard(text) {
await navigator.clipboard.writeText(text)
showToast({
type: 'success',
message: '链接已复制到剪贴板',
position: 'bottom',
})
}
async function handleShare() {
if (isLoading.value || props.disabled)
return
// 如果是示例模式直接分享当前URL
if (props.isExample) {
try {
const currentUrl = window.location.href
await copyToClipboard(currentUrl)
showToast({
type: 'success',
message: '示例链接已复制到剪贴板',
position: 'bottom',
})
}
catch {
showToast({
type: 'fail',
message: '复制链接失败',
position: 'bottom',
})
}
return
}
// 优先使用 orderId如果没有则使用 orderNo
const orderIdentifier = props.orderId || props.orderNo
if (!orderIdentifier) {
showToast({
type: 'fail',
message: '缺少订单标识',
position: 'bottom',
})
return
}
isLoading.value = true
try {
// 根据实际使用的标识构建请求参数
const requestData = props.orderId
? { order_id: Number.parseInt(props.orderId) }
: { order_no: props.orderNo }
const { data, error } = await useApiFetch('/query/generate_share_link')
.post(requestData)
.json()
if (error.value) {
throw new Error(error.value)
}
if (data.value?.code === 200 && data.value.data?.share_link) {
const baseUrl = window.location.origin
const linkId = encodeURIComponent(data.value.data.share_link)
const fullShareUrl = `${baseUrl}/report/share/${linkId}`
try {
const { confirm } = await uni.showModal({
title: '分享链接已生成',
content: '链接将在7天后过期是否复制到剪贴板',
confirmText: '复制链接',
cancelText: '取消',
})
if (!confirm)
return
await copyToClipboard(fullShareUrl)
}
catch (dialogErr) {
console.error(dialogErr)
}
}
else {
throw new Error(data.value?.message || '生成分享链接失败')
}
}
catch (err) {
showToast({
type: 'fail',
message: err.message || '生成分享链接失败',
position: 'bottom',
})
}
finally {
isLoading.value = false
}
}
</script>
<template>
<view
class="bg-primary border-primary hover:bg-primary-600 flex cursor-pointer items-center justify-center border rounded-[40px] px-3 py-1 transition-colors duration-200"
:class="{ 'opacity-50 cursor-not-allowed': isLoading || disabled }" @click="handleShare"
>
<image src="/static/images/report/fx.png" alt="分享" class="mr-1 h-4 w-4" />
<text class="text-sm text-white font-medium">
{{ isLoading ? "生成中..." : (isExample ? "分享示例" : "分享报告") }}
</text>
</view>
</template>
<style lang="scss" scoped>
/* 样式已通过 Tailwind CSS 类实现 */
</style>

View File

@@ -1,44 +0,0 @@
<script setup>
// 透传所有属性和事件到 van-tabs
defineOptions({
inheritAttrs: false,
})
</script>
<template>
<wd-tabs v-bind="$attrs" type="card" class="styled-tabs">
<slot />
</wd-tabs>
</template>
<style scoped>
/* van-tabs 卡片样式定制 - 仅用于此组件 */
.styled-tabs:deep(.van-tabs__line) {
background-color: var(--van-theme-primary) !important;
}
.styled-tabs:deep(.van-tabs__nav) {
gap: 10px;
}
.styled-tabs:deep(.van-tabs__nav--card) {
border: unset !important;
}
.styled-tabs:deep(.van-tab--card) {
color: #666666 !important;
border-right: unset !important;
background-color: #eeeeee !important;
border-radius: 8px !important;
}
.styled-tabs:deep(.van-tab--active) {
color: white !important;
background-color: var(--van-theme-primary) !important;
}
.styled-tabs:deep(.van-tabs__wrap) {
background-color: #ffffff !important;
padding: 9px 0;
}
</style>

View File

@@ -1,23 +0,0 @@
<script setup>
// 不需要额外的 props 或逻辑,只是一个简单的样式组件
</script>
<template>
<view class="title-banner">
<slot />
</view>
</template>
<style scoped>
.title-banner {
@apply mx-auto mt-2 w-64 py-1.5 text-center text-white font-bold text-lg relative rounded-2xl;
background: var(--color-primary);
border: 1px solid var(--color-primary-300);
background-image:
linear-gradient(45deg, transparent 25%, rgba(255, 255, 255, 0.1) 25%, rgba(255, 255, 255, 0.1) 50%, transparent 50%, transparent 75%, rgba(255, 255, 255, 0.1) 75%);
background-size: 20px 20px;
background-position: 0 0;
position: relative;
overflow: hidden;
}
</style>

View File

@@ -1,187 +0,0 @@
<script setup>
import LTitle from './LTitle.vue'
import ShareReportButton from './ShareReportButton.vue'
const props = defineProps({
reportParams: {
type: Object,
required: true,
},
reportDateTime: {
type: [String, null],
required: false,
default: null,
},
reportName: {
type: String,
required: true,
},
isEmpty: {
type: Boolean,
required: true,
},
isShare: {
type: Boolean,
required: false,
default: false,
},
orderId: {
type: [String, Number],
required: false,
default: null,
},
orderNo: {
type: String,
required: false,
default: null,
},
})
// 脱敏函数
function maskValue(type, value) {
if (!value)
return value
if (type === 'name') {
// 姓名脱敏(保留首位)
if (value.length === 1) {
return '*' // 只保留一个字,返回 "*"
}
else if (value.length === 2) {
return `${value[0]}*` // 两个字,保留姓氏,第二个字用 "*" 替代
}
else {
return (
value[0]
+ '*'.repeat(value.length - 2)
+ value[value.length - 1]
) // 两个字以上,保留第一个和最后一个字,其余的用 "*" 替代
}
}
else if (type === 'id_card') {
// 身份证号脱敏保留前6位和最后4位
return value.replace(/^(.{6})\d+(.{4})$/, '$1****$2')
}
else if (type === 'mobile') {
if (value.length === 11) {
return `${value.substring(0, 3)}****${value.substring(7)}`
}
return value // 如果手机号不合法或长度不为 11 位,直接返回原手机号
}
return value
}
</script>
<template>
<view class="card" style="padding-left: 0; padding-right: 0; padding-bottom: 24px;">
<view class="flex flex-col gap-y-2">
<!-- 报告信息 -->
<view class="flex items-center justify-between py-2">
<LTitle title="报告信息" />
<!-- 分享按钮 -->
<ShareReportButton
v-if="!isShare" :order-id="orderId" :order-no="orderNo" :is-example="!orderId"
class="mr-4"
/>
</view>
<view class="mx-4 my-2 flex flex-col gap-2">
<view class="flex pb-2 pl-2">
<text class="w-[6em] text-[#666666]">报告时间</text>
<text class="text-gray-600">{{
reportDateTime
|| "2025-01-01 12:00:00"
}}</text>
</view>
<view v-if="!isEmpty" class="flex pb-2 pl-2">
<text class="w-[6em] text-[#666666]">报告项目</text>
<text class="text-gray-600 font-bold">
{{ reportName }}</text>
</view>
</view>
<!-- 报告对象 -->
<template v-if="Object.keys(reportParams).length != 0">
<LTitle title="报告对象" />
<view class="mx-4 my-2 flex flex-col gap-2">
<!-- 姓名 -->
<view v-if="reportParams?.name" class="flex pb-2 pl-2">
<text class="w-[6em] text-[#666666]">姓名</text>
<text class="text-gray-600">{{
maskValue(
"name",
reportParams?.name,
)
}}</text>
</view>
<!-- 身份证号 -->
<view v-if="reportParams?.id_card" class="flex pb-2 pl-2">
<text class="w-[6em] text-[#666666]">身份证号</text>
<text class="text-gray-600">{{
maskValue(
"id_card",
reportParams?.id_card,
)
}}</text>
</view>
<!-- 手机号 -->
<view v-if="reportParams?.mobile" class="flex pb-2 pl-2">
<text class="w-[6em] text-[#666666]">手机号</text>
<text class="text-gray-600">{{
maskValue(
"mobile",
reportParams?.mobile,
)
}}</text>
</view>
<!-- 验证卡片 -->
<view class="mt-4 flex flex-col gap-4">
<!-- 身份证检查结果 -->
<view class="flex flex-1 items-center border border-[#EEEEEE] rounded-lg bg-[#F9F9F9] px-4 py-3">
<view class="mr-4 h-11 w-11 flex items-center justify-center">
<image src="/static/images/report/sfz.png" alt="身份证" class="h-10 w-10 object-contain" />
</view>
<view class="flex-1">
<view class="text-lg text-gray-800 font-bold">
身份证检查结果
</view>
<view class="text-sm text-[#999999]">
身份证信息核验通过
</view>
</view>
<view class="ml-4 h-11 w-11 flex items-center justify-center">
<image src="/static/images/report/zq.png" alt="资金安全" class="h-10 w-10 object-contain" />
</view>
</view>
<!-- 手机号检测结果 -->
<!-- <view class="flex items-center px-4 py-3 flex-1 border border-[#EEEEEE] rounded-lg bg-[#F9F9F9]">
<view class="w-11 h-11 flex items-center justify-center mr-4">
<image src="/static/images/report/sjh.png" alt="手机号" class="w-10 h-10 object-contain" />
</view>
<view class="flex-1">
<view class="font-bold text-gray-800 text-lg">
手机号检测结果
</view>
<view class="text-sm text-[#999999]">
被查询人姓名与运营商提供的一致
</view>
<view class="text-sm text-[#999999]">
被查询人身份证与运营商提供的一致
</view>
</view>
<view class="w-11 h-11 flex items-center justify-center ml-4">
<image src="/static/images/report/zq.png" alt="资金安全" class="w-10 h-10 object-contain" />
</view>
</view> -->
</view>
</view>
</template>
</view>
</view>
</template>
<style scoped>
/* 组件样式已通过 Tailwind CSS 类实现 */
</style>