增加发布协议确认配置
This commit is contained in:
@@ -3,11 +3,14 @@ import { useRouter } from 'vue-router'
|
||||
|
||||
import { fetchFileBlobByURL, uploadFile } from '@/shared/api/files'
|
||||
import {
|
||||
emptyListingPublishAgreements,
|
||||
emptyListingPublishOptions,
|
||||
emptyListingSalePriceConfig,
|
||||
fetchListingPublishAgreements,
|
||||
fetchListingPublishOptions,
|
||||
fetchListingSalePriceConfig,
|
||||
type ChargeMode,
|
||||
type ListingPublishAgreements,
|
||||
type ListingPublishOptions,
|
||||
type PublishSalePriceConfig,
|
||||
type ScreenshotKey,
|
||||
@@ -45,6 +48,9 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
||||
const draftReady = ref(false)
|
||||
const publishOptions = ref<ListingPublishOptions>(emptyListingPublishOptions)
|
||||
const salePriceConfig = ref<PublishSalePriceConfig>(emptyListingSalePriceConfig)
|
||||
const publishAgreements = ref<ListingPublishAgreements>(emptyListingPublishAgreements)
|
||||
const virtualAssetSaleAgreementChecked = ref(false)
|
||||
const sellerAgreementChecked = ref(false)
|
||||
const fileInput = ref<HTMLInputElement | null>(null)
|
||||
const activeUploadKey = ref<ScreenshotKey>('coin')
|
||||
const form = reactive<PublishForm>(defaultPublishForm())
|
||||
@@ -66,6 +72,9 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
||||
})
|
||||
const uploadedScreenshotCount = computed(() => pricing.screenshotUrls.value.length)
|
||||
const requiredScreenshotCount = ref(0)
|
||||
const canPublishAfterAgreements = computed(
|
||||
() => virtualAssetSaleAgreementChecked.value && sellerAgreementChecked.value,
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
restoreDraft()
|
||||
@@ -114,12 +123,14 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
||||
|
||||
async function loadPublishOptions() {
|
||||
try {
|
||||
const [nextPublishOptions, nextSalePriceConfig] = await Promise.all([
|
||||
const [nextPublishOptions, nextSalePriceConfig, nextPublishAgreements] = await Promise.all([
|
||||
fetchListingPublishOptions(),
|
||||
fetchListingSalePriceConfig(),
|
||||
fetchListingPublishAgreements(),
|
||||
])
|
||||
publishOptions.value = nextPublishOptions
|
||||
salePriceConfig.value = nextSalePriceConfig
|
||||
publishAgreements.value = nextPublishAgreements
|
||||
|
||||
// 默认数字都是0
|
||||
for (const item of nextPublishOptions.quantity_items) {
|
||||
@@ -130,6 +141,7 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
||||
} catch {
|
||||
publishOptions.value = emptyListingPublishOptions
|
||||
salePriceConfig.value = emptyListingSalePriceConfig
|
||||
publishAgreements.value = emptyListingPublishAgreements
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,6 +217,8 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
||||
clearRecord(screenshotFiles)
|
||||
revokeAllScreenshotPreviews()
|
||||
selectedSkins.value = []
|
||||
virtualAssetSaleAgreementChecked.value = false
|
||||
sellerAgreementChecked.value = false
|
||||
activeUploadKey.value = 'coin'
|
||||
|
||||
// Also re-initialize quantityValues to 0
|
||||
@@ -397,6 +411,8 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
||||
screenshot_urls: pricing.screenshotUrls.value,
|
||||
price: pricing.calculatedFinalPrice.value,
|
||||
deposit_amount: Number(form.deposit_amount),
|
||||
agreed_virtual_asset_sale: virtualAssetSaleAgreementChecked.value,
|
||||
agreed_seller_agreement: sellerAgreementChecked.value,
|
||||
})
|
||||
removePublishDraft(options.draftKey)
|
||||
suppressDraftSave.value = true
|
||||
@@ -445,6 +461,7 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
||||
}
|
||||
if (!pricing.calculatedFinalPrice.value) return '请完善币数、保险、体力和负重后再发布'
|
||||
if (!Number.isFinite(pricing.calculatedFinalPrice.value)) return '发布价格计算异常,请检查填写内容'
|
||||
if (!canPublishAfterAgreements.value) return '请先阅读并勾选两份发布协议'
|
||||
for (const item of pricing.screenshotSlots.value) {
|
||||
if (isScreenshotRequired(item) && !screenshotFiles[item.key]) return `请上传${item.label}`
|
||||
}
|
||||
@@ -530,6 +547,10 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
||||
router,
|
||||
loading,
|
||||
uploading,
|
||||
publishAgreements,
|
||||
virtualAssetSaleAgreementChecked,
|
||||
sellerAgreementChecked,
|
||||
canPublishAfterAgreements,
|
||||
fileInput,
|
||||
activeUploadKey,
|
||||
form,
|
||||
|
||||
@@ -644,6 +644,64 @@
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.publish-agreement-card {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
margin-bottom: 12px;
|
||||
padding: 12px;
|
||||
border: 1px solid #edf0f4;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.agreement-check-label {
|
||||
color: #30343a;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.agreement-link {
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #1477ff;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.publish-agreement-popup {
|
||||
max-height: 86vh;
|
||||
}
|
||||
|
||||
.agreement-popup-body {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
max-height: 86vh;
|
||||
overflow: auto;
|
||||
padding: 20px 16px calc(16px + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.agreement-popup-body h3 {
|
||||
margin: 0;
|
||||
color: #17233d;
|
||||
font-size: 18px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.agreement-popup-content {
|
||||
max-height: 62vh;
|
||||
overflow: auto;
|
||||
padding: 12px;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
background: #f8fafc;
|
||||
color: #1f2937;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
line-height: 1.7;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.publish-actions {
|
||||
display: grid;
|
||||
grid-template-columns: 92px minmax(0, 1fr);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { showDialog, showToast } from 'vant'
|
||||
import { computed, ref } from 'vue'
|
||||
import MobileBottomNav from '@/components/MobileBottomNav.vue'
|
||||
|
||||
import { usePublishForm } from '@/features/seller/composables/usePublishForm'
|
||||
import type { AgreementContent } from '@/features/listings/api/listingOptions'
|
||||
|
||||
const {
|
||||
commonOnlineTimes,
|
||||
@@ -11,6 +13,10 @@ const {
|
||||
router,
|
||||
loading,
|
||||
uploading,
|
||||
publishAgreements,
|
||||
virtualAssetSaleAgreementChecked,
|
||||
sellerAgreementChecked,
|
||||
canPublishAfterAgreements,
|
||||
fileInput,
|
||||
activeUploadKey,
|
||||
form,
|
||||
@@ -78,7 +84,15 @@ const {
|
||||
notifyError: (message) => showToast({ message, icon: 'cross' }),
|
||||
})
|
||||
|
||||
const agreementPopupVisible = ref(false)
|
||||
const activeAgreement = ref<AgreementContent | null>(null)
|
||||
const activeAgreementTitle = computed(() => activeAgreement.value?.title || '协议内容')
|
||||
const activeAgreementContent = computed(() => activeAgreement.value?.content || '')
|
||||
|
||||
function openPublishAgreement(agreement: AgreementContent) {
|
||||
activeAgreement.value = agreement
|
||||
agreementPopupVisible.value = true
|
||||
}
|
||||
|
||||
function selectRadio<T extends string>(value: T, setter: (value: T) => void) {
|
||||
setter(value)
|
||||
@@ -637,6 +651,33 @@ function selectOnlineEnd(value: string) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="publish-agreement-card">
|
||||
<van-checkbox v-model="virtualAssetSaleAgreementChecked" icon-size="18px">
|
||||
<span class="agreement-check-label">
|
||||
我已阅读并同意
|
||||
<button
|
||||
type="button"
|
||||
class="agreement-link"
|
||||
@click.stop.prevent="openPublishAgreement(publishAgreements.virtual_asset_sale)"
|
||||
>
|
||||
《{{ publishAgreements.virtual_asset_sale.title }}》
|
||||
</button>
|
||||
</span>
|
||||
</van-checkbox>
|
||||
<van-checkbox v-model="sellerAgreementChecked" icon-size="18px">
|
||||
<span class="agreement-check-label">
|
||||
我已阅读并同意
|
||||
<button
|
||||
type="button"
|
||||
class="agreement-link"
|
||||
@click.stop.prevent="openPublishAgreement(publishAgreements.seller_agreement)"
|
||||
>
|
||||
《{{ publishAgreements.seller_agreement.title }}》
|
||||
</button>
|
||||
</span>
|
||||
</van-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="publish-actions">
|
||||
<van-button
|
||||
round
|
||||
@@ -651,6 +692,7 @@ function selectOnlineEnd(value: string) {
|
||||
round
|
||||
class="submit-btn"
|
||||
:loading="loading"
|
||||
:disabled="!canPublishAfterAgreements"
|
||||
loading-text="发布中..."
|
||||
@click="handleSubmit"
|
||||
>
|
||||
@@ -659,6 +701,13 @@ function selectOnlineEnd(value: string) {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<van-popup v-model:show="agreementPopupVisible" round closeable position="bottom" lock-scroll class="publish-agreement-popup">
|
||||
<div class="agreement-popup-body">
|
||||
<h3>{{ activeAgreementTitle }}</h3>
|
||||
<div class="agreement-popup-content">{{ activeAgreementContent }}</div>
|
||||
</div>
|
||||
</van-popup>
|
||||
|
||||
<MobileBottomNav />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
@@ -679,14 +679,11 @@
|
||||
position: sticky;
|
||||
top: var(--summary-sticky-top);
|
||||
align-self: start;
|
||||
max-height: calc(100vh - var(--summary-sticky-top) - 24px);
|
||||
}
|
||||
|
||||
.summary-panel {
|
||||
padding: 16px;
|
||||
max-height: inherit;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
padding: 14px;
|
||||
overflow: visible;
|
||||
border: 1px solid #e6ebf2;
|
||||
border-radius: var(--radius-8);
|
||||
background: #fff;
|
||||
@@ -710,9 +707,9 @@
|
||||
|
||||
.summary-breakdown {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
margin-top: 14px;
|
||||
padding: 12px;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
padding: 10px;
|
||||
border: 1px solid #ffe1c7;
|
||||
border-radius: var(--radius-8);
|
||||
background: #fffaf6;
|
||||
@@ -745,15 +742,15 @@
|
||||
|
||||
.summary-list {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
margin: 14px 0;
|
||||
gap: 8px;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.summary-list div {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding-bottom: 10px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #eef2f6;
|
||||
}
|
||||
|
||||
@@ -764,6 +761,76 @@
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.publish-agreement-panel {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
margin-bottom: 12px;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid #eef2f6;
|
||||
border-radius: var(--radius-8);
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.publish-agreement-panel :deep(.el-checkbox) {
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
margin-right: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.publish-agreement-panel :deep(.el-checkbox__label) {
|
||||
min-width: 0;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.agreement-check-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
color: var(--color-text-main);
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.agreement-link {
|
||||
display: inline-block;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #1477ff;
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.agreement-link:hover {
|
||||
color: #0f5fd0;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.agreement-dialog-content {
|
||||
max-height: 62vh;
|
||||
overflow: auto;
|
||||
padding: 14px;
|
||||
border: 1px solid #d8dee9;
|
||||
border-radius: var(--radius-8);
|
||||
background: #f8fafc;
|
||||
color: #1f2937;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
line-height: 1.75;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.summary-actions {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { Delete, DocumentChecked, Picture, RefreshRight, UploadFilled } from '@element-plus/icons-vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { usePublishForm } from '@/features/seller/composables/usePublishForm'
|
||||
import type { AgreementContent } from '@/features/listings/api/listingOptions'
|
||||
import OptionChips from './components/OptionChips.vue'
|
||||
import PublishSection from './components/PublishSection.vue'
|
||||
|
||||
@@ -12,6 +14,10 @@ const {
|
||||
formatNumber,
|
||||
loading,
|
||||
uploading,
|
||||
publishAgreements,
|
||||
virtualAssetSaleAgreementChecked,
|
||||
sellerAgreementChecked,
|
||||
canPublishAfterAgreements,
|
||||
fileInput,
|
||||
activeUploadKey,
|
||||
form,
|
||||
@@ -83,10 +89,20 @@ const {
|
||||
notifyError: (message) => ElMessage.error(message),
|
||||
})
|
||||
|
||||
const agreementDialogVisible = ref(false)
|
||||
const activeAgreement = ref<AgreementContent | null>(null)
|
||||
const activeAgreementTitle = computed(() => activeAgreement.value?.title || '协议内容')
|
||||
const activeAgreementContent = computed(() => activeAgreement.value?.content || '')
|
||||
|
||||
function toggleSkinOption(value: string | number) {
|
||||
toggleSkin(String(value))
|
||||
}
|
||||
|
||||
function openPublishAgreement(agreement: AgreementContent) {
|
||||
activeAgreement.value = agreement
|
||||
agreementDialogVisible.value = true
|
||||
}
|
||||
|
||||
function selectDailyLoss(value: string | number) {
|
||||
form.daily_loss_m = Number(value)
|
||||
}
|
||||
@@ -510,14 +526,55 @@ function selectOnlineEnd(value: string | number) {
|
||||
<strong>{{ uploadedScreenshotCount }}/{{ requiredScreenshotCount }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="publish-agreement-panel">
|
||||
<el-checkbox v-model="virtualAssetSaleAgreementChecked">
|
||||
<span class="agreement-check-label">
|
||||
我已阅读并同意
|
||||
<button
|
||||
type="button"
|
||||
class="agreement-link"
|
||||
:title="publishAgreements.virtual_asset_sale.title"
|
||||
@click.prevent.stop="openPublishAgreement(publishAgreements.virtual_asset_sale)"
|
||||
>
|
||||
《{{ publishAgreements.virtual_asset_sale.title }}》
|
||||
</button>
|
||||
</span>
|
||||
</el-checkbox>
|
||||
<el-checkbox v-model="sellerAgreementChecked">
|
||||
<span class="agreement-check-label">
|
||||
我已阅读并同意
|
||||
<button
|
||||
type="button"
|
||||
class="agreement-link"
|
||||
:title="publishAgreements.seller_agreement.title"
|
||||
@click.prevent.stop="openPublishAgreement(publishAgreements.seller_agreement)"
|
||||
>
|
||||
《{{ publishAgreements.seller_agreement.title }}》
|
||||
</button>
|
||||
</span>
|
||||
</el-checkbox>
|
||||
</div>
|
||||
<div class="summary-actions">
|
||||
<el-button :icon="UploadFilled" class="btn-publish" type="primary" :loading="loading" @click="handleSubmit">立即发布</el-button>
|
||||
<el-button
|
||||
:icon="UploadFilled"
|
||||
class="btn-publish"
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
:disabled="!canPublishAfterAgreements"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
立即发布
|
||||
</el-button>
|
||||
<el-button :icon="DocumentChecked" :disabled="loading" @click="handleSaveDraft">保存草稿</el-button>
|
||||
<el-button :icon="RefreshRight" :disabled="loading" @click="handleResetDraft">重置草稿</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="agreementDialogVisible" :title="activeAgreementTitle" width="720px" class="publish-agreement-dialog">
|
||||
<div class="agreement-dialog-content">{{ activeAgreementContent }}</div>
|
||||
</el-dialog>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user