|
@@ -0,0 +1,362 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <view class="page">
|
|
|
|
|
+ <!-- 抬头列表 -->
|
|
|
|
|
+ <view class="block" v-if="titleList.length">
|
|
|
|
|
+ <view
|
|
|
|
|
+ v-for="(item, idx) in titleList"
|
|
|
|
|
+ :key="item.id"
|
|
|
|
|
+ class="swipe-clip"
|
|
|
|
|
+ :style="{ width: itemWidth + 'px' }"
|
|
|
|
|
+ >
|
|
|
|
|
+ <view
|
|
|
|
|
+ class="swipe-track"
|
|
|
|
|
+ :class="{ default: item.isDefault }"
|
|
|
|
|
+ :style="trackStyle(idx)"
|
|
|
|
|
+ @touchstart="onTouchStart($event, idx)"
|
|
|
|
|
+ @touchmove="onTouchMove($event, idx)"
|
|
|
|
|
+ @touchend="onTouchEnd(idx)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <view
|
|
|
|
|
+ class="item-content"
|
|
|
|
|
+ :style="{ width: itemWidth + 'px' }"
|
|
|
|
|
+ >
|
|
|
|
|
+ <view class="item-header">
|
|
|
|
|
+ <view class="item-title-row">
|
|
|
|
|
+ <text class="title-name">{{ item.invoiceTitle }}</text>
|
|
|
|
|
+ <text class="type-tag" :class="item.invoiceType === 'ORGANIZATION' ? 'tag-org' : 'tag-personal'">
|
|
|
|
|
+ {{ item.invoiceType === 'ORGANIZATION' ? '企业' : '个人' }}
|
|
|
|
|
+ </text>
|
|
|
|
|
+ <text class="default-tag" v-if="item.isDefault">默认</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="item-detail" v-if="item.taxId">税号:{{ item.taxId }}</view>
|
|
|
|
|
+ <view class="item-footer" v-if="!item.isDefault">
|
|
|
|
|
+ <text class="set-default-btn" @click.stop="setDefault(item.id)">设为默认</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="item-btns" :style="{ width: btnWidth + 'px' }">
|
|
|
|
|
+ <view class="action-btn edit-btn" @click.stop="editTitle(item)">编辑</view>
|
|
|
|
|
+ <view class="action-btn delete-btn" @click.stop="removeTitle(item.id)">删除</view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 空状态 -->
|
|
|
|
|
+ <view class="empty-state" v-else>
|
|
|
|
|
+ <uni-icons type="compose" size="64" color="rgba(0,0,0,0.1)"></uni-icons>
|
|
|
|
|
+ <text class="empty-title">暂无保存的抬头</text>
|
|
|
|
|
+ <text class="empty-desc">添加抬头后可快速开具发票</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 底部新增按钮 -->
|
|
|
|
|
+ <view class="add-row">
|
|
|
|
|
+ <view class="add-btn" @click="openForm()">新增抬头</view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 编辑/新增弹窗 -->
|
|
|
|
|
+ <view class="modal-mask" :class="{ visible: showForm }" v-if="showForm" @click="closeForm">
|
|
|
|
|
+ <view class="modal-content" :class="{ visible: showForm }" @click.stop>
|
|
|
|
|
+ <!-- 拖拽手柄 -->
|
|
|
|
|
+ <view class="modal-handle"><view class="handle-bar"></view></view>
|
|
|
|
|
+
|
|
|
|
|
+ <view class="modal-header">
|
|
|
|
|
+ <text class="modal-title">{{ editId ? '编辑抬头' : '新增抬头' }}</text>
|
|
|
|
|
+ <view class="modal-close" @click="closeForm">
|
|
|
|
|
+ <uni-icons type="closeempty" size="20" color="rgba(0,0,0,0.4)"></uni-icons>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <view class="modal-body" :style="{ maxHeight: maxModalHeight + 'px' }">
|
|
|
|
|
+ <view class="form-group">
|
|
|
|
|
+ <view class="form-label">发票类型</view>
|
|
|
|
|
+ <view class="type-options">
|
|
|
|
|
+ <view class="type-opt" :class="{ active: editForm.invoiceType === 'INDIVIDUAL' }" @click="editForm.invoiceType = 'INDIVIDUAL'">个人</view>
|
|
|
|
|
+ <view class="type-opt" :class="{ active: editForm.invoiceType === 'ORGANIZATION' }" @click="editForm.invoiceType = 'ORGANIZATION'">企业</view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <view class="form-group">
|
|
|
|
|
+ <view class="form-label">
|
|
|
|
|
+ {{ editForm.invoiceType === 'ORGANIZATION' ? '公司名称' : '姓名' }}
|
|
|
|
|
+ <text class="required">*</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="form-row"><input class="form-input" v-model="editForm.invoiceTitle" :placeholder="editForm.invoiceType === 'ORGANIZATION' ? '请输入公司全称' : '请输入姓名'" placeholder-class="input-placeholder" /></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <template v-if="editForm.invoiceType === 'ORGANIZATION'">
|
|
|
|
|
+ <view class="form-section-label">企业信息</view>
|
|
|
|
|
+ <view class="form-group">
|
|
|
|
|
+ <view class="form-label">公司税号<text class="required">*</text></view>
|
|
|
|
|
+ <view class="form-row"><input class="form-input" v-model="editForm.taxId" placeholder="请输入纳税人识别号" placeholder-class="input-placeholder" /></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="form-group"><view class="form-label">公司地址</view><view class="form-row"><input class="form-input" v-model="editForm.address" placeholder="请输入注册地址" placeholder-class="input-placeholder" /></view>
|
|
|
|
|
+ <view class="form-group"><view class="form-label">公司电话</view><view class="form-row"><input class="form-input" v-model="editForm.phone" type="number" placeholder="请输入公司电话" placeholder-class="input-placeholder" /></view>
|
|
|
|
|
+ <view class="form-group"><view class="form-label">开户银行</view><view class="form-row"><input class="form-input" v-model="editForm.bankName" placeholder="请输入开户行名称" placeholder-class="input-placeholder" /></view>
|
|
|
|
|
+ <view class="form-group"><view class="form-label">银行账号</view><view class="form-row"><input class="form-input" v-model="editForm.bankAccount" type="number" placeholder="请输入银行账号" placeholder-class="input-placeholder" /></view>
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <view class="form-section-label">其他</view>
|
|
|
|
|
+ <view class="form-group">
|
|
|
|
|
+ <view class="form-label">接收邮箱</view>
|
|
|
|
|
+ <view class="form-row"><input class="form-input" v-model="editForm.email" placeholder="选填,用于接收电子发票" placeholder-class="input-placeholder" /></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <view class="form-group form-switch">
|
|
|
|
|
+ <view class="form-label">设为默认抬头</view>
|
|
|
|
|
+ <view class="switch" :class="{ on: editForm.isDefault }" @click="editForm.isDefault = !editForm.isDefault"><view class="switch-dot"></view></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <view class="modal-footer">
|
|
|
|
|
+ <view class="btn-cancel" @click="closeForm">取消</view>
|
|
|
|
|
+ <view class="btn-save" :class="{ loading: saving }" @click="saveTitle">
|
|
|
|
|
+ <text v-if="!saving">保存</text>
|
|
|
|
|
+ <text v-else>保存中</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup lang="ts">
|
|
|
|
|
+import { onShow } from "@dcloudio/uni-app";
|
|
|
|
|
+import { ref, reactive } from "vue";
|
|
|
|
|
+import { fetchTitleList, createTitle, updateTitle, deleteTitle, setDefaultTitle } from "@/api";
|
|
|
|
|
+
|
|
|
|
|
+interface InvoiceTitleItem {
|
|
|
|
|
+ id: number; userId: number; invoiceType: string; invoiceTitle: string;
|
|
|
|
|
+ taxId?: string; address?: string; telephone?: string; phone?: string;
|
|
|
|
|
+ bankName?: string; bankAccount?: string; email?: string; isDefault?: boolean;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface EditForm {
|
|
|
|
|
+ invoiceType: string; invoiceTitle: string; taxId: string; address: string;
|
|
|
|
|
+ phone: string; bankName: string; bankAccount: string; email: string; isDefault: boolean;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// --- swipe ---
|
|
|
|
|
+const swipedIdx = ref(-1);
|
|
|
|
|
+const swipeX = ref(0);
|
|
|
|
|
+const touchStartX = ref(0);
|
|
|
|
|
+const touchMoved = ref(false);
|
|
|
|
|
+
|
|
|
|
|
+function rpxToPx(rpx: number) {
|
|
|
|
|
+ return rpx * (uni.getSystemInfoSync().windowWidth / 750);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function closeSwipe() { swipeX.value = 0; swipedIdx.value = -1; }
|
|
|
|
|
+
|
|
|
|
|
+// layout constants (px)
|
|
|
|
|
+const itemWidth = uni.getSystemInfoSync().windowWidth - rpxToPx(40);
|
|
|
|
|
+const btnWidth = rpxToPx(160);
|
|
|
|
|
+
|
|
|
|
|
+function trackStyle(idx: number) {
|
|
|
|
|
+ const x = swipedIdx.value === idx ? swipeX.value : 0;
|
|
|
|
|
+ return { transform: 'translateX(' + x + 'px)', width: (itemWidth + btnWidth) + 'px' };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function onTouchStart(e: { touches: Array<{ clientX: number }> }, idx: number) {
|
|
|
|
|
+ if (swipedIdx.value !== idx && swipedIdx.value !== -1) closeSwipe();
|
|
|
|
|
+ touchMoved.value = false;
|
|
|
|
|
+ touchStartX.value = e.touches[0].clientX;
|
|
|
|
|
+ swipeX.value = swipedIdx.value === idx ? -btnWidth : 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function onTouchMove(e: { touches: Array<{ clientX: number }> }, idx: number) {
|
|
|
|
|
+ const dx = e.touches[0].clientX - touchStartX.value;
|
|
|
|
|
+ if (!touchMoved.value && Math.abs(dx) > 8) touchMoved.value = true;
|
|
|
|
|
+ if (!touchMoved.value) return;
|
|
|
|
|
+ const base = swipedIdx.value === idx ? -btnWidth : 0;
|
|
|
|
|
+ swipeX.value = Math.max(-btnWidth, Math.min(0, base + dx));
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function onTouchEnd(idx: number) {
|
|
|
|
|
+ if (!touchMoved.value) { closeSwipe(); return; }
|
|
|
|
|
+ if (swipeX.value < -btnWidth * 0.4) { swipeX.value = -btnWidth; swipedIdx.value = idx; }
|
|
|
|
|
+ else closeSwipe();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// --- state ---
|
|
|
|
|
+const titleList = ref<InvoiceTitleItem[]>([]);
|
|
|
|
|
+const showForm = ref(false);
|
|
|
|
|
+const editId = ref(0);
|
|
|
|
|
+const saving = ref(false);
|
|
|
|
|
+const maxModalHeight = ref(600);
|
|
|
|
|
+
|
|
|
|
|
+const editForm = reactive<EditForm>({
|
|
|
|
|
+ invoiceType: "ORGANIZATION", invoiceTitle: "", taxId: "", address: "",
|
|
|
|
|
+ phone: "", bankName: "", bankAccount: "", email: "", isDefault: false,
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+const loadList = () => { fetchTitleList().then((res: InvoiceTitleItem[]) => { titleList.value = res || []; if (titleList.value.length && !hintShown) playHint(); }).catch(() => {}); };
|
|
|
|
|
+
|
|
|
|
|
+const hintShown = ref(false);
|
|
|
|
|
+
|
|
|
|
|
+function playHint() {
|
|
|
|
|
+ hintShown.value = true;
|
|
|
|
|
+ swipedIdx.value = 0;
|
|
|
|
|
+ swipeX.value = 0;
|
|
|
|
|
+ // trigger transition in
|
|
|
|
|
+ setTimeout(() => { swipeX.value = -btnWidth; }, 100);
|
|
|
|
|
+ // hold
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ // snap back
|
|
|
|
|
+ swipeX.value = 0;
|
|
|
|
|
+ setTimeout(() => { swipedIdx.value = -1; }, 250);
|
|
|
|
|
+ }, 1600);
|
|
|
|
|
+}
|
|
|
|
|
+const openForm = () => { resetForm(); showForm.value = true; calcModalHeight(); };
|
|
|
|
|
+const closeForm = () => { if (!saving.value) showForm.value = false; };
|
|
|
|
|
+const calcModalHeight = () => { maxModalHeight.value = (uni.getSystemInfoSync().windowHeight || 800) * 0.7; };
|
|
|
|
|
+
|
|
|
|
|
+const resetForm = () => {
|
|
|
|
|
+ editId.value = 0; editForm.invoiceType = "ORGANIZATION"; editForm.invoiceTitle = "";
|
|
|
|
|
+ editForm.taxId = ""; editForm.address = ""; editForm.phone = ""; editForm.bankName = "";
|
|
|
|
|
+ editForm.bankAccount = ""; editForm.email = ""; editForm.isDefault = false;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const editTitle = (item: InvoiceTitleItem) => {
|
|
|
|
|
+ editId.value = item.id; editForm.invoiceType = item.invoiceType || "ORGANIZATION";
|
|
|
|
|
+ editForm.invoiceTitle = item.invoiceTitle || ""; editForm.taxId = item.taxId || "";
|
|
|
|
|
+ editForm.address = item.address || ""; editForm.phone = item.phone || item.telephone || "";
|
|
|
|
|
+ editForm.bankName = item.bankName || ""; editForm.bankAccount = item.bankAccount || "";
|
|
|
|
|
+ editForm.email = item.email || ""; editForm.isDefault = !!item.isDefault;
|
|
|
|
|
+ showForm.value = true; calcModalHeight(); closeSwipe();
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const saveTitle = () => {
|
|
|
|
|
+ if (!editForm.invoiceTitle.trim()) { uni.showToast({ title: "请填写抬头名称", icon: "none" }); return; }
|
|
|
|
|
+ if (editForm.invoiceType === "ORGANIZATION" && !editForm.taxId.trim()) { uni.showToast({ title: "企业抬头请填写税号", icon: "none" }); return; }
|
|
|
|
|
+ saving.value = true;
|
|
|
|
|
+ const data: Record<string, unknown> = { ...editForm };
|
|
|
|
|
+ const req = editId.value ? updateTitle(editId.value, data) : createTitle(data);
|
|
|
|
|
+ req.then(() => { saving.value = false; showForm.value = false; resetForm(); loadList(); uni.showToast({ title: editId.value ? "修改成功" : "已添加", icon: "success" }); })
|
|
|
|
|
+ .catch((err: { errMsg?: string }) => { saving.value = false; uni.showModal({ content: err.errMsg || "操作失败,请重试" }); });
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const removeTitle = (id: number) => {
|
|
|
|
|
+ closeSwipe();
|
|
|
|
|
+ uni.showModal({ title: "确认删除", content: "删除后不可恢复,确定删除此抬头吗?", confirmText: "删除", confirmColor: "#e74c3c",
|
|
|
|
|
+ success: (res: { confirm: boolean }) => { if (res.confirm) { deleteTitle(id).then(() => { loadList(); uni.showToast({ title: "已删除", icon: "success" }); }).catch((err: { errMsg?: string }) => uni.showToast({ title: err.errMsg || "删除失败", icon: "none" })); } },
|
|
|
|
|
+ });
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const setDefault = (id: number) => { setDefaultTitle(id).then(() => { loadList(); uni.showToast({ title: "已设为默认", icon: "success" }); }).catch((err: { errMsg?: string }) => uni.showToast({ title: err.errMsg || "操作失败", icon: "none" })); };
|
|
|
|
|
+
|
|
|
|
|
+onShow(() => { loadList(); });
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style lang="scss">
|
|
|
|
|
+// === Page ===
|
|
|
|
|
+.page { min-height: 100vh; background-color: #f6f7fa; padding: 20rpx 20rpx 140rpx; }
|
|
|
|
|
+
|
|
|
|
|
+// === Block ===
|
|
|
|
|
+.block { background: #fff; border-radius: 20rpx; overflow: hidden; }
|
|
|
|
|
+
|
|
|
|
|
+// === Swipe ===
|
|
|
|
|
+.swipe-clip {
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+
|
|
|
|
|
+ &:not(:last-child) {
|
|
|
|
|
+ border-bottom: 1rpx solid rgba(0, 0, 0, 0.06);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.swipe-track {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: nowrap;
|
|
|
|
|
+ transition: transform 0.2s ease-out;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+
|
|
|
|
|
+ &.default {
|
|
|
|
|
+ background: linear-gradient(135deg, rgba(65, 157, 149, 0.03) 0%, rgba(65, 157, 149, 0.01) 100%);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.item-content {
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ padding: 28rpx 28rpx;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+
|
|
|
|
|
+ .swipe-track.default & {
|
|
|
|
|
+ background: transparent;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.item-btns {
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.action-btn {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ font-size: 26rpx;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.edit-btn { background: #419D95; }
|
|
|
|
|
+.delete-btn { background: #e74c3c; }
|
|
|
|
|
+
|
|
|
|
|
+// === Item content ===
|
|
|
|
|
+.item-header { display: flex; align-items: flex-start; justify-content: space-between; }
|
|
|
|
|
+.item-title-row { display: flex; align-items: center; flex: 1; min-width: 0; }
|
|
|
|
|
+.title-name { font-size: 30rpx; font-weight: 600; color: #1a1a1a; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; min-width: 0; }
|
|
|
|
|
+
|
|
|
|
|
+.type-tag { font-size: 20rpx; padding: 4rpx 12rpx; border-radius: 6rpx; margin-left: 12rpx; flex-shrink: 0;
|
|
|
|
|
+ &.tag-org { background: rgba(65, 157, 149, 0.1); color: #419D95; }
|
|
|
|
|
+ &.tag-personal { background: rgba(255, 153, 0, 0.1); color: #cc7a00; }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.default-tag { font-size: 20rpx; padding: 4rpx 12rpx; border-radius: 6rpx; margin-left: 8rpx; background: rgba(65, 157, 149, 0.12); color: #419D95; flex-shrink: 0; }
|
|
|
|
|
+.item-detail { font-size: 24rpx; color: #999; margin-top: 10rpx; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
|
|
+.item-footer { margin-top: 16rpx; }
|
|
|
|
|
+
|
|
|
|
|
+.set-default-btn { display: inline-block; font-size: 22rpx; color: #419D95; padding: 6rpx 20rpx; border: 1rpx solid rgba(65, 157, 149, 0.4); border-radius: 20rpx; }
|
|
|
|
|
+
|
|
|
|
|
+// === Empty ===
|
|
|
|
|
+.empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; padding-top: 240rpx; }
|
|
|
|
|
+.empty-title { font-size: 28rpx; color: #999; margin-top: 24rpx; }
|
|
|
|
|
+.empty-desc { font-size: 24rpx; color: #ccc; margin-top: 10rpx; }
|
|
|
|
|
+
|
|
|
|
|
+// === Add button ===
|
|
|
|
|
+.add-row { position: fixed; bottom: 0; left: 0; right: 0; padding: 16rpx 0; padding-bottom: calc(16rpx + env(safe-area-inset-bottom)); display: flex; justify-content: center; }
|
|
|
|
|
+.add-btn { display: flex; align-items: center; justify-content: center; width: 400rpx; height: 80rpx; background: #419D95; color: #fff; font-size: 32rpx; font-weight: 500; border-radius: 40rpx; }
|
|
|
|
|
+
|
|
|
|
|
+// === Modal ===
|
|
|
|
|
+.modal-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0); z-index: 1000; display: flex; align-items: flex-end; transition: background 0.25s ease-out; &.visible { background: rgba(0,0,0,0.45); } }
|
|
|
|
|
+.modal-content { width: 100%; max-width: 100vw; background: #fff; border-radius: 32rpx 32rpx 0 0; transform: translateY(100%); transition: transform 0.3s cubic-bezier(0.32, 0.72, 0, 1); overflow: hidden; &.visible { transform: translateY(0); } }
|
|
|
|
|
+
|
|
|
|
|
+.modal-handle { display: flex; justify-content: center; padding: 16rpx 0 4rpx; }
|
|
|
|
|
+.handle-bar { width: 56rpx; height: 6rpx; border-radius: 3rpx; background: rgba(0,0,0,0.15); }
|
|
|
|
|
+
|
|
|
|
|
+.modal-header { display: flex; align-items: center; justify-content: center; padding: 8rpx 30rpx 20rpx; position: relative; border-bottom: 1rpx solid rgba(0,0,0,0.05); }
|
|
|
|
|
+.modal-title { font-size: 32rpx; font-weight: 600; color: #1a1a1a; }
|
|
|
|
|
+.modal-close { position: absolute; right: 30rpx; top: 50%; transform: translateY(-50%); padding: 12rpx; }
|
|
|
|
|
+
|
|
|
|
|
+.modal-body { padding: 8rpx 30rpx 0; overflow-y: auto; overflow-x: hidden; box-sizing: border-box; }
|
|
|
|
|
+
|
|
|
|
|
+.modal-footer { display: flex; padding: 20rpx 30rpx; padding-bottom: calc(20rpx + env(safe-area-inset-bottom)); gap: 20rpx; border-top: 1rpx solid rgba(0,0,0,0.05); }
|
|
|
|
|
+.btn-cancel { width: 160rpx; height: 80rpx; display: flex; align-items: center; justify-content: center; font-size: 28rpx; color: #419D95; background: rgba(65, 157, 149, 0.08); border-radius: 40rpx; flex-shrink: 0; }
|
|
|
|
|
+.btn-save { flex: 1; height: 80rpx; display: flex; align-items: center; justify-content: center; font-size: 28rpx; font-weight: 500; color: #fff; background: #419D95; border-radius: 40rpx; &.loading { opacity: 0.7; } }
|
|
|
|
|
+
|
|
|
|
|
+// === Forms ===
|
|
|
|
|
+.form-section-label { font-size: 22rpx; color: #999; padding: 16rpx 0 8rpx; margin-bottom: 4rpx; }
|
|
|
|
|
+
|
|
|
|
|
+.form-group { margin-bottom: 20rpx; width: 100%; overflow: hidden; box-sizing: border-box; }
|
|
|
|
|
+.form-label { font-size: 26rpx; color: #333; margin-bottom: 10rpx; }
|
|
|
|
|
+.required { color: #e74c3c; margin-left: 4rpx; }
|
|
|
|
|
+.form-row { display: flex; overflow: hidden; }
|
|
|
|
|
+.form-input { flex: 1; height: 80rpx; padding: 0 20rpx; margin: 0; background: #f6f7fa; border: 1rpx solid #eee; border-radius: 12rpx; font-size: 28rpx; color: #1a1a1a; box-sizing: border-box; }
|
|
|
|
|
+.input-placeholder { color: #bbb; }
|
|
|
|
|
+.type-options { display: flex; }
|
|
|
|
|
+.type-opt { display: flex; align-items: center; justify-content: center; font-size: 26rpx; color: #888; padding: 10rpx 28rpx; border: 1rpx solid #e0e0e0; border-radius: 8rpx; margin-right: 16rpx; transition: all 0.15s; &.active { border-color: #419D95; background: rgba(65, 157, 149, 0.06); color: #419D95; } }
|
|
|
|
|
+.form-switch { display: flex; align-items: center; justify-content: space-between; .form-label { margin-bottom: 0; } }
|
|
|
|
|
+.switch { width: 72rpx; height: 40rpx; background: #d0d0d0; border-radius: 24rpx; position: relative; transition: background 0.2s; &.on { background: #419D95; } }
|
|
|
|
|
+.switch-dot { width: 32rpx; height: 32rpx; background: #fff; border-radius: 50%; position: absolute; top: 4rpx; left: 4rpx; transition: transform 0.2s; .switch.on & { transform: translateX(32rpx); } }
|
|
|
|
|
+</style>
|