feat(app): add membership edit modal with tab UI
- Replace detail-only modal with tab-based (detail/edit) modal - Detail tab shows membership card info, stats, and action buttons - Edit tab provides form to modify card type, remaining times, start/expire dates - Add loadDetailMembership, switchTab, initEditForm, onSaveMembership, onClearMembership functions - Add computed isTimeBasedCard for edit form conditional rendering - Append new styles for modal-header, tab-bar, membership-card, edit-form, etc. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -87,40 +87,187 @@
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- Detail modal -->
|
||||
<view v-if="showDetail && detailMember" class="modal-mask" @tap.self="showDetail = false">
|
||||
<!-- Detail/Edit modal -->
|
||||
<view v-if="showDetail && detailMember" class="modal-mask" @tap.self="closeModal">
|
||||
<view class="modal">
|
||||
<view class="detail-header">
|
||||
<view class="detail-avatar">
|
||||
<image v-if="detailMember.avatarUrl" class="avatar-img" :src="detailMember.avatarUrl" mode="aspectFill" />
|
||||
<view v-else class="avatar-placeholder avatar-placeholder--lg">
|
||||
<text class="avatar-text avatar-text--lg">{{ (detailMember.nickname || '?').slice(0, 1) }}</text>
|
||||
<!-- modal-header -->
|
||||
<view class="modal-header">
|
||||
<view class="modal-close-btn" @tap="closeModal">
|
||||
<text class="modal-close-icon">×</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Tab bar -->
|
||||
<view class="tab-bar">
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ 'tab-item--active': activeTab === 'detail' }"
|
||||
@tap="switchTab('detail')"
|
||||
>
|
||||
<text class="tab-text" :class="{ 'tab-text--active': activeTab === 'detail' }">详情</text>
|
||||
</view>
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ 'tab-item--active': activeTab === 'edit' }"
|
||||
@tap="switchTab('edit')"
|
||||
>
|
||||
<text class="tab-text" :class="{ 'tab-text--active': activeTab === 'edit' }">编辑</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Loading overlay for edit tab -->
|
||||
<view v-if="editLoading" class="edit-loading">
|
||||
<text class="edit-loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- Detail tab content -->
|
||||
<view v-if="activeTab === 'detail'" class="tab-content">
|
||||
<!-- User info -->
|
||||
<view class="detail-header">
|
||||
<view class="detail-avatar">
|
||||
<image v-if="detailMember.avatarUrl" class="avatar-img" :src="detailMember.avatarUrl" mode="aspectFill" />
|
||||
<view v-else class="avatar-placeholder avatar-placeholder--lg">
|
||||
<text class="avatar-text avatar-text--lg">{{ (detailMember.nickname || '?').slice(0, 1) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="detail-name">{{ detailMember.nickname || '未知用户' }}</text>
|
||||
<text class="detail-openid" @tap="copyOpenid(detailMember.openid)">
|
||||
{{ detailMember.openid }}
|
||||
</text>
|
||||
<text class="detail-phone">{{ detailMember.phone || '未绑定手机' }}</text>
|
||||
</view>
|
||||
|
||||
<!-- Stats -->
|
||||
<view class="detail-stats">
|
||||
<view class="detail-stat">
|
||||
<text class="detail-stat-value">{{ detailMember.totalBookings }}</text>
|
||||
<text class="detail-stat-label">总预约</text>
|
||||
</view>
|
||||
<view class="detail-stat">
|
||||
<text class="detail-stat-value">{{ detailMember.completedBookings }}</text>
|
||||
<text class="detail-stat-label">已完成</text>
|
||||
</view>
|
||||
<view class="detail-stat">
|
||||
<text class="detail-stat-value">{{ detailMember.cancelledBookings }}</text>
|
||||
<text class="detail-stat-label">已取消</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="detail-name">{{ detailMember.nickname || '未知用户' }}</text>
|
||||
<text class="detail-openid" @tap="copyOpenid(detailMember.openid)">
|
||||
{{ detailMember.openid }}
|
||||
</text>
|
||||
<text class="detail-phone">{{ detailMember.phone || '未绑定手机' }}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-stats">
|
||||
<view class="detail-stat">
|
||||
<text class="detail-stat-value">{{ detailMember.totalBookings }}</text>
|
||||
<text class="detail-stat-label">总预约</text>
|
||||
<!-- Membership card -->
|
||||
<view v-if="detailMembership" class="membership-card">
|
||||
<view class="membership-card-header">
|
||||
<text class="membership-card-name">{{ detailMembership.cardTypeName }}</text>
|
||||
<text class="membership-card-badge" :class="'badge--' + detailMembership.status.toLowerCase()">
|
||||
{{ statusLabel(detailMembership.status) }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="membership-card-info">
|
||||
<view class="membership-info-item">
|
||||
<text class="membership-info-label">剩余次数</text>
|
||||
<text class="membership-info-value">{{ detailMembership.remainingTimes ?? '不限' }}</text>
|
||||
</view>
|
||||
<view class="membership-info-item">
|
||||
<text class="membership-info-label">开始日期</text>
|
||||
<text class="membership-info-value">{{ formatDate(detailMembership.startDate) }}</text>
|
||||
</view>
|
||||
<view class="membership-info-item">
|
||||
<text class="membership-info-label">到期日期</text>
|
||||
<text class="membership-info-value">{{ formatDate(detailMembership.expireDate) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="detail-stat">
|
||||
<text class="detail-stat-value">{{ detailMember.completedBookings }}</text>
|
||||
<text class="detail-stat-label">已完成</text>
|
||||
|
||||
<!-- No membership -->
|
||||
<view v-else class="no-membership">
|
||||
<text class="no-membership-text">暂无会员卡</text>
|
||||
<view class="no-membership-btn" @tap="goEdit">
|
||||
<text class="no-membership-btn-text">去开卡</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="detail-stat">
|
||||
<text class="detail-stat-value">{{ detailMember.cancelledBookings }}</text>
|
||||
<text class="detail-stat-label">已取消</text>
|
||||
|
||||
<!-- Danger zone -->
|
||||
<view v-if="detailMembership" class="danger-zone">
|
||||
<view class="danger-btn" @tap="onClearMembership">
|
||||
<text class="danger-btn-text">解除会员卡</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="modal-close" @tap="showDetail = false">
|
||||
<text class="modal-close-text">关闭</text>
|
||||
<!-- Edit tab content -->
|
||||
<view v-if="activeTab === 'edit'" class="tab-content">
|
||||
<view class="edit-form">
|
||||
<!-- Card type -->
|
||||
<view class="form-item">
|
||||
<text class="form-label">卡类型</text>
|
||||
<picker
|
||||
class="form-picker"
|
||||
mode="selector"
|
||||
:value="editForm.cardTypeIndex"
|
||||
:range="editCardTypes"
|
||||
range-key="name"
|
||||
@change="onCardTypeChangeForEdit"
|
||||
>
|
||||
<view class="form-picker-inner">
|
||||
<text class="form-picker-text">{{ editCardTypes[editForm.cardTypeIndex]?.name || '请选择' }}</text>
|
||||
<text class="form-picker-arrow">▾</text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<!-- Remaining times (for TIMES/TRIAL) -->
|
||||
<view v-if="isTimeBasedCard" class="form-item">
|
||||
<text class="form-label">剩余次数</text>
|
||||
<input
|
||||
class="form-input"
|
||||
type="number"
|
||||
v-model="editForm.remainingTimes"
|
||||
placeholder="请输入剩余次数"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- Start date -->
|
||||
<view class="form-item">
|
||||
<text class="form-label">开始日期</text>
|
||||
<picker
|
||||
class="form-picker"
|
||||
mode="date"
|
||||
:value="editForm.startDate"
|
||||
@change="onStartDateChange"
|
||||
>
|
||||
<view class="form-picker-inner">
|
||||
<text class="form-picker-text">{{ editForm.startDate || '请选择' }}</text>
|
||||
<text class="form-picker-arrow">▾</text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<!-- Expire date -->
|
||||
<view class="form-item">
|
||||
<text class="form-label">到期日期</text>
|
||||
<picker
|
||||
class="form-picker"
|
||||
mode="date"
|
||||
:value="editForm.expireDate"
|
||||
@change="onExpireDateChange"
|
||||
>
|
||||
<view class="form-picker-inner">
|
||||
<text class="form-picker-text">{{ editForm.expireDate || '请选择' }}</text>
|
||||
<text class="form-picker-arrow">▾</text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Submit button -->
|
||||
<view class="edit-submit">
|
||||
<view
|
||||
class="submit-btn"
|
||||
:class="{ 'submit-btn--disabled': editSubmitting }"
|
||||
@tap="onSaveMembership"
|
||||
>
|
||||
<text class="submit-btn-text">{{ editSubmitting ? '保存中...' : '保存' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -128,7 +275,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { onReachBottom } from '@dcloudio/uni-app'
|
||||
import CustomNavBar from '../../components/CustomNavBar.vue'
|
||||
import { getSystemLayout } from '../../utils/system'
|
||||
@@ -151,6 +298,26 @@ const hasMore = ref(false)
|
||||
const showDetail = ref(false)
|
||||
const detailMember = ref<MemberSummary | null>(null)
|
||||
|
||||
const activeTab = ref<'detail' | 'edit'>('detail')
|
||||
const detailMembership = ref<any>(null)
|
||||
const editLoading = ref(false)
|
||||
const editSubmitting = ref(false)
|
||||
const editCardTypes = ref<any[]>([])
|
||||
|
||||
const editForm = ref({
|
||||
cardTypeIndex: 0,
|
||||
cardTypeId: '',
|
||||
remainingTimes: null as number | null,
|
||||
startDate: '',
|
||||
expireDate: '',
|
||||
manuallyEditedExpire: false,
|
||||
})
|
||||
|
||||
const isTimeBasedCard = computed(() => {
|
||||
const card = editCardTypes.value[editForm.value.cardTypeIndex]
|
||||
return card && (card.type === 'TIMES' || card.type === 'TRIAL')
|
||||
})
|
||||
|
||||
const LIMIT = 20
|
||||
|
||||
const cardTypeOptions = [
|
||||
@@ -225,6 +392,9 @@ onReachBottom(() => {
|
||||
function openDetail(m: MemberSummary) {
|
||||
detailMember.value = m
|
||||
showDetail.value = true
|
||||
detailMembership.value = null
|
||||
activeTab.value = 'detail'
|
||||
loadDetailMembership(m.userId)
|
||||
}
|
||||
|
||||
function copyOpenid(openid: string) {
|
||||
@@ -234,6 +404,171 @@ function copyOpenid(openid: string) {
|
||||
})
|
||||
}
|
||||
|
||||
function formatDate(dateStr: string): string {
|
||||
if (!dateStr) return '-'
|
||||
return dateStr.slice(0, 10)
|
||||
}
|
||||
|
||||
function statusLabel(status: string): string {
|
||||
const map: Record<string, string> = {
|
||||
ACTIVE: '有效',
|
||||
EXPIRED: '已过期',
|
||||
USED_UP: '已用完',
|
||||
}
|
||||
return map[status] || status
|
||||
}
|
||||
|
||||
async function loadDetailMembership(userId: string) {
|
||||
detailMembership.value = null
|
||||
editLoading.value = true
|
||||
try {
|
||||
const result = await adminStore.getUserMembership(userId)
|
||||
detailMembership.value = result.membership
|
||||
} catch {
|
||||
} finally {
|
||||
editLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function switchTab(tab: 'detail' | 'edit') {
|
||||
activeTab.value = tab
|
||||
if (tab === 'edit') {
|
||||
if (!editCardTypes.value.length) {
|
||||
adminStore.fetchCardTypes().then((types) => {
|
||||
editCardTypes.value = types
|
||||
})
|
||||
}
|
||||
if (!detailMembership.value) {
|
||||
loadDetailMembership(detailMember.value!.userId).then(() => {
|
||||
initEditForm()
|
||||
})
|
||||
} else {
|
||||
initEditForm()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initEditForm() {
|
||||
const m = detailMembership.value
|
||||
const types = editCardTypes.value
|
||||
if (m) {
|
||||
const idx = types.findIndex((t: any) => t.id === m.cardTypeId)
|
||||
editForm.value = {
|
||||
cardTypeIndex: idx >= 0 ? idx : 0,
|
||||
cardTypeId: m.cardTypeId,
|
||||
remainingTimes: m.remainingTimes,
|
||||
startDate: m.startDate.slice(0, 10),
|
||||
expireDate: m.expireDate.slice(0, 10),
|
||||
manuallyEditedExpire: false,
|
||||
}
|
||||
} else {
|
||||
editForm.value = {
|
||||
cardTypeIndex: 0,
|
||||
cardTypeId: types[0]?.id || '',
|
||||
remainingTimes: types[0]?.totalTimes ?? null,
|
||||
startDate: formatDate2(new Date()),
|
||||
expireDate: calculateExpireDate(formatDate2(new Date()), types[0]?.durationDays ?? 30),
|
||||
manuallyEditedExpire: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function formatDate2(date: Date): string {
|
||||
const y = date.getFullYear()
|
||||
const m = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const d = String(date.getDate()).padStart(2, '0')
|
||||
return `${y}-${m}-${d}`
|
||||
}
|
||||
|
||||
function calculateExpireDate(startDate: string, durationDays: number): string {
|
||||
const d = new Date(startDate)
|
||||
d.setDate(d.getDate() + durationDays)
|
||||
return formatDate2(d)
|
||||
}
|
||||
|
||||
function onCardTypeChangeForEdit(e: { detail: { value: number } }) {
|
||||
const idx = e.detail.value
|
||||
const cardType = editCardTypes.value[idx]
|
||||
editForm.value.cardTypeIndex = idx
|
||||
editForm.value.cardTypeId = cardType.id
|
||||
if (cardType.totalTimes != null) {
|
||||
editForm.value.remainingTimes = cardType.totalTimes
|
||||
}
|
||||
if (!editForm.value.manuallyEditedExpire) {
|
||||
editForm.value.startDate = formatDate2(new Date())
|
||||
editForm.value.expireDate = calculateExpireDate(formatDate2(new Date()), cardType.durationDays)
|
||||
}
|
||||
}
|
||||
|
||||
function onStartDateChange(e: { detail: { value: string } }) {
|
||||
editForm.value.startDate = e.detail.value
|
||||
if (!editForm.value.manuallyEditedExpire) {
|
||||
const cardType = editCardTypes.value[editForm.value.cardTypeIndex]
|
||||
if (cardType) {
|
||||
editForm.value.expireDate = calculateExpireDate(e.detail.value, cardType.durationDays)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onExpireDateChange(e: { detail: { value: string } }) {
|
||||
editForm.value.expireDate = e.detail.value
|
||||
editForm.value.manuallyEditedExpire = true
|
||||
}
|
||||
|
||||
function goEdit() {
|
||||
switchTab('edit')
|
||||
}
|
||||
|
||||
async function onSaveMembership() {
|
||||
if (editSubmitting.value) return
|
||||
const userId = detailMember.value?.userId
|
||||
if (!userId) return
|
||||
editSubmitting.value = true
|
||||
try {
|
||||
await adminStore.updateUserMembership(userId, {
|
||||
cardTypeId: editForm.value.cardTypeId,
|
||||
remainingTimes: editForm.value.remainingTimes,
|
||||
startDate: editForm.value.startDate,
|
||||
expireDate: editForm.value.expireDate,
|
||||
})
|
||||
uni.showToast({ title: '保存成功', icon: 'success' })
|
||||
activeTab.value = 'detail'
|
||||
await loadDetailMembership(userId)
|
||||
loadMembers(false)
|
||||
} catch {
|
||||
uni.showToast({ title: '保存失败', icon: 'none' })
|
||||
} finally {
|
||||
editSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function onClearMembership() {
|
||||
const userId = detailMember.value?.userId
|
||||
if (!userId) return
|
||||
uni.showModal({
|
||||
title: '确认解除',
|
||||
content: '确定要解除该用户的会员卡吗?',
|
||||
confirmColor: '#e64329',
|
||||
success: async (res) => {
|
||||
if (!res.confirm) return
|
||||
try {
|
||||
await adminStore.deleteUserMembership(userId)
|
||||
uni.showToast({ title: '已解除', icon: 'success' })
|
||||
showDetail.value = false
|
||||
loadMembers(false)
|
||||
} catch {
|
||||
uni.showToast({ title: '操作失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
showDetail.value = false
|
||||
activeTab.value = 'detail'
|
||||
detailMembership.value = null
|
||||
}
|
||||
|
||||
onMounted(() => loadMembers(true))
|
||||
</script>
|
||||
|
||||
@@ -546,4 +881,270 @@ onMounted(() => loadMembers(true))
|
||||
}
|
||||
|
||||
.modal-close-text { font-size: 28rpx; color: $text-secondary; }
|
||||
|
||||
/* ── Modal header ─────────────────────────── */
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.modal-close-btn {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: $bg-page;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.modal-close-icon {
|
||||
font-size: 36rpx;
|
||||
color: $text-secondary;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* ── Tab bar ──────────────────────────────── */
|
||||
.tab-bar {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: $bg-page;
|
||||
border-radius: $radius-md;
|
||||
}
|
||||
|
||||
.tab-item--active {
|
||||
background: $brand-color;
|
||||
}
|
||||
|
||||
.tab-text {
|
||||
font-size: 28rpx;
|
||||
color: $text-secondary;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tab-text--active {
|
||||
color: $accent-color;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* ── Tab content ──────────────────────────── */
|
||||
.tab-content {
|
||||
min-height: 400rpx;
|
||||
}
|
||||
|
||||
.edit-loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 400rpx;
|
||||
}
|
||||
|
||||
.edit-loading-text {
|
||||
font-size: 28rpx;
|
||||
color: $text-hint;
|
||||
}
|
||||
|
||||
/* ── Membership card ──────────────────────── */
|
||||
.membership-card {
|
||||
background: $bg-page;
|
||||
border-radius: $radius-md;
|
||||
padding: 28rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.membership-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.membership-card-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: $brand-color;
|
||||
}
|
||||
|
||||
.membership-card-badge {
|
||||
font-size: 22rpx;
|
||||
font-weight: 600;
|
||||
padding: 4rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.badge--active {
|
||||
color: #52c41a;
|
||||
background: rgba(#52c41a, 0.1);
|
||||
}
|
||||
|
||||
.badge--expired {
|
||||
color: #999;
|
||||
background: rgba(#999, 0.1);
|
||||
}
|
||||
|
||||
.badge--used_up {
|
||||
color: #e64329;
|
||||
background: rgba(#e64329, 0.1);
|
||||
}
|
||||
|
||||
.membership-card-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.membership-info-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.membership-info-label {
|
||||
font-size: 26rpx;
|
||||
color: $text-hint;
|
||||
}
|
||||
|
||||
.membership-info-value {
|
||||
font-size: 26rpx;
|
||||
color: $text-primary;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ── No membership ────────────────────────── */
|
||||
.no-membership {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
padding: 48rpx 0;
|
||||
}
|
||||
|
||||
.no-membership-text {
|
||||
font-size: 28rpx;
|
||||
color: $text-hint;
|
||||
}
|
||||
|
||||
.no-membership-btn {
|
||||
background: $brand-color;
|
||||
border-radius: $radius-md;
|
||||
padding: 20rpx 48rpx;
|
||||
}
|
||||
|
||||
.no-membership-btn-text {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: $accent-color;
|
||||
}
|
||||
|
||||
/* ── Danger zone ─────────────────────────── */
|
||||
.danger-zone {
|
||||
margin-top: 32rpx;
|
||||
padding-top: 32rpx;
|
||||
border-top: 1rpx solid $border-color;
|
||||
}
|
||||
|
||||
.danger-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(#e64329, 0.08);
|
||||
border-radius: $radius-md;
|
||||
}
|
||||
|
||||
.danger-btn-text {
|
||||
font-size: 28rpx;
|
||||
color: #e64329;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ── Edit form ───────────────────────────── */
|
||||
.edit-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: 26rpx;
|
||||
color: $text-secondary;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
height: 80rpx;
|
||||
background: $bg-page;
|
||||
border-radius: $radius-md;
|
||||
padding: 0 24rpx;
|
||||
font-size: 28rpx;
|
||||
color: $text-primary;
|
||||
}
|
||||
|
||||
.form-picker {
|
||||
height: 80rpx;
|
||||
background: $bg-page;
|
||||
border-radius: $radius-md;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.form-picker-inner {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.form-picker-text {
|
||||
font-size: 28rpx;
|
||||
color: $text-primary;
|
||||
}
|
||||
|
||||
.form-picker-arrow {
|
||||
font-size: 20rpx;
|
||||
color: $text-hint;
|
||||
}
|
||||
|
||||
/* ── Edit submit ──────────────────────────── */
|
||||
.edit-submit {
|
||||
margin-top: 32rpx;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background: $brand-color;
|
||||
border-radius: 44rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.submit-btn--disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.submit-btn-text {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: $accent-color;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user