- 发起组局页 games 初始改为空,仅从 nav_categories(status=1) 加载;加载失败/为空显示重试提示,禁止提交空品类 - 合规商圈初始改为空,仅从 safety_places(status=正常营业) 加载;无数据时提示手动填写地点 - 活动列表品类导航移除默认 3 个兜底品类 - 117 套件/764 用例全绿
1887 行
51 KiB
Vue
1887 行
51 KiB
Vue
<template>
|
||
<view class="create-activity-container" :class="{ 'theme-warm': currentThemeKey === 'warm' }">
|
||
<!-- 发布类型切换:发起组局 / 平台派单 -->
|
||
<view class="publish-mode-wrap">
|
||
<view
|
||
class="publish-mode-item btn-active"
|
||
:class="{ active: publishMode === 'ACTIVITY' }"
|
||
@click="publishMode = 'ACTIVITY'"
|
||
>
|
||
<text class="publish-mode-title">发起组局</text>
|
||
<text class="publish-mode-desc">多人报名 · AA 或房主请客</text>
|
||
</view>
|
||
<view
|
||
class="publish-mode-item btn-active"
|
||
:class="{ active: publishMode === 'DISPATCH' }"
|
||
@click="publishMode = 'DISPATCH'"
|
||
>
|
||
<text class="publish-mode-title">平台派单</text>
|
||
<text class="publish-mode-desc">发布需求 · 附近大神抢单</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 模式选择 Tab (AA制 / 项目制) -->
|
||
<view class="mode-switch-wrap" v-if="publishMode === 'ACTIVITY'">
|
||
<view
|
||
class="mode-item btn-active"
|
||
:class="{ active: form.payType === 'AA' }"
|
||
@click="form.payType = 'AA'"
|
||
>
|
||
<image class="mode-svg" :src="ICONS.bolt" mode="aspectFit" />
|
||
<view class="mode-text-box">
|
||
<text class="mode-title">AA制拼桌</text>
|
||
<text class="mode-desc">报名人均平摊费用</text>
|
||
</view>
|
||
</view>
|
||
<view
|
||
class="mode-item btn-active"
|
||
:class="{ active: form.payType === 'PROJECT' }"
|
||
@click="form.payType = 'PROJECT'"
|
||
>
|
||
<image class="mode-svg" :src="ICONS.trophy" mode="aspectFit" />
|
||
<view class="mode-text-box">
|
||
<text class="mode-title">房主请客</text>
|
||
<text class="mode-desc">房主全额买单请客</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 品类选择 -->
|
||
<view class="form-card">
|
||
<view class="card-label">选择活动品类</view>
|
||
<view class="game-pills" v-if="games.length > 0">
|
||
<view
|
||
v-for="g in games"
|
||
:key="g"
|
||
class="game-pill btn-active"
|
||
:class="{ selected: form.game === g }"
|
||
@click="form.game = g"
|
||
>
|
||
{{ g }}
|
||
</view>
|
||
</view>
|
||
<text v-else class="config-empty-tip" @click="loadLivePlaces">
|
||
{{ configLoading ? '品类加载中…' : '暂无可选品类,点击重试' }}
|
||
</text>
|
||
</view>
|
||
|
||
<!-- 基础信息录入 -->
|
||
<view class="form-card">
|
||
<view class="input-row">
|
||
<text class="row-label">活动标题</text>
|
||
<input
|
||
class="row-input"
|
||
v-model="form.title"
|
||
placeholder="如:三里屯铜锅涮肉AA探店 / 工体纯K麦霸欢唱局"
|
||
placeholder-class="ph"
|
||
/>
|
||
</view>
|
||
|
||
<view class="input-row">
|
||
<text class="row-label">活动时段</text>
|
||
<picker
|
||
class="time-picker-flex"
|
||
mode="multiSelector"
|
||
:range="timePickerRange"
|
||
:value="timePickerIndex"
|
||
@change="onTimePickerChange"
|
||
>
|
||
<view class="picker-display-box btn-active">
|
||
<text class="picker-val" :class="{ placeholder: !form.startTime }">
|
||
{{ form.startTime || '请选择活动开始时段' }}
|
||
</text>
|
||
<view class="picker-right-tag">
|
||
<text class="picker-tag-text">选择时间</text>
|
||
<text class="picker-arrow">›</text>
|
||
</view>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<!-- 快捷时段点选胶囊 (告别纯手打易错) -->
|
||
<view class="quick-chip-row">
|
||
<text class="chip-label">快捷时段:</text>
|
||
<view class="quick-chips">
|
||
<view
|
||
v-for="timeItem in presetTimeOptions"
|
||
:key="timeItem"
|
||
class="time-chip btn-active"
|
||
:class="{ selected: form.startTime === timeItem }"
|
||
@click="selectQuickTime(timeItem)"
|
||
>
|
||
{{ timeItem }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="location-block">
|
||
<view class="location-header-row">
|
||
<text class="row-label">地点/商圈</text>
|
||
<view class="btn-choose-loc btn-active" @click="handleChooseLocation">
|
||
<image class="loc-pin-svg" :src="ICONS.mapPin" mode="aspectFit" />
|
||
<text class="loc-btn-text">地图选点</text>
|
||
<text class="loc-btn-arrow">›</text>
|
||
</view>
|
||
</view>
|
||
<view class="location-input-box">
|
||
<textarea
|
||
class="location-textarea"
|
||
auto-height
|
||
v-model="form.location"
|
||
maxlength="120"
|
||
placeholder="请输入详细活动地点,或点击上方地图选点 / 下方合规商圈"
|
||
placeholder-class="ph"
|
||
/>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 合规示范商圈一键点选 (防风控与合规保障) -->
|
||
<view class="quick-chip-row">
|
||
<text class="chip-label">合规商圈:</text>
|
||
<view class="quick-chips" v-if="recommendPublicZones.length > 0">
|
||
<view
|
||
v-for="zoneItem in recommendPublicZones.slice(0, 4)"
|
||
:key="zoneItem"
|
||
class="zone-chip-item btn-active"
|
||
:class="{ selected: form.location === zoneItem }"
|
||
@click="selectZone(zoneItem)"
|
||
>
|
||
{{ zoneItem.split(' ')[0] }}
|
||
</view>
|
||
</view>
|
||
<text v-else class="config-empty-tip" @click="loadLivePlaces">
|
||
{{ configLoading ? '商圈加载中…' : '暂无合规商圈,请手动填写地点(点击重试)' }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 席位与费用配置 -->
|
||
<view class="form-card" v-if="publishMode === 'ACTIVITY'">
|
||
<view class="stepper-row">
|
||
<view class="stepper-info">
|
||
<text class="row-label">招募席位 (人)</text>
|
||
<text class="sub-label">包含房主在内的组局总人数 ({{ minParticipants }}-{{ maxParticipants }}人)</text>
|
||
</view>
|
||
<view class="stepper-box">
|
||
<button class="step-btn btn-active" :disabled="form.maxParticipants <= minParticipants" @click="changeParticipants(-1)">-</button>
|
||
<text class="step-val">{{ form.maxParticipants }} 人</text>
|
||
<button class="step-btn btn-active" :disabled="form.maxParticipants >= maxParticipants" @click="changeParticipants(1)">+</button>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="input-row" v-if="form.payType === 'AA'">
|
||
<text class="row-label">人均分摊 (元/人)</text>
|
||
<input
|
||
class="row-input price-input"
|
||
type="digit"
|
||
v-model="form.aaFee"
|
||
placeholder="如:50"
|
||
placeholder-class="price-ph"
|
||
/>
|
||
</view>
|
||
|
||
<view v-else>
|
||
<!-- 房主请客 / 悬赏组队 出价模式切换 -->
|
||
<view class="pricing-mode-row">
|
||
<text class="row-label">出价方式</text>
|
||
<view class="pricing-mode-group">
|
||
<view
|
||
class="pricing-mode-btn"
|
||
:class="{ active: form.pricingMode === 'PER_PERSON' }"
|
||
@click="form.pricingMode = 'PER_PERSON'"
|
||
>
|
||
按人出价
|
||
</view>
|
||
<view
|
||
class="pricing-mode-btn"
|
||
:class="{ active: form.pricingMode === 'TOTAL' }"
|
||
@click="form.pricingMode = 'TOTAL'"
|
||
>
|
||
包场总预算
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="input-row" v-if="form.pricingMode === 'PER_PERSON'">
|
||
<text class="row-label">单人出价 (元/人)</text>
|
||
<input
|
||
class="row-input price-input"
|
||
type="digit"
|
||
v-model="form.perPersonFee"
|
||
placeholder="如:500"
|
||
placeholder-class="price-ph"
|
||
/>
|
||
</view>
|
||
|
||
<!-- 常用赏金快捷选项 (按人出价) -->
|
||
<view class="preset-price-row" v-if="form.pricingMode === 'PER_PERSON'">
|
||
<text class="preset-label">快捷赏金:</text>
|
||
<view class="preset-chips">
|
||
<view
|
||
v-for="amt in presetPerPersonFees"
|
||
:key="amt"
|
||
class="preset-chip btn-active"
|
||
:class="{ selected: Number(form.perPersonFee) === amt }"
|
||
@click="form.perPersonFee = amt"
|
||
>
|
||
¥{{ amt }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="input-row" v-else>
|
||
<text class="row-label">房主买单总预算 (元)</text>
|
||
<input
|
||
class="row-input price-input"
|
||
type="digit"
|
||
v-model="form.totalFee"
|
||
placeholder="如:2000"
|
||
placeholder-class="price-ph"
|
||
/>
|
||
</view>
|
||
|
||
<!-- 常用总预算快捷选项 (包场总预算) -->
|
||
<view class="preset-price-row" v-if="form.pricingMode === 'TOTAL'">
|
||
<text class="preset-label">快捷预算:</text>
|
||
<view class="preset-chips">
|
||
<view
|
||
v-for="amt in presetTotalFees"
|
||
:key="amt"
|
||
class="preset-chip btn-active"
|
||
:class="{ selected: Number(form.totalFee) === amt }"
|
||
@click="form.totalFee = amt"
|
||
>
|
||
¥{{ amt }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="calc-preview">
|
||
<text class="preview-label">预算测算:</text>
|
||
<text class="preview-val highlight">
|
||
{{
|
||
form.payType === 'AA'
|
||
? `总费用预估约 ¥${calcTotalFee} (人均 ¥${form.aaFee || 0})`
|
||
: form.pricingMode === 'PER_PERSON'
|
||
? `单人出价 ¥${form.perPersonFee || 0} × 招募${form.maxParticipants}人 = 预估总额 ¥${calcTotalFee} (发单后可随时追加金额)`
|
||
: `房主全额买单请客,总预算约 ¥${form.totalFee || 0} (发单后可随时追加预算)`
|
||
}}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 房主调价与追加金额设置 (随时灵活追加) -->
|
||
<view class="form-card dynamic-price-card" v-if="form.payType === 'PROJECT' && publishMode === 'ACTIVITY'">
|
||
<view class="card-header-switch">
|
||
<view class="switch-title-box">
|
||
<text class="form-title">灵活调价与追加金额设置</text>
|
||
<text class="sub-label">组局进行中允许房主随时追加金额、加赏加急或修改单价</text>
|
||
</view>
|
||
<switch
|
||
:checked="form.allowDynamicPrice"
|
||
@change="form.allowDynamicPrice = $event.detail.value"
|
||
color="#6366F1"
|
||
/>
|
||
</view>
|
||
|
||
<view class="dynamic-config-body" v-if="form.allowDynamicPrice">
|
||
<text class="config-sec-title">追加金额/改价补差规则</text>
|
||
<view class="rule-cards">
|
||
<view
|
||
class="rule-card"
|
||
:class="{ active: form.priceAdjustRule === 'UNIFIED_HIGH' }"
|
||
@click="form.priceAdjustRule = 'UNIFIED_HIGH'"
|
||
>
|
||
<view class="rule-card-header">
|
||
<text class="rule-name">全员同享追加新高价</text>
|
||
<text class="rule-tag recommend">推荐</text>
|
||
</view>
|
||
<text class="rule-desc">
|
||
房主追加金额提价时(如从 500元/人 追加提高到 800元/人),已报名的成员自动同步补齐 300元 差额。先行者不吃亏,大幅提升组队热度。
|
||
</text>
|
||
</view>
|
||
|
||
<view
|
||
class="rule-card"
|
||
:class="{ active: form.priceAdjustRule === 'TIERED' }"
|
||
@click="form.priceAdjustRule = 'TIERED'"
|
||
>
|
||
<view class="rule-card-header">
|
||
<text class="rule-name">阶梯差价招募</text>
|
||
</view>
|
||
<text class="rule-desc">
|
||
追加金额仅对后续新加入的成员生效。已报名的老成员按其报名时的出价结算,互不影响。
|
||
</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 调价场景与降价提示说明 -->
|
||
<view class="scenario-notice">
|
||
<text class="notice-title">灵活追加金额说明:</text>
|
||
<text class="notice-item">• 随时追加金额:如原定 500元/人 招募4人,已有2人报名;房主在活动页可随时点击【追加金额】,选择按人追加(如每人追加 +¥300)或整局追加预算,系统自动完成老成员差额补偿与看板刷新。</text>
|
||
<text class="notice-item">• 降价调整保护:若房主手动调低单价,已报名的 2 人仍按原价受协议保护,新报名单人出价生效。</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 平台派单:预算与时长 -->
|
||
<view class="form-card" v-if="publishMode === 'DISPATCH'">
|
||
<view class="input-row">
|
||
<text class="row-label">预算 (元/小时)</text>
|
||
<input
|
||
class="row-input price-input"
|
||
type="digit"
|
||
v-model="dispatchBudget"
|
||
placeholder="如:88"
|
||
placeholder-class="price-ph"
|
||
/>
|
||
</view>
|
||
<view class="stepper-row">
|
||
<view class="stepper-info">
|
||
<text class="row-label">服务时长</text>
|
||
<text class="sub-label">1 - 8 小时</text>
|
||
</view>
|
||
<view class="stepper-box">
|
||
<button class="step-btn btn-active" :disabled="dispatchHours <= 1" @click="changeDispatchHours(-1)">-</button>
|
||
<text class="step-val">{{ dispatchHours }} 小时</text>
|
||
<button class="step-btn btn-active" :disabled="dispatchHours >= 8" @click="changeDispatchHours(1)">+</button>
|
||
</view>
|
||
</view>
|
||
<view class="dispatch-total-row">
|
||
<text class="row-label">预计支付</text>
|
||
<text class="dispatch-total">¥{{ ((Number(dispatchBudget) || 0) * dispatchHours).toFixed(2) }}</text>
|
||
</view>
|
||
<text class="sub-label">支付后进入附近 5km 派单池,认证大神抢单;30 分钟无人接单自动全额退款。</text>
|
||
</view>
|
||
|
||
<!-- 底部发起操作栏 (适配安全区) -->
|
||
<view class="bottom-bar">
|
||
<button class="btn-submit btn-active" @click="handleSubmit">
|
||
{{ publishMode === 'DISPATCH' ? '发布需求并支付' : '立即发起同城组局' }}
|
||
</button>
|
||
</view>
|
||
|
||
<!-- 敏感商圈智能风控友好预检弹窗 (P1安全风控) -->
|
||
<view class="risk-modal-mask" v-if="riskModalVisible" @click="riskModalVisible = false">
|
||
<view class="risk-modal-box" @click.stop="stopPropagation">
|
||
<view class="risk-modal-header">
|
||
<image class="risk-shield-icon" :src="ICONS.shield" mode="aspectFit" />
|
||
<text class="risk-title">同城线下组局安全风控提示</text>
|
||
</view>
|
||
<view class="risk-body">
|
||
<text class="risk-desc">
|
||
平台严格保障人身安全,严禁将私人住宅、酒店客房、封闭公寓等非公开场所作为履约地点。
|
||
</text>
|
||
<text class="risk-sub-tip" v-if="recommendPublicZones.length > 0">请选择以下平台认证的示范公开商圈:</text>
|
||
<text class="risk-sub-tip" v-else>暂无可推荐的合规商圈,请手动填写公开商业场所。</text>
|
||
<view class="recommend-zones" v-if="recommendPublicZones.length > 0">
|
||
<view
|
||
v-for="zone in recommendPublicZones"
|
||
:key="zone"
|
||
class="zone-chip btn-active"
|
||
@click="applyRecommendZone(zone)"
|
||
>
|
||
<image class="zone-pin-icon" :src="ICONS.mapPin" mode="aspectFit" />
|
||
<text class="zone-text">{{ zone }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="risk-footer">
|
||
<button class="risk-cancel-btn btn-active" @click="riskModalVisible = false">修改地点</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
||
import { onShow } from '@dcloudio/uni-app';
|
||
import { ICONS } from '@/common/icons.js';
|
||
import { createActivity, createOrder, payOrder as payOrderApi, requestWechatPayment, normalizeUserId, getSafetyPlaces, getNavCategories, getPlatformConfigs } from '@/common/api.js';
|
||
import { getCurrentThemeKey, applyThemeToNativeUI } from '@/common/theme.js';
|
||
import { ensureLoginOrRedirect } from '@/common/role.js';
|
||
|
||
const games = ref([]);
|
||
const configLoading = ref(true);
|
||
const currentThemeKey = ref(getCurrentThemeKey());
|
||
|
||
const onThemeChanged = (key) => {
|
||
currentThemeKey.value = key;
|
||
applyThemeToNativeUI(key);
|
||
};
|
||
|
||
onMounted(() => {
|
||
currentThemeKey.value = getCurrentThemeKey();
|
||
applyThemeToNativeUI(currentThemeKey.value);
|
||
uni.$on('theme-change', onThemeChanged);
|
||
loadLivePlaces();
|
||
});
|
||
|
||
onShow(() => {
|
||
if (!ensureLoginOrRedirect('/pages/activity/create')) {
|
||
return;
|
||
}
|
||
currentThemeKey.value = getCurrentThemeKey();
|
||
applyThemeToNativeUI(currentThemeKey.value);
|
||
loadLivePlaces();
|
||
});
|
||
|
||
onUnmounted(() => {
|
||
uni.$off('theme-change', onThemeChanged);
|
||
});
|
||
|
||
const form = ref({
|
||
payType: 'PROJECT',
|
||
game: '',
|
||
title: '',
|
||
startTime: '',
|
||
location: '',
|
||
latitude: null,
|
||
longitude: null,
|
||
maxParticipants: 4,
|
||
aaFee: '',
|
||
pricingMode: 'PER_PERSON',
|
||
perPersonFee: '',
|
||
totalFee: '',
|
||
allowDynamicPrice: true,
|
||
priceAdjustRule: 'UNIFIED_HIGH',
|
||
});
|
||
|
||
const calcTotalFee = computed(() => {
|
||
if (form.value.payType === 'AA') {
|
||
return (Number(form.value.aaFee) || 0) * form.value.maxParticipants;
|
||
}
|
||
if (form.value.pricingMode === 'PER_PERSON') {
|
||
return (Number(form.value.perPersonFee) || 0) * form.value.maxParticipants;
|
||
}
|
||
return Number(form.value.totalFee) || 0;
|
||
});
|
||
|
||
const stopPropagation = (e) => {
|
||
if (e && typeof e.stopPropagation === 'function') {
|
||
e.stopPropagation();
|
||
}
|
||
};
|
||
|
||
const riskModalVisible = ref(false);
|
||
|
||
// 发布类型:ACTIVITY=组局(多人报名),DISPATCH=平台派单(订单进抢单池)
|
||
const publishMode = ref('ACTIVITY');
|
||
const dispatchBudget = ref(88);
|
||
const dispatchHours = ref(2);
|
||
|
||
const changeDispatchHours = (delta) => {
|
||
dispatchHours.value = Math.min(8, Math.max(1, Number(dispatchHours.value) + delta));
|
||
};
|
||
const recommendPublicZones = ref([]);
|
||
|
||
const presetTimeOptions = ref([
|
||
'今晚 19:30',
|
||
'今晚 20:30',
|
||
'明天 14:00',
|
||
'周六 18:30',
|
||
'周日 15:00'
|
||
]);
|
||
|
||
const timePickerDays = (() => {
|
||
const baseDays = ['今晚', '今天', '明天', '后天', '周五', '周六', '周日'];
|
||
const weekNames = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
|
||
const now = new Date();
|
||
for (let i = 1; i <= 7; i++) {
|
||
const d = new Date(now.getTime() + i * 86400000);
|
||
const m = d.getMonth() + 1;
|
||
const day = d.getDate();
|
||
const w = weekNames[d.getDay()];
|
||
baseDays.push(`${m}月${day}日(${w})`);
|
||
}
|
||
return baseDays;
|
||
})();
|
||
|
||
const timePickerSlots = [
|
||
'09:00', '09:30', '10:00', '10:30', '11:00', '11:30',
|
||
'12:00', '12:30', '13:00', '13:30', '14:00', '14:30',
|
||
'15:00', '15:30', '16:00', '16:30', '17:00', '17:30',
|
||
'18:00', '18:30', '19:00', '19:30', '20:00', '20:30',
|
||
'21:00', '21:30', '22:00', '22:30', '23:00', '23:30',
|
||
'00:00', '00:30', '01:00'
|
||
];
|
||
|
||
const timePickerRange = ref([timePickerDays, timePickerSlots]);
|
||
const timePickerIndex = ref([5, 19]); // 默认对应 周六 18:30
|
||
|
||
const onTimePickerChange = (e) => {
|
||
const val = (e && e.detail && Array.isArray(e.detail.value)) ? e.detail.value : [0, 0];
|
||
const dayIdx = Number(val[0]) || 0;
|
||
const slotIdx = Number(val[1]) || 0;
|
||
timePickerIndex.value = [dayIdx, slotIdx];
|
||
const dayStr = timePickerDays[dayIdx] || timePickerDays[0];
|
||
const slotStr = timePickerSlots[slotIdx] || timePickerSlots[0];
|
||
form.value.startTime = `${dayStr} ${slotStr}`;
|
||
};
|
||
|
||
const selectQuickTime = (timeItem) => {
|
||
form.value.startTime = timeItem;
|
||
const parts = String(timeItem).split(' ');
|
||
if (parts.length === 2) {
|
||
const dIdx = timePickerDays.indexOf(parts[0]);
|
||
const sIdx = timePickerSlots.indexOf(parts[1]);
|
||
timePickerIndex.value = [
|
||
dIdx >= 0 ? dIdx : 0,
|
||
sIdx >= 0 ? sIdx : 0
|
||
];
|
||
}
|
||
};
|
||
|
||
const selectZone = (zoneItem) => {
|
||
form.value.location = zoneItem;
|
||
form.value.latitude = null;
|
||
form.value.longitude = null;
|
||
};
|
||
|
||
const handleChooseLocation = () => {
|
||
// #ifdef MP-WEIXIN || APP-PLUS
|
||
uni.chooseLocation({
|
||
success: (res) => {
|
||
if (res && (res.name || res.address)) {
|
||
form.value.location = res.name ? `${res.address ? res.address + ' ' : ''}${res.name}` : res.address;
|
||
form.value.latitude = Number.isFinite(Number(res.latitude)) ? Number(res.latitude) : null;
|
||
form.value.longitude = Number.isFinite(Number(res.longitude)) ? Number(res.longitude) : null;
|
||
uni.showToast({ title: '已选取地点', icon: 'success' });
|
||
}
|
||
},
|
||
fail: (err) => {
|
||
console.warn('[ActivityCreate] 地图选点取消或未授权:', err);
|
||
uni.showToast({ title: '未选择地点,可手动填写', icon: 'none' });
|
||
}
|
||
});
|
||
// #endif
|
||
|
||
// #ifndef MP-WEIXIN || APP-PLUS
|
||
uni.showToast({ title: '当前环境不支持地图选点,请手动填写', icon: 'none' });
|
||
// #endif
|
||
};
|
||
|
||
const minParticipants = ref(2);
|
||
const maxParticipants = ref(10);
|
||
const presetPerPersonFees = ref([200, 300, 500, 800, 1000]);
|
||
const presetTotalFees = ref([500, 1000, 2000, 3000, 5000]);
|
||
|
||
let placesRequestSeq = 0;
|
||
|
||
const loadLivePlaces = async () => {
|
||
const seq = ++placesRequestSeq;
|
||
configLoading.value = true;
|
||
try {
|
||
const [placesList, catList, configsRes] = await Promise.allSettled([
|
||
getSafetyPlaces(),
|
||
getNavCategories(),
|
||
getPlatformConfigs()
|
||
]);
|
||
if (seq !== placesRequestSeq) return;
|
||
|
||
// 商圈完全以后台「线下商圈场所库」为准,不再使用本地兜底数据
|
||
if (placesList.status === 'fulfilled' && Array.isArray(placesList.value)) {
|
||
recommendPublicZones.value = placesList.value
|
||
.filter((p) => p.status === '正常营业')
|
||
.map((p) => `${p.area} · ${p.name}`);
|
||
} else {
|
||
recommendPublicZones.value = [];
|
||
}
|
||
|
||
// 活动品类完全以后台「金刚区导航品类」为准,不再使用本地兜底数据
|
||
if (catList.status === 'fulfilled' && Array.isArray(catList.value)) {
|
||
const activeCats = catList.value
|
||
.filter((c) => c.status === 1 && c.name && c.name !== '全部活动')
|
||
.map((c) => c.name);
|
||
games.value = activeCats;
|
||
if (activeCats.length === 0) {
|
||
form.value.game = '';
|
||
} else if (!activeCats.includes(form.value.game)) {
|
||
form.value.game = activeCats[0];
|
||
}
|
||
} else {
|
||
games.value = [];
|
||
form.value.game = '';
|
||
}
|
||
|
||
if (configsRes.status === 'fulfilled' && configsRes.value?.activity_rules) {
|
||
const actRules = configsRes.value.activity_rules;
|
||
if (actRules.minParticipants) minParticipants.value = Number(actRules.minParticipants);
|
||
if (actRules.maxParticipants) maxParticipants.value = Number(actRules.maxParticipants);
|
||
if (Array.isArray(actRules.presetPerPersonFees) && actRules.presetPerPersonFees.length > 0) {
|
||
presetPerPersonFees.value = actRules.presetPerPersonFees;
|
||
}
|
||
if (Array.isArray(actRules.presetTotalFees) && actRules.presetTotalFees.length > 0) {
|
||
presetTotalFees.value = actRules.presetTotalFees;
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.warn('[ActivityCreate] 加载合规场所与品类异常:', e);
|
||
if (seq === placesRequestSeq) {
|
||
recommendPublicZones.value = [];
|
||
games.value = [];
|
||
form.value.game = '';
|
||
}
|
||
} finally {
|
||
if (seq === placesRequestSeq) {
|
||
configLoading.value = false;
|
||
}
|
||
}
|
||
};
|
||
|
||
const applyRecommendZone = (zone) => {
|
||
form.value.location = zone;
|
||
riskModalVisible.value = false;
|
||
uni.showToast({
|
||
title: '已选定合规商圈,请继续发起',
|
||
icon: 'none',
|
||
duration: 1500
|
||
});
|
||
};
|
||
|
||
const changeParticipants = (delta) => {
|
||
form.value.maxParticipants = Math.max(
|
||
minParticipants.value,
|
||
Math.min(maxParticipants.value, form.value.maxParticipants + delta)
|
||
);
|
||
};
|
||
|
||
const submitDispatch = async (title, startTime, location) => {
|
||
const budget = Number(dispatchBudget.value);
|
||
if (!budget || budget <= 0) {
|
||
uni.showToast({ title: '请输入有效的预算', icon: 'none' });
|
||
return;
|
||
}
|
||
if (budget > 2000) {
|
||
uni.showToast({ title: '预算不能超过 ¥2000/小时', icon: 'none' });
|
||
return;
|
||
}
|
||
const hours = Math.min(8, Math.max(1, Math.round(Number(dispatchHours.value) || 1)));
|
||
const amount = Number((budget * hours).toFixed(2));
|
||
const uid = normalizeUserId();
|
||
const cachedUser = uni.getStorageSync('userInfo') || {};
|
||
|
||
uni.showLoading({ title: '正在创建需求单...' });
|
||
let order = null;
|
||
try {
|
||
order = await createOrder({
|
||
buyerId: uid,
|
||
clientName: cachedUser.nickname || '同城用户',
|
||
expertId: 0,
|
||
companionName: '平台派单',
|
||
expertAvatar: '',
|
||
track: form.value.game,
|
||
serviceTitle: title,
|
||
hourlyRate: budget,
|
||
durationHours: hours,
|
||
amount,
|
||
payType: 'WECHAT',
|
||
location,
|
||
latitude: form.value.latitude,
|
||
longitude: form.value.longitude,
|
||
expenseBurden: 'AA制各自付',
|
||
remark: startTime ? `${title}|期望:${startTime}` : title,
|
||
});
|
||
} catch (err) {
|
||
uni.hideLoading();
|
||
uni.showToast({ title: err.message || '需求发布失败', icon: 'none' });
|
||
return;
|
||
}
|
||
uni.hideLoading();
|
||
|
||
try {
|
||
await requestWechatPayment({
|
||
amount,
|
||
title: `${form.value.game} · ${title}`,
|
||
orderId: order.id,
|
||
scene: 'ORDER',
|
||
userId: uid,
|
||
});
|
||
await payOrderApi(order.id, { targetStatus: '待接单', payType: 'WECHAT', buyerId: uid });
|
||
uni.showToast({ title: '需求已发布,已进入 5km 派单池', icon: 'success', duration: 2000 });
|
||
} catch (payErr) {
|
||
uni.showToast({
|
||
title: (payErr && payErr.cancelled) ? '已取消支付,可在订单中心继续支付' : (payErr.message || '支付未完成,可在订单中心继续支付'),
|
||
icon: 'none',
|
||
duration: 2200,
|
||
});
|
||
}
|
||
setTimeout(() => {
|
||
uni.switchTab({ url: '/pages/order/list' });
|
||
}, 1200);
|
||
};
|
||
|
||
const handleSubmit = async () => {
|
||
if (!ensureLoginOrRedirect('/pages/activity/create')) {
|
||
return;
|
||
}
|
||
// 1. 必填项完整校验(严禁未填写任何内容直接发布)
|
||
if (!form.value.game || !String(form.value.game).trim()) {
|
||
uni.showToast({ title: '请选择活动品类', icon: 'none' });
|
||
return;
|
||
}
|
||
|
||
const trimmedTitle = String(form.value.title || '').trim();
|
||
if (!trimmedTitle) {
|
||
uni.showToast({ title: '请填写活动标题', icon: 'none' });
|
||
return;
|
||
}
|
||
if (trimmedTitle.length < 2) {
|
||
uni.showToast({ title: '活动标题至少2个字', icon: 'none' });
|
||
return;
|
||
}
|
||
|
||
const trimmedTime = String(form.value.startTime || '').trim();
|
||
if (!trimmedTime) {
|
||
uni.showToast({ title: '请选择活动时段', icon: 'none' });
|
||
return;
|
||
}
|
||
|
||
const trimmedLocation = String(form.value.location || '').trim();
|
||
if (!trimmedLocation) {
|
||
uni.showToast({ title: '请填写或选择活动地点', icon: 'none' });
|
||
return;
|
||
}
|
||
|
||
// 2. P1 敏感商圈安全风控友好预检拦截
|
||
const sensitivePattern = /(酒店|客房|宾馆|民宿|旅馆|公寓|小区|住宅|私密|住宅区|私人房|卧室)/;
|
||
if (sensitivePattern.test(trimmedLocation)) {
|
||
riskModalVisible.value = true;
|
||
return;
|
||
}
|
||
|
||
// 平台派单:直接创建订单并拉起支付,支付成功后进入抢单池
|
||
if (publishMode.value === 'DISPATCH') {
|
||
await submitDispatch(trimmedTitle, trimmedTime, trimmedLocation);
|
||
return;
|
||
}
|
||
|
||
// 3. 费用与预算必填及数值合法性校验
|
||
const isProject = form.value.payType === 'PROJECT';
|
||
if (!isProject) {
|
||
const aaVal = Number(form.value.aaFee);
|
||
if (!form.value.aaFee || isNaN(aaVal) || aaVal <= 0) {
|
||
uni.showToast({ title: '请填写有效的人均分摊', icon: 'none' });
|
||
return;
|
||
}
|
||
} else if (form.value.pricingMode === 'PER_PERSON') {
|
||
const ppVal = Number(form.value.perPersonFee);
|
||
if (!form.value.perPersonFee || isNaN(ppVal) || ppVal <= 0) {
|
||
uni.showToast({ title: '请填写或选择单人出价', icon: 'none' });
|
||
return;
|
||
}
|
||
} else {
|
||
const totalVal = Number(form.value.totalFee);
|
||
if (!form.value.totalFee || isNaN(totalVal) || totalVal <= 0) {
|
||
uni.showToast({ title: '请填写或选择总预算', icon: 'none' });
|
||
return;
|
||
}
|
||
}
|
||
|
||
const perPerson = isProject && form.value.pricingMode === 'PER_PERSON'
|
||
? (Number(form.value.perPersonFee) || 0)
|
||
: (!isProject ? (Number(form.value.aaFee) || 0) : 0);
|
||
|
||
const cachedUser = uni.getStorageSync('userInfo') || uni.getStorageSync('USER_INFO') || {};
|
||
if (cachedUser.status === '已封禁' || cachedUser.status === '封禁') {
|
||
uni.showToast({ title: '账号已被封禁,无法发起组局', icon: 'none' });
|
||
return;
|
||
}
|
||
const currentUserId = normalizeUserId();
|
||
const newActivityPayload = {
|
||
creatorId: currentUserId,
|
||
game: form.value.game,
|
||
title: trimmedTitle,
|
||
payType: form.value.payType,
|
||
pricingMode: isProject ? form.value.pricingMode : 'AA',
|
||
perPersonFee: perPerson,
|
||
allowDynamicPrice: isProject ? form.value.allowDynamicPrice : false,
|
||
priceAdjustRule: form.value.priceAdjustRule,
|
||
totalFee: calcTotalFee.value,
|
||
aaFee: Number(form.value.aaFee) || 0,
|
||
maxParticipants: form.value.maxParticipants,
|
||
startTime: trimmedTime,
|
||
location: trimmedLocation,
|
||
latitude: form.value.latitude,
|
||
longitude: form.value.longitude,
|
||
creatorNickname: cachedUser.nickname || '同城用户',
|
||
creatorAvatar: cachedUser.avatar || 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=150&h=150&fit=crop',
|
||
};
|
||
|
||
uni.showLoading({ title: '正在发布组局...' });
|
||
|
||
try {
|
||
await createActivity(newActivityPayload);
|
||
uni.hideLoading();
|
||
// 重置已填表单项
|
||
form.value.title = '';
|
||
form.value.startTime = '';
|
||
form.value.location = '';
|
||
form.value.aaFee = '';
|
||
form.value.perPersonFee = '';
|
||
form.value.totalFee = '';
|
||
uni.showToast({
|
||
title: '组局发起成功',
|
||
icon: 'success',
|
||
duration: 1500
|
||
});
|
||
setTimeout(() => {
|
||
uni.switchTab({
|
||
url: '/pages/activity/list'
|
||
});
|
||
}, 1200);
|
||
} catch (err) {
|
||
uni.hideLoading();
|
||
uni.showToast({
|
||
title: err.message || '发布异常,请重试',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
.create-activity-container {
|
||
min-height: 100vh;
|
||
background-color: #0A0F1D;
|
||
padding: 24rpx 32rpx calc(160rpx + env(safe-area-inset-bottom));
|
||
}
|
||
|
||
.publish-mode-wrap {
|
||
display: flex;
|
||
gap: 16rpx;
|
||
margin-bottom: 20rpx;
|
||
|
||
.publish-mode-item {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 6rpx;
|
||
padding: 20rpx 12rpx;
|
||
border-radius: 20rpx;
|
||
border: 1rpx solid rgba(148, 163, 184, 0.28);
|
||
background: rgba(255, 255, 255, 0.04);
|
||
|
||
&.active {
|
||
border-color: #10B981;
|
||
background: rgba(16, 185, 129, 0.14);
|
||
}
|
||
|
||
.publish-mode-title {
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
color: var(--text-primary, #F8FAFC);
|
||
}
|
||
|
||
.publish-mode-desc {
|
||
font-size: 21rpx;
|
||
color: var(--text-muted, #94A3B8);
|
||
}
|
||
}
|
||
}
|
||
|
||
.dispatch-total-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin: 12rpx 0;
|
||
|
||
.dispatch-total {
|
||
font-size: 32rpx;
|
||
font-weight: 700;
|
||
color: #10B981;
|
||
}
|
||
}
|
||
|
||
.mode-switch-wrap {
|
||
display: flex;
|
||
gap: 16rpx;
|
||
margin-bottom: 24rpx;
|
||
|
||
.mode-item {
|
||
flex: 1;
|
||
background: #151D30;
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
border-radius: 20rpx;
|
||
padding: 20rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 14rpx;
|
||
|
||
.mode-svg {
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
}
|
||
|
||
.mode-text-box {
|
||
display: flex;
|
||
flex-direction: column;
|
||
|
||
.mode-title {
|
||
font-size: 26rpx;
|
||
font-weight: 700;
|
||
color: #F8FAFC;
|
||
margin-bottom: 4rpx;
|
||
}
|
||
.mode-desc {
|
||
font-size: 18rpx;
|
||
color: #64748B;
|
||
}
|
||
}
|
||
|
||
&.active {
|
||
background: linear-gradient(135deg, rgba(99, 102, 241, 0.18) 0%, rgba(139, 92, 246, 0.1) 100%);
|
||
border-color: #6366F1;
|
||
box-shadow: 0 0 16rpx rgba(99, 102, 241, 0.3);
|
||
|
||
.mode-title {
|
||
color: #818CF8;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.form-card {
|
||
background: #151D30;
|
||
border-radius: 24rpx;
|
||
padding: 28rpx;
|
||
margin-bottom: 24rpx;
|
||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||
|
||
.card-label {
|
||
font-size: 26rpx;
|
||
font-weight: 700;
|
||
color: #F8FAFC;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.game-pills {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 14rpx;
|
||
|
||
.game-pill {
|
||
font-size: 24rpx;
|
||
color: #94A3B8;
|
||
background: rgba(255, 255, 255, 0.04);
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
padding: 10rpx 24rpx;
|
||
border-radius: 28rpx;
|
||
|
||
&.selected {
|
||
background: rgba(99, 102, 241, 0.2);
|
||
color: #818CF8;
|
||
border-color: #6366F1;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
}
|
||
|
||
.config-empty-tip {
|
||
display: block;
|
||
padding: 12rpx 0;
|
||
font-size: 22rpx;
|
||
color: #94A3B8;
|
||
}
|
||
|
||
.input-row {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 20rpx 0;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
|
||
|
||
&:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.row-label {
|
||
width: 220rpx;
|
||
font-size: 26rpx;
|
||
color: #CBD5E1;
|
||
height: 48rpx;
|
||
line-height: 48rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.row-input {
|
||
flex: 1;
|
||
font-size: 26rpx;
|
||
color: #F8FAFC;
|
||
height: 48rpx;
|
||
line-height: 48rpx;
|
||
box-sizing: border-box;
|
||
|
||
&.price-input {
|
||
font-size: 34rpx;
|
||
font-weight: 700;
|
||
color: #F8FAFC;
|
||
letter-spacing: 0.5px;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
}
|
||
|
||
.time-picker-flex {
|
||
flex: 1;
|
||
|
||
.picker-display-box {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
height: 52rpx;
|
||
|
||
.picker-val {
|
||
font-size: 26rpx;
|
||
font-weight: 600;
|
||
color: #F8FAFC;
|
||
font-variant-numeric: tabular-nums;
|
||
|
||
&.placeholder {
|
||
color: #475569;
|
||
font-weight: 400;
|
||
}
|
||
}
|
||
|
||
.picker-right-tag {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6rpx;
|
||
background: rgba(99, 102, 241, 0.14);
|
||
border: 1px solid rgba(99, 102, 241, 0.3);
|
||
padding: 4rpx 14rpx;
|
||
border-radius: 20rpx;
|
||
|
||
.picker-tag-text {
|
||
font-size: 20rpx;
|
||
font-weight: 600;
|
||
color: #818CF8;
|
||
}
|
||
|
||
.picker-arrow {
|
||
font-size: 24rpx;
|
||
font-weight: 700;
|
||
color: #818CF8;
|
||
line-height: 1;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.ph {
|
||
color: #475569;
|
||
}
|
||
|
||
.price-ph {
|
||
font-size: 26rpx;
|
||
font-weight: normal;
|
||
color: #475569;
|
||
}
|
||
}
|
||
|
||
.location-block {
|
||
padding: 20rpx 0 18rpx;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
|
||
|
||
.location-header-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 14rpx;
|
||
|
||
.row-label {
|
||
font-size: 26rpx;
|
||
color: #CBD5E1;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.btn-choose-loc {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6rpx;
|
||
background: rgba(56, 189, 248, 0.14);
|
||
border: 1px solid rgba(56, 189, 248, 0.35);
|
||
padding: 4rpx 16rpx;
|
||
border-radius: 22rpx;
|
||
white-space: nowrap;
|
||
flex-shrink: 0;
|
||
|
||
.loc-pin-svg {
|
||
width: 24rpx;
|
||
height: 24rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.loc-btn-text {
|
||
font-size: 20rpx;
|
||
font-weight: 600;
|
||
color: #38BDF8;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.loc-btn-arrow {
|
||
font-size: 24rpx;
|
||
font-weight: 700;
|
||
color: #38BDF8;
|
||
line-height: 1;
|
||
}
|
||
}
|
||
}
|
||
|
||
.location-input-box {
|
||
background: rgba(255, 255, 255, 0.04);
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
border-radius: 16rpx;
|
||
padding: 16rpx 20rpx;
|
||
|
||
.location-textarea {
|
||
width: 100%;
|
||
min-height: 40rpx;
|
||
font-size: 26rpx;
|
||
font-weight: 600;
|
||
color: #F8FAFC;
|
||
line-height: 1.45;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.ph {
|
||
color: #475569;
|
||
font-weight: 400;
|
||
}
|
||
}
|
||
}
|
||
|
||
.quick-chip-row {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 12rpx 0 16rpx;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
|
||
|
||
.chip-label {
|
||
font-size: 22rpx;
|
||
color: #64748B;
|
||
width: 140rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.quick-chips {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 12rpx;
|
||
|
||
.time-chip, .zone-chip-item {
|
||
font-size: 22rpx;
|
||
color: #94A3B8;
|
||
background: rgba(255, 255, 255, 0.04);
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
padding: 6rpx 16rpx;
|
||
border-radius: 12rpx;
|
||
transition: all 0.15s;
|
||
|
||
&.selected {
|
||
color: #818CF8;
|
||
border-color: #6366F1;
|
||
background: rgba(99, 102, 241, 0.15);
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
|
||
.zone-chip-item.selected {
|
||
color: #38BDF8;
|
||
border-color: #38BDF8;
|
||
background: rgba(56, 189, 248, 0.15);
|
||
}
|
||
}
|
||
}
|
||
|
||
.stepper-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding-bottom: 20rpx;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
|
||
|
||
.stepper-info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
|
||
.row-label {
|
||
font-size: 26rpx;
|
||
color: #CBD5E1;
|
||
font-weight: 600;
|
||
}
|
||
.sub-label {
|
||
font-size: 20rpx;
|
||
color: #64748B;
|
||
margin-top: 4rpx;
|
||
}
|
||
}
|
||
|
||
.stepper-box {
|
||
display: flex;
|
||
align-items: center;
|
||
background: rgba(255, 255, 255, 0.04);
|
||
border-radius: 16rpx;
|
||
padding: 6rpx;
|
||
|
||
.step-btn {
|
||
width: 68rpx;
|
||
height: 68rpx;
|
||
line-height: 64rpx;
|
||
padding: 0;
|
||
background: #1E2942;
|
||
color: #FFF;
|
||
font-size: 32rpx;
|
||
border-radius: 12rpx;
|
||
border: none;
|
||
margin: 0;
|
||
}
|
||
|
||
.step-val {
|
||
font-size: 28rpx;
|
||
color: #F8FAFC;
|
||
font-weight: 600;
|
||
margin: 0 20rpx;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
}
|
||
}
|
||
|
||
.calc-preview {
|
||
margin-top: 20rpx;
|
||
padding: 16rpx 20rpx;
|
||
background: rgba(99, 102, 241, 0.08);
|
||
border-radius: 14rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
|
||
.preview-label {
|
||
font-size: 22rpx;
|
||
color: #94A3B8;
|
||
}
|
||
.preview-val {
|
||
font-size: 22rpx;
|
||
color: #10B981;
|
||
font-weight: 600;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
}
|
||
|
||
.pricing-mode-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 20rpx 0;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
|
||
|
||
.pricing-mode-group {
|
||
display: flex;
|
||
gap: 12rpx;
|
||
|
||
.pricing-mode-btn {
|
||
font-size: 22rpx;
|
||
padding: 8rpx 22rpx;
|
||
border-radius: 20rpx;
|
||
background: rgba(255, 255, 255, 0.05);
|
||
color: #94A3B8;
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
|
||
&.active {
|
||
background: rgba(99, 102, 241, 0.2);
|
||
color: #818CF8;
|
||
border-color: #6366F1;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
.preset-price-row {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 14rpx 0;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
|
||
|
||
.preset-label {
|
||
font-size: 20rpx;
|
||
color: #94A3B8;
|
||
width: 140rpx;
|
||
}
|
||
|
||
.preset-chips {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 12rpx;
|
||
|
||
.preset-chip {
|
||
font-size: 22rpx;
|
||
padding: 6rpx 18rpx;
|
||
border-radius: 12rpx;
|
||
background: rgba(255, 255, 255, 0.04);
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
color: #CBD5E1;
|
||
|
||
&.selected {
|
||
background: rgba(99, 102, 241, 0.2);
|
||
border-color: #6366F1;
|
||
color: #818CF8;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
&.dynamic-price-card {
|
||
.card-header-switch {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
|
||
.switch-title-box {
|
||
display: flex;
|
||
flex-direction: column;
|
||
|
||
.form-title {
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
color: #F8FAFC;
|
||
}
|
||
.sub-label {
|
||
font-size: 20rpx;
|
||
color: #64748B;
|
||
margin-top: 4rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
.dynamic-config-body {
|
||
margin-top: 24rpx;
|
||
padding-top: 20rpx;
|
||
border-top: 1px solid rgba(255, 255, 255, 0.04);
|
||
|
||
.config-sec-title {
|
||
font-size: 22rpx;
|
||
font-weight: 600;
|
||
color: #94A3B8;
|
||
display: block;
|
||
margin-bottom: 14rpx;
|
||
}
|
||
|
||
.rule-cards {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 14rpx;
|
||
|
||
.rule-card {
|
||
background: rgba(255, 255, 255, 0.03);
|
||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||
border-radius: 16rpx;
|
||
padding: 18rpx 20rpx;
|
||
|
||
&.active {
|
||
background: rgba(99, 102, 241, 0.12);
|
||
border-color: #6366F1;
|
||
}
|
||
|
||
.rule-card-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
margin-bottom: 8rpx;
|
||
|
||
.rule-name {
|
||
font-size: 26rpx;
|
||
font-weight: 700;
|
||
color: #F8FAFC;
|
||
}
|
||
|
||
.rule-tag {
|
||
font-size: 18rpx;
|
||
padding: 2rpx 10rpx;
|
||
border-radius: 6rpx;
|
||
|
||
&.recommend {
|
||
background: rgba(16, 185, 129, 0.2);
|
||
color: #10B981;
|
||
}
|
||
}
|
||
}
|
||
|
||
.rule-desc {
|
||
font-size: 20rpx;
|
||
color: #94A3B8;
|
||
line-height: 1.45;
|
||
display: block;
|
||
}
|
||
}
|
||
}
|
||
|
||
.scenario-notice {
|
||
margin-top: 20rpx;
|
||
padding: 16rpx 20rpx;
|
||
background: rgba(99, 102, 241, 0.08);
|
||
border: 1px dashed rgba(99, 102, 241, 0.3);
|
||
border-radius: 14rpx;
|
||
|
||
.notice-title {
|
||
font-size: 22rpx;
|
||
font-weight: 700;
|
||
color: #818CF8;
|
||
display: block;
|
||
margin-bottom: 6rpx;
|
||
}
|
||
|
||
.notice-item {
|
||
font-size: 20rpx;
|
||
color: #94A3B8;
|
||
line-height: 1.5;
|
||
display: block;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.bottom-bar {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: calc(120rpx + env(safe-area-inset-bottom));
|
||
padding: 0 32rpx env(safe-area-inset-bottom);
|
||
box-sizing: border-box;
|
||
background-color: rgba(21, 29, 48, 0.95);
|
||
backdrop-filter: blur(12px);
|
||
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||
display: flex;
|
||
align-items: center;
|
||
z-index: 100;
|
||
max-width: 450px;
|
||
margin: 0 auto;
|
||
|
||
.btn-submit {
|
||
flex: 1;
|
||
background: linear-gradient(135deg, #6366F1 0%, #8B5CF6 100%);
|
||
color: #FFF;
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
border-radius: 44rpx;
|
||
height: 84rpx;
|
||
line-height: 84rpx;
|
||
box-shadow: 0 4rpx 20rpx rgba(99, 102, 241, 0.4);
|
||
margin: 0;
|
||
}
|
||
}
|
||
|
||
.create-activity-container.theme-warm {
|
||
.form-card .config-empty-tip {
|
||
color: #78716C;
|
||
}
|
||
|
||
background-color: #FDFBF7;
|
||
|
||
.mode-switch-wrap .mode-item {
|
||
background-color: #FFFFFF;
|
||
border-color: #F0ECE4;
|
||
box-shadow: 0 4rpx 16rpx rgba(28, 25, 23, 0.04);
|
||
|
||
.mode-text-box .mode-title {
|
||
color: #1C1917;
|
||
}
|
||
.mode-text-box .mode-desc {
|
||
color: #78716C;
|
||
}
|
||
|
||
&.active {
|
||
background: rgba(234, 88, 12, 0.08);
|
||
border-color: #EA580C;
|
||
.mode-text-box .mode-title {
|
||
color: #EA580C;
|
||
}
|
||
}
|
||
}
|
||
|
||
.form-card {
|
||
background-color: #FFFFFF;
|
||
border-color: #F0ECE4;
|
||
box-shadow: 0 4rpx 20rpx rgba(28, 25, 23, 0.05);
|
||
|
||
.card-label {
|
||
color: #1C1917;
|
||
}
|
||
|
||
.game-pills .game-pill {
|
||
background: #F8F5F0;
|
||
border-color: #E7E5E4;
|
||
color: #78716C;
|
||
|
||
&.selected {
|
||
background: rgba(234, 88, 12, 0.1);
|
||
border-color: #EA580C;
|
||
color: #EA580C;
|
||
font-weight: 700;
|
||
}
|
||
}
|
||
|
||
.stepper-row {
|
||
border-bottom-color: #F5F2EC;
|
||
|
||
.stepper-info {
|
||
.row-label {
|
||
color: #1C1917;
|
||
}
|
||
.sub-label {
|
||
color: #78716C;
|
||
}
|
||
}
|
||
|
||
.stepper-box {
|
||
background: #F8F5F0;
|
||
border: 1px solid #E7E5E4;
|
||
|
||
.step-btn {
|
||
background: #FFFFFF;
|
||
color: #1C1917;
|
||
border: 1px solid #E7E5E4;
|
||
box-shadow: 0 2rpx 6rpx rgba(28, 25, 23, 0.04);
|
||
|
||
&:disabled {
|
||
color: #A8A29E;
|
||
background: #F5F2EC;
|
||
border-color: #E7E5E4;
|
||
}
|
||
}
|
||
|
||
.step-val {
|
||
color: #1C1917;
|
||
font-weight: 700;
|
||
}
|
||
}
|
||
}
|
||
|
||
.form-title {
|
||
color: #1C1917;
|
||
}
|
||
.input-row {
|
||
border-bottom-color: #F5F2EC;
|
||
.row-label {
|
||
color: #1C1917;
|
||
}
|
||
.row-input {
|
||
color: #1C1917;
|
||
|
||
&.price-input {
|
||
color: #000000;
|
||
font-weight: 800;
|
||
}
|
||
}
|
||
.time-picker-flex .picker-display-box {
|
||
.picker-val {
|
||
color: #1C1917;
|
||
&.placeholder {
|
||
color: #A8A29E;
|
||
}
|
||
}
|
||
.picker-right-tag {
|
||
background: #FFF7ED;
|
||
border-color: #FDBA74;
|
||
.picker-tag-text,
|
||
.picker-arrow {
|
||
color: #EA580C;
|
||
}
|
||
}
|
||
}
|
||
.ph,
|
||
.price-ph {
|
||
color: #A8A29E;
|
||
}
|
||
}
|
||
|
||
.location-block {
|
||
border-bottom-color: #F5F2EC;
|
||
|
||
.location-header-row {
|
||
.row-label {
|
||
color: #1C1917;
|
||
}
|
||
.btn-choose-loc {
|
||
background: #FFF7ED;
|
||
border-color: #FDBA74;
|
||
.loc-btn-text,
|
||
.loc-btn-arrow {
|
||
color: #EA580C;
|
||
}
|
||
}
|
||
}
|
||
|
||
.location-input-box {
|
||
background: #FAF8F5;
|
||
border-color: #E7E5E4;
|
||
|
||
.location-textarea {
|
||
color: #1C1917;
|
||
}
|
||
.ph {
|
||
color: #A8A29E;
|
||
}
|
||
}
|
||
}
|
||
|
||
.quick-chip-row {
|
||
border-bottom-color: #F5F2EC;
|
||
.chip-label {
|
||
color: #78716C;
|
||
}
|
||
.quick-chips {
|
||
.time-chip,
|
||
.zone-chip-item {
|
||
background: #F8F5F0;
|
||
border-color: #E7E5E4;
|
||
color: #57534E;
|
||
|
||
&.selected {
|
||
background: #FFF7ED;
|
||
border-color: #EA580C;
|
||
color: #EA580C;
|
||
font-weight: 700;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.calc-preview {
|
||
background: #F8F5F0;
|
||
.preview-label {
|
||
color: #78716C;
|
||
}
|
||
}
|
||
|
||
.pricing-mode-row {
|
||
border-bottom-color: #F5F2EC;
|
||
.pricing-mode-btn {
|
||
background: #F8F5F0;
|
||
color: #78716C;
|
||
border-color: #E7E5E4;
|
||
&.active {
|
||
background: rgba(234, 88, 12, 0.1);
|
||
color: #EA580C;
|
||
border-color: #EA580C;
|
||
}
|
||
}
|
||
}
|
||
|
||
.preset-price-row {
|
||
border-bottom-color: #F5F2EC;
|
||
.preset-label {
|
||
color: #78716C;
|
||
}
|
||
.preset-chips .preset-chip {
|
||
background: #F8F5F0;
|
||
border-color: #E7E5E4;
|
||
color: #78716C;
|
||
&.selected {
|
||
background: rgba(234, 88, 12, 0.1);
|
||
border-color: #EA580C;
|
||
color: #EA580C;
|
||
font-weight: 700;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
&.dynamic-price-card {
|
||
.card-header-switch .switch-title-box .form-title {
|
||
color: #1C1917;
|
||
}
|
||
.dynamic-config-body {
|
||
border-top-color: #F5F2EC;
|
||
.config-sec-title {
|
||
color: #78716C;
|
||
}
|
||
.rule-cards .rule-card {
|
||
background: #FAFAF9;
|
||
border-color: #E7E5E4;
|
||
&.active {
|
||
background: rgba(234, 88, 12, 0.06);
|
||
border-color: #EA580C;
|
||
.rule-card-header .rule-name {
|
||
color: #EA580C;
|
||
}
|
||
}
|
||
.rule-card-header .rule-name {
|
||
color: #1C1917;
|
||
}
|
||
.rule-desc {
|
||
color: #78716C;
|
||
}
|
||
}
|
||
.scenario-notice {
|
||
background: #FFF7ED;
|
||
border-color: #FDBA74;
|
||
.notice-title {
|
||
color: #EA580C;
|
||
}
|
||
.notice-item {
|
||
color: #78716C;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.bottom-bar {
|
||
background-color: rgba(253, 251, 247, 0.95);
|
||
border-top-color: #F0ECE4;
|
||
|
||
.btn-submit {
|
||
background: linear-gradient(135deg, #F97316 0%, #EA580C 100%);
|
||
box-shadow: 0 4rpx 20rpx rgba(234, 88, 12, 0.35);
|
||
}
|
||
}
|
||
|
||
.risk-modal-box {
|
||
background: #FFFFFF;
|
||
border-color: rgba(239, 68, 68, 0.4);
|
||
|
||
.risk-modal-header .risk-title {
|
||
color: #DC2626;
|
||
}
|
||
|
||
.risk-body {
|
||
.risk-desc {
|
||
color: #475569;
|
||
}
|
||
.risk-sub-tip {
|
||
color: #64748B;
|
||
}
|
||
.recommend-zones .zone-chip {
|
||
background: rgba(234, 88, 12, 0.08);
|
||
border-color: rgba(234, 88, 12, 0.25);
|
||
.zone-text {
|
||
color: #EA580C;
|
||
}
|
||
}
|
||
}
|
||
|
||
.risk-footer .risk-cancel-btn {
|
||
background: #F1F5F9;
|
||
color: #334155;
|
||
border-color: #CBD5E1;
|
||
}
|
||
}
|
||
}
|
||
|
||
.risk-modal-mask {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.75);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 999;
|
||
padding: 40rpx;
|
||
backdrop-filter: blur(6px);
|
||
}
|
||
|
||
.risk-modal-box {
|
||
width: 100%;
|
||
max-width: 620rpx;
|
||
background: #151D30;
|
||
border: 1px solid rgba(239, 68, 68, 0.35);
|
||
border-radius: 28rpx;
|
||
padding: 36rpx;
|
||
box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.6);
|
||
|
||
.risk-modal-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 14rpx;
|
||
margin-bottom: 20rpx;
|
||
|
||
.risk-shield-icon {
|
||
width: 44rpx;
|
||
height: 44rpx;
|
||
}
|
||
|
||
.risk-title {
|
||
font-size: 30rpx;
|
||
font-weight: 700;
|
||
color: #F87171;
|
||
}
|
||
}
|
||
|
||
.risk-body {
|
||
margin-bottom: 30rpx;
|
||
|
||
.risk-desc {
|
||
display: block;
|
||
font-size: 24rpx;
|
||
color: #CBD5E1;
|
||
line-height: 1.6;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.risk-sub-tip {
|
||
display: block;
|
||
font-size: 22rpx;
|
||
color: #94A3B8;
|
||
margin-bottom: 16rpx;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.recommend-zones {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12rpx;
|
||
|
||
.zone-chip {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
background: rgba(99, 102, 241, 0.12);
|
||
border: 1px solid rgba(99, 102, 241, 0.3);
|
||
border-radius: 14rpx;
|
||
padding: 16rpx 20rpx;
|
||
|
||
.zone-pin-icon {
|
||
width: 28rpx;
|
||
height: 28rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.zone-text {
|
||
font-size: 24rpx;
|
||
color: #A5B4FC;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.risk-footer {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
|
||
.risk-cancel-btn {
|
||
width: 100%;
|
||
height: 72rpx;
|
||
line-height: 72rpx;
|
||
background: #1E293B;
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
color: #E2E8F0;
|
||
font-size: 26rpx;
|
||
border-radius: 16rpx;
|
||
}
|
||
}
|
||
}
|
||
</style>
|