🎨 新增vip用户管理以及讲师包月服务管理功能

This commit is contained in:
2025-09-09 07:21:39 +08:00
parent 773f13104b
commit 3378e709cf
7 changed files with 717 additions and 8 deletions

View File

@@ -0,0 +1,279 @@
<template>
<div>
<div class="gva-search-box">
<el-form :inline="true" :model="searchInfo" class="demo-form-inline" @keyup.enter="onSubmit">
<el-form-item label="讲师">
<div style="display:flex;gap:8px;align-items:center">
<el-input v-model="searchInfo.teacher_name" placeholder="请选择讲师" clearable style="width: 220px" readonly />
<el-button type="primary" plain @click="openTeacherChoose('search')">选择讲师</el-button>
<el-button @click="clearTeacher('search')">清空</el-button>
</div>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="search" @click="onSubmit">查询</el-button>
<el-button icon="refresh" @click="onReset">重置</el-button>
</el-form-item>
</el-form>
</div>
<div class="gva-table-box">
<div class="gva-btn-list">
<el-button type="primary" icon="plus" @click="openEdit('create')">新增</el-button>
</div>
<el-table
ref="multipleTable"
style="width: 100%"
tooltip-effect="dark"
:data="tableData"
row-key="ID"
>
<el-table-column type="index" label="#" width="60" />
<el-table-column align="left" label="ID" prop="ID" width="100" />
<el-table-column align="left" label="标题" prop="title" min-width="160" />
<el-table-column align="left" label="讲师ID" prop="teacher_id" width="110" />
<el-table-column align="left" label="讲师" prop="teacher_name" min-width="140" />
<el-table-column align="left" label="头像" width="120">
<template #default="scope">
<el-image
v-if="scope.row.avatar"
style="width: 60px; height: 60px"
:src="scope.row.avatar"
:preview-src-list="[scope.row.avatar]"
fit="cover"
/>
<span v-else></span>
</template>
</el-table-column>
<el-table-column align="left" label="价格" prop="price" width="120">
<template #default="{ row }">
{{ formatPrice(row.price) }}
</template>
</el-table-column>
<el-table-column align="left" label="更新时间" prop="UpdatedAt" width="180">
<template #default="{ row }">{{ formatDate(row.UpdatedAt) }}</template>
</el-table-column>
<el-table-column align="left" label="描述" prop="desc" min-width="200" show-overflow-tooltip />
<el-table-column align="left" label="操作" fixed="right" width="160">
<template #default="{ row }">
<el-button type="primary" link @click="openEdit('update', row)">编辑</el-button>
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="gva-pagination">
<el-pagination
layout="total, sizes, prev, pager, next, jumper"
:current-page="page"
:page-size="pageSize"
:page-sizes="[10, 30, 50, 100]"
:total="total"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</div>
</div>
<!-- 选择讲师弹窗 -->
<userChoose ref="userChooseRef" title="选择讲师人员" @getRecipientInfo="onChooseTeacher" />
<!-- 新增/编辑 抽屉 -->
<el-drawer destroy-on-close :size="'520px'" v-model="dialogVisible" :show-close="false" :before-close="closeDialog">
<template #header>
<div class="flex justify-between items-center">
<span class="text-lg">{{ editType==='create'?'新增讲师包月':'编辑讲师包月' }}</span>
<div>
<el-button :loading="btnLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="closeDialog"> </el-button>
</div>
</div>
</template>
<el-form :model="formData" ref="formRef" label-position="top" :rules="rules">
<el-form-item label="标题" prop="title">
<el-input v-model="formData.title" placeholder="请输入标题" />
</el-form-item>
<el-form-item label="讲师" prop="teacher_id">
<div style="display:flex;gap:8px;align-items:center;width:100%">
<el-input v-model="formData.teacher_name" placeholder="请选择讲师" readonly />
<el-button type="primary" plain @click="openTeacherChoose('form')">选择讲师</el-button>
</div>
</el-form-item>
<el-form-item label="价格(元)" prop="price">
<el-input v-model.number="formData.price" type="number" min="0" placeholder="请输入价格" />
</el-form-item>
<el-form-item label="描述" prop="desc">
<el-input v-model="formData.desc" type="textarea" :rows="4" placeholder="请输入描述" />
</el-form-item>
</el-form>
</el-drawer>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { list as getTeacherVipList, add as createTeacherVip, edit as updateTeacherVip, del as deleteTeacherVip, detail as getDetail } from '@/api/goods/teacherVip'
import { ElMessageBox, ElMessage } from 'element-plus'
import { formatDate } from '@/utils/format'
import userChoose from '@/components/userChoose/index.vue'
const page = ref(1)
const total = ref(0)
const pageSize = ref(10)
const tableData = ref([])
const searchInfo = ref({ teacher_id: undefined, teacher_name: '' })
// 讲师选择
const userChooseRef = ref()
const chooseScene = ref('search')
const openTeacherChoose = (scene) => {
chooseScene.value = scene
userChooseRef.value && userChooseRef.value.open()
}
const clearTeacher = (scene) => {
if (scene === 'search') {
searchInfo.value.teacher_id = undefined
searchInfo.value.teacher_name = ''
} else {
formData.value.teacher_id = undefined
formData.value.teacher_name = ''
}
}
const onChooseTeacher = (data) => {
const id = data.id || data.ID
const name = data.nick_name || data.username || data.name
if (chooseScene.value === 'search') {
searchInfo.value.teacher_id = id
searchInfo.value.teacher_name = name
} else {
formData.value.teacher_id = id
formData.value.teacher_name = name
}
}
const formatPrice = (val) => {
if (val === null || val === undefined) return '-'
const yuan = Number(val) / 100
return `${yuan.toFixed(2)}`
}
const onReset = () => {
searchInfo.value = { teacher_id: undefined }
page.value = 1
getTableData()
}
const onSubmit = () => {
page.value = 1
getTableData()
}
const handleSizeChange = (val) => {
pageSize.value = val
getTableData()
}
const handleCurrentChange = (val) => {
page.value = val
getTableData()
}
const getTableData = async () => {
const params = { page: page.value, pageSize: pageSize.value }
if (searchInfo.value.teacher_id !== undefined && searchInfo.value.teacher_id !== '' && searchInfo.value.teacher_id !== null) {
params.teacher_id = searchInfo.value.teacher_id
}
const res = await getTeacherVipList(params)
if (res.code === 0) {
tableData.value = res.data.list || []
total.value = res.data.total || 0
page.value = res.data.page || page.value
pageSize.value = res.data.pageSize || pageSize.value
}
}
getTableData()
// ============== 新增/编辑/删除 ==============
const dialogVisible = ref(false)
const formRef = ref()
const btnLoading = ref(false)
const editType = ref('create')
const formData = ref({
ID: undefined,
title: '',
teacher_id: undefined,
teacher_name: '',
price: 0,
desc: ''
})
const rules = {
title: [{ required: true, message: '请输入标题', trigger: ['blur','input'] }],
teacher_id: [{ required: true, message: '请选择讲师', trigger: ['change','blur'] }],
price: [{ required: true, message: '请输入价格', trigger: ['blur','input'] }]
}
const resetForm = () => {
formData.value = { ID: undefined, title: '', teacher_id: undefined, teacher_name: '', price: 0, desc: '' }
}
const openEdit = async(type, row) => {
editType.value = type
if (type === 'update' && row?.ID) {
const res = await getDetail({ id: row.ID })
if (res.code === 0) {
const data = { ...res.data }
if (data.price !== null && data.price !== undefined) {
data.price = Number(data.price) / 100
}
formData.value = { ...formData.value, ...data }
}
} else {
resetForm()
}
dialogVisible.value = true
}
const closeDialog = () => {
dialogVisible.value = false
resetForm()
}
const submitForm = () => {
formRef.value?.validate(async(valid)=>{
if (!valid) return
btnLoading.value = true
const payload = { ...formData.value }
// 元 -> 分
if (payload.price !== null && payload.price !== undefined) {
payload.price = Math.round(Number(payload.price) * 100)
}
let res
if (editType.value === 'create') {
res = await createTeacherVip(payload)
} else {
res = await updateTeacherVip(payload)
}
btnLoading.value = false
if (res && res.code === 0) {
ElMessage.success('保存成功')
closeDialog()
getTableData()
}
})
}
const handleDelete = async(row) => {
try {
await ElMessageBox.confirm('确定要删除该记录吗?','提示',{ type:'warning' })
const res = await deleteTeacherVip({ ID: row.ID })
if (res.code === 0) {
ElMessage.success('删除成功')
getTableData()
}
} catch { /* 用户取消 */ }
}
</script>
<style scoped>
</style>