mahjong_group/pages/appointment/appointment-page.vue
2025-12-02 14:07:08 +08:00

962 lines
27 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<com-page-container-base ref="_containerBase">
<view class="content column">
<text class="page-title">发起预约</text>
<view class="column" style="overflow-y: auto;">
<card-container marginTop="30rpx">
<label-field label="预定时长">
<tag-select :options="timeRange" v-model="timeRangeValue" @change="onTimeRangeChange" />
</label-field>
<view :style="{ height: lineHeight }"></view>
<time-select-cell label="开始时间" icon="@@:app/static/time_start.png" @select="openUpDatesTimePicker">
{{ getDayDescription(reservationInfo.start_time * 1000) }}
</time-select-cell>
<view :style="{ height: lineHeight }"></view>
<time-select-cell label="结束时间" icon="@@:app/static/time_end.png" @select="openUpDatesTimePickerEnd">
{{ getDayDescription(reservationInfo.end_time * 1000) }}
</time-select-cell>
<view :style="{ height: lineHeight }"></view>
<time-select-cell label="房间" @select="openRoomPicker">
{{ getRoomPickerName() }}
</time-select-cell>
</card-container>
<card-container marginTop="30rpx">
<label-field label="组局名称">
<view class="input-wrapper">
<up-input placeholder="请输入内容" border="surround" v-model="reservationInfo.title"></up-input>
</view>
</label-field>
<view :style="{ height: lineHeight }"></view>
<label-field label="人数">
<uni-data-select v-model="reservationInfo.player_count" :placeholder="peopleText"
:localdata="peopleRange"></uni-data-select>
</label-field>
<view :style="{ height: lineHeight }"></view>
<label-field label="玩法类型">
<uni-data-select v-model="reservationInfo.game_type" placeholder="请选择玩法类型"
:localdata="gameTypeRange" @change="gameTypeRangeChange"></uni-data-select>
</label-field>
<view :style="{ height: lineHeight }"></view>
<label-field label="具体规则">
<uni-data-select v-model="reservationInfo.game_rule" :placeholder="gameRuleText"
:localdata="gameRuleRange" @change="gameRuleRangeChange"></uni-data-select>
</label-field>
<view :style="{ height: lineHeight }"></view>
<label-field label="其他补充">
<view class="input-wrapper">
<up-input placeholder="请输入其他补充内容" v-model="reservationInfo.extra_info"></up-input>
</view>
</label-field>
</card-container>
<card-container marginTop="30rpx">
<label-slect-field label="是否禁烟">
<view style="height: 61rpx;display: flex;justify-content: left;align-items: center;">
<com-appointment-radio-select :options="smokingOptions"
v-model="reservationInfo.is_smoking" />
</view>
</label-slect-field>
<view :style="{ height: lineHeight }"></view>
<label-slect-field label="性别">
<view style="height: 61rpx;display: flex;justify-content: left;align-items: center;">
<com-appointment-radio-select :options="genderOptions"
v-model="reservationInfo.gender_limit" />
</view>
</label-slect-field>
<view :style="{ height: lineHeight }"></view>
<label-slect-field label="年龄范围">
<view @click="openAgePicker"
style="height: 61rpx;display: flex;justify-content: left;align-items: center;"
class="clickable-row">
{{ getAgeRangeText() }}
</view>
</label-slect-field>
<view :style="{ height: lineHeight }"></view>
<label-slect-field label="信誉">
<view class="flex-center-row" style="height: 61rpx;">
<text class="inline-label" style="margin-top: 12rpx;">大于等于</text>
<view class="counter-container">
<view @click="decrement" :disabled="currentValue <= 0">
<uni-icons type="minus" size="24" color="#00AC4E" />
</view>
<view class="counter-value">
{{ currentValue.toFixed(1) }}
</view>
<view @click="increment" :disabled="currentValue >= 5">
<uni-icons type="plus" size="24" color="#00AC4E" />
</view>
</view>
</view>
</label-slect-field>
</card-container>
<card-container marginTop="30rpx">
<label-slect-field label="鸽子费">
<com-appointment-radio-select :options="depositOptions" v-model="reservationInfo.deposit_fee" />
</label-slect-field>
<view v-if="reservationInfo.deposit_fee === -1" :style="{ height: lineHeight }"></view>
<label-slect-field v-if="reservationInfo.deposit_fee === -1" label="金额">
<view class="input-wrapper">
<up-input type="number" placeholder="请输入0-50" v-model="customDeposit" @input="onCustomDepositInput"></up-input>
</view>
</label-slect-field>
<view :style="{ height: lineHeight }"></view>
<text class="note-text">鸽子费(保证金),参与人需缴纳鸽子费。若有参与者在预约后没有赴约,其鸽子费由在场的所有人平分。组局成功或失败后鸽子费将全额返还。</text>
</card-container>
<view class="action-row">
<view class="center reset-button" @click="resetForm">
<text style="margin: 20rpx;">重置</text>
</view>
<view class="center submit-button action-submit" @click="submitReservation">
<text style="margin: 20rpx; color: white;">发起预约</text>
</view>
</view>
<view class="center note-container" @click="tipsShow">
<!-- <text class="muted-text">组局成功后,发起者可通过店员领取线下红包</text> -->
</view>
</view>
</view>
<com-appointment-picker-data ref="roomPickerRef" />
<up-picker title="年龄范围选择" :show="agePickerVisible" :columns="agePickerColumns" :keyName="'text'"
:defaultIndex="agePickerDefaultIndex" @confirm="onAgePickerConfirm" @cancel="() => agePickerVisible = false"
@close="() => agePickerVisible = false"></up-picker>
<uni-popup ref="submitPopupRef" type="center">
<view style="width: 90vw;height:300rpx;background-color: #fff;border-radius: 20rpx;">
<view>
<view style="height: 80rpx;"></view>
<view style="font-size: 32rpx;font-weight: 500;color: #000;text-align: center;">发起预约成功!</view>
<view style="height:100rpx;"></view>
<view style="display: flex;width: 100%;height:100rpx;">
<button @click.stop open-type="share"
style=" background-color:#00AC4E;color: #fff;width: 50%;height: 100%; background-color:#00AC4E;"
class="center evaluate-btn" :data-item="reservation">
<text class="evaluate-btn-text">分享给好友</text>
</button>
<view
style="width: 50%;height: 100%;background-color: #f7f7f7;display: flex;align-items: center;justify-content: center;"
@click="submitPopupRef.close();">
<text>关闭</text>
</view>
</view>
</view>
</view>
</uni-popup>
</com-page-container-base>
</template>
<script setup>
import {
ref,
watch
} from 'vue';
import {
getConfigData,
getSubscribeMessage
} from '@/common/server/config'
import {
usePay
} from '@/common/server/interface/user'
import {
requestSubscribeMessage,
requestPayment
} from '@/common/utils'
import {
getDayDescription,
ceilMinuteToNext5
} from '@/common/system/timeUtile';
import {
isLogin
} from '@/common/server/user'
import {
forEach,
union
} from 'lodash';
import {
getDetail
} from '@/common/server/index'
import TimeSelectCell from '@/components/com/appointment/time-select-cell.vue'
import LabelField from '@/components/com/appointment/label-field.vue'
import LabelSlectField from '@/components/com/appointment/label-slect-field.vue'
import CardContainer from '@/components/com/appointment/card-container.vue'
import TagSelect from '@/components/com/appointment/tag-select.vue'
import ComAppointmentPickerData from '@/components/com/appointment/picker-data.vue'
import ComAppointmentRadioSelect from '@/components/com/appointment/radio-select.vue'
import {
getReservationRoomList,
addSQReservation,
cancelReservation,
canCreateSQReservation
} from '@/common/server/interface/sq'
const _containerBase = ref(null)
const submitPopupRef = ref(null)
// 年龄选择器状态
const agePickerVisible = ref(false)
const agePickerColumns = ref([
[],
[]
])
const agePickerDefaultIndex = ref([0, 0])
const reservationData = ref(null)
const lineHeight = ref("15rpx")
const timeRange = ref(["2小时", "3小时", "4小时", "自定义"])
const timeRangeValue = ref("")
// 房间选项
const roomOptions = ref([])
const roomPickerRef = ref(null)
//提交表单数据
const reservationInfo = ref({
room_id: 0, //房间id 非空
room_name: '请选择房间', //房间名称
start_time: 0, //开始时间 时间戳 非空
end_time: 0, //结束时间 时间戳 非空
max_age: 0, //最大年龄 默认0
min_age: 0, //最小年龄 非空
title: '', //组局名称 非空
extra_info: '', //其它说明 可为空
game_rule: '', //具体规则 非空
game_type: '', //玩法类型 非空
gender_limit: 0, //性别限制
is_smoking: 2, //是否禁烟
credit_limit: 0, //信誉限制
deposit_fee: 0, //鸽子费
player_count: 0, //人数
});
//自动计算结束时间
const onTimeRangeChange = async (val) => {
timeRangeValue.value = val;
console.log('timeRange change:', val)
if (val != "") {
await openUpDatesTimePicker(false);
if (reservationInfo.value.start_time > 0) {
var str = val;
if (str == "2小时") {
reservationInfo.value.end_time = reservationInfo.value.start_time + 2 * 60 * 60;
} else if (str == "3小时") {
reservationInfo.value.end_time = reservationInfo.value.start_time + 3 * 60 * 60;
} else if (str == "4小时") {
reservationInfo.value.end_time = reservationInfo.value.start_time + 4 * 60 * 60;
} else {
await openUpDatesTimePickerEnd(false);
}
}
}
}
const getRoomPickerName = () => {
return reservationInfo.value.room_name;
}
const tipsShow = () => {
submitPopupRef.value.open()
}
const openUpDatesTimePicker = async (isManual = true) => {
var now = reservationInfo.value.start_time * 1000;
var min = Date.now();
min += 1000 * 60 * 30;
min = ceilMinuteToNext5(min).valueOf();
if (reservationInfo.value.start_time == 0) {
now = Date.now();
now += 1000 * 60 * 30;
now = ceilMinuteToNext5(now).valueOf();
}
const startTime = await _containerBase.value.openUpDatesTimePicker(now, min, "预约开始时间")
// 直接设置到表单对象,毫秒转秒
reservationInfo.value.start_time = Math.floor(startTime / 1000)
// 手动选择时间则切换为"自定义"
if (isManual) {
timeRangeValue.value = "自定义"
}
}
const openUpDatesTimePickerEnd = async (isManual = true) => {
if (reservationInfo.value.start_time == 0) {
uni.showToast({
title: '请先选择开始时间',
icon: 'none'
})
return;
}
var now = reservationInfo.value.end_time * 1000;
var min = (reservationInfo.value.start_time * 1000 + 1000 * 60 * 30);
if (now == 0) {
now = (reservationInfo.value.start_time * 1000 + 1000 * 60 * 30);
}
//minDate+1000*60*30 最小间隔30分钟
const endTime = await _containerBase.value.openUpDatesTimePicker(now, min, "预约结束时间")
// 直接设置到表单对象,毫秒转秒
reservationInfo.value.end_time = Math.floor(endTime / 1000)
// 手动选择时间则切换为"自定义"
if (isManual) {
timeRangeValue.value = "自定义"
}
}
const maxPlayerCount = ref(0)
const openRoomPicker = async () => {
// 需先选择有效的开始/结束时间
if (!reservationInfo.value.start_time || !reservationInfo.value.end_time) {
uni.showToast({
title: '请先选择开始和结束时间',
icon: 'none'
})
return
}
if (reservationInfo.value.end_time <= reservationInfo.value.start_time) {
uni.showToast({
title: '结束时间需晚于开始时间',
icon: 'none'
})
return
}
// 无数据则尝试拉取
if (!roomOptions.value || roomOptions.value.length === 0) {
await tryLoadRooms()
}
if (!roomOptions.value || roomOptions.value.length === 0) {
uni.showToast({
title: '暂无可预约房间',
icon: 'none'
})
return
}
const selected = await roomPickerRef.value.show({
options: roomOptions.value,
valueKey: 'id',
labelKey: 'name',
modelValue: reservationInfo.value.room_id
})
if (selected) {
reservationInfo.value.room_id = selected.id
reservationInfo.value.room_name = selected.name
maxPlayerCount.value = selected.capacity
}
}
const peopleRange = ref([])
const peopleText = ref("请先选择房间");
const gameTypeRange = ref([])
const gameRuleRange = ref([])
const gameRuleText = ref("请先选择玩法类型");
const gameTypeRangeChange = (e) => {
console.log('gameTypeRangeChange:', e)
// gameRuleRange.value
if (e == 0) {
gameRuleRange.value = [];
reservationInfo.value.game_rule = 0;
gameRuleText.value = "请先选择玩法类型";
} else {
var f = gameTypeRange.value.find(it => it.value == e);
console.log('f', f);
gameRuleText.value = "请选择具体规则";
var temp = [];
if (f.children != null && f.children.length > 0) {
f.children.forEach(e => {
temp.push({
value: e.id,
text: e.name
})
})
}
gameRuleRange.value = temp;
}
}
const gameRuleRangeChange = (e) => {
console.log('gameRuleRangeChange:', e)
}
// 获取玩法类型文本
const getGameTypeText = (gameTypeValue) => {
if (!gameTypeValue || gameTypeValue === 0) {
return '';
}
const gameType = gameTypeRange.value.find(item => item.value === gameTypeValue);
return gameType ? gameType.text : '';
}
// 获取具体规则文本
const getGameRuleText = (gameRuleValue) => {
if (!gameRuleValue || gameRuleValue === 0) {
return '';
}
const gameRule = gameRuleRange.value.find(item => item.value === gameRuleValue);
return gameRule ? gameRule.text : '';
}
const smokingOptions = ref([
{ value: 2, text: '不禁烟' },
{ value: 1, text: '禁烟' },
])
const genderOptions = ref([
{ value: 0, text: '不限' },
{ value: 1, text: '男' },
{ value: 2, text: '女' },
])
const depositOptions = ref([
{ value: 0, text: '0元' },
{ value: 5, text: '5元' },
{ value: 10, text: '10元' },
{ value: -1, text: '自定义' },
])
const currentValue = ref(0)
const customDeposit = ref('')
const onCustomDepositInput = (val) => {
// 仅保留数字与小数点,但需求是整数金额,按只允许数字处理
let v = String(val).replace(/[^0-9]/g, '')
if (v === '') v = '0'
let n = Number(v)
if (n > 50) n = 50
if (n < 0) n = 0
customDeposit.value = String(n)
}
const increment = () => {
if (currentValue.value < 5) {
currentValue.value += 0.5
if (currentValue.value > 5) {
currentValue.value = 5
}
// 同步到表单对象
reservationInfo.value.credit_limit = currentValue.value
}
}
const decrement = () => {
if (currentValue.value > 0) {
currentValue.value -= 0.5
if (currentValue.value < 0) {
currentValue.value = 0
}
// 同步到表单对象
reservationInfo.value.credit_limit = currentValue.value
}
}
const changeLog = (e) => {
console.log('change事件:', e)
}
// 表单验证方法
const validateForm = () => {
const info = reservationInfo.value
// 必填字段验证
if (!info.room_id || info.room_id === 0) {
uni.showToast({
title: '请选择房间',
icon: 'none'
})
return false
}
if (!info.start_time || info.start_time === 0) {
uni.showToast({
title: '请选择开始时间',
icon: 'none'
})
return false
}
if (!info.end_time || info.end_time === 0) {
uni.showToast({
title: '请选择结束时间',
icon: 'none'
})
return false
}
if (info.end_time <= info.start_time) {
uni.showToast({
title: '结束时间需晚于开始时间',
icon: 'none'
})
return false
}
if (!info.title || info.title.trim() === '') {
uni.showToast({
title: '请输入组局名称',
icon: 'none'
})
return false
}
if (!info.player_count || info.player_count === 0) {
uni.showToast({
title: '请选择游玩人数',
icon: 'none'
})
return false
}
if (!info.game_type || info.game_type === 0) {
uni.showToast({
title: '请选择玩法类型',
icon: 'none'
})
return false
}
if (!info.game_rule || info.game_rule === 0) {
uni.showToast({
title: '请选择具体规则',
icon: 'none'
})
return false
}
// 年龄范围验证(如果设置了年龄限制)
if (info.min_age > 0 && info.max_age > 0 && info.min_age > info.max_age) {
uni.showToast({
title: '最小年龄不能大于最大年龄',
icon: 'none'
})
return false
}
return true
}
// 提交预约方法
const submitReservation = async () => {
var isLoginSucces = await isLogin();
if (!isLoginSucces) {
uni.navigateTo({
url: '/pages/me/login'
});
return;
}
// 验证表单
if (!validateForm()) {
return
}
try {
var messageId = await getSubscribeMessage(reservationInfo.value.deposit_fee);
console.log("messageId", messageId);
var subscribeMessage = await requestSubscribeMessage(messageId);
console.log("message", subscribeMessage);
// 显示加载状态
uni.showLoading({
title: '提交中...'
})
// 处理自定义鸽子费
let finalDeposit = reservationInfo.value.deposit_fee
if (finalDeposit === -1) {
const n = Number(customDeposit.value || 0)
if (isNaN(n) || n < 0 || n > 50) {
uni.showToast({ title: '自定义金额需在0-50之间', icon: 'none' })
return
}
finalDeposit = n
}
// 准备提交数据
const submitData = {
...reservationInfo.value,
// 确保信誉限制同步
credit_limit: currentValue.value,
deposit_fee: finalDeposit,
important_data: {}
}
// 将玩法类型和具体规则从数字转换为字符串
submitData.game_type = getGameTypeText(submitData.game_type);
submitData.game_rule = getGameRuleText(submitData.game_rule);
var important_data = "";
if (subscribeMessage.result != null) {
important_data = JSON.stringify(subscribeMessage.result);
}
submitData.important_data = important_data;
// 检测是否能创建预约 ,如果不能创建则提示并中断
const canCreateRes = await canCreateSQReservation(submitData)
if (!canCreateRes || canCreateRes.canCreate !== true) {
uni.hideLoading()
uni.showToast({
title: (canCreateRes && canCreateRes.message) ? canCreateRes.message : '当前条件不可创建预约',
icon: 'none'
})
return
}
var resPay = null;
if (submitData.deposit_fee > 0) {
resPay = await usePay(submitData.deposit_fee);
if (resPay == null) {
uni.showToast({
title: '鸽子费订单创建失败,请重新提交预约数据!',
icon: 'none'
})
return;
}
subscribeMessage.result.paymentId = resPay.paymentId;
if (subscribeMessage.result != null) {
important_data = JSON.stringify(subscribeMessage.result);
}
submitData.important_data = important_data;
}
// TODO: 调用后端API提交预约
// 调用后端API提交预约
const result = await addSQReservation(submitData)
uni.hideLoading()
console.log("result", result);
if (!result.success) {
uni.showToast({
title: result.message,
icon: 'none'
})
return;
}
if (submitData.deposit_fee > 0 && resPay != null) {
var payRes = await requestPayment(resPay);
if (payRes.success) {
uni.showToast({
title: '鸽子费支付成功',
icon: 'success'
})
} else {
await cancelReservation(result.data, "鸽子费支付失败,请重新预约!");
uni.showToast({
title: '鸽子费支付失败,请重新预约!',
icon: 'none'
})
return;
}
console.log("payRes", payRes);
}
var share_id = result.data;
var detailData = await getDetail(share_id);
if (detailData != null) {
reservationData.value = detailData;
tipsShow();
} else {
// 提交成功
uni.showToast({
title: '预约提交成功',
icon: 'success'
})
}
// 可以跳转到其他页面或重置表单
// uni.navigateBack() // 返回上一页
// 或者重置表单
resetForm()
} catch (error) {
uni.hideLoading()
console.error('提交预约失败:', error)
uni.showToast({
title: '提交失败,请重试',
icon: 'none'
})
}
}
// 重置表单方法(可选)
const resetForm = () => {
reservationInfo.value = {
room_id: 0,
room_name: '请选择房间',
start_time: 0,
end_time: 0,
max_age: 0,
min_age: 0,
title: '',
extra_info: '',
game_rule: '',
game_type: '',
gender_limit: 0,
is_smoking: 2,
credit_limit: 0,
deposit_fee: 0,
player_count: 0,
}
currentValue.value = 0
timeRangeValue.value = ""
gameRuleRange.value = []
peopleRange.value = []
peopleText.value = "请先选择房间"
gameRuleText.value = "请先选择玩法类型"
}
// 当开始或结束时间变化时自动刷新房间
watch([() => reservationInfo.value.start_time, () => reservationInfo.value.end_time], async ([s, e]) => {
await tryLoadRooms()
})
watch(() => reservationInfo.value.room_id, async (newId, oldId) => {
console.log('room_id changed:', oldId, '->', newId);
if (newId == 0) {
reservationInfo.value.player_count = 0;
peopleRange.value = [];
peopleText.value = "请先选择房间";
} else {
var t = [];
peopleText.value = "请选择游玩人数";
for (let i = 2; i <= maxPlayerCount.value; i++) {
t.push({
value: i,
text: i + '人'
});
}
console.log("peopleRange.value ", maxPlayerCount.value, t);
if (reservationInfo.value.player_count > maxPlayerCount.value) {
reservationInfo.value.player_count = 0;
}
peopleRange.value = t;
}
});
// 拉取可预约房间
const tryLoadRooms = async () => {
if (!reservationInfo.value.start_time || !reservationInfo.value.end_time) return
if (reservationInfo.value.end_time <= reservationInfo.value.start_time) return
const list = await getReservationRoomList(reservationInfo.value.start_time, reservationInfo.value.end_time)
// list.push()
if (Array.isArray(list)) {
roomOptions.value = Array.isArray(list) ? list : []
} else {
roomOptions.value = []
}
// 若当前选中房间不在可选列表中,则清空
console.log("房间id==>", reservationInfo.value.room_id, "房间列表==>", roomOptions.value);
if (!roomOptions.value.some(it => it.id === reservationInfo.value.room_id)) {
reservationInfo.value.room_id = 0;
reservationInfo.value.room_name = '请选择房间';
}
}
onLoad(async () => {
const config = await getConfigData();
console.log('config', config);
if (config != null && config.config != null) {
gameTypeRange.value = [...config.config.playingMethodOptions];
}
})
onShow(async () => {
// resetForm();
})
// 年龄列构建与显示/文案
const buildAgeColumns = () => {
const minList = [{
value: 0,
text: '不限'
}]
for (let i = 18; i <= 80; i++) minList.push({
value: i,
text: String(i)
})
const maxList = [{
value: 0,
text: '不限'
}]
for (let i = 18; i <= 80; i++) maxList.push({
value: i,
text: String(i)
})
agePickerColumns.value = [minList, maxList]
}
const getAgeRangeText = () => {
const {
min_age,
max_age
} = reservationInfo.value
if (min_age == 0 && max_age == 0) {
return '不限'
}
const minText = min_age === 0 ? '不限' : min_age + '岁'
const maxText = max_age === 0 ? '不限' : max_age + '岁'
return minText + ' - ' + maxText
}
const openAgePicker = () => {
buildAgeColumns()
// 计算默认索引
const {
min_age,
max_age
} = reservationInfo.value
const [mins, maxs] = agePickerColumns.value
const findIndexByValue = (arr, val) => {
const idx = arr.findIndex(it => Number(it.value) === Number(val))
return idx >= 0 ? idx : 0
}
agePickerDefaultIndex.value = [
findIndexByValue(mins, min_age ?? 0),
findIndexByValue(maxs, max_age ?? 0),
]
agePickerVisible.value = true
}
const onAgePickerConfirm = (e) => {
// uview-plus up-picker confirm 回调 e.value 为两列所选对象数组
const selected = e && e.value ? e.value : []
const min = selected[0] ? Number(selected[0].value) : 0
const max = selected[1] ? Number(selected[1].value) : 0
// 若最小值大于最大值,自动交换或归零“不限”优先
let minAge = min
let maxAge = max
if (minAge !== 0 && maxAge !== 0 && minAge > maxAge) {
[minAge, maxAge] = [maxAge, minAge]
}
reservationInfo.value.min_age = minAge
reservationInfo.value.max_age = maxAge
agePickerVisible.value = false
}
</script>
<style lang="scss">
.content {
width: 100%;
height: 100vh;
background-color: #F7F7F7;
}
/* 页面标题 */
.page-title {
margin-top: 100rpx;
text-align: center;
margin-bottom: 20rpx;
}
/* 输入外层统一样式 */
.input-wrapper {
border: 1px solid #515151;
border-radius: 4px;
}
/* 可点击的行年龄范围显示 */
.clickable-row {
font-size: 28rpx;
width: 100%;
display: flex;
align-items: center;
height: 60rpx;
}
/* 内联标签信誉左侧于等于 */
.inline-label {
font-size: 25.86rpx;
width: 120rpx;
}
/* 通用居中容器 */
.center {
display: flex;
align-items: center;
justify-content: center;
}
/* 提交按钮容器样式 */
.submit-button {
width: 90%;
border-radius: 10rpx;
margin: 30rpx auto 0;
background-color: #00AC4E;
}
/* 操作区一行布局重置 + 发起 */
.action-row {
width: 90%;
margin: 30rpx auto 0;
display: flex;
gap: 20rpx;
}
.reset-button {
flex: 0 0 30%;
height: 88rpx;
border-radius: 10rpx;
background-color: #FFFFFF;
color: #333;
}
.action-submit {
flex: 1 1 auto;
height: 88rpx;
margin: 0;
/* 覆盖原有 submit-button 的外边距使其与 reset 同行 */
}
/* 备注容器及文本 */
.note-container {
width: 90%;
margin: 20rpx auto 20rpx;
}
.muted-text {
color: #979797;
font-size: 24rpx;
}
.note-text {
font-size: 24rpx;
margin-left: 20rpx;
margin-bottom: 20rpx;
}
.counter-container {
display: flex;
align-items: center;
justify-content: center;
}
.counter-btn {
width: 80rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
padding: 0;
line-height: 1;
}
.minus-btn {
background-color: #f5f5f5;
border: 1px solid #eee;
}
.counter-value {
width: 90rpx;
text-align: center;
font-size: 28rpx;
font-weight: 500;
color: #333;
}
.custom-select ::v-deep .uni-input-input {
/* 正常状态下的边框颜色 */
border: 1px solid #007aff !important;
border-radius: 6px;
}
.flex-center-row {
display: flex;
}
</style>