Files
medical-mall/pages/mall/admin/marketing/recharge/quota.uvue
2026-02-15 16:37:37 +08:00

430 lines
14 KiB
Plaintext
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>
<view class="marketing-recharge-quota">
<view class="content-layout">
<!-- 左侧预览 -->
<view class="preview-side">
<view class="phone-mock">
<view class="phone-header">
<text class="balance-label">我的余额</text>
<view class="balance-val">
<text class="symbol">¥</text>
<text class="num">0.00</text>
</view>
</view>
<view class="recharge-tabs">
<view class="tab active"><text class="tab-txt">账户充值</text></view>
<view class="tab"><text class="tab-txt">佣金导入</text></view>
</view>
<view class="quota-grid">
<view v-for="(item, index) in list" :key="index" class="quota-item" :class="{ active: index === 0 }">
<text class="price">{{ item.price }}元</text>
<text class="bonus">赠送{{ item.bonus }}元</text>
</view>
<view class="quota-item other">
<text class="other-txt">其他</text>
</view>
</view>
<view class="notice-section">
<text class="notice-title">注意事项:</text>
<text class="notice-content">充值后账户的金额不能提现可用于商城消费使用。佣金导入账户之后不能再次导出、不可提现。账户充值出现问题可联系商城客服也可拨打商城客服热线4008888888。</text>
</view>
<button class="recharge-btn">立即充值</button>
</view>
</view>
<!-- 右侧设置 -->
<view class="table-side border-shadow">
<view class="side-header">
<text class="side-title">充值金额设置</text>
</view>
<view class="action-row">
<button class="btn-primary" @click="showAddModal = true">添加数据</button>
</view>
<view class="table-container">
<view class="table-head">
<view class="th cell-id">编号</view>
<view class="th cell-price">售价</view>
<view class="th cell-bonus">赠送</view>
<view class="th cell-status">是否可用</view>
<view class="th cell-sort">排序</view>
<view class="th cell-op">操作</view>
</view>
<view class="table-body">
<view v-for="item in list" :key="item.id" class="table-row">
<view class="td cell-id"><text class="td-txt">{{ item.id }}</text></view>
<view class="td cell-price"><text class="td-txt">{{ item.price }}</text></view>
<view class="td cell-bonus"><text class="td-txt">{{ item.bonus }}</text></view>
<view class="td cell-status">
<view class="switch-mock" :class="{ active: item.is_open }" @click="toggleStatus(item)">
<view class="switch-dot"></view>
</view>
</view>
<view class="td cell-sort"><text class="td-txt">{{ item.sort }}</text></view>
<view class="td cell-op">
<text class="op-link" @click="handleEdit(item)">编辑</text>
<text class="op-link del ml-10" @click="handleDelete(item)">删除</text>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 添加数据弹窗 -->
<view v-if="showAddModal" class="modal-mask">
<view class="modal-content">
<view class="modal-header">
<text class="modal-title">添加数据</text>
<text class="modal-close" @click="showAddModal = false">×</text>
</view>
<view class="modal-body">
<view class="form-item">
<text class="form-label">售价:</text>
<input class="form-input" v-model="formData.price" type="number" placeholder="请输入售价" />
</view>
<view class="form-item">
<text class="form-label">赠送:</text>
<input class="form-input" v-model="formData.bonus" type="number" placeholder="请输入赠送" />
</view>
<view class="form-item">
<text class="form-label">排序:</text>
<input class="form-input" v-model="formData.sort" type="number" />
</view>
<view class="form-item">
<text class="form-label">状态:</text>
<view class="radio-group">
<view class="radio-item" @click="formData.is_open = true">
<view class="radio-circle" :class="{ checked: formData.is_open }"></view>
<text class="radio-txt">显示</text>
</view>
<view class="radio-item ml-20" @click="formData.is_open = false">
<view class="radio-circle" :class="{ checked: !formData.is_open }"></view>
<text class="radio-txt">隐藏</text>
</view>
</view>
</view>
</view>
<view class="modal-footer">
<button class="btn-cancel" @click="showAddModal = false">取消</button>
<button class="btn-submit" @click="handleSubmit">确定</button>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, reactive, onMounted } from 'vue'
import { fetchRechargeQuotas, saveRechargeQuota, deleteRechargeQuota, RechargeQuota } from '@/services/admin/marketingService.uts'
const showAddModal = ref(false)
const isLoading = ref(false)
const isSaving = ref(false)
const list = ref<RechargeQuota[]>([])
const formData = reactive({
id: null as string | null,
price: '',
bonus: '',
sort: 1,
is_open: true
})
onMounted(() => {
loadData()
})
async function loadData() {
isLoading.value = true
try {
const res = await fetchRechargeQuotas()
list.value = res
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
isLoading.value = false
}
}
async function toggleStatus(item : RechargeQuota) {
if (item.id == null) return
const nextStatus = !item.is_open
isLoading.value = true
try {
const success = await saveRechargeQuota({ ...item, is_open: nextStatus } as RechargeQuota)
if (success) {
item.is_open = nextStatus
uni.showToast({ title: '操作成功', icon: 'success' })
}
} finally {
isLoading.value = false
}
}
function handleEdit(item : RechargeQuota) {
formData.id = item.id ?? null
formData.price = String(item.price)
formData.bonus = String(item.bonus_price)
formData.sort = item.sort_order
formData.is_open = item.is_open
showAddModal.value = true
}
async function handleDelete(item : RechargeQuota) {
if (item.id == null) return
uni.showModal({
title: '提示',
content: '确认删除该项吗?',
success: async (res) => {
if (res.confirm) {
isLoading.value = true
try {
const success = await deleteRechargeQuota(item.id!)
if (success) {
uni.showToast({ title: '已删除', icon: 'success' })
loadData()
}
} finally {
isLoading.value = false
}
}
}
})
}
async function handleSubmit() {
if (!formData.price) {
uni.showToast({ title: '请输入售价', icon: 'none' })
return
}
isSaving.value = true
const payload : RechargeQuota = {
id: formData.id ?? undefined,
price: parseFloat(formData.price),
bonus_price: parseFloat(formData.bonus || '0'),
sort_order: parseInt(formData.sort.toString()),
is_open: formData.is_open
}
try {
const success = await saveRechargeQuota(payload)
if (success) {
uni.showToast({ title: formData.id ? '修改成功' : '添加成功', icon: 'success' })
showAddModal.value = false
resetForm()
loadData()
}
} catch (e) {
uni.showToast({ title: '操作异常', icon: 'none' })
} finally {
isSaving.value = false
}
}
function resetForm() {
formData.id = null
formData.price = ''
formData.bonus = ''
formData.sort = 1
formData.is_open = true
}
</script>
<style scoped lang="scss">
.marketing-recharge-quota {
padding: 16px;
background: #f0f2f5;
min-height: 100vh;
}
.content-layout {
display: flex;
flex-direction: row;
}
/* 左侧预览 */
.preview-side {
width: 320px;
margin-right: 24px;
}
.phone-mock {
background: #fff;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 16px rgba(0,0,0,0.1);
padding-bottom: 24px;
}
.phone-header {
height: 120px;
background: #e74c3c;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #fff;
}
.balance-label { font-size: 13px; opacity: 0.9; margin-bottom: 8px; }
.balance-val { display: flex; flex-direction: row; align-items: baseline; }
.symbol { font-size: 18px; margin-right: 4px; }
.num { font-size: 32px; font-weight: bold; }
.recharge-tabs {
display: flex;
flex-direction: row;
height: 44px;
border-bottom: 1px solid #f5f5f5;
}
.tab { flex: 1; display: flex; align-items: center; justify-content: center; position: relative; }
.tab.active::after { content: ''; position: absolute; bottom: 0; width: 40px; height: 2px; background: #e74c3c; }
.tab-txt { font-size: 14px; color: #666; font-weight: bold; }
.tab.active .tab-txt { color: #333; }
.quota-grid {
display: flex;
flex-direction: row;
flex-wrap: wrap;
padding: 16px;
justify-content: space-between;
}
.quota-item {
width: 90px;
height: 60px;
border: 1px solid #f0f0f0;
border-radius: 4px;
margin-bottom: 12px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: #f8f8f8;
}
.quota-item.active {
border-color: #e74c3c;
background: #fff;
position: relative;
}
.quota-item.active::after {
content: '';
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
border: 1px solid #e74c3c;
border-radius: 4px;
}
.price { font-size: 14px; color: #333; font-weight: bold; margin-bottom: 4px; }
.bonus { font-size: 11px; color: #999; }
.quota-item.active .price { color: #e74c3c; }
.quota-item.active .bonus { color: #e74c3c; opacity: 0.8; }
.other { background: #f0f0f0; border: none; }
.other-txt { font-size: 14px; color: #666; }
.notice-section {
padding: 0 16px;
margin-top: 10px;
}
.notice-title { font-size: 13px; color: #333; font-weight: bold; margin-bottom: 8px; display: block; }
.notice-content { font-size: 12px; color: #999; line-height: 1.6; }
.recharge-btn {
margin: 24px 16px 0;
height: 40px;
line-height: 40px;
background: #e74c3c;
color: #fff;
border-radius: 20px;
font-size: 15px;
border: none;
}
/* 右侧设置 */
.table-side {
flex: 1;
background: #fff;
padding: 24px;
}
.border-shadow { border-radius: 4px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05); }
.side-header { border-left: 4px solid #1890ff; padding-left: 12px; margin-bottom: 24px; }
.side-title { font-size: 16px; font-weight: bold; color: #333; }
.action-row { margin-bottom: 16px; }
.btn-primary {
background: #1890ff; color: #fff; border: none;
height: 32px; line-height: 32px; padding: 0 16px; font-size: 14px; border-radius: 4px; cursor: pointer;
}
.table-head { display: flex; flex-direction: row; background: #f8f8f9; border-bottom: 1px solid #e8eaec; }
.th { padding: 12px 8px; font-size: 13px; color: #515a6e; font-weight: bold; }
.table-row { display: flex; flex-direction: row; border-bottom: 1px solid #e8eaec; align-items: center; }
.td { padding: 16px 8px; }
.td-txt { font-size: 13px; color: #515a6e; }
.cell-id { width: 80px; }
.cell-price { width: 120px; }
.cell-bonus { width: 120px; }
.cell-status { width: 100px; text-align: center; }
.cell-sort { width: 100px; text-align: center; }
.cell-op { flex: 1; text-align: right; }
.op-link { color: #1890ff; font-size: 13px; cursor: pointer; }
.op-link.del { color: #ff4d4f; }
.ml-10 { margin-left: 10px; }
.switch-mock {
width: 44px; height: 22px; background-color: #bfbfbf; border-radius: 11px;
display: flex; align-items: center; padding: 0 4px; position: relative;
transition: background-color 0.3s; cursor: pointer;
}
.switch-mock.active { background-color: #1890ff; }
.switch-dot {
width: 14px; height: 14px; background-color: #fff; border-radius: 50%;
position: absolute; left: 4px; transition: left 0.3s;
}
.switch-mock.active .switch-dot { left: 26px; }
/* Modal */
.modal-mask {
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 1000;
}
.modal-content { width: 500px; background: #fff; border-radius: 4px; }
.modal-header { padding: 16px 24px; border-bottom: 1px solid #e8eaec; display: flex; justify-content: space-between; align-items: center; }
.modal-title { font-size: 16px; font-weight: bold; }
.modal-close { font-size: 24px; color: #999; cursor: pointer; }
.modal-body { padding: 24px; }
.modal-footer { padding: 12px 24px; border-top: 1px solid #e8eaec; display: flex; justify-content: flex-end; }
.form-item { display: flex; flex-direction: row; margin-bottom: 20px; align-items: center; }
.form-label { width: 80px; font-size: 14px; color: #606266; }
.form-input { flex: 1; height: 32px; border: 1px solid #dcdfe6; border-radius: 4px; padding: 0 12px; }
.radio-group { display: flex; flex-direction: row; flex: 1; }
.radio-item { display: flex; flex-direction: row; align-items: center; cursor: pointer; }
.radio-circle { width: 14px; height: 14px; border: 1px solid #dcdfe6; border-radius: 50%; margin-right: 6px; position: relative; }
.radio-circle.checked { border-color: #1890ff; }
.radio-circle.checked::after { content: ''; position: absolute; width: 8px; height: 8px; background: #1890ff; border-radius: 50%; top: 2px; left: 2px; }
.radio-txt { font-size: 14px; color: #606266; }
.ml-20 { margin-left: 20px; }
.btn-cancel { margin-right: 8px; height: 32px; line-height: 32px; padding: 0 16px; font-size: 14px; border-radius: 4px; border: 1px solid #dcdfe6; background: #fff; }
.btn-submit { height: 32px; line-height: 32px; padding: 0 16px; font-size: 14px; border-radius: 4px; background: #1890ff; color: #fff; border: none; }
</style>