大致完成页面

This commit is contained in:
2026-02-05 09:01:16 +08:00
parent c411c23b9c
commit d51e6a8f72
40 changed files with 11023 additions and 737 deletions

View File

@@ -1,27 +0,0 @@
<template>
<AdminLayout :currentPage="currentPage">
<view class="page">
<view class="header">
<text class="title">{{ title }}</text>
<text class="sub-title">页面占位 (自动生成)</text>
</view>
</view>
</AdminLayout>
</template>
<script setup lang="uts">
import { ref } from 'vue'
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
const currentPage = ref<string>('recharge-amount')
const title = ref<string>('amount')
</script>
<style scoped lang="scss">
@import '@/uni.scss';
.page { padding: $space-lg; }
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
</style>

View File

@@ -1,27 +1,187 @@
<template>
<AdminLayout :currentPage="currentPage">
<view class="page">
<view class="header">
<text class="title">{{ title }}</text>
<text class="sub-title">页面占位 (自动生成)</text>
<view class="marketing-recharge-config">
<view class="config-card border-shadow">
<view class="config-header">
<text class="config-title">用户充值配置</text>
</view>
<view class="config-body">
<view class="config-item">
<view class="item-label">
<text class="label-txt">余额功能启用:</text>
<text class="label-desc">商城余额功能启用或者关闭</text>
</view>
<view class="item-content">
<view class="radio-group">
<view class="radio-item" @click="config.balance_enabled = true">
<view class="radio-circle" :class="{ checked: config.balance_enabled }"></view>
<text class="radio-txt">开启</text>
</view>
<view class="radio-item ml-20" @click="config.balance_enabled = false">
<view class="radio-circle" :class="{ checked: !config.balance_enabled }"></view>
<text class="radio-txt">关闭</text>
</view>
</view>
</view>
</view>
<view class="config-item">
<view class="item-label">
<text class="label-txt">充值注意事项:</text>
<text class="label-desc">充值注意事项</text>
</view>
<view class="item-content">
<textarea class="config-textarea" v-model="config.notice" placeholder="请输入充值注意事项"></textarea>
</view>
</view>
<view class="config-item">
<view class="item-label">
<text class="label-txt">小程序充值开关:</text>
<text class="label-desc">仅小程序端的充值开关,小程序提交审核前,需要关闭此功能</text>
</view>
<view class="item-content">
<view class="radio-group">
<view class="radio-item" @click="config.mp_recharge = true">
<view class="radio-circle" :class="{ checked: config.mp_recharge }"></view>
<text class="radio-txt">开启</text>
</view>
<view class="radio-item ml-20" @click="config.mp_recharge = false">
<view class="radio-circle" :class="{ checked: !config.mp_recharge }"></view>
<text class="radio-txt">关闭</text>
</view>
</view>
</view>
</view>
<view class="config-item">
<view class="item-label">
<text class="label-txt">最低充值金额:</text>
<text class="label-desc">用户单次最低充值金额</text>
</view>
<view class="item-content">
<input class="config-input" type="number" v-model="config.min_amount" />
</view>
</view>
<view class="config-footer">
<button class="btn-submit" @click="handleSave">提交</button>
</view>
</view>
</view>
</AdminLayout>
</view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
const currentPage = ref<string>('recharge-config')
const title = ref<string>('config')
import { reactive } from 'vue'
const config = reactive({
balance_enabled: true,
notice: '充值后账户的金额不能提现,可用于商城消费使用\n佣金导入账户之后不能再次导出、不可提现\n账户充值出现问题可联系商城客服',
mp_recharge: false,
min_amount: 0.01
})
const handleSave = () => {
uni.showToast({ title: '保存成功', icon: 'success' })
}
</script>
<style scoped lang="scss">
@import '@/uni.scss';
.page { padding: $space-lg; }
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
.marketing-recharge-config {
padding: 16px;
background: #f0f2f5;
min-height: 100vh;
}
.border-shadow {
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
}
.config-card { padding: 24px; }
.config-header {
border-bottom: 1px solid #e8eaec;
padding-bottom: 16px;
margin-bottom: 24px;
}
.config-title {
font-size: 16px;
font-weight: bold;
color: #17233d;
position: relative;
padding-left: 12px;
}
.config-title::before {
content: '';
position: absolute;
left: 0;
top: 4px;
bottom: 4px;
width: 3px;
background: #1890ff;
}
.config-item {
display: flex;
flex-direction: row;
margin-bottom: 30px;
align-items: flex-start;
}
.item-label { width: 220px; display: flex; flex-direction: column; }
.label-txt { font-size: 14px; color: #333; margin-bottom: 4px; }
.label-desc { font-size: 12px; color: #999; }
.item-content { flex: 1; }
.radio-group { display: flex; flex-direction: row; padding-top: 4px; }
.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; }
.config-textarea {
width: 100%;
max-width: 600px;
height: 120px;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 12px;
font-size: 14px;
}
.config-input {
width: 300px;
height: 32px;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 0 12px;
font-size: 14px;
}
.config-footer {
margin-top: 40px;
padding-left: 220px;
}
.btn-submit {
width: 80px;
height: 32px;
line-height: 32px;
background: #1890ff;
color: #fff;
border: none;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
}
</style>

View File

@@ -0,0 +1,383 @@
<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 } from 'vue'
const showAddModal = ref(false)
const formData = reactive({
price: '',
bonus: '',
sort: 1,
is_open: true
})
const list = ref([
{ id: 640, price: 10, bonus: 2, is_open: true, sort: 6 },
{ id: 641, price: 20, bonus: 8, is_open: true, sort: 5 },
{ id: 642, price: 50, bonus: 20, is_open: true, sort: 4 },
{ id: 643, price: 100, bonus: 50, is_open: true, sort: 3 },
{ id: 644, price: 200, bonus: 110, is_open: true, sort: 2 },
{ id: 645, price: 300, bonus: 200, is_open: true, sort: 1 }
])
const toggleStatus = (item: any) => {
item.is_open = !item.is_open
uni.showToast({ title: '操作成功', icon: 'success' })
}
const handleEdit = (item: any) => {
uni.showToast({ title: '编辑功能', icon: 'none' })
}
const handleDelete = (item: any) => {
uni.showModal({
title: '提示',
content: '确认删除该项吗?',
success: (res) => {
if (res.confirm) {
const index = list.value.findIndex(i => i.id === item.id)
if (index > -1) {
list.value.splice(index, 1)
uni.showToast({ title: '已删除', icon: 'success' })
}
}
}
})
}
const handleSubmit = () => {
if (!formData.price) {
uni.showToast({ title: '请输入售价', icon: 'none' })
return
}
const newItem = {
id: Math.floor(Math.random() * 1000) + 700,
price: parseFloat(formData.price),
bonus: parseFloat(formData.bonus || '0'),
sort: parseInt(formData.sort.toString()),
is_open: formData.is_open
}
list.value.push(newItem)
// 重置表单
formData.price = ''
formData.bonus = ''
formData.sort = 1
formData.is_open = true
showAddModal.value = false
uni.showToast({ title: '添加成功', icon: 'success' })
}
</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>