feat(quality-owner): 增加优质号主、极品专区与无忧租号保险
- 支持保证金余额支付、在线支付开通及后台手动设置审计 - 新增极品专区发布权限、赔付方案与保险结算费率快照 - 优化首页、商品卡和个人中心的优质号主身份展示
This commit is contained in:
@@ -13,6 +13,7 @@ export interface AuthUser {
|
||||
credit_score: number
|
||||
renter_growth_points?: number
|
||||
renter_growth_level?: string
|
||||
quality_owner_status?: string
|
||||
status: UserStatus
|
||||
}
|
||||
|
||||
|
||||
@@ -96,6 +96,16 @@
|
||||
border: none;
|
||||
}
|
||||
|
||||
.owner-quality-tag {
|
||||
height: 18px;
|
||||
padding: 0 7px;
|
||||
border: 1px solid #d8b4fe;
|
||||
background: linear-gradient(135deg, #6d28d9 0%, #9333ea 100%) !important;
|
||||
color: #fff !important;
|
||||
font-size: 10px !important;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.user-meta {
|
||||
margin: 4px 0 0;
|
||||
color: #6b7280;
|
||||
@@ -664,6 +674,76 @@
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.quality-owner-card {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
margin: 10px 12px;
|
||||
padding: 16px;
|
||||
border: 1px solid #e9e2fb;
|
||||
border-radius: 14px;
|
||||
background: linear-gradient(135deg, #faf8ff 0%, #ffffff 72%);
|
||||
}
|
||||
|
||||
.quality-owner-card.is-active {
|
||||
border-color: #c4b5fd;
|
||||
}
|
||||
|
||||
.quality-owner-heading,
|
||||
.quality-owner-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.quality-owner-heading > div,
|
||||
.quality-owner-info > div {
|
||||
display: grid;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.quality-owner-heading span,
|
||||
.quality-owner-info span {
|
||||
color: #7b8492;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.quality-owner-heading strong {
|
||||
color: #312e81;
|
||||
font-size: 15px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.quality-owner-info {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.quality-owner-info > div {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
background: rgba(245, 243, 255, 0.85);
|
||||
}
|
||||
|
||||
.quality-owner-info strong {
|
||||
color: #6d28d9;
|
||||
font-size: 14px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.quality-owner-actions {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.quality-owner-card p {
|
||||
margin: 0;
|
||||
color: #7b8492;
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* ========== 响应式适配 ========== */
|
||||
@media (min-width: 520px) {
|
||||
.mobile-profile-shell {
|
||||
|
||||
@@ -18,6 +18,18 @@ import { uploadFile } from '@/shared/api/files'
|
||||
import { formatCent } from '@/shared/utils/money'
|
||||
import { setPassword } from '@/features/auth/api/auth'
|
||||
import { fetchUserInvitationSummary } from '@/features/invitation/api/invitation'
|
||||
import {
|
||||
activateQualityOwner,
|
||||
fetchQualityOwnerStatus,
|
||||
queryQualityOwnerPayment,
|
||||
startQualityOwnerPayment,
|
||||
type QualityOwnerStatus,
|
||||
} from '@/features/quality-owner/api/qualityOwner'
|
||||
import type { PaymentPayWay } from '@/features/orders/api/orders'
|
||||
import { useMobilePayWaySelect } from '@/features/orders/composables/useMobilePayWaySelect'
|
||||
import { useMobilePaymentCashier } from '@/features/orders/composables/useMobilePaymentCashier'
|
||||
import MobilePayWaySelectPopup from '@/features/orders/components/MobilePayWaySelectPopup.vue'
|
||||
import MobilePaymentCashierPopup from '@/features/orders/components/MobilePaymentCashierPopup.vue'
|
||||
|
||||
import { useDistributionSiteStore } from '@/stores/distributionSite'
|
||||
|
||||
@@ -33,6 +45,22 @@ const { unreadCount: notificationUnreadCount, unreadLabel: notificationUnreadLab
|
||||
|
||||
/** 数据项 */
|
||||
const availableBalanceCent = ref(0)
|
||||
const qualityOwnerStatus = ref<QualityOwnerStatus | null>(null)
|
||||
const qualityOwnerLoading = ref(false)
|
||||
const qualityOwnerOnlineLoading = ref(false)
|
||||
const qualityOwnerPayWaySelect = useMobilePayWaySelect()
|
||||
const qualityOwnerCashier = useMobilePaymentCashier({
|
||||
queryPayment: () => queryQualityOwnerPayment(),
|
||||
paidMessage: '保证金支付成功,优质号主已开通',
|
||||
})
|
||||
const {
|
||||
paymentPopupVisible: qualityOwnerPaymentPopupVisible,
|
||||
activePayment: activeQualityOwnerPayment,
|
||||
payURL: qualityOwnerPaymentPayURL,
|
||||
paymentQRCodeURL: qualityOwnerPaymentQRCodeURL,
|
||||
qrGenerating: qualityOwnerPaymentQRGenerating,
|
||||
checkingPayment: qualityOwnerPaymentChecking,
|
||||
} = qualityOwnerCashier
|
||||
const showSettings = ref(false)
|
||||
const showProfileEditor = ref(false)
|
||||
const savingProfile = ref(false)
|
||||
@@ -121,10 +149,72 @@ onMounted(async () => {
|
||||
// 保留本地会话展示,避免个人中心因网络短暂异常整页不可用。
|
||||
}
|
||||
void loadBalance()
|
||||
void loadQualityOwnerStatus()
|
||||
void loadInviteRate()
|
||||
}
|
||||
})
|
||||
|
||||
async function loadQualityOwnerStatus() {
|
||||
try {
|
||||
qualityOwnerStatus.value = await fetchQualityOwnerStatus()
|
||||
} catch {
|
||||
qualityOwnerStatus.value = null
|
||||
}
|
||||
}
|
||||
|
||||
async function handleActivateQualityOwner() {
|
||||
const status = qualityOwnerStatus.value
|
||||
if (!status || status.active) return
|
||||
try {
|
||||
await showDialog({
|
||||
title: '开通优质号主',
|
||||
message: `确认从可用余额中扣除 ¥${formatCent(
|
||||
status.required_deposit_cent
|
||||
)} 作为保证金?取消资格时会退回余额。`,
|
||||
showCancelButton: true,
|
||||
confirmButtonText: '确认缴纳',
|
||||
cancelButtonText: '取消',
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
qualityOwnerLoading.value = true
|
||||
try {
|
||||
qualityOwnerStatus.value = await activateQualityOwner()
|
||||
await session.loadMe()
|
||||
await loadBalance()
|
||||
showToast({ message: '优质号主已开通', icon: 'passed' })
|
||||
} catch (error) {
|
||||
showToast({ message: readError(error, '开通失败,请确认余额充足'), icon: 'cross' })
|
||||
} finally {
|
||||
qualityOwnerLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleOnlineQualityOwnerPayment() {
|
||||
const payWay = await qualityOwnerPayWaySelect.select()
|
||||
if (!payWay) return
|
||||
qualityOwnerOnlineLoading.value = true
|
||||
try {
|
||||
const payment = await startQualityOwnerPayment(payWay as PaymentPayWay)
|
||||
if (payment.paid) {
|
||||
await handleQualityOwnerPaid()
|
||||
return
|
||||
}
|
||||
await qualityOwnerCashier.openMobilePaymentCashier(payment, handleQualityOwnerPaid)
|
||||
} catch (error) {
|
||||
showToast({ message: readError(error, '发起在线支付失败'), icon: 'cross' })
|
||||
} finally {
|
||||
qualityOwnerOnlineLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleQualityOwnerPaid() {
|
||||
qualityOwnerStatus.value = await fetchQualityOwnerStatus()
|
||||
await session.loadMe()
|
||||
await loadBalance()
|
||||
}
|
||||
|
||||
async function loadBalance() {
|
||||
try {
|
||||
const wallet = await fetchWalletBalance()
|
||||
@@ -366,6 +456,9 @@ function resolveAvatarURL(url: string | undefined | null) {
|
||||
>
|
||||
未实名
|
||||
</van-tag>
|
||||
<van-tag v-if="qualityOwnerStatus?.active" round class="owner-quality-tag">
|
||||
<van-icon name="award-o" /> 优质号主
|
||||
</van-tag>
|
||||
</div>
|
||||
<p class="user-meta">ID: {{ displayId }} · {{ maskedPhone }}</p>
|
||||
</div>
|
||||
@@ -393,6 +486,65 @@ function resolveAvatarURL(url: string | undefined | null) {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="quality-owner-card" :class="{ 'is-active': qualityOwnerStatus?.active }">
|
||||
<div class="quality-owner-heading">
|
||||
<div>
|
||||
<span>优质号主</span>
|
||||
<strong>{{ qualityOwnerStatus?.active ? '已开通' : '解锁极品专区发布权限' }}</strong>
|
||||
</div>
|
||||
<van-tag :type="qualityOwnerStatus?.active ? 'success' : 'default'" round>
|
||||
{{ qualityOwnerStatus?.active ? '优质号主' : '未开通' }}
|
||||
</van-tag>
|
||||
</div>
|
||||
<div class="quality-owner-info">
|
||||
<div>
|
||||
<span>保证金</span>
|
||||
<strong>¥{{ formatCent(qualityOwnerStatus?.required_deposit_cent || 20000) }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>极品专区保险费率</span>
|
||||
<strong
|
||||
>{{
|
||||
((qualityOwnerStatus?.premium_insurance_fee_bps || 400) / 100).toFixed(1)
|
||||
}}%</strong
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!qualityOwnerStatus?.active" class="quality-owner-actions">
|
||||
<van-button
|
||||
block
|
||||
round
|
||||
type="primary"
|
||||
color="#7c3aed"
|
||||
:loading="qualityOwnerLoading"
|
||||
:disabled="
|
||||
(qualityOwnerStatus?.available_balance_cent || 0) <
|
||||
(qualityOwnerStatus?.required_deposit_cent || 20000)
|
||||
"
|
||||
@click="handleActivateQualityOwner"
|
||||
>
|
||||
余额支付开通
|
||||
</van-button>
|
||||
<van-button
|
||||
block
|
||||
round
|
||||
plain
|
||||
type="primary"
|
||||
:loading="qualityOwnerOnlineLoading"
|
||||
@click="handleOnlineQualityOwnerPayment"
|
||||
>
|
||||
微信 / 支付宝支付开通
|
||||
</van-button>
|
||||
</div>
|
||||
<p v-else>
|
||||
{{
|
||||
qualityOwnerStatus?.deposit_source === 'manual'
|
||||
? '已由后台开通优质号主。'
|
||||
: '保证金已冻结,取消优质号主资格时退回可用余额。'
|
||||
}}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- 余额卡片 - 银行卡质感暗色卡片 -->
|
||||
<div class="balance-card">
|
||||
<div class="balance-row">
|
||||
@@ -496,6 +648,22 @@ function resolveAvatarURL(url: string | undefined | null) {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<MobilePayWaySelectPopup
|
||||
v-model:show="qualityOwnerPayWaySelect.visible.value"
|
||||
@choose="qualityOwnerPayWaySelect.choose"
|
||||
@closed="qualityOwnerPayWaySelect.handleClosed"
|
||||
/>
|
||||
|
||||
<MobilePaymentCashierPopup
|
||||
v-model:show="qualityOwnerPaymentPopupVisible"
|
||||
:payment="activeQualityOwnerPayment"
|
||||
:pay-url="qualityOwnerPaymentPayURL"
|
||||
:qr-code-url="qualityOwnerPaymentQRCodeURL"
|
||||
:qr-generating="qualityOwnerPaymentQRGenerating"
|
||||
:checking="qualityOwnerPaymentChecking"
|
||||
@refresh="qualityOwnerCashier.refreshPaymentStatus(false)"
|
||||
/>
|
||||
|
||||
<!-- 账单明细 Popup -->
|
||||
<van-popup
|
||||
v-model:show="showLedgers"
|
||||
|
||||
@@ -4,13 +4,16 @@ import {
|
||||
Camera,
|
||||
CircleCheckFilled,
|
||||
EditPen,
|
||||
Loading,
|
||||
Lock,
|
||||
Picture,
|
||||
Postcard,
|
||||
RefreshRight,
|
||||
Trophy,
|
||||
User,
|
||||
WarningFilled,
|
||||
} from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
@@ -18,10 +21,25 @@ import { uploadFile } from '@/shared/api/files'
|
||||
import { useSessionStore } from '@/stores/session'
|
||||
import { realnameStatusLabel } from '@/shared/utils/statusLabels'
|
||||
import { setPassword } from '@/features/auth/api/auth'
|
||||
import {
|
||||
activateQualityOwner,
|
||||
fetchQualityOwnerStatus,
|
||||
queryQualityOwnerPayment,
|
||||
startQualityOwnerPayment,
|
||||
type QualityOwnerStatus,
|
||||
} from '@/features/quality-owner/api/qualityOwner'
|
||||
import { formatCent } from '@/shared/utils/money'
|
||||
import type { PaymentPayWay } from '@/features/orders/api/orders'
|
||||
import { useOrderPaymentCashier } from '@/features/orders/composables/useOrderPaymentCashier'
|
||||
import PayWaySelectDialog from '@/features/orders/components/PayWaySelectDialog.vue'
|
||||
|
||||
const session = useSessionStore()
|
||||
const router = useRouter()
|
||||
const saving = ref(false)
|
||||
const qualityOwnerLoading = ref(false)
|
||||
const qualityOwnerOnlineLoading = ref(false)
|
||||
const qualityOwnerPayWayVisible = ref(false)
|
||||
const qualityOwnerStatus = ref<QualityOwnerStatus | null>(null)
|
||||
const uploadingAvatar = ref(false)
|
||||
const avatarFileInput = ref<HTMLInputElement | null>(null)
|
||||
const form = reactive({
|
||||
@@ -36,6 +54,20 @@ const passwordForm = reactive({
|
||||
confirmPassword: '',
|
||||
})
|
||||
|
||||
const qualityOwnerCashier = useOrderPaymentCashier({
|
||||
notifySuccess: message => ElMessage.success(message),
|
||||
notifyError: message => ElMessage.error(message),
|
||||
notifyInfo: message => ElMessage.info(message),
|
||||
notifyFallback: message => ElMessage.warning(message),
|
||||
paidMessage: '保证金支付成功,优质号主已开通',
|
||||
queryPayment: () => queryQualityOwnerPayment(),
|
||||
})
|
||||
const qualityOwnerPaymentVisible = qualityOwnerCashier.paymentPopupVisible
|
||||
const activeQualityOwnerPayment = qualityOwnerCashier.activePayment
|
||||
const qualityOwnerPaymentQRCodeURL = qualityOwnerCashier.paymentQRCodeURL
|
||||
const qualityOwnerQrGenerating = qualityOwnerCashier.qrGenerating
|
||||
const qualityOwnerCheckingPayment = qualityOwnerCashier.checkingPayment
|
||||
|
||||
const displayName = computed(() => session.displayName)
|
||||
const maskedPhone = computed(() => {
|
||||
if (!session.phone) return '未绑定手机号'
|
||||
@@ -56,8 +88,72 @@ onMounted(async () => {
|
||||
// 保留本地会话展示,避免资料页因网络短暂异常整页不可用。
|
||||
}
|
||||
resetForm()
|
||||
void loadQualityOwnerStatus()
|
||||
})
|
||||
|
||||
async function loadQualityOwnerStatus() {
|
||||
try {
|
||||
qualityOwnerStatus.value = await fetchQualityOwnerStatus()
|
||||
} catch {
|
||||
qualityOwnerStatus.value = null
|
||||
}
|
||||
}
|
||||
|
||||
async function handleActivateQualityOwner() {
|
||||
const status = qualityOwnerStatus.value
|
||||
if (!status || status.active) return
|
||||
const deposit = formatCent(status.required_deposit_cent)
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确认从可用余额中扣除 ¥${deposit} 作为优质号主保证金?保证金会冻结,取消优质号主资格时退回。`,
|
||||
'开通优质号主',
|
||||
{
|
||||
type: 'warning',
|
||||
confirmButtonText: '确认缴纳',
|
||||
cancelButtonText: '取消',
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
qualityOwnerLoading.value = true
|
||||
try {
|
||||
qualityOwnerStatus.value = await activateQualityOwner()
|
||||
await session.loadMe()
|
||||
ElMessage.success('优质号主已开通,可在发布时选择极品专区')
|
||||
} catch (error) {
|
||||
ElMessage.error(readError(error, '开通失败,请确认余额充足'))
|
||||
} finally {
|
||||
qualityOwnerLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openQualityOwnerOnlinePayment() {
|
||||
qualityOwnerPayWayVisible.value = true
|
||||
}
|
||||
|
||||
async function chooseQualityOwnerPayWay(payWay: PaymentPayWay) {
|
||||
qualityOwnerPayWayVisible.value = false
|
||||
qualityOwnerOnlineLoading.value = true
|
||||
try {
|
||||
const payment = await startQualityOwnerPayment(payWay)
|
||||
if (payment.paid) {
|
||||
await handleQualityOwnerPaymentSucceeded()
|
||||
return
|
||||
}
|
||||
await qualityOwnerCashier.openPaymentCashier(payment, handleQualityOwnerPaymentSucceeded)
|
||||
} catch (error) {
|
||||
ElMessage.error(readError(error, '发起在线支付失败'))
|
||||
} finally {
|
||||
qualityOwnerOnlineLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleQualityOwnerPaymentSucceeded() {
|
||||
qualityOwnerStatus.value = await fetchQualityOwnerStatus()
|
||||
await session.loadMe()
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
form.nickname = session.nickname || session.displayName
|
||||
form.avatar_url = session.avatarUrl || ''
|
||||
@@ -153,14 +249,20 @@ async function savePassword() {
|
||||
|
||||
<template>
|
||||
<section class="profile-page">
|
||||
<div class="profile-hero">
|
||||
<div class="profile-hero" :class="{ 'is-quality-owner': qualityOwnerStatus?.active }">
|
||||
<div class="hero-avatar">
|
||||
<img v-if="session.avatarUrl" :src="resolveAvatarURL(session.avatarUrl)" alt="" />
|
||||
<span v-else>{{ session.avatarText }}</span>
|
||||
</div>
|
||||
<div class="hero-copy">
|
||||
<span class="eyebrow">Profile</span>
|
||||
<h1>个人资料</h1>
|
||||
<div class="hero-title-row">
|
||||
<h1>个人资料</h1>
|
||||
<span v-if="qualityOwnerStatus?.active" class="hero-quality-chip">
|
||||
<el-icon><Trophy /></el-icon>
|
||||
优质号主
|
||||
</span>
|
||||
</div>
|
||||
<p>{{ displayName }} · {{ maskedPhone }}</p>
|
||||
</div>
|
||||
<button
|
||||
@@ -176,6 +278,24 @@ async function savePassword() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<section v-if="qualityOwnerStatus?.active" class="quality-owner-spotlight">
|
||||
<div class="quality-owner-spotlight-mark">
|
||||
<el-icon :size="25"><Trophy /></el-icon>
|
||||
</div>
|
||||
<div class="quality-owner-spotlight-copy">
|
||||
<span>PREMIUM OWNER</span>
|
||||
<strong>优质号主身份已点亮</strong>
|
||||
<p>
|
||||
你的账号将优先展示优质号主标识,并可发布至首页极品专区,享受更高曝光与 4% 保险专享费率。
|
||||
</p>
|
||||
</div>
|
||||
<div class="quality-owner-spotlight-benefits">
|
||||
<span>极致曝光</span>
|
||||
<span>身份认证</span>
|
||||
<span>专享费率</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="profile-layout">
|
||||
<section class="edit-panel">
|
||||
<div class="panel-head">
|
||||
@@ -280,6 +400,104 @@ async function savePassword() {
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<section class="quality-owner-card" :class="{ 'is-active': qualityOwnerStatus?.active }">
|
||||
<div class="quality-owner-heading">
|
||||
<div>
|
||||
<span class="card-kicker">Premium Owner</span>
|
||||
<h2>优质号主</h2>
|
||||
<p>
|
||||
开通后可发布到极品专区;在极品专区购买无忧租号保险可享
|
||||
{{ ((qualityOwnerStatus?.premium_insurance_fee_bps || 400) / 100).toFixed(1) }}%
|
||||
专享费率。
|
||||
</p>
|
||||
</div>
|
||||
<span class="quality-owner-status">
|
||||
{{ qualityOwnerStatus?.active ? '已开通' : '未开通' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="quality-owner-metrics">
|
||||
<div>
|
||||
<span>保证金</span>
|
||||
<strong>¥{{ formatCent(qualityOwnerStatus?.required_deposit_cent || 20000) }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>普通保险服务费</span>
|
||||
<strong>{{ ((qualityOwnerStatus?.insurance_fee_bps || 800) / 100).toFixed(1) }}%</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>极品专区保险服务费</span>
|
||||
<strong
|
||||
>{{
|
||||
((qualityOwnerStatus?.premium_insurance_fee_bps || 400) / 100).toFixed(1)
|
||||
}}%</strong
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<span>当前可用余额</span>
|
||||
<strong>¥{{ formatCent(qualityOwnerStatus?.available_balance_cent || 0) }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="quality-owner-actions">
|
||||
<span v-if="qualityOwnerStatus?.active">
|
||||
<template v-if="qualityOwnerStatus.deposit_source === 'manual'">已由后台开通</template>
|
||||
<template v-else>保证金已冻结</template>
|
||||
</span>
|
||||
<span v-else>保证金取消资格时原路退回余额。</span>
|
||||
<div v-if="!qualityOwnerStatus?.active" class="quality-owner-action-buttons">
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="qualityOwnerLoading"
|
||||
:disabled="
|
||||
(qualityOwnerStatus?.available_balance_cent || 0) <
|
||||
(qualityOwnerStatus?.required_deposit_cent || 20000)
|
||||
"
|
||||
@click="handleActivateQualityOwner"
|
||||
>
|
||||
余额支付开通
|
||||
</el-button>
|
||||
<el-button :loading="qualityOwnerOnlineLoading" @click="openQualityOwnerOnlinePayment">
|
||||
微信 / 支付宝支付开通
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<PayWaySelectDialog v-model="qualityOwnerPayWayVisible" @choose="chooseQualityOwnerPayWay" />
|
||||
|
||||
<el-dialog
|
||||
v-model="qualityOwnerPaymentVisible"
|
||||
title="优质号主保证金支付"
|
||||
width="460px"
|
||||
append-to-body
|
||||
class="quality-owner-pay-dialog"
|
||||
>
|
||||
<div v-if="activeQualityOwnerPayment" class="quality-owner-pay-dialog-body">
|
||||
<div class="quality-owner-pay-amount">
|
||||
<span>支付金额</span>
|
||||
<strong>¥{{ formatCent(activeQualityOwnerPayment.amount_cent) }}</strong>
|
||||
</div>
|
||||
<div class="quality-owner-pay-qr">
|
||||
<el-icon v-if="qualityOwnerQrGenerating" class="is-loading" :size="30">
|
||||
<Loading />
|
||||
</el-icon>
|
||||
<img
|
||||
v-else-if="qualityOwnerPaymentQRCodeURL"
|
||||
:src="qualityOwnerPaymentQRCodeURL"
|
||||
alt="保证金支付二维码"
|
||||
/>
|
||||
<el-icon v-else :size="42"><Picture /></el-icon>
|
||||
</div>
|
||||
<p>请使用{{ activeQualityOwnerPayment.pay_way === 'WXZF' ? '微信' : '支付宝' }}扫码支付</p>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="qualityOwnerCheckingPayment"
|
||||
@click="qualityOwnerCashier.refreshPaymentStatus(false)"
|
||||
>
|
||||
我已支付,刷新状态
|
||||
</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="showPasswordDialog"
|
||||
title="修改密码"
|
||||
@@ -345,6 +563,19 @@ async function savePassword() {
|
||||
box-shadow: 0 16px 36px rgba(23, 35, 61, 0.08);
|
||||
}
|
||||
|
||||
.profile-hero.is-quality-owner {
|
||||
border-color: #d8c9fb;
|
||||
background:
|
||||
radial-gradient(circle at 86% 0%, rgba(245, 158, 11, 0.15), transparent 34%),
|
||||
linear-gradient(135deg, #f3edff 0%, #ffffff 58%, #fff8e8 100%);
|
||||
box-shadow: 0 16px 42px rgba(109, 40, 217, 0.12);
|
||||
}
|
||||
|
||||
.profile-hero.is-quality-owner .hero-avatar {
|
||||
background: linear-gradient(135deg, #5b21b6 0%, #9333ea 62%, #f59e0b 100%);
|
||||
box-shadow: 0 8px 20px rgba(109, 40, 217, 0.22);
|
||||
}
|
||||
|
||||
.hero-avatar,
|
||||
.avatar-preview {
|
||||
display: grid;
|
||||
@@ -389,6 +620,27 @@ async function savePassword() {
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.hero-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.hero-quality-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
min-height: 26px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid #d8b4fe;
|
||||
border-radius: 999px;
|
||||
background: #f5f3ff;
|
||||
color: #6d28d9;
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.hero-copy p {
|
||||
margin: 10px 0 0;
|
||||
color: #64748b;
|
||||
@@ -433,6 +685,69 @@ async function savePassword() {
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.quality-owner-spotlight {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-top: 18px;
|
||||
padding: 17px 20px;
|
||||
border: 1px solid #e1d7fa;
|
||||
border-radius: 14px;
|
||||
background: linear-gradient(110deg, #faf7ff 0%, #ffffff 62%, #fffaf0 100%);
|
||||
}
|
||||
|
||||
.quality-owner-spotlight-mark {
|
||||
display: grid;
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
place-items: center;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(135deg, #5b21b6 0%, #9333ea 65%, #f59e0b 100%);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.quality-owner-spotlight-copy {
|
||||
display: grid;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.quality-owner-spotlight-copy > span {
|
||||
color: #7c3aed;
|
||||
font-size: 10px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
.quality-owner-spotlight-copy strong {
|
||||
color: #2e1065;
|
||||
font-size: 17px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.quality-owner-spotlight-copy p {
|
||||
margin: 0;
|
||||
color: #70677f;
|
||||
font-size: 12px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.quality-owner-spotlight-benefits {
|
||||
display: flex;
|
||||
gap: 7px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.quality-owner-spotlight-benefits span {
|
||||
padding: 5px 9px;
|
||||
border-radius: 999px;
|
||||
background: #f5f3ff;
|
||||
color: #6d28d9;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.profile-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 360px;
|
||||
@@ -647,9 +962,178 @@ async function savePassword() {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.quality-owner-card {
|
||||
margin-top: 22px;
|
||||
padding: 26px 28px;
|
||||
border: 1px solid #e8e4f4;
|
||||
border-radius: 16px;
|
||||
background: linear-gradient(135deg, #faf8ff 0%, #ffffff 68%);
|
||||
box-shadow: 0 10px 28px rgba(76, 29, 149, 0.06);
|
||||
}
|
||||
|
||||
.quality-owner-card.is-active {
|
||||
border-color: #c4b5fd;
|
||||
}
|
||||
|
||||
.quality-owner-heading,
|
||||
.quality-owner-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.quality-owner-heading h2 {
|
||||
margin: 6px 0 0;
|
||||
color: #312e81;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.quality-owner-heading p {
|
||||
max-width: 680px;
|
||||
margin: 8px 0 0;
|
||||
color: #6b7280;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.quality-owner-status {
|
||||
flex-shrink: 0;
|
||||
padding: 6px 12px;
|
||||
border-radius: 999px;
|
||||
background: #f1f5f9;
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.quality-owner-card.is-active .quality-owner-status {
|
||||
background: #ede9fe;
|
||||
color: #6d28d9;
|
||||
}
|
||||
|
||||
.quality-owner-metrics {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.quality-owner-metrics div {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
padding: 14px;
|
||||
border: 1px solid #ede9fe;
|
||||
border-radius: 10px;
|
||||
background: rgba(255, 255, 255, 0.82);
|
||||
}
|
||||
|
||||
.quality-owner-metrics span,
|
||||
.quality-owner-actions span {
|
||||
color: #7b8492;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.quality-owner-metrics strong {
|
||||
color: #312e81;
|
||||
font-size: 18px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.quality-owner-actions {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.quality-owner-actions :deep(.el-button--primary) {
|
||||
border: none;
|
||||
background: #7c3aed;
|
||||
}
|
||||
|
||||
.quality-owner-action-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.quality-owner-action-buttons :deep(.el-button) {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.quality-owner-pay-dialog-body {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
.quality-owner-pay-amount {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 10px;
|
||||
color: #64748b;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.quality-owner-pay-amount strong {
|
||||
color: #f97316;
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.quality-owner-pay-qr {
|
||||
display: grid;
|
||||
width: 228px;
|
||||
height: 228px;
|
||||
place-items: center;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.quality-owner-pay-qr img {
|
||||
width: 214px;
|
||||
height: 214px;
|
||||
}
|
||||
|
||||
.quality-owner-pay-dialog-body p {
|
||||
margin: 0;
|
||||
color: #475569;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.profile-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.quality-owner-metrics {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.quality-owner-heading,
|
||||
.quality-owner-actions {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.quality-owner-action-buttons {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.quality-owner-metrics {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.quality-owner-spotlight {
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.quality-owner-spotlight-benefits {
|
||||
grid-column: 1 / -1;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user