页面初始化
This commit is contained in:
114
src/pages.json
114
src/pages.json
@@ -108,6 +108,96 @@
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/distribution/data",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/distribution/profit-rule",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/distribution/qrcode",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/account/manage",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/account/quota",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/account/log",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/account/personal",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/student/list",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/student/analysis",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/student/remind",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/member/order",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/member/gift",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/member/general-code",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/member/personal-order",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/member/personal-gift",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/school-info",
|
||||
"style": {
|
||||
@@ -167,6 +257,30 @@
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/stats/distribution",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/stats/member",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/stats/student",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/student/detail",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tabBar": {
|
||||
|
||||
490
src/pages/account/log.vue
Normal file
490
src/pages/account/log.vue
Normal file
@@ -0,0 +1,490 @@
|
||||
<template>
|
||||
<view class="log-manage-container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<view class="back-icon">←</view>
|
||||
</view>
|
||||
<view class="header-title">操作日志查看</view>
|
||||
<view class="header-right" @click="exportLog">
|
||||
<view class="export-btn">导出</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选条件 -->
|
||||
<view class="filter-section">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">操作类型</view>
|
||||
<view class="filter-control">
|
||||
<picker
|
||||
:range="operationOptions"
|
||||
:value="operationIndex"
|
||||
@change="onOperationChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ operationOptions[operationIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">时间范围</view>
|
||||
<view class="filter-control">
|
||||
<picker
|
||||
mode="date"
|
||||
start="2026-01-01"
|
||||
end="2026-12-31"
|
||||
:value="dateRange"
|
||||
@change="onDateChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ dateRangeDisplay }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">搜索</view>
|
||||
<view class="filter-control">
|
||||
<input
|
||||
v-model="searchKeyword"
|
||||
class="search-input"
|
||||
placeholder="请输入操作人或账号"
|
||||
@input="onSearch"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 日志列表 -->
|
||||
<view class="log-list">
|
||||
<view
|
||||
v-for="(log, index) in logList"
|
||||
:key="index"
|
||||
class="log-item"
|
||||
>
|
||||
<view class="log-header">
|
||||
<view class="log-time">{{ log.time }}</view>
|
||||
<view class="log-status" :class="log.statusClass">{{ log.status }}</view>
|
||||
</view>
|
||||
<view class="log-content">
|
||||
<view class="log-operation">{{ log.operation }}</view>
|
||||
<view class="log-detail">{{ log.detail }}</view>
|
||||
</view>
|
||||
<view class="log-meta">
|
||||
<view class="log-operator">{{ log.operator }}</view>
|
||||
<view class="log-ip">{{ log.ip }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="logList.length === 0" class="empty-state">
|
||||
<view class="empty-icon">📋</view>
|
||||
<view class="empty-text">暂无操作日志</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页 -->
|
||||
<view v-if="logList.length > 0" class="pagination">
|
||||
<view class="page-info">
|
||||
共 {{ totalLogs }} 条,当前第 {{ currentPage }} 页
|
||||
</view>
|
||||
<view class="page-controls">
|
||||
<view
|
||||
class="page-btn"
|
||||
:class="{ disabled: currentPage === 1 }"
|
||||
@click="goToPage(currentPage - 1)"
|
||||
>
|
||||
上一页
|
||||
</view>
|
||||
<view
|
||||
class="page-btn"
|
||||
:class="{ disabled: currentPage === totalPages }"
|
||||
@click="goToPage(currentPage + 1)"
|
||||
>
|
||||
下一页
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from "vue"
|
||||
|
||||
// 操作类型选项
|
||||
const operationOptions = ['全部', '账号创建', '账号冻结', '账号启用', '密码修改', '权限变更']
|
||||
const operationIndex = ref(0)
|
||||
|
||||
// 日期范围
|
||||
const dateRange = ref('2026-01-29')
|
||||
const dateRangeDisplay = ref('2026-01-29')
|
||||
|
||||
// 搜索关键词
|
||||
const searchKeyword = ref('')
|
||||
|
||||
// 日志列表
|
||||
const logList = ref([
|
||||
{
|
||||
id: 1,
|
||||
time: '2026-01-29 15:30:45',
|
||||
operation: '账号创建',
|
||||
detail: '创建了新的分销员账号:张教练',
|
||||
operator: '系统管理员',
|
||||
ip: '192.168.1.100',
|
||||
status: '成功',
|
||||
statusClass: 'status-success'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
time: '2026-01-29 14:20:30',
|
||||
operation: '账号冻结',
|
||||
detail: '冻结了分销员账号:王教练',
|
||||
operator: '系统管理员',
|
||||
ip: '192.168.1.100',
|
||||
status: '成功',
|
||||
statusClass: 'status-success'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
time: '2026-01-29 10:15:20',
|
||||
operation: '密码修改',
|
||||
detail: '修改了账号密码:李教练',
|
||||
operator: '李教练',
|
||||
ip: '192.168.1.101',
|
||||
status: '成功',
|
||||
statusClass: 'status-success'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
time: '2026-01-28 16:45:10',
|
||||
operation: '权限变更',
|
||||
detail: '修改了分销员权限:张教练',
|
||||
operator: '系统管理员',
|
||||
ip: '192.168.1.100',
|
||||
status: '成功',
|
||||
statusClass: 'status-success'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
time: '2026-01-28 09:30:00',
|
||||
operation: '账号创建',
|
||||
detail: '创建了新的分销员账号:刘教练',
|
||||
operator: '系统管理员',
|
||||
ip: '192.168.1.100',
|
||||
status: '成功',
|
||||
statusClass: 'status-success'
|
||||
}
|
||||
])
|
||||
|
||||
// 分页信息
|
||||
const currentPage = ref(1)
|
||||
const totalLogs = ref(100)
|
||||
const pageSize = ref(10)
|
||||
|
||||
// 计算总页数
|
||||
const totalPages = computed(() => {
|
||||
return Math.ceil(totalLogs.value / pageSize.value)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// 实际项目中应从接口获取操作日志
|
||||
// loadLogList()
|
||||
})
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}
|
||||
|
||||
// 导出日志
|
||||
function exportLog() {
|
||||
uni.showModal({
|
||||
title: '导出日志',
|
||||
content: '确定要导出当前筛选条件的操作日志吗?',
|
||||
success: function(res) {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: '导出中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '导出成功',
|
||||
icon: 'success'
|
||||
})
|
||||
// 实际项目中应调用接口导出日志
|
||||
// exportLogData()
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 操作类型变更
|
||||
function onOperationChange(e) {
|
||||
const value = e.detail.value
|
||||
operationIndex.value = value
|
||||
// 实际项目中应根据操作类型筛选日志
|
||||
// filterLogList()
|
||||
}
|
||||
|
||||
// 日期变更
|
||||
function onDateChange(e) {
|
||||
dateRange.value = e.detail.value
|
||||
dateRangeDisplay.value = e.detail.value
|
||||
// 实际项目中应根据日期筛选日志
|
||||
// filterLogList()
|
||||
}
|
||||
|
||||
// 搜索
|
||||
function onSearch() {
|
||||
// 实际项目中应根据关键词搜索日志
|
||||
// searchLogList()
|
||||
}
|
||||
|
||||
// 跳转到指定页码
|
||||
function goToPage(page) {
|
||||
if (page < 1 || page > totalPages.value) {
|
||||
return
|
||||
}
|
||||
currentPage.value = page
|
||||
// 实际项目中应加载指定页码的日志
|
||||
// loadLogList(page)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* #ifndef APP-NVUE */
|
||||
page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
view {
|
||||
font-size: 14px;
|
||||
line-height: inherit;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.log-manage-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 120rpx;
|
||||
background-color: #fff;
|
||||
padding: 0 32rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 40rpx;
|
||||
color: #303133;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 60rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.export-btn {
|
||||
font-size: 28rpx;
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 筛选条件 */
|
||||
.filter-section {
|
||||
padding: 24rpx 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
flex: 1;
|
||||
min-width: 200rpx;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.filter-control {
|
||||
background-color: #f9f9f9;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.picker {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 日志列表 */
|
||||
.log-list {
|
||||
flex: 1;
|
||||
padding: 0 16rpx;
|
||||
}
|
||||
|
||||
.log-item {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.log-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.log-time {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.log-status {
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.status-success {
|
||||
color: #67c23a;
|
||||
background-color: rgba(103, 194, 58, 0.1);
|
||||
}
|
||||
|
||||
.status-fail {
|
||||
color: #f56c6c;
|
||||
background-color: rgba(245, 108, 108, 0.1);
|
||||
}
|
||||
|
||||
.log-content {
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.log-operation {
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.log-detail {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.log-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 20rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 分页 */
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.page-info {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.page-controls {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
color: #409eff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.page-btn.disabled {
|
||||
color: #909399;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
390
src/pages/account/manage.vue
Normal file
390
src/pages/account/manage.vue
Normal file
@@ -0,0 +1,390 @@
|
||||
<template>
|
||||
<view class="account-manage-container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<view class="back-icon">←</view>
|
||||
</view>
|
||||
<view class="header-title">账号创建与管控</view>
|
||||
<view class="header-right" @click="addAccount">
|
||||
<view class="add-btn">+ 添加</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选条件 -->
|
||||
<view class="filter-section">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">账号状态</view>
|
||||
<view class="filter-control">
|
||||
<picker
|
||||
:range="statusOptions"
|
||||
:value="statusIndex"
|
||||
@change="onStatusChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ statusOptions[statusIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">搜索</view>
|
||||
<view class="filter-control">
|
||||
<input
|
||||
v-model="searchKeyword"
|
||||
class="search-input"
|
||||
placeholder="请输入账号名称或手机号"
|
||||
@input="onSearch"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 账号列表 -->
|
||||
<view class="account-list">
|
||||
<view
|
||||
v-for="(account, index) in accountList"
|
||||
:key="index"
|
||||
class="account-item"
|
||||
>
|
||||
<view class="account-info">
|
||||
<view class="account-name">{{ account.name }}</view>
|
||||
<view class="account-meta">
|
||||
<view class="meta-item">{{ account.phone }}</view>
|
||||
<view class="meta-item">{{ account.role }}</view>
|
||||
<view class="meta-item status-{{ account.status }}">{{ account.statusText }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="account-actions">
|
||||
<view class="action-btn edit-btn" @click="editAccount(account.id)">
|
||||
编辑
|
||||
</view>
|
||||
<view
|
||||
class="action-btn"
|
||||
:class="account.status === 'active' ? 'disable-btn' : 'enable-btn'"
|
||||
@click="toggleAccountStatus(account.id, account.status)"
|
||||
>
|
||||
{{ account.status === 'active' ? '冻结' : '启用' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="accountList.length === 0" class="empty-state">
|
||||
<view class="empty-icon">👥</view>
|
||||
<view class="empty-text">暂无账号数据</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue"
|
||||
|
||||
// 状态选项
|
||||
const statusOptions = ['全部', '启用', '冻结']
|
||||
const statusIndex = ref(0)
|
||||
|
||||
// 搜索关键词
|
||||
const searchKeyword = ref('')
|
||||
|
||||
// 账号列表
|
||||
const accountList = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: '张教练',
|
||||
phone: '138****1234',
|
||||
role: '分销员',
|
||||
status: 'active',
|
||||
statusText: '启用'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '李教练',
|
||||
phone: '139****5678',
|
||||
role: '分销员',
|
||||
status: 'active',
|
||||
statusText: '启用'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '王教练',
|
||||
phone: '137****9012',
|
||||
role: '分销员',
|
||||
status: 'inactive',
|
||||
statusText: '冻结'
|
||||
}
|
||||
])
|
||||
|
||||
onMounted(() => {
|
||||
// 实际项目中应从接口获取账号列表
|
||||
// loadAccountList()
|
||||
})
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}
|
||||
|
||||
// 添加账号
|
||||
function addAccount() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/account/add'
|
||||
})
|
||||
}
|
||||
|
||||
// 编辑账号
|
||||
function editAccount(accountId) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/account/edit?id=${accountId}`
|
||||
})
|
||||
}
|
||||
|
||||
// 切换账号状态
|
||||
function toggleAccountStatus(accountId, currentStatus) {
|
||||
const newStatus = currentStatus === 'active' ? 'inactive' : 'active'
|
||||
const newStatusText = newStatus === 'active' ? '启用' : '冻结'
|
||||
|
||||
uni.showModal({
|
||||
title: '确认操作',
|
||||
content: `确定要${newStatusText}该账号吗?`,
|
||||
success: function(res) {
|
||||
if (res.confirm) {
|
||||
// 实际项目中应调用接口切换账号状态
|
||||
const account = accountList.value.find(item => item.id === accountId)
|
||||
if (account) {
|
||||
account.status = newStatus
|
||||
account.statusText = newStatusText
|
||||
}
|
||||
uni.showToast({
|
||||
title: `账号已${newStatusText}`,
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 状态变更
|
||||
function onStatusChange(e) {
|
||||
const value = e.detail.value
|
||||
statusIndex.value = value
|
||||
// 实际项目中应根据状态筛选账号列表
|
||||
// filterAccountList()
|
||||
}
|
||||
|
||||
// 搜索
|
||||
function onSearch() {
|
||||
// 实际项目中应根据关键词搜索账号列表
|
||||
// searchAccountList()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* #ifndef APP-NVUE */
|
||||
page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
view {
|
||||
font-size: 14px;
|
||||
line-height: inherit;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.account-manage-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 120rpx;
|
||||
background-color: #fff;
|
||||
padding: 0 32rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 40rpx;
|
||||
color: #303133;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 100rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
font-size: 24rpx;
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 筛选条件 */
|
||||
.filter-section {
|
||||
padding: 24rpx 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.filter-control {
|
||||
background-color: #f9f9f9;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.picker {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 账号列表 */
|
||||
.account-list {
|
||||
flex: 1;
|
||||
padding: 0 16rpx;
|
||||
}
|
||||
|
||||
.account-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.account-info {
|
||||
flex: 1;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
|
||||
.account-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.account-meta {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.status-active {
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.status-inactive {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.account-actions {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
background-color: #ecf5ff;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.disable-btn {
|
||||
background-color: #fef0f0;
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.enable-btn {
|
||||
background-color: #f0f9eb;
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
410
src/pages/account/personal.vue
Normal file
410
src/pages/account/personal.vue
Normal file
@@ -0,0 +1,410 @@
|
||||
<template>
|
||||
<view class="personal-account-container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<view class="back-icon">←</view>
|
||||
</view>
|
||||
<view class="header-title">个人账号管理</view>
|
||||
<view class="header-right" @click="saveChanges">
|
||||
<view class="save-btn">保存</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 个人信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">个人信息</view>
|
||||
<view class="info-card">
|
||||
<view class="info-row">
|
||||
<view class="info-item">
|
||||
<view class="info-label">姓名</view>
|
||||
<view class="info-control">
|
||||
<input
|
||||
v-model="userInfo.name"
|
||||
class="info-input"
|
||||
placeholder="请输入姓名"
|
||||
maxlength="20"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<view class="info-item">
|
||||
<view class="info-label">手机号码</view>
|
||||
<view class="info-control">
|
||||
<input
|
||||
v-model="userInfo.phone"
|
||||
class="info-input"
|
||||
placeholder="请输入手机号码"
|
||||
maxlength="11"
|
||||
type="number"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<view class="info-item">
|
||||
<view class="info-label">邮箱</view>
|
||||
<view class="info-control">
|
||||
<input
|
||||
v-model="userInfo.email"
|
||||
class="info-input"
|
||||
placeholder="请输入邮箱"
|
||||
maxlength="50"
|
||||
type="email"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<view class="info-item">
|
||||
<view class="info-label">性别</view>
|
||||
<view class="info-control">
|
||||
<picker
|
||||
:range="genders"
|
||||
:value="genderIndex"
|
||||
@change="onGenderChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ userInfo.gender || '请选择性别' }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<view class="info-item">
|
||||
<view class="info-label">所属驾校</view>
|
||||
<view class="info-control">
|
||||
<view class="info-text">{{ userInfo.school || '顺达驾校' }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<view class="info-item">
|
||||
<view class="info-label">职位</view>
|
||||
<view class="info-control">
|
||||
<input
|
||||
v-model="userInfo.position"
|
||||
class="info-input"
|
||||
placeholder="请输入职位"
|
||||
maxlength="20"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 账号安全 -->
|
||||
<view class="security-section">
|
||||
<view class="section-title">账号安全</view>
|
||||
<view class="security-card">
|
||||
<view class="security-item" @click="goToPasswordChange">
|
||||
<view class="security-label">修改登录密码</view>
|
||||
<view class="security-arrow">→</view>
|
||||
</view>
|
||||
<view class="security-item" @click="bindPhone">
|
||||
<view class="security-label">绑定手机号</view>
|
||||
<view class="security-status">{{ userInfo.phone ? '已绑定' : '未绑定' }}</view>
|
||||
<view class="security-arrow">→</view>
|
||||
</view>
|
||||
<view class="security-item" @click="bindEmail">
|
||||
<view class="security-label">绑定邮箱</view>
|
||||
<view class="security-status">{{ userInfo.email ? '已绑定' : '未绑定' }}</view>
|
||||
<view class="security-arrow">→</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 账号信息 -->
|
||||
<view class="account-info-section">
|
||||
<view class="section-title">账号信息</view>
|
||||
<view class="info-card">
|
||||
<view class="info-row">
|
||||
<view class="info-item">
|
||||
<view class="info-label">账号ID</view>
|
||||
<view class="info-value">{{ userInfo.id || '10001' }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<view class="info-item">
|
||||
<view class="info-label">账号类型</view>
|
||||
<view class="info-value">{{ userInfo.role || '分销员' }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<view class="info-item">
|
||||
<view class="info-label">注册时间</view>
|
||||
<view class="info-value">{{ userInfo.registerTime || '2026-01-15' }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<view class="info-item">
|
||||
<view class="info-label">最后登录</view>
|
||||
<view class="info-value">{{ userInfo.lastLogin || '2026-01-29 10:30:00' }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from "vue"
|
||||
|
||||
// 性别选项
|
||||
const genders = ['男', '女', '保密']
|
||||
|
||||
// 用户信息
|
||||
const userInfo = ref({
|
||||
id: '10001',
|
||||
name: '张教练',
|
||||
phone: '13800138000',
|
||||
email: 'zhang@shunda.com',
|
||||
gender: '男',
|
||||
school: '顺达驾校',
|
||||
position: '科目二教练',
|
||||
role: '分销员',
|
||||
registerTime: '2026-01-15',
|
||||
lastLogin: '2026-01-29 10:30:00'
|
||||
})
|
||||
|
||||
// 计算性别索引
|
||||
const genderIndex = computed(() => {
|
||||
return genders.indexOf(userInfo.value.gender)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// 实际项目中应从接口获取用户信息
|
||||
// loadUserInfo()
|
||||
})
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}
|
||||
|
||||
// 保存修改
|
||||
function saveChanges() {
|
||||
uni.showModal({
|
||||
title: '保存修改',
|
||||
content: '确定要保存个人信息修改吗?',
|
||||
success: function(res) {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: '保存中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '保存成功',
|
||||
icon: 'success'
|
||||
})
|
||||
// 实际项目中应调用接口保存用户信息
|
||||
// saveUserInfo()
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 性别变更
|
||||
function onGenderChange(e) {
|
||||
const index = e.detail.value
|
||||
userInfo.value.gender = genders[index]
|
||||
}
|
||||
|
||||
// 跳转到修改密码页面
|
||||
function goToPasswordChange() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mine/password-change'
|
||||
})
|
||||
}
|
||||
|
||||
// 绑定手机号
|
||||
function bindPhone() {
|
||||
uni.showToast({
|
||||
title: '绑定手机号功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
// 绑定邮箱
|
||||
function bindEmail() {
|
||||
uni.showToast({
|
||||
title: '绑定邮箱功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* #ifndef APP-NVUE */
|
||||
page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
view {
|
||||
font-size: 14px;
|
||||
line-height: inherit;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.personal-account-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 120rpx;
|
||||
background-color: #fff;
|
||||
padding: 0 32rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 40rpx;
|
||||
color: #303133;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 60rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.save-btn {
|
||||
font-size: 28rpx;
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 通用部分样式 */
|
||||
.info-section,
|
||||
.security-section,
|
||||
.account-info-section {
|
||||
padding: 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 0 16rpx 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 24rpx;
|
||||
padding-left: 12rpx;
|
||||
border-left: 8rpx solid #409eff;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* 个人信息 */
|
||||
.info-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.info-control {
|
||||
background-color: #f9f9f9;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.info-input {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.info-text {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.picker {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
/* 账号安全 */
|
||||
.security-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.security-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.security-label {
|
||||
flex: 1;
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.security-status {
|
||||
font-size: 20rpx;
|
||||
color: #67c23a;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.security-arrow {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
454
src/pages/account/quota.vue
Normal file
454
src/pages/account/quota.vue
Normal file
@@ -0,0 +1,454 @@
|
||||
<template>
|
||||
<view class="quota-manage-container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<view class="back-icon">←</view>
|
||||
</view>
|
||||
<view class="header-title">赠会员额度分配</view>
|
||||
<view class="header-right" @click="saveQuota">
|
||||
<view class="save-btn">保存</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选条件 -->
|
||||
<view class="filter-section">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">选择月份</view>
|
||||
<view class="filter-control">
|
||||
<picker
|
||||
mode="date"
|
||||
fields="month"
|
||||
:value="currentMonth"
|
||||
start="2026-01"
|
||||
end="2026-12"
|
||||
@change="onMonthChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ currentMonthDisplay }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 总额度设置 -->
|
||||
<view class="total-quota-section">
|
||||
<view class="section-title">总额度设置</view>
|
||||
<view class="quota-card">
|
||||
<view class="quota-item">
|
||||
<view class="quota-label">月度赠会员总额度</view>
|
||||
<view class="quota-control">
|
||||
<input
|
||||
v-model="totalQuota"
|
||||
type="number"
|
||||
class="quota-input"
|
||||
placeholder="请输入总额度"
|
||||
min="0"
|
||||
max="1000"
|
||||
/>
|
||||
<view class="quota-unit">个</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="quota-hint">
|
||||
• 总额度将分配给所有分销员
|
||||
• 单个分销员最高可分配额度为总额度的50%
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分销员额度分配 -->
|
||||
<view class="distributor-quota-section">
|
||||
<view class="section-title">分销员额度分配</view>
|
||||
<view class="quota-list">
|
||||
<view
|
||||
v-for="(distributor, index) in distributors"
|
||||
:key="distributor.id"
|
||||
class="quota-item"
|
||||
>
|
||||
<view class="distributor-info">
|
||||
<view class="distributor-name">{{ distributor.name }}</view>
|
||||
<view class="distributor-id">ID: {{ distributor.id }}</view>
|
||||
</view>
|
||||
<view class="quota-control">
|
||||
<input
|
||||
v-model="distributor.quota"
|
||||
type="number"
|
||||
class="quota-input"
|
||||
placeholder="0"
|
||||
min="0"
|
||||
:max="maxDistributorQuota"
|
||||
/>
|
||||
<view class="quota-unit">个</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 配额统计 -->
|
||||
<view class="quota-stats">
|
||||
<view class="stats-item">
|
||||
<view class="stats-label">已分配额度:</view>
|
||||
<view class="stats-value">{{ allocatedQuota }} 个</view>
|
||||
</view>
|
||||
<view class="stats-item">
|
||||
<view class="stats-label">剩余额度:</view>
|
||||
<view class="stats-value" :class="{ 'warning': remainingQuota < 0 }">
|
||||
{{ remainingQuota }} 个
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from "vue"
|
||||
|
||||
// 当前月份
|
||||
const currentMonth = ref('2026-01')
|
||||
const currentMonthDisplay = ref('2026年01月')
|
||||
|
||||
// 总额度
|
||||
const totalQuota = ref(100)
|
||||
|
||||
// 分销员列表
|
||||
const distributors = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: '张分销',
|
||||
quota: 30
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '李分销',
|
||||
quota: 25
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '王分销',
|
||||
quota: 20
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '刘分销',
|
||||
quota: 15
|
||||
}
|
||||
])
|
||||
|
||||
// 计算单个分销员最大可分配额度
|
||||
const maxDistributorQuota = computed(() => {
|
||||
return Math.floor(totalQuota.value * 0.5)
|
||||
})
|
||||
|
||||
// 计算已分配额度
|
||||
const allocatedQuota = computed(() => {
|
||||
return distributors.value.reduce((sum, distributor) => {
|
||||
return sum + parseInt(distributor.quota || 0)
|
||||
}, 0)
|
||||
})
|
||||
|
||||
// 计算剩余额度
|
||||
const remainingQuota = computed(() => {
|
||||
return totalQuota.value - allocatedQuota.value
|
||||
})
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}
|
||||
|
||||
// 月份变更
|
||||
function onMonthChange(e) {
|
||||
currentMonth.value = e.detail.value
|
||||
const date = new Date(e.detail.value)
|
||||
currentMonthDisplay.value = `${date.getFullYear()}年${String(date.getMonth() + 1).padStart(2, '0')}月`
|
||||
// 实际项目中应根据选择的月份加载对应的额度配置
|
||||
// loadQuotaByMonth(currentMonth.value)
|
||||
}
|
||||
|
||||
// 保存配额
|
||||
function saveQuota() {
|
||||
if (!totalQuota.value || totalQuota.value <= 0) {
|
||||
uni.showToast({
|
||||
title: '请设置有效的总额度',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (remainingQuota.value < 0) {
|
||||
uni.showToast({
|
||||
title: '已分配额度超过总额度',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
uni.showLoading({ title: '保存中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '保存成功',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
// 实际项目中应调用接口保存配额配置
|
||||
// saveQuotaConfig()
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 实际项目中应从接口获取分销员列表和当前月份的配额配置
|
||||
// loadDistributors()
|
||||
// loadCurrentMonthQuota()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* #ifndef APP-NVUE */
|
||||
page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
view {
|
||||
font-size: 14px;
|
||||
line-height: inherit;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.quota-manage-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 120rpx;
|
||||
background-color: #fff;
|
||||
padding: 0 32rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 40rpx;
|
||||
color: #303133;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 60rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.save-btn {
|
||||
font-size: 28rpx;
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 筛选条件 */
|
||||
.filter-section {
|
||||
padding: 24rpx 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.filter-control {
|
||||
background-color: #f9f9f9;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.picker {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
/* 通用部分样式 */
|
||||
.total-quota-section,
|
||||
.distributor-quota-section {
|
||||
padding: 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 0 16rpx 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 24rpx;
|
||||
padding-left: 12rpx;
|
||||
border-left: 8rpx solid #409eff;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* 总额度设置 */
|
||||
.quota-card {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.quota-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.quota-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.quota-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.quota-input {
|
||||
width: 120rpx;
|
||||
height: 60rpx;
|
||||
background-color: #fff;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 0 16rpx;
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.quota-unit {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.quota-hint {
|
||||
font-size: 20rpx;
|
||||
color: #909399;
|
||||
margin-top: 16rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* 分销员额度分配 */
|
||||
.quota-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.distributor-info {
|
||||
flex: 1;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
|
||||
.distributor-name {
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.distributor-id {
|
||||
font-size: 20rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 配额统计 */
|
||||
.quota-stats {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding: 24rpx;
|
||||
background-color: #fff;
|
||||
margin: 0 16rpx 32rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.stats-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.stats-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.stats-value {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.stats-value.warning {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media screen and (min-width: 500px) {
|
||||
.quota-manage-container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.quota-input {
|
||||
width: 160rpx;
|
||||
height: 72rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.distributor-name {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
672
src/pages/distribution/data.vue
Normal file
672
src/pages/distribution/data.vue
Normal file
@@ -0,0 +1,672 @@
|
||||
<template>
|
||||
<view class="distribution-data-container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<view class="back-icon">←</view>
|
||||
</view>
|
||||
<view class="header-title">分销数据查看</view>
|
||||
<view class="header-right" @click="exportData">
|
||||
<view class="export-btn">导出</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选条件 -->
|
||||
<view class="filter-section">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">时间范围</view>
|
||||
<view class="filter-control">
|
||||
<picker
|
||||
:range="dateOptions"
|
||||
:value="dateIndex"
|
||||
@change="onDateChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ dateOptions[dateIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">分销员</view>
|
||||
<view class="filter-control">
|
||||
<picker
|
||||
:range="distributorOptions"
|
||||
:value="distributorIndex"
|
||||
@change="onDistributorChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ distributorOptions[distributorIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">搜索</view>
|
||||
<view class="filter-control">
|
||||
<input
|
||||
v-model="searchKeyword"
|
||||
class="search-input"
|
||||
placeholder="请输入推广码或学员姓名"
|
||||
@input="onSearch"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 核心指标 -->
|
||||
<view class="metrics-section">
|
||||
<view class="section-title">核心指标</view>
|
||||
<view class="metrics-grid">
|
||||
<view class="metric-card">
|
||||
<view class="metric-icon">📱</view>
|
||||
<view class="metric-content">
|
||||
<view class="metric-value">{{ metrics.scanCount }}</view>
|
||||
<view class="metric-label">扫码数</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="metric-card">
|
||||
<view class="metric-icon">👥</view>
|
||||
<view class="metric-content">
|
||||
<view class="metric-value">{{ metrics.registerCount }}</view>
|
||||
<view class="metric-label">注册数</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="metric-card">
|
||||
<view class="metric-icon">🎓</view>
|
||||
<view class="metric-content">
|
||||
<view class="metric-value">{{ metrics.memberCount }}</view>
|
||||
<view class="metric-label">会员数</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="metric-card">
|
||||
<view class="metric-icon">💰</view>
|
||||
<view class="metric-content">
|
||||
<view class="metric-value">{{ metrics.totalProfit }}</view>
|
||||
<view class="metric-label">分润总额</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 数据趋势 -->
|
||||
<view class="trend-section">
|
||||
<view class="section-title">数据趋势</view>
|
||||
<view class="chart-container">
|
||||
<!-- 模拟图表,实际项目中应使用图表库 -->
|
||||
<view class="mock-chart">
|
||||
<view class="chart-header">
|
||||
<view class="chart-title">近7天数据趋势</view>
|
||||
</view>
|
||||
<view class="chart-body">
|
||||
<view class="chart-bars">
|
||||
<view v-for="(day, index) in trendData" :key="index" class="chart-bar">
|
||||
<view class="bar-container">
|
||||
<view class="bar" :style="{ height: day.value + '%' }"></view>
|
||||
</view>
|
||||
<view class="bar-label">{{ day.date }}</view>
|
||||
<view class="bar-value">{{ day.count }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 推广明细 -->
|
||||
<view class="detail-section">
|
||||
<view class="section-title">推广明细</view>
|
||||
<view class="detail-list">
|
||||
<view
|
||||
v-for="(item, index) in promotionList"
|
||||
:key="index"
|
||||
class="detail-item"
|
||||
>
|
||||
<view class="detail-header">
|
||||
<view class="detail-time">{{ item.time }}</view>
|
||||
<view class="detail-status" :class="item.statusClass">{{ item.status }}</view>
|
||||
</view>
|
||||
<view class="detail-content">
|
||||
<view class="detail-info">
|
||||
<view class="info-item">
|
||||
<view class="info-label">推广码:</view>
|
||||
<view class="info-value">{{ item.promotionCode }}</view>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="info-label">学员:</view>
|
||||
<view class="info-value">{{ item.studentName }}</view>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="info-label">分销员:</view>
|
||||
<view class="info-value">{{ item.distributorName }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="detail-amount">
|
||||
<view class="amount-label">分润金额:</view>
|
||||
<view class="amount-value">{{ item.profitAmount }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页 -->
|
||||
<view class="pagination">
|
||||
<view class="page-info">
|
||||
共 {{ totalItems }} 条,当前第 {{ currentPage }} 页
|
||||
</view>
|
||||
<view class="page-controls">
|
||||
<view
|
||||
class="page-btn"
|
||||
:class="{ disabled: currentPage === 1 }"
|
||||
@click="goToPage(currentPage - 1)"
|
||||
>
|
||||
上一页
|
||||
</view>
|
||||
<view
|
||||
class="page-btn"
|
||||
:class="{ disabled: currentPage === totalPages }"
|
||||
@click="goToPage(currentPage + 1)"
|
||||
>
|
||||
下一页
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from "vue"
|
||||
|
||||
// 时间范围选项
|
||||
const dateOptions = ['近7天', '近30天', '近90天', '今年', '全部']
|
||||
const dateIndex = ref(0)
|
||||
|
||||
// 分销员选项
|
||||
const distributorOptions = ['全部分销员', '张分销', '李分销', '王分销', '刘分销']
|
||||
const distributorIndex = ref(0)
|
||||
|
||||
// 搜索关键词
|
||||
const searchKeyword = ref('')
|
||||
|
||||
// 核心指标
|
||||
const metrics = ref({
|
||||
scanCount: 1234,
|
||||
registerCount: 890,
|
||||
memberCount: 456,
|
||||
totalProfit: '¥12,345.67'
|
||||
})
|
||||
|
||||
// 趋势数据
|
||||
const trendData = ref([
|
||||
{ date: '1/23', count: 120, value: 60 },
|
||||
{ date: '1/24', count: 150, value: 75 },
|
||||
{ date: '1/25', count: 100, value: 50 },
|
||||
{ date: '1/26', count: 200, value: 100 },
|
||||
{ date: '1/27', count: 180, value: 90 },
|
||||
{ date: '1/28', count: 160, value: 80 },
|
||||
{ date: '1/29', count: 190, value: 95 }
|
||||
])
|
||||
|
||||
// 推广明细
|
||||
const promotionList = ref([
|
||||
{
|
||||
id: 1,
|
||||
time: '2026-01-29 15:30:45',
|
||||
promotionCode: 'SD20260129001',
|
||||
studentName: '张**',
|
||||
distributorName: '张分销',
|
||||
profitAmount: '¥128.00',
|
||||
status: '已完成',
|
||||
statusClass: 'status-success'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
time: '2026-01-29 14:20:30',
|
||||
promotionCode: 'SD20260129002',
|
||||
studentName: '李**',
|
||||
distributorName: '李分销',
|
||||
profitAmount: '¥99.00',
|
||||
status: '已完成',
|
||||
statusClass: 'status-success'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
time: '2026-01-29 10:15:20',
|
||||
promotionCode: 'SD20260129003',
|
||||
studentName: '王**',
|
||||
distributorName: '王分销',
|
||||
profitAmount: '¥128.00',
|
||||
status: '已完成',
|
||||
statusClass: 'status-success'
|
||||
}
|
||||
])
|
||||
|
||||
// 分页信息
|
||||
const currentPage = ref(1)
|
||||
const totalItems = ref(100)
|
||||
const pageSize = ref(10)
|
||||
|
||||
// 计算总页数
|
||||
const totalPages = computed(() => {
|
||||
return Math.ceil(totalItems.value / pageSize.value)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// 实际项目中应从接口获取分销数据
|
||||
// loadDistributionData()
|
||||
})
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}
|
||||
|
||||
// 导出数据
|
||||
function exportData() {
|
||||
uni.showModal({
|
||||
title: '导出数据',
|
||||
content: '确定要导出当前筛选条件的分销数据吗?',
|
||||
success: function(res) {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: '导出中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '导出成功',
|
||||
icon: 'success'
|
||||
})
|
||||
// 实际项目中应调用接口导出数据
|
||||
// exportDistributionData()
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 时间范围变更
|
||||
function onDateChange(e) {
|
||||
const value = e.detail.value
|
||||
dateIndex.value = value
|
||||
// 实际项目中应根据时间范围筛选数据
|
||||
// filterData()
|
||||
}
|
||||
|
||||
// 分销员变更
|
||||
function onDistributorChange(e) {
|
||||
const value = e.detail.value
|
||||
distributorIndex.value = value
|
||||
// 实际项目中应根据分销员筛选数据
|
||||
// filterData()
|
||||
}
|
||||
|
||||
// 搜索
|
||||
function onSearch() {
|
||||
// 实际项目中应根据关键词搜索数据
|
||||
// searchData()
|
||||
}
|
||||
|
||||
// 跳转到指定页码
|
||||
function goToPage(page) {
|
||||
if (page < 1 || page > totalPages.value) {
|
||||
return
|
||||
}
|
||||
currentPage.value = page
|
||||
// 实际项目中应加载指定页码的数据
|
||||
// loadPageData(page)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* #ifndef APP-NVUE */
|
||||
page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
view {
|
||||
font-size: 14px;
|
||||
line-height: inherit;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.distribution-data-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 120rpx;
|
||||
background-color: #fff;
|
||||
padding: 0 32rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 40rpx;
|
||||
color: #303133;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 60rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.export-btn {
|
||||
font-size: 28rpx;
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 筛选条件 */
|
||||
.filter-section {
|
||||
padding: 24rpx 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
flex: 1;
|
||||
min-width: 200rpx;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.filter-control {
|
||||
background-color: #f9f9f9;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.picker {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 通用部分样式 */
|
||||
.metrics-section,
|
||||
.trend-section,
|
||||
.detail-section {
|
||||
padding: 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 0 16rpx 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 24rpx;
|
||||
padding-left: 12rpx;
|
||||
border-left: 8rpx solid #409eff;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* 核心指标 */
|
||||
.metrics-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.metric-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.metric-icon {
|
||||
font-size: 48rpx;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
|
||||
.metric-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.metric-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
/* 数据趋势 */
|
||||
.chart-container {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.mock-chart {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.chart-header {
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.chart-bars {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
height: 300rpx;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.chart-bar {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.bar-container {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.bar {
|
||||
width: 40rpx;
|
||||
background-color: #409eff;
|
||||
border-radius: 4rpx 4rpx 0 0;
|
||||
transition: height 0.5s ease;
|
||||
}
|
||||
|
||||
.bar-label {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.bar-value {
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
/* 推广明细 */
|
||||
.detail-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.detail-time {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.detail-status {
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.status-success {
|
||||
color: #67c23a;
|
||||
background-color: rgba(103, 194, 58, 0.1);
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.detail-info {
|
||||
flex: 1;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
width: 80rpx;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 20rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.detail-amount {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.amount-label {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.amount-value {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
/* 分页 */
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.page-info {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.page-controls {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
color: #409eff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.page-btn.disabled {
|
||||
color: #909399;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
535
src/pages/distribution/personal.vue
Normal file
535
src/pages/distribution/personal.vue
Normal file
@@ -0,0 +1,535 @@
|
||||
<template>
|
||||
<view class="personal-promotion-container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<view class="back-icon">←</view>
|
||||
</view>
|
||||
<view class="header-title">个人推广管理</view>
|
||||
<view class="header-right" @click="sharePromotion">
|
||||
<view class="share-btn">分享</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 推广码信息 -->
|
||||
<view class="promotion-info-section">
|
||||
<view class="section-title">推广码信息</view>
|
||||
<view class="info-card">
|
||||
<view class="info-row">
|
||||
<view class="info-label">推广码</view>
|
||||
<view class="info-value">{{ promotionCode }}</view>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<view class="info-label">生成时间</view>
|
||||
<view class="info-value">{{ createTime }}</view>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<view class="info-label">有效期</view>
|
||||
<view class="info-value">{{ validityPeriod }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 推广二维码 -->
|
||||
<view class="qrcode-section">
|
||||
<view class="section-title">推广二维码</view>
|
||||
<view class="qrcode-card">
|
||||
<view class="qrcode-image">
|
||||
<!-- 模拟二维码图片 -->
|
||||
<view class="mock-qrcode">
|
||||
<view class="qrcode-pattern"></view>
|
||||
<view class="qrcode-text">{{ promotionCode }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="qrcode-hint">
|
||||
请保存二维码并分享给学员,学员扫码注册后将自动绑定为您的推广学员
|
||||
</view>
|
||||
<view class="qrcode-actions">
|
||||
<view class="action-btn download-btn" @click="downloadQrcode">
|
||||
下载二维码
|
||||
</view>
|
||||
<view class="action-btn refresh-btn" @click="refreshQrcode">
|
||||
刷新二维码
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 推广数据 -->
|
||||
<view class="promotion-data-section">
|
||||
<view class="section-title">推广数据</view>
|
||||
<view class="data-card">
|
||||
<view class="data-row">
|
||||
<view class="data-item">
|
||||
<view class="data-value">{{ totalScanCount }}</view>
|
||||
<view class="data-label">扫码次数</view>
|
||||
</view>
|
||||
<view class="data-item">
|
||||
<view class="data-value">{{ totalRegisterCount }}</view>
|
||||
<view class="data-label">注册人数</view>
|
||||
</view>
|
||||
<view class="data-item">
|
||||
<view class="data-value">{{ totalMemberCount }}</view>
|
||||
<view class="data-label">会员人数</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="data-row">
|
||||
<view class="data-item">
|
||||
<view class="data-value">{{ totalProfit }}</view>
|
||||
<view class="data-label">累计分润</view>
|
||||
</view>
|
||||
<view class="data-item">
|
||||
<view class="data-value">{{ todayScanCount }}</view>
|
||||
<view class="data-label">今日扫码</view>
|
||||
</view>
|
||||
<view class="data-item">
|
||||
<view class="data-value">{{ todayRegisterCount }}</view>
|
||||
<view class="data-label">今日注册</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 推广记录 -->
|
||||
<view class="promotion-record-section">
|
||||
<view class="section-title">推广记录</view>
|
||||
<view class="record-card">
|
||||
<view class="record-header">
|
||||
<view class="header-item">时间</view>
|
||||
<view class="header-item">类型</view>
|
||||
<view class="header-item">状态</view>
|
||||
<view class="header-item">收益</view>
|
||||
</view>
|
||||
<view class="record-list">
|
||||
<view
|
||||
v-for="(record, index) in promotionRecords"
|
||||
:key="index"
|
||||
class="record-item"
|
||||
>
|
||||
<view class="record-cell">{{ record.time }}</view>
|
||||
<view class="record-cell">{{ record.type }}</view>
|
||||
<view class="record-cell" :class="record.statusClass">{{ record.status }}</view>
|
||||
<view class="record-cell profit">{{ record.profit }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="record-footer">
|
||||
<view class="footer-text">
|
||||
共 {{ totalRecords }} 条记录,显示最近 {{ promotionRecords.length }} 条
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue"
|
||||
|
||||
// 推广码信息
|
||||
const promotionCode = ref('SD20260129001')
|
||||
const createTime = ref('2026-01-29 10:00:00')
|
||||
const validityPeriod = ref('永久有效')
|
||||
|
||||
// 推广数据
|
||||
const totalScanCount = ref(120)
|
||||
const totalRegisterCount = ref(89)
|
||||
const totalMemberCount = ref(45)
|
||||
const totalProfit = ref('¥1,234.56')
|
||||
const todayScanCount = ref(12)
|
||||
const todayRegisterCount = ref(8)
|
||||
|
||||
// 推广记录
|
||||
const promotionRecords = ref([
|
||||
{
|
||||
time: '2026-01-29 15:30:00',
|
||||
type: '扫码注册',
|
||||
status: '成功',
|
||||
statusClass: 'status-success',
|
||||
profit: '¥10.00'
|
||||
},
|
||||
{
|
||||
time: '2026-01-29 14:20:00',
|
||||
type: '购买会员',
|
||||
status: '成功',
|
||||
statusClass: 'status-success',
|
||||
profit: '¥7.98'
|
||||
},
|
||||
{
|
||||
time: '2026-01-29 13:10:00',
|
||||
type: '扫码注册',
|
||||
status: '成功',
|
||||
statusClass: 'status-success',
|
||||
profit: '¥10.00'
|
||||
},
|
||||
{
|
||||
time: '2026-01-29 12:00:00',
|
||||
type: '扫码注册',
|
||||
status: '失败',
|
||||
statusClass: 'status-failed',
|
||||
profit: '¥0.00'
|
||||
}
|
||||
])
|
||||
|
||||
const totalRecords = ref(100)
|
||||
|
||||
onMounted(() => {
|
||||
// 实际项目中应从接口获取个人推广信息
|
||||
// loadPersonalPromotionInfo()
|
||||
})
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}
|
||||
|
||||
// 分享推广
|
||||
function sharePromotion() {
|
||||
uni.showActionSheet({
|
||||
itemList: ['分享到微信', '分享到朋友圈', '复制推广链接'],
|
||||
success: function(res) {
|
||||
switch(res.tapIndex) {
|
||||
case 0:
|
||||
uni.showToast({ title: '分享到微信', duration: 1000 })
|
||||
break
|
||||
case 1:
|
||||
uni.showToast({ title: '分享到朋友圈', duration: 1000 })
|
||||
break
|
||||
case 2:
|
||||
uni.setClipboardData({
|
||||
data: `https://example.com/register?code=${promotionCode.value}`,
|
||||
success: function() {
|
||||
uni.showToast({ title: '推广链接已复制', duration: 1000 })
|
||||
}
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 下载二维码
|
||||
function downloadQrcode() {
|
||||
uni.showLoading({ title: '下载中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '二维码已下载',
|
||||
icon: 'success'
|
||||
})
|
||||
// 实际项目中应调用接口下载二维码
|
||||
// downloadQrcodeById()
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
// 刷新二维码
|
||||
function refreshQrcode() {
|
||||
uni.showModal({
|
||||
title: '刷新二维码',
|
||||
content: '刷新二维码后,原二维码将失效,确定要刷新吗?',
|
||||
success: function(res) {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: '刷新中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
// 模拟刷新后生成新的推广码
|
||||
promotionCode.value = 'SD' + new Date().getTime().toString().substring(0, 10)
|
||||
createTime.value = new Date().toLocaleString('zh-CN')
|
||||
uni.showToast({
|
||||
title: '二维码已刷新',
|
||||
icon: 'success'
|
||||
})
|
||||
// 实际项目中应调用接口刷新二维码
|
||||
// refreshQrcodeById()
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* #ifndef APP-NVUE */
|
||||
page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
view {
|
||||
font-size: 14px;
|
||||
line-height: inherit;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.personal-promotion-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 120rpx;
|
||||
background-color: #fff;
|
||||
padding: 0 32rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 40rpx;
|
||||
color: #303133;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 60rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.share-btn {
|
||||
font-size: 24rpx;
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 通用部分样式 */
|
||||
.promotion-info-section,
|
||||
.qrcode-section,
|
||||
.promotion-data-section,
|
||||
.promotion-record-section {
|
||||
padding: 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 0 16rpx 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 24rpx;
|
||||
padding-left: 12rpx;
|
||||
border-left: 8rpx solid #409eff;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* 推广码信息 */
|
||||
.info-card {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.info-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 推广二维码 */
|
||||
.qrcode-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.qrcode-image {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.mock-qrcode {
|
||||
width: 300rpx;
|
||||
height: 300rpx;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.qrcode-pattern {
|
||||
width: 240rpx;
|
||||
height: 240rpx;
|
||||
background-color: #303133;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.qrcode-text {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.qrcode-hint {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
text-align: center;
|
||||
line-height: 1.4;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.qrcode-actions {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 12rpx 24rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.download-btn {
|
||||
background-color: #ecf5ff;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
background-color: #f0f9eb;
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
/* 推广数据 */
|
||||
.data-card {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.data-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.data-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.data-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.data-value {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.data-label {
|
||||
font-size: 18rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
/* 推广记录 */
|
||||
.record-card {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.record-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16rpx;
|
||||
padding-bottom: 16rpx;
|
||||
border-bottom: 1rpx solid #e0e0e0;
|
||||
}
|
||||
|
||||
.header-item {
|
||||
flex: 1;
|
||||
font-size: 20rpx;
|
||||
font-weight: bold;
|
||||
color: #606266;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.record-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.record-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.record-cell {
|
||||
flex: 1;
|
||||
font-size: 20rpx;
|
||||
color: #303133;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.record-cell.profit {
|
||||
color: #f56c6c;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.status-success {
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.status-failed {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.record-footer {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
font-size: 18rpx;
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
533
src/pages/distribution/profit-rule.vue
Normal file
533
src/pages/distribution/profit-rule.vue
Normal file
@@ -0,0 +1,533 @@
|
||||
<template>
|
||||
<view class="profit-rule-container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<view class="back-icon">←</view>
|
||||
</view>
|
||||
<view class="header-title">分润规则配置</view>
|
||||
<view class="header-right" @click="saveRules">
|
||||
<view class="save-btn">保存</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 规则说明 -->
|
||||
<view class="rule-info-section">
|
||||
<view class="section-title">规则说明</view>
|
||||
<view class="info-card">
|
||||
<view class="info-item">• 分润规则适用于所有分销员</view>
|
||||
<view class="info-item">• 扫码注册分润:学员通过推广码扫码注册后,分销员可获得的分润</view>
|
||||
<view class="info-item">• 购买会员分润:学员通过推广码购买会员后,分销员可获得的分润</view>
|
||||
<view class="info-item">• 分润比例:按会员价格的百分比计算,最高不超过50%</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分润规则配置 -->
|
||||
<view class="rule-config-section">
|
||||
<view class="section-title">分润规则配置</view>
|
||||
<view class="rule-card">
|
||||
<!-- 扫码注册分润 -->
|
||||
<view class="rule-item">
|
||||
<view class="rule-header">
|
||||
<view class="rule-title">扫码注册分润</view>
|
||||
<view class="rule-toggle">
|
||||
<switch
|
||||
:checked="scanRegisterRule.enabled"
|
||||
@change="onScanRegisterToggle"
|
||||
class="toggle-switch"
|
||||
active-color="#409eff"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="scanRegisterRule.enabled" class="rule-content">
|
||||
<view class="rule-row">
|
||||
<view class="rule-label">分润金额</view>
|
||||
<view class="rule-control">
|
||||
<input
|
||||
v-model="scanRegisterRule.amount"
|
||||
type="number"
|
||||
class="rule-input"
|
||||
placeholder="请输入分润金额"
|
||||
min="0"
|
||||
max="100"
|
||||
step="0.1"
|
||||
/>
|
||||
<view class="rule-unit">元</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="rule-hint">
|
||||
• 固定金额分润,学员扫码注册后立即发放
|
||||
• 建议设置为5-20元
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 购买会员分润 -->
|
||||
<view class="rule-item">
|
||||
<view class="rule-header">
|
||||
<view class="rule-title">购买会员分润</view>
|
||||
<view class="rule-toggle">
|
||||
<switch
|
||||
:checked="memberPurchaseRule.enabled"
|
||||
@change="onMemberPurchaseToggle"
|
||||
class="toggle-switch"
|
||||
active-color="#409eff"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="memberPurchaseRule.enabled" class="rule-content">
|
||||
<view class="rule-row">
|
||||
<view class="rule-label">分润比例</view>
|
||||
<view class="rule-control">
|
||||
<input
|
||||
v-model="memberPurchaseRule.percentage"
|
||||
type="number"
|
||||
class="rule-input"
|
||||
placeholder="请输入分润比例"
|
||||
min="0"
|
||||
max="50"
|
||||
step="0.1"
|
||||
/>
|
||||
<view class="rule-unit">%</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="rule-row">
|
||||
<view class="rule-label">会员类型</view>
|
||||
<view class="rule-control">
|
||||
<picker
|
||||
:range="memberTypes"
|
||||
:value="memberTypeIndex"
|
||||
@change="onMemberTypeChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ memberTypes[memberTypeIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="rule-hint">
|
||||
• 按会员价格的百分比计算分润
|
||||
• 最高分润比例为50%
|
||||
• 建议设置为10%-30%
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 会员价格设置 -->
|
||||
<view class="price-section">
|
||||
<view class="section-title">会员价格设置</view>
|
||||
<view class="price-card">
|
||||
<view class="price-item">
|
||||
<view class="price-label">月度会员价格</view>
|
||||
<view class="price-control">
|
||||
<input
|
||||
v-model="memberPrices.monthly"
|
||||
type="number"
|
||||
class="price-input"
|
||||
placeholder="请输入月度会员价格"
|
||||
min="0"
|
||||
max="1000"
|
||||
step="0.1"
|
||||
/>
|
||||
<view class="price-unit">元</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="price-item">
|
||||
<view class="price-label">季度会员价格</view>
|
||||
<view class="price-control">
|
||||
<input
|
||||
v-model="memberPrices.quarterly"
|
||||
type="number"
|
||||
class="price-input"
|
||||
placeholder="请输入季度会员价格"
|
||||
min="0"
|
||||
max="3000"
|
||||
step="0.1"
|
||||
/>
|
||||
<view class="price-unit">元</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="price-item">
|
||||
<view class="price-label">年度会员价格</view>
|
||||
<view class="price-control">
|
||||
<input
|
||||
v-model="memberPrices.yearly"
|
||||
type="number"
|
||||
class="price-input"
|
||||
placeholder="请输入年度会员价格"
|
||||
min="0"
|
||||
max="10000"
|
||||
step="0.1"
|
||||
/>
|
||||
<view class="price-unit">元</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分润预览 -->
|
||||
<view class="preview-section">
|
||||
<view class="section-title">分润预览</view>
|
||||
<view class="preview-card">
|
||||
<view class="preview-item">
|
||||
<view class="preview-label">月度会员分润</view>
|
||||
<view class="preview-value">{{ calculatePreview('monthly') }}</view>
|
||||
</view>
|
||||
<view class="preview-item">
|
||||
<view class="preview-label">季度会员分润</view>
|
||||
<view class="preview-value">{{ calculatePreview('quarterly') }}</view>
|
||||
</view>
|
||||
<view class="preview-item">
|
||||
<view class="preview-label">年度会员分润</view>
|
||||
<view class="preview-value">{{ calculatePreview('yearly') }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from "vue"
|
||||
|
||||
// 扫码注册分润规则
|
||||
const scanRegisterRule = ref({
|
||||
enabled: true,
|
||||
amount: 10
|
||||
})
|
||||
|
||||
// 购买会员分润规则
|
||||
const memberPurchaseRule = ref({
|
||||
enabled: true,
|
||||
percentage: 20
|
||||
})
|
||||
|
||||
// 会员类型选项
|
||||
const memberTypes = ['全部会员类型', '月度会员', '季度会员', '年度会员']
|
||||
const memberTypeIndex = ref(0)
|
||||
|
||||
// 会员价格
|
||||
const memberPrices = ref({
|
||||
monthly: 39.9,
|
||||
quarterly: 99.9,
|
||||
yearly: 299.9
|
||||
})
|
||||
|
||||
// 计算分润预览
|
||||
function calculatePreview(type) {
|
||||
if (!memberPurchaseRule.value.enabled) {
|
||||
return '¥0.00'
|
||||
}
|
||||
|
||||
const price = memberPrices.value[type]
|
||||
const percentage = memberPurchaseRule.value.percentage
|
||||
const amount = (price * percentage) / 100
|
||||
|
||||
return `¥${amount.toFixed(2)}`
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 实际项目中应从接口获取分润规则配置
|
||||
// loadProfitRules()
|
||||
})
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}
|
||||
|
||||
// 保存规则
|
||||
function saveRules() {
|
||||
uni.showModal({
|
||||
title: '保存规则',
|
||||
content: '确定要保存分润规则配置吗?',
|
||||
success: function(res) {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: '保存中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '保存成功',
|
||||
icon: 'success'
|
||||
})
|
||||
// 实际项目中应调用接口保存分润规则
|
||||
// saveProfitRules()
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 扫码注册分润开关
|
||||
function onScanRegisterToggle(e) {
|
||||
scanRegisterRule.value.enabled = e.detail.value
|
||||
}
|
||||
|
||||
// 购买会员分润开关
|
||||
function onMemberPurchaseToggle(e) {
|
||||
memberPurchaseRule.value.enabled = e.detail.value
|
||||
}
|
||||
|
||||
// 会员类型变更
|
||||
function onMemberTypeChange(e) {
|
||||
memberTypeIndex.value = e.detail.value
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* #ifndef APP-NVUE */
|
||||
page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
view {
|
||||
font-size: 14px;
|
||||
line-height: inherit;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.profit-rule-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 120rpx;
|
||||
background-color: #fff;
|
||||
padding: 0 32rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 40rpx;
|
||||
color: #303133;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 60rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.save-btn {
|
||||
font-size: 28rpx;
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 通用部分样式 */
|
||||
.rule-info-section,
|
||||
.rule-config-section,
|
||||
.price-section,
|
||||
.preview-section {
|
||||
padding: 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 0 16rpx 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 24rpx;
|
||||
padding-left: 12rpx;
|
||||
border-left: 8rpx solid #409eff;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* 规则说明 */
|
||||
.info-card {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 8rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* 分润规则配置 */
|
||||
.rule-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.rule-item {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.rule-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.rule-title {
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.rule-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.rule-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.rule-label {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.rule-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.rule-input {
|
||||
width: 120rpx;
|
||||
height: 60rpx;
|
||||
background-color: #fff;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 0 16rpx;
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.rule-unit {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.rule-hint {
|
||||
font-size: 20rpx;
|
||||
color: #909399;
|
||||
margin-top: 8rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.picker {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
min-width: 200rpx;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
/* 会员价格设置 */
|
||||
.price-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.price-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.price-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.price-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.price-input {
|
||||
width: 160rpx;
|
||||
height: 60rpx;
|
||||
background-color: #fff;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 0 16rpx;
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.price-unit {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
/* 分润预览 */
|
||||
.preview-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.preview-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.preview-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.preview-value {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #f56c6c;
|
||||
}
|
||||
</style>
|
||||
528
src/pages/distribution/profit.vue
Normal file
528
src/pages/distribution/profit.vue
Normal file
@@ -0,0 +1,528 @@
|
||||
<template>
|
||||
<view class="profit-query-container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<view class="back-icon">←</view>
|
||||
</view>
|
||||
<view class="header-title">分润查询</view>
|
||||
<view class="header-right" @click="exportProfit">
|
||||
<view class="export-btn">导出</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分润概览 -->
|
||||
<view class="profit-overview-section">
|
||||
<view class="section-title">分润概览</view>
|
||||
<view class="overview-card">
|
||||
<view class="overview-row">
|
||||
<view class="overview-item">
|
||||
<view class="overview-value">{{ totalProfit }}</view>
|
||||
<view class="overview-label">累计分润</view>
|
||||
</view>
|
||||
<view class="overview-item">
|
||||
<view class="overview-value">{{ todayProfit }}</view>
|
||||
<view class="overview-label">今日分润</view>
|
||||
</view>
|
||||
<view class="overview-item">
|
||||
<view class="overview-value">{{ monthProfit }}</view>
|
||||
<view class="overview-label">本月分润</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="overview-row">
|
||||
<view class="overview-item">
|
||||
<view class="overview-value">{{ pendingProfit }}</view>
|
||||
<view class="overview-label">待结算</view>
|
||||
</view>
|
||||
<view class="overview-item">
|
||||
<view class="overview-value">{{ settledProfit }}</view>
|
||||
<view class="overview-label">已结算</view>
|
||||
</view>
|
||||
<view class="overview-item">
|
||||
<view class="overview-value">{{ withdrawableProfit }}</view>
|
||||
<view class="overview-label">可提现</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选条件 -->
|
||||
<view class="filter-section">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">时间范围</view>
|
||||
<view class="filter-control">
|
||||
<picker
|
||||
:range="timeRangeOptions"
|
||||
:value="timeRangeIndex"
|
||||
@change="onTimeRangeChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ timeRangeOptions[timeRangeIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">分润类型</view>
|
||||
<view class="filter-control">
|
||||
<picker
|
||||
:range="profitTypeOptions"
|
||||
:value="profitTypeIndex"
|
||||
@change="onProfitTypeChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ profitTypeOptions[profitTypeIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">状态</view>
|
||||
<view class="filter-control">
|
||||
<picker
|
||||
:range="statusOptions"
|
||||
:value="statusIndex"
|
||||
@change="onStatusChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ statusOptions[statusIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分润明细 -->
|
||||
<view class="profit-detail-section">
|
||||
<view class="section-title">分润明细</view>
|
||||
<view class="detail-card">
|
||||
<view class="detail-header">
|
||||
<view class="header-item">时间</view>
|
||||
<view class="header-item">类型</view>
|
||||
<view class="header-item">金额</view>
|
||||
<view class="header-item">状态</view>
|
||||
</view>
|
||||
<view class="detail-list">
|
||||
<view
|
||||
v-for="(item, index) in profitList"
|
||||
:key="index"
|
||||
class="detail-item"
|
||||
>
|
||||
<view class="detail-cell">{{ item.time }}</view>
|
||||
<view class="detail-cell">{{ item.type }}</view>
|
||||
<view class="detail-cell amount">{{ item.amount }}</view>
|
||||
<view class="detail-cell" :class="item.statusClass">{{ item.status }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="detail-footer">
|
||||
<view class="footer-text">
|
||||
共 {{ totalItems }} 条记录,当前第 {{ currentPage }} / {{ totalPages }} 页
|
||||
</view>
|
||||
<view class="footer-controls">
|
||||
<view
|
||||
class="control-btn"
|
||||
:class="{ disabled: currentPage === 1 }"
|
||||
@click="goToPage(currentPage - 1)"
|
||||
>
|
||||
上一页
|
||||
</view>
|
||||
<view
|
||||
class="control-btn"
|
||||
:class="{ disabled: currentPage === totalPages }"
|
||||
@click="goToPage(currentPage + 1)"
|
||||
>
|
||||
下一页
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from "vue"
|
||||
|
||||
// 分润概览数据
|
||||
const totalProfit = ref('¥1,234.56')
|
||||
const todayProfit = ref('¥123.45')
|
||||
const monthProfit = ref('¥3,456.78')
|
||||
const pendingProfit = ref('¥567.89')
|
||||
const settledProfit = ref('¥666.67')
|
||||
const withdrawableProfit = ref('¥456.78')
|
||||
|
||||
// 筛选条件
|
||||
const timeRangeOptions = ['全部时间', '今日', '本周', '本月', '本季度', '本年', '自定义']
|
||||
const timeRangeIndex = ref(3)
|
||||
|
||||
const profitTypeOptions = ['全部类型', '扫码注册', '购买会员']
|
||||
const profitTypeIndex = ref(0)
|
||||
|
||||
const statusOptions = ['全部状态', '待结算', '已结算', '已提现']
|
||||
const statusIndex = ref(0)
|
||||
|
||||
// 分润明细数据
|
||||
const profitList = ref([
|
||||
{
|
||||
time: '2026-01-29 15:30:00',
|
||||
type: '扫码注册',
|
||||
amount: '¥10.00',
|
||||
status: '待结算',
|
||||
statusClass: 'status-pending'
|
||||
},
|
||||
{
|
||||
time: '2026-01-29 14:20:00',
|
||||
type: '购买会员',
|
||||
amount: '¥7.98',
|
||||
status: '待结算',
|
||||
statusClass: 'status-pending'
|
||||
},
|
||||
{
|
||||
time: '2026-01-28 10:15:00',
|
||||
type: '扫码注册',
|
||||
amount: '¥10.00',
|
||||
status: '已结算',
|
||||
statusClass: 'status-settled'
|
||||
},
|
||||
{
|
||||
time: '2026-01-27 09:45:00',
|
||||
type: '购买会员',
|
||||
amount: '¥7.98',
|
||||
status: '已结算',
|
||||
statusClass: 'status-settled'
|
||||
},
|
||||
{
|
||||
time: '2026-01-26 16:20:00',
|
||||
type: '扫码注册',
|
||||
amount: '¥10.00',
|
||||
status: '已提现',
|
||||
statusClass: 'status-withdrawn'
|
||||
}
|
||||
])
|
||||
|
||||
// 分页信息
|
||||
const currentPage = ref(1)
|
||||
const totalItems = ref(100)
|
||||
const pageSize = ref(10)
|
||||
|
||||
// 计算总页数
|
||||
const totalPages = computed(() => {
|
||||
return Math.ceil(totalItems.value / pageSize.value)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// 实际项目中应从接口获取分润数据
|
||||
// loadProfitData()
|
||||
})
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}
|
||||
|
||||
// 导出分润
|
||||
function exportProfit() {
|
||||
uni.showModal({
|
||||
title: '导出分润明细',
|
||||
content: '确定要导出当前筛选条件下的分润明细吗?',
|
||||
success: function(res) {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: '导出中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '导出成功',
|
||||
icon: 'success'
|
||||
})
|
||||
// 实际项目中应调用接口导出分润
|
||||
// exportProfitData()
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 时间范围变更
|
||||
function onTimeRangeChange(e) {
|
||||
const value = e.detail.value
|
||||
timeRangeIndex.value = value
|
||||
// 实际项目中应根据时间范围筛选分润数据
|
||||
// filterProfitData()
|
||||
}
|
||||
|
||||
// 分润类型变更
|
||||
function onProfitTypeChange(e) {
|
||||
const value = e.detail.value
|
||||
profitTypeIndex.value = value
|
||||
// 实际项目中应根据分润类型筛选分润数据
|
||||
// filterProfitData()
|
||||
}
|
||||
|
||||
// 状态变更
|
||||
function onStatusChange(e) {
|
||||
const value = e.detail.value
|
||||
statusIndex.value = value
|
||||
// 实际项目中应根据状态筛选分润数据
|
||||
// filterProfitData()
|
||||
}
|
||||
|
||||
// 跳转到指定页码
|
||||
function goToPage(page) {
|
||||
if (page < 1 || page > totalPages.value) {
|
||||
return
|
||||
}
|
||||
currentPage.value = page
|
||||
// 实际项目中应加载指定页码的分润数据
|
||||
// loadProfitPage(page)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* #ifndef APP-NVUE */
|
||||
page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
view {
|
||||
font-size: 14px;
|
||||
line-height: inherit;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.profit-query-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 120rpx;
|
||||
background-color: #fff;
|
||||
padding: 0 32rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 40rpx;
|
||||
color: #303133;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 60rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.export-btn {
|
||||
font-size: 24rpx;
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 通用部分样式 */
|
||||
.profit-overview-section,
|
||||
.filter-section,
|
||||
.profit-detail-section {
|
||||
padding: 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 0 16rpx 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 24rpx;
|
||||
padding-left: 12rpx;
|
||||
border-left: 8rpx solid #409eff;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* 分润概览 */
|
||||
.overview-card {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.overview-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.overview-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.overview-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.overview-value {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.overview-label {
|
||||
font-size: 18rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
/* 筛选条件 */
|
||||
.filter-row {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
flex: 1;
|
||||
min-width: 200rpx;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.filter-control {
|
||||
background-color: #f9f9f9;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.picker {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
/* 分润明细 */
|
||||
.detail-card {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16rpx;
|
||||
padding-bottom: 16rpx;
|
||||
border-bottom: 1rpx solid #e0e0e0;
|
||||
}
|
||||
|
||||
.header-item {
|
||||
flex: 1;
|
||||
font-size: 20rpx;
|
||||
font-weight: bold;
|
||||
color: #606266;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.detail-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.detail-cell {
|
||||
flex: 1;
|
||||
font-size: 20rpx;
|
||||
color: #303133;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.detail-cell.amount {
|
||||
color: #f56c6c;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.status-settled {
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.status-withdrawn {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.detail-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-top: 16rpx;
|
||||
border-top: 1rpx solid #e0e0e0;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.footer-controls {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 20rpx;
|
||||
color: #409eff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.control-btn.disabled {
|
||||
color: #909399;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
654
src/pages/distribution/qrcode.vue
Normal file
654
src/pages/distribution/qrcode.vue
Normal file
@@ -0,0 +1,654 @@
|
||||
<template>
|
||||
<view class="qrcode-manage-container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<view class="back-icon">←</view>
|
||||
</view>
|
||||
<view class="header-title">专属二维码管理</view>
|
||||
<view class="header-right" @click="generateBatchQrcode">
|
||||
<view class="batch-btn">批量生成</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选条件 -->
|
||||
<view class="filter-section">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">分销员</view>
|
||||
<view class="filter-control">
|
||||
<picker
|
||||
:range="distributorOptions"
|
||||
:value="distributorIndex"
|
||||
@change="onDistributorChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ distributorOptions[distributorIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">状态</view>
|
||||
<view class="filter-control">
|
||||
<picker
|
||||
:range="statusOptions"
|
||||
:value="statusIndex"
|
||||
@change="onStatusChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ statusOptions[statusIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">搜索</view>
|
||||
<view class="filter-control">
|
||||
<input
|
||||
v-model="searchKeyword"
|
||||
class="search-input"
|
||||
placeholder="请输入推广码或备注"
|
||||
@input="onSearch"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 二维码列表 -->
|
||||
<view class="qrcode-list-section">
|
||||
<view class="section-title">二维码列表</view>
|
||||
<view class="qrcode-grid">
|
||||
<view
|
||||
v-for="(qrcode, index) in qrcodeList"
|
||||
:key="index"
|
||||
class="qrcode-item"
|
||||
>
|
||||
<view class="qrcode-card">
|
||||
<view class="qrcode-header">
|
||||
<view class="qrcode-code">{{ qrcode.code }}</view>
|
||||
<view class="qrcode-status" :class="qrcode.statusClass">{{ qrcode.status }}</view>
|
||||
</view>
|
||||
<view class="qrcode-body">
|
||||
<view class="qrcode-image">
|
||||
<!-- 模拟二维码图片 -->
|
||||
<view class="mock-qrcode">
|
||||
<view class="qrcode-pattern"></view>
|
||||
<view class="qrcode-text">{{ qrcode.code }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="qrcode-info">
|
||||
<view class="info-item">
|
||||
<view class="info-label">分销员:</view>
|
||||
<view class="info-value">{{ qrcode.distributor }}</view>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="info-label">生成时间:</view>
|
||||
<view class="info-value">{{ qrcode.createTime }}</view>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="info-label">扫码次数:</view>
|
||||
<view class="info-value">{{ qrcode.scanCount }}</view>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="info-label">备注:</view>
|
||||
<view class="info-value">{{ qrcode.remark }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="qrcode-footer">
|
||||
<view class="action-btn download-btn" @click="downloadQrcode(qrcode.id)">
|
||||
下载
|
||||
</view>
|
||||
<view class="action-btn preview-btn" @click="previewQrcode(qrcode.id)">
|
||||
预览
|
||||
</view>
|
||||
<view class="action-btn" :class="qrcode.status === '启用' ? 'disable-btn' : 'enable-btn'"
|
||||
@click="toggleQrcodeStatus(qrcode.id, qrcode.status)">
|
||||
{{ qrcode.status === '启用' ? '禁用' : '启用' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="qrcodeList.length === 0" class="empty-state">
|
||||
<view class="empty-icon">🔗</view>
|
||||
<view class="empty-text">暂无二维码数据</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页 -->
|
||||
<view v-if="qrcodeList.length > 0" class="pagination">
|
||||
<view class="page-info">
|
||||
共 {{ totalQrcodes }} 条,当前第 {{ currentPage }} 页
|
||||
</view>
|
||||
<view class="page-controls">
|
||||
<view
|
||||
class="page-btn"
|
||||
:class="{ disabled: currentPage === 1 }"
|
||||
@click="goToPage(currentPage - 1)"
|
||||
>
|
||||
上一页
|
||||
</view>
|
||||
<view
|
||||
class="page-btn"
|
||||
:class="{ disabled: currentPage === totalPages }"
|
||||
@click="goToPage(currentPage + 1)"
|
||||
>
|
||||
下一页
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from "vue"
|
||||
|
||||
// 分销员选项
|
||||
const distributorOptions = ['全部分销员', '张分销', '李分销', '王分销', '刘分销']
|
||||
const distributorIndex = ref(0)
|
||||
|
||||
// 状态选项
|
||||
const statusOptions = ['全部状态', '启用', '禁用']
|
||||
const statusIndex = ref(0)
|
||||
|
||||
// 搜索关键词
|
||||
const searchKeyword = ref('')
|
||||
|
||||
// 二维码列表
|
||||
const qrcodeList = ref([
|
||||
{
|
||||
id: 1,
|
||||
code: 'SD20260129001',
|
||||
distributor: '张分销',
|
||||
createTime: '2026-01-29 10:00:00',
|
||||
scanCount: 120,
|
||||
status: '启用',
|
||||
statusClass: 'status-active',
|
||||
remark: '推广活动专用'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
code: 'SD20260129002',
|
||||
distributor: '李分销',
|
||||
createTime: '2026-01-29 11:00:00',
|
||||
scanCount: 89,
|
||||
status: '启用',
|
||||
statusClass: 'status-active',
|
||||
remark: '日常推广'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
code: 'SD20260129003',
|
||||
distributor: '王分销',
|
||||
createTime: '2026-01-29 12:00:00',
|
||||
scanCount: 45,
|
||||
status: '禁用',
|
||||
statusClass: 'status-inactive',
|
||||
remark: '备用二维码'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
code: 'SD20260129004',
|
||||
distributor: '刘分销',
|
||||
createTime: '2026-01-29 13:00:00',
|
||||
scanCount: 67,
|
||||
status: '启用',
|
||||
statusClass: 'status-active',
|
||||
remark: '线下活动专用'
|
||||
}
|
||||
])
|
||||
|
||||
// 分页信息
|
||||
const currentPage = ref(1)
|
||||
const totalQrcodes = ref(100)
|
||||
const pageSize = ref(12)
|
||||
|
||||
// 计算总页数
|
||||
const totalPages = computed(() => {
|
||||
return Math.ceil(totalQrcodes.value / pageSize.value)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// 实际项目中应从接口获取二维码列表
|
||||
// loadQrcodeList()
|
||||
})
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}
|
||||
|
||||
// 批量生成二维码
|
||||
function generateBatchQrcode() {
|
||||
uni.showModal({
|
||||
title: '批量生成二维码',
|
||||
content: '请输入要生成的二维码数量',
|
||||
editable: true,
|
||||
placeholderText: '请输入数量',
|
||||
success: function(res) {
|
||||
if (res.confirm && res.content) {
|
||||
const count = parseInt(res.content)
|
||||
if (isNaN(count) || count <= 0 || count > 100) {
|
||||
uni.showToast({
|
||||
title: '请输入1-100之间的数字',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
uni.showLoading({ title: '生成中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: `成功生成${count}个二维码`,
|
||||
icon: 'success'
|
||||
})
|
||||
// 实际项目中应调用接口批量生成二维码
|
||||
// generateBatchQrcodes(count)
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 分销员变更
|
||||
function onDistributorChange(e) {
|
||||
const value = e.detail.value
|
||||
distributorIndex.value = value
|
||||
// 实际项目中应根据分销员筛选二维码
|
||||
// filterQrcodeList()
|
||||
}
|
||||
|
||||
// 状态变更
|
||||
function onStatusChange(e) {
|
||||
const value = e.detail.value
|
||||
statusIndex.value = value
|
||||
// 实际项目中应根据状态筛选二维码
|
||||
// filterQrcodeList()
|
||||
}
|
||||
|
||||
// 搜索
|
||||
function onSearch() {
|
||||
// 实际项目中应根据关键词搜索二维码
|
||||
// searchQrcodeList()
|
||||
}
|
||||
|
||||
// 下载二维码
|
||||
function downloadQrcode(id) {
|
||||
uni.showLoading({ title: '下载中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '下载成功',
|
||||
icon: 'success'
|
||||
})
|
||||
// 实际项目中应调用接口下载二维码
|
||||
// downloadQrcodeById(id)
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
// 预览二维码
|
||||
function previewQrcode(id) {
|
||||
uni.showToast({
|
||||
title: '预览功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
// 切换二维码状态
|
||||
function toggleQrcodeStatus(id, currentStatus) {
|
||||
const newStatus = currentStatus === '启用' ? '禁用' : '启用'
|
||||
|
||||
uni.showModal({
|
||||
title: '切换状态',
|
||||
content: `确定要${newStatus === '启用' ? '启用' : '禁用'}该二维码吗?`,
|
||||
success: function(res) {
|
||||
if (res.confirm) {
|
||||
// 实际项目中应调用接口切换二维码状态
|
||||
const qrcode = qrcodeList.value.find(item => item.id === id)
|
||||
if (qrcode) {
|
||||
qrcode.status = newStatus
|
||||
qrcode.statusClass = newStatus === '启用' ? 'status-active' : 'status-inactive'
|
||||
}
|
||||
uni.showToast({
|
||||
title: `二维码已${newStatus}`,
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 跳转到指定页码
|
||||
function goToPage(page) {
|
||||
if (page < 1 || page > totalPages.value) {
|
||||
return
|
||||
}
|
||||
currentPage.value = page
|
||||
// 实际项目中应加载指定页码的二维码
|
||||
// loadQrcodePage(page)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* #ifndef APP-NVUE */
|
||||
page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
view {
|
||||
font-size: 14px;
|
||||
line-height: inherit;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.qrcode-manage-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 120rpx;
|
||||
background-color: #fff;
|
||||
padding: 0 32rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 40rpx;
|
||||
color: #303133;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 120rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.batch-btn {
|
||||
font-size: 24rpx;
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 筛选条件 */
|
||||
.filter-section {
|
||||
padding: 24rpx 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
flex: 1;
|
||||
min-width: 200rpx;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.filter-control {
|
||||
background-color: #f9f9f9;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.picker {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 二维码列表 */
|
||||
.qrcode-list-section {
|
||||
flex: 1;
|
||||
padding: 0 16rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 24rpx;
|
||||
padding-left: 12rpx;
|
||||
border-left: 8rpx solid #409eff;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.qrcode-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300rpx, 1fr));
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.qrcode-item {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.qrcode-card {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.qrcode-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.qrcode-code {
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.qrcode-status {
|
||||
font-size: 16rpx;
|
||||
font-weight: 600;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.status-active {
|
||||
color: #67c23a;
|
||||
background-color: rgba(103, 194, 58, 0.1);
|
||||
}
|
||||
|
||||
.status-inactive {
|
||||
color: #909399;
|
||||
background-color: rgba(144, 147, 153, 0.1);
|
||||
}
|
||||
|
||||
.qrcode-body {
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.qrcode-image {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.mock-qrcode {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.qrcode-pattern {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
background-color: #303133;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.qrcode-text {
|
||||
font-size: 16rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.qrcode-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
font-size: 18rpx;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #606266;
|
||||
width: 80rpx;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #303133;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.qrcode-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 16rpx;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.download-btn {
|
||||
background-color: #ecf5ff;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.preview-btn {
|
||||
background-color: #f0f9eb;
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.disable-btn {
|
||||
background-color: #fef0f0;
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.enable-btn {
|
||||
background-color: #f0f9eb;
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 分页 */
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #fff;
|
||||
margin: 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.page-info {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.page-controls {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
color: #409eff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.page-btn.disabled {
|
||||
color: #909399;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
@@ -1,224 +1,314 @@
|
||||
<template>
|
||||
<view class="add-container">
|
||||
<!-- 头部 -->
|
||||
<view class="header">
|
||||
<uni-icons type="back" size="28" color="#303133" @click="handleCancel" />
|
||||
<text class="title">新增分销员</text>
|
||||
<view class="header-right"></view>
|
||||
<view class="distributor-add-container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<view class="back-icon">←</view>
|
||||
</view>
|
||||
<view class="header-title">添加分销员</view>
|
||||
<view class="header-right" @click="saveDistributor">
|
||||
<view class="save-btn">保存</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 表单容器 -->
|
||||
<view class="form-container">
|
||||
<uni-forms :model="formData" ref="formRef" labelWidth="100rpx">
|
||||
<uni-forms-item label="姓名" required name="name">
|
||||
<uni-easyinput
|
||||
v-model="formData.name"
|
||||
placeholder="请输入姓名"
|
||||
class="form-input"
|
||||
:focus="true"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
|
||||
<uni-forms-item label="手机号" required name="phone">
|
||||
<uni-easyinput
|
||||
v-model="formData.phone"
|
||||
placeholder="请输入手机号"
|
||||
class="form-input"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
|
||||
<uni-forms-item label="OpenID" required name="openid">
|
||||
<uni-easyinput
|
||||
v-model="formData.openid"
|
||||
placeholder="请输入OpenID"
|
||||
class="form-input"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
|
||||
<uni-forms-item label="父ID" required name="parent_id">
|
||||
<uni-easyinput
|
||||
v-model="formData.parent_id"
|
||||
placeholder="请输入父ID"
|
||||
type="number"
|
||||
class="form-input"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
|
||||
<uni-forms-item label="租户ID" required name="tenant_id">
|
||||
<uni-easyinput
|
||||
v-model="formData.tenant_id"
|
||||
placeholder="请输入租户ID"
|
||||
type="number"
|
||||
class="form-input"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
|
||||
<uni-forms-item label="苹果URL" name="applet_url">
|
||||
<uni-easyinput
|
||||
v-model="formData.applet_url"
|
||||
placeholder="请输入苹果URL"
|
||||
class="form-input"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
|
||||
<uni-forms-item label="状态" name="is_active">
|
||||
<uni-data-checkbox
|
||||
v-model="formData.is_active"
|
||||
:localdata="statusOptions"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
</uni-forms>
|
||||
</view>
|
||||
|
||||
<!-- 按钮容器 -->
|
||||
<view class="btn-container">
|
||||
<button class="cancel-btn" @click="handleCancel">
|
||||
<uni-icons type="close" size="20" color="#fff" />
|
||||
<text class="btn-text">取消</text>
|
||||
</button>
|
||||
<button class="submit-btn" @click="handleSubmit">
|
||||
<uni-icons type="checkmark" size="20" color="#fff" />
|
||||
<text class="btn-text">提交</text>
|
||||
</button>
|
||||
<!-- 表单内容 -->
|
||||
<view class="form-section">
|
||||
<view class="section-title">基本信息</view>
|
||||
<view class="form-card">
|
||||
<view class="form-row">
|
||||
<view class="form-label">姓名</view>
|
||||
<view class="form-control">
|
||||
<input
|
||||
v-model="distributorForm.name"
|
||||
class="form-input"
|
||||
placeholder="请输入姓名"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-row">
|
||||
<view class="form-label">手机号</view>
|
||||
<view class="form-control">
|
||||
<input
|
||||
v-model="distributorForm.phone"
|
||||
type="number"
|
||||
class="form-input"
|
||||
placeholder="请输入手机号"
|
||||
maxlength="11"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-row">
|
||||
<view class="form-label">角色</view>
|
||||
<view class="form-control">
|
||||
<picker
|
||||
:range="roleOptions"
|
||||
:value="roleIndex"
|
||||
@change="onRoleChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ roleOptions[roleIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-row">
|
||||
<view class="form-label">状态</view>
|
||||
<view class="form-control">
|
||||
<picker
|
||||
:range="statusOptions"
|
||||
:value="statusIndex"
|
||||
@change="onStatusChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ statusOptions[statusIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-row">
|
||||
<view class="form-label">备注</view>
|
||||
<view class="form-control">
|
||||
<textarea
|
||||
v-model="distributorForm.remark"
|
||||
class="form-textarea"
|
||||
placeholder="请输入备注信息"
|
||||
maxlength="200"
|
||||
></textarea>
|
||||
<view class="textarea-count">{{ distributorForm.remark.length }}/200</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue"
|
||||
import { ref, onMounted } from "vue"
|
||||
|
||||
const formRef = ref(null)
|
||||
|
||||
const formData = ref({
|
||||
// 分销员表单数据
|
||||
const distributorForm = ref({
|
||||
name: '',
|
||||
phone: '',
|
||||
openid: '',
|
||||
parent_id: '',
|
||||
tenant_id: '',
|
||||
applet_url: '',
|
||||
is_active: 'Y'
|
||||
role: '初级分销员',
|
||||
status: '启用',
|
||||
remark: ''
|
||||
})
|
||||
|
||||
const statusOptions = [
|
||||
{ text: '激活', value: 'Y' },
|
||||
{ text: '未激活', value: 'N' }
|
||||
]
|
||||
|
||||
// 取消
|
||||
function handleCancel() {
|
||||
uni.navigateBack()
|
||||
// 角色选项
|
||||
const roleOptions = ['初级分销员', '中级分销员', '高级分销员']
|
||||
const roleIndex = ref(0)
|
||||
|
||||
// 状态选项
|
||||
const statusOptions = ['启用', '禁用']
|
||||
const statusIndex = ref(0)
|
||||
|
||||
onMounted(() => {
|
||||
// 初始化表单数据
|
||||
})
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}
|
||||
|
||||
// 提交
|
||||
function handleSubmit() {
|
||||
formRef.value.validate().then(() => {
|
||||
// 这里应该调用后端API提交数据
|
||||
uni.showToast({ title: '新增成功' })
|
||||
uni.navigateTo({ url: '/pages/distributor/index' })
|
||||
}).catch(err => {
|
||||
console.log('表单验证失败', err)
|
||||
})
|
||||
|
||||
// 保存分销员
|
||||
function saveDistributor() {
|
||||
if (!distributorForm.value.name) {
|
||||
uni.showToast({
|
||||
title: '请输入姓名',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (!distributorForm.value.phone || distributorForm.value.phone.length !== 11) {
|
||||
uni.showToast({
|
||||
title: '请输入有效的手机号',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
uni.showLoading({ title: '保存中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '添加成功',
|
||||
icon: 'success'
|
||||
})
|
||||
// 实际项目中应调用接口保存分销员
|
||||
// saveDistributorData()
|
||||
|
||||
// 保存成功后返回上一页
|
||||
setTimeout(() => {
|
||||
goBack()
|
||||
}, 1000)
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
// 角色变更
|
||||
function onRoleChange(e) {
|
||||
const value = e.detail.value
|
||||
roleIndex.value = value
|
||||
distributorForm.value.role = roleOptions[value]
|
||||
}
|
||||
|
||||
// 状态变更
|
||||
function onStatusChange(e) {
|
||||
const value = e.detail.value
|
||||
statusIndex.value = value
|
||||
distributorForm.value.status = statusOptions[value]
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.add-container {
|
||||
padding: 20rpx;
|
||||
/* #ifndef APP-NVUE */
|
||||
page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
view {
|
||||
font-size: 14px;
|
||||
line-height: inherit;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.distributor-add-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 头部样式 */
|
||||
.header {
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 24rpx;
|
||||
padding-bottom: 16rpx;
|
||||
border-bottom: 2rpx solid #ecf5ff;
|
||||
height: 120rpx;
|
||||
background-color: #fff;
|
||||
padding: 0 32rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.title {
|
||||
.header-left {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 40rpx;
|
||||
color: #303133;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 28px;
|
||||
width: 60rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* 表单容器样式 */
|
||||
.form-container {
|
||||
.save-btn {
|
||||
font-size: 24rpx;
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 表单内容 */
|
||||
.form-section {
|
||||
padding: 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 0 16rpx 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 24rpx;
|
||||
padding-left: 12rpx;
|
||||
border-left: 8rpx solid #409eff;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.form-card {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
/* 表单输入框样式 */
|
||||
.form-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
border-radius: 8rpx !important;
|
||||
border: 1rpx solid #dcdfe6 !important;
|
||||
transition: all 0.3s ease !important;
|
||||
}
|
||||
|
||||
.form-input:focus {
|
||||
border-color: #409eff !important;
|
||||
box-shadow: 0 0 0 2rpx rgba(64, 158, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
/* 按钮容器样式 */
|
||||
.btn-container {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
margin-top: 32rpx;
|
||||
}
|
||||
|
||||
.cancel-btn,
|
||||
.submit-btn {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
padding: 16rpx 0;
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
padding: 16rpx;
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.cancel-btn:active,
|
||||
.submit-btn:active {
|
||||
transform: translateY(2rpx);
|
||||
box-shadow: 0 1rpx 4rpx rgba(0, 0, 0, 0.1);
|
||||
.picker {
|
||||
background-color: #fff;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
background-color: #909399;
|
||||
color: #fff;
|
||||
.picker-text {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
background-color: #409eff;
|
||||
color: #fff;
|
||||
.form-textarea {
|
||||
width: 100%;
|
||||
height: 160rpx;
|
||||
background-color: #fff;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media screen and (max-width: 750rpx) {
|
||||
.form-container {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.btn-container {
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
.textarea-count {
|
||||
position: absolute;
|
||||
bottom: 8rpx;
|
||||
right: 16rpx;
|
||||
font-size: 18rpx;
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
@@ -1,85 +1,88 @@
|
||||
<template>
|
||||
<view class="edit-container">
|
||||
<!-- 头部 -->
|
||||
<view class="header">
|
||||
<uni-icons type="back" size="28" color="#303133" @click="handleCancel" />
|
||||
<text class="title">修改分销员</text>
|
||||
<view class="header-right"></view>
|
||||
<view class="distributor-edit-container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<view class="back-icon">←</view>
|
||||
</view>
|
||||
<view class="header-title">编辑分销员</view>
|
||||
<view class="header-right" @click="saveDistributor">
|
||||
<view class="save-btn">保存</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 表单容器 -->
|
||||
<view class="form-container">
|
||||
<uni-forms :model="formData" ref="formRef" labelWidth="100rpx">
|
||||
<uni-forms-item label="姓名" required name="name">
|
||||
<uni-easyinput
|
||||
v-model="formData.name"
|
||||
placeholder="请输入姓名"
|
||||
class="form-input"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
|
||||
<uni-forms-item label="手机号" required name="phone">
|
||||
<uni-easyinput
|
||||
v-model="formData.phone"
|
||||
placeholder="请输入手机号"
|
||||
class="form-input"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
|
||||
<uni-forms-item label="OpenID" required name="openid">
|
||||
<uni-easyinput
|
||||
v-model="formData.openid"
|
||||
placeholder="请输入OpenID"
|
||||
class="form-input"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
|
||||
<uni-forms-item label="父ID" required name="parent_id">
|
||||
<uni-easyinput
|
||||
v-model="formData.parent_id"
|
||||
placeholder="请输入父ID"
|
||||
type="number"
|
||||
class="form-input"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
|
||||
<uni-forms-item label="租户ID" required name="tenant_id">
|
||||
<uni-easyinput
|
||||
v-model="formData.tenant_id"
|
||||
placeholder="请输入租户ID"
|
||||
type="number"
|
||||
class="form-input"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
|
||||
<uni-forms-item label="苹果URL" name="applet_url">
|
||||
<uni-easyinput
|
||||
v-model="formData.applet_url"
|
||||
placeholder="请输入苹果URL"
|
||||
class="form-input"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
|
||||
<uni-forms-item label="状态" name="is_active">
|
||||
<uni-data-checkbox
|
||||
v-model="formData.is_active"
|
||||
:localdata="statusOptions"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
</uni-forms>
|
||||
<!-- 表单内容 -->
|
||||
<view class="form-section">
|
||||
<view class="section-title">基本信息</view>
|
||||
<view class="form-card">
|
||||
<view class="form-row">
|
||||
<view class="form-label">姓名</view>
|
||||
<view class="form-control">
|
||||
<input
|
||||
v-model="distributorForm.name"
|
||||
class="form-input"
|
||||
placeholder="请输入姓名"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-row">
|
||||
<view class="form-label">手机号</view>
|
||||
<view class="form-control">
|
||||
<input
|
||||
v-model="distributorForm.phone"
|
||||
type="number"
|
||||
class="form-input"
|
||||
placeholder="请输入手机号"
|
||||
maxlength="11"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-row">
|
||||
<view class="form-label">角色</view>
|
||||
<view class="form-control">
|
||||
<picker
|
||||
:range="roleOptions"
|
||||
:value="roleIndex"
|
||||
@change="onRoleChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ roleOptions[roleIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-row">
|
||||
<view class="form-label">状态</view>
|
||||
<view class="form-control">
|
||||
<picker
|
||||
:range="statusOptions"
|
||||
:value="statusIndex"
|
||||
@change="onStatusChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ statusOptions[statusIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-row">
|
||||
<view class="form-label">备注</view>
|
||||
<view class="form-control">
|
||||
<textarea
|
||||
v-model="distributorForm.remark"
|
||||
class="form-textarea"
|
||||
placeholder="请输入备注信息"
|
||||
maxlength="200"
|
||||
></textarea>
|
||||
<view class="textarea-count">{{ distributorForm.remark.length }}/200</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 按钮容器 -->
|
||||
<view class="btn-container">
|
||||
<button class="cancel-btn" @click="handleCancel">
|
||||
<uni-icons type="close" size="20" color="#fff" />
|
||||
<text class="btn-text">取消</text>
|
||||
</button>
|
||||
<button class="submit-btn" @click="handleSubmit">
|
||||
<uni-icons type="checkmark" size="20" color="#fff" />
|
||||
<text class="btn-text">提交</text>
|
||||
</button>
|
||||
<!-- 操作按钮 -->
|
||||
<view class="action-section">
|
||||
<view class="action-btn delete-btn" @click="deleteDistributor">
|
||||
删除分销员
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -87,162 +90,286 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue"
|
||||
|
||||
const formRef = ref(null)
|
||||
const distributorId = ref(uni.getStorageSync('__uni_route_query')?.id || '')
|
||||
|
||||
const formData = ref({
|
||||
// 分销员表单数据
|
||||
const distributorForm = ref({
|
||||
id: '',
|
||||
name: '',
|
||||
phone: '',
|
||||
openid: '',
|
||||
parent_id: '',
|
||||
tenant_id: '',
|
||||
applet_url: '',
|
||||
is_active: 'Y'
|
||||
name: '张三',
|
||||
phone: '13800138001',
|
||||
role: '初级分销员',
|
||||
status: '启用',
|
||||
remark: '默认分销员'
|
||||
})
|
||||
|
||||
const statusOptions = [
|
||||
{ text: '激活', value: 'Y' },
|
||||
{ text: '未激活', value: 'N' }
|
||||
]
|
||||
|
||||
// 初始化加载数据
|
||||
// 角色选项
|
||||
const roleOptions = ['初级分销员', '中级分销员', '高级分销员']
|
||||
const roleIndex = ref(0)
|
||||
|
||||
// 状态选项
|
||||
const statusOptions = ['启用', '禁用']
|
||||
const statusIndex = ref(0)
|
||||
|
||||
onMounted(() => {
|
||||
fetchDistributorDetail()
|
||||
// 实际项目中应从接口获取分销员详情
|
||||
// loadDistributorDetail()
|
||||
})
|
||||
|
||||
// 获取分销员详情
|
||||
function fetchDistributorDetail() {
|
||||
// 这里应该调用后端API获取数据
|
||||
// 模拟数据
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}
|
||||
|
||||
// 保存分销员
|
||||
function saveDistributor() {
|
||||
if (!distributorForm.value.name) {
|
||||
uni.showToast({
|
||||
title: '请输入姓名',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (!distributorForm.value.phone || distributorForm.value.phone.length !== 11) {
|
||||
uni.showToast({
|
||||
title: '请输入有效的手机号',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
uni.showLoading({ title: '保存中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
formData.value = {
|
||||
id: distributorId.value,
|
||||
name: '张三',
|
||||
phone: '13800138001',
|
||||
openid: 'openid123456',
|
||||
parent_id: '0',
|
||||
tenant_id: '1',
|
||||
applet_url: 'https://example.com',
|
||||
is_active: 'Y'
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '保存成功',
|
||||
icon: 'success'
|
||||
})
|
||||
// 实际项目中应调用接口保存分销员
|
||||
// updateDistributorData()
|
||||
|
||||
// 保存成功后返回上一页
|
||||
setTimeout(() => {
|
||||
goBack()
|
||||
}, 1000)
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
// 删除分销员
|
||||
function deleteDistributor() {
|
||||
uni.showModal({
|
||||
title: '删除分销员',
|
||||
content: '确定要删除该分销员吗?',
|
||||
success: function(res) {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: '删除中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '删除成功',
|
||||
icon: 'success'
|
||||
})
|
||||
// 实际项目中应调用接口删除分销员
|
||||
// deleteDistributorData()
|
||||
|
||||
// 删除成功后返回上一页
|
||||
setTimeout(() => {
|
||||
goBack()
|
||||
}, 1000)
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
}, 500)
|
||||
}
|
||||
|
||||
// 取消
|
||||
function handleCancel() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
// 提交
|
||||
function handleSubmit() {
|
||||
formRef.value.validate().then(() => {
|
||||
// 这里应该调用后端API提交数据
|
||||
uni.showToast({ title: '修改成功' })
|
||||
uni.navigateTo({ url: '/pages/distributor/index' })
|
||||
}).catch(err => {
|
||||
console.log('表单验证失败', err)
|
||||
})
|
||||
}
|
||||
|
||||
// 角色变更
|
||||
function onRoleChange(e) {
|
||||
const value = e.detail.value
|
||||
roleIndex.value = value
|
||||
distributorForm.value.role = roleOptions[value]
|
||||
}
|
||||
|
||||
// 状态变更
|
||||
function onStatusChange(e) {
|
||||
const value = e.detail.value
|
||||
statusIndex.value = value
|
||||
distributorForm.value.status = statusOptions[value]
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.edit-container {
|
||||
padding: 20rpx;
|
||||
/* #ifndef APP-NVUE */
|
||||
page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
view {
|
||||
font-size: 14px;
|
||||
line-height: inherit;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.distributor-edit-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 头部样式 */
|
||||
.header {
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 24rpx;
|
||||
padding-bottom: 16rpx;
|
||||
border-bottom: 2rpx solid #ecf5ff;
|
||||
height: 120rpx;
|
||||
background-color: #fff;
|
||||
padding: 0 32rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.title {
|
||||
.header-left {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 40rpx;
|
||||
color: #303133;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 28px;
|
||||
width: 60rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* 表单容器样式 */
|
||||
.form-container {
|
||||
.save-btn {
|
||||
font-size: 24rpx;
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 表单内容 */
|
||||
.form-section {
|
||||
padding: 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 0 16rpx 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 24rpx;
|
||||
padding-left: 12rpx;
|
||||
border-left: 8rpx solid #409eff;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.form-card {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
/* 表单输入框样式 */
|
||||
.form-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
border-radius: 8rpx !important;
|
||||
border: 1rpx solid #dcdfe6 !important;
|
||||
transition: all 0.3s ease !important;
|
||||
}
|
||||
|
||||
.form-input:focus {
|
||||
border-color: #409eff !important;
|
||||
box-shadow: 0 0 0 2rpx rgba(64, 158, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
/* 按钮容器样式 */
|
||||
.btn-container {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
margin-top: 32rpx;
|
||||
}
|
||||
|
||||
.cancel-btn,
|
||||
.submit-btn {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
padding: 16rpx 0;
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
padding: 16rpx;
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.cancel-btn:active,
|
||||
.submit-btn:active {
|
||||
transform: translateY(2rpx);
|
||||
box-shadow: 0 1rpx 4rpx rgba(0, 0, 0, 0.1);
|
||||
.picker {
|
||||
background-color: #fff;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
background-color: #909399;
|
||||
color: #fff;
|
||||
.picker-text {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
background-color: #409eff;
|
||||
color: #fff;
|
||||
.form-textarea {
|
||||
width: 100%;
|
||||
height: 160rpx;
|
||||
background-color: #fff;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
margin-left: 8rpx;
|
||||
.textarea-count {
|
||||
position: absolute;
|
||||
bottom: 8rpx;
|
||||
right: 16rpx;
|
||||
font-size: 18rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media screen and (max-width: 750rpx) {
|
||||
.form-container {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.btn-container {
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
/* 操作按钮 */
|
||||
.action-section {
|
||||
padding: 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 0 16rpx 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding: 20rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
background-color: #fef0f0;
|
||||
color: #f56c6c;
|
||||
}
|
||||
</style>
|
||||
@@ -1,398 +1,495 @@
|
||||
<template>
|
||||
<view class="distributor-container">
|
||||
<!-- 头部 -->
|
||||
<view class="header">
|
||||
<text class="title">分销员管理</text>
|
||||
<button class="add-btn" @click="handleAdd">
|
||||
<uni-icons type="plus" size="20" color="#fff" />
|
||||
<text class="add-btn-text">新增分销员</text>
|
||||
</button>
|
||||
<view class="distributor-list-container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<view class="back-icon">←</view>
|
||||
</view>
|
||||
<view class="header-title">分销员管理</view>
|
||||
<view class="header-right" @click="addDistributor">
|
||||
<view class="add-btn">添加</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索框 -->
|
||||
<view class="search-box">
|
||||
<view class="search-input-wrapper">
|
||||
<uni-icons type="search" size="20" color="#909399" class="search-icon" />
|
||||
<input
|
||||
v-model="searchForm.name"
|
||||
type="text"
|
||||
placeholder="请输入分销员姓名"
|
||||
class="search-input"
|
||||
/>
|
||||
<!-- 筛选条件 -->
|
||||
<view class="filter-section">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">状态</view>
|
||||
<view class="filter-control">
|
||||
<picker
|
||||
:range="statusOptions"
|
||||
:value="statusIndex"
|
||||
@change="onStatusChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ statusOptions[statusIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">搜索</view>
|
||||
<view class="filter-control">
|
||||
<input
|
||||
v-model="searchKeyword"
|
||||
class="search-input"
|
||||
placeholder="请输入姓名或手机号"
|
||||
@input="onSearch"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="search-input-wrapper">
|
||||
<uni-icons type="phone" size="20" color="#909399" class="search-icon" />
|
||||
<input
|
||||
v-model="searchForm.phone"
|
||||
type="text"
|
||||
placeholder="请输入手机号"
|
||||
class="search-input"
|
||||
/>
|
||||
</view>
|
||||
<button class="search-btn" @click="handleSearch">
|
||||
<uni-icons type="search" size="20" color="#fff" />
|
||||
<text class="search-btn-text">查询</text>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 表格容器 -->
|
||||
<view class="table-container">
|
||||
<uni-table :loading="loading" border stripe emptyText="暂无数据">
|
||||
<uni-tr>
|
||||
<uni-th width="120">姓名</uni-th>
|
||||
<uni-th width="150">手机号</uni-th>
|
||||
<uni-th width="100">总收益</uni-th>
|
||||
<uni-th width="100">可用收益</uni-th>
|
||||
<uni-th width="80">状态</uni-th>
|
||||
<uni-th width="180">操作</uni-th>
|
||||
</uni-tr>
|
||||
<uni-tr v-for="item in distributorList" :key="item.id" hover>
|
||||
<uni-td>{{ item.name }}</uni-td>
|
||||
<uni-td>{{ item.phone }}</uni-td>
|
||||
<uni-td>{{ item.total_income }}</uni-td>
|
||||
<uni-td>{{ item.available_income }}</uni-td>
|
||||
<uni-td>
|
||||
<view class="status-badge" :class="item.is_active === 'Y' ? 'active' : 'inactive'">
|
||||
{{ item.is_active === 'Y' ? '激活' : '未激活' }}
|
||||
<!-- 分销员列表 -->
|
||||
<view class="distributor-list-section">
|
||||
<view class="section-title">分销员列表</view>
|
||||
<view class="distributor-list">
|
||||
<view
|
||||
v-for="(distributor, index) in distributorList"
|
||||
:key="index"
|
||||
class="distributor-item"
|
||||
>
|
||||
<view class="distributor-avatar">
|
||||
<view class="avatar-placeholder">{{ getInitials(distributor.name) }}</view>
|
||||
</view>
|
||||
<view class="distributor-info">
|
||||
<view class="distributor-name">{{ distributor.name }}</view>
|
||||
<view class="distributor-meta">
|
||||
<view class="meta-item">{{ distributor.phone }}</view>
|
||||
<view class="meta-item">{{ distributor.role }}</view>
|
||||
<view class="meta-item status-{{ distributor.statusClass }}">{{ distributor.status }}</view>
|
||||
</view>
|
||||
</uni-td>
|
||||
<uni-td>
|
||||
<view class="action-buttons">
|
||||
<button class="action-btn edit" @click="handleEdit(item)">
|
||||
<uni-icons type="compose" size="18" color="#409eff" />
|
||||
<text>修改</text>
|
||||
</button>
|
||||
<button class="action-btn delete" @click="handleDelete(item.id)">
|
||||
<uni-icons type="trash" size="18" color="#f56c6c" />
|
||||
<text>删除</text>
|
||||
</button>
|
||||
<button class="action-btn rule" @click="handleRule(item.id)">
|
||||
<uni-icons type="settings" size="18" color="#e6a23c" />
|
||||
<text>分润</text>
|
||||
</button>
|
||||
</view>
|
||||
<view class="distributor-actions">
|
||||
<view class="action-btn edit-btn" @click="editDistributor(distributor.id)">
|
||||
编辑
|
||||
</view>
|
||||
</uni-td>
|
||||
</uni-tr>
|
||||
</uni-table>
|
||||
<view class="action-btn delete-btn" @click="deleteDistributor(distributor.id)">
|
||||
删除
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="distributorList.length === 0" class="empty-state">
|
||||
<view class="empty-icon">👥</view>
|
||||
<view class="empty-text">暂无分销员数据</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页 -->
|
||||
<view class="pagination-container">
|
||||
<uni-pagination
|
||||
:current="currentPage"
|
||||
:pageSize="pageSize"
|
||||
:total="total"
|
||||
@change="handlePageChange"
|
||||
show-icon
|
||||
prev-text="上一页"
|
||||
next-text="下一页"
|
||||
/>
|
||||
<view v-if="distributorList.length > 0" class="pagination">
|
||||
<view class="page-info">
|
||||
共 {{ totalDistributors }} 条,当前第 {{ currentPage }} 页
|
||||
</view>
|
||||
<view class="page-controls">
|
||||
<view
|
||||
class="page-btn"
|
||||
:class="{ disabled: currentPage === 1 }"
|
||||
@click="goToPage(currentPage - 1)"
|
||||
>
|
||||
上一页
|
||||
</view>
|
||||
<view
|
||||
class="page-btn"
|
||||
:class="{ disabled: currentPage === totalPages }"
|
||||
@click="goToPage(currentPage + 1)"
|
||||
>
|
||||
下一页
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue"
|
||||
import { ref, computed, onMounted } from "vue"
|
||||
|
||||
const loading = ref(false)
|
||||
const distributorList = ref([])
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const total = ref(0)
|
||||
// 筛选条件
|
||||
const statusOptions = ['全部状态', '启用', '禁用']
|
||||
const statusIndex = ref(0)
|
||||
const searchKeyword = ref('')
|
||||
|
||||
const searchForm = ref({
|
||||
name: '',
|
||||
phone: ''
|
||||
// 分销员列表数据
|
||||
const distributorList = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: '张三',
|
||||
phone: '13800138001',
|
||||
role: '初级分销员',
|
||||
status: '启用',
|
||||
statusClass: 'active'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '李四',
|
||||
phone: '13800138002',
|
||||
role: '中级分销员',
|
||||
status: '启用',
|
||||
statusClass: 'active'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '王五',
|
||||
phone: '13800138003',
|
||||
role: '高级分销员',
|
||||
status: '禁用',
|
||||
statusClass: 'inactive'
|
||||
}
|
||||
])
|
||||
|
||||
// 分页信息
|
||||
const currentPage = ref(1)
|
||||
const totalDistributors = ref(50)
|
||||
const pageSize = ref(10)
|
||||
|
||||
// 计算总页数
|
||||
const totalPages = computed(() => {
|
||||
return Math.ceil(totalDistributors.value / pageSize.value)
|
||||
})
|
||||
|
||||
// 初始化加载数据
|
||||
|
||||
onMounted(() => {
|
||||
fetchDistributorList()
|
||||
// 实际项目中应从接口获取分销员列表
|
||||
// loadDistributorList()
|
||||
})
|
||||
|
||||
// 获取分销员列表
|
||||
function fetchDistributorList() {
|
||||
loading.value = true
|
||||
// 这里应该调用后端API获取数据
|
||||
// 模拟数据
|
||||
setTimeout(() => {
|
||||
distributorList.value = [
|
||||
{
|
||||
id: 1,
|
||||
name: '张三',
|
||||
phone: '13800138001',
|
||||
total_income: 1000.00,
|
||||
available_income: 800.00,
|
||||
is_active: 'Y'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '李四',
|
||||
phone: '13900139002',
|
||||
total_income: 2000.00,
|
||||
available_income: 1500.00,
|
||||
is_active: 'Y'
|
||||
}
|
||||
]
|
||||
total.value = 2
|
||||
loading.value = false
|
||||
}, 500)
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}
|
||||
|
||||
// 搜索
|
||||
function handleSearch() {
|
||||
currentPage.value = 1
|
||||
fetchDistributorList()
|
||||
}
|
||||
|
||||
// 分页
|
||||
function handlePageChange(e) {
|
||||
currentPage.value = e.current
|
||||
fetchDistributorList()
|
||||
}
|
||||
|
||||
// 新增分销员
|
||||
function handleAdd() {
|
||||
// 跳转到新增页面
|
||||
uni.navigateTo({ url: '/pages/distributor/add' })
|
||||
}
|
||||
|
||||
// 修改分销员
|
||||
function handleEdit(item) {
|
||||
// 跳转到修改页面,并传递参数
|
||||
uni.navigateTo({
|
||||
url: `/pages/distributor/edit?id=${item.id}`
|
||||
|
||||
// 添加分销员
|
||||
function addDistributor() {
|
||||
uni.showToast({
|
||||
title: '添加分销员功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 编辑分销员
|
||||
function editDistributor(id) {
|
||||
uni.showToast({
|
||||
title: '编辑分销员功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
// 删除分销员
|
||||
function handleDelete(id) {
|
||||
function deleteDistributor(id) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
title: '删除分销员',
|
||||
content: '确定要删除该分销员吗?',
|
||||
success: (res) => {
|
||||
success: function(res) {
|
||||
if (res.confirm) {
|
||||
// 这里应该调用后端API删除数据
|
||||
fetchDistributorList()
|
||||
uni.showToast({ title: '删除成功' })
|
||||
// 实际项目中应调用接口删除分销员
|
||||
const index = distributorList.value.findIndex(item => item.id === id)
|
||||
if (index !== -1) {
|
||||
distributorList.value.splice(index, 1)
|
||||
}
|
||||
uni.showToast({
|
||||
title: '删除成功',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 配置分润规则
|
||||
function handleRule(id) {
|
||||
// 跳转到分润规则配置页面
|
||||
uni.navigateTo({
|
||||
url: `/pages/distributor/rule?id=${id}`
|
||||
})
|
||||
|
||||
// 状态变更
|
||||
function onStatusChange(e) {
|
||||
const value = e.detail.value
|
||||
statusIndex.value = value
|
||||
// 实际项目中应根据状态筛选分销员
|
||||
// filterDistributorList()
|
||||
}
|
||||
|
||||
// 搜索
|
||||
function onSearch() {
|
||||
// 实际项目中应根据关键词搜索分销员
|
||||
// searchDistributorList()
|
||||
}
|
||||
|
||||
// 跳转到指定页码
|
||||
function goToPage(page) {
|
||||
if (page < 1 || page > totalPages.value) {
|
||||
return
|
||||
}
|
||||
currentPage.value = page
|
||||
// 实际项目中应加载指定页码的分销员
|
||||
// loadDistributorPage(page)
|
||||
}
|
||||
|
||||
// 获取姓名首字母
|
||||
function getInitials(name) {
|
||||
return name.charAt(0).toUpperCase()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.distributor-container {
|
||||
padding: 20rpx;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 头部样式 */
|
||||
.header {
|
||||
/* #ifndef APP-NVUE */
|
||||
page {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24rpx;
|
||||
padding-bottom: 16rpx;
|
||||
border-bottom: 2rpx solid #ecf5ff;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
view {
|
||||
font-size: 14px;
|
||||
line-height: inherit;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.distributor-list-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 120rpx;
|
||||
background-color: #fff;
|
||||
padding: 0 32rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 40rpx;
|
||||
color: #303133;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 60rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #409eff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 12rpx 24rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 24rpx;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2rpx 8rpx rgba(64, 158, 255, 0.3);
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.add-btn:active {
|
||||
transform: translateY(2rpx);
|
||||
box-shadow: 0 1rpx 4rpx rgba(64, 158, 255, 0.3);
|
||||
}
|
||||
|
||||
.add-btn-text {
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
/* 搜索框样式 */
|
||||
.search-box {
|
||||
display: flex;
|
||||
margin-bottom: 24rpx;
|
||||
gap: 16rpx;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-input-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* 筛选条件 */
|
||||
.filter-section {
|
||||
padding: 24rpx 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.filter-control {
|
||||
background-color: #f9f9f9;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 0 16rpx;
|
||||
transition: all 0.3s ease;
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.search-input-wrapper:focus-within {
|
||||
border-color: #409eff;
|
||||
box-shadow: 0 0 0 2rpx rgba(64, 158, 255, 0.2);
|
||||
.picker {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
padding: 16rpx 0;
|
||||
border: none;
|
||||
.picker-text {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #67c23a;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 16rpx 24rpx;
|
||||
border-radius: 8rpx;
|
||||
.search-input {
|
||||
font-size: 24rpx;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2rpx 8rpx rgba(103, 194, 58, 0.3);
|
||||
color: #303133;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-btn:active {
|
||||
transform: translateY(2rpx);
|
||||
box-shadow: 0 1rpx 4rpx rgba(103, 194, 58, 0.3);
|
||||
/* 分销员列表 */
|
||||
.distributor-list-section {
|
||||
flex: 1;
|
||||
padding: 0 16rpx;
|
||||
}
|
||||
|
||||
.search-btn-text {
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
/* 表格容器样式 */
|
||||
.table-container {
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
padding-left: 12rpx;
|
||||
border-left: 8rpx solid #409eff;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* 状态标签样式 */
|
||||
.status-badge {
|
||||
display: inline-block;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 20rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-badge.active {
|
||||
background-color: #f0f9eb;
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.status-badge.inactive {
|
||||
background-color: #fef0f0;
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
/* 操作按钮样式 */
|
||||
.action-buttons {
|
||||
.distributor-list {
|
||||
display: flex;
|
||||
gap: 8rpx;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
.distributor-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.distributor-avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
background-color: #409eff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
padding: 8rpx 12rpx;
|
||||
border-radius: 6rpx;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
|
||||
.avatar-placeholder {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.distributor-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.distributor-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.distributor-meta {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
font-size: 20rpx;
|
||||
transition: all 0.3s ease;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.action-btn:active {
|
||||
transform: translateY(1rpx);
|
||||
.meta-item.status-active {
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.action-btn.edit {
|
||||
.meta-item.status-inactive {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.distributor-actions {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 16rpx;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
background-color: #ecf5ff;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.action-btn.delete {
|
||||
.delete-btn {
|
||||
background-color: #fef0f0;
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.action-btn.rule {
|
||||
background-color: #fdf6ec;
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.action-btn text {
|
||||
margin-left: 4rpx;
|
||||
}
|
||||
|
||||
/* 分页样式 */
|
||||
.pagination-container {
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16rpx 0;
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media screen and (max-width: 750rpx) {
|
||||
.search-box {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.search-input-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
min-width: 80rpx;
|
||||
}
|
||||
.empty-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 分页 */
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #fff;
|
||||
margin: 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.page-info {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.page-controls {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 20rpx;
|
||||
color: #409eff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.page-btn.disabled {
|
||||
color: #909399;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
@@ -1,40 +1,153 @@
|
||||
<template>
|
||||
<view class="rule-container">
|
||||
<view class="header">
|
||||
<text class="title">分润规则配置</text>
|
||||
<view class="distributor-rule-container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<view class="back-icon">←</view>
|
||||
</view>
|
||||
<view class="header-title">分销规则配置</view>
|
||||
<view class="header-right" @click="saveRule">
|
||||
<view class="save-btn">保存</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-container">
|
||||
<uni-forms :model="ruleForm" ref="formRef">
|
||||
<uni-forms-item label="分销员ID" required>
|
||||
<uni-easyinput v-model="ruleForm.distributor_id" placeholder="请输入分销员ID" type="number" />
|
||||
</uni-forms-item>
|
||||
|
||||
<uni-forms-item label="分润比例" required>
|
||||
<uni-easyinput v-model="ruleForm.profit_ratio" placeholder="请输入分润比例(%)" type="number" />
|
||||
</uni-forms-item>
|
||||
|
||||
<uni-forms-item label="规则名称" required>
|
||||
<uni-easyinput v-model="ruleForm.rule_name" placeholder="请输入规则名称" />
|
||||
</uni-forms-item>
|
||||
|
||||
<uni-forms-item label="规则描述">
|
||||
<uni-easyinput v-model="ruleForm.rule_desc" placeholder="请输入规则描述" type="textarea" />
|
||||
</uni-forms-item>
|
||||
|
||||
<uni-forms-item label="生效时间">
|
||||
<uni-datetime-picker v-model="ruleForm.effective_time" type="datetime" placeholder="请选择生效时间" />
|
||||
</uni-forms-item>
|
||||
|
||||
<uni-forms-item label="失效时间">
|
||||
<uni-datetime-picker v-model="ruleForm.expire_time" type="datetime" placeholder="请选择失效时间" />
|
||||
</uni-forms-item>
|
||||
</uni-forms>
|
||||
<!-- 规则配置 -->
|
||||
<view class="rule-section">
|
||||
<view class="section-title">基本规则</view>
|
||||
<view class="rule-card">
|
||||
<view class="rule-row">
|
||||
<view class="rule-label">分销员申请审核</view>
|
||||
<view class="rule-control">
|
||||
<switch
|
||||
:checked="ruleForm.needAudit"
|
||||
@change="onAuditChange"
|
||||
class="switch"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="rule-row">
|
||||
<view class="rule-label">分销员等级制度</view>
|
||||
<view class="rule-control">
|
||||
<switch
|
||||
:checked="ruleForm.enableLevel"
|
||||
@change="onLevelChange"
|
||||
class="switch"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="rule-row">
|
||||
<view class="rule-label">分销员自购优惠</view>
|
||||
<view class="rule-control">
|
||||
<switch
|
||||
:checked="ruleForm.enableSelfBuy"
|
||||
@change="onSelfBuyChange"
|
||||
class="switch"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="rule-row">
|
||||
<view class="rule-label">分销员邀请奖励</view>
|
||||
<view class="rule-control">
|
||||
<switch
|
||||
:checked="ruleForm.enableInviteReward"
|
||||
@change="onInviteRewardChange"
|
||||
class="switch"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="btn-container">
|
||||
<button class="cancel-btn" @click="handleCancel">取消</button>
|
||||
<button class="submit-btn" @click="handleSubmit">提交</button>
|
||||
<!-- 等级配置 -->
|
||||
<view class="rule-section" v-if="ruleForm.enableLevel">
|
||||
<view class="section-title">等级配置</view>
|
||||
<view class="rule-card">
|
||||
<view class="level-config">
|
||||
<view
|
||||
v-for="(level, index) in levelList"
|
||||
:key="index"
|
||||
class="level-item"
|
||||
>
|
||||
<view class="level-header">
|
||||
<view class="level-title">{{ level.name }}</view>
|
||||
<view class="level-condition">
|
||||
<view class="condition-label">升级条件:</view>
|
||||
<view class="condition-value">{{ level.condition }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="level-benefit">
|
||||
<view class="benefit-item">
|
||||
<view class="benefit-label">分销佣金比例:</view>
|
||||
<view class="benefit-value">{{ level.commissionRate }}%</view>
|
||||
</view>
|
||||
<view class="benefit-item">
|
||||
<view class="benefit-label">自购优惠比例:</view>
|
||||
<view class="benefit-value">{{ level.selfBuyRate }}%</view>
|
||||
</view>
|
||||
<view class="benefit-item">
|
||||
<view class="benefit-label">邀请奖励:</view>
|
||||
<view class="benefit-value">¥{{ level.inviteReward.toFixed(2) }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 佣金规则 -->
|
||||
<view class="rule-section">
|
||||
<view class="section-title">佣金规则</view>
|
||||
<view class="rule-card">
|
||||
<view class="rule-row">
|
||||
<view class="rule-label">佣金计算方式</view>
|
||||
<view class="rule-control">
|
||||
<picker
|
||||
:range="commissionTypeOptions"
|
||||
:value="commissionTypeIndex"
|
||||
@change="onCommissionTypeChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ commissionTypeOptions[commissionTypeIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="rule-row">
|
||||
<view class="rule-label">佣金结算周期</view>
|
||||
<view class="rule-control">
|
||||
<picker
|
||||
:range="settlementCycleOptions"
|
||||
:value="settlementCycleIndex"
|
||||
@change="onSettlementCycleChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ settlementCycleOptions[settlementCycleIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="rule-row">
|
||||
<view class="rule-label">最低提现金额</view>
|
||||
<view class="rule-control">
|
||||
<input
|
||||
v-model="ruleForm.minWithdrawAmount"
|
||||
type="number"
|
||||
class="form-input"
|
||||
placeholder="请输入最低提现金额"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="rule-row">
|
||||
<view class="rule-label">提现手续费</view>
|
||||
<view class="rule-control">
|
||||
<input
|
||||
v-model="ruleForm.withdrawFee"
|
||||
type="number"
|
||||
class="form-input"
|
||||
placeholder="请输入提现手续费比例"
|
||||
/>
|
||||
<view class="input-suffix">%</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -42,104 +155,319 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue"
|
||||
|
||||
const formRef = ref(null)
|
||||
const distributorId = ref(uni.getStorageSync('__uni_route_query')?.id || '')
|
||||
|
||||
// 规则表单数据
|
||||
const ruleForm = ref({
|
||||
distributor_id: distributorId.value,
|
||||
profit_ratio: '',
|
||||
rule_name: '',
|
||||
rule_desc: '',
|
||||
effective_time: '',
|
||||
expire_time: ''
|
||||
needAudit: true,
|
||||
enableLevel: true,
|
||||
enableSelfBuy: true,
|
||||
enableInviteReward: true,
|
||||
minWithdrawAmount: 100,
|
||||
withdrawFee: 0.5
|
||||
})
|
||||
|
||||
// 初始化加载数据
|
||||
|
||||
// 佣金计算方式选项
|
||||
const commissionTypeOptions = ['按比例', '固定金额']
|
||||
const commissionTypeIndex = ref(0)
|
||||
|
||||
// 结算周期选项
|
||||
const settlementCycleOptions = ['实时结算', '每日结算', '每周结算', '每月结算']
|
||||
const settlementCycleIndex = ref(3)
|
||||
|
||||
// 等级配置
|
||||
const levelList = ref([
|
||||
{
|
||||
name: '初级分销员',
|
||||
condition: '邀请1人注册',
|
||||
commissionRate: 10,
|
||||
selfBuyRate: 5,
|
||||
inviteReward: 5
|
||||
},
|
||||
{
|
||||
name: '中级分销员',
|
||||
condition: '邀请5人注册',
|
||||
commissionRate: 15,
|
||||
selfBuyRate: 8,
|
||||
inviteReward: 8
|
||||
},
|
||||
{
|
||||
name: '高级分销员',
|
||||
condition: '邀请10人注册',
|
||||
commissionRate: 20,
|
||||
selfBuyRate: 10,
|
||||
inviteReward: 10
|
||||
}
|
||||
])
|
||||
|
||||
onMounted(() => {
|
||||
fetchRuleDetail()
|
||||
// 实际项目中应从接口获取规则配置
|
||||
// loadRuleConfig()
|
||||
})
|
||||
|
||||
// 获取分润规则详情
|
||||
function fetchRuleDetail() {
|
||||
// 这里应该调用后端API获取数据
|
||||
// 模拟数据
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}
|
||||
|
||||
// 保存规则
|
||||
function saveRule() {
|
||||
uni.showLoading({ title: '保存中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
ruleForm.value = {
|
||||
distributor_id: distributorId.value,
|
||||
profit_ratio: '10',
|
||||
rule_name: '默认分润规则',
|
||||
rule_desc: '基础分润规则',
|
||||
effective_time: new Date().toISOString(),
|
||||
expire_time: ''
|
||||
}
|
||||
}, 500)
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '保存成功',
|
||||
icon: 'success'
|
||||
})
|
||||
// 实际项目中应调用接口保存规则配置
|
||||
// saveRuleConfig()
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
// 取消
|
||||
function handleCancel() {
|
||||
uni.navigateBack()
|
||||
|
||||
// 审核开关变更
|
||||
function onAuditChange(e) {
|
||||
const value = e.detail.value
|
||||
ruleForm.value.needAudit = value
|
||||
}
|
||||
|
||||
// 提交
|
||||
function handleSubmit() {
|
||||
formRef.value.validate().then(() => {
|
||||
// 这里应该调用后端API提交数据
|
||||
uni.showToast({ title: '配置成功' })
|
||||
uni.navigateTo({ url: '/pages/distributor/index' })
|
||||
}).catch(err => {
|
||||
console.log('表单验证失败', err)
|
||||
})
|
||||
|
||||
// 等级制度开关变更
|
||||
function onLevelChange(e) {
|
||||
const value = e.detail.value
|
||||
ruleForm.value.enableLevel = value
|
||||
}
|
||||
|
||||
// 自购优惠开关变更
|
||||
function onSelfBuyChange(e) {
|
||||
const value = e.detail.value
|
||||
ruleForm.value.enableSelfBuy = value
|
||||
}
|
||||
|
||||
// 邀请奖励开关变更
|
||||
function onInviteRewardChange(e) {
|
||||
const value = e.detail.value
|
||||
ruleForm.value.enableInviteReward = value
|
||||
}
|
||||
|
||||
// 佣金计算方式变更
|
||||
function onCommissionTypeChange(e) {
|
||||
const value = e.detail.value
|
||||
commissionTypeIndex.value = value
|
||||
}
|
||||
|
||||
// 结算周期变更
|
||||
function onSettlementCycleChange(e) {
|
||||
const value = e.detail.value
|
||||
settlementCycleIndex.value = value
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.rule-container {
|
||||
padding: 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
/* #ifndef APP-NVUE */
|
||||
page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
view {
|
||||
font-size: 14px;
|
||||
line-height: inherit;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.distributor-rule-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 20rpx;
|
||||
padding-bottom: 20rpx;
|
||||
border-bottom: 1rpx solid #e5e5e5;
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 120rpx;
|
||||
background-color: #fff;
|
||||
padding: 0 32rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.title {
|
||||
.header-left {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 40rpx;
|
||||
color: #303133;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
.header-right {
|
||||
width: 60rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.save-btn {
|
||||
font-size: 24rpx;
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 规则配置 */
|
||||
.rule-section {
|
||||
padding: 32rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 8rpx;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
margin: 0 16rpx 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.btn-container {
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 24rpx;
|
||||
padding-left: 12rpx;
|
||||
border-left: 8rpx solid #409eff;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.rule-card {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.rule-row {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
flex: 1;
|
||||
background-color: #909399;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 20rpx;
|
||||
border-radius: 4rpx;
|
||||
font-size: 28rpx;
|
||||
.rule-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
flex: 1;
|
||||
background-color: #409eff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 20rpx;
|
||||
border-radius: 4rpx;
|
||||
font-size: 28rpx;
|
||||
.rule-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.rule-control {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.switch {
|
||||
transform: scale(0.8);
|
||||
}
|
||||
|
||||
.picker {
|
||||
background-color: #fff;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
min-width: 200rpx;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
background-color: #fff;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
min-width: 150rpx;
|
||||
}
|
||||
|
||||
.input-suffix {
|
||||
position: absolute;
|
||||
right: 32rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
/* 等级配置 */
|
||||
.level-config {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.level-item {
|
||||
background-color: #fff;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.level-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.level-title {
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.level-condition {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.condition-label {
|
||||
font-size: 18rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.condition-value {
|
||||
font-size: 18rpx;
|
||||
color: #409eff;
|
||||
margin-left: 4rpx;
|
||||
}
|
||||
|
||||
.level-benefit {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.benefit-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.benefit-label {
|
||||
font-size: 18rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.benefit-value {
|
||||
font-size: 18rpx;
|
||||
color: #303133;
|
||||
margin-left: 4rpx;
|
||||
}
|
||||
</style>
|
||||
433
src/pages/member/general-code.vue
Normal file
433
src/pages/member/general-code.vue
Normal file
@@ -0,0 +1,433 @@
|
||||
<template>
|
||||
<view class="general-code-container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<view class="back-icon">←</view>
|
||||
</view>
|
||||
<view class="header-title">通用会员推广码</view>
|
||||
<view class="header-right" @click="refreshCode">
|
||||
<view class="refresh-btn">刷新</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 推广码信息 -->
|
||||
<view class="code-info-section">
|
||||
<view class="section-title">推广码信息</view>
|
||||
<view class="info-card">
|
||||
<view class="info-row">
|
||||
<view class="info-label">推广码</view>
|
||||
<view class="info-value">{{ generalCode }}</view>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<view class="info-label">生成时间</view>
|
||||
<view class="info-value">{{ createTime }}</view>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<view class="info-label">有效期</view>
|
||||
<view class="info-value">{{ validityPeriod }}</view>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<view class="info-label">使用次数</view>
|
||||
<view class="info-value">{{ usageCount }}次</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 推广二维码 -->
|
||||
<view class="qrcode-section">
|
||||
<view class="section-title">推广二维码</view>
|
||||
<view class="qrcode-card">
|
||||
<view class="qrcode-image">
|
||||
<!-- 模拟二维码图片 -->
|
||||
<view class="mock-qrcode">
|
||||
<view class="qrcode-pattern"></view>
|
||||
<view class="qrcode-text">{{ generalCode }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="qrcode-hint">
|
||||
这是通用推广二维码,适用于所有渠道的推广活动
|
||||
</view>
|
||||
<view class="qrcode-actions">
|
||||
<view class="action-btn download-btn" @click="downloadQrcode">
|
||||
下载二维码
|
||||
</view>
|
||||
<view class="action-btn share-btn" @click="shareQrcode">
|
||||
分享二维码
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 使用记录 -->
|
||||
<view class="usage-record-section">
|
||||
<view class="section-title">使用记录</view>
|
||||
<view class="record-card">
|
||||
<view class="record-header">
|
||||
<view class="header-item">时间</view>
|
||||
<view class="header-item">使用人</view>
|
||||
<view class="header-item">操作</view>
|
||||
</view>
|
||||
<view class="record-list">
|
||||
<view
|
||||
v-for="(record, index) in usageRecords"
|
||||
:key="index"
|
||||
class="record-item"
|
||||
>
|
||||
<view class="record-cell">{{ record.time }}</view>
|
||||
<view class="record-cell">{{ record.user }}</view>
|
||||
<view class="record-cell">{{ record.action }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="record-footer">
|
||||
<view class="footer-text">
|
||||
共 {{ totalRecords }} 条记录,显示最近 {{ usageRecords.length }} 条
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue"
|
||||
|
||||
// 推广码信息
|
||||
const generalCode = ref('GC202601290001')
|
||||
const createTime = ref('2026-01-29 10:00:00')
|
||||
const validityPeriod = ref('永久有效')
|
||||
const usageCount = ref(123)
|
||||
|
||||
// 使用记录
|
||||
const usageRecords = ref([
|
||||
{
|
||||
time: '2026-01-29 15:30:00',
|
||||
user: '张三',
|
||||
action: '扫码注册'
|
||||
},
|
||||
{
|
||||
time: '2026-01-29 14:20:00',
|
||||
user: '李四',
|
||||
action: '购买会员'
|
||||
},
|
||||
{
|
||||
time: '2026-01-29 13:10:00',
|
||||
user: '王五',
|
||||
action: '扫码注册'
|
||||
}
|
||||
])
|
||||
|
||||
const totalRecords = ref(100)
|
||||
|
||||
onMounted(() => {
|
||||
// 实际项目中应从接口获取通用推广码信息
|
||||
// loadGeneralCodeInfo()
|
||||
})
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}
|
||||
|
||||
// 刷新推广码
|
||||
function refreshCode() {
|
||||
uni.showModal({
|
||||
title: '刷新推广码',
|
||||
content: '刷新推广码后,原推广码将失效,确定要刷新吗?',
|
||||
success: function(res) {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: '刷新中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
// 模拟刷新后生成新的推广码
|
||||
generalCode.value = 'GC' + new Date().getTime().toString().substring(0, 12)
|
||||
createTime.value = new Date().toLocaleString('zh-CN')
|
||||
usageCount.value = 0
|
||||
uni.showToast({
|
||||
title: '推广码已刷新',
|
||||
icon: 'success'
|
||||
})
|
||||
// 实际项目中应调用接口刷新推广码
|
||||
// refreshGeneralCode()
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 下载二维码
|
||||
function downloadQrcode() {
|
||||
uni.showLoading({ title: '下载中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '二维码已下载',
|
||||
icon: 'success'
|
||||
})
|
||||
// 实际项目中应调用接口下载二维码
|
||||
// downloadGeneralQrcode()
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
// 分享二维码
|
||||
function shareQrcode() {
|
||||
uni.showActionSheet({
|
||||
itemList: ['分享到微信', '分享到朋友圈', '复制推广链接'],
|
||||
success: function(res) {
|
||||
switch(res.tapIndex) {
|
||||
case 0:
|
||||
uni.showToast({ title: '分享到微信', duration: 1000 })
|
||||
break
|
||||
case 1:
|
||||
uni.showToast({ title: '分享到朋友圈', duration: 1000 })
|
||||
break
|
||||
case 2:
|
||||
uni.setClipboardData({
|
||||
data: `https://example.com/register?code=${generalCode.value}`,
|
||||
success: function() {
|
||||
uni.showToast({ title: '推广链接已复制', duration: 1000 })
|
||||
}
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* #ifndef APP-NVUE */
|
||||
page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
view {
|
||||
font-size: 14px;
|
||||
line-height: inherit;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.general-code-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 120rpx;
|
||||
background-color: #fff;
|
||||
padding: 0 32rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 40rpx;
|
||||
color: #303133;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 60rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
font-size: 24rpx;
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 通用部分样式 */
|
||||
.code-info-section,
|
||||
.qrcode-section,
|
||||
.usage-record-section {
|
||||
padding: 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 0 16rpx 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 24rpx;
|
||||
padding-left: 12rpx;
|
||||
border-left: 8rpx solid #409eff;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* 推广码信息 */
|
||||
.info-card {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.info-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 推广二维码 */
|
||||
.qrcode-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.qrcode-image {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.mock-qrcode {
|
||||
width: 300rpx;
|
||||
height: 300rpx;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.qrcode-pattern {
|
||||
width: 240rpx;
|
||||
height: 240rpx;
|
||||
background-color: #303133;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.qrcode-text {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.qrcode-hint {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
text-align: center;
|
||||
line-height: 1.4;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.qrcode-actions {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 12rpx 24rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.download-btn {
|
||||
background-color: #ecf5ff;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.share-btn {
|
||||
background-color: #f0f9eb;
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
/* 使用记录 */
|
||||
.record-card {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.record-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16rpx;
|
||||
padding-bottom: 16rpx;
|
||||
border-bottom: 1rpx solid #e0e0e0;
|
||||
}
|
||||
|
||||
.header-item {
|
||||
flex: 1;
|
||||
font-size: 20rpx;
|
||||
font-weight: bold;
|
||||
color: #606266;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.record-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.record-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.record-cell {
|
||||
flex: 1;
|
||||
font-size: 20rpx;
|
||||
color: #303133;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.record-footer {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
font-size: 18rpx;
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
633
src/pages/member/gift.vue
Normal file
633
src/pages/member/gift.vue
Normal file
@@ -0,0 +1,633 @@
|
||||
<template>
|
||||
<view class="gift-member-container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<view class="back-icon">←</view>
|
||||
</view>
|
||||
<view class="header-title">赠送会员管理</view>
|
||||
<view class="header-right" @click="generateBatchCode">
|
||||
<view class="batch-btn">批量生成</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 生成核验码 -->
|
||||
<view class="generate-section">
|
||||
<view class="section-title">生成核验码</view>
|
||||
<view class="generate-card">
|
||||
<view class="generate-row">
|
||||
<view class="generate-label">会员类型</view>
|
||||
<view class="generate-control">
|
||||
<picker
|
||||
:range="memberTypeOptions"
|
||||
:value="memberTypeIndex"
|
||||
@change="onMemberTypeChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ memberTypeOptions[memberTypeIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="generate-row">
|
||||
<view class="generate-label">生成数量</view>
|
||||
<view class="generate-control">
|
||||
<input
|
||||
v-model="generateCount"
|
||||
type="number"
|
||||
class="count-input"
|
||||
placeholder="请输入生成数量"
|
||||
min="1"
|
||||
max="100"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="generate-row">
|
||||
<view class="generate-label">有效期</view>
|
||||
<view class="generate-control">
|
||||
<input
|
||||
v-model="validityPeriod"
|
||||
class="period-input"
|
||||
placeholder="请选择有效期"
|
||||
@click="showDatePicker"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="generate-action">
|
||||
<view class="action-btn primary-btn" @click="generateCode">
|
||||
生成核验码
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 核验码列表 -->
|
||||
<view class="code-list-section">
|
||||
<view class="section-title">核验码列表</view>
|
||||
<view class="code-list">
|
||||
<view
|
||||
v-for="(code, index) in codeList"
|
||||
:key="index"
|
||||
class="code-item"
|
||||
>
|
||||
<view class="code-header">
|
||||
<view class="code-value">{{ code.code }}</view>
|
||||
<view class="code-status" :class="code.statusClass">{{ code.status }}</view>
|
||||
</view>
|
||||
<view class="code-body">
|
||||
<view class="code-info">
|
||||
<view class="info-item">
|
||||
<view class="info-label">会员类型:</view>
|
||||
<view class="info-value">{{ code.memberType }}</view>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="info-label">生成时间:</view>
|
||||
<view class="info-value">{{ code.createTime }}</view>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="info-label">有效期至:</view>
|
||||
<view class="info-value">{{ code.validUntil }}</view>
|
||||
</view>
|
||||
<view class="info-item" v-if="code.usedTime">
|
||||
<view class="info-label">使用时间:</view>
|
||||
<view class="info-value">{{ code.usedTime }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="code-footer">
|
||||
<view class="code-actions">
|
||||
<view class="action-btn copy-btn" @click="copyCode(code.code)">
|
||||
复制
|
||||
</view>
|
||||
<view class="action-btn delete-btn" @click="deleteCode(code.id)">
|
||||
删除
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="codeList.length === 0" class="empty-state">
|
||||
<view class="empty-icon">🎁</view>
|
||||
<view class="empty-text">暂无核验码数据</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页 -->
|
||||
<view v-if="codeList.length > 0" class="pagination">
|
||||
<view class="page-info">
|
||||
共 {{ totalCodes }} 条,当前第 {{ currentPage }} 页
|
||||
</view>
|
||||
<view class="page-controls">
|
||||
<view
|
||||
class="page-btn"
|
||||
:class="{ disabled: currentPage === 1 }"
|
||||
@click="goToPage(currentPage - 1)"
|
||||
>
|
||||
上一页
|
||||
</view>
|
||||
<view
|
||||
class="page-btn"
|
||||
:class="{ disabled: currentPage === totalPages }"
|
||||
@click="goToPage(currentPage + 1)"
|
||||
>
|
||||
下一页
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from "vue"
|
||||
|
||||
// 会员类型选项
|
||||
const memberTypeOptions = ['月度会员', '季度会员', '年度会员']
|
||||
const memberTypeIndex = ref(0)
|
||||
|
||||
// 生成数量
|
||||
const generateCount = ref('1')
|
||||
|
||||
// 有效期
|
||||
const validityPeriod = ref('')
|
||||
|
||||
// 核验码列表数据
|
||||
const codeList = ref([
|
||||
{
|
||||
id: 1,
|
||||
code: 'GC20260129001',
|
||||
memberType: '月度会员',
|
||||
createTime: '2026-01-29 15:30:00',
|
||||
validUntil: '2026-02-29 23:59:59',
|
||||
status: '未使用',
|
||||
statusClass: 'status-unused'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
code: 'GC20260129002',
|
||||
memberType: '季度会员',
|
||||
createTime: '2026-01-29 14:20:00',
|
||||
validUntil: '2026-04-29 23:59:59',
|
||||
status: '已使用',
|
||||
statusClass: 'status-used',
|
||||
usedTime: '2026-01-29 14:30:00'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
code: 'GC20260129003',
|
||||
memberType: '年度会员',
|
||||
createTime: '2026-01-29 13:10:00',
|
||||
validUntil: '2027-01-29 23:59:59',
|
||||
status: '已过期',
|
||||
statusClass: 'status-expired'
|
||||
}
|
||||
])
|
||||
|
||||
// 分页信息
|
||||
const currentPage = ref(1)
|
||||
const totalCodes = ref(100)
|
||||
const pageSize = ref(10)
|
||||
|
||||
// 计算总页数
|
||||
const totalPages = computed(() => {
|
||||
return Math.ceil(totalCodes.value / pageSize.value)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// 实际项目中应从接口获取核验码列表
|
||||
// loadCodeList()
|
||||
})
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}
|
||||
|
||||
// 批量生成
|
||||
function generateBatchCode() {
|
||||
uni.showModal({
|
||||
title: '批量生成核验码',
|
||||
content: '请输入要生成的核验码数量',
|
||||
editable: true,
|
||||
placeholderText: '请输入数量',
|
||||
success: function(res) {
|
||||
if (res.confirm && res.content) {
|
||||
const count = parseInt(res.content)
|
||||
if (isNaN(count) || count <= 0 || count > 100) {
|
||||
uni.showToast({
|
||||
title: '请输入1-100之间的数字',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
uni.showLoading({ title: '生成中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: `成功生成${count}个核验码`,
|
||||
icon: 'success'
|
||||
})
|
||||
// 实际项目中应调用接口批量生成核验码
|
||||
// generateBatchCodes(count)
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 会员类型变更
|
||||
function onMemberTypeChange(e) {
|
||||
const value = e.detail.value
|
||||
memberTypeIndex.value = value
|
||||
}
|
||||
|
||||
// 显示日期选择器
|
||||
function showDatePicker() {
|
||||
const now = new Date()
|
||||
uni.showDatePicker({
|
||||
year: now.getFullYear(),
|
||||
month: now.getMonth(),
|
||||
day: now.getDate(),
|
||||
success: function(res) {
|
||||
validityPeriod.value = `${res.year}-${res.month + 1}-${res.day}`
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 生成核验码
|
||||
function generateCode() {
|
||||
if (!generateCount.value || parseInt(generateCount.value) <= 0) {
|
||||
uni.showToast({
|
||||
title: '请输入有效的生成数量',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (!validityPeriod.value) {
|
||||
uni.showToast({
|
||||
title: '请选择有效期',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
uni.showLoading({ title: '生成中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '核验码生成成功',
|
||||
icon: 'success'
|
||||
})
|
||||
// 实际项目中应调用接口生成核验码
|
||||
// generateVerificationCode()
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
// 复制核验码
|
||||
function copyCode(code) {
|
||||
uni.setClipboardData({
|
||||
data: code,
|
||||
success: function() {
|
||||
uni.showToast({
|
||||
title: '核验码已复制',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 删除核验码
|
||||
function deleteCode(id) {
|
||||
uni.showModal({
|
||||
title: '删除核验码',
|
||||
content: '确定要删除该核验码吗?',
|
||||
success: function(res) {
|
||||
if (res.confirm) {
|
||||
// 实际项目中应调用接口删除核验码
|
||||
const index = codeList.value.findIndex(item => item.id === id)
|
||||
if (index !== -1) {
|
||||
codeList.value.splice(index, 1)
|
||||
}
|
||||
uni.showToast({
|
||||
title: '删除成功',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 跳转到指定页码
|
||||
function goToPage(page) {
|
||||
if (page < 1 || page > totalPages.value) {
|
||||
return
|
||||
}
|
||||
currentPage.value = page
|
||||
// 实际项目中应加载指定页码的核验码
|
||||
// loadCodePage(page)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* #ifndef APP-NVUE */
|
||||
page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
view {
|
||||
font-size: 14px;
|
||||
line-height: inherit;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.gift-member-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 120rpx;
|
||||
background-color: #fff;
|
||||
padding: 0 32rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 40rpx;
|
||||
color: #303133;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 120rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.batch-btn {
|
||||
font-size: 24rpx;
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 通用部分样式 */
|
||||
.generate-section,
|
||||
.code-list-section {
|
||||
padding: 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 0 16rpx 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 24rpx;
|
||||
padding-left: 12rpx;
|
||||
border-left: 8rpx solid #409eff;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* 生成核验码 */
|
||||
.generate-card {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.generate-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.generate-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.generate-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.generate-control {
|
||||
background-color: #fff;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.picker {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.count-input,
|
||||
.period-input {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.generate-action {
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 16rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.primary-btn {
|
||||
background-color: #409eff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 核验码列表 */
|
||||
.code-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.code-item {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.code-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
padding-bottom: 16rpx;
|
||||
border-bottom: 1rpx solid #e0e0e0;
|
||||
}
|
||||
|
||||
.code-value {
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.code-status {
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.status-unused {
|
||||
color: #409eff;
|
||||
background-color: rgba(64, 158, 255, 0.1);
|
||||
}
|
||||
|
||||
.status-used {
|
||||
color: #67c23a;
|
||||
background-color: rgba(103, 194, 58, 0.1);
|
||||
}
|
||||
|
||||
.status-expired {
|
||||
color: #909399;
|
||||
background-color: rgba(144, 147, 153, 0.1);
|
||||
}
|
||||
|
||||
.code-body {
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.code-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #606266;
|
||||
width: 120rpx;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #303133;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.code-footer {
|
||||
border-top: 1rpx solid #e0e0e0;
|
||||
padding-top: 16rpx;
|
||||
}
|
||||
|
||||
.code-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
background-color: #ecf5ff;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
background-color: #fef0f0;
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 分页 */
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #fff;
|
||||
margin: 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.page-info {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.page-controls {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 20rpx;
|
||||
color: #409eff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.page-btn.disabled {
|
||||
color: #909399;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
623
src/pages/member/order.vue
Normal file
623
src/pages/member/order.vue
Normal file
@@ -0,0 +1,623 @@
|
||||
<template>
|
||||
<view class="member-order-container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<view class="back-icon">←</view>
|
||||
</view>
|
||||
<view class="header-title">会员订单管理</view>
|
||||
<view class="header-right" @click="exportOrders">
|
||||
<view class="export-btn">导出</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选条件 -->
|
||||
<view class="filter-section">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">状态</view>
|
||||
<view class="filter-control">
|
||||
<picker
|
||||
:range="statusOptions"
|
||||
:value="statusIndex"
|
||||
@change="onStatusChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ statusOptions[statusIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">会员类型</view>
|
||||
<view class="filter-control">
|
||||
<picker
|
||||
:range="memberTypeOptions"
|
||||
:value="memberTypeIndex"
|
||||
@change="onMemberTypeChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ memberTypeOptions[memberTypeIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">搜索</view>
|
||||
<view class="filter-control">
|
||||
<input
|
||||
v-model="searchKeyword"
|
||||
class="search-input"
|
||||
placeholder="请输入订单号或学员姓名"
|
||||
@input="onSearch"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 订单列表 -->
|
||||
<view class="order-list-section">
|
||||
<view class="section-title">订单列表</view>
|
||||
<view class="order-list">
|
||||
<view
|
||||
v-for="(order, index) in orderList"
|
||||
:key="index"
|
||||
class="order-item"
|
||||
>
|
||||
<view class="order-header">
|
||||
<view class="order-code">{{ order.code }}</view>
|
||||
<view class="order-status" :class="order.statusClass">{{ order.status }}</view>
|
||||
</view>
|
||||
<view class="order-body">
|
||||
<view class="order-info">
|
||||
<view class="info-item">
|
||||
<view class="info-label">学员姓名:</view>
|
||||
<view class="info-value">{{ order.studentName }}</view>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="info-label">会员类型:</view>
|
||||
<view class="info-value">{{ order.memberType }}</view>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="info-label">订单金额:</view>
|
||||
<view class="info-value price">{{ order.amount }}</view>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="info-label">下单时间:</view>
|
||||
<view class="info-value">{{ order.createTime }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="order-footer">
|
||||
<view class="order-actions">
|
||||
<view v-if="order.status === '待处理'" class="action-btn process-btn" @click="processOrder(order.id)">
|
||||
处理
|
||||
</view>
|
||||
<view class="action-btn detail-btn" @click="viewOrderDetail(order.id)">
|
||||
详情
|
||||
</view>
|
||||
<view class="action-btn refund-btn" @click="refundOrder(order.id)">
|
||||
退款
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="orderList.length === 0" class="empty-state">
|
||||
<view class="empty-icon">🛒</view>
|
||||
<view class="empty-text">暂无订单数据</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页 -->
|
||||
<view v-if="orderList.length > 0" class="pagination">
|
||||
<view class="page-info">
|
||||
共 {{ totalOrders }} 条,当前第 {{ currentPage }} 页
|
||||
</view>
|
||||
<view class="page-controls">
|
||||
<view
|
||||
class="page-btn"
|
||||
:class="{ disabled: currentPage === 1 }"
|
||||
@click="goToPage(currentPage - 1)"
|
||||
>
|
||||
上一页
|
||||
</view>
|
||||
<view
|
||||
class="page-btn"
|
||||
:class="{ disabled: currentPage === totalPages }"
|
||||
@click="goToPage(currentPage + 1)"
|
||||
>
|
||||
下一页
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from "vue"
|
||||
|
||||
// 筛选条件
|
||||
const statusOptions = ['全部状态', '待处理', '已完成', '已退款', '异常']
|
||||
const statusIndex = ref(0)
|
||||
|
||||
const memberTypeOptions = ['全部类型', '月度会员', '季度会员', '年度会员']
|
||||
const memberTypeIndex = ref(0)
|
||||
|
||||
const searchKeyword = ref('')
|
||||
|
||||
// 订单列表数据
|
||||
const orderList = ref([
|
||||
{
|
||||
id: 1,
|
||||
code: 'MO20260129001',
|
||||
studentName: '张三',
|
||||
memberType: '年度会员',
|
||||
amount: '¥299.90',
|
||||
createTime: '2026-01-29 15:30:00',
|
||||
status: '待处理',
|
||||
statusClass: 'status-pending'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
code: 'MO20260129002',
|
||||
studentName: '李四',
|
||||
memberType: '月度会员',
|
||||
amount: '¥39.90',
|
||||
createTime: '2026-01-29 14:20:00',
|
||||
status: '已完成',
|
||||
statusClass: 'status-completed'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
code: 'MO20260129003',
|
||||
studentName: '王五',
|
||||
memberType: '季度会员',
|
||||
amount: '¥99.90',
|
||||
createTime: '2026-01-29 13:10:00',
|
||||
status: '异常',
|
||||
statusClass: 'status-exception'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
code: 'MO20260129004',
|
||||
studentName: '赵六',
|
||||
memberType: '年度会员',
|
||||
amount: '¥299.90',
|
||||
createTime: '2026-01-29 12:00:00',
|
||||
status: '已退款',
|
||||
statusClass: 'status-refunded'
|
||||
}
|
||||
])
|
||||
|
||||
// 分页信息
|
||||
const currentPage = ref(1)
|
||||
const totalOrders = ref(100)
|
||||
const pageSize = ref(10)
|
||||
|
||||
// 计算总页数
|
||||
const totalPages = computed(() => {
|
||||
return Math.ceil(totalOrders.value / pageSize.value)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// 实际项目中应从接口获取订单列表
|
||||
// loadOrderList()
|
||||
})
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}
|
||||
|
||||
// 导出订单
|
||||
function exportOrders() {
|
||||
uni.showModal({
|
||||
title: '导出订单',
|
||||
content: '确定要导出当前筛选条件下的订单吗?',
|
||||
success: function(res) {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: '导出中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '导出成功',
|
||||
icon: 'success'
|
||||
})
|
||||
// 实际项目中应调用接口导出订单
|
||||
// exportOrderList()
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 状态变更
|
||||
function onStatusChange(e) {
|
||||
const value = e.detail.value
|
||||
statusIndex.value = value
|
||||
// 实际项目中应根据状态筛选订单
|
||||
// filterOrderList()
|
||||
}
|
||||
|
||||
// 会员类型变更
|
||||
function onMemberTypeChange(e) {
|
||||
const value = e.detail.value
|
||||
memberTypeIndex.value = value
|
||||
// 实际项目中应根据会员类型筛选订单
|
||||
// filterOrderList()
|
||||
}
|
||||
|
||||
// 搜索
|
||||
function onSearch() {
|
||||
// 实际项目中应根据关键词搜索订单
|
||||
// searchOrderList()
|
||||
}
|
||||
|
||||
// 处理订单
|
||||
function processOrder(id) {
|
||||
uni.showModal({
|
||||
title: '处理订单',
|
||||
content: '确定要处理该订单吗?',
|
||||
success: function(res) {
|
||||
if (res.confirm) {
|
||||
// 实际项目中应调用接口处理订单
|
||||
const order = orderList.value.find(item => item.id === id)
|
||||
if (order) {
|
||||
order.status = '已完成'
|
||||
order.statusClass = 'status-completed'
|
||||
}
|
||||
uni.showToast({
|
||||
title: '订单处理成功',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 查看订单详情
|
||||
function viewOrderDetail(id) {
|
||||
uni.showToast({
|
||||
title: '查看订单详情功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
// 退款订单
|
||||
function refundOrder(id) {
|
||||
uni.showModal({
|
||||
title: '退款订单',
|
||||
content: '确定要退款该订单吗?',
|
||||
success: function(res) {
|
||||
if (res.confirm) {
|
||||
// 实际项目中应调用接口退款订单
|
||||
const order = orderList.value.find(item => item.id === id)
|
||||
if (order) {
|
||||
order.status = '已退款'
|
||||
order.statusClass = 'status-refunded'
|
||||
}
|
||||
uni.showToast({
|
||||
title: '订单退款成功',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 跳转到指定页码
|
||||
function goToPage(page) {
|
||||
if (page < 1 || page > totalPages.value) {
|
||||
return
|
||||
}
|
||||
currentPage.value = page
|
||||
// 实际项目中应加载指定页码的订单
|
||||
// loadOrderPage(page)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* #ifndef APP-NVUE */
|
||||
page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
view {
|
||||
font-size: 14px;
|
||||
line-height: inherit;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.member-order-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 120rpx;
|
||||
background-color: #fff;
|
||||
padding: 0 32rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 40rpx;
|
||||
color: #303133;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 60rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.export-btn {
|
||||
font-size: 24rpx;
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 筛选条件 */
|
||||
.filter-section {
|
||||
padding: 24rpx 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
flex: 1;
|
||||
min-width: 200rpx;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.filter-control {
|
||||
background-color: #f9f9f9;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.picker {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 订单列表 */
|
||||
.order-list-section {
|
||||
flex: 1;
|
||||
padding: 0 16rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 24rpx;
|
||||
padding-left: 12rpx;
|
||||
border-left: 8rpx solid #409eff;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.order-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.order-item {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.order-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
padding-bottom: 16rpx;
|
||||
border-bottom: 1rpx solid #e0e0e0;
|
||||
}
|
||||
|
||||
.order-code {
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.order-status {
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
color: #e6a23c;
|
||||
background-color: rgba(230, 162, 60, 0.1);
|
||||
}
|
||||
|
||||
.status-completed {
|
||||
color: #67c23a;
|
||||
background-color: rgba(103, 194, 58, 0.1);
|
||||
}
|
||||
|
||||
.status-exception {
|
||||
color: #f56c6c;
|
||||
background-color: rgba(245, 108, 108, 0.1);
|
||||
}
|
||||
|
||||
.status-refunded {
|
||||
color: #909399;
|
||||
background-color: rgba(144, 147, 153, 0.1);
|
||||
}
|
||||
|
||||
.order-body {
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.order-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #606266;
|
||||
width: 120rpx;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #303133;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.info-value.price {
|
||||
color: #f56c6c;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.order-footer {
|
||||
border-top: 1rpx solid #e0e0e0;
|
||||
padding-top: 16rpx;
|
||||
}
|
||||
|
||||
.order-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 16rpx;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.process-btn {
|
||||
background-color: #ecf5ff;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.detail-btn {
|
||||
background-color: #f0f9eb;
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.refund-btn {
|
||||
background-color: #fef0f0;
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 分页 */
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #fff;
|
||||
margin: 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.page-info {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.page-controls {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 20rpx;
|
||||
color: #409eff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.page-btn.disabled {
|
||||
color: #909399;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
548
src/pages/member/personal-gift.vue
Normal file
548
src/pages/member/personal-gift.vue
Normal file
@@ -0,0 +1,548 @@
|
||||
<template>
|
||||
<view class="personal-gift-container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<view class="back-icon">←</view>
|
||||
</view>
|
||||
<view class="header-title">赠送会员操作</view>
|
||||
<view class="header-right" @click="viewHistory">
|
||||
<view class="history-btn">历史</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 可用额度 -->
|
||||
<view class="quota-section">
|
||||
<view class="section-title">可用额度</view>
|
||||
<view class="quota-card">
|
||||
<view class="quota-info">
|
||||
<view class="quota-label">本月可用赠送额度</view>
|
||||
<view class="quota-value">{{ availableQuota }}个</view>
|
||||
</view>
|
||||
<view class="quota-hint">
|
||||
剩余 {{ availableQuota }} 个赠送会员名额,请合理使用
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 生成核验码 -->
|
||||
<view class="generate-section">
|
||||
<view class="section-title">生成核验码</view>
|
||||
<view class="generate-card">
|
||||
<view class="generate-row">
|
||||
<view class="generate-label">会员类型</view>
|
||||
<view class="generate-control">
|
||||
<picker
|
||||
:range="memberTypeOptions"
|
||||
:value="memberTypeIndex"
|
||||
@change="onMemberTypeChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ memberTypeOptions[memberTypeIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="generate-row">
|
||||
<view class="generate-label">生成数量</view>
|
||||
<view class="generate-control">
|
||||
<input
|
||||
v-model="generateCount"
|
||||
type="number"
|
||||
class="count-input"
|
||||
placeholder="请输入生成数量"
|
||||
min="1"
|
||||
:max="availableQuota"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="generate-action">
|
||||
<view class="action-btn primary-btn" @click="generateCode">
|
||||
生成核验码
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 核验码列表 -->
|
||||
<view class="code-list-section">
|
||||
<view class="section-title">我的核验码</view>
|
||||
<view class="code-list">
|
||||
<view
|
||||
v-for="(code, index) in codeList"
|
||||
:key="index"
|
||||
class="code-item"
|
||||
>
|
||||
<view class="code-header">
|
||||
<view class="code-value">{{ code.code }}</view>
|
||||
<view class="code-status" :class="code.statusClass">{{ code.status }}</view>
|
||||
</view>
|
||||
<view class="code-body">
|
||||
<view class="code-info">
|
||||
<view class="info-item">
|
||||
<view class="info-label">会员类型:</view>
|
||||
<view class="info-value">{{ code.memberType }}</view>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="info-label">生成时间:</view>
|
||||
<view class="info-value">{{ code.createTime }}</view>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="info-label">有效期至:</view>
|
||||
<view class="info-value">{{ code.validUntil }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="code-footer">
|
||||
<view class="code-actions">
|
||||
<view class="action-btn copy-btn" @click="copyCode(code.code)">
|
||||
复制
|
||||
</view>
|
||||
<view class="action-btn share-btn" @click="shareCode(code.code)">
|
||||
分享
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="codeList.length === 0" class="empty-state">
|
||||
<view class="empty-icon">🎁</view>
|
||||
<view class="empty-text">暂无核验码数据</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue"
|
||||
|
||||
// 可用额度
|
||||
const availableQuota = ref(10)
|
||||
|
||||
// 会员类型选项
|
||||
const memberTypeOptions = ['月度会员', '季度会员', '年度会员']
|
||||
const memberTypeIndex = ref(0)
|
||||
|
||||
// 生成数量
|
||||
const generateCount = ref('1')
|
||||
|
||||
// 核验码列表数据
|
||||
const codeList = ref([
|
||||
{
|
||||
id: 1,
|
||||
code: 'PG20260129001',
|
||||
memberType: '月度会员',
|
||||
createTime: '2026-01-29 15:30:00',
|
||||
validUntil: '2026-02-29 23:59:59',
|
||||
status: '未使用',
|
||||
statusClass: 'status-unused'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
code: 'PG20260129002',
|
||||
memberType: '季度会员',
|
||||
createTime: '2026-01-29 14:20:00',
|
||||
validUntil: '2026-04-29 23:59:59',
|
||||
status: '已使用',
|
||||
statusClass: 'status-used'
|
||||
}
|
||||
])
|
||||
|
||||
onMounted(() => {
|
||||
// 实际项目中应从接口获取可用额度和核验码列表
|
||||
// loadQuotaAndCodes()
|
||||
})
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}
|
||||
|
||||
// 查看历史
|
||||
function viewHistory() {
|
||||
uni.showToast({
|
||||
title: '查看历史功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
// 会员类型变更
|
||||
function onMemberTypeChange(e) {
|
||||
const value = e.detail.value
|
||||
memberTypeIndex.value = value
|
||||
}
|
||||
|
||||
// 生成核验码
|
||||
function generateCode() {
|
||||
const count = parseInt(generateCount.value)
|
||||
if (!count || count <= 0) {
|
||||
uni.showToast({
|
||||
title: '请输入有效的生成数量',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (count > availableQuota) {
|
||||
uni.showToast({
|
||||
title: '生成数量超过可用额度',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
uni.showLoading({ title: '生成中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
// 模拟生成核验码
|
||||
const newCode = {
|
||||
id: codeList.value.length + 1,
|
||||
code: 'PG' + new Date().getTime().toString().substring(0, 10),
|
||||
memberType: memberTypeOptions[memberTypeIndex.value],
|
||||
createTime: new Date().toLocaleString('zh-CN'),
|
||||
validUntil: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toLocaleDateString('zh-CN'),
|
||||
status: '未使用',
|
||||
statusClass: 'status-unused'
|
||||
}
|
||||
codeList.value.unshift(newCode)
|
||||
availableQuota.value -= count
|
||||
uni.showToast({
|
||||
title: '核验码生成成功',
|
||||
icon: 'success'
|
||||
})
|
||||
// 实际项目中应调用接口生成核验码
|
||||
// generateVerificationCode(count)
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
// 复制核验码
|
||||
function copyCode(code) {
|
||||
uni.setClipboardData({
|
||||
data: code,
|
||||
success: function() {
|
||||
uni.showToast({
|
||||
title: '核验码已复制',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 分享核验码
|
||||
function shareCode(code) {
|
||||
uni.showActionSheet({
|
||||
itemList: ['分享到微信', '分享到朋友圈', '复制链接'],
|
||||
success: function(res) {
|
||||
switch(res.tapIndex) {
|
||||
case 0:
|
||||
uni.showToast({ title: '分享到微信', duration: 1000 })
|
||||
break
|
||||
case 1:
|
||||
uni.showToast({ title: '分享到朋友圈', duration: 1000 })
|
||||
break
|
||||
case 2:
|
||||
uni.setClipboardData({
|
||||
data: `https://example.com/activate?code=${code}`,
|
||||
success: function() {
|
||||
uni.showToast({ title: '链接已复制', duration: 1000 })
|
||||
}
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* #ifndef APP-NVUE */
|
||||
page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
view {
|
||||
font-size: 14px;
|
||||
line-height: inherit;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.personal-gift-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 120rpx;
|
||||
background-color: #fff;
|
||||
padding: 0 32rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 40rpx;
|
||||
color: #303133;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 60rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.history-btn {
|
||||
font-size: 24rpx;
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 通用部分样式 */
|
||||
.quota-section,
|
||||
.generate-section,
|
||||
.code-list-section {
|
||||
padding: 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 0 16rpx 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 24rpx;
|
||||
padding-left: 12rpx;
|
||||
border-left: 8rpx solid #409eff;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* 可用额度 */
|
||||
.quota-card {
|
||||
background-color: #ecf5ff;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.quota-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.quota-label {
|
||||
font-size: 24rpx;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.quota-value {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.quota-hint {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* 生成核验码 */
|
||||
.generate-card {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.generate-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.generate-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.generate-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.generate-control {
|
||||
background-color: #fff;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.picker {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.count-input {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.generate-action {
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 16rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.primary-btn {
|
||||
background-color: #409eff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 核验码列表 */
|
||||
.code-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.code-item {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.code-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
padding-bottom: 16rpx;
|
||||
border-bottom: 1rpx solid #e0e0e0;
|
||||
}
|
||||
|
||||
.code-value {
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.code-status {
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.status-unused {
|
||||
color: #409eff;
|
||||
background-color: rgba(64, 158, 255, 0.1);
|
||||
}
|
||||
|
||||
.status-used {
|
||||
color: #67c23a;
|
||||
background-color: rgba(103, 194, 58, 0.1);
|
||||
}
|
||||
|
||||
.code-body {
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.code-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #606266;
|
||||
width: 120rpx;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #303133;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.code-footer {
|
||||
border-top: 1rpx solid #e0e0e0;
|
||||
padding-top: 16rpx;
|
||||
}
|
||||
|
||||
.code-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
background-color: #ecf5ff;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.share-btn {
|
||||
background-color: #f0f9eb;
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
548
src/pages/member/personal-order.vue
Normal file
548
src/pages/member/personal-order.vue
Normal file
@@ -0,0 +1,548 @@
|
||||
<template>
|
||||
<view class="personal-order-container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<view class="back-icon">←</view>
|
||||
</view>
|
||||
<view class="header-title">订单查看</view>
|
||||
<view class="header-right" @click="exportOrders">
|
||||
<view class="export-btn">导出</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选条件 -->
|
||||
<view class="filter-section">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">时间范围</view>
|
||||
<view class="filter-control">
|
||||
<picker
|
||||
:range="timeRangeOptions"
|
||||
:value="timeRangeIndex"
|
||||
@change="onTimeRangeChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ timeRangeOptions[timeRangeIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">订单状态</view>
|
||||
<view class="filter-control">
|
||||
<picker
|
||||
:range="statusOptions"
|
||||
:value="statusIndex"
|
||||
@change="onStatusChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ statusOptions[statusIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 订单概览 -->
|
||||
<view class="overview-section">
|
||||
<view class="section-title">订单概览</view>
|
||||
<view class="overview-card">
|
||||
<view class="overview-row">
|
||||
<view class="overview-item">
|
||||
<view class="overview-value">{{ totalOrders }}</view>
|
||||
<view class="overview-label">总订单数</view>
|
||||
</view>
|
||||
<view class="overview-item">
|
||||
<view class="overview-value">{{ completedOrders }}</view>
|
||||
<view class="overview-label">已完成</view>
|
||||
</view>
|
||||
<view class="overview-item">
|
||||
<view class="overview-value">{{ totalAmount }}</view>
|
||||
<view class="overview-label">总金额</view>
|
||||
</view>
|
||||
<view class="overview-item">
|
||||
<view class="overview-value">{{ totalProfit }}</view>
|
||||
<view class="overview-label">总收益</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 订单列表 -->
|
||||
<view class="order-list-section">
|
||||
<view class="section-title">订单列表</view>
|
||||
<view class="order-list">
|
||||
<view
|
||||
v-for="(order, index) in orderList"
|
||||
:key="index"
|
||||
class="order-item"
|
||||
>
|
||||
<view class="order-header">
|
||||
<view class="order-code">{{ order.code }}</view>
|
||||
<view class="order-status" :class="order.statusClass">{{ order.status }}</view>
|
||||
</view>
|
||||
<view class="order-body">
|
||||
<view class="order-info">
|
||||
<view class="info-item">
|
||||
<view class="info-label">学员姓名:</view>
|
||||
<view class="info-value">{{ order.studentName }}</view>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="info-label">会员类型:</view>
|
||||
<view class="info-value">{{ order.memberType }}</view>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="info-label">订单金额:</view>
|
||||
<view class="info-value price">{{ order.amount }}</view>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="info-label">下单时间:</view>
|
||||
<view class="info-value">{{ order.createTime }}</view>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="info-label">收益:</view>
|
||||
<view class="info-value profit">{{ order.profit }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="orderList.length === 0" class="empty-state">
|
||||
<view class="empty-icon">📋</view>
|
||||
<view class="empty-text">暂无订单数据</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页 -->
|
||||
<view v-if="orderList.length > 0" class="pagination">
|
||||
<view class="page-info">
|
||||
共 {{ totalOrders }} 条,当前第 {{ currentPage }} 页
|
||||
</view>
|
||||
<view class="page-controls">
|
||||
<view
|
||||
class="page-btn"
|
||||
:class="{ disabled: currentPage === 1 }"
|
||||
@click="goToPage(currentPage - 1)"
|
||||
>
|
||||
上一页
|
||||
</view>
|
||||
<view
|
||||
class="page-btn"
|
||||
:class="{ disabled: currentPage === totalPages }"
|
||||
@click="goToPage(currentPage + 1)"
|
||||
>
|
||||
下一页
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from "vue"
|
||||
|
||||
// 筛选条件
|
||||
const timeRangeOptions = ['全部时间', '今日', '本周', '本月', '本季度', '本年']
|
||||
const timeRangeIndex = ref(3)
|
||||
|
||||
const statusOptions = ['全部状态', '待处理', '已完成', '已退款']
|
||||
const statusIndex = ref(0)
|
||||
|
||||
// 订单概览数据
|
||||
const totalOrders = ref(100)
|
||||
const completedOrders = ref(85)
|
||||
const totalAmount = ref('¥12,345.67')
|
||||
const totalProfit = ref('¥1,234.56')
|
||||
|
||||
// 订单列表数据
|
||||
const orderList = ref([
|
||||
{
|
||||
id: 1,
|
||||
code: 'MO20260129001',
|
||||
studentName: '张三',
|
||||
memberType: '年度会员',
|
||||
amount: '¥299.90',
|
||||
profit: '¥29.99',
|
||||
createTime: '2026-01-29 15:30:00',
|
||||
status: '已完成',
|
||||
statusClass: 'status-completed'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
code: 'MO20260129002',
|
||||
studentName: '李四',
|
||||
memberType: '月度会员',
|
||||
amount: '¥39.90',
|
||||
profit: '¥3.99',
|
||||
createTime: '2026-01-29 14:20:00',
|
||||
status: '已完成',
|
||||
statusClass: 'status-completed'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
code: 'MO20260129003',
|
||||
studentName: '王五',
|
||||
memberType: '季度会员',
|
||||
amount: '¥99.90',
|
||||
profit: '¥9.99',
|
||||
createTime: '2026-01-29 13:10:00',
|
||||
status: '待处理',
|
||||
statusClass: 'status-pending'
|
||||
}
|
||||
])
|
||||
|
||||
// 分页信息
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
|
||||
// 计算总页数
|
||||
const totalPages = computed(() => {
|
||||
return Math.ceil(totalOrders.value / pageSize.value)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// 实际项目中应从接口获取订单数据
|
||||
// loadOrderData()
|
||||
})
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}
|
||||
|
||||
// 导出订单
|
||||
function exportOrders() {
|
||||
uni.showModal({
|
||||
title: '导出订单',
|
||||
content: '确定要导出当前筛选条件下的订单吗?',
|
||||
success: function(res) {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: '导出中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '导出成功',
|
||||
icon: 'success'
|
||||
})
|
||||
// 实际项目中应调用接口导出订单
|
||||
// exportOrderData()
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 时间范围变更
|
||||
function onTimeRangeChange(e) {
|
||||
const value = e.detail.value
|
||||
timeRangeIndex.value = value
|
||||
// 实际项目中应根据时间范围筛选订单
|
||||
// filterOrderData()
|
||||
}
|
||||
|
||||
// 状态变更
|
||||
function onStatusChange(e) {
|
||||
const value = e.detail.value
|
||||
statusIndex.value = value
|
||||
// 实际项目中应根据状态筛选订单
|
||||
// filterOrderData()
|
||||
}
|
||||
|
||||
// 跳转到指定页码
|
||||
function goToPage(page) {
|
||||
if (page < 1 || page > totalPages.value) {
|
||||
return
|
||||
}
|
||||
currentPage.value = page
|
||||
// 实际项目中应加载指定页码的订单
|
||||
// loadOrderPage(page)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* #ifndef APP-NVUE */
|
||||
page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
view {
|
||||
font-size: 14px;
|
||||
line-height: inherit;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.personal-order-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 120rpx;
|
||||
background-color: #fff;
|
||||
padding: 0 32rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 40rpx;
|
||||
color: #303133;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 60rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.export-btn {
|
||||
font-size: 24rpx;
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 通用部分样式 */
|
||||
.filter-section,
|
||||
.overview-section,
|
||||
.order-list-section {
|
||||
padding: 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 0 16rpx 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 24rpx;
|
||||
padding-left: 12rpx;
|
||||
border-left: 8rpx solid #409eff;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* 筛选条件 */
|
||||
.filter-row {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.filter-control {
|
||||
background-color: #f9f9f9;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.picker {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
/* 订单概览 */
|
||||
.overview-card {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.overview-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.overview-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.overview-value {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.overview-label {
|
||||
font-size: 18rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
/* 订单列表 */
|
||||
.order-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.order-item {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.order-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
padding-bottom: 16rpx;
|
||||
border-bottom: 1rpx solid #e0e0e0;
|
||||
}
|
||||
|
||||
.order-code {
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.order-status {
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
color: #e6a23c;
|
||||
background-color: rgba(230, 162, 60, 0.1);
|
||||
}
|
||||
|
||||
.status-completed {
|
||||
color: #67c23a;
|
||||
background-color: rgba(103, 194, 58, 0.1);
|
||||
}
|
||||
|
||||
.status-refunded {
|
||||
color: #909399;
|
||||
background-color: rgba(144, 147, 153, 0.1);
|
||||
}
|
||||
|
||||
.order-body {
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.order-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #606266;
|
||||
width: 120rpx;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #303133;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.info-value.price,
|
||||
.info-value.profit {
|
||||
color: #f56c6c;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 分页 */
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #fff;
|
||||
margin: 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.page-info {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.page-controls {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 20rpx;
|
||||
color: #409eff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.page-btn.disabled {
|
||||
color: #909399;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
@@ -55,9 +55,10 @@
|
||||
<view class="form-label">字段类型</view>
|
||||
<view class="form-control">
|
||||
<picker
|
||||
v-model="field.type"
|
||||
:value="getFieldTypeIndex(field.type)"
|
||||
:range="fieldTypes"
|
||||
:range-key="'label'"
|
||||
@change="onFieldTypeChange"
|
||||
class="form-picker"
|
||||
>
|
||||
<view class="picker-display">{{ getFieldTypeName(field.type) }}</view>
|
||||
@@ -82,7 +83,8 @@
|
||||
<view class="form-label">是否必填</view>
|
||||
<view class="form-control">
|
||||
<switch
|
||||
v-model="field.required"
|
||||
:checked="field.required"
|
||||
@change="onRequiredChange($event, index)"
|
||||
class="form-switch"
|
||||
active-color="#409eff"
|
||||
/>
|
||||
@@ -113,7 +115,8 @@
|
||||
<view class="rule-label">启用字段筛选</view>
|
||||
<view class="rule-control">
|
||||
<switch
|
||||
v-model="filterRules.enabled"
|
||||
:checked="filterRules.enabled"
|
||||
@change="onFilterEnabledChange"
|
||||
class="form-switch"
|
||||
active-color="#409eff"
|
||||
/>
|
||||
@@ -123,18 +126,19 @@
|
||||
<view class="rule-item">
|
||||
<view class="rule-label">默认显示字段</view>
|
||||
<view class="rule-control">
|
||||
<picker
|
||||
v-model="filterRules.defaultFields"
|
||||
:range="availableFields"
|
||||
:range-key="'name'"
|
||||
mode="multiSelector"
|
||||
class="form-picker"
|
||||
>
|
||||
<view class="picker-display">
|
||||
{{ filterRules.defaultFields.length }} 个字段
|
||||
<picker
|
||||
:value="getDefaultFieldsIndex()"
|
||||
:range="availableFields"
|
||||
:range-key="'name'"
|
||||
mode="multiSelector"
|
||||
@change="onDefaultFieldsChange"
|
||||
class="form-picker"
|
||||
>
|
||||
<view class="picker-display">
|
||||
{{ filterRules.defaultFields.length }} 个字段
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -188,6 +192,9 @@
|
||||
defaultFields: [0, 1]
|
||||
})
|
||||
|
||||
// 当前编辑的字段索引
|
||||
const currentEditIndex = ref(null)
|
||||
|
||||
// 计算可用字段
|
||||
const availableFields = computed(() => {
|
||||
return customFields.value.map((field, index) => ({
|
||||
@@ -237,6 +244,39 @@
|
||||
})
|
||||
}
|
||||
|
||||
// 获取字段类型索引
|
||||
function getFieldTypeIndex(type) {
|
||||
return fieldTypes.value.findIndex(item => item.value === type)
|
||||
}
|
||||
|
||||
// 处理字段类型变更
|
||||
function onFieldTypeChange(e) {
|
||||
const index = e.detail.value
|
||||
if (currentEditIndex.value !== null) {
|
||||
customFields.value[currentEditIndex.value].type = fieldTypes.value[index].value
|
||||
}
|
||||
}
|
||||
|
||||
// 获取默认字段索引
|
||||
function getDefaultFieldsIndex() {
|
||||
return filterRules.value.defaultFields
|
||||
}
|
||||
|
||||
// 处理默认字段变更
|
||||
function onDefaultFieldsChange(e) {
|
||||
filterRules.value.defaultFields = e.detail.value
|
||||
}
|
||||
|
||||
// 处理必填状态变更
|
||||
function onRequiredChange(e, index) {
|
||||
customFields.value[index].required = e.detail.value
|
||||
}
|
||||
|
||||
// 处理筛选启用状态变更
|
||||
function onFilterEnabledChange(e) {
|
||||
filterRules.value.enabled = e.detail.value
|
||||
}
|
||||
|
||||
// 移动字段
|
||||
function moveField(index, direction) {
|
||||
if (direction === 'up' && index > 0) {
|
||||
|
||||
@@ -69,16 +69,16 @@
|
||||
<view class="tip-item" :class="{ active: passwordForm.newPassword.length >= 8 }">
|
||||
• 密码长度至少8位
|
||||
</view>
|
||||
<view class="tip-item" :class="{ active: /[A-Z]/.test(passwordForm.newPassword) }">
|
||||
<view class="tip-item" :class="{ active: hasUpperCase }">
|
||||
• 包含大写字母
|
||||
</view>
|
||||
<view class="tip-item" :class="{ active: /[a-z]/.test(passwordForm.newPassword) }">
|
||||
<view class="tip-item" :class="{ active: hasLowerCase }">
|
||||
• 包含小写字母
|
||||
</view>
|
||||
<view class="tip-item" :class="{ active: /[0-9]/.test(passwordForm.newPassword) }">
|
||||
<view class="tip-item" :class="{ active: hasNumber }">
|
||||
• 包含数字
|
||||
</view>
|
||||
<view class="tip-item" :class="{ active: /[!@#$%^&*(),.?":{}|<>]/.test(passwordForm.newPassword) }">
|
||||
<view class="tip-item" :class="{ active: hasSpecialChar }">
|
||||
• 包含特殊字符
|
||||
</view>
|
||||
</view>
|
||||
@@ -119,7 +119,13 @@
|
||||
confirmPassword: ''
|
||||
})
|
||||
|
||||
// 显示密码
|
||||
// 密码强度计算属性
|
||||
const hasUpperCase = computed(() => /[A-Z]/.test(passwordForm.value.newPassword))
|
||||
const hasLowerCase = computed(() => /[a-z]/.test(passwordForm.value.newPassword))
|
||||
const hasNumber = computed(() => /[0-9]/.test(passwordForm.value.newPassword))
|
||||
const hasSpecialChar = computed(() => /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(passwordForm.value.newPassword))
|
||||
|
||||
// 密码强度文本显示密码
|
||||
const showPassword = ref(false)
|
||||
|
||||
// 切换密码显示状态
|
||||
|
||||
@@ -67,8 +67,9 @@
|
||||
<view class="form-label">性别</view>
|
||||
<view class="form-control">
|
||||
<picker
|
||||
v-model="userInfo.gender"
|
||||
:value="getGenderIndex()"
|
||||
:range="genders"
|
||||
@change="onGenderChange"
|
||||
class="form-picker"
|
||||
>
|
||||
<view class="picker-display">{{ userInfo.gender || '请选择性别' }}</view>
|
||||
@@ -165,6 +166,17 @@
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}
|
||||
|
||||
// 获取性别索引
|
||||
function getGenderIndex() {
|
||||
return genders.indexOf(userInfo.value.gender)
|
||||
}
|
||||
|
||||
// 处理性别变更
|
||||
function onGenderChange(e) {
|
||||
const index = e.detail.value
|
||||
userInfo.value.gender = genders[index]
|
||||
}
|
||||
|
||||
// 选择头像
|
||||
function chooseAvatar() {
|
||||
uni.chooseImage({
|
||||
|
||||
589
src/pages/stats/distribution.vue
Normal file
589
src/pages/stats/distribution.vue
Normal file
@@ -0,0 +1,589 @@
|
||||
<template>
|
||||
<view class="distribution-stats-container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<view class="back-icon">←</view>
|
||||
</view>
|
||||
<view class="header-title">分销业绩统计</view>
|
||||
<view class="header-right" @click="exportData">
|
||||
<view class="export-btn">导出</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选条件 -->
|
||||
<view class="filter-section">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">时间范围</view>
|
||||
<view class="filter-control">
|
||||
<picker-view
|
||||
:value="dateRangeValue"
|
||||
@change="onDateRangeChange"
|
||||
class="date-picker"
|
||||
>
|
||||
<picker-view-column>
|
||||
<view v-for="(date, index) in dateOptions" :key="index">{{ date }}</view>
|
||||
</picker-view-column>
|
||||
</picker-view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">分销员</view>
|
||||
<view class="filter-control">
|
||||
<picker
|
||||
:range="distributorOptions"
|
||||
:value="distributorIndex"
|
||||
@change="onDistributorChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ distributorOptions[distributorIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 核心指标 -->
|
||||
<view class="metrics-section">
|
||||
<view class="metrics-grid">
|
||||
<view class="metric-card">
|
||||
<view class="metric-icon">💰</view>
|
||||
<view class="metric-content">
|
||||
<view class="metric-value">{{ metrics.totalProfit }}</view>
|
||||
<view class="metric-label">总提成</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="metric-card">
|
||||
<view class="metric-icon">📱</view>
|
||||
<view class="metric-content">
|
||||
<view class="metric-value">{{ metrics.scanCount }}</view>
|
||||
<view class="metric-label">扫码数</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="metric-card">
|
||||
<view class="metric-icon">👥</view>
|
||||
<view class="metric-content">
|
||||
<view class="metric-value">{{ metrics.paidCount }}</view>
|
||||
<view class="metric-label">付费人数</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="metric-card">
|
||||
<view class="metric-icon">📈</view>
|
||||
<view class="metric-content">
|
||||
<view class="metric-value">{{ metrics.conversionRate }}</view>
|
||||
<view class="metric-label">转化率</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 趋势图表 -->
|
||||
<view class="chart-section">
|
||||
<view class="section-title">业绩趋势</view>
|
||||
<view class="chart-container">
|
||||
<!-- 模拟图表,实际项目中应使用图表库 -->
|
||||
<view class="mock-chart">
|
||||
<view class="chart-header">
|
||||
<view class="chart-title">近7天业绩趋势</view>
|
||||
</view>
|
||||
<view class="chart-body">
|
||||
<view class="chart-bars">
|
||||
<view v-for="(day, index) in trendData" :key="index" class="chart-bar">
|
||||
<view class="bar-container">
|
||||
<view class="bar" :style="{ height: day.value + '%' }"></view>
|
||||
</view>
|
||||
<view class="bar-label">{{ day.date }}</view>
|
||||
<view class="bar-value">{{ day.amount }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 排行榜 -->
|
||||
<view class="ranking-section">
|
||||
<view class="section-title">分销员排行榜</view>
|
||||
<view class="ranking-list">
|
||||
<view
|
||||
v-for="(item, index) in rankingData"
|
||||
:key="index"
|
||||
class="ranking-item"
|
||||
:class="{ 'top-three': index < 3 }"
|
||||
>
|
||||
<view class="ranking-number">{{ index + 1 }}</view>
|
||||
<view class="ranking-info">
|
||||
<view class="ranking-name">{{ item.name }}</view>
|
||||
<view class="ranking-stats">
|
||||
<view class="stat-item">
|
||||
<view class="stat-label">提成:</view>
|
||||
<view class="stat-value">{{ item.profit }}</view>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<view class="stat-label">扫码:</view>
|
||||
<view class="stat-value">{{ item.scans }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="ranking-arrow" @click="viewDistributorDetail(item.id)">
|
||||
→
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue"
|
||||
|
||||
// 筛选条件
|
||||
const dateOptions = ['全部时间', '近7天', '近30天', '近90天', '今年', '去年']
|
||||
const dateRangeValue = ref([0])
|
||||
|
||||
const distributorOptions = ['全部分销员', '张三', '李四', '王五', '赵六']
|
||||
const distributorIndex = ref(0)
|
||||
|
||||
// 核心指标
|
||||
const metrics = ref({
|
||||
totalProfit: '¥12,345.67',
|
||||
scanCount: 1234,
|
||||
paidCount: 456,
|
||||
conversionRate: '36.9%'
|
||||
})
|
||||
|
||||
// 趋势数据
|
||||
const trendData = ref([
|
||||
{ date: '1/23', amount: '¥1,234', value: 30 },
|
||||
{ date: '1/24', amount: '¥1,567', value: 40 },
|
||||
{ date: '1/25', amount: '¥987', value: 25 },
|
||||
{ date: '1/26', amount: '¥2,109', value: 55 },
|
||||
{ date: '1/27', amount: '¥1,876', value: 48 },
|
||||
{ date: '1/28', amount: '¥2,345', value: 60 },
|
||||
{ date: '1/29', amount: '¥1,987', value: 50 }
|
||||
])
|
||||
|
||||
// 排行榜数据
|
||||
const rankingData = ref([
|
||||
{ id: 1, name: '张三', profit: '¥3,456.78', scans: 345 },
|
||||
{ id: 2, name: '李四', profit: '¥2,890.12', scans: 298 },
|
||||
{ id: 3, name: '王五', profit: '¥2,123.45', scans: 215 },
|
||||
{ id: 4, name: '赵六', profit: '¥1,876.54', scans: 189 },
|
||||
{ id: 5, name: '钱七', profit: '¥1,543.21', scans: 156 }
|
||||
])
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}
|
||||
|
||||
// 导出数据
|
||||
function exportData() {
|
||||
uni.showModal({
|
||||
title: '导出数据',
|
||||
content: '是否导出当前统计数据?',
|
||||
success: function(res) {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: '导出中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '导出成功', icon: 'success' })
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 日期范围变化
|
||||
function onDateRangeChange(e) {
|
||||
const value = e.detail.value
|
||||
dateRangeValue.value = value
|
||||
// 实际项目中应根据选择的日期范围重新获取数据
|
||||
refreshData()
|
||||
}
|
||||
|
||||
// 分销员变化
|
||||
function onDistributorChange(e) {
|
||||
const value = e.detail.value
|
||||
distributorIndex.value = value
|
||||
// 实际项目中应根据选择的分销员重新获取数据
|
||||
refreshData()
|
||||
}
|
||||
|
||||
// 刷新数据
|
||||
function refreshData() {
|
||||
uni.showLoading({ title: '加载中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
// 实际项目中应从接口获取数据
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
// 查看分销员详情
|
||||
function viewDistributorDetail(distributorId) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/distributor/detail?id=${distributorId}`
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 实际项目中应从接口获取数据
|
||||
// loadStatsData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* #ifndef APP-NVUE */
|
||||
page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
view {
|
||||
font-size: 14px;
|
||||
line-height: inherit;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.distribution-stats-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 120rpx;
|
||||
background-color: #fff;
|
||||
padding: 0 32rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 40rpx;
|
||||
color: #303133;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 60rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.export-btn {
|
||||
font-size: 28rpx;
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 筛选条件 */
|
||||
.filter-section {
|
||||
padding: 24rpx 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.filter-control {
|
||||
background-color: #f9f9f9;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.date-picker {
|
||||
height: 150rpx;
|
||||
}
|
||||
|
||||
.picker {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
/* 核心指标 */
|
||||
.metrics-section {
|
||||
padding: 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 0 16rpx 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.metrics-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.metric-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.metric-icon {
|
||||
font-size: 48rpx;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
|
||||
.metric-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.metric-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
/* 图表区域 */
|
||||
.chart-section {
|
||||
padding: 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 0 16rpx 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 24rpx;
|
||||
padding-left: 12rpx;
|
||||
border-left: 8rpx solid #409eff;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.mock-chart {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.chart-header {
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.chart-bars {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
height: 300rpx;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.chart-bar {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.bar-container {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.bar {
|
||||
width: 40rpx;
|
||||
background-color: #409eff;
|
||||
border-radius: 4rpx 4rpx 0 0;
|
||||
transition: height 0.5s ease;
|
||||
}
|
||||
|
||||
.bar-label {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.bar-value {
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
/* 排行榜 */
|
||||
.ranking-section {
|
||||
padding: 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 0 16rpx 32rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.ranking-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.ranking-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.ranking-item:active {
|
||||
transform: translateY(2rpx);
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.ranking-item.top-three {
|
||||
background-color: #f0f9eb;
|
||||
border-left: 8rpx solid #67c23a;
|
||||
}
|
||||
|
||||
.ranking-number {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border-radius: 24rpx;
|
||||
background-color: #409eff;
|
||||
color: #fff;
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
|
||||
.ranking-item.top-three .ranking-number {
|
||||
background-color: #67c23a;
|
||||
}
|
||||
|
||||
.ranking-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.ranking-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.ranking-stats {
|
||||
display: flex;
|
||||
gap: 32rpx;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.ranking-arrow {
|
||||
font-size: 32rpx;
|
||||
color: #909399;
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media screen and (min-width: 500px) {
|
||||
.distribution-stats-container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.metrics-grid {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.chart-bars {
|
||||
height: 400rpx;
|
||||
}
|
||||
|
||||
.bar {
|
||||
width: 60rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
743
src/pages/stats/member.vue
Normal file
743
src/pages/stats/member.vue
Normal file
@@ -0,0 +1,743 @@
|
||||
<template>
|
||||
<view class="member-stats-container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<view class="back-icon">←</view>
|
||||
</view>
|
||||
<view class="header-title">会员运营统计</view>
|
||||
<view class="header-right" @click="exportData">
|
||||
<view class="export-btn">导出</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选条件 -->
|
||||
<view class="filter-section">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">时间范围</view>
|
||||
<view class="filter-control">
|
||||
<picker
|
||||
:range="dateOptions"
|
||||
:value="dateIndex"
|
||||
@change="onDateChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ dateOptions[dateIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">会员类型</view>
|
||||
<view class="filter-control">
|
||||
<picker
|
||||
:range="memberTypeOptions"
|
||||
:value="memberTypeIndex"
|
||||
@change="onMemberTypeChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ memberTypeOptions[memberTypeIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 核心指标 -->
|
||||
<view class="metrics-section">
|
||||
<view class="metrics-grid">
|
||||
<view class="metric-card">
|
||||
<view class="metric-icon">🛒</view>
|
||||
<view class="metric-content">
|
||||
<view class="metric-value">{{ metrics.totalOrders }}</view>
|
||||
<view class="metric-label">总订单数</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="metric-card">
|
||||
<view class="metric-icon">💰</view>
|
||||
<view class="metric-content">
|
||||
<view class="metric-value">{{ metrics.totalAmount }}</view>
|
||||
<view class="metric-label">总金额</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="metric-card">
|
||||
<view class="metric-icon">📈</view>
|
||||
<view class="metric-content">
|
||||
<view class="metric-value">{{ metrics.averageAmount }}</view>
|
||||
<view class="metric-label">平均金额</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="metric-card">
|
||||
<view class="metric-icon">⚠️</view>
|
||||
<view class="metric-content">
|
||||
<view class="metric-value">{{ metrics.refundRate }}</view>
|
||||
<view class="metric-label">退款率</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 订单趋势 -->
|
||||
<view class="chart-section">
|
||||
<view class="section-title">订单趋势</view>
|
||||
<view class="chart-container">
|
||||
<!-- 模拟图表,实际项目中应使用图表库 -->
|
||||
<view class="mock-chart">
|
||||
<view class="chart-header">
|
||||
<view class="chart-title">近30天订单趋势</view>
|
||||
</view>
|
||||
<view class="chart-body">
|
||||
<view class="chart-lines">
|
||||
<view class="chart-line-container">
|
||||
<view class="chart-line order-line"></view>
|
||||
</view>
|
||||
<view class="chart-line-container">
|
||||
<view class="chart-line amount-line"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="chart-legend">
|
||||
<view class="legend-item">
|
||||
<view class="legend-color order-color"></view>
|
||||
<view class="legend-text">订单数</view>
|
||||
</view>
|
||||
<view class="legend-item">
|
||||
<view class="legend-color amount-color"></view>
|
||||
<view class="legend-text">金额</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 会员类型分布 -->
|
||||
<view class="distribution-section">
|
||||
<view class="section-title">会员类型分布</view>
|
||||
<view class="distribution-grid">
|
||||
<view
|
||||
v-for="(item, index) in memberTypeDistribution"
|
||||
:key="index"
|
||||
class="distribution-card"
|
||||
>
|
||||
<view class="distribution-icon">{{ item.icon }}</view>
|
||||
<view class="distribution-content">
|
||||
<view class="distribution-title">{{ item.type }}</view>
|
||||
<view class="distribution-stats">
|
||||
<view class="stat-item">
|
||||
<view class="stat-label">订单:</view>
|
||||
<view class="stat-value">{{ item.orders }}</view>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<view class="stat-label">占比:</view>
|
||||
<view class="stat-value">{{ item.percentage }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 最近订单 -->
|
||||
<view class="recent-orders-section">
|
||||
<view class="section-title">最近订单</view>
|
||||
<view class="order-list">
|
||||
<view
|
||||
v-for="(order, index) in recentOrders"
|
||||
:key="index"
|
||||
class="order-item"
|
||||
@click="viewOrderDetail(order.id)"
|
||||
>
|
||||
<view class="order-header">
|
||||
<view class="order-no">订单号:{{ order.orderNo }}</view>
|
||||
<view class="order-status" :class="order.status">{{ order.statusText }}</view>
|
||||
</view>
|
||||
<view class="order-info">
|
||||
<view class="info-row">
|
||||
<view class="info-label">会员类型:</view>
|
||||
<view class="info-value">{{ order.memberType }}</view>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<view class="info-label">学员:</view>
|
||||
<view class="info-value">{{ order.studentName }}</view>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<view class="info-label">金额:</view>
|
||||
<view class="info-value price">{{ order.amount }}</view>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<view class="info-label">时间:</view>
|
||||
<view class="info-value">{{ order.createTime }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue"
|
||||
|
||||
// 筛选条件
|
||||
const dateOptions = ['近7天', '近30天', '近90天', '今年', '全部']
|
||||
const dateIndex = ref(0)
|
||||
|
||||
const memberTypeOptions = ['全部类型', '月度会员', '季度会员', '年度会员']
|
||||
const memberTypeIndex = ref(0)
|
||||
|
||||
// 核心指标
|
||||
const metrics = ref({
|
||||
totalOrders: 789,
|
||||
totalAmount: '¥89,765.43',
|
||||
averageAmount: '¥113.77',
|
||||
refundRate: '2.1%'
|
||||
})
|
||||
|
||||
// 会员类型分布
|
||||
const memberTypeDistribution = ref([
|
||||
{
|
||||
type: '月度会员',
|
||||
icon: '📅',
|
||||
orders: 345,
|
||||
percentage: '43.7%'
|
||||
},
|
||||
{
|
||||
type: '季度会员',
|
||||
icon: '📊',
|
||||
orders: 234,
|
||||
percentage: '29.7%'
|
||||
},
|
||||
{
|
||||
type: '年度会员',
|
||||
icon: '📈',
|
||||
orders: 210,
|
||||
percentage: '26.6%'
|
||||
}
|
||||
])
|
||||
|
||||
// 最近订单
|
||||
const recentOrders = ref([
|
||||
{
|
||||
id: 1,
|
||||
orderNo: 'M202601290001',
|
||||
memberType: '年度会员',
|
||||
studentName: '张**',
|
||||
amount: '¥299.00',
|
||||
status: 'success',
|
||||
statusText: '支付成功',
|
||||
createTime: '2026-01-29 10:30:45'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
orderNo: 'M202601290002',
|
||||
memberType: '季度会员',
|
||||
studentName: '李**',
|
||||
amount: '¥99.00',
|
||||
status: 'success',
|
||||
statusText: '支付成功',
|
||||
createTime: '2026-01-29 09:15:30'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
orderNo: 'M202601290003',
|
||||
memberType: '月度会员',
|
||||
studentName: '王**',
|
||||
amount: '¥39.00',
|
||||
status: 'pending',
|
||||
statusText: '待支付',
|
||||
createTime: '2026-01-29 08:45:12'
|
||||
}
|
||||
])
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}
|
||||
|
||||
// 导出数据
|
||||
function exportData() {
|
||||
uni.showModal({
|
||||
title: '导出数据',
|
||||
content: '是否导出当前统计数据?',
|
||||
success: function(res) {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: '导出中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '导出成功', icon: 'success' })
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 日期变化
|
||||
function onDateChange(e) {
|
||||
const value = e.detail.value
|
||||
dateIndex.value = value
|
||||
// 实际项目中应根据选择的日期重新获取数据
|
||||
refreshData()
|
||||
}
|
||||
|
||||
// 会员类型变化
|
||||
function onMemberTypeChange(e) {
|
||||
const value = e.detail.value
|
||||
memberTypeIndex.value = value
|
||||
// 实际项目中应根据选择的会员类型重新获取数据
|
||||
refreshData()
|
||||
}
|
||||
|
||||
// 刷新数据
|
||||
function refreshData() {
|
||||
uni.showLoading({ title: '加载中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
// 实际项目中应从接口获取数据
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
// 查看订单详情
|
||||
function viewOrderDetail(orderId) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/member/order-detail?id=${orderId}`
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 实际项目中应从接口获取数据
|
||||
// loadStatsData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* #ifndef APP-NVUE */
|
||||
page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
view {
|
||||
font-size: 14px;
|
||||
line-height: inherit;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.member-stats-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 120rpx;
|
||||
background-color: #fff;
|
||||
padding: 0 32rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 40rpx;
|
||||
color: #303133;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 60rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.export-btn {
|
||||
font-size: 28rpx;
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 筛选条件 */
|
||||
.filter-section {
|
||||
padding: 24rpx 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.filter-control {
|
||||
background-color: #f9f9f9;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.picker {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
/* 核心指标 */
|
||||
.metrics-section {
|
||||
padding: 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 0 16rpx 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.metrics-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.metric-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.metric-icon {
|
||||
font-size: 48rpx;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
|
||||
.metric-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.metric-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
/* 图表区域 */
|
||||
.chart-section {
|
||||
padding: 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 0 16rpx 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 24rpx;
|
||||
padding-left: 12rpx;
|
||||
border-left: 8rpx solid #409eff;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.mock-chart {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.chart-header {
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.chart-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.chart-lines {
|
||||
height: 300rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.chart-line-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.chart-line {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 60%;
|
||||
background: linear-gradient(to top, rgba(64, 158, 255, 0.2), transparent);
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.chart-line.amount-line {
|
||||
height: 40%;
|
||||
background: linear-gradient(to top, rgba(103, 194, 58, 0.2), transparent);
|
||||
}
|
||||
|
||||
.chart-legend {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 32rpx;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.legend-color {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.legend-color.order-color {
|
||||
background-color: #409eff;
|
||||
}
|
||||
|
||||
.legend-color.amount-color {
|
||||
background-color: #67c23a;
|
||||
}
|
||||
|
||||
.legend-text {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
/* 会员类型分布 */
|
||||
.distribution-section {
|
||||
padding: 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 0 16rpx 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.distribution-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.distribution-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.distribution-icon {
|
||||
font-size: 48rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.distribution-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.distribution-title {
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.distribution-stats {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
/* 最近订单 */
|
||||
.recent-orders-section {
|
||||
padding: 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 0 16rpx 32rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.order-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.order-item {
|
||||
padding: 24rpx;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.order-item:active {
|
||||
transform: translateY(2rpx);
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.order-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.order-no {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.order-status {
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 4rpx;
|
||||
font-size: 18rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.order-status.success {
|
||||
background-color: #f0f9eb;
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.order-status.pending {
|
||||
background-color: #ecf5ff;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.order-status.refund {
|
||||
background-color: #fef0f0;
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.order-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
width: 120rpx;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 20rpx;
|
||||
color: #303133;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.info-value.price {
|
||||
font-weight: 600;
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media screen and (min-width: 500px) {
|
||||
.member-stats-container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.metrics-grid {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
.distribution-grid {
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.order-item {
|
||||
padding: 32rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
762
src/pages/stats/student.vue
Normal file
762
src/pages/stats/student.vue
Normal file
@@ -0,0 +1,762 @@
|
||||
<template>
|
||||
<view class="student-stats-container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<view class="back-icon">←</view>
|
||||
</view>
|
||||
<view class="header-title">学员学情统计</view>
|
||||
<view class="header-right" @click="exportData">
|
||||
<view class="export-btn">导出</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选条件 -->
|
||||
<view class="filter-section">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">时间范围</view>
|
||||
<view class="filter-control">
|
||||
<picker
|
||||
:range="dateOptions"
|
||||
:value="dateIndex"
|
||||
@change="onDateChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ dateOptions[dateIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">班级</view>
|
||||
<view class="filter-control">
|
||||
<picker
|
||||
:range="classOptions"
|
||||
:value="classIndex"
|
||||
@change="onClassChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ classOptions[classIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 核心指标 -->
|
||||
<view class="metrics-section">
|
||||
<view class="metrics-grid">
|
||||
<view class="metric-card">
|
||||
<view class="metric-icon">🎓</view>
|
||||
<view class="metric-content">
|
||||
<view class="metric-value">{{ metrics.totalStudents }}</view>
|
||||
<view class="metric-label">总学员数</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="metric-card">
|
||||
<view class="metric-icon">👥</view>
|
||||
<view class="metric-content">
|
||||
<view class="metric-value">{{ metrics.memberStudents }}</view>
|
||||
<view class="metric-label">会员学员</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="metric-card">
|
||||
<view class="metric-icon">📊</view>
|
||||
<view class="metric-content">
|
||||
<view class="metric-value">{{ metrics.avgCorrectRate }}</view>
|
||||
<view class="metric-label">平均正确率</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="metric-card">
|
||||
<view class="metric-icon">✏️</view>
|
||||
<view class="metric-content">
|
||||
<view class="metric-value">{{ metrics.avgExamScore }}</view>
|
||||
<view class="metric-label">平均模考分</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 学习趋势 -->
|
||||
<view class="chart-section">
|
||||
<view class="section-title">学习趋势</view>
|
||||
<view class="chart-container">
|
||||
<!-- 模拟图表,实际项目中应使用图表库 -->
|
||||
<view class="mock-chart">
|
||||
<view class="chart-header">
|
||||
<view class="chart-title">近30天学习时长趋势</view>
|
||||
</view>
|
||||
<view class="chart-body">
|
||||
<view class="chart-bars">
|
||||
<view v-for="(day, index) in trendData" :key="index" class="chart-bar">
|
||||
<view class="bar-container">
|
||||
<view class="bar" :style="{ height: day.value + '%' }"></view>
|
||||
</view>
|
||||
<view class="bar-label">{{ day.date }}</view>
|
||||
<view class="bar-value">{{ day.hours }}h</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 学习情况分布 -->
|
||||
<view class="distribution-section">
|
||||
<view class="section-title">学习情况分布</view>
|
||||
<view class="distribution-grid">
|
||||
<view
|
||||
v-for="(item, index) in learningDistribution"
|
||||
:key="index"
|
||||
class="distribution-card"
|
||||
>
|
||||
<view class="distribution-icon">{{ item.icon }}</view>
|
||||
<view class="distribution-content">
|
||||
<view class="distribution-title">{{ item.level }}</view>
|
||||
<view class="distribution-stats">
|
||||
<view class="stat-item">
|
||||
<view class="stat-label">人数:</view>
|
||||
<view class="stat-value">{{ item.count }}</view>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<view class="stat-label">占比:</view>
|
||||
<view class="stat-value">{{ item.percentage }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 学员列表 -->
|
||||
<view class="student-section">
|
||||
<view class="section-title">学员列表</view>
|
||||
<view class="student-list">
|
||||
<view
|
||||
v-for="(student, index) in studentList"
|
||||
:key="index"
|
||||
class="student-item"
|
||||
@click="viewStudentDetail(student.id)"
|
||||
>
|
||||
<view class="student-info">
|
||||
<view class="student-name">{{ student.name }}</view>
|
||||
<view class="student-meta">
|
||||
<view class="meta-item">
|
||||
<view class="meta-label">班级:</view>
|
||||
<view class="meta-value">{{ student.class }}</view>
|
||||
</view>
|
||||
<view class="meta-item">
|
||||
<view class="meta-label">会员:</view>
|
||||
<view class="meta-value" :class="{ 'member': student.isMember }">{{ student.isMember ? '是' : '否' }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="student-stats">
|
||||
<view class="stat-item">
|
||||
<view class="stat-label">正确率:</view>
|
||||
<view class="stat-value">{{ student.correctRate }}</view>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<view class="stat-label">模考分:</view>
|
||||
<view class="stat-value">{{ student.examScore }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="student-arrow">
|
||||
→
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="load-more" @click="loadMoreStudents">
|
||||
加载更多
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue"
|
||||
|
||||
// 筛选条件
|
||||
const dateOptions = ['近7天', '近30天', '近90天', '今年', '全部']
|
||||
const dateIndex = ref(0)
|
||||
|
||||
const classOptions = ['全部班级', '一班', '二班', '三班', '四班']
|
||||
const classIndex = ref(0)
|
||||
|
||||
// 核心指标
|
||||
const metrics = ref({
|
||||
totalStudents: 1234,
|
||||
memberStudents: 890,
|
||||
avgCorrectRate: '78.5%',
|
||||
avgExamScore: '82.3'
|
||||
})
|
||||
|
||||
// 学习趋势数据
|
||||
const trendData = ref([
|
||||
{ date: '1/20', hours: 2.5, value: 30 },
|
||||
{ date: '1/21', hours: 3.2, value: 40 },
|
||||
{ date: '1/22', hours: 2.8, value: 35 },
|
||||
{ date: '1/23', hours: 4.5, value: 60 },
|
||||
{ date: '1/24', hours: 3.8, value: 50 },
|
||||
{ date: '1/25', hours: 4.2, value: 55 },
|
||||
{ date: '1/26', hours: 3.5, value: 45 }
|
||||
])
|
||||
|
||||
// 学习情况分布
|
||||
const learningDistribution = ref([
|
||||
{
|
||||
level: '优秀',
|
||||
icon: '🏆',
|
||||
count: 345,
|
||||
percentage: '28.0%'
|
||||
},
|
||||
{
|
||||
level: '良好',
|
||||
icon: '📈',
|
||||
count: 456,
|
||||
percentage: '37.0%'
|
||||
},
|
||||
{
|
||||
level: '一般',
|
||||
icon: '📊',
|
||||
count: 321,
|
||||
percentage: '26.0%'
|
||||
},
|
||||
{
|
||||
level: '待提高',
|
||||
icon: '⚠️',
|
||||
count: 112,
|
||||
percentage: '9.0%'
|
||||
}
|
||||
])
|
||||
|
||||
// 学员列表
|
||||
const studentList = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: '张**',
|
||||
class: '一班',
|
||||
isMember: true,
|
||||
correctRate: '92.5%',
|
||||
examScore: '95'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '李**',
|
||||
class: '二班',
|
||||
isMember: true,
|
||||
correctRate: '88.2%',
|
||||
examScore: '90'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '王**',
|
||||
class: '一班',
|
||||
isMember: false,
|
||||
correctRate: '76.8%',
|
||||
examScore: '82'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '刘**',
|
||||
class: '三班',
|
||||
isMember: true,
|
||||
correctRate: '94.3%',
|
||||
examScore: '97'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: '陈**',
|
||||
class: '二班',
|
||||
isMember: false,
|
||||
correctRate: '72.1%',
|
||||
examScore: '78'
|
||||
}
|
||||
])
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}
|
||||
|
||||
// 导出数据
|
||||
function exportData() {
|
||||
uni.showModal({
|
||||
title: '导出数据',
|
||||
content: '是否导出当前统计数据?',
|
||||
success: function(res) {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: '导出中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '导出成功', icon: 'success' })
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 日期变化
|
||||
function onDateChange(e) {
|
||||
const value = e.detail.value
|
||||
dateIndex.value = value
|
||||
// 实际项目中应根据选择的日期重新获取数据
|
||||
refreshData()
|
||||
}
|
||||
|
||||
// 班级变化
|
||||
function onClassChange(e) {
|
||||
const value = e.detail.value
|
||||
classIndex.value = value
|
||||
// 实际项目中应根据选择的班级重新获取数据
|
||||
refreshData()
|
||||
}
|
||||
|
||||
// 刷新数据
|
||||
function refreshData() {
|
||||
uni.showLoading({ title: '加载中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
// 实际项目中应从接口获取数据
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
// 查看学员详情
|
||||
function viewStudentDetail(studentId) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/student/detail?id=${studentId}`
|
||||
})
|
||||
}
|
||||
|
||||
// 加载更多学员
|
||||
function loadMoreStudents() {
|
||||
uni.showLoading({ title: '加载中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
// 实际项目中应从接口获取更多学员数据
|
||||
uni.showToast({ title: '已加载全部数据', icon: 'none' })
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 实际项目中应从接口获取数据
|
||||
// loadStatsData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* #ifndef APP-NVUE */
|
||||
page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
view {
|
||||
font-size: 14px;
|
||||
line-height: inherit;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.student-stats-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 120rpx;
|
||||
background-color: #fff;
|
||||
padding: 0 32rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 40rpx;
|
||||
color: #303133;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 60rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.export-btn {
|
||||
font-size: 28rpx;
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 筛选条件 */
|
||||
.filter-section {
|
||||
padding: 24rpx 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.filter-control {
|
||||
background-color: #f9f9f9;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.picker {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
/* 核心指标 */
|
||||
.metrics-section {
|
||||
padding: 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 0 16rpx 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.metrics-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.metric-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.metric-icon {
|
||||
font-size: 48rpx;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
|
||||
.metric-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.metric-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
/* 图表区域 */
|
||||
.chart-section {
|
||||
padding: 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 0 16rpx 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 24rpx;
|
||||
padding-left: 12rpx;
|
||||
border-left: 8rpx solid #409eff;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.mock-chart {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.chart-header {
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.chart-bars {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
height: 300rpx;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.chart-bar {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.bar-container {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.bar {
|
||||
width: 40rpx;
|
||||
background-color: #409eff;
|
||||
border-radius: 4rpx 4rpx 0 0;
|
||||
transition: height 0.5s ease;
|
||||
}
|
||||
|
||||
.bar-label {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.bar-value {
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
/* 学习情况分布 */
|
||||
.distribution-section {
|
||||
padding: 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 0 16rpx 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.distribution-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.distribution-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.distribution-icon {
|
||||
font-size: 48rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.distribution-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.distribution-title {
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.distribution-stats {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
/* 学员列表 */
|
||||
.student-section {
|
||||
padding: 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 0 16rpx 32rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.student-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.student-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.student-item:active {
|
||||
transform: translateY(2rpx);
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.student-info {
|
||||
flex: 1;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
|
||||
.student-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.student-meta {
|
||||
display: flex;
|
||||
gap: 32rpx;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.meta-label {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.meta-value {
|
||||
font-size: 20rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.meta-value.member {
|
||||
color: #67c23a;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.student-stats {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
|
||||
.student-arrow {
|
||||
font-size: 32rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.load-more {
|
||||
text-align: center;
|
||||
padding: 24rpx;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
font-size: 24rpx;
|
||||
color: #409eff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media screen and (min-width: 500px) {
|
||||
.student-stats-container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.metrics-grid {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
.chart-bars {
|
||||
height: 400rpx;
|
||||
}
|
||||
|
||||
.bar {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
.student-item {
|
||||
padding: 32rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
475
src/pages/student/analysis.vue
Normal file
475
src/pages/student/analysis.vue
Normal file
@@ -0,0 +1,475 @@
|
||||
<template>
|
||||
<view class="learning-analysis-container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<view class="back-icon">←</view>
|
||||
</view>
|
||||
<view class="header-title">学情统计分析</view>
|
||||
<view class="header-right" @click="exportAnalysis">
|
||||
<view class="export-btn">导出</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选条件 -->
|
||||
<view class="filter-section">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">时间范围</view>
|
||||
<view class="filter-control">
|
||||
<picker
|
||||
:range="timeRangeOptions"
|
||||
:value="timeRangeIndex"
|
||||
@change="onTimeRangeChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ timeRangeOptions[timeRangeIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">学员类型</view>
|
||||
<view class="filter-control">
|
||||
<picker
|
||||
:range="studentTypeOptions"
|
||||
:value="studentTypeIndex"
|
||||
@change="onStudentTypeChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ studentTypeOptions[studentTypeIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 核心数据 -->
|
||||
<view class="core-data-section">
|
||||
<view class="section-title">核心数据</view>
|
||||
<view class="data-card">
|
||||
<view class="data-row">
|
||||
<view class="data-item">
|
||||
<view class="data-value">{{ totalStudents }}</view>
|
||||
<view class="data-label">总学员数</view>
|
||||
</view>
|
||||
<view class="data-item">
|
||||
<view class="data-value">{{ activeStudents }}</view>
|
||||
<view class="data-label">活跃学员</view>
|
||||
</view>
|
||||
<view class="data-item">
|
||||
<view class="data-value">{{ memberStudents }}</view>
|
||||
<view class="data-label">会员学员</view>
|
||||
</view>
|
||||
<view class="data-item">
|
||||
<view class="data-value">{{ avgStudyTime }}</view>
|
||||
<view class="data-label">平均学习时长</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 学习趋势 -->
|
||||
<view class="trend-section">
|
||||
<view class="section-title">学习趋势</view>
|
||||
<view class="trend-card">
|
||||
<view class="chart-placeholder">
|
||||
<view class="chart-rect"></view>
|
||||
<view class="chart-text">学习时长趋势图</view>
|
||||
</view>
|
||||
<view class="trend-hint">
|
||||
近7天学员学习时长变化趋势
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 学习分布 -->
|
||||
<view class="distribution-section">
|
||||
<view class="section-title">学习分布</view>
|
||||
<view class="distribution-card">
|
||||
<view class="chart-placeholder">
|
||||
<view class="chart-rect"></view>
|
||||
<view class="chart-text">学习分布饼图</view>
|
||||
</view>
|
||||
<view class="distribution-hint">
|
||||
学员学习科目分布
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 学习排行 -->
|
||||
<view class="ranking-section">
|
||||
<view class="section-title">学习排行</view>
|
||||
<view class="ranking-card">
|
||||
<view class="ranking-header">
|
||||
<view class="header-item">排名</view>
|
||||
<view class="header-item">学员姓名</view>
|
||||
<view class="header-item">学习时长</view>
|
||||
<view class="header-item">完成题目</view>
|
||||
</view>
|
||||
<view class="ranking-list">
|
||||
<view
|
||||
v-for="(item, index) in rankingList"
|
||||
:key="index"
|
||||
class="ranking-item"
|
||||
>
|
||||
<view class="ranking-cell rank">{{ index + 1 }}</view>
|
||||
<view class="ranking-cell name">{{ item.name }}</view>
|
||||
<view class="ranking-cell time">{{ item.studyTime }}</view>
|
||||
<view class="ranking-cell count">{{ item.completedCount }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue"
|
||||
|
||||
// 筛选条件
|
||||
const timeRangeOptions = ['最近7天', '最近30天', '最近90天', '本学期', '自定义']
|
||||
const timeRangeIndex = ref(0)
|
||||
|
||||
const studentTypeOptions = ['全部学员', '普通学员', '会员学员', '分销员学员']
|
||||
const studentTypeIndex = ref(0)
|
||||
|
||||
// 核心数据
|
||||
const totalStudents = ref(1200)
|
||||
const activeStudents = ref(850)
|
||||
const memberStudents = ref(450)
|
||||
const avgStudyTime = ref('2.5小时')
|
||||
|
||||
// 学习排行
|
||||
const rankingList = ref([
|
||||
{
|
||||
name: '张三',
|
||||
studyTime: '15.5小时',
|
||||
completedCount: 120
|
||||
},
|
||||
{
|
||||
name: '李四',
|
||||
studyTime: '12.3小时',
|
||||
completedCount: 98
|
||||
},
|
||||
{
|
||||
name: '王五',
|
||||
studyTime: '10.8小时',
|
||||
completedCount: 85
|
||||
},
|
||||
{
|
||||
name: '赵六',
|
||||
studyTime: '9.2小时',
|
||||
completedCount: 75
|
||||
},
|
||||
{
|
||||
name: '钱七',
|
||||
studyTime: '8.5小时',
|
||||
completedCount: 68
|
||||
}
|
||||
])
|
||||
|
||||
onMounted(() => {
|
||||
// 实际项目中应从接口获取学情数据
|
||||
// loadLearningData()
|
||||
})
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}
|
||||
|
||||
// 导出分析
|
||||
function exportAnalysis() {
|
||||
uni.showModal({
|
||||
title: '导出分析报告',
|
||||
content: '确定要导出当前分析报告吗?',
|
||||
success: function(res) {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: '导出中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '导出成功',
|
||||
icon: 'success'
|
||||
})
|
||||
// 实际项目中应调用接口导出分析报告
|
||||
// exportAnalysisReport()
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 时间范围变更
|
||||
function onTimeRangeChange(e) {
|
||||
const value = e.detail.value
|
||||
timeRangeIndex.value = value
|
||||
// 实际项目中应根据时间范围筛选数据
|
||||
// filterLearningData()
|
||||
}
|
||||
|
||||
// 学员类型变更
|
||||
function onStudentTypeChange(e) {
|
||||
const value = e.detail.value
|
||||
studentTypeIndex.value = value
|
||||
// 实际项目中应根据学员类型筛选数据
|
||||
// filterLearningData()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* #ifndef APP-NVUE */
|
||||
page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
view {
|
||||
font-size: 14px;
|
||||
line-height: inherit;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.learning-analysis-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 120rpx;
|
||||
background-color: #fff;
|
||||
padding: 0 32rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 40rpx;
|
||||
color: #303133;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 60rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.export-btn {
|
||||
font-size: 24rpx;
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 通用部分样式 */
|
||||
.filter-section,
|
||||
.core-data-section,
|
||||
.trend-section,
|
||||
.distribution-section,
|
||||
.ranking-section {
|
||||
padding: 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 0 16rpx 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 24rpx;
|
||||
padding-left: 12rpx;
|
||||
border-left: 8rpx solid #409eff;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* 筛选条件 */
|
||||
.filter-row {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.filter-control {
|
||||
background-color: #f9f9f9;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.picker {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
/* 核心数据 */
|
||||
.data-card {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.data-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.data-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.data-value {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.data-label {
|
||||
font-size: 18rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
/* 学习趋势 */
|
||||
.trend-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.chart-placeholder {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 48rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.chart-rect {
|
||||
width: 100%;
|
||||
height: 300rpx;
|
||||
background-color: #e0e0e0;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.chart-text {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.trend-hint {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 学习分布 */
|
||||
.distribution-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.distribution-hint {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 学习排行 */
|
||||
.ranking-card {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.ranking-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16rpx;
|
||||
padding-bottom: 16rpx;
|
||||
border-bottom: 1rpx solid #e0e0e0;
|
||||
}
|
||||
|
||||
.header-item {
|
||||
flex: 1;
|
||||
font-size: 20rpx;
|
||||
font-weight: bold;
|
||||
color: #606266;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ranking-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.ranking-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.ranking-cell {
|
||||
flex: 1;
|
||||
font-size: 20rpx;
|
||||
color: #303133;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ranking-cell.rank {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.ranking-cell.time {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.ranking-cell.count {
|
||||
color: #67c23a;
|
||||
}
|
||||
</style>
|
||||
642
src/pages/student/detail.vue
Normal file
642
src/pages/student/detail.vue
Normal file
@@ -0,0 +1,642 @@
|
||||
<template>
|
||||
<view class="student-detail-container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<view class="back-icon">←</view>
|
||||
</view>
|
||||
<view class="header-title">学员详情</view>
|
||||
<view class="header-right">
|
||||
<view class="action-btn" @click="editStudent">编辑</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 学员基本信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">基本信息</view>
|
||||
<view class="info-card">
|
||||
<view class="info-row">
|
||||
<view class="info-item">
|
||||
<view class="info-label">学员姓名</view>
|
||||
<view class="info-value">{{ studentInfo.name }}</view>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="info-label">性别</view>
|
||||
<view class="info-value">{{ studentInfo.gender }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<view class="info-item">
|
||||
<view class="info-label">手机号码</view>
|
||||
<view class="info-value">{{ studentInfo.phone }}</view>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="info-label">年龄</view>
|
||||
<view class="info-value">{{ studentInfo.age }}岁</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<view class="info-item">
|
||||
<view class="info-label">所在班级</view>
|
||||
<view class="info-value">{{ studentInfo.class }}</view>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="info-label">会员状态</view>
|
||||
<view class="info-value" :class="{ 'member': studentInfo.isMember }">{{ studentInfo.isMember ? '会员' : '非会员' }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<view class="info-item full-width">
|
||||
<view class="info-label">注册时间</view>
|
||||
<view class="info-value">{{ studentInfo.registerTime }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 学习情况 -->
|
||||
<view class="learning-section">
|
||||
<view class="section-title">学习情况</view>
|
||||
<view class="learning-card">
|
||||
<view class="learning-metrics">
|
||||
<view class="metric-item">
|
||||
<view class="metric-icon">📚</view>
|
||||
<view class="metric-content">
|
||||
<view class="metric-value">{{ learningStats.totalQuestions }}</view>
|
||||
<view class="metric-label">总练习题数</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="metric-item">
|
||||
<view class="metric-icon">🎯</view>
|
||||
<view class="metric-content">
|
||||
<view class="metric-value">{{ learningStats.correctRate }}</view>
|
||||
<view class="metric-label">正确率</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="metric-item">
|
||||
<view class="metric-icon">✏️</view>
|
||||
<view class="metric-content">
|
||||
<view class="metric-value">{{ learningStats.totalExams }}</view>
|
||||
<view class="metric-label">总模考次数</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="metric-item">
|
||||
<view class="metric-icon">🏆</view>
|
||||
<view class="metric-content">
|
||||
<view class="metric-value">{{ learningStats.avgScore }}</view>
|
||||
<view class="metric-label">平均模考分</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 学习进度 -->
|
||||
<view class="progress-section">
|
||||
<view class="progress-title">学习进度</view>
|
||||
<view class="progress-items">
|
||||
<view
|
||||
v-for="(progress, index) in learningProgress"
|
||||
:key="index"
|
||||
class="progress-item"
|
||||
>
|
||||
<view class="progress-header">
|
||||
<view class="progress-subject">{{ progress.subject }}</view>
|
||||
<view class="progress-percentage">{{ progress.percentage }}</view>
|
||||
</view>
|
||||
<view class="progress-bar">
|
||||
<view
|
||||
class="progress-fill"
|
||||
:style="{ width: progress.percentage }"
|
||||
></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 最近学习记录 -->
|
||||
<view class="record-section">
|
||||
<view class="section-title">最近学习记录</view>
|
||||
<view class="record-list">
|
||||
<view
|
||||
v-for="(record, index) in recentRecords"
|
||||
:key="index"
|
||||
class="record-item"
|
||||
>
|
||||
<view class="record-time">{{ record.time }}</view>
|
||||
<view class="record-content">
|
||||
<view class="record-type">{{ record.type }}</view>
|
||||
<view class="record-detail">{{ record.detail }}</view>
|
||||
</view>
|
||||
<view class="record-status" :class="record.statusClass">{{ record.status }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="view-all-btn" @click="viewAllRecords">
|
||||
查看全部记录
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="action-section">
|
||||
<view class="action-grid">
|
||||
<view class="action-item" @click="sendMessage">
|
||||
<view class="action-icon">💬</view>
|
||||
<view class="action-label">发送消息</view>
|
||||
</view>
|
||||
<view class="action-item" @click="callStudent">
|
||||
<view class="action-icon">📞</view>
|
||||
<view class="action-label">拨打电话</view>
|
||||
</view>
|
||||
<view class="action-item" @click="viewExamRecords">
|
||||
<view class="action-icon">📝</view>
|
||||
<view class="action-label">模考记录</view>
|
||||
</view>
|
||||
<view class="action-item" @click="viewErrorSets">
|
||||
<view class="action-icon">❌</view>
|
||||
<view class="action-label">错题本</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue"
|
||||
|
||||
// 学员基本信息
|
||||
const studentInfo = ref({
|
||||
name: '张**',
|
||||
gender: '男',
|
||||
phone: '138****1234',
|
||||
age: 25,
|
||||
class: '一班',
|
||||
isMember: true,
|
||||
registerTime: '2023-10-01 10:00:00'
|
||||
})
|
||||
|
||||
// 学习统计
|
||||
const learningStats = ref({
|
||||
totalQuestions: 1256,
|
||||
correctRate: '88.5%',
|
||||
totalExams: 24,
|
||||
avgScore: '92.3'
|
||||
})
|
||||
|
||||
// 学习进度
|
||||
const learningProgress = ref([
|
||||
{
|
||||
subject: '科目一',
|
||||
percentage: '95%'
|
||||
},
|
||||
{
|
||||
subject: '科目二',
|
||||
percentage: '80%'
|
||||
},
|
||||
{
|
||||
subject: '科目三',
|
||||
percentage: '65%'
|
||||
},
|
||||
{
|
||||
subject: '科目四',
|
||||
percentage: '75%'
|
||||
}
|
||||
])
|
||||
|
||||
// 最近学习记录
|
||||
const recentRecords = ref([
|
||||
{
|
||||
time: '2024-01-26 14:30:00',
|
||||
type: '模考',
|
||||
detail: '科目一模拟考试',
|
||||
status: '通过',
|
||||
statusClass: 'status-pass'
|
||||
},
|
||||
{
|
||||
time: '2024-01-26 10:15:00',
|
||||
type: '练习',
|
||||
detail: '科目一专项练习-交通标志',
|
||||
status: '完成',
|
||||
statusClass: 'status-complete'
|
||||
},
|
||||
{
|
||||
time: '2024-01-25 16:45:00',
|
||||
type: '模考',
|
||||
detail: '科目一模拟考试',
|
||||
status: '通过',
|
||||
statusClass: 'status-pass'
|
||||
},
|
||||
{
|
||||
time: '2024-01-25 09:30:00',
|
||||
type: '练习',
|
||||
detail: '科目一专项练习-安全驾驶',
|
||||
status: '完成',
|
||||
statusClass: 'status-complete'
|
||||
}
|
||||
])
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}
|
||||
|
||||
// 编辑学员信息
|
||||
function editStudent() {
|
||||
uni.showToast({
|
||||
title: '编辑功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
function sendMessage() {
|
||||
uni.showModal({
|
||||
title: '发送消息',
|
||||
content: '确认发送消息给学员?',
|
||||
success: function(res) {
|
||||
if (res.confirm) {
|
||||
uni.showToast({
|
||||
title: '消息发送成功',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 拨打电话
|
||||
function callStudent() {
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: '13800138000', // 实际项目中应使用真实手机号
|
||||
success: function() {
|
||||
console.log('拨打电话成功')
|
||||
},
|
||||
fail: function() {
|
||||
console.log('拨打电话失败')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 查看模考记录
|
||||
function viewExamRecords() {
|
||||
uni.navigateTo({
|
||||
url: `/pages/student/exam-records?id=${1}` // 实际项目中应传入学员ID
|
||||
})
|
||||
}
|
||||
|
||||
// 查看错题本
|
||||
function viewErrorSets() {
|
||||
uni.navigateTo({
|
||||
url: `/pages/student/error-sets?id=${1}` // 实际项目中应传入学员ID
|
||||
})
|
||||
}
|
||||
|
||||
// 查看全部记录
|
||||
function viewAllRecords() {
|
||||
uni.navigateTo({
|
||||
url: `/pages/student/all-records?id=${1}` // 实际项目中应传入学员ID
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 实际项目中应从接口获取学员详情数据
|
||||
// const studentId = getCurrentPages().pop().options.id
|
||||
// loadStudentDetail(studentId)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* #ifndef APP-NVUE */
|
||||
page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
view {
|
||||
font-size: 14px;
|
||||
line-height: inherit;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.student-detail-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 120rpx;
|
||||
background-color: #fff;
|
||||
padding: 0 32rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 40rpx;
|
||||
color: #303133;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 60rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
font-size: 28rpx;
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 通用部分样式 */
|
||||
.info-section,
|
||||
.learning-section,
|
||||
.record-section {
|
||||
padding: 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 0 16rpx 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 24rpx;
|
||||
padding-left: 12rpx;
|
||||
border-left: 8rpx solid #409eff;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* 学员基本信息 */
|
||||
.info-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
gap: 32rpx;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.info-item.full-width {
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
/* 学习情况 */
|
||||
.learning-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.learning-metrics {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.metric-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.metric-icon {
|
||||
font-size: 48rpx;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
|
||||
.metric-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.metric-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
/* 学习进度 */
|
||||
.progress-section {
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.progress-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.progress-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.progress-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.progress-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.progress-subject {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.progress-percentage {
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 12rpx;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 6rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background-color: #409eff;
|
||||
border-radius: 6rpx;
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
/* 最近学习记录 */
|
||||
.record-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.record-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.record-time {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
margin-right: 24rpx;
|
||||
width: 160rpx;
|
||||
}
|
||||
|
||||
.record-content {
|
||||
flex: 1;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
|
||||
.record-type {
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.record-detail {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.record-status {
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.status-pass {
|
||||
color: #67c23a;
|
||||
background-color: rgba(103, 194, 58, 0.1);
|
||||
}
|
||||
|
||||
.status-complete {
|
||||
color: #409eff;
|
||||
background-color: rgba(64, 158, 255, 0.1);
|
||||
}
|
||||
|
||||
.view-all-btn {
|
||||
text-align: center;
|
||||
padding: 16rpx;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
font-size: 24rpx;
|
||||
color: #409eff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 操作按钮 */
|
||||
.action-section {
|
||||
padding: 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 0 16rpx 32rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.action-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.action-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 32rpx 16rpx;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.action-item:active {
|
||||
transform: translateY(2rpx);
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
font-size: 48rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.action-label {
|
||||
font-size: 20rpx;
|
||||
color: #303133;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media screen and (min-width: 500px) {
|
||||
.student-detail-container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.learning-metrics {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
.info-row {
|
||||
gap: 48rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
511
src/pages/student/list.vue
Normal file
511
src/pages/student/list.vue
Normal file
@@ -0,0 +1,511 @@
|
||||
<template>
|
||||
<view class="student-list-container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<view class="back-icon">←</view>
|
||||
</view>
|
||||
<view class="header-title">学员列表管理</view>
|
||||
<view class="header-right" @click="addStudent">
|
||||
<view class="add-btn">添加</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选条件 -->
|
||||
<view class="filter-section">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">状态</view>
|
||||
<view class="filter-control">
|
||||
<picker
|
||||
:range="statusOptions"
|
||||
:value="statusIndex"
|
||||
@change="onStatusChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ statusOptions[statusIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<view class="filter-label">搜索</view>
|
||||
<view class="filter-control">
|
||||
<input
|
||||
v-model="searchKeyword"
|
||||
class="search-input"
|
||||
placeholder="请输入姓名或手机号"
|
||||
@input="onSearch"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 学员列表 -->
|
||||
<view class="student-list-section">
|
||||
<view class="section-title">学员列表</view>
|
||||
<view class="student-list">
|
||||
<view
|
||||
v-for="(student, index) in studentList"
|
||||
:key="index"
|
||||
class="student-item"
|
||||
@click="goToStudentDetail(student.id)"
|
||||
>
|
||||
<view class="student-avatar">
|
||||
<view class="avatar-placeholder">{{ getInitials(student.name) }}</view>
|
||||
</view>
|
||||
<view class="student-info">
|
||||
<view class="student-name">{{ student.name }}</view>
|
||||
<view class="student-meta">
|
||||
<view class="meta-item">{{ student.phone }}</view>
|
||||
<view class="meta-item">{{ student.school }}</view>
|
||||
<view class="meta-item status-{{ student.statusClass }}">{{ student.status }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="student-actions">
|
||||
<view class="action-btn edit-btn" @click.stop="editStudent(student.id)">
|
||||
编辑
|
||||
</view>
|
||||
<view class="action-btn delete-btn" @click.stop="deleteStudent(student.id)">
|
||||
删除
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="studentList.length === 0" class="empty-state">
|
||||
<view class="empty-icon">🎓</view>
|
||||
<view class="empty-text">暂无学员数据</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页 -->
|
||||
<view v-if="studentList.length > 0" class="pagination">
|
||||
<view class="page-info">
|
||||
共 {{ totalStudents }} 条,当前第 {{ currentPage }} 页
|
||||
</view>
|
||||
<view class="page-controls">
|
||||
<view
|
||||
class="page-btn"
|
||||
:class="{ disabled: currentPage === 1 }"
|
||||
@click="goToPage(currentPage - 1)"
|
||||
>
|
||||
上一页
|
||||
</view>
|
||||
<view
|
||||
class="page-btn"
|
||||
:class="{ disabled: currentPage === totalPages }"
|
||||
@click="goToPage(currentPage + 1)"
|
||||
>
|
||||
下一页
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from "vue"
|
||||
|
||||
// 筛选条件
|
||||
const statusOptions = ['全部状态', '正常', '禁用']
|
||||
const statusIndex = ref(0)
|
||||
const searchKeyword = ref('')
|
||||
|
||||
// 学员列表数据
|
||||
const studentList = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: '张三',
|
||||
phone: '13800138001',
|
||||
school: '北京大学',
|
||||
status: '正常',
|
||||
statusClass: 'active'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '李四',
|
||||
phone: '13800138002',
|
||||
school: '清华大学',
|
||||
status: '正常',
|
||||
statusClass: 'active'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '王五',
|
||||
phone: '13800138003',
|
||||
school: '复旦大学',
|
||||
status: '禁用',
|
||||
statusClass: 'inactive'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '赵六',
|
||||
phone: '13800138004',
|
||||
school: '上海交通大学',
|
||||
status: '正常',
|
||||
statusClass: 'active'
|
||||
}
|
||||
])
|
||||
|
||||
// 分页信息
|
||||
const currentPage = ref(1)
|
||||
const totalStudents = ref(100)
|
||||
const pageSize = ref(10)
|
||||
|
||||
// 计算总页数
|
||||
const totalPages = computed(() => {
|
||||
return Math.ceil(totalStudents.value / pageSize.value)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// 实际项目中应从接口获取学员列表
|
||||
// loadStudentList()
|
||||
})
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}
|
||||
|
||||
// 添加学员
|
||||
function addStudent() {
|
||||
uni.showToast({
|
||||
title: '添加学员功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
// 跳转到学员详情
|
||||
function goToStudentDetail(id) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/student/detail?id=${id}`
|
||||
})
|
||||
}
|
||||
|
||||
// 编辑学员
|
||||
function editStudent(id) {
|
||||
uni.showToast({
|
||||
title: '编辑学员功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
// 删除学员
|
||||
function deleteStudent(id) {
|
||||
uni.showModal({
|
||||
title: '删除学员',
|
||||
content: '确定要删除该学员吗?',
|
||||
success: function(res) {
|
||||
if (res.confirm) {
|
||||
// 实际项目中应调用接口删除学员
|
||||
const index = studentList.value.findIndex(item => item.id === id)
|
||||
if (index !== -1) {
|
||||
studentList.value.splice(index, 1)
|
||||
}
|
||||
uni.showToast({
|
||||
title: '删除成功',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 状态变更
|
||||
function onStatusChange(e) {
|
||||
const value = e.detail.value
|
||||
statusIndex.value = value
|
||||
// 实际项目中应根据状态筛选学员
|
||||
// filterStudentList()
|
||||
}
|
||||
|
||||
// 搜索
|
||||
function onSearch() {
|
||||
// 实际项目中应根据关键词搜索学员
|
||||
// searchStudentList()
|
||||
}
|
||||
|
||||
// 跳转到指定页码
|
||||
function goToPage(page) {
|
||||
if (page < 1 || page > totalPages.value) {
|
||||
return
|
||||
}
|
||||
currentPage.value = page
|
||||
// 实际项目中应加载指定页码的学员
|
||||
// loadStudentPage(page)
|
||||
}
|
||||
|
||||
// 获取姓名首字母
|
||||
function getInitials(name) {
|
||||
return name.charAt(0).toUpperCase()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* #ifndef APP-NVUE */
|
||||
page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
view {
|
||||
font-size: 14px;
|
||||
line-height: inherit;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.student-list-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 120rpx;
|
||||
background-color: #fff;
|
||||
padding: 0 32rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 40rpx;
|
||||
color: #303133;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 60rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
font-size: 24rpx;
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 筛选条件 */
|
||||
.filter-section {
|
||||
padding: 24rpx 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.filter-control {
|
||||
background-color: #f9f9f9;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.picker {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 学员列表 */
|
||||
.student-list-section {
|
||||
flex: 1;
|
||||
padding: 0 16rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 24rpx;
|
||||
padding-left: 12rpx;
|
||||
border-left: 8rpx solid #409eff;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.student-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.student-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.student-avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
background-color: #409eff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
|
||||
.avatar-placeholder {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.student-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.student-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.student-meta {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.meta-item.status-active {
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.meta-item.status-inactive {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.student-actions {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 16rpx;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
background-color: #ecf5ff;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
background-color: #fef0f0;
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 分页 */
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #fff;
|
||||
margin: 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.page-info {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.page-controls {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 20rpx;
|
||||
color: #409eff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.page-btn.disabled {
|
||||
color: #909399;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
515
src/pages/student/remind.vue
Normal file
515
src/pages/student/remind.vue
Normal file
@@ -0,0 +1,515 @@
|
||||
<template>
|
||||
<view class="learning-remind-container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<view class="back-icon">←</view>
|
||||
</view>
|
||||
<view class="header-title">学习提醒</view>
|
||||
<view class="header-right" @click="sendBatchRemind">
|
||||
<view class="batch-btn">批量发送</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 提醒设置 -->
|
||||
<view class="remind-setting-section">
|
||||
<view class="section-title">提醒设置</view>
|
||||
<view class="setting-card">
|
||||
<view class="setting-row">
|
||||
<view class="setting-label">提醒类型</view>
|
||||
<view class="setting-control">
|
||||
<picker
|
||||
:range="remindTypeOptions"
|
||||
:value="remindTypeIndex"
|
||||
@change="onRemindTypeChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ remindTypeOptions[remindTypeIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="setting-row">
|
||||
<view class="setting-label">提醒内容</view>
|
||||
<view class="setting-control">
|
||||
<textarea
|
||||
v-model="remindContent"
|
||||
class="content-textarea"
|
||||
placeholder="请输入提醒内容"
|
||||
maxlength="200"
|
||||
></textarea>
|
||||
<view class="content-count">{{ remindContent.length }}/200</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="setting-row">
|
||||
<view class="setting-label">发送时间</view>
|
||||
<view class="setting-control">
|
||||
<input
|
||||
v-model="sendTime"
|
||||
class="time-input"
|
||||
placeholder="请选择发送时间"
|
||||
@click="showTimePicker"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 学员选择 -->
|
||||
<view class="student-select-section">
|
||||
<view class="section-title">学员选择</view>
|
||||
<view class="select-card">
|
||||
<view class="select-row">
|
||||
<view class="select-label">选择方式</view>
|
||||
<view class="select-control">
|
||||
<picker
|
||||
:range="selectTypeOptions"
|
||||
:value="selectTypeIndex"
|
||||
@change="onSelectTypeChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text">{{ selectTypeOptions[selectTypeIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="select-row" v-if="selectTypeIndex === 1">
|
||||
<view class="select-label">选择学员</view>
|
||||
<view class="select-control">
|
||||
<textarea
|
||||
v-model="selectedStudents"
|
||||
class="students-textarea"
|
||||
placeholder="请输入学员ID,多个ID用逗号分隔"
|
||||
></textarea>
|
||||
</view>
|
||||
</view>
|
||||
<view class="select-row" v-if="selectTypeIndex === 2">
|
||||
<view class="select-label">上传文件</view>
|
||||
<view class="select-control">
|
||||
<view class="upload-btn" @click="uploadFile">
|
||||
选择文件
|
||||
</view>
|
||||
<view class="file-name" v-if="uploadedFile">{{ uploadedFile }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 发送预览 -->
|
||||
<view class="preview-section">
|
||||
<view class="section-title">发送预览</view>
|
||||
<view class="preview-card">
|
||||
<view class="preview-header">
|
||||
<view class="preview-title">提醒内容预览</view>
|
||||
</view>
|
||||
<view class="preview-content">
|
||||
{{ remindContent || '请输入提醒内容' }}
|
||||
</view>
|
||||
<view class="preview-footer">
|
||||
<view class="preview-info">
|
||||
<view class="info-item">提醒类型:{{ remindTypeOptions[remindTypeIndex] }}</view>
|
||||
<view class="info-item">发送时间:{{ sendTime || '立即发送' }}</view>
|
||||
<view class="info-item">接收人数:{{ estimatedCount }}人</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="action-section">
|
||||
<view class="action-btn primary-btn" @click="sendRemind">
|
||||
发送提醒
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue"
|
||||
|
||||
// 提醒类型选项
|
||||
const remindTypeOptions = ['学习提醒', '作业提醒', '考试提醒', '活动提醒', '其他提醒']
|
||||
const remindTypeIndex = ref(0)
|
||||
|
||||
// 提醒内容
|
||||
const remindContent = ref('')
|
||||
|
||||
// 发送时间
|
||||
const sendTime = ref('')
|
||||
|
||||
// 选择方式选项
|
||||
const selectTypeOptions = ['全部学员', '指定学员', '上传文件']
|
||||
const selectTypeIndex = ref(0)
|
||||
|
||||
// 选中的学员
|
||||
const selectedStudents = ref('')
|
||||
|
||||
// 上传的文件
|
||||
const uploadedFile = ref('')
|
||||
|
||||
// 估计接收人数
|
||||
const estimatedCount = ref(100)
|
||||
|
||||
onMounted(() => {
|
||||
// 实际项目中应从接口获取默认设置
|
||||
// loadDefaultSettings()
|
||||
})
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}
|
||||
|
||||
// 批量发送
|
||||
function sendBatchRemind() {
|
||||
uni.showModal({
|
||||
title: '批量发送提醒',
|
||||
content: '确定要向所有学员批量发送提醒吗?',
|
||||
success: function(res) {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: '发送中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '提醒发送成功',
|
||||
icon: 'success'
|
||||
})
|
||||
// 实际项目中应调用接口批量发送提醒
|
||||
// sendBatchRemindApi()
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 提醒类型变更
|
||||
function onRemindTypeChange(e) {
|
||||
const value = e.detail.value
|
||||
remindTypeIndex.value = value
|
||||
}
|
||||
|
||||
// 选择方式变更
|
||||
function onSelectTypeChange(e) {
|
||||
const value = e.detail.value
|
||||
selectTypeIndex.value = value
|
||||
// 根据选择方式更新估计接收人数
|
||||
if (value === 0) {
|
||||
estimatedCount.value = 100
|
||||
} else if (value === 1) {
|
||||
estimatedCount.value = 10
|
||||
} else {
|
||||
estimatedCount.value = 50
|
||||
}
|
||||
}
|
||||
|
||||
// 显示时间选择器
|
||||
function showTimePicker() {
|
||||
const now = new Date()
|
||||
uni.showDateTimePicker({
|
||||
year: now.getFullYear(),
|
||||
month: now.getMonth(),
|
||||
day: now.getDate(),
|
||||
hour: now.getHours(),
|
||||
minute: now.getMinutes(),
|
||||
success: function(res) {
|
||||
sendTime.value = `${res.year}-${res.month + 1}-${res.day} ${res.hour}:${res.minute}`
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 上传文件
|
||||
function uploadFile() {
|
||||
uni.chooseFile({
|
||||
count: 1,
|
||||
extension: ['.txt', '.csv', '.xlsx'],
|
||||
success: function(res) {
|
||||
uploadedFile.value = res.tempFiles[0].name
|
||||
uni.showToast({
|
||||
title: '文件选择成功',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 发送提醒
|
||||
function sendRemind() {
|
||||
if (!remindContent.value) {
|
||||
uni.showToast({
|
||||
title: '请输入提醒内容',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
uni.showModal({
|
||||
title: '发送提醒',
|
||||
content: `确定要发送提醒给${estimatedCount.value}名学员吗?`,
|
||||
success: function(res) {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: '发送中...' })
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '提醒发送成功',
|
||||
icon: 'success'
|
||||
})
|
||||
// 实际项目中应调用接口发送提醒
|
||||
// sendRemindApi()
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* #ifndef APP-NVUE */
|
||||
page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
view {
|
||||
font-size: 14px;
|
||||
line-height: inherit;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.learning-remind-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 120rpx;
|
||||
background-color: #fff;
|
||||
padding: 0 32rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 40rpx;
|
||||
color: #303133;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 100rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.batch-btn {
|
||||
font-size: 24rpx;
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 通用部分样式 */
|
||||
.remind-setting-section,
|
||||
.student-select-section,
|
||||
.preview-section {
|
||||
padding: 32rpx;
|
||||
background-color: #fff;
|
||||
margin: 0 16rpx 16rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 24rpx;
|
||||
padding-left: 12rpx;
|
||||
border-left: 8rpx solid #409eff;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* 提醒设置 */
|
||||
.setting-card {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.setting-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.setting-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.setting-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.setting-control {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.picker {
|
||||
background-color: #fff;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.content-textarea {
|
||||
width: 100%;
|
||||
height: 160rpx;
|
||||
background-color: #fff;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.content-count {
|
||||
position: absolute;
|
||||
bottom: 8rpx;
|
||||
right: 16rpx;
|
||||
font-size: 18rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.time-input {
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.students-textarea {
|
||||
width: 100%;
|
||||
height: 120rpx;
|
||||
background-color: #fff;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.upload-btn {
|
||||
background-color: #ecf5ff;
|
||||
color: #409eff;
|
||||
padding: 16rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
margin-top: 8rpx;
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
/* 发送预览 */
|
||||
.preview-card {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.preview-header {
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.preview-title {
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.preview-content {
|
||||
background-color: #fff;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 8rpx;
|
||||
padding: 24rpx;
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
min-height: 120rpx;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.preview-footer {
|
||||
border-top: 1rpx solid #e0e0e0;
|
||||
padding-top: 16rpx;
|
||||
}
|
||||
|
||||
.preview-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
font-size: 20rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
/* 操作按钮 */
|
||||
.action-section {
|
||||
padding: 32rpx;
|
||||
margin: 0 16rpx 16rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
width: 100%;
|
||||
padding: 24rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.primary-btn {
|
||||
background-color: #409eff;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
@@ -235,17 +235,6 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="feature-card" @click="goToAiRechargeException">
|
||||
<view class="feature-icon notice-icon">
|
||||
<view class="icon-text">🤖</view>
|
||||
<view v-if="exceptionCount.ai > 0" class="exception-badge">{{ exceptionCount.ai }}</view>
|
||||
</view>
|
||||
<view class="feature-info">
|
||||
<view class="feature-title">AI充值订单异常</view>
|
||||
<view class="feature-desc">处理AI充值订单失败等异常</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="feature-card" @click="goToMemberOrderException">
|
||||
<view class="feature-icon notice-icon">
|
||||
<view class="icon-text">🛒</view>
|
||||
@@ -268,7 +257,6 @@
|
||||
const userRole = ref('admin') // 模拟角色,实际应从登录状态获取
|
||||
const exceptionCount = ref({ // 模拟异常数量
|
||||
profit: 2, // 分润异常数量
|
||||
ai: 1, // AI充值订单异常数量
|
||||
member: 3 // 会员订单异常数量
|
||||
})
|
||||
|
||||
@@ -457,12 +445,6 @@
|
||||
})
|
||||
}
|
||||
|
||||
function goToAiRechargeException() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/exception/ai-recharge'
|
||||
})
|
||||
}
|
||||
|
||||
function goToMemberOrderException() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/exception/member-order'
|
||||
|
||||
Reference in New Issue
Block a user