This commit is contained in:
qsh
2026-02-02 15:58:11 +08:00
parent 1f3b7e6506
commit a5977aa41f
3 changed files with 388 additions and 211 deletions

47
src/api/member/index.js Normal file
View File

@@ -0,0 +1,47 @@
import request from '@/utils/request';
export const getVipTypeList = async params => {
return await request({
url: '/applet/xunjia/member/list',
method: 'get',
params: params
});
};
export const addVipType = async data => {
return await request({
url: '/applet/xunjia/member/add',
method: 'post',
data
});
};
export const updateVipType = async data => {
return await request({
url: '/applet/xunjia/member/update',
method: 'post',
data
});
};
export const deleteVipType = async id => {
return await request({
url: '/applet/xunjia/member/delete?memberId=' + id,
method: 'delete'
});
};
export const getVipTypeOptions = async params => {
return await request({
url: '/applet/xunjia/member/simple-list',
method: 'get',
params: params
});
};
export const getVipType = async id => {
return await request({
url: '/applet/xunjia/member/get?memberId=' + id,
method: 'get'
});
};

View File

@@ -12,44 +12,61 @@
<!-- 会员名称 -->
<view class="form-item">
<view class="label">会员名称</view>
<input type="text" v-model="formData.name" placeholder="请输入会员名称" class="input" />
<input type="text" v-model="formData.memberName" placeholder="请输入会员名称" class="input" />
</view>
<!-- 车型 -->
<view class="form-item">
<view class="label">车型</view>
<picker @change="bindCarTypeChange" :range="carTypes" :value="carTypeIndex" class="picker">
<view class="picker-text">{{ carTypes[carTypeIndex] || '请选择车型' }}</view>
</picker>
<radio-group @change="handleCarTypeChange">
<label
v-for="(carType, index) in carTypes"
:key="carType.value"
class="radio-item"
>
<radio :value="carType.value" :checked="formData.carTypeId == carType.value" />
<text class="radio-label">{{ carType.label }}</text>
</label>
</radio-group>
</view>
<!-- 科目 -->
<view class="form-item">
<view class="label">科目</view>
<picker @change="bindSubjectChange" :range="subjects" :value="subjectIndex" class="picker">
<view class="picker-text">{{ subjects[subjectIndex] || '请选择科目' }}</view>
</picker>
<checkbox-group @change="handleSubjectChange">
<label
v-for="(subject, index) in subjectOptions"
:key="subject.value"
class="checkbox-item"
>
<checkbox :value="subject.value" :checked="formData.subjectList.includes(subject.value)" />
<text class="checkbox-label">{{ subject.label }}</text>
</label>
</checkbox-group>
</view>
<!-- 原价 -->
<view class="form-item">
<view class="label">原价</view>
<input type="number" v-model="formData.originalPrice" placeholder="请输入原价" class="input" />
<input type="number" v-model="formData.price" placeholder="请输入原价" class="input" />
</view>
<!-- 折扣价 -->
<view class="form-item">
<view class="label">折扣价</view>
<input type="number" v-model="formData.discountPrice" placeholder="请输入折扣价" class="input" />
<input type="number" v-model="formData.discount" placeholder="请输入折扣价" class="input" />
</view>
<!-- 有效期 -->
<view class="form-item">
<view class="label">有效期</view>
<view class="有效期-input">
<input type="number" v-model="formData.validityPeriod" placeholder="请输入数字" class="validity-input" />
<picker @change="bindUnitChange" :range="units" :value="unitIndex" class="unit-picker">
<view class="unit-text">{{ units[unitIndex] }}</view>
<input type="number" v-model="formData.duration" placeholder="请输入数字" class="validity-input" />
<picker @change="bindUnitChange" :range="units" range-key="label" :value="unitIndex" class="unit-picker">
<view class="unit-text">
{{ units[unitIndex].label }}
<view class="dropdown-arrow"></view>
</view>
</picker>
</view>
</view>
@@ -57,16 +74,16 @@
<!-- 使用方式 -->
<view class="form-item">
<view class="label">使用方式</view>
<view class="use-type-container">
<view class="use-type-item" @click="toggleUseType('student')">
<view class="checkbox" :class="{ checked: formData.useTypes.includes('student') }"></view>
<view class="use-type-text">学员购买</view>
</view>
<view class="use-type-item" @click="toggleUseType('admin')">
<view class="checkbox" :class="{ checked: formData.useTypes.includes('admin') }"></view>
<view class="use-type-text">客服赠送</view>
</view>
</view>
<checkbox-group @change="handleUseTypeChange">
<label class="checkbox-item">
<checkbox value="1" :checked="formData.useTypes.includes('1')" />
<text class="checkbox-label">学员购买</text>
</label>
<label class="checkbox-item">
<checkbox value="2" :checked="formData.useTypes.includes('2')" />
<text class="checkbox-label">客服赠送</text>
</label>
</checkbox-group>
</view>
<!-- 包含权益 -->
@@ -75,12 +92,12 @@
<view class="rights-container">
<view v-if="loading" class="loading">加载中...</view>
<view v-else-if="rights.length === 0" class="no-rights">暂无权益</view>
<view v-else class="rights-list">
<view v-for="right in rights" :key="right.id" class="right-item">
<view class="checkbox" :class="{ checked: formData.rights.includes(right.id) }" @click="toggleRight(right.id)"></view>
<view class="right-text">{{ right.name }}</view>
</view>
</view>
<checkbox-group v-else @change="handleRightsChange" class="rights-list">
<label v-for="right in rights" :key="right.value" class="checkbox-item">
<checkbox :value="right.value" :checked="formData.memberBenefits.includes(right.value)" />
<text class="checkbox-label">{{ right.label }}</text>
</label>
</checkbox-group>
</view>
</view>
@@ -94,31 +111,40 @@
<script setup>
import { ref, onMounted } from 'vue'
import { getVipType, addVipType, updateVipType } from '@/api/member'
// 表单数据
const formData = ref({
id: '',
name: '',
carType: '',
subject: '',
originalPrice: '',
discountPrice: '',
validityPeriod: '',
unit: '天',
useTypes: [],
rights: []
memberId: '',
memberName: '',
carTypeId: '1001',
subjectList: ['1'],
price: '',
discount: '',
duration: '',
unit: 1,
useTypes: ['1'],
memberBenefits: ['JX', 'REAL_EXAM', 'SECRET', 'AI']
})
// 车型选项
const carTypes = ref(['C1', 'C2', 'A1', 'A2', 'B1', 'B2'])
const carTypes = ref([
{ value: '1001', label: '小车' },
{ value: '1002', label: '摩托车' }
])
const carTypeIndex = ref(0)
// 科目选项
const subjects = ref(['科目一', '科目二', '科目三', '科目四', '全科'])
const subjectIndex = ref(0)
const subjectOptions = ref([
{ value: '1', label: '科一' },
{ value: '4', label: '科四' }
])
// 单位选项
const units = ref(['天', '年'])
const units = ref([
{ value: '1', label: '天' },
{ value: '3', label: '年' }
])
const unitIndex = ref(0)
// 包含权益
@@ -140,10 +166,10 @@
const currentPage = pages[pages.length - 1]
const options = currentPage.options
if (options.id) {
if (options.memberId) {
// 编辑模式,获取会员详情
isEditMode.value = true
getMemberDetail(options.id)
getMemberDetail(options.memberId)
} else {
// 新增模式
isEditMode.value = false
@@ -152,67 +178,30 @@
// 获取会员详情
function getMemberDetail(id) {
// 实际项目中应调用接口获取会员详情
setTimeout(() => {
const mockData = {
id: id,
name: '基础会员',
carType: 'C1',
subject: '科目一',
originalPrice: 199,
discountPrice: 99,
validityPeriod: 30,
unit: '天',
useTypes: ['student'],
rights: [1, 4]
}
formData.value = {
id: mockData.id,
name: mockData.name,
carType: mockData.carType,
subject: mockData.subject,
originalPrice: mockData.originalPrice,
discountPrice: mockData.discountPrice,
validityPeriod: mockData.validityPeriod,
unit: mockData.unit,
useTypes: [...mockData.useTypes],
rights: [...mockData.rights]
}
getVipType(id).then(res => {
if (res.code == '0000') {
formData.value = res.data
formData.value.useTypes = res.data.useTypes.map(type => type.toString())
// 设置选择器索引
carTypeIndex.value = carTypes.value.indexOf(mockData.carType)
subjectIndex.value = subjects.value.indexOf(mockData.subject)
unitIndex.value = units.value.indexOf(mockData.unit)
}, 300)
carTypeIndex.value = carTypes.value.findIndex(type => type.value == res.data.carTypeId)
unitIndex.value = units.value.findIndex(unit => unit.value == res.data.unit)
}
})
}
// 获取权益列表
function getRightsList() {
loading.value = true
// 实际项目中应调用接口获取权益列表
setTimeout(() => {
rights.value = [
{ id: 1, name: '无限次模拟考试' },
{ id: 2, name: '视频课程免费看' },
{ id: 3, name: '一对一在线辅导' },
{ id: 4, name: '错题本' },
{ id: 5, name: '考试技巧指导' }
{ value: 'JX', label: '精选题库' },
{ value: 'REAL_EXAM', label: '真实考场' },
{ value: 'SECRET', label: '考前密卷' },
{ value: 'AI', label: 'AI助手' },
]
loading.value = false
}, 500)
}
// 车型选择
function bindCarTypeChange(e) {
carTypeIndex.value = e.detail.value
formData.value.carType = carTypes.value[e.detail.value]
}
// 科目选择
function bindSubjectChange(e) {
subjectIndex.value = e.detail.value
formData.value.subject = subjects.value[e.detail.value]
// 车型选择变化处理
function handleCarTypeChange(e) {
formData.value.carTypeId = e.detail.value
}
// 单位选择
@@ -221,42 +210,44 @@
formData.value.unit = units.value[e.detail.value]
}
// 切换使用方式
function toggleUseType(type) {
const index = formData.value.useTypes.indexOf(type)
if (index === -1) {
formData.value.useTypes.push(type)
} else {
formData.value.useTypes.splice(index, 1)
}
// 科目选择变化处理
function handleSubjectChange(e) {
formData.value.subjectList = e.detail.value
}
// 切换权益
function toggleRight(rightId) {
const index = formData.value.rights.indexOf(rightId)
if (index === -1) {
formData.value.rights.push(rightId)
} else {
formData.value.rights.splice(index, 1)
// 使用方式选择变化处理
function handleUseTypeChange(e) {
formData.value.useTypes = e.detail.value
}
// 权益选择变化处理
function handleRightsChange(e) {
formData.value.memberBenefits = e.detail.value
}
// 提交表单
function submitForm() {
// 实际项目中应调用接口提交表单
console.log('表单数据:', formData.value)
if (isEditMode.value) {
updateVipType(formData.value).then(res => {
if (res.code == '0000') {
uni.showToast({
title: '修改成功',
icon: 'success'
})
}
})
} else {
addVipType(formData.value).then(res => {
if (res.code == '0000') {
uni.showToast({
title: '新增成功',
icon: 'success'
})
}
})
}
// 返回上一页
setTimeout(() => {
@@ -377,46 +368,55 @@
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
background-color: #f9f9f9;
}
.unit-picker:hover {
background-color: #f0f0f0;
border-color: #007aff;
}
.unit-text {
font-size: 14px;
color: #333;
}
.use-type-container {
display: flex;
gap: 20px;
align-items: center;
gap: 6px;
}
.use-type-item {
.dropdown-arrow {
width: 0;
height: 0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid #666;
transition: transform 0.2s ease;
}
.unit-picker:hover .dropdown-arrow {
border-top-color: #007aff;
transform: translateY(1px);
}
/* Radio和Checkbox样式 */
radio-group,
checkbox-group {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.radio-item,
.checkbox-item {
display: flex;
align-items: center;
gap: 8px;
}
.checkbox {
width: 20px;
height: 20px;
border: 1px solid #e5e5e5;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
}
.checkbox.checked {
background-color: #007aff;
border-color: #007aff;
}
.checkbox.checked::after {
content: '✓';
color: #fff;
font-size: 12px;
}
.use-type-text {
.radio-label,
.checkbox-label {
font-size: 14px;
color: #333;
}
@@ -440,19 +440,6 @@
.rights-list {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.right-item {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
}
.right-text {
font-size: 14px;
color: #333;
}
.submit-btn {

View File

@@ -12,6 +12,24 @@
</view>
</view>
<!-- 车型筛选 -->
<view class="filter-container">
<view class="filter-row">
<!-- 车型平铺单选 -->
<view class="filter-options">
<view
v-for="(option, index) in carTypeOptions"
:key="option.value"
class="filter-option"
:class="{ 'active': searchForm.carTypeId === option.value }"
@click="selectCarType(option.value)"
>
{{ option.label }}
</view>
</view>
</view>
</view>
<!-- 列表视图 -->
<view class="list-container">
<view v-if="loading" class="loading">加载中...</view>
@@ -26,40 +44,41 @@
<view v-for="member in memberList" :key="member.id" class="member-item">
<view class="member-info">
<view class="flex justify-between" style="border-bottom: 1px solid #f0f0f0;">
<view class="member-name">{{ member.name }}</view>
<view class="member-name">{{ member.memberName }}</view>
<view class="member-actions">
<view class="action-btn edit-btn" @click="goToEdit(member)">
<text>编辑</text>
</view>
<view class="action-btn delete-btn" @click="deleteMember(member.id)">
<view class="action-btn delete-btn" @click="deleteMember(member.memberId)">
<text>删除</text>
</view>
</view>
</view>
<view class="member-details">
<view class="detail-row">
<view class="detail-item">车型{{ member.carType }}</view>
<view class="detail-item">科目{{ member.subject }}</view>
<view class="detail-item">车型{{ member.carName }}</view>
<view class="detail-item">科目{{ member.subjects }}</view>
</view>
<view class="detail-item price-item">
<span class="price-label">价格</span>
<span class="discount-price">¥{{ member.discountPrice }}</span>
<span class="original-price"> (原价¥{{ member.originalPrice }})</span>
<span class="discount-price">¥{{ member.discount }}</span>
<span class="original-price"> (原价¥{{ member.price }})</span>
</view>
<view class="detail-item">有效期{{ member.validityPeriod }}{{ member.unit }}</view>
<!-- 100 -->
<view class="detail-item">有效期{{ member.duration }}{{ unitLabelOptions[member.unit] }}</view>
<view class="detail-item">
<span class="detail-label">使用方式</span>
<view class="use-type-tags">
<span v-for="(type, index) in member.useTypes" :key="index" class="use-type-tag">
{{ type === 'student' ? '学员购买' : '客服赠送' }}
{{ type == 1 ? '用户购买' : '客服赠送' }}
</span>
</view>
</view>
<view class="detail-item">
<view class="detail-item" style="align-items: baseline;">
<span class="detail-label">包含权益</span>
<view class="rights-tags">
<span v-for="(right, index) in member.rights" :key="index" class="right-tag">
{{ right }}
<span v-for="(right, index) in member.memberBenefits" :key="index" class="right-tag">
{{ rights.find(item => item.value == right)?.label || right }}
</span>
</view>
</view>
@@ -72,50 +91,48 @@
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ref } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { getVipTypeList, deleteVipType } from '@/api/member'
// 会员列表
const memberList = ref([])
const loading = ref(false)
const searchForm = ref({
carTypeId: '1001',
pageNo: 1,
pageSize: 10
})
const unitLabelOptions = ['', '天', '月', '年']
// 筛选选项
const carTypeOptions = [
{ value: '1001', label: '小车' },
{ value: '1002', label: '摩托车' }
]
const rights = [
{ value: 'JX', label: '精选题库' },
{ value: 'REAL_EXAM', label: '真实考场' },
{ value: 'SECRET', label: '考前密卷' },
{ value: 'AI', label: 'AI助手' },
]
// 生命周期
onMounted(() => {
onShow(() => {
getMemberList()
})
// 获取会员列表
function getMemberList() {
loading.value = true
// 实际项目中应调用接口获取会员列表
setTimeout(() => {
memberList.value = [
{
id: 1,
name: '基础会员',
carType: 'C1',
subject: '科目一',
originalPrice: 199,
discountPrice: 99,
validityPeriod: 30,
unit: '天',
useTypes: ['student'],
rights: ['无限次模拟考试', '错题本']
},
{
id: 2,
name: '高级会员',
carType: 'C1',
subject: '全科',
originalPrice: 599,
discountPrice: 299,
validityPeriod: 1,
unit: '年',
useTypes: ['student', 'admin'],
rights: ['无限次模拟考试', '视频课程免费看', '一对一在线辅导', '错题本', '考试技巧指导']
}
]
getVipTypeList(searchForm.value).then(res => {
if (res.code == '0000') {
memberList.value = res.data.list || []
loading.value = false
}, 500)
}
})
}
// 跳转到新增页面
@@ -128,7 +145,7 @@
// 跳转到编辑页面
function goToEdit(member) {
uni.navigateTo({
url: `/pages/member/setting-form?id=${member.id}`
url: `/pages/member/setting-form?memberId=${member.memberId}`
})
}
@@ -139,12 +156,15 @@
content: '确定要删除该会员配置吗?',
success: (res) => {
if (res.confirm) {
// 实际项目中应调用接口删除会员
memberList.value = memberList.value.filter(item => item.id !== id)
deleteVipType(id).then(res => {
if (res.code == '0000') {
uni.showToast({
title: '删除成功',
icon: 'success'
})
getMemberList()
}
})
}
}
})
@@ -154,6 +174,14 @@
function goBack() {
uni.navigateBack()
}
// 选择车型(平铺单选)
function selectCarType(value) {
searchForm.value.carTypeId = value
getMemberList()
}
</script>
<style scoped>
@@ -172,6 +200,121 @@
border-bottom: 1px solid #e5e5e5;
}
/* 筛选区域样式 */
.filter-container {
background-color: #fff;
padding: 12px 16px;
border-bottom: 1px solid #e5e5e5;
}
.filter-row {
display: flex;
align-items: center;
gap: 12px;
justify-content: flex-start;
}
/* 车型Tabs样式 */
.filter-options {
display: flex;
align-items: center;
background-color: #f8f9fa;
border-radius: 8px;
padding: 4px;
gap: 2px;
}
.filter-option {
padding: 8px 16px;
background-color: transparent;
border: none;
border-radius: 6px;
font-size: 14px;
color: #666;
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
flex: 1;
text-align: center;
}
.filter-option:hover {
color: #007aff;
background-color: rgba(0, 122, 255, 0.05);
}
.filter-option.active {
background-color: #007aff;
color: #fff;
box-shadow: 0 2px 4px rgba(0, 122, 255, 0.2);
}
/* 筛选选择器样式 */
.filter-select {
flex: 1;
max-width: 180px;
position: relative;
}
.picker-text {
padding: 10px 16px;
background-color: #f8f9fa;
border-radius: 8px;
font-size: 14px;
color: #333;
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
border: 1px solid #e9ecef;
transition: all 0.2s ease;
}
.picker-text:hover {
border-color: #007aff;
box-shadow: 0 1px 3px rgba(0, 122, 255, 0.1);
}
.dropdown-icon {
font-size: 12px;
color: #666;
margin-left: 8px;
transition: transform 0.2s ease;
}
.picker-text:hover .dropdown-icon {
color: #007aff;
transform: translateY(1px);
}
/* 响应式调整 */
@media (max-width: 480px) {
.filter-row {
flex-direction: column;
align-items: stretch;
gap: 12px;
}
.filter-options {
justify-content: flex-start;
}
.filter-select {
flex: 1;
max-width: none;
}
.filter-option {
padding: 6px 12px;
font-size: 13px;
}
.picker-text {
padding: 8px 12px;
font-size: 13px;
}
}
.back-btn {
width: 44px;
height: 44px;