页面初始化

This commit is contained in:
qsh
2026-01-29 18:29:34 +08:00
parent db42252b6c
commit 35f5621d7e
30 changed files with 13297 additions and 813 deletions

490
src/pages/account/log.vue Normal file
View 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>

View 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>

View 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
View 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>