🎨 新增讲师申请审核页,新增vip管理页面
This commit is contained in:
		| @@ -4,4 +4,4 @@ ENV = 'production' | |||||||
| VITE_BASE_API = /api | VITE_BASE_API = /api | ||||||
| VITE_FILE_API = /api | VITE_FILE_API = /api | ||||||
| #下方修改为你的线上ip(如果需要在线使用表单构建工具时使用,其余情况无需使用以下环境变量) | #下方修改为你的线上ip(如果需要在线使用表单构建工具时使用,其余情况无需使用以下环境变量) | ||||||
| VITE_BASE_PATH = https://demo.gin-vue-admin.com | VITE_BASE_PATH = http://lckt.hnlc5588.cn | ||||||
|   | |||||||
							
								
								
									
										42
									
								
								src/api/goods/vip.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/api/goods/vip.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | |||||||
|  | import service from '@/utils/request' | ||||||
|  |  | ||||||
|  | // @tag goods | ||||||
|  | // @summary 获取vip列表 | ||||||
|  | // @param {object} params | ||||||
|  | // @return {object} data | ||||||
|  | // @router get /vip/list | ||||||
|  | export const list = (params) => { | ||||||
|  |     return service({ | ||||||
|  |         url: '/vip/list', | ||||||
|  |         method: 'get', | ||||||
|  |         params | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export const add = (data) => { | ||||||
|  |     return service({ | ||||||
|  |         url: '/vip', | ||||||
|  |         method: 'post', | ||||||
|  |         data | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  | export const edit = (data) => { | ||||||
|  |     return service({ | ||||||
|  |         url: '/vip', | ||||||
|  |         method: 'put', | ||||||
|  |         data | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  | export const del = (data) => { | ||||||
|  |     return service({ | ||||||
|  |         url: '/vip', | ||||||
|  |         method: 'delete', | ||||||
|  |         data | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  | export const detail = (id) => { | ||||||
|  |     return service({ | ||||||
|  |         url: '/vip/'+ id, | ||||||
|  |         method: 'get', | ||||||
|  |     }) | ||||||
|  | } | ||||||
| @@ -1,6 +1,10 @@ | |||||||
| import {getDict} from '@/utils/dictionary' | // 移除 top-level await,改为静态配置 | ||||||
| export let userStatus = await getDict('user-status') | export const userStatus = [ | ||||||
| export  const ORDER_SEARCH_CONFIG = [ |   { label: '启用', value: 1 }, | ||||||
|  |   { label: '禁用', value: 0 } | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | export const ORDER_SEARCH_CONFIG = [ | ||||||
|   { |   { | ||||||
|     type: 'input', |     type: 'input', | ||||||
|     prop:'order_no', |     prop:'order_no', | ||||||
|   | |||||||
							
								
								
									
										329
									
								
								src/view/goods/vip/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										329
									
								
								src/view/goods/vip/index.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,329 @@ | |||||||
|  | <template> | ||||||
|  |   <div> | ||||||
|  |     <div class="gva-search-box"> | ||||||
|  |       <el-form ref="elSearchFormRef" :inline="true" :model="searchInfo" class="demo-form-inline" :rules="searchRule" @keyup.enter="onSubmit"> | ||||||
|  |         <el-form-item label="创建日期" prop="createdAt"> | ||||||
|  |           <template #label> | ||||||
|  |             <span> | ||||||
|  |               创建日期 | ||||||
|  |               <el-tooltip content="搜索范围是开始日期(包含)至结束日期(不包含)"> | ||||||
|  |                 <el-icon><QuestionFilled /></el-icon> | ||||||
|  |               </el-tooltip> | ||||||
|  |             </span> | ||||||
|  |           </template> | ||||||
|  |           <el-date-picker v-model="searchInfo.startCreatedAt" type="datetime" placeholder="开始日期" :disabled-date="time=> searchInfo.endCreatedAt ? time.getTime() > searchInfo.endCreatedAt.getTime() : false"></el-date-picker> | ||||||
|  |           — | ||||||
|  |           <el-date-picker v-model="searchInfo.endCreatedAt" type="datetime" placeholder="结束日期" :disabled-date="time=> searchInfo.startCreatedAt ? time.getTime() < searchInfo.startCreatedAt.getTime() : false"></el-date-picker> | ||||||
|  |         </el-form-item> | ||||||
|  |  | ||||||
|  |         <template v-if="showAllQuery"> | ||||||
|  |           <el-form-item label="名称"> | ||||||
|  |             <el-input v-model="searchInfo.name" placeholder="按名称搜索" clearable /> | ||||||
|  |           </el-form-item> | ||||||
|  |           <el-form-item label="等级"> | ||||||
|  |             <el-select v-model="searchInfo.level" placeholder="按等级搜索" clearable style="width: 180px"> | ||||||
|  |               <el-option :value="1" label="Vip" /> | ||||||
|  |               <el-option :value="2" label="Svip" /> | ||||||
|  |             </el-select> | ||||||
|  |           </el-form-item> | ||||||
|  |         </template> | ||||||
|  |  | ||||||
|  |         <el-form-item> | ||||||
|  |           <el-button type="primary" icon="search" @click="onSubmit">查询</el-button> | ||||||
|  |           <el-button icon="refresh" @click="onReset">重置</el-button> | ||||||
|  |           <el-button link type="primary" icon="arrow-down" @click="showAllQuery=true" v-if="!showAllQuery">展开</el-button> | ||||||
|  |           <el-button link type="primary" icon="arrow-up" @click="showAllQuery=false" v-else>收起</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="openDialog()">新增</el-button> | ||||||
|  |         <el-button icon="delete" style="margin-left: 10px;" :disabled="!multipleSelection.length" @click="onDelete">删除</el-button> | ||||||
|  |       </div> | ||||||
|  |       <el-table | ||||||
|  |         ref="multipleTable" | ||||||
|  |         style="width: 100%" | ||||||
|  |         tooltip-effect="dark" | ||||||
|  |         :data="tableData" | ||||||
|  |         row-key="ID" | ||||||
|  |         @selection-change="handleSelectionChange" | ||||||
|  |         @sort-change="sortChange" | ||||||
|  |       > | ||||||
|  |         <el-table-column type="selection" width="55" /> | ||||||
|  |         <el-table-column align="left" label="日期" prop="CreatedAt" width="180"> | ||||||
|  |           <template #default="scope">{{ formatDate(scope.row.CreatedAt) }}</template> | ||||||
|  |         </el-table-column> | ||||||
|  |         <el-table-column sortable align="left" label="名称" prop="name" min-width="150" /> | ||||||
|  |         <el-table-column sortable align="left" label="等级" prop="level" width="120"> | ||||||
|  |           <template #default="scope">{{ formatLevel(scope.row.level) }}</template> | ||||||
|  |         </el-table-column> | ||||||
|  |         <el-table-column sortable align="left" label="价格" prop="price" width="120"> | ||||||
|  |           <template #default="scope">¥{{ Number(scope.row.price).toFixed(2) }}</template> | ||||||
|  |         </el-table-column> | ||||||
|  |         <el-table-column sortable align="left" label="有效期(天)" prop="expiration" width="120" /> | ||||||
|  |         <el-table-column align="left" label="描述" prop="des" min-width="200" show-overflow-tooltip /> | ||||||
|  |         <el-table-column align="left" label="操作" fixed="right" :min-width="appStore.operateMinWith"> | ||||||
|  |           <template #default="scope"> | ||||||
|  |             <el-button type="primary" link icon="edit" class="table-button" @click="updateRow(scope.row)">编辑</el-button> | ||||||
|  |             <el-button type="primary" link icon="delete" @click="deleteRow(scope.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> | ||||||
|  |  | ||||||
|  |     <el-drawer destroy-on-close :size="appStore.drawerSize" v-model="dialogFormVisible" :show-close="false" :before-close="closeDialog"> | ||||||
|  |       <template #header> | ||||||
|  |         <div class="flex justify-between items-center"> | ||||||
|  |           <span class="text-lg">{{type==='create'?'新增VIP':'编辑VIP'}}</span> | ||||||
|  |           <div> | ||||||
|  |             <el-button :loading="btnLoading" type="primary" @click="enterDialog">确 定</el-button> | ||||||
|  |             <el-button @click="closeDialog">取 消</el-button> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </template> | ||||||
|  |       <el-form :model="formData" label-position="top" ref="elFormRef" :rules="rule" label-width="80px"> | ||||||
|  |         <el-form-item label="名称" prop="name"> | ||||||
|  |           <el-input v-model="formData.name" placeholder="请输入名称" /> | ||||||
|  |         </el-form-item> | ||||||
|  |         <el-form-item label="等级" prop="level"> | ||||||
|  |           <el-select v-model="formData.level" placeholder="请选择等级"> | ||||||
|  |             <el-option :value="1" label="Vip" /> | ||||||
|  |             <el-option :value="2" label="Svip" /> | ||||||
|  |           </el-select> | ||||||
|  |         </el-form-item> | ||||||
|  |         <el-form-item label="价格(元)" prop="price"> | ||||||
|  |           <el-input v-model.number="formData.price" placeholder="请输入价格(数字)" /> | ||||||
|  |         </el-form-item> | ||||||
|  |         <el-form-item label="有效期(天)" prop="expiration"> | ||||||
|  |           <el-input v-model.number="formData.expiration" placeholder="请输入有效期天数(数字)" /> | ||||||
|  |         </el-form-item> | ||||||
|  |         <el-form-item label="描述" prop="des"> | ||||||
|  |           <el-input v-model="formData.des" placeholder="请输入描述" type="textarea" :rows="3" /> | ||||||
|  |         </el-form-item> | ||||||
|  |       </el-form> | ||||||
|  |     </el-drawer> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script setup> | ||||||
|  | import { list as apiList, add as apiAdd, edit as apiEdit, del as apiDel, detail as apiDetail } from '@/api/goods/vip' | ||||||
|  | import { ElMessage, ElMessageBox } from 'element-plus' | ||||||
|  | import { ref, reactive } from 'vue' | ||||||
|  | import { useAppStore } from '@/pinia' | ||||||
|  | import { formatDate } from '@/utils/format' | ||||||
|  |  | ||||||
|  | defineOptions({ | ||||||
|  |   name: 'VipList' | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | const appStore = useAppStore() | ||||||
|  | const btnLoading = ref(false) | ||||||
|  |  | ||||||
|  | const elFormRef = ref() | ||||||
|  | const elSearchFormRef = ref() | ||||||
|  |  | ||||||
|  | const showAllQuery = ref(false) | ||||||
|  |  | ||||||
|  | const formData = ref({ | ||||||
|  |   ID: undefined, | ||||||
|  |   name: '', | ||||||
|  |   level: undefined, | ||||||
|  |   price: undefined, | ||||||
|  |   expiration: undefined, | ||||||
|  |   des: '' | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | const rule = reactive({ | ||||||
|  |   name: [ | ||||||
|  |     { required: true, message: '请输入名称', trigger: ['blur','input'] }, | ||||||
|  |     { whitespace: true, message: '不能只输入空格', trigger: ['blur','input'] } | ||||||
|  |   ], | ||||||
|  |   level: [ { required: true, message: '请输入等级', trigger: ['blur','input'] } ], | ||||||
|  |   price: [ { required: true, message: '请输入价格', trigger: ['blur','input'] } ], | ||||||
|  |   expiration: [ { required: true, message: '请输入有效期', trigger: ['blur','input'] } ] | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | const searchRule = reactive({ | ||||||
|  |   createdAt: [ | ||||||
|  |     { validator: (rule, value, callback) => { | ||||||
|  |       if (searchInfo.value.startCreatedAt && !searchInfo.value.endCreatedAt) { | ||||||
|  |         callback(new Error('请填写结束日期')) | ||||||
|  |       } else if (!searchInfo.value.startCreatedAt && searchInfo.value.endCreatedAt) { | ||||||
|  |         callback(new Error('请填写开始日期')) | ||||||
|  |       } else if (searchInfo.value.startCreatedAt && searchInfo.value.endCreatedAt && (searchInfo.value.startCreatedAt.getTime() === searchInfo.value.endCreatedAt.getTime() || searchInfo.value.startCreatedAt.getTime() > searchInfo.value.endCreatedAt.getTime())) { | ||||||
|  |         callback(new Error('开始日期应当早于结束日期')) | ||||||
|  |       } else { | ||||||
|  |         callback() | ||||||
|  |       } | ||||||
|  |     }, trigger: 'change' } | ||||||
|  |   ], | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | const page = ref(1) | ||||||
|  | const total = ref(0) | ||||||
|  | const pageSize = ref(10) | ||||||
|  | const tableData = ref([]) | ||||||
|  | const searchInfo = ref({}) | ||||||
|  |  | ||||||
|  | const sortChange = ({ prop, order }) => { | ||||||
|  |   const sortMap = { | ||||||
|  |     name: 'name', | ||||||
|  |     level: 'level', | ||||||
|  |     price: 'price', | ||||||
|  |     expiration: 'expiration', | ||||||
|  |   } | ||||||
|  |   let sort = sortMap[prop] || prop | ||||||
|  |   searchInfo.value.sort = sort | ||||||
|  |   searchInfo.value.order = order | ||||||
|  |   getTableData() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const onReset = () => { | ||||||
|  |   searchInfo.value = {} | ||||||
|  |   getTableData() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const onSubmit = () => { | ||||||
|  |   elSearchFormRef.value?.validate(async (valid) => { | ||||||
|  |     if (!valid) return | ||||||
|  |     page.value = 1 | ||||||
|  |     getTableData() | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const handleSizeChange = (val) => { | ||||||
|  |   pageSize.value = val | ||||||
|  |   getTableData() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const handleCurrentChange = (val) => { | ||||||
|  |   page.value = val | ||||||
|  |   getTableData() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const getTableData = async () => { | ||||||
|  |   const res = await apiList({ page: page.value, pageSize: pageSize.value, ...searchInfo.value }) | ||||||
|  |   if (res.code === 0) { | ||||||
|  |     tableData.value = res.data.list | ||||||
|  |     total.value = res.data.total | ||||||
|  |     page.value = res.data.page | ||||||
|  |     pageSize.value = res.data.pageSize | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | getTableData() | ||||||
|  |  | ||||||
|  | const multipleSelection = ref([]) | ||||||
|  | const handleSelectionChange = (val) => { | ||||||
|  |   multipleSelection.value = val | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const type = ref('') | ||||||
|  |  | ||||||
|  | const formatLevel = (level) => { | ||||||
|  |   if (level === 1) return 'Vip' | ||||||
|  |   if (level === 2) return 'Svip' | ||||||
|  |   return level | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const updateRow = async (row) => { | ||||||
|  |   const res = await apiDetail(row.ID) | ||||||
|  |   type.value = 'update' | ||||||
|  |   if (res.code === 0) { | ||||||
|  |     formData.value = { ...res.data } | ||||||
|  |     dialogFormVisible.value = true | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const deleteRow = async (row) => { | ||||||
|  |   ElMessageBox.confirm('确定要删除吗?', '提示', { | ||||||
|  |     confirmButtonText: '确定', | ||||||
|  |     cancelButtonText: '取消', | ||||||
|  |     type: 'warning' | ||||||
|  |   }).then(async () => { | ||||||
|  |     const res = await apiDel({ ID: row.ID }) | ||||||
|  |     if (res.code === 0) { | ||||||
|  |       ElMessage({ type: 'success', message: '删除成功' }) | ||||||
|  |       if (tableData.value.length === 1 && page.value > 1) { | ||||||
|  |         page.value-- | ||||||
|  |       } | ||||||
|  |       getTableData() | ||||||
|  |     } | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const onDelete = async () => { | ||||||
|  |   ElMessageBox.confirm('确定要删除吗?', '提示', { | ||||||
|  |     confirmButtonText: '确定', | ||||||
|  |     cancelButtonText: '取消', | ||||||
|  |     type: 'warning' | ||||||
|  |   }).then(async () => { | ||||||
|  |     const IDs = [] | ||||||
|  |     if (multipleSelection.value.length === 0) { | ||||||
|  |       ElMessage({ type: 'warning', message: '请选择要删除的数据' }) | ||||||
|  |       return | ||||||
|  |     } | ||||||
|  |     multipleSelection.value.forEach(item => IDs.push(item.ID)) | ||||||
|  |     // 后端若支持批量删除可改为批量接口;这里逐个删除 | ||||||
|  |     for (const id of IDs) { | ||||||
|  |       await apiDel({ ID: id }) | ||||||
|  |     } | ||||||
|  |     ElMessage({ type: 'success', message: '删除成功' }) | ||||||
|  |     if (tableData.value.length === IDs.length && page.value > 1) { | ||||||
|  |       page.value-- | ||||||
|  |     } | ||||||
|  |     getTableData() | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const dialogFormVisible = ref(false) | ||||||
|  | const openDialog = () => { | ||||||
|  |   type.value = 'create' | ||||||
|  |   dialogFormVisible.value = true | ||||||
|  | } | ||||||
|  | const closeDialog = () => { | ||||||
|  |   dialogFormVisible.value = false | ||||||
|  |   formData.value = { ID: undefined, name: '', level: undefined, price: undefined, expiration: undefined, des: '' } | ||||||
|  | } | ||||||
|  | const enterDialog = async () => { | ||||||
|  |   btnLoading.value = true | ||||||
|  |   elFormRef.value?.validate(async (valid) => { | ||||||
|  |     if (!valid) return (btnLoading.value = false) | ||||||
|  |     let res | ||||||
|  |     switch (type.value) { | ||||||
|  |       case 'create': | ||||||
|  |         res = await apiAdd(formData.value) | ||||||
|  |         break | ||||||
|  |       case 'update': | ||||||
|  |         res = await apiEdit(formData.value) | ||||||
|  |         break | ||||||
|  |       default: | ||||||
|  |         res = await apiAdd(formData.value) | ||||||
|  |         break | ||||||
|  |     } | ||||||
|  |     btnLoading.value = false | ||||||
|  |     if (res.code === 0) { | ||||||
|  |       ElMessage({ type: 'success', message: '创建/更改成功' }) | ||||||
|  |       closeDialog() | ||||||
|  |       getTableData() | ||||||
|  |     } | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style> | ||||||
|  |  | ||||||
|  | </style> | ||||||
							
								
								
									
										337
									
								
								src/view/user/user/teacherApply.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										337
									
								
								src/view/user/user/teacherApply.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,337 @@ | |||||||
|  | <template> | ||||||
|  |   <div> | ||||||
|  |     <div class="gva-table-box"> | ||||||
|  |       <div class="gva-btn-list"> | ||||||
|  |         <el-button type="primary" @click="refreshList">刷新列表</el-button> | ||||||
|  |       </div> | ||||||
|  |  | ||||||
|  |       <!-- 统计信息 --> | ||||||
|  |       <div style="margin-bottom: 20px; padding: 15px; background: #f8f9fa; border-radius: 8px;"> | ||||||
|  |         <el-row :gutter="20"> | ||||||
|  |           <el-col :span="6"> | ||||||
|  |             <div style="text-align: center;"> | ||||||
|  |               <div style="font-size: 24px; font-weight: bold; color: #409eff;">{{ getStats().total }}</div> | ||||||
|  |               <div style="color: #666;">总申请数</div> | ||||||
|  |             </div> | ||||||
|  |           </el-col> | ||||||
|  |           <el-col :span="6"> | ||||||
|  |             <div style="text-align: center;"> | ||||||
|  |               <div style="font-size: 24px; font-weight: bold; color: #e6a23c;">{{ getStats().pending }}</div> | ||||||
|  |               <div style="color: #666;">待审核</div> | ||||||
|  |             </div> | ||||||
|  |           </el-col> | ||||||
|  |           <el-col :span="6"> | ||||||
|  |             <div style="text-align: center;"> | ||||||
|  |               <div style="font-size: 24px; font-weight: bold; color: #67c23a;">{{ getStats().approved }}</div> | ||||||
|  |               <div style="color: #666;">已通过</div> | ||||||
|  |             </div> | ||||||
|  |           </el-col> | ||||||
|  |           <el-col :span="6"> | ||||||
|  |             <div style="text-align: center;"> | ||||||
|  |               <div style="font-size: 24px; font-weight: bold; color: #f56c6c;">{{ getStats().rejected }}</div> | ||||||
|  |               <div style="color: #666;">已拒绝</div> | ||||||
|  |             </div> | ||||||
|  |           </el-col> | ||||||
|  |         </el-row> | ||||||
|  |       </div> | ||||||
|  |  | ||||||
|  |       <!-- 讲师申请列表 --> | ||||||
|  |       <el-table | ||||||
|  |         :data="tableData" | ||||||
|  |         v-loading="tableLoading" | ||||||
|  |         border | ||||||
|  |         stripe | ||||||
|  |         style="width: 100%" | ||||||
|  |       > | ||||||
|  |         <el-table-column type="index" label="序号" width="60" align="center" /> | ||||||
|  |         <el-table-column prop="nickname" label="申请人" align="center" width="100" /> | ||||||
|  |         <el-table-column prop="phone" label="手机号" align="center" width="120" /> | ||||||
|  |         <el-table-column prop="reason" label="申请理由" align="center" min-width="150" show-overflow-tooltip /> | ||||||
|  |         <el-table-column prop="expectRate" label="期望分成比例" align="center" width="120"> | ||||||
|  |           <template #default="{ row }"> | ||||||
|  |             <span class="text-blue-600 font-bold">{{ row.expectRate }}%</span> | ||||||
|  |           </template> | ||||||
|  |         </el-table-column> | ||||||
|  |         <el-table-column label="申请状态" align="center" width="100"> | ||||||
|  |           <template #default="{ row }"> | ||||||
|  |             <el-tag :type="getStatusTag(row.status).type"> | ||||||
|  |               {{ getStatusTag(row.status).label }} | ||||||
|  |             </el-tag> | ||||||
|  |           </template> | ||||||
|  |         </el-table-column> | ||||||
|  |         <el-table-column prop="CreatedAt" label="申请时间" align="center" width="180"> | ||||||
|  |           <template #default="{ row }"> | ||||||
|  |             {{ formatDate(row.CreatedAt) }} | ||||||
|  |           </template> | ||||||
|  |         </el-table-column> | ||||||
|  |         <el-table-column label="操作" align="center" width="200"> | ||||||
|  |           <template #default="{ row }"> | ||||||
|  |             <el-button | ||||||
|  |               v-if="row.status === 0" | ||||||
|  |               type="success" | ||||||
|  |               size="small" | ||||||
|  |               @click="handleReview(row, 1)" | ||||||
|  |             > | ||||||
|  |               通过 | ||||||
|  |             </el-button> | ||||||
|  |             <el-button | ||||||
|  |               v-if="row.status === 0" | ||||||
|  |               type="danger" | ||||||
|  |               size="small" | ||||||
|  |               @click="handleReview(row, 2)" | ||||||
|  |             > | ||||||
|  |               拒绝 | ||||||
|  |             </el-button> | ||||||
|  |             <el-button | ||||||
|  |               type="primary" | ||||||
|  |               size="small" | ||||||
|  |               @click="handleDetail(row)" | ||||||
|  |             > | ||||||
|  |               详情 | ||||||
|  |             </el-button> | ||||||
|  |           </template> | ||||||
|  |         </el-table-column> | ||||||
|  |       </el-table> | ||||||
|  |  | ||||||
|  |       <!-- 分页 --> | ||||||
|  |       <div style="margin-top: 20px; text-align: right;"> | ||||||
|  |         <el-pagination | ||||||
|  |           v-model:current-page="queryParams.page" | ||||||
|  |           v-model:page-size="queryParams.pageSize" | ||||||
|  |           :page-sizes="[10, 20, 50, 100]" | ||||||
|  |           :total="total" | ||||||
|  |           layout="total, sizes, prev, pager, next, jumper" | ||||||
|  |           @size-change="handleSizeChange" | ||||||
|  |           @current-change="changePage" | ||||||
|  |         /> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |  | ||||||
|  |     <!-- 审核弹窗 --> | ||||||
|  |     <el-dialog | ||||||
|  |       v-model="reviewVisible" | ||||||
|  |       :title="reviewData.status === 1 ? '通过申请' : '拒绝申请'" | ||||||
|  |       width="500px" | ||||||
|  |       destroy-on-close | ||||||
|  |     > | ||||||
|  |       <el-form :model="reviewForm" label-width="100px"> | ||||||
|  |         <el-form-item label="申请人"> | ||||||
|  |           <span>{{ reviewData.nickname }}</span> | ||||||
|  |         </el-form-item> | ||||||
|  |         <el-form-item label="申请理由"> | ||||||
|  |           <span>{{ reviewData.reason }}</span> | ||||||
|  |         </el-form-item> | ||||||
|  |         <el-form-item label="期望分成"> | ||||||
|  |           <span>{{ reviewData.expectRate }}%</span> | ||||||
|  |         </el-form-item> | ||||||
|  |         <el-form-item label="审核备注" v-if="reviewData.status === 2"> | ||||||
|  |           <el-input | ||||||
|  |             v-model="reviewForm.note" | ||||||
|  |             type="textarea" | ||||||
|  |             :rows="3" | ||||||
|  |             placeholder="请输入拒绝理由" | ||||||
|  |             maxlength="200" | ||||||
|  |             show-word-limit | ||||||
|  |           /> | ||||||
|  |         </el-form-item> | ||||||
|  |       </el-form> | ||||||
|  |       <template #footer> | ||||||
|  |         <span class="dialog-footer"> | ||||||
|  |           <el-button @click="reviewVisible = false">取消</el-button> | ||||||
|  |           <el-button | ||||||
|  |             :type="reviewData.status === 1 ? 'success' : 'danger'" | ||||||
|  |             @click="confirmReview" | ||||||
|  |           > | ||||||
|  |             {{ reviewData.status === 1 ? '确认通过' : '确认拒绝' }} | ||||||
|  |           </el-button> | ||||||
|  |         </span> | ||||||
|  |       </template> | ||||||
|  |     </el-dialog> | ||||||
|  |  | ||||||
|  |     <!-- 详情弹窗 --> | ||||||
|  |     <el-dialog | ||||||
|  |       v-model="detailVisible" | ||||||
|  |       title="申请详情" | ||||||
|  |       width="600px" | ||||||
|  |       destroy-on-close | ||||||
|  |     > | ||||||
|  |       <el-descriptions :column="2" border> | ||||||
|  |         <el-descriptions-item label="申请人">{{ detailData.nickname || EMPTY_STR }}</el-descriptions-item> | ||||||
|  |         <el-descriptions-item label="手机号">{{ detailData.phone || EMPTY_STR }}</el-descriptions-item> | ||||||
|  |         <el-descriptions-item label="申请理由" :span="2">{{ detailData.reason || EMPTY_STR }}</el-descriptions-item> | ||||||
|  |         <el-descriptions-item label="期望分成比例">{{ (detailData.expectRate || 0) }}%</el-descriptions-item> | ||||||
|  |         <el-descriptions-item label="申请状态"> | ||||||
|  |           <el-tag :type="getStatusTag(detailData.status).type"> | ||||||
|  |             {{ getStatusTag(detailData.status).label }} | ||||||
|  |           </el-tag> | ||||||
|  |         </el-descriptions-item> | ||||||
|  |         <el-descriptions-item label="申请时间">{{ formatDate(detailData.CreatedAt) }}</el-descriptions-item> | ||||||
|  |         <el-descriptions-item label="更新时间">{{ formatDate(detailData.UpdatedAt) }}</el-descriptions-item> | ||||||
|  |         <el-descriptions-item label="审核备注" :span="2">{{ detailData.note || '无' }}</el-descriptions-item> | ||||||
|  |       </el-descriptions> | ||||||
|  |     </el-dialog> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script setup> | ||||||
|  | import { getTeacherApplyList, reviewTeacherApply } from '@/api/user/index.js' | ||||||
|  | import { ref, onMounted } from 'vue' | ||||||
|  | import { formatDate } from '@/utils/format' | ||||||
|  | import { ElMessage } from 'element-plus' | ||||||
|  |  | ||||||
|  | const tableLoading = ref(true) | ||||||
|  | const tableData = ref([]) | ||||||
|  | const detailVisible = ref(false) | ||||||
|  | const detailData = ref({}) | ||||||
|  | const reviewVisible = ref(false) | ||||||
|  | const reviewData = ref({}) | ||||||
|  |  | ||||||
|  | const queryParams = ref({ | ||||||
|  |   page: 1, | ||||||
|  |   pageSize: 10 | ||||||
|  | }) | ||||||
|  | const total = ref(0) | ||||||
|  |  | ||||||
|  | const EMPTY_STR = '- -' | ||||||
|  |  | ||||||
|  | // 获取状态标签 | ||||||
|  | const getStatusTag = (status) => { | ||||||
|  |   const map = { | ||||||
|  |     0: { type: 'warning', label: '待审核' }, | ||||||
|  |     1: { type: 'success', label: '已通过' }, | ||||||
|  |     2: { type: 'danger', label: '已拒绝' } | ||||||
|  |   } | ||||||
|  |   return map[status] || { type: '', label: EMPTY_STR } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 获取统计数据 | ||||||
|  | const getStats = () => { | ||||||
|  |   const stats = { | ||||||
|  |     total: total.value, | ||||||
|  |     pending: tableData.value.filter(item => item.status === 0).length, | ||||||
|  |     approved: tableData.value.filter(item => item.status === 1).length, | ||||||
|  |     rejected: tableData.value.filter(item => item.status === 2).length | ||||||
|  |   } | ||||||
|  |   return stats | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 获取列表数据 | ||||||
|  | const getList = async () => { | ||||||
|  |   tableLoading.value = true | ||||||
|  |   try { | ||||||
|  |     const res = await getTeacherApplyList(queryParams.value) | ||||||
|  |     if (res.code === 0) { | ||||||
|  |       tableData.value = res.data.list | ||||||
|  |       total.value = res.data.total | ||||||
|  |       console.log('讲师申请数据获取成功:', { | ||||||
|  |         total: res.data.total, | ||||||
|  |         list: res.data.list | ||||||
|  |       }) | ||||||
|  |     } else { | ||||||
|  |       ElMessage.error(res.msg || '获取讲师申请列表失败') | ||||||
|  |     } | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error('获取讲师申请列表失败:', error) | ||||||
|  |     ElMessage.error('获取讲师申请列表失败') | ||||||
|  |   } finally { | ||||||
|  |     tableLoading.value = false | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 查看详情 | ||||||
|  | const handleDetail = (row) => { | ||||||
|  |   detailData.value = { ...row } | ||||||
|  |   detailVisible.value = true | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 审核操作 | ||||||
|  | const handleReview = (row, status) => { | ||||||
|  |   reviewData.value = { ...row, status } | ||||||
|  |   reviewVisible.value = true | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 确认审核 | ||||||
|  | const confirmReview = async () => { | ||||||
|  |   try { | ||||||
|  |     const data = { | ||||||
|  |       ID: reviewData.value.ID, | ||||||
|  |       status: reviewData.value.status, | ||||||
|  |       note: reviewData.value.status === 2 ? reviewForm.value.note : '' | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const res = await reviewTeacherApply(data) | ||||||
|  |     if (res.code === 0) { | ||||||
|  |       ElMessage.success(reviewData.value.status === 1 ? '审核通过成功' : '审核拒绝成功') | ||||||
|  |       reviewVisible.value = false | ||||||
|  |       getList() // 刷新列表 | ||||||
|  |     } else { | ||||||
|  |       ElMessage.error(res.msg || '审核操作失败') | ||||||
|  |     } | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error('审核操作失败:', error) | ||||||
|  |     ElMessage.error('审核操作失败') | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 分页相关 | ||||||
|  | const changePage = (page) => { | ||||||
|  |   queryParams.value.page = page | ||||||
|  |   getList() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const handleSizeChange = (size) => { | ||||||
|  |   queryParams.value.pageSize = size | ||||||
|  |   queryParams.value.page = 1 | ||||||
|  |   getList() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 刷新列表 | ||||||
|  | const refreshList = () => { | ||||||
|  |   getList() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 审核表单 | ||||||
|  | const reviewForm = ref({ | ||||||
|  |   note: '' | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | onMounted(() => { | ||||||
|  |   getList() | ||||||
|  | }) | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | .gva-table-box { | ||||||
|  |   .gva-btn-list { | ||||||
|  |     margin-bottom: 20px; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .el-row { | ||||||
|  |     .el-col { | ||||||
|  |       .el-statistic { | ||||||
|  |         text-align: center; | ||||||
|  |         padding: 20px; | ||||||
|  |         background: white; | ||||||
|  |         border-radius: 8px; | ||||||
|  |         box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); | ||||||
|  |  | ||||||
|  |         .el-statistic__number { | ||||||
|  |           font-size: 24px; | ||||||
|  |           font-weight: bold; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         .el-statistic__label { | ||||||
|  |           color: #666; | ||||||
|  |           margin-top: 8px; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .dialog-footer { | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: flex-end; | ||||||
|  |   gap: 10px; | ||||||
|  | } | ||||||
|  | </style> | ||||||
		Reference in New Issue
	
	Block a user